监听 JavaScript 中的变量变化

IT技术 javascript jquery dom-events
2021-01-19 00:40:22

当某个变量的值发生变化时,是否有可能在 JS 中触发一个事件?接受 JQuery。

6个回答

这个问题最初发布于 2009 年,现有的大多数答案要么过时、无效,要么需要包含大型臃肿的库:

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

 
@Cristiano 我想 Elliot 想说的是您可以使用代理而不是实际对象,这意味着您可以像传递代理一样传递代理,并使应用程序的其他部分与您的代理进行交互。代理上的更改将反映在实际对象上。
2021-03-19 00:40:22
you don't actually watch changes on the target object but only on proxy object——这不太准确。Proxy对象没有被修改-它没有它自己的目标的副本。you just want to know when a property change on the target object-- 您可以使用 来实现Proxy,这是代理的主要用例之一。
2021-03-22 00:40:22
不,因为您正在直接修改目标。如果要观察对 的修改target,则必须通过代理进行。但是proxy.hello_world = "test"并不意味着您正在修改代理,代理保持不变,target会被修改(如果您的设置处理程序配置为这样做)。听起来你的观点是你不能直接观察target.hello_world = "test"那是真实的。普通变量赋值不会发出任何类型的事件。这就是为什么我们必须使用这个问题的答案中描述的工具。
2021-03-23 00:40:22
我还要添加另一个缺点,您实际上并没有观察目标对象上的变化,而只是观察代理对象上的变化。在某些情况下,您只想知道目标对象的属性何时发生变化(即target.hello_world = "test"
2021-03-24 00:40:22
谢谢 Elliot B. It sounds like your point is that you cannot directly observe target.hello_world = "test". That is true.这正是我的观点。在我的情况下,我在其他地方创建了一个对象,并由其他一些代码更新......在这种情况下,代理没有用,因为更改将在目标上完成。
2021-03-27 00:40:22

是的,这现在完全有可能!

我知道这是一个旧线程,但现在使用访问器(getter 和 setter)可以实现这种效果:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters

您可以像这样定义一个对象,其中aInternal表示字段a

x = {
  aInternal: 10,
  aListener: function(val) {},
  set a(val) {
    this.aInternal = val;
    this.aListener(val);
  },
  get a() {
    return this.aInternal;
  },
  registerListener: function(listener) {
    this.aListener = listener;
  }
}

然后,您可以使用以下命令注册侦听器:

x.registerListener(function(val) {
  alert("Someone changed the value of x.a to " + val);
});

因此,每当 的值发生任何变化时x.a,都会触发侦听器函数。运行以下行将弹出警报:

x.a = 42;

在此处查看示例:https : //jsfiddle.net/5o1wf1bn/1/

您还可以使用一组侦听器而不是单个侦听器槽,但我想给您提供一个最简单的示例。

这是一个旧的答案,但我想补充一点,这对数组值很有效,只要您设置数组的值而不是推送到它。
2021-03-14 00:40:22
一种检测新属性何时添加到对象或已删除的方法呢?
2021-03-17 00:40:22
这基本上只是INotifyPropertyChangedC# 中的模拟,对吗?
2021-03-30 00:40:22
@Akira 不错的解决方案!如何将多个侦听器注册到同一个变量?
2021-04-05 00:40:22
您只需要拥有一组侦听器而不是单个侦听器,然后不仅要调用this.aListener(val),还必须遍历所有侦听器函数并通过val. 通常,该方法被调用addListener而不是registerListener
2021-04-09 00:40:22

不。

但是,如果真的那么重要,您有两个选择(第一个经过测试,第二个没有):

首先,使用 setter 和 getter,如下所示:

var myobj = {a : 1};

function create_gets_sets(obj) { // make this a framework/global function
    var proxy = {}
    for ( var i in obj ) {
        if (obj.hasOwnProperty(i)) {
            var k = i;
            proxy["set_"+i] = function (val) { this[k] = val; };
            proxy["get_"+i] = function ()    { return this[k]; };
        }
    }
    for (var i in proxy) {
        if (proxy.hasOwnProperty(i)) {
            obj[i] = proxy[i];
        }
    }
}

create_gets_sets(myobj);

那么你可以做这样的事情:

function listen_to(obj, prop, handler) {
    var current_setter = obj["set_" + prop];
    var old_val = obj["get_" + prop]();
    obj["set_" + prop] = function(val) { current_setter.apply(obj, [old_val, val]); handler(val));
}

然后将侦听器设置为:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}

其次,您可以关注value:

鉴于上面的 myobj ,上面有“a”:

function watch(obj, prop, handler) { // make this a framework/global function
    var currval = obj[prop];
    function callback() {
        if (obj[prop] != currval) {
            var temp = currval;
            currval = obj[prop];
            handler(temp, currval);
        }
    }
    return callback;
}

var myhandler = function (oldval, newval) {
    //do something
};

var intervalH = setInterval(watch(myobj, "a", myhandler), 100);

myobj.set_a(2);
“观察”实际上并没有检测到变化。这不是基于事件的,肯定会很快减慢整个应用程序的速度。恕我直言,这些方法永远不应该成为真正项目的一部分。
2021-03-20 00:40:22

使用Prototypehttps : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var myVar = 123;

Object.defineProperty(this, 'varWatch', {
  get: function () { return myVar; },
  set: function (v) {
    myVar = v;
    print('Value changed! New value: ' + v);
  }
});

print(varWatch);
varWatch = 456;
print(varWatch);
<pre id="console">
</pre>

其他例子

// Console
function print(t) {
  var c = document.getElementById('console');
  c.innerHTML = c.innerHTML + '<br />' + t;
}

// Demo
var varw = (function (context) {
  return function (varName, varValue) {
    var value = varValue;
  
    Object.defineProperty(context, varName, {
      get: function () { return value; },
      set: function (v) {
        value = v;
        print('Value changed! New value: ' + value);
      }
    });
  };
})(window);

varw('varWatch'); // Declare
print(varWatch);
varWatch = 456;
print(varWatch);

print('---');

varw('otherVarWatch', 123); // Declare with initial value
print(otherVarWatch);
otherVarWatch = 789;
print(otherVarWatch);
<pre id="console">
</pre>

第二个示例有点误导,varw需要 2 个参数,但您的示例的一部分显示仅使用 value 参数调用的函数。
2021-03-25 00:40:22

很抱歉提出一个旧线程,但这里有一个小手册,适用于那些(像我一样!)不了解 Eli Grey 示例如何工作的人:

var test = new Object();
test.watch("elem", function(prop,oldval,newval){
    //Your code
    return newval;
});

希望这可以帮助某人

@PaulMcClean 这个答案是对 Eli Grey 包含 Polyfill 的答案的回应。 gist.github.com/eligrey/384583
2021-03-13 00:40:22
目前 Chrome 或 Safari 不支持 Watch,仅 Firefox
2021-03-25 00:40:22
根据 mozilla 开发者网络,不推荐这样做。Object.prototype.watch() 仅用于测试,不应在生产代码中使用。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-03-27 00:40:22
手表已被 Mozilla 弃用。这个答案可能会产生误导。
2021-03-30 00:40:22