Возможно ли иметь событие в JS, которое срабатывает при изменении значения определенной переменной? JQuery принимается.
variable
, но все ответы здесь относятся property
. Интересно, можем ли мы прислушиваться к local variable
изменениям?
Возможно ли иметь событие в JS, которое срабатывает при изменении значения определенной переменной? JQuery принимается.
variable
, но все ответы здесь относятся property
. Интересно, можем ли мы прислушиваться к local variable
изменениям?
Ответы:
Да, это теперь вполне возможно!
Я знаю, что это старый поток, но теперь этот эффект возможен с использованием методов доступа (геттеры и сеттеры): 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/
Вы также можете использовать массив слушателей вместо одного слота слушателя, но я хотел бы привести вам самый простой пример.
this.aListener(val)
вам придется циклически перебирать все функции слушателя и вызывать каждую проходящую функцию val
. Как правило, метод вызывается addListener
вместо registerListener
.
Большинство ответов на этот вопрос либо устарели, либо неэффективны, либо требуют включения больших раздутых библиотек:
Сегодня вы можете использовать объект Proxy для мониторинга (и перехвата) изменений, внесенных в объект. Он специально создан для того, что пытается сделать ОП. Вот основной пример:
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
- это можно сделать с помощью a Proxy
, это один из основных вариантов использования прокси.
target
, то вы должны сделать это через прокси. Однако proxy.hello_world = "test"
это не означает, что вы модифицируете прокси, прокси остается неизменным, target
модифицируется (если ваш установленный обработчик настроен для этого). Похоже, ваша точка зрения в том, что вы не можете наблюдать напрямую target.hello_world = "test"
. Это правда. Простые присвоения переменных не генерируют никаких событий. Вот почему мы должны использовать инструменты, подобные описанным в ответах на этот вопрос.
It sounds like your point is that you cannot directly observe target.hello_world = "test". That is true.
, это точно моя точка зрения. В моем случае у меня есть объект, созданный где-то еще и обновленный каким-то другим кодом ... прокси-сервер в этом случае бесполезен, так как изменения будут внесены в цель.
Нет.
Но, если это действительно так важно, у вас есть 2 варианта (первый проверен, второй нет):
Во-первых, используйте сеттеры и геттеры, вот так:
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);
С помощью 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.
Извините, что поднял старую ветку, но вот небольшое руководство для тех, кто (как и я!) Не видит, как работает пример Эли Грея:
var test = new Object();
test.watch("elem", function(prop,oldval,newval){
//Your code
return newval;
});
Надеюсь, что это может помочь кому-то
Как ответ Люка Шефера ( примечание : это относится к его первоначальному сообщению; но весь смысл здесь остается действительным после редактирования ), я бы также предложил пару методов 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
};
})();
Теперь вы можете использовать тот же метод для создания и добавления слушателей, как предложил Люк, но вы можете быть уверены, что нет никакого способа читать или писать, чтобы a
остаться незамеченным!
Продолжая следить за Люком, я предлагаю теперь простой способ добавления инкапсулированных полей и соответствующих методов получения / установки к объектам с помощью простого вызова функции.
Обратите внимание, что это будет правильно работать только с типами значений . Для этого , чтобы работать с ссылочными типами , какое - то глубокая копии должно быть выполнено (см этой , например).
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 />.
<input/>
элементу?
<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');
AngularJS
(Я знаю, что это не так JQuery
, но это может помочь. [Чистый JS хорош только в теории]):
$scope.$watch('data', function(newValue) { ..
где «данные» - это имя вашей переменной в области видимости.
Есть ссылка на док.
$scope.$apply()
запуске
Для тех, кто занимается тюнингом через пару лет:
Доступно решение для большинства браузеров (и IE6 +), которое использует событие onpropertychange и более новую спецификацию defineProperty. Небольшая проблема в том, что вам нужно сделать вашу переменную объектом dom.
Полная информация:
http://johndyer.name/native-browser-get-set-properties-in-javascript/
Функциональность, которую вы ищете, может быть достигнута с помощью метода 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);
Довольно простое и упрощенное решение состоит в том, чтобы просто использовать вызов функции для установки значения глобальной переменной и никогда не устанавливать ее значение напрямую. Таким образом, вы имеете полный контроль:
var globalVar;
function setGlobalVar(value) {
globalVar = value;
console.log("Value of globalVar set to: " + globalVar);
//Whatever else
}
Нет никакого способа обеспечить это, это просто требует дисциплины программирования ... хотя вы можете использовать grep
(или что-то подобное), чтобы проверить, что нигде ваш код не устанавливает непосредственно значениеglobalVar
.
Или вы можете инкапсулировать его в объектный и пользовательский методы getter и setter ... просто мысль.
var
в модулях ES6 - это единственное решение.
Пожалуйста, ребята, помните, что первоначальный вопрос был для ПЕРЕМЕННЫХ, а не для ОБЪЕКТОВ;)
в дополнение ко всем ответам выше я создал крошечную библиотеку forTheWatch.js , которая использует тот же способ для отлова и обратного вызова для изменений в обычных глобальных переменных в javascript.
Совместим с переменными JQUERY, нет необходимости использовать OBJECTS, и вы можете при необходимости напрямую передавать массив из нескольких переменных.
Если это может быть полезно ...:
https://bitbucket.org/esabora/forthewatch
В основном вам просто нужно вызвать функцию:
watchIt("theVariableToWatch", "varChangedFunctionCallback");
И извините заранее, если не актуально.
Самый простой способ, который я нашел, начиная с этого ответа :
// 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;
}
});
Это не возможно напрямую.
Однако это можно сделать с помощью 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.
С помощью 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
изменения, давайте создадим экземпляр MonitoredVariable
с начальными деньгами 0
:
const money = new MonitoredVariable(0);
Тогда мы могли бы получить или установить его значение, используя money.val
:
console.log(money.val); // Get its value
money.val = 2; // Set its value
Поскольку мы не определили слушателей для него, ничего особенного не происходит после money.val
изменений в 2.
Теперь давайте определимся с некоторыми слушателями. У нас есть четыре слушателей доступны: beforeSet
, beforeChange
, afterChange
, afterSet
. Следующее произойдет последовательно, когда вы используете 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;
В консоли вы увидите следующее:
Money has been changed from 2 to 3
Полный код см. По адресу https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab .
Это старая тема, но я наткнулся на второй по величине ответ (пользовательские слушатели), когда искал решение с использованием 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);
}
Теперь это будет обновлять n
родительский элемент при каждом нажатии дочерней кнопки. Рабочий стекблиц
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]);}
});
}