【问题标题】:Listener for property value changes in a Javascript objectJavascript 对象中属性值更改的侦听器
【发布时间】:2010-11-07 00:30:54
【问题描述】:

浏览 Javascript 文档,我发现 Javascript 对象上的以下两个函数看起来很有趣:

.watch - 监视要分配值的属性并在发生时运行函数。
.unwatch - 删除使用 watch 方法设置的观察点。


更新弃用警告
不要使用watch()unwatch()!这两个 方法仅在 Firefox 58 之前的版本中实现,它们是 Firefox 中已弃用和删除 58+


示例用法:

o = { p: 1 };
o.watch("p", function (id,oldval,newval) {
    console.log("o." + id + " changed from " + oldval + " to " + newval)
    return newval;
});

每当我们改变“p”的属性值时,这个函数就会被触发。

o.p = 2;   //logs: "o.p changed from 1 to 2"

过去几年我一直在研究 Javascript,但从未使用过这些功能。
有人可以提出一些好的用例,这些功能会派上用场吗?

【问题讨论】:

  • 这些仅适用于基于 Gecko 的浏览器,例如 Mozilla Firefox。 Internet Explorer 通过对象公开了一个类似的方法,称为 onpropertychange。

标签: javascript browser dom-events


【解决方案1】:

现在是 2018 年,这个问题的答案有点过时了:

今天,您现在可以使用Proxy 对象来监控(并拦截)对对象所做的更改。它是专为 OP 试图做的事情而设计的。这是一个基本示例:

var targetObj = {};
var targetProxy = new Proxy(targetObj, {
  set: function (target, key, value) {
      console.log(`${key} set to ${value}`);
      target[key] = value;
      return true;
  }
});

targetProxy.hello_world = "test"; // console: 'hello_world set to test'

Proxy 对象的唯一缺点是:

  1. Proxy 对象在旧版浏览器(例如 IE11)中不可用,polyfill 无法完全复制 Proxy 功能。
  2. 代理对象与特殊对象(例如,Date)的行为并不总是如预期一样——Proxy 对象最好与普通对象或数组配对。

如果您需要观察对嵌套对象所做的更改,则需要使用专门的库,例如 Observable Slim(我编写的)。它的工作原理是这样的:

var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
    console.log(JSON.stringify(changes));
});

p.testing.blah = 42; // console:  [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]

【讨论】:

  • 不确定我是否理解您的代理示例。您写道,它可以拦截对目标对象的更改,但在您的示例中,您通过代理而不是目标对象修改属性值。目前尚不清楚如何使用 this 拦截对目标对象的更改。
  • @Johncl 这就是代理对象的工作方式:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 行为是否最好用“拦截”、“虚拟化”、“陷阱”或其他词来描述,这在某种程度上取决于解释。
  • 但这与听原帖者要求的属性变化完全不同。如果您有一个具有某些属性的对象,并且您将该对象传递到另一个黑匣子 - 但黑匣子想要侦听此对象中属性的更改并对其采取行动,那么上面的代理对象将无济于事。
  • @Johncl 这并没有完全不同——使用Proxy,您可以获得完全相同的最终结果。但是,是的,您是对的,您没有观察到对目标对象直接所做的更改——名称Proxy 暗示了这一点。
  • @ElliotB。你怎么能听 window.test obj ?例如,当有人更改 window.test 然后控制台记录它
【解决方案2】:

手表的真正设计目的是验证属性值。例如,您可以验证某物是否为整数:

obj.watch('count', function(id, oldval, newval) {
    var val = parseInt(newval, 10);
    if(isNaN(val)) return oldval;
    return val;
});

您可以使用它来验证字符串长度:

obj.watch('name', function(id, oldval, newval) {
    return newval.substr(0, 20);
});

但是,这些仅在最新版本的 SpiderMonkey javascript 引擎中可用。如果您正在使用 Jaxer 或嵌入 SpiderMonkey 引擎,但在您的浏览器中尚未真正可用(除非您是使用FF3)。

【讨论】:

  • 请注意,现在可在所有现代浏览器中使用。建议:改用getter和setter。
  • 这些方法,watchunwatch 已被弃用。请不要使用它。
【解决方案3】:

查看Object.definePropertyObject.prototype.\__defineGetter__(或 \__defineSetter__)查看此功能的发展方向。

Object.defineProperty 应该很快就会在所有当代浏览器中可用。

