如何知道哪个对象属性发生了变化?

时间:2014-04-01 15:13:55

标签: javascript angularjs

我有一个包含10个物体的地图。每个对象都有10个属性val1,...,val10。现在要检查地图中特定对象的特定属性是否已更改,我需要编写$ scope。$ watch 100次。如何观看整个地图/ JSobject并确切知道哪个属性发生了变化?

/*watch individual property*/
            $scope.$watch('map[someid].val1', function(new_val, old_val)
            {
                //do something
            });

    /*watch entire object, how would this help me?*/
            $scope.$watch('map', function(new_val, old_val)
            {
                //do something
            }, true);

7 个答案:

答案 0 :(得分:5)

由于之前的答案似乎表明AngularJS没有为您的问题提供有针对性的解决方案,让我建议使用vanilla JS。

AFAIK如果你想实现对象的观察者,你需要某种getter / setter模式(aka。Mutator method)。我对AngularJS一无所知,但我确信他们必须使用类似的东西。有多种方法可以在JavaScript中实现getter和setter(参见上面链接的Wikipedia页面),但一般来说你有三个主要选择:

  • 使用外部处理程序(一个完全隐藏内部数据结构,只提供读写API的对象)
  • 使用Java风格的getSomething和setSomething方法
  • 使用ECMAScript 5.1中引入的原生JavaScript属性getter和setter

出于某种原因,我已经看到了更多通过前两种方法做到这一点的例子,但另一方面,我看到了一个用第三种方法构建的相当复杂和漂亮的API的例子。我将给你一个使用Object.defineProperty通过第三种方法解决问题的例子:

我们的想法是提供一个观察者功能,然后将其作为属性设置器的一部分进行调用:

function watcher(objectIndex, attributeName) {
  console.log('map[' + objectIndex + '].' + attributeName + ' has changed');
}

Object.defineProperty有点像为对象定义属性的复杂方式。所以当你通常写的时候

obj.val1 = "foo";

你要写

Object.defineProperty(obj, 'val1', {
  get: function ()         { /* ... */ },
  set: function (newValue) { /* ... */ },
  // possible other parameters for defineProperty
})

IE中。您可以定义在读取和写入obj.val1时要执行的操作。这使您可以将您的观察者功能称为setter的一部分:

Object.defineProperty(obj, 'val1', {
  get: function ()         { /* ... */ },
  set: function (newValue) {
    // set the value somewhere; just don't use this.val1
    /* call the */ watcher(/* with args, of course */)
  }
})

对于非常简单的事情来说,似乎有很多代码行,但是您可以使用函数式编程技术和闭包来构建程序,这样它实际上并不是那么复杂。所以从本质上讲,尽管这可能看起来不像是一个" AngularJS-way"在做事情上,手工实施的开销实际上并不多。

以下是针对您的问题的完整代码示例:http://codepen.io/cido/pen/DEpLj/

在示例中,观察器功能很难编码为数据结构实现的一部分。但是,如果您需要在多个位置重复使用此代码,则没有什么能阻止您使用例如扩展解决方案。 addEventListener-stylish hooks等。

由于这种方法非常使用闭包,如果你真的处理大量数据,它可能会有一些内存含义。但是,在大多数用例中,您不必担心这类事情。而另一方面,所有解决方案都基于"深度平等"支票也不会是超级内存或快速的。

答案 1 :(得分:4)

目前AngularJS中没有任何内容可以帮助您处理此问题。

您必须自己实施或找到适合您的库。

如果您想使用任何内置观察器功能自行实现它,有三个选项:

<强> 1。 $ watch with false as third parameter:比较以供参考     平等。在你的情况下不是一个选项。

<强> 2。 $ watchCollection:将浅看地图中的十个对象。不是一种选择。

第3。 $ watch使用true作为第三个参数:将使用angular.equals比较对象的相等性。适用于您的用例。

下面是一个示例,它将整个地图的观察者注册为true作为第三个参数,并手动进行比较。

注意:根据您的应用程序(例如绑定次数和对$ digest的调用),观看大型复杂对象可能会对内存和性能产生不利影响。由于此示例使用另一个angular.copy($ watch至少在内部使用一个),因此会对性能产生更大影响。

$scope.map = {};

for (var i = 0; i < 3; i++) {
  $scope.map[i] = {
    property1: 'value1',
    property2: 'value2',
    property3: 'value3'
  };
}

var source = [];

var updateSource = function(newSource) {
  angular.copy(newSource, source);
};

