监听JavaScript中的变量变化

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

529863 次浏览

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

不。

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

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

作为卢克·谢弗的回答 (备注:这是指他原来的帖子;但在编辑之后,整个观点仍然有效),我还建议使用一对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建议的相同方法来创建和添加侦听器,但是您可以放心,不可能不被注意地读取或写入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; }
}

这与之前的工作相同:我们在函数上创建一个局部变量,然后创建一个闭包。

如何使用它?简单:

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/>元素。

对于那些几年后收听的人来说:

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

详情:

http://johndyer.name/native-browser-get-set-properties-in-javascript/ < a href = " http://johndyer.name/native-browser-get-set-properties-in-javascript/ " > < / >

很抱歉提出了一个旧的线程,但这里有一个小手册,为那些(像我一样!)不明白Eli Grey的例子是如何工作的:

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

希望这能帮助到一些人

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

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

其中“data”是作用域内变量的名称。

有一个链接到文档。

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

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

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

https://github.com/jarederaj/jQueue

对象的队列回调的jQuery小扩展 变量、对象或键的存在。你可以分配任意数量的 对可能受影响的任意个数的数据点的回调 进程在后台运行。jQueue监听并等待 您指定的这些数据开始存在,然后发射

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

使用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) {
/**
* 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>

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

var globalVar;


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

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

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

是的,现在这是完全可能的!

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

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

这个问题最初发布于2009年,现有的大多数答案要么过时,无效,要么需要包含庞大的库:

  • Object.watchObject.observe都已弃用,不应该使用。
  • onPropertyChange是一个DOM元素事件处理程序,只适用于某些版本的IE。
  • Object.defineProperty允许您使对象属性不可变,这将允许您检测尝试的更改,但它也将阻止任何更改。
  • 定义setter和getter可以工作,但是它需要大量的设置代码,并且当您需要删除或创建新属性时,它不能很好地工作。

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

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


 

这是不可能的。

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

这是一个老线程,但我在使用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);
}

现在每次单击子按钮时,都会更新父节点上的non。# EYZ1工作

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

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

与JQUERY变量兼容,不需要使用object,如果需要,可以直接传递几个变量的ARRAY。

如果有帮助…: 基本上你只需要调用函数:
# EYZ0
< / p >

如果不相关,先说句抱歉。

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的变化,让我们用初始资金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改变变量的值时,以下将依次发生:

  1. 钱。beforeSet (newValue oldValue);
  2. 钱。beforeChange (newValue oldValue);(如果它的值没有改变,将被跳过)
  3. 钱。val = newValue;
  4. 钱。afterChange (newValue oldValue);(如果它的值没有改变,将被跳过)
  5. 钱。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

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

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

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

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

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

有人提出了一个使用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块也会执行。

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

我来这里是为了寻找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循环来检测它。

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

缺点:

  • 你只能知道整个物体是否发生了变化
  • 您必须手动检测更改
  • 你最好在对象中只有基本字段(没有属性,没有函数……)

我搜索了JavaScript双向数据绑定库,发现了这一个

我没有成功地使它在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原始代码

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

问题是关于变量,而不是对象属性!因此,我的方法是利用窗口对象及其自定义getter /setter,然后使用/更改变量像一个“normal”;变量(不像对象属性)。

最简单的方法是@José Antonio Postigo在他的回答中(我投票了那个答案)。我在这里想做的,是把它简化成一个更简单的“创造者”。函数(因此,即使不理解对象getter /setter的人也可以轻松使用它)。

一个活生生的例子是: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;
};

然后,不要使用var来声明变量,而是使用下面的语句:

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

就是这样!现在,你可以像“normal”一样更改它;变量:

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

基于akira的回答,我补充说,你可以通过listerner操纵dom。

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

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:

<div id="showNumber">
 

</div>




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

当变量x.a发生变化时,registerListener方法将被触发。

@akira和@mo-d-genesis的解决方案可以进一步简化,因为在这个例子中DOM操作不依赖于state:

< a href = " https://codepen.io/andrekovac/pen/eYVPXNV?编辑=1010" rel="nofollow noreferrer">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;

对应的html:

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


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

# EYZ0:

  1. 我重命名了变量和函数,以更好地反映它们的语义。
  2. 供参考:苗条的通过改变变量提供了非常类似的反应行为

是一个理想的答案,但是它所做的是在JavaScript中每100毫秒设置一个间隔,并检查变量是否被改变,当它被改变时,它会做一些事情(OP想要的任何事情),然后清除间隔,所以它有点模拟OP的要求。

let myvar = "myvar";


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

现在,如果myvar被更改为其他内容,那么这个程序将显示"My precious var has been changed!":)

这是一个古老而伟大的问题,已经超过12年了。此外,有很多方法来解决它。然而,大多数都是复杂的或使用旧的JS概念,我们在2022年,我们可以使用ES6来改进我们的代码。

我将实现我经常使用的两个主要解决方案。

简单的变量

如果我们有一个简单的变量,我们不关心重用,那么我们可以将变量声明为一个对象。我们定义了setget方法和listener属性来处理“change"事件。

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>

用于重用的高级类

如果我们有多个变量,并且需要监控它们,我们可以创建一个class,然后将它应用到我们的变量上。我建议添加两个监听器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>