简体   繁体   English

监听 JavaScript 中的变量变化

[英]Listening for variable changes in JavaScript

Is it possible to have an event in JS that fires when the value of a certain variable changes?当某个变量的值发生变化时,是否可以在 JS 中触发一个事件? JQuery is accepted. JQuery 被接受。

This question was originally posted in 2009 and most of the existing answers are either outdated, ineffective, or require the inclusion of large bloated libraries:这个问题最初发布于 2009 年,现有的大多数答案要么已过时、无效,要么需要包含大型臃肿的库:

As of 2018, you can now use the Proxy object to monitor (and intercept) changes made to an object.自 2018 年起,您现在可以使用Proxy对象来监视(和拦截)对对象所做的更改。 It is purpose built for what the OP is trying to do.它是专为 OP 试图做的事情而设计的。 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: Proxy对象的唯一缺点是:

  1. The Proxy object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy functionality. Proxy对象在旧版浏览器(例如 IE11)中不可用,并且polyfill无法完全复制Proxy功能。
  2. Proxy objects do not always behave as expected with special objects (eg, Date ) -- the Proxy object is best paired with plain Objects or Arrays. Proxy 对象在特殊对象(例如Date )中的行为并不总是与预期的一样Proxy对象最好与普通对象或数组配对。

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 have published) .如果您需要观察对嵌套对象所做的更改,那么您需要使用专门的库,例如Observable Slim (我已发布) 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}}]

 

Yes, this is now completely possible!是的,这现在是完全可能的!

I know this is an old thread but now this effect is possible using accessors (getters and setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters我知道这是一个旧线程,但现在可以使用访问器(getter 和 setter)实现这种效果: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters

You can define an object like this, in which aInternal represents the field a :您可以像这样定义一个对象,其中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;
  }
}

Then you can register a listener using the following:然后,您可以使用以下内容注册侦听器:

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

So whenever anything changes the value of xa , the listener function will be fired.因此,无论何时更改xa的值,都会触发侦听器函数。 Running the following line will bring the alert popup:运行以下行将带来警报弹出窗口:

x.a = 42;

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

You can also user an array of listeners instead of a single listener slot, but I wanted to give you the simplest possible example.您还可以使用一组侦听器而不是单个侦听器插槽,但我想给您一个最简单的示例。

Using Prototype : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty使用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>

Other example其他示例

 // Console function print(t) { var c = document.getElementById('console'); c.innerHTML = c.innerHTML + '<br />' + t; } // Demo var varw = (function (context) { /** * Declare a new variable. * * @param {string} Variable name. * @param {any | undefined} varValue Default/Initial value. * You can use an object reference for example. */ 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 without initial value print(varWatch); varWatch = 456; print(varWatch); print('---'); varw('otherVarWatch', 123); // Declare with initial value print(otherVarWatch); otherVarWatch = 789; print(otherVarWatch);
 <pre id="console"> </pre>

No.不。

But, if it's really that important, you have 2 options (first is tested, second isn't):但是,如果它真的那么重要,你有 2 个选项(第一个是测试的,第二个不是):

First, use setters and getters, like so:首先,使用 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);

then you can do something like:那么您可以执行以下操作:

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));
}

then set the listener like:然后将侦听器设置为:

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

Second, you could put a watch on the value:其次,您可以关注价值:

Given myobj above, with 'a' on it:鉴于上面的 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);

Sorry to bring up an old thread, but here is a little manual for those who (like me!) don't see how Eli Grey's example works:很抱歉提出一个旧线程,但对于那些(像我一样!)看不到 Eli Grey 的示例如何工作的人来说,这里有一个小手册:

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

Hope this can help someone希望这可以帮助某人

As Luke Schafer's answer ( note : this refers to his original post; but the whole point here remains valid after the edit ), I would also suggest a pair of Get/Set methods to access your value.正如Luke Schafer 的回答注意:这是指他的原始帖子;但这里的整点在编辑后仍然有效),我还建议使用一对 Get/Set 方法来访问您的值。

