监听 JavaScript 中的变量变化

Posted

技术标签:

【中文标题】监听 JavaScript 中的变量变化【英文标题】:Listening for variable changes in JavaScript 【发布时间】:2010-12-18 02:45:42 【问题描述】:

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

【问题讨论】:

@BenjaminGruenbaum 可能你想说 MutableObserver(用于 DOM)。对象仅适用于我记得的 JS 对象。 @HellBaby 这个问题是关于变量——而不是 DOM。 @BenjaminGruenbaum 根据developer.mozilla.org/en-US/docs/Web/javascript/Reference/… Object.observe 已过时或已弃用。推荐的替换(每页)是代理对象。 这个问题只问variable,但这里所有的答案都参考property。我想知道我们是否可以收听local variable 的变化。 【参考方案1】:

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

【讨论】:

【参考方案2】:

没有。

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

首先,使用 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);

其次,你可以关注价值:

给定上面的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);

【讨论】:

"watching" 实际上并没有检测到变化。这不是基于事件的,并且肯定会很快减慢整个应用程序的速度。这些方法恕我直言,永远不应该成为实际项目的一部分。【参考方案3】:

作为Luke Schafer's answer(注意:这是指他的原始帖子;但这里的整点在编辑后仍然有效),我还建议一对获取/设置方法来访问您的值。

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

该代码的一个问题是对象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!

封装

因此,为了保证真正调用了侦听器,我们必须禁止直接访问字段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
    ;
)();

现在您可以使用与 Luke 建议的方法相同的方法来创建和添加侦听器,但您可以放心,没有办法读取或写入 a 被忽视!

以编程方式添加封装字段

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

请注意,这仅适用于 值类型。要使其与引用类型一起工作,必须实现某种深拷贝(例如,参见this one)。

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

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

如何使用它?简单:

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

【讨论】:

+1 用于封装。这是我的第一个想法,但我希望最终能够添加 create_gets_sets 方法,因为它是不分青红皂白的,隐藏值并不酷:) 我们可以更进一步,写一些东西来隐藏值,但是我认为我发布的代码对大多数人来说已经足够混乱了......也许如果有需要的话......【参考方案4】:

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

【讨论】:

我不太明白。如何将变量附加到隐藏的 <input/> 元素? 我认为 Chuck 建议您可以使用 jquery 设置输入值,然后使用 .change() 事件侦听器。如果您使用 .val() 更新输入值,则 .change() 事件回调将触发。 <input type="hidden" value="" id="thisOne" /> 和 jQuery $("#thisOne").change(function() do stuff here );$("#thisOne").val(myVariableWhichIsNew); 然后 .change() 将触发。 这是我书中最好的解决方案。简单,轻松。而不是更改代码中的变量,例如var1 = 'new value';,您将改为设置此隐藏输入的值,然后添加一个侦听器来更改变量。 $("#val1").on('change', function() val1 = this.val(); ... do the stuff that you wanted to do when val1 changed... ); $("#val1").val('new value'); 对于任何和我有同样问题的人,如果更改事件没有触发尝试 $("#thisOne").val(myVariableWhichIsNew).trigger('change') 。希望这有帮助 【参考方案5】:

对于几年后调整的人:

针对大多数浏览器(和 IE6+)的解决方案是可用的,它使用 onpropertychange 事件和较新的规范 defineProperty。需要注意的是,您需要将变量设为 dom 对象。

详细信息:

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

【讨论】:

【参考方案6】:

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

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

希望这可以帮助某人

【讨论】:

目前 Chrome 或 Safari 不支持 Watch,只有 Firefox 支持 每个 mozilla 开发者网络,不建议这样做。 Object.prototype.watch() 仅用于测试,不应在生产代码中使用。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… @PaulMcClean 这个答案是为了回应 Eli Grey 的回答,其中包含一个 Polyfill。 gist.github.com/eligrey/384583 手表已被 Mozilla 弃用。这个答案可能会产生误导。【参考方案7】:

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

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

其中“数据”是范围内变量的名称。

有一个link to doc.

【讨论】:

不幸的是,它迫使您将变量绑定到范围 只有在$scope.$apply()运行时才会触发【参考方案8】:

您正在寻找的功能可以通过使用“defineProperty()”方法来实现——该方法仅适用于现代浏览器:

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

如果您需要更多跨浏览器支持,我已经编写了一个具有类似功能的 jQuery 扩展:

https://github.com/jarederaj/jQueue

一个小的 jQuery 扩展,用于处理队列回调 变量、对象或键的存在。您可以分配任意数量的 回调到可能受到影响的任意数量的数据点 在后台运行的进程。 jQueue 监听并等待 您指定的这些数据开始存在,然后触发 正确的回调及其参数。

【讨论】:

【参考方案9】:
//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();
    
