$watch 内部循环的执行

execution of $watch inside loop

我正在读一本书,其中遇到了以下代码(有一个名为 products 的二维数组,它有 3 行)-

html

<div unordered-list="products" list-property="price | currency"></div>

和 JS

angular.module("exampleApp", [])
.directive("unorderedList", function () {

return function (scope, element, attrs) {

    var data = scope[attrs["unorderedList"]];
    var propertyExpression = attrs["listProperty"];

    if (angular.isArray(data)) {

        var listElem = angular.element("<ul>");
        element.append(listElem);

        for (var i = 0; i < data.length; i++) {

            var itemElement = angular.element('<li>');
            listElem.append(itemElement);

            var watcherFn = function (watchScope) {             
                                return watchScope.$eval(propertyExpression, data[i]);               
                            }

            scope.$watch(watcherFn, function (newValue, oldValue) {
                itemElement.text(newValue);
            });
        }
    }
}
})

书上说,"AngularJS evaluates the three watcher functions, which refer to data[i] after the loop terminates"。 “到时间循环终止时,i 的值为 3,这意味着所有三个观察者函数都尝试访问一个 数据数组中的对象不存在,这就是该指令不起作用的原因。

watcher 函数在循环内部并从作用域调用。$watch 为什么会这样?

这是由于 javascript 的变量作用域的工作原理。

您可以在以下位置阅读完整的答案:JavaScript closure inside loops – simple practical example

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

同样适用于javascript个watchers,应用于不同的tick。 tick 表示下一个事件循环执行。 要了解更多信息,请阅读有关 angular 的摘要周期的更多信息。

所以在函数执行之前变量的值已经改变了。

简单解决方案 1 - 特定于 angular

针对 angular 的一个简单解决方案是为 $watch 构造一个字符串表达式。

所以 $scope.$watch('products[1].price | currency', ..) 应该可以。 根据您的代码段将其转换为变量

 $scope.$watch(`${attrs['unorderdList']}[${i}].${attrs['listProperty']}`, function(newValue){ ... }) 

或者类似的东西应该可以。我将需要更多信息来提供 运行 示例。

简单解决方案 2 - noneangular具体

另一种解决方案(并非特定于 angular)是将其绑定到不同的范围。所以如果循环的第一行是

for (let i = 0; i < data.length; i++) {

只需将 var 替换为 let - 就可以了。 该解决方案利用了新的 JavaScript let 语法,它具有块作用域而不是函数作用域。

   for (let i = 0; i < data.length; i++) {
        var itemElement = angular.element('<li>');
        listElem.append(itemElement);

        var watcherFn = function (watchScope) {             
                            return watchScope.$eval(propertyExpression, data[i]);               
                        }

        scope.$watch(watcherFn, function (newValue, oldValue) {
            itemElement.text(newValue);
        });
    }

发生这种情况是因为这些函数将与表示执行时外部 JS 函数作用域状态的闭包一起工作。这就是为什么在循环停止后变量 i 将引用最后分配的值:3.

要使变量 i 在迭代时等于值,您需要创建中间作用域并通过附加函数包装它:

var watcherFn = (function(i)
  return function (watchScope) {             
      return watchScope.$eval(propertyExpression, data[i]);               
  }
}(i));

您可以在这个答案中阅读更多关于 JS 中的闭包的信息: How do JavaScript closures work?