However I would suggest some modifications (and that's why I'm posting...).但是我会建议一些修改(这就是我发布的原因......)。

A problem with that code is that the field a of the object myobj is directly accessible, so it's possible to access it / change its value without triggering the listeners:该代码的一个问题是对象myobj的字段a是可直接访问的,因此可以在不触发侦听器的情况下访问它/更改其值:

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!

Encapsulation封装

So, to guarantee that the listeners are actually called, we would have to prohibit that direct access to the field a .因此,为了保证真正调用了侦听器,我们必须禁止直接访问字段a How to do so?怎么做? Use a closure !使用闭包

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
    };
})();

Now you can use the same method to create and add the listeners as Luke proposed, but you can rest assured that there's no possible way to read from or write to a going unnoticed!现在您可以使用与 Luke 建议的方法相同的方法来创建和添加侦听器,但您可以放心,没有任何方法可以读取或写入不被注意a对象!

Adding encapsulated fields programmatically以编程方式添加封装字段

Still on Luke's track, I propose now a simple way to add encapsulated fields and the respective getters/setters to objects by the means of a simple function call.仍然在 Luke 的轨道上,我现在提出一种简单的方法,通过简单的函数调用将封装的字段和相应的 getter/setter 添加到对象。

Note that this will only work properly with value types .请注意,这仅适用于值类型 For this to work with reference types , some kind of deep copy would have to be implemented (see this one , for instance).要使其与引用类型一起工作,必须实现某种深拷贝(例如,参见这个)。

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

This works the same as before: we create a local variable on a function, and then we create a closure.这和以前一样:我们在函数上创建一个局部变量,然后我们创建一个闭包。

How to use it?如何使用它? Simple:简单的:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);

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

AngularJS (I know this is not JQuery , but that might help. [Pure JS is good in theory only]): AngularJS (我知道这不是JQuery ,但这可能会有所帮助。[纯 JS 仅在理论上很好]):

$scope.$watch('data', function(newValue) { ..

where "data" is name of your variable in the scope.其中“数据”是范围内变量的名称。

There is a link to doc.有一个文档链接。

Recently found myself with the same issue.最近发现自己有同样的问题。 Wanted to listen for on change of a variable and do some stuff when the variable changed.想要监听变量的变化并在变量变化时做一些事情。

Someone suggested a simple solution of setting the value using a setter.有人提出了一个使用 setter 设置值的简单解决方案。

Declaring a simple object that keeps the value of my variable here:在这里声明一个简单的对象来保存我的变量的值:

var variableObject = {
    value: false,
    set: function (value) {
        this.value = value;
        this.getOnChange();
    }
}

The object contains a set method via which I can change the value.该对象包含一个 set 方法,我可以通过该方法更改值。 But it also calls a getOnChange() method in there.但它也在那里调用了一个getOnChange()方法。 Will define it now.现在将定义它。

variableObject.getOnChange = function() {
    if(this.value) {
        // do some stuff
    }
}

Now whenever I do variableObject.set(true) , the getOnChange method fires, and if the value was set as desired (in my case: true ), the if block also executes.现在,每当我执行variableObject.set(true)时,都会触发getOnChange方法,并且如果根据需要设置了值(在我的情况下为true ),则 if 块也会执行。

This is the simplest way I found to do this stuff.这是我发现做这些事情的最简单方法。

For those tuning in a couple years later:对于几年后调整的人:

A solution for most browsers (and IE6+) is available that uses the onpropertychange event and the newer spec defineProperty.大多数浏览器(和 IE6+)的解决方案都可用,它使用 onpropertychange 事件和较新的规范 defineProperty。 The slight catch is that you'll need to make your variable a dom object.轻微的问题是您需要将变量设为 dom 对象。

Full details:完整详情:

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

Easiest way I have found, starting from this answer :这个答案开始,我找到了最简单的方法:

// 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);
  }
};

And then:接着:

state.pageNumber = 0;
// watch the console

