在JavaScript对象中,属性值更改的侦听器

发布于 2025-01-24 23:40:09 字数 916 浏览 6 评论 0 原文

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

.watch - 手表要分配一个值并在发生时运行一个函数。
.unwatch - 使用手表方法删除设置的观察点。


更新折旧警告
请勿使用 Watch() untatch()!这两个 方法仅在版本 58 之前在 firefox 中实现,它们是 在 firefox 58+

中删除并删除

用法

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

firefox

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

58+示例 过去几年,从未使用过这些功能。
有人可以扔一些好的用例,这些功能会派上用场吗?

Going through Javascript documentation, I found the following two functions on a Javascript object looks interesting:

.watch - Watches for a property to be assigned a value and runs a function when that occurs.
.unwatch - Removes a watchpoint set with the watch method.


UPDATE: Deprecation warning
Do not use watch() and unwatch()! These two
methods were implemented only in Firefox prior to version 58, they're
deprecated and removed in Firefox 58+


Sample usage:

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

Whenever we change the property value of "p", this function gets triggered.

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

I am working on Javascript for the past few years and never used these functions.
Can someone please throw some good use cases where these functions will come in handy?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

染火枫林 2025-01-31 23:40:09

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

今天,您现在可以使用对象要监视对象进行的更改。它是为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'

代理对象的唯一缺点是:

  1. 代理对象在较旧的浏览器(例如IE11)和 polyfill 无法完全复制代理函数。
  2. 代理对象并不总是与特殊对象(例如, date )的预期行为 - 代理对象最好与普通对象或数组配对。

如果您需要观察对嵌套对象进行的更改,则需要使用 可观察的纤细 (我撰写了)。它是这样的工作:

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}}]

It's now 2018 and the answers to this question are a bit outdated:

  • Object.watch and Object.observe are both deprecated and should not be used.
  • onPropertyChange is a DOM element event handler that only works in some versions of IE.
  • Object.defineProperty allows you to make an object property immutable, which would allow you to detect attempted changes, but it would also block any changes.
  • Defining setters and getters works, but it requires a lot of setup code and it does not work well when you need to delete or create new properties.

Today, you can now use the Proxy object to monitor (and intercept) changes made to an object. It is purpose built for what the OP is trying to do. Here's a basic example:

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'

The only drawbacks of the Proxy object are:

  1. The Proxy object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy functionality.
  2. Proxy objects do not always behave as expected with special objects (e.g., Date) -- the Proxy object is best paired with plain Objects or Arrays.

If you need to observe changes made to a nested object, then you need to use a specialized library such as Observable Slim (which I authored). It works like this:

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}}]
猥︴琐丶欲为 2025-01-31 23:40:09

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

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或Jaxer或嵌入SpidermonKey引擎,但浏览器中尚未真正可用(除非您使用的是FF3)。

What watch is really designed for is validation of property values. For example you could validate that something is an integer:

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

You could use it to validate string length:

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

However, these are only available in the latest versions of the SpiderMonkey javascript engine. Great if you are using Jaxer or embedding the SpiderMonkey engine, but not really available in your browser yet (unless you are using FF3).

花开柳相依 2025-01-31 23:40:09

查看Object.prototype.\__defineGetter__ (or \ __定义__ )查看此功能在哪里。

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

Check out Object.defineProperty and
Object.prototype.\__defineGetter__ (or \__defineSetter__ ) to see where this functionality is heading.

Object.defineProperty should be available in all contemporary browsers real soon now.

极度宠爱 2025-01-31 23:40:09

这是一种简单的替代方法,可以使用Getter/setter观看/UNDATCH字面的对象。每当更改 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;

Here's a simple alternative to watch/unwatch for an object literal using just a getter/setter. Whenever the p property is changed, any function can be called.

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;

我纯我任性 2025-01-31 23:40:09

您可以看一下 javascript Prepery Events 库。这是我最近制作的一些事件呼叫者的小型库。它在[event] 属性上添加了一些,这些属性可以像[event] html-objects上的上的一样。它还具有简单的类型检查,如果失败,该检查将调用 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);
    }
})

You could take a look at the Javascript Propery Events library. It's a small library extending Object.defineProperty with some event callers, that I made recently. It adds a few on[event] properties that can be used like the on[event] properties of HTML-Objects. It also has a simple type check, which calls the onerror event if it fails.

Taking your code it would result in something like this:

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);
    }
})
话少情深 2025-01-31 23:40:09

​=“ https://developer.mozilla.org/en-us/docs/web/javascript/Reference/global_objects/promise/promise” rel =“ nofollow noreferrer”> Promise> Promise>

