听取JavaScript中的变量更改

ras*_*t22 422 javascript jquery dom-events

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

Aki*_*ira 77

是的,现在完全有可能!

我知道这是一个旧线程,但现在使用访问器(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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

因此,每当有任何更改值时x.a,将触发侦听器函数.运行以下行将弹出警报:

x.a = 42;
Run Code Online (Sandbox Code Playgroud)

在这里查看示例:https://jsfiddle.net/5o1wf1bn/1/

您也可以使用一组侦听器而不是一个侦听器插槽,但我想给您一个最简单的示例.

  • 这是每个对象的大量代码,有没有办法让它更可重用? (4认同)
  • 您只需要拥有一组侦听器而不是单个侦听器,然后不仅要调用“this.aListener(val)”,还必须遍历所有侦听器函数并通过“val”调用每个侦听器函数。通常,该方法称为 `addListener` 而不是 `registerListener`。 (2认同)

Ell*_* B. 46

这个问题的大部分答案要么过时,要么无效,要么包含大量膨胀的库:

今天,您现在可以使用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'
Run Code Online (Sandbox Code Playgroud)

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}}]
Run Code Online (Sandbox Code Playgroud)

  • 我还要添加另一个缺点,您实际上不会看到目标对象上的更改,而只能看到代理对象上的更改。在某些情况下,您只想知道目标对象的属性何时发生更改(即 `target.hello_world = "test"`) (4认同)
  • “实际上,您实际上不是在观察目标对象的变化,而只是在代理对象上的变化”,这不是很准确。Proxy对象没有被修改-它没有它自己的目标副本。“您只想知道目标对象上的属性何时发生变化”,就可以使用“代理”来完成,这是代理的主要用例之一。 (2认同)
  • 否,因为您是直接修改目标。如果您想观察对“ target”的修改,则必须通过代理进行。但是`proxy.hello_world =“ test”`并不意味着您正在修改代理,代理保持不变,`target`被修改(如果您的设置处理程序被配置为这样做)。听起来您的观点是您无法直接观察`target.hello_world =“ test”`。那是真实的。纯变量分配不会发出任何类型的事件。因此,我们必须使用该问题的答案中所述的工具。 (2认同)
  • 谢谢ElliotB。`听起来您的意思是您不能直接观察target.hello_world =“ test”。没错。`这正是我的意思。在我的情况下,我在其他地方创建了一个对象,并通过其他一些代码对其进行了更新……在这种情况下,代理没有用,因为更改将在目标上完成。 (2认同)

Luk*_*fer 33

没有.

但是,如果它真的那么重要,你有两个选择(第一个是测试,第二个不是):

首先,使用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);
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

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));
}
Run Code Online (Sandbox Code Playgroud)

然后将监听器设置为:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}
Run Code Online (Sandbox Code Playgroud)

其次,我实际上忘记了,我会在考虑的时候提交:)

编辑:哦,我记得:)你可以看一下值:

鉴于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);
Run Code Online (Sandbox Code Playgroud)

  • "观察"实际上并没有发现变化.这不是基于事件的,并且肯定会很快减慢整个应用程序的速度.这些方法恕我直言,永远不应成为真正的项目的一部分. (6认同)

Edu*_*omo 25

使用Prototype:https://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);
Run Code Online (Sandbox Code Playgroud)
<pre id="console">
</pre>
Run Code Online (Sandbox Code Playgroud)

其他例子

// 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);
Run Code Online (Sandbox Code Playgroud)
<pre id="console">
</pre>
Run Code Online (Sandbox Code Playgroud)


jav*_*ure 21

很抱歉打开一个旧线程,但这里有一个小手册,对于那些(像我一样!)看不到Eli Gray的例子是如何工作的:

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

希望这可以帮助别人

  • 目前Chrome或Safari不支持观看,只有Firefox (9认同)
  • 对于每个Mozilla开发人员网络,不建议这样做.Object.prototype.watch()仅用于测试,不应在生产代码中使用.https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch (5认同)
  • @PaulMcClean这个答案是回应Eli Gray的答案,其中包含一个Polyfill.https://gist.github.com/eligrey/384583 (4认同)
  • 手表已被Mozilla弃用。这个答案可能会引起误解。 (2认同)

Roh*_*han 8

最近发现自己也遇到了同样的问题。想要监听变量的变化并在变量变化时做一些事情。

有人提出了一个使用 setter 设置值的简单解决方案。

声明一个简单的对象,在此处保留变量的值:

var variableObject = {
    value: false,
    set: function (value) {
        this.value = value;
        this.getOnChange();
    }
}
Run Code Online (Sandbox Code Playgroud)

该对象包含一个 set 方法,我可以通过该方法更改值。但它也调用了getOnChange()其中的一个方法。现在将定义它。