state.pageNumber = 15;
// watch the console

The functionality you're looking for can be achieved through the use of the "defineProperty()" method--which is only available to modern browsers:您正在寻找的功能可以通过使用“defineProperty()”方法来实现——该方法仅适用于现代浏览器:

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

I've written a jQuery extension that has some similar functionality if you need more cross browser support:如果您需要更多跨浏览器支持,我编写了一个具有类似功能的 jQuery 扩展:

https://github.com/jarederaj/jQueue https://github.com/jarederaj/jQueue

A small jQuery extension that handles queuing callbacks to the existence of a variable, object, or key.一个小的 jQuery 扩展,它处理对变量、对象或键的存在的队列回调。 You can assign any number of callbacks to any number of data points that might be affected by processes running in the background.您可以将任意数量的回调分配给可能受后台运行的进程影响的任意数量的数据点。 jQueue listens and waits for these data you specify to come into existence and then fires off the correct callback with its arguments. jQueue 侦听并等待您指定的这些数据出现,然后使用其参数触发正确的回调。

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

A rather simple and simplistic solution is to just use a function call to set the value of the global variable, and never set its value directly.一个相当简单的解决方案是只使用函数调用来设置全局变量的值,而不是直接设置它的值。 This way you have total control:这样您就可以完全控制:

var globalVar;

function setGlobalVar(value) {
    globalVar = value;
    console.log("Value of globalVar set to: " + globalVar);
    //Whatever else
}

There is no way to enforce this, it just requires programming discipline... though you can use grep (or something similar) to check that nowhere does your code directly set the value of globalVar .没有办法强制执行这一点,它只需要编程纪律......尽管您可以使用grep (或类似的东西)来检查您的代码是否没有直接设置globalVar的值。

Or you could encapsulate it in an object and user getter and setter methods... just a thought.或者你可以将它封装在一个对象和用户 getter 和 setter 方法中......只是一个想法。

In my case, I was trying to find out if any library I was including in my project was redefining my window.player .就我而言,我试图找出我在项目中包含的任何库是否正在重新定义我的window.player So, at the begining of my code, I just did:所以,在我的代码开始时,我只是做了:

Object.defineProperty(window, 'player', {
  get: () => this._player,
  set: v => {
    console.log('window.player has been redefined!');
    this._player = v;
  }
});

Based On akira's answer I added that you can manipulate the dom through the listerner.根据 akira 的回答,我补充说您可以通过侦听器操作 dom。

https://jsfiddle.net/2zcr0Lnh/2/ https://jsfiddle.net/2zcr0Lnh/2/

javascript: javascript:

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) {
document.getElementById('showNumber').innerHTML = val;
});


x.a = 50;

function onClick(){
x.a = x.a + 1;
}

html: html:

<div id="showNumber">
 
</div>


<button onclick="onClick()">
click me to rerender
</button>

The registerListener method is fired when the variable xa changes. registerListener 方法在变量xa更改时触发。

With the help of getter and setter , you can define a JavaScript class that does such a thing.gettersetter的帮助下,你可以定义一个 JavaScript 类来做这样的事情。

First, we define our class called MonitoredVariable :首先,我们定义名为MonitoredVariable的类:

class MonitoredVariable {
  constructor(initialValue) {
    this._innerValue = initialValue;
    this.beforeSet = (newValue, oldValue) => {};
    this.beforeChange = (newValue, oldValue) => {};
    this.afterChange = (newValue, oldValue) => {};
    this.afterSet = (newValue, oldValue) => {};
  }

  set val(newValue) {
    const oldValue = this._innerValue;
    // newValue, oldValue may be the same
    this.beforeSet(newValue, oldValue);
    if (oldValue !== newValue) {
      this.beforeChange(newValue, oldValue);
      this._innerValue = newValue;
      this.afterChange(newValue, oldValue);
    }
    // newValue, oldValue may be the same
    this.afterSet(newValue, oldValue);
  }

  get val() {
    return this._innerValue;
  }
}