;

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

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

【讨论】:

【参考方案10】:
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]);
                    );
            

【讨论】:

【参考方案11】:

使用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);
<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 参数调用的函数。【参考方案12】:

一个相当简单的解决方案是只使用函数调用来设置全局变量的值,而不是直接设置它的值。这样您就可以完全控制:

var globalVar;

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

没有办法强制执行此操作,它只需要编程纪律...尽管您可以使用 grep(或类似的东西)来检查您的代码是否没有直接设置 globalVar 的值。

或者你可以将它封装在一个对象和用户 getter 和 setter 方法中......只是一个想法。

【讨论】:

对于一个不是可以访问的对象的属性的变量——就像在 ES6 模块中用var 声明的变量一样——这是only 解决方案。【参考方案13】:

是的,这完全可以实现!

我知道这是一个旧线程,但现在可以使用访问器(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/

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

【讨论】:

有什么方法可以检测一个新属性何时被添加到一个对象中,或者一个新属性何时被删除? 这是一个旧的答案,但我想补充一点,这适用于数组值,只要您设置数组的值而不是推送到它。 @Akira 不错的解决方案!如何将多个侦听器注册到同一个变量? 您只需要拥有一组侦听器而不是单个侦听器,然后不仅要调用this.aListener(val),还必须遍历所有侦听器函数并调用每个传递val 的侦听器函数。通常,该方法称为addListener 而不是registerListener 这基本上只是 C# 中 INotifyPropertyChanged 的模拟,对吧?【参考方案14】:

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

Object.watch 和 Object.observe 均已弃用,不应使用。 onPropertyChange 是一个 DOM 元素事件处理程序,仅适用于某些版本的 IE。 Object.defineProperty 允许您使对象属性不可变,这将允许您检测尝试的更改,但它也会阻止任何更改。 Defining setters and getters 有效,但需要大量设置代码,并且在您需要删除或创建新属性时效果不佳。

自 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 对象的唯一缺点是:

    Proxy 对象在旧版浏览器(例如 IE11)中不可用,polyfill 无法完全复制 Proxy 功能。 代理对象与特殊对象(例如,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]

 

【讨论】:

我还要添加另一个缺点,您实际上并没有观察目标对象的变化,而只观察代理对象。在某些情况下,您只想知道目标对象的属性何时更改(即target.hello_world = "test" 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 来实现,这是代理的主要用例之一。 不,因为您正在直接修改目标。如果要观察对target 的修改,则必须通过代理进行。但是proxy.hello_world = "test" 并不意味着您正在修改代理,代理保持不变,target 被修改(如果您的设置处理程序配置为这样做)。听起来你的意思是你不能直接观察target.hello_world = "test"。那是真实的。普通变量赋值不会发出任何类型的事件。这就是为什么我们必须使用这个问题的答案中描述的工具。 谢谢 Elliot B。It sounds like your point is that you cannot directly observe target.hello_world = "test". That is true. 这正是我的意思。在我的情况下,我在其他地方创建了一个对象并由其他代码更新......在这种情况下,代理没有用,因为更改将在目标上完成。 @Cristiano 我猜 Elliot 想说的是您可以使用代理 而不是 实际对象,这意味着您可以像传递代理一样传递代理对象并使应用程序的其他部分与您的代理交互。代理上的更改将反映在实际对象上。【参考方案15】:

这是不可能的。

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

下面的方法接受一个变量名数组作为输入,并为每个变量添加事件侦听器,并为变量值的任何更改触发事件。

该方法使用轮询来检测值的变化。您可以以毫秒为单位增加超时值。

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

通过调用方法:

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

它会检测变量username 和userid 的变化。

【讨论】:

【参考方案16】:

这是一个旧线程,但我在寻找使用 Angular 的解决方案时偶然发现了第二高的答案(自定义侦听器)。虽然该解决方案有效,但 Angular 具有更好的内置方法来使用 @Output 和事件发射器解决此问题。离开自定义侦听器答案中的示例:

子组件.html

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

子组件.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);

父组件.html

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

父组件.ts

public n: number = 0;

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

现在每次单击子按钮时都会更新父级上的n。工作stackblitz

【讨论】:

【参考方案17】:

请大家记住最初的问题是针对变量的,而不是针对对象的;)

除了以上所有答案,我还创建了一个名为 forTheWatch.js 的小型库, 使用相同的方式来捕获和回调 javascript 中普通全局变量的变化。

兼容JQUERY变量,无需使用OBJECTS,如果需要可以直接传递多个变量的ARRAY。

如果有帮助的话……: https://bitbucket.org/esabora/forthewatch 基本上你只需要调用函数:watchIt("theVariableToWatch", "varChangedFunctionCallback");

如果不相关,请提前道歉。

【讨论】:

【参考方案18】:

