Javascript 对象中属性值更改的侦听器

Posted

技术标签:

【中文标题】Javascript 对象中属性值更改的侦听器【英文标题】:Listener for property value changes in a Javascript object 【发布时间】: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。 【参考方案1】:

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

Object.watch 和 Object.observe 均已弃用,不应使用。 onPropertyChange 是一个 DOM 元素事件处理程序,仅适用于某些版本的 IE。 Object.defineProperty 允许您使对象属性不可变,这将允许您检测尝试的更改,但它也会阻止任何更改。 Defining setters and getters 有效,但需要大量设置代码,并且在您需要删除或创建新属性时效果不佳。

今天,您现在可以使用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 对象的唯一缺点是:

    Proxy 对象在旧版浏览器(例如 IE11)中不可用,polyfill 无法完全复制 Proxy 功能。 代理对象与特殊对象(例如,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);
);

【讨论】:

以上是关于Javascript 对象中属性值更改的侦听器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 javascript 中更改 HTML 对象元素数据属性值

在Javascript中推送不重复的数组并更改对象的属性值?

从 Javascript 中的事件侦听器调用访问对象的属性

在Javascript中来回更改布尔值

如何从类属性 TypeScript 中侦听值更改 - Angular

如何在javascript中更改数组对象(关于谷歌地图)的值属性?