Assume that we want to listen for money changes, let's create an instance of MonitoredVariable with initial money 0 :假设我们想监听money变化,让我们创建一个MonitoredVariable实例,初始货币0

const money = new MonitoredVariable(0);

Then we could get or set its value using money.val :然后我们可以使用money.val获取或设置它的值:

console.log(money.val); // Get its value
money.val = 2; // Set its value

Since we have not defined any listeners for it, nothing special happens after money.val changes to 2.由于我们没有为它定义任何监听器,所以在money.val更改为 2 之后没有什么特别的事情发生。

Now let's define some listeners.现在让我们定义一些监听器。 We have four listeners available: beforeSet , beforeChange , afterChange , afterSet .我们有四个可用的侦听器: beforeSetbeforeChangeafterChangeafterSet The following will happen sequentially when you use money.val = newValue to change variable's value:当您使用money.val = newValue更改变量的值时,将依次发生以下情况:

  1. money.beforeSet(newValue, oldValue); money.beforeSet(newValue, oldValue);
  2. money.beforeChange(newValue, oldValue); money.beforeChange(newValue, oldValue); (Will be skipped if its value not changed) (如果它的值没有改变,将被跳过)
  3. money.val = newValue;金钱.val = newValue;
  4. money.afterChange(newValue, oldValue); money.afterChange(newValue, oldValue); (Will be skipped if its value not changed) (如果它的值没有改变,将被跳过)
  5. money.afterSet(newValue, oldValue); money.afterSet(newValue, oldValue);

Now we define afterChange listener which be triggered only after money.val has changed (while afterSet will be triggered even if the new value is the same as the old one):现在我们定义afterChange监听器,它仅在money.val发生更改后触发(而afterSet将被触发,即使新值与旧值相同):

money.afterChange = (newValue, oldValue) => {
  console.log(`Money has been changed from ${oldValue} to ${newValue}`);
};

Now set a new value 3 and see what happens:现在设置一个新值3看看会发生什么:

money.val = 3;

You will see the following in the console:您将在控制台中看到以下内容:

Money has been changed from 2 to 3

For full code, see https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab .完整代码见https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab

//ex:
/*
var x1 = {currentStatus:undefined};
your need is x1.currentStatus value is change trigger event ?
below the code is use try it.
*/
function statusChange(){
    console.log("x1.currentStatus_value_is_changed"+x1.eventCurrentStatus);
};

var x1 = {
    eventCurrentStatus:undefined,
    get currentStatus(){
        return this.eventCurrentStatus;
    },
    set currentStatus(val){
        this.eventCurrentStatus=val;
      //your function();
    }
};

or或者

/*  var x1 = {
eventCurrentStatus:undefined,
currentStatus : {
    get : function(){
        return Events.eventCurrentStatus
        },
    set : function(status){
        Events.eventCurrentStatus=status;

    },
}*/
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="create"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="edit"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
console.log("currentStatus = "+ x1.currentStatus);

or或者

/* global variable ku*/
    var jsVarEvents={};
    Object.defineProperty(window, "globalvar1", {//no i18n
        get: function() { return window.jsVarEvents.globalvarTemp},
        set: function(value) { window.window.jsVarEvents.globalvarTemp = value; }
    });
    console.log(globalvar1);
    globalvar1=1;
    console.log(globalvar1);

Please guys remember the initial question was for VARIABLES, not for OBJECTS ;)请大家记住最初的问题是针对变量的,而不是针对对象的;)

in addition to all answers above, I created a tiny lib called forTheWatch.js , that use the same way to catch and callback for changes in normal global variables in javascript.除了上述所有答案之外,我还创建了一个名为forTheWatch.js的小库,它使用相同的方式来捕获和回调 javascript 中正常全局变量的变化。

Compatible with JQUERY variables, no need to use OBJECTS, and you can pass directly an ARRAY of several variables if needed.兼容 JQUERY 变量,无需使用 OBJECTS,如果需要可以直接传递多个变量的 ARRAY。

