听取JavaScript中的变量更改

时间:2009-11-18 23:58:15

标签: javascript javascript-events jquery

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

24 个答案:

答案 0 :(得分:192)

是的,object.watch(虽然这是非标准的)。 Here's my implementation适用于当前所有主流浏览器。

答案 1 :(得分:63)

是的,现在完全有可能!

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

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

答案 2 :(得分:34)

没有

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

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

答案 3 :(得分:28)

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

今天,您现在可以使用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对象最好与普通对象或数组配对。
  3. 如果您需要观察对嵌套对象所做的更改,那么您需要使用专门的库,例如 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}}]
    

答案 4 :(得分:23)

使用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>

答案 5 :(得分:22)

很抱歉打开一个旧帖子,但这里有一本小手册(对我来说!)看不到Eli Gray的例子如何工作:

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

希望这可以帮助某人

答案 6 :(得分:7)

作为Luke Schafer's answer 注意 :这是指他的原始帖子;但此处的整点在编辑后仍然有效 ),我还建议使用一对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!

封装

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

答案 7 :(得分:7)

如果您正在使用jQuery {UI}(每个人都应该使用:-)),您可以使用带有隐藏&lt; input /&gt;的.change()。元件。

答案 8 :(得分:6)

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

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

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

有一个link to doc.

答案 9 :(得分:5)

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

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

完整详情:

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

答案 10 :(得分:2)

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

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

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

https://github.com/jarederaj/jQueue

  

一个小的jQuery扩展,用于处理排队的回调   存在变量,对象或密钥。您可以指定任意数量的   回调可能受到影响的任意数量的数据点   进程在后台运行。 jQueue会倾听并等待   您指定的这些数据是否存在然后触发了   正确的回调及其参数。

答案 11 :(得分:2)

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

有人建议使用设置器设置值的简单解决方案。

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

import pandas as pd

df = pd.DataFrame({"city":[1,1], "A":[5,6]})
print(df.columns.tolist())

对象包含一个set方法,通过它可以更改值。但是它也在那里调用了['city', 'A'] 方法。现在将定义它。

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

现在,每当我执行getOnChange()时,就会触发variableObject.getOnChange = function() { if(this.value) { // do some stuff } } 方法,并且如果该值是根据需要设置的(在我的情况下为variableObject.set(true)),则if块也会执行。

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

答案 12 :(得分:2)

以我为例,我试图确定项目中包含的任何库是否正在重新定义window.player。因此,在我的代码开始时,我只是做了:

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

答案 13 :(得分:2)

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

答案 14 :(得分:2)

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

答案 15 :(得分:1)

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

var globalVar;

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

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

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

答案 16 :(得分:1)

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

答案 17 :(得分:0)

这是一个旧线程,但是我在寻找使用Angular的解决方案时偶然发现了第二高的答案(自定义侦听器)。虽然该解决方案有效,但使用@Output和事件发射器可以更好地构建angular解决方案。从自定义侦听器答案中的示例开始:

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

这将在每次单击子按钮时在父上更新n。工作stackblitz

答案 18 :(得分:0)

借助gettersetter,您可以定义一个执行此类操作的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的变化,让我们创建一个初始资金为MonitoredVariable的{​​{1}}的实例:

0

然后我们可以使用const money = new MonitoredVariable(0); 获取或设置其值:

money.val

由于我们尚未为其定义任何侦听器,因此console.log(money.val); // Get its value money.val = 2; // Set its value 更改为2后不会发生任何特殊情况。

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

  1. money.beforeSet(newValue,oldValue);
  2. money.beforeChange(newValue,oldValue); (如果其值未更改,将被跳过)
  3. money.val = newValue;
  4. money.afterChange(newValue,oldValue); (如果其值未更改,将被跳过)
  5. money.afterSet(newValue,oldValue);

现在我们定义money.val = newValue侦听器,仅在afterChange更改后才触发(而money.val会被触发,即使新值与旧值相同):

afterSet

现在设置一个新值money.afterChange = (newValue, oldValue) => { console.log(`Money has been changed from ${oldValue} to ${newValue}`); }; ,然后看看会发生什么:

3

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

money.val = 3;

有关完整代码,请参见https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab

答案 19 :(得分:0)

这不可能直接实现。

但是,可以使用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的更改。

答案 20 :(得分:0)

我来到这里为节点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循环进行检测。

答案 21 :(得分:-1)

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

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

与JQUERY变量兼容,无需使用OBJECTS,并且如果需要,您可以直接传递多个变量的数组。

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

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

答案 22 :(得分:-1)

我搜索了 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.

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

答案 23 :(得分:-3)

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