在循环中执行$ watch

时间:2017-11-07 17:29:20

标签: javascript angularjs angularjs-directive

我正在读一本我遇到过以下代码的书(有一个名为product的2D数组有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评估了三个观察者函数,它们在循环终止后引用数据[i]”。 “到时间循环终止时,i的值为3,这意味着所有三个观察者函数都试图访问 数据数组中不存在的对象,这就是指令不起作用的原因“。

观察者功能在循环内部并从范围调用。$ watch为什么会这样?

2 个答案:

答案 0 :(得分:0)

这是由于javascript的变量范围有效。

您可以在JavaScript closure inside loops – simple practical example

上阅读完整的答案
  

嗯,问题是每个匿名函数中的变量i都绑定到函数外的同一个变量。

同样适用于javascript观察者,这些观察者应用于不同的tick。 tick表示下一个事件循环执行。 要了解更多相关信息,请阅读有关角度消化周期的更多信息。

因此,在执行函数之前,变量的值已经改变。

简单解决方案1 ​​ - 特定于角度

特定于角度的简单解决方案是为$watch构造字符串表达式。

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

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

或类似的东西应该有用。我需要更多信息来提供一个运行示例。

简单解决方案2 - 无角度特定

另一种不是针对角度的解决方案是将其绑定到不同的范围。所以如果循环的第一行是

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);
        });
    }

答案 1 :(得分:0)

这是因为这些函数将与闭包一起使用,闭包表示执行时的外部JS函数作用域的状态。这就是为什么在循环停止后变量i将引用最后指定的值:3。

要使变量i等于迭代时刻的值,您需要创建通过附加函数包装它的中间范围:

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

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