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


当前回答

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

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

其他回答

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

这是不可能的。

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

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

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

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的更改。

No.

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

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

这是一个老线程,但我在使用Angular寻找解决方案时偶然发现了第二高的答案(自定义侦听器)。虽然这个解决方案是可行的,但angular有一个更好的内置方法来解决这个问题,使用@Output和事件发射器。在自定义监听器回答中的例子:

ChildComponent.html

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

ChildComponent.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

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

ParentComponent.ts

public n: number = 0;

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

现在每次单击子按钮时都会更新非父按钮。工作stackblitz

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

然而,我想建议一些修改(这就是为什么我在这里发帖……)

这段代码的一个问题是对象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建议的相同方法来创建和添加侦听器,但是您可以放心,没有可能的方法来读取或写入一个未被注意到的对象!

以编程方式添加封装的字段

继续Luke的思路,我现在提出一种简单的方法,通过简单的函数调用将封装的字段和各自的getter /setter添加到对象中。

注意,这只适用于值类型。为了与引用类型一起工作,必须实现某种深度复制(例如,请参阅这个)。

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