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

IT技术 javascript browser dom-events
2021-03-05 19:37:28

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

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


更新弃用警告
不要使用watch()unwatch()这两种方法只在执行的Firefox版本之前58,他们弃用,在去除火狐 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,但从未使用过这些功能。
有人可以抛出一些很好的用例来使这些功能派上用场吗?

6个回答

现在是 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}}]
@Johncl 这就是 Proxy 对象的工作原理:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...行为是否最好用“拦截”、“虚拟化”、“陷阱”或别的东西有点取决于解释。
2021-04-19 19:37:28
但这与聆听原始海报要求的属性更改完全不同。如果您有一个具有某些属性的对象,并且您将该对象传递到另一个黑匣子中 - 但黑匣子想要侦听此对象中属性的更改并对其进行操作,则上面的 Proxy 对象根本没有帮助。
2021-05-02 19:37:28
@ElliotB。你怎么能听 window.test obj ?例如,当有人更改 window.test 然后控制台记录它
2021-05-06 19:37:28
@Johncl 这并不是完全不同的——Proxy你可以达到完全相同的最终结果。但是,是的,您是对的,您没有观察直接对目标对象所做的更改——名称暗示了这一点Proxy
2021-05-10 19:37:28
不确定我是否理解您的代理示例。您写道它可以拦截对目标对象的更改,但在您的示例中,您通过代理而不是目标对象修改属性值。目前尚不清楚如何使用此拦截对目标对象的更改。
2021-05-16 19:37:28

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

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。
2021-04-24 19:37:28
这些方法,watchunwatch已被弃用。请不要使用它。
2021-05-11 19:37:28

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

Object.defineProperty 现在应该可以在所有现代浏览器中使用。

您可以查看Javascript 属性事件库。Object.defineProperty是我最近制作的一个带有一些事件调用程序的小型库它添加了一些on[event]可以像on[event]HTML 对象属性一样使用的属性。它还有一个简单的类型检查,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);
    }
})

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)