在getter 和setter 的帮助下,你可以定义一个JavaScript 类来做这样的事情。

首先,我们定义名为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;
  

假设我们想监听money的变化,让我们用初始资金0创建一个MonitoredVariable的实例:

const money = new MonitoredVariable(0);

然后我们可以使用money.val获取或设置它的值:

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

由于我们没有为它定义任何监听器,money.val 更改为 2 后没有什么特别的事情发生。

现在让我们定义一些监听器。我们有四个可用的听众:beforeSetbeforeChangeafterChangeafterSet。 当您使用money.val = newValue 更改变量的值时,将依次发生以下情况:

    money.beforeSet(newValue, oldValue); money.beforeChange(newValue, oldValue); (如果它的值没有改变,将被跳过) money.val = newValue; money.afterChange(newValue, oldValue); (如果它的值没有改变,将被跳过) money.afterSet(newValue, oldValue);

现在我们定义afterChange监听器,它只在money.val发生变化后触发(而afterSet即使新值与旧值相同也会被触发):

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

现在设置一个新值3,看看会发生什么:

money.val = 3;

您将在控制台中看到以下内容:

Money has been changed from 2 to 3

完整代码见https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab。

【讨论】:

太棒了......我想扩展这个代码示例,只是想知道什么是代码 sn-p 许可证。 +1 使其可通过类以及处理程序之前和之后重用【参考方案19】:

我找到的最简单的方法,从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);
  
;

然后:

state.pageNumber = 0;
// watch the console

state.pageNumber = 15;
// watch the console

【讨论】:

【参考方案20】:

就我而言,我试图找出我在项目中包含的任何库是否重新定义了我的window.player。所以,在我的代码开始时,我只是这样做了:

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

【讨论】:

【参考方案21】:

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

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

在这里声明一个简单的对象来保存我的变量的值:

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

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

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

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

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

【讨论】:

【参考方案22】:

我来这里是为 node js 寻找相同的答案。所以这里是

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

我正在做的是在值更改时设置一些事件发射器并使用 do while 循环来检测它。

【讨论】:

【参考方案23】:

这就是我所做的:调用 JSON.stringify 两次并比较两个字符串...

缺点:

你只能知道整个对象是否发生变化 您必须手动检测更改 您最好在对象中只包含原始字段(没有属性,没有函数...)

【讨论】:

【参考方案24】:

我搜索了 JavaScript 双向数据绑定库,遇到了this one。

我没有成功使它在DOM to variable 方向上工作,但在variable to DOM 方向上它可以工作,这就是我们需要的。

我稍微重写了它,因为原始代码很难阅读(对我来说)。它用 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.

JSFiddle with original code.

在提供的示例中,对象xa 属性由setInterval 更新,text 输入的值也自动更新。如果还不够,而 event 是您要查找的内容,则可以将 onchange 侦听器添加到上述输入中。如果需要,也可以隐藏输入。

【讨论】:

能否使用 MutationObserver 来实现双向绑定的另一端? Object.define() 将变量绑定到 DOM,MutationObserver 将 DOM 绑定到变量。 另外,您的代码在按原样运行时会为我抛出此错误:Uncaught TypeError: Cannot use 'in' operator to search for 'value' in undefined @BrandonMcConnell。双向绑定在这里不是重点。也许你可以用 MutationObserver 做到这一点。它可以通过支持它的 DOM 元素的简单 Change 事件以及 div 和 span 的 DOMSubtreeModified 来完成。我不能说为什么你的代码不起作用。检查 jSfiddle - 它可以工作。 双向绑定是这里的重点。您在解决方案中提到您“搜索了一个 JavaScript 双向数据绑定库”【参考方案25】:

问题是关于变量,而不是对象属性!所以我的方法是利用窗口 object 及其自定义 getter/setter,然后使用/更改变量像“普通”变量(不像对象属性)。

最简单的方法是@José Antonio Postigo 在他的回答中(我投了那个答案)。我想在这里做的是将其简化为更简单的“创建者”功能(因此即使是不了解对象获取器/设置器的人也可以轻松使用它)。

这里有一个活生生的例子:https://codepen.io/dimvai/pen/LYzzbpz

这是您必须具备的一般“创建者”功能:

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

然后,不要使用 varlet 来声明变量,而是使用:

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

或者,为了使用您的自定义回调(而不是默认的 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);

就是这样!现在,您可以将其更改为“普通”变量:

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

【讨论】:

以上是关于监听 JavaScript 中的变量变化的主要内容,如果未能解决你的问题,请参考以下文章

如何监听JS数组的变化

react监听仓库数据变化

Vue watch监听 date中的变量 与 数组或者对象的数据变化

监听 javascript 对象的变化

vue 中的computed 和 watch 监听

如何监听 js 中变量的变化