Promise>在目标浏览器中支持的

很重要:

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

2)object.defineProperty不会触发回调,只有分配操作员'='dim

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)

Object.defineProperty

Promise

remove Promise and keep callback only if Promise is not supported in your target browser

Important:

1) Be aware of async behaviour on using promise.

2) Object.defineProperty doesn't trigger the callback, only assign operator '=' does

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)
铜锣湾横着走 2025-01-31 23:40:09

而不是使用代理,因此不是直接在原始对象上“聆听”,而是可以使用这样的东西:

const obj = {};

obj.state = {
  isLoaded: false,
  isOpen: false,
};

obj.setState = (newState) => {
  // Before using obj.setState => result Object { isLoaded: false, isOpen: false }
  console.log(obj.state); 
  obj.state = { ...obj.state, ...newState };
  // After using obj.setState ex. obj.setState({new:''}) => result Object { isLoaded: false, isOpen: false, new: "" }
  console.log(obj.state); 
};

该方法或多或少是在ReactJ中工作的方式,而是在此示例中具有源对象< strong> obj.state 和setter obj.setstate

您不必像我一样将两个都放在一个对象中,但似乎是组织事物的好方法

Instead of using Proxy and therefore not "listening" directly on the original object you can use something like this:

const obj = {};

obj.state = {
  isLoaded: false,
  isOpen: false,
};

obj.setState = (newState) => {
  // Before using obj.setState => result Object { isLoaded: false, isOpen: false }
  console.log(obj.state); 
  obj.state = { ...obj.state, ...newState };
  // After using obj.setState ex. obj.setState({new:''}) => result Object { isLoaded: false, isOpen: false, new: "" }
  console.log(obj.state); 
};

That approach is more or less how it would work in ReactJS, you have source object in this example obj.state and setter obj.setState

You don't have to put both in one object like I did but seems like a good way to organize things

夜还是长夜 2025-01-31 23:40:09

据我所知,没有答案与OP的问题有关:

有人可以扔一些好的用例,这些功能会派上用场吗?

我的用例:想象一下现有库中的一个对象,该对象在实例化中接收 options -Object,在创建后也可以使用。这些配置值中的某些可以在该对象的生命周期内更改,并且下次调用特定方法时,这些值将使用这些新的配置值。

但是,现在我找到了只有在实例化期间提供的效果的选项,即b/c会注册某些事件听众 - 更改后更新值,但不会改变行为。但是我确实需要在运行时打开或关闭此行为。为了参数,让我们调用该选项 dosomethingonkeydown

如何向不会改变API的该库中添加拉动请求?

我构建

  // … we're somewhere in the constructor
  this.options = new Proxy(this.options, {
    set: function (target, key, value) {
      if (key == 'doSomethingOnKeyDown' && value !== target[key]) {
        value === true 
          ? window.addEventListener('keydown', keydownHandler) 
          : window.removeEventListener('keydown', keydownHandler) 
      }
      
      target[key] = value // default behavior: store the value
      
      return true // Indicate success
    }
  })

”现在,任何人都可以在运行时更改对象上的此选项,即 myinstance.options.options.dosomethingonkeydown = false ,它将相应地更新其行为 - 并且对象的API保持不变。

As far as i can read, no answer relates to the OP's question:

Can someone please throw some good use cases where these functions will come in handy?

My use case: Imagine an object in an existing library that receives an options-object on instantiation, which is also available after it was created. Some of these config values can be altered during lifetime of that object, and on calling specific methods the next time, those will use these new config values.

However, now i have found an option that only has an effect if supplied during instantiation, i.e. b/c it will register certain event listeners – changing it afterwards updates the value but doesn't change the behavior. But i really need to turn this behavior on or off during runtime. Lets call that option doSomethingOnKeyDown for the sake of the argument.

How can i add a pull request to that library that doesn't fundamentally change the API?

I build a proxy:

  // … we're somewhere in the constructor
  this.options = new Proxy(this.options, {
    set: function (target, key, value) {
      if (key == 'doSomethingOnKeyDown' && value !== target[key]) {
        value === true 
          ? window.addEventListener('keydown', keydownHandler) 
          : window.removeEventListener('keydown', keydownHandler) 
      }
      
      target[key] = value // default behavior: store the value
      
      return true // Indicate success
    }
  })

Et voilá: now anyone can change this option on the object during runtime, i.e. myInstance.options.doSomethingOnKeyDown = false and it will update its behavior accordingly – and the API of the object stays the same.

川水往事 2025-01-31 23:40:09

您可以使用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);
});

You can use 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);
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文