【讨论】:

    【解决方案4】:

    您可以查看Javascript Propery Events 库。这是一个小型库,扩展了Object.defineProperty,带有一些事件调用者,我最近制作的。它添加了一些on[event] 属性,可以像HTML-Objects 的on[event] 属性一样使用。它还有一个简单的类型检查,如果失败则调用onerror 事件。

    使用您的代码会产生如下结果:

    var o = {}
    Object.defineProperty(o, "p", {
        value:1,
        writable:true,
        onchange:function(e){
            console.log("o." + e.target + " changed from " + e.previousValue + " to " + e.returnValue);
        }
    })
    

    【讨论】:

      【解决方案5】:

      Object.defineProperty

      Promise

      仅当目标浏览器不支持 Promise 时删除 Promise 并保留回调

      重要:

      1) 注意使用 promise 时的异步行为。

      2) Object.defineProperty 不会触发回调,只有赋值运算符'='才会触发

      Object.onPropertySet = function onPropertySet(obj, prop, ...callback_or_once){
          let callback, once;
          for(let arg of callback_or_once){
              switch(typeof arg){
              case "function": callback = arg; break;
              case "boolean": once = arg; break;
              }
          }
      
      
          let inner_value = obj[prop];
          let p = new Promise(resolve => Object.defineProperty(obj, prop, {
              configurable: true,
              // enumerable: true,
              get(){ return inner_value; },
              set(v){
                  inner_value = v;
                  if(once){
                      Object.defineProperty(obj, prop, {
                          configurable: true,
                          // enumerable: true,
                          value: v,
                          writable: true,
                      });
                  }
                  (callback || resolve)(v);
              }
          }));
          if(!callback) return p;
      };
      
      // usage
      let a = {};
      function sayHiValue(v){ console.log(`Hi "${v}"`); return v; }
      
      // do
      Object.onPropertySet(a, "b", sayHiValue);
      a.b = 2; // Hi "2"
      a.b = 5; // Hi "5"
      
      // or
      Object.onPropertySet(a, "c", true).then(sayHiValue).then(v => {
          console.log(a.c); // 4 // because a.c is set immediatly after a.c = 3
          console.log(v); // 3 // very important: v != a.c if a.c is reassigned immediatly
          a.c = 2; // property "c" of object "a" is re-assignable by '=' operator
          console.log(a.c === 2); // true
      });
      a.c = 3; // Hi "3"
      a.c = 4; // (Nothing)
      

      【讨论】:

        【解决方案6】:

        这是一个简单的替代方法,可以仅使用 getter/setter 来监视/取消监视对象字面量。每当p属性改变时,任何函数都可以被调用。

        var o = {
         _p: 0,
          get p() {
            return this._p;
          },
          set p(p){    
            console.log(`Changing p from ${this._p} to ${p}`);
            this._p = p;    
            return this._p;
          }
        }
        
        o.p = 4;
        o.p = 5;

        【讨论】:

        • 不会捕获arrayobjects的变化。前任。 - o.p = [1,2,3] 然后o.p.length = 1
        • 此答案仅适用于对象文字,如问题所示。我不建议将其用于对象文字以外的任何东西。
        【解决方案7】:

        你可以使用 setInterval

        Object.prototype.startWatch = function (onWatch) {
        
            var self = this;
        
            if (!self.watchTask) {
                self.oldValues = [];
        
                for (var propName in self) {
                    self.oldValues[propName] = self[propName];
                }
        
        
                self.watchTask = setInterval(function () {
                    for (var propName in self) {
                        var propValue = self[propName];
                        if (typeof (propValue) != 'function') {
        
        
                            var oldValue = self.oldValues[propName];
        
                            if (propValue != oldValue) {
                                self.oldValues[propName] = propValue;
        
                                onWatch({ obj: self, propName: propName, oldValue: oldValue, newValue: propValue });
        
                            }
        
                        }
                    }
                }, 1);
            }
        
        
        
        }
        
        var o = { a: 1, b: 2 };
        
        o.startWatch(function (e) {
            console.log("property changed: " + e.propName);
            console.log("old value: " + e.oldValue);
            console.log("new value: " + e.newValue);
        });
        

        【讨论】:

          猜你喜欢
          • 2014-11-10
          • 2021-10-06
          • 1970-01-01
          • 1970-01-01
          • 2019-05-13
          • 2010-11-08
          • 2015-05-10
          • 2011-04-26
          相关资源
          最近更新 更多