If it can be helpful... : https://bitbucket.org/esabora/forthewatch如果有帮助的话……: https ://bitbucket.org/esabora/forthewatch
Basically you just have to call the function :基本上你只需要调用函数:
watchIt("theVariableToWatch", "varChangedFunctionCallback");

And sorry by advance if not relevant.如果不相关,请提前道歉。

The question is about variables , not object properties!问题是关于变量,而不是对象属性! So my approach is to take advantage of the window object , with its custom getters/setters, and then use/change the variable like a "normal" variable (not like an object property).所以我的方法是利用 window object及其自定义 getter/setter,然后像“普通”变量(而不是对象属性)一样使用/更改变量。

The simplest way is that of @José Antonio Postigo in his answer (i voted that answer).最简单的方法是@José Antonio Postigo 在他的回答中(我投了那个答案)。 What I'd like to do here, is to reduce that to an even simpler "creator" function (so even someone that does not understand object getters/setters can easily use it).我想在这里做的是将其简化为更简单的“创建者”功能(因此即使是不了解对象获取器/设置器的人也可以轻松使用它)。

A live example is here: https://codepen.io/dimvai/pen/LYzzbpz一个活生生的例子在这里: https ://codepen.io/dimvai/pen/LYzzbpz

This is the general "creator" function you must have as is:这是您必须具备的一般“创建者”功能:

let createWatchedVariable = (variableName,initialValue,callbackFunction) => {
    // set default callback=console.log if missing
    callbackFunction ??= function(){console.log(variableName+" changed to " + window[variableName])};
    // the actual useful code:
    Object.defineProperty(window, variableName, {
      set: function(value) {window["_"+variableName] = value; callbackFunction()},
      get: function() {return window["_"+variableName]}
    });
    window[variableName]=initialValue??null;
};

Then, instead of declaring the variable using var or let , use this:然后,不要使用varlet声明变量,而是使用以下命令:

// 1st approach - default callback//    
createWatchedVariable ('myFirstVariable',12);  
// instead of: let myFirstVariable = 12;

Or, in order to use your custom callback (instead of the default console.log) use:或者,为了使用您的自定义回调(而不是默认的 console.log),请使用:

// 2nd approach - set a custom callback//
var myCallback = ()=>{/*your custom code...*/}
// now use callback function as the third optional argument
createWatchedVariable('mySecondVariable',0,myCallback);

That's it!而已! Now, you can change it like a "normal" variable:现在,您可以将其更改为“正常”变量:

myFirstVariable = 15;      // logs to console
myFirstVariable++;         // logs to console
mySecondVariable = 1001;   // executes your custom code
mySecondVariable++;        // executes your custom code

It's not directly possible.这不是直接可能的。

However, this can be done using CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent但是,这可以使用 CustomEvent 来完成: https ://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

The below method accepts an array of variable names as an input and adds event listener for each variable and triggers the event for any changes to the value of the variables.下面的方法接受一个变量名称数组作为输入,并为每个变量添加事件侦听器,并为变量值的任何更改触发事件。

The Method uses polling to detect the change in the value.该方法使用轮询来检测值的变化。 You can increase the value for timeout in milliseconds.您可以以毫秒为单位增加超时值。

function watchVariable(varsToWatch) {
    let timeout = 1000;
    let localCopyForVars = {};
    let pollForChange = function () {
        for (let varToWatch of varsToWatch) {
            if (localCopyForVars[varToWatch] !== window[varToWatch]) {
                let event = new CustomEvent('onVar_' + varToWatch + 'Change', {
                    detail: {
                        name: varToWatch,
                        oldValue: localCopyForVars[varToWatch],
                        newValue: window[varToWatch]
                    }
                });
                document.dispatchEvent(event);
                localCopyForVars[varToWatch] = window[varToWatch];
            }
        }
        setTimeout(pollForChange, timeout);
    };
    let respondToNewValue = function (varData) {
        console.log("The value of the variable " + varData.name + " has been Changed from " + varData.oldValue + " to " + varData.newValue + "!!!"); 
    }
    for (let varToWatch of varsToWatch) {
        localCopyForVars[varToWatch] = window[varToWatch];
        document.addEventListener('onVar_' + varToWatch + 'Change', function (e) {
            respondToNewValue(e.detail);
        });
    }
    setTimeout(pollForChange, timeout);
}