updateSource($scope.map);

$scope.$watch("map", function(newMap, previousMap) {

  if (newMap !== previousMap) {

    for (var id in newMap) {

      if (angular.equals(newMap[id], source[id])) continue;

      var newObject = newMap[id];
      var previousObject = source[id];

      for (var property in newObject) {

        if (!newObject.hasOwnProperty(property) ||
          angular.equals(newObject[property], previousObject[property])) continue;

        var cd = $scope.changeDetection = {};

        cd.changedObjectId = id;
        cd.previousObject = previousObject;
        cd.newObject = newObject;
        cd.changedProperty = property;
        cd.previousPropertyValue = previousObject[property];
        cd.newPropertyValue = newObject[property];
      }

      updateSource(newMap);
    }
  }
}, true);

演示http://plnkr.co/edit/MtfJTY3D4osUtXtIcCiR?p=preview

答案 2 :(得分:2)

我会尝试使用角度文档

$scope.$watchCollection(collection, function(newValue, oldValue) {
 //do stuff when collection changes
});

http://docs.angularjs.org/api/ng/type/ $ rootScope.Scope

答案 3 :(得分:2)

$ watch有一个不太知名的第三个参数,如果为true,将使用angular.equals()而不是使用===来测试相等性。现在angular.equals()确实考虑两个对象等于一组条件中的任何一个是真的,其中一个是:

  
      
  • 通过将它们与angular.equals进行比较,两个对象或值都属于同一类型,并且它们的所有属性都相同。
  •   

这基本上是说它会进行递归(深度)比较,这就是你需要的。因此,检测任何嵌套属性的更改都非常简单:

scope.$watch('map', function (newmap,oldmap) { 
  // detect what changed manually with your own recursive function
}, true);

这不会告诉你究竟发生了什么变化,所以你需要自己做这个部分。考虑直接将您的视图绑定到map,并确保所有更改都发生在角度$ digest周期内,或者将它们包含在$ apply中,以便更轻松地解决问题。如果您的用例必须使用$ watch,您还可以递归迭代对象并在那里设置单独的手表。

答案 4 :(得分:1)

我知道我迟到了,但我需要类似的东西,以上的答案都没有帮助。

我使用Angular的$ watch函数来检测变量的变化。我不仅需要知道属性是否在变量上发生了变化,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。

以下是代码:https://jsfiddle.net/rv01x6jo/

以下是如何使用它:

$scope.$watch($scope.MyObjectToWatch, function(newValue, oldValue) {

    // To only return the difference
    var difference = diff(newValue, oldValue);  

    // To exclude certain properties from being evaluated (i.e. temporary/calculated/meta properties)
    var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);

    if (Object.keys(difference).length == 0) 
        return;  // Nothing has changed
    else
        doSomething();  // Something has changed

});

通过检查Object.keys(difference)数组,您可以看到所有已更改的属性。

希望这有助于某人。

答案 5 :(得分:0)

一种方法是编写一个小函数来展平对象nowthen并形成字符串。从那时起,比较字符串并找到不匹配点非常简单,这是对象发生变化的点。

毋庸置疑,所有对象属性都应该公开

var flatten = function (key, obj) {
    var ret = '';
    $.each(obj, function (_key, val) {
        if (typeof val == 'object') {
            ret += flatten(key + '.' + _key, val);
        } else {
            ret += ((key ? key + '.' : '') + _key + ':' + val + ',');
        }
    });
    return ret;
};

Fiddle

其次是:

$scope.$watch(obj, function (now, then) {
    var nowFlat = flatten(now),
        thenFlat = flatten(then);
    compareStrings(nowFlat, thenFlat); // add custom string compare function here
}, true)

作为性能改进,您可以随时缓存nowFlat,并在下次触发thenFlat时将其用作$watch

答案 6 :(得分:0)

我知道我迟到了,但是我需要类似上面的答案没有用的东西。

我使用Angular的$ watch函数来检测变量的变化。我不仅需要知道属性是否在变量上发生了变化,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。

以下是代码:https://jsfiddle.net/rv01x6jo/

以下是如何使用它:

$scope.$watch($scope.MyObjectToWatch, function(newValue, oldValue) {

    // To only return the difference
    var difference = diff(newValue, oldValue);  

    // To exclude certain properties from being evaluated (i.e. temporary/calculated/meta properties)
    var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);

    if (Object.keys(difference).length == 0) 
        return;  // Nothing has changed
    else
        doSomething();  // Something has changed

});

希望这有助于某人。