variableObject.getOnChange = function() {
    if(this.value) {
        // do some stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,每当我这样做时variableObject.set(true),该getOnChange方法都会触发,并且如果根据需要设置了值(在我的例子中:)true,则 if 块也会执行。

这是我发现做这件事的最简单的方法。


Bru*_*eis 7

正如Luke Schafer的回答(注意:这指的是他原来的帖子;但是这一点在编辑后仍然有效),我还建议使用一对Get/Set方法来访问你的价值.

但是我会建议一些修改(这就是我发布的原因......).

该代码的一个问题是a对象的字段myobj是可以直接访问的,因此可以访问它/更改其值而不触发侦听器:

var myobj = { a : 5, get_a : function() { return this.a;}, set_a : function(val) { this.a = val; }}
/* add listeners ... */
myobj.a = 10; // no listeners called!
Run Code Online (Sandbox Code Playgroud)

封装

因此,为了保证实际调用侦听器,我们必须禁止直接访问该字段a.怎么办?使用封口!

var myobj = (function() { // Anonymous function to create scope.

    var a = 5;            // 'a' is local to this function
                          // and cannot be directly accessed from outside
                          // this anonymous function's scope

    return {
        get_a : function() { return a; },   // These functions are closures:
        set_a : function(val) { a = val; }  // they keep reference to
                                            // something ('a') that was on scope
                                            // where they were defined
    };
})();
Run Code Online (Sandbox Code Playgroud)

现在你可以使用相同的方法来创建和添加听众,就像Luke建议的那样,但你可以放心,没有可能的方法来读取或写入a被忽视!

以编程方式添加封装字段

在Luke的轨道上,我现在提出了一种通过简单的函数调用将封装字段和相应的getter/setter添加到对象的简单方法.

请注意,这只适用于值类型.为了使用引用类型,必须实现某种深层复制(例如,参见本文).

function addProperty(obj, name, initial) {
    var field = initial;
    obj["get_" + name] = function() { return field; }
    obj["set_" + name] = function(val) { field = val; }
}
Run Code Online (Sandbox Code Playgroud)

这和以前一样:我们在函数上创建局部变量,然后创建一个闭包.

如何使用它?简单:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
Run Code Online (Sandbox Code Playgroud)

  • 封装+1.这是我的第一个想法,但我希望能够最终添加create_gets_sets方法,因为它是不加选择的,隐藏值并不酷:)我们可以更进一步,写一些东西来隐藏值,但是我认为我发布的代码对大多数人来说足够混乱......也许如果有它的话...... (2认同)

Chu*_*Han 7

如果您正在使用jQuery {UI}(每个人都应该使用:-)),您可以将.change()与隐藏的<input />元素一起使用.

  • 我不太明白.如何将变量附加到隐藏的`<input />`元素? (7认同)
  • 我认为Chuck建议你可以使用jquery然后和.change()事件监听器来设置输入的值.如果使用.val()更新输入值,则会触发.change()事件回调. (4认同)
  • `&lt;input type =“ hidden” value =“” id =“ thisOne” /&gt;`并使用jQuery`$(“#thisOne”)。change(function(){在这里做些事});`和`$(“ #thisOne“)。val(myVariableWhichIsNew);`,然后将触发.change()`。 (2认同)
  • 这是我书中的最佳解决方案.简单,容易.而不是更改代码中的变量,例如`var1 ='new value';`,您将改为设置此隐藏输入的值,然后添加一个侦听器来更改变量.`$("#val1").on('change',function(){val1 = this.val(); ...执行val1更改后要执行的操作...}); $("#val1").val('new value');` (2认同)
  • 对于与我有同样问题的人,如果更改事件没有触发尝试$("#thisOne").val(myVariableWhichIsNew).trigger('change').希望这有帮助 (2认同)

ses*_*ses 6

AngularJS(我知道这不是JQuery,但这可能有所帮助.[纯JS在理论上只是好的]):

$scope.$watch('data', function(newValue) { ..
Run Code Online (Sandbox Code Playgroud)

其中"data"是范围中变量的名称.

有一个指向doc链接.


Man*_*ndo 5

对于那些在几年后调整的人:

大多数浏览器(和IE6 +)的解决方案都可用,它使用onpropertychange事件和较新的规范defineProperty.轻微的问题是你需要使你的变量成为一个dom对象.

详细信息:

http://johndyer.name/native-browser-get-set-properties-in-javascript/


Gio*_*sta 5

我找到的最简单的方法,从这个答案开始:

// variable holding your data
const state = {
  count: null,
  update() {
    console.log(`this gets called and your value is ${this.pageNumber}`);
  },
  get pageNumber() {
    return this.count;
  },
  set pageNumber(pageNumber) {
    this.count = pageNumber;
    // here you call the code you need
    this.update(this.count);
  }
};
Run Code Online (Sandbox Code Playgroud)

进而:

state.pageNumber = 0;
// watch the console

state.pageNumber = 15;
// watch the console
Run Code Online (Sandbox Code Playgroud)


jld*_*ont 3

不是直接的:你需要一对 getter/setter 和某种“addListener/removeListener”接口......或者一个 NPAPI 插件(但这完全是另一个故事)。