By calling the Method:通过调用方法:

watchVariables(['username', 'userid']);

It will detect the changes to variables username and userid.它将检测变量 username 和 userid 的更改。

This is what I did: Call JSON.stringify twice and compare the two strings...这就是我所做的:调用 JSON.stringify 两次并比较两个字符串......

Drawbacks:缺点:

  • You can only know whether the whole object changes你只能知道整个对象是否发生了变化
  • You have to detect changes manually您必须手动检测更改
  • You better have only primitive fields in the object(no properties, no functions...)你最好在对象中只有原始字段(没有属性,没有函数......)

The solution of @akira and @mo-d-genesis can be further simplified because the DOM manipulation does not depend on state in this example: @akira 和 @mo-d-genesis 的解决方案可以进一步简化,因为在此示例中 DOM 操作不依赖于state

CodePen代码笔

const render = (val) => {
  document.getElementById("numberDiv").innerHTML = val;
};

state = {
  _value_internal: undefined,
  set value(val) {
    // 1. set state value
    this._value_internal = val;
    // 2. render user interface
    render(val);
  },
  get value() {
    return this._value_internal;
  },
};

const onClick = () => {
  state.value = state.value + 1; // state change leads to re-render!
};

// set default value
state.value = 0;

The corresponding html :对应的html

<div id="numberDiv"></div>

<button onclick="onClick()">
  Click to rerender
</button>

Remarks :备注

  1. I renamed variables and functions to better reflect their semantics.我重命名了变量和函数以更好地反映它们的语义。
  2. FYI: Svelte offers a very similar reactive behavior by changing variables仅供参考: Svelte通过更改变量提供了非常相似的反应行为

This is NOT a production ideal answer, but what it is doing is setting an interval in JavaScript for every 100 milliseconds and checking to see if the variable is changed and when it is, it does something (anything intended by the OP) and then clears the interval, so it sort of simulates what the OP is asking.不是一个生产理想的答案,但它正在做的是在 JavaScript 中每 100 毫秒设置一个间隔,并检查变量是否发生了变化,当它发生变化时,它会做一些事情(OP 想要做的任何事情)然后清除间隔,所以它有点模拟OP的要求。

let myvar = "myvar";

const checkChange = setInterval(() => {
    if (myvar !== "myvar") {
        console.log("My precious var has been changed!");
        clearInterval(checkChange);
    }
}, 100);

Now if myvar gets changed to something else then this program will say "My precious var has been changed!"现在,如果myvar更改为其他内容,则此程序将显示"My precious var has been changed!" :) :)

This is an old great question, has more than 12 years.这是一个古老的大问题,已经超过 12 年了。 Also, there are many ways to solve it.此外,有很多方法可以解决它。 However, most of then are complicated or using old JS concepts we are in 2022 and we can use ES6 to improve our code.然而,大多数都是复杂的或使用旧的 JS 概念,我们在 2022 年可以使用 ES6 来改进我们的代码。

I will implemented two main solutions that I constantly use.我将实施我经常使用的两个主要解决方案。

Simple variable简单变量

If we have a simple variable and we don't care about reutilization then we can declare our variable as an object. We define a set and get methods and a listener attribute to handle the "change" event.如果我们有一个简单的变量并且我们不关心重用,那么我们可以将我们的变量声明为 object。我们定义一个setget方法以及一个listener器属性来处理“更改”事件。

 const $countBtn = document.getElementById('counter') const $output = document.getElementById('output') const counter = { v: 0, listener: undefined, set value(v) { this.v = v if (this.listener) this.listener(v) }, get value() { return this.v }, count() { this.value++ }, registerListener(callback) { this.listener = callback }, } const countOnClick = () => { counter.count() } $countBtn.onclick = countOnClick counter.registerListener(v => { $output.textContent = v }) counter.value = 50
 #output { display: block; font-size: 2em; margin-top: 0.67em; margin-bottom: 0.67em; margin-left: 0; margin-right: 0; font-weight: bold; }
 <button id="counter">Count</button> <div id="output"></div>

Advanced Class for reusability高级 Class 可重复使用

If we will have multiple variables and we need to monitor them, we can create a class and then apply it to our variables.如果我们有多个变量并且需要监视它们,我们可以创建一个class然后将其应用于我们的变量。 I recommend to add two listeners one beforeChange and afterChange this will give you flexibility to use the variable in different process.我建议添加两个侦听器,一个beforeChangeafterChange这将使您可以灵活地在不同的进程中使用变量。

 class ObservableObject { constructor(v) { this.v = v?? 0 this.on = { beforeChange(newValue, oldValue) {}, afterChange(newValue, oldValue) {}, } } set value(newValue) { const oldValue = this.v // newValue, oldValue are the same if (oldValue === newValue) return this.on.beforeChange(newValue, oldValue) this.v = newValue this.on.afterChange(newValue, oldValue) } get value() { return this.v } } const $countABtn = document.getElementById('counter-a') const $countBBtn = document.getElementById('counter-b') const $outputA = document.getElementById('output-a') const $outputB = document.getElementById('output-b') const counterA = new ObservableObject() const counterB = new ObservableObject() const countOnClick = counter => { counter.value++ } const onChange = (v, output) => { output.textContent = v } $countABtn.onclick = () => { countOnClick(counterA) } $countBBtn.onclick = () => { countOnClick(counterB) } counterA.on.afterChange = v => { onChange(v, $outputA) } counterB.on.afterChange = v => { onChange(v, $outputB) } counterA.value = 50 counterB.value = 20
 .wrapper { display: flex; flex-flow: row wrap; justify-content: center; align-items: center; width: 100vw }.item { width: 50% }.output { display: block; font-size: 2em; margin-top: 0.67em; margin-bottom: 0.67em; margin-left: 0; margin-right: 0; font-weight: bold; }
 <div class="wrapper"> <div class="item"> <button id="counter-a">Count A</button> <div id="output-a" class="output"></div> </div> <div class="item"> <button id="counter-b">Count B</button> <div id="output-b" class="output"></div> </div> </div>

I searched for JavaScript two-way data binding library and came across this one .我搜索了 JavaScript 双向数据绑定库,遇到了这个

I did not succeed to make it work in DOM to variable direction, but in variable to DOM direction it works and that is what we need here.我没有成功使它在DOM to variable方向工作,但在variable to DOM方向中它可以工作,这就是我们在这里需要的。

I have rewritten it slightly, as the original code is very hard to read (for me).我稍微重写了它,因为原始代码很难阅读(对我来说)。 It uses Object.defineProperty , so the second most upvoted answer by Eliot B. at least partially wrong.它使用Object.defineProperty ,因此艾略特 B 的第二个最赞成的答案至少部分错误。

<!DOCTYPE html>
<html>
  <head>
    <title>TODO supply a title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script>
        const dataBind = (function () {
            const getElementValue = function (selector) {
                let element = document.querySelectorAll(selector)[0];
                return 'value' in element ? element.value : element.innerHTML;
            };
            const setElementValue = function (selector, newValue) {
                let elementArray = document.querySelectorAll(selector);
                for (let i = 0; i < elementArray.length; i++) {
                    let element = elementArray[i];
                    if ('value' in element) {
                        element.value = newValue;
                        if (element.tagName.toLowerCase() === 'select'){
                            let options = element.querySelectorAll('option');
                            for (let option in options){
                                if (option.value === newValue){
                                    option.selected = true;
                                    break;
                                }
                            }
                        }
                    } else {
                        element.innerHTML = newValue;
                    }
                }
            };

            const bindModelToView = function (selector, object, property, enumerable) {
                Object.defineProperty(object, property, {
                    get: function () {
                        return getElementValue(selector);
                    },
                    set: function (newValue) {
                        setElementValue(selector, newValue);
                    },
                    configurable: true,
                    enumerable: (enumerable)
                });
            };
            return {
                bindModelToView
            };
        })();
    </script>
</head>
<body>
    <div style="padding: 20%;">
        <input  type="text" id="text" style="width: 40px;"/>
    </div>
    <script>
        let x = {a: 1, b: 2};
        dataBind.bindModelToView('#text', x, 'a'); //data to dom

        setInterval(function () {
             x.a++;
        }, 1000);
   </script> 
</body>

</html>

JSFiddle. JSF 中。

JSFiddle with original code. JSFiddle 与原始代码。

In the provided example a property of object x updated by the setInterval and value of text input automatically updated as well.在提供的示例中,对象x a属性由setInterval更新, text输入的值也自动更新。 If it is not enough and event is what you looking for, you can add onchange listener to the above input.如果这还不够,并且event是您要寻找的,您可以将onchange侦听器添加到上述输入中。 Input also can be made hidden if needed.如果需要,也可以隐藏输入。

This is an old thread but I stumbled onto second highest answer (custom listeners) while looking for a solution using Angular.这是一个旧线程,但我在寻找使用 Angular 的解决方案时偶然发现了第二高的答案(自定义侦听器)。 While the solution works, angular has a better built in way to resolve this using @Output and event emitters.虽然该解决方案有效,但 Angular 具有更好的内置方法来使用@Output和事件发射器解决此问题。 Going off of the example in custom listener answer:离开自定义侦听器答案中的示例:

ChildComponent.html ChildComponent.html

<button (click)="increment(1)">Increment</button>

ChildComponent.ts子组件.ts

import {EventEmitter, Output } from '@angular/core';

@Output() myEmitter: EventEmitter<number> = new EventEmitter<number>();

private myValue: number = 0;

public increment(n: number){
  this.myValue += n;

  // Send a change event to the emitter
  this.myEmitter.emit(this.myValue);
}

ParentComponent.html父组件.html

<child-component (myEmitter)="monitorChanges($event)"></child-component>
<br/>
<label>{{n}}</label>

ParentComponent.ts父组件.ts

public n: number = 0;

public monitorChanges(n: number){
  this.n = n;
  console.log(n);
}

This will now update n on parent each time the child button is clicked.现在,每次单击子按钮时,都会在父级上更新n Working stackblitz工作堆栈闪电战

I came here looking for same answer for node js.我来这里为节点 js 寻找相同的答案。 So here it is所以这里

const events = require('events');
const eventEmitter = new events.EventEmitter();

// Createing state to watch and trigger on change
let x = 10 // x is being watched for changes in do while loops below

do {
    eventEmitter.emit('back to normal');
}
while (x !== 10);

do {
    eventEmitter.emit('something changed');
}
while (x === 10);

What I am doing is setting some event emitters when values are changed and using do while loops to detect it.我正在做的是在值更改时设置一些事件发射器并使用 do while 循环来检测它。

Utils = {
    eventRegister_globalVariable : function(variableName,handlers){
        eventRegister_JsonVariable(this,variableName,handlers);
    },
    eventRegister_jsonVariable : function(jsonObj,variableName,handlers){
        if(jsonObj.eventRegisteredVariable === undefined) {
            jsonObj.eventRegisteredVariable={};//this Object is used for trigger event in javascript variable value changes ku
        }
        Object.defineProperty(jsonObj, variableName , {
                    get: function() { 
                        return jsonObj.eventRegisteredVariable[variableName] },
                    set: function(value) {
                        jsonObj.eventRegisteredVariable[variableName] = value; handlers(jsonObj.eventRegisteredVariable[variableName]);}
                    });
            }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM