Чи можлива подія в JS, яка запускається, коли значення певної змінної змінюється? JQuery прийнято.
variable
, але всі відповіді тут стосуються property
. Цікаво, чи ми можемо прислухатися до local variable
змін.
Чи можлива подія в JS, яка запускається, коли значення певної змінної змінюється? JQuery прийнято.
variable
, але всі відповіді тут стосуються property
. Цікаво, чи ми можемо прислухатися до local variable
змін.
Відповіді:
Так, це зараз цілком можливо!
Я знаю, що це стара тема, але тепер цей ефект можливий за допомогою аксесуарів (getters і setters): 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
- ви можете досягти цього за допомогою 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.
Це якраз моя думка. У моєму випадку у мене є об’єкт, створений десь в іншому місці, і оновлений іншим кодом ... проксі, в цьому випадку, не корисний, оскільки зміни будуть зроблені в цільовому.
Ні.
Але, якщо це дійсно так важливо, у вас є два варіанти (перший тестується, другий - ні):
По-перше, використовуйте сетери та геттери, наприклад:
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);
}
По-друге, я фактично забув, я підкажу, поки я думаю про це :)
EDIT: О, я пам'ятаю :) Ви можете поставити годинник на значення:
Враховуючи 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 аргументів, але частина вашого прикладу показує функцію, яка викликається лише параметром значення.
Вибачте, що знайдете стару тему, але ось невеликий посібник для тих, хто (як я!) Не бачить, як працює приклад Елі Грей:
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) { ..
де "data" - ім'я вашої змінної в області.
$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
.
Або ви могли б інкапсулювати його в об єкт та користувальницькі способи отримання та встановлення ... просто думка.
var
у модулях ES6 - це єдине рішення.
Згадайте, хлопці, пам'ятайте, що початкове запитання було для VARIABLES, а не для OBJECTS;)
на додаток до всіх відповідей вище, я створив крихітну губку, яку називають forTheWatch.js , яка використовує той самий спосіб лову та зворотного виклику для зміни звичайних глобальних змінних у JavaScript.
Сумісний зі змінними JQUERY, не потрібно використовувати OBJECTS, і ви можете передати безпосередньо ARRAY з декількох змінних, якщо це необхідно.
Якщо це може бути корисно ...:
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']);
Він виявить зміни змінних ім'я користувача та userid.
За допомогою геттера та сеттера ви можете визначити клас 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. Хоча рішення працює, кутовий має кращий спосіб вирішити це за допомогою @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]);}
});
}