É possível ter um evento em JS que é acionado quando o valor de uma determinada variável é alterado? JQuery é aceito.
variable
, mas todas as respostas aqui se referem property
. Eu me pergunto se podemos ouvir local variable
mudanças.
É possível ter um evento em JS que é acionado quando o valor de uma determinada variável é alterado? JQuery é aceito.
variable
, mas todas as respostas aqui se referem property
. Eu me pergunto se podemos ouvir local variable
mudanças.
Respostas:
Sim, isso agora é completamente possível!
Sei que esse é um thread antigo, mas agora esse efeito é possível usando acessadores (getters e setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters
Você pode definir um objeto como este, no qual aInternal
representa o campo 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;
}
}
Em seguida, você pode registrar um ouvinte usando o seguinte:
x.registerListener(function(val) {
alert("Someone changed the value of x.a to " + val);
});
Portanto, sempre que algo mudar o valor de x.a
, a função de ouvinte será acionada. A execução da seguinte linha exibirá o pop-up de alerta:
x.a = 42;
Veja um exemplo aqui: https://jsfiddle.net/5o1wf1bn/1/
Você também pode usar uma matriz de ouvintes em vez de um único slot de ouvinte, mas eu queria dar o exemplo mais simples possível.
this.aListener(val)
, você terá que percorrer todas as funções do ouvinte e chamar cada uma delas de passagem val
. Normalmente, o método é chamado em addListener
vez de registerListener
.
A maioria das respostas a esta pergunta está desatualizada, ineficaz ou requer a inclusão de grandes bibliotecas inchadas:
Hoje, agora você pode usar o objeto Proxy para monitorar (e interceptar) as alterações feitas em um objeto. Foi desenvolvido especificamente para o que o OP está tentando fazer. Aqui está um exemplo básico:
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'
Os únicos inconvenientes do Proxy
objeto são:
Proxy
objeto não está disponível em navegadores mais antigos (como o IE11) e o polyfill não pode replicar totalmente a Proxy
funcionalidade.Date
) - o Proxy
objeto é melhor emparelhado com objetos ou matrizes simples.Se você precisar observar as alterações feitas em um objeto aninhado , precisará usar uma biblioteca especializada, como o Observable Slim (que publiquei), que funciona assim:
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
- isso não é bem preciso. O Proxy
objeto não é modificado - ele não possui sua própria cópia do destino. you just want to know when a property change on the target object
- você pode fazer isso com a Proxy
, esse é um dos principais casos de uso de proxies.
target
, precisará fazê-lo através de um proxy. No entanto, proxy.hello_world = "test"
isso não significa que você está modificando o proxy, o proxy permanece inalterado, target
é modificado (se o manipulador de conjunto estiver configurado para isso). Parece que o seu ponto é que você não pode observar diretamente target.hello_world = "test"
. Isso é verdade. Designações de variáveis simples não emitem nenhum tipo de evento. É por isso que temos que usar ferramentas como as descritas nas respostas a esta pergunta.
It sounds like your point is that you cannot directly observe target.hello_world = "test". That is true.
Esse é exatamente o meu ponto. No meu caso, eu tenho um objeto criado em outro lugar e fui atualizado por outro código ... um proxy, nesse caso, não é útil, pois as alterações serão feitas no destino.
Não.
Mas, se é realmente tão importante, você tem 2 opções (a primeira é testada, a segunda não):
Primeiro, use setters e getters, assim:
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);
então você pode fazer algo como:
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));
}
defina o ouvinte como:
listen_to(myobj, "a", function(oldval, newval) {
alert("old : " + oldval + " new : " + newval);
}
Em segundo lugar, eu realmente esqueci, vou enviar enquanto penso sobre isso :)
EDIT: Ah, eu me lembro :) Você pode colocar um relógio no valor:
Dado myobj acima, com '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);
Usando 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
requer 2 argumentos, mas parte do seu exemplo mostra a função sendo chamada apenas com o parâmetro value.
Desculpe trazer um tópico antigo, mas aqui está um pequeno manual para quem (como eu!) Não vê como o exemplo de Eli Grey funciona:
var test = new Object();
test.watch("elem", function(prop,oldval,newval){
//Your code
return newval;
});
Espero que isso possa ajudar alguém
Como resposta de Luke Schafer ( nota : isso se refere ao seu post original; mas todo o ponto aqui permanece válido após a edição ), eu também sugeriria um par de métodos Get / Set para acessar seu valor.
No entanto, eu sugeriria algumas modificações (e é por isso que estou postando ...).
Um problema com esse código é que o campo a
do objeto myobj
é diretamente acessível, portanto, é possível acessá-lo / alterar seu valor sem acionar os ouvintes:
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!
Portanto, para garantir que os ouvintes sejam realmente chamados, teríamos de proibir esse acesso direto ao campo a
. Como fazer isso? Use um fechamento !
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
};
})();
Agora você pode usar o mesmo método para criar e adicionar os ouvintes que Luke propôs, mas tenha certeza de que não há como ler ou escrever para a
passar despercebido!
Ainda na trilha de Luke, proponho agora uma maneira simples de adicionar campos encapsulados e os respectivos getters / setters aos objetos por meio de uma simples chamada de função.
Observe que isso só funcionará corretamente com tipos de valor . Para que isso funcione com tipos de referência , algum tipo de cópia profunda teria que ser implementado (veja este , por exemplo).
function addProperty(obj, name, initial) {
var field = initial;
obj["get_" + name] = function() { return field; }
obj["set_" + name] = function(val) { field = val; }
}
Isso funciona da mesma maneira que antes: criamos uma variável local em uma função e, em seguida, criamos um fechamento.
Como usá-lo? Simples:
var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
Se você estiver usando jQuery {UI} (que todo mundo deveria usar :-)), poderá usar .change () com um elemento <input /> oculto.
<input/>
elemento oculto ?
<input type="hidden" value="" id="thisOne" />
e com jQuery $("#thisOne").change(function() { do stuff here });
e, em $("#thisOne").val(myVariableWhichIsNew);
seguida, o .change()
irá disparar.
var1 = 'new value';
, você definirá o valor dessa entrada oculta e adicionará um ouvinte para alterar a variável. $("#val1").on('change', function(){ val1 = this.val(); ... do the stuff that you wanted to do when val1 changed... }); $("#val1").val('new value');
AngularJS
(Eu sei que isso não é JQuery
, mas isso pode ajudar. [JS puro é bom apenas em teoria]):
$scope.$watch('data', function(newValue) { ..
onde "dados" é o nome da sua variável no escopo.
Há um link para doc.
$scope.$apply()
é executado
Para aqueles que sintonizam alguns anos depois:
Está disponível uma solução para a maioria dos navegadores (e IE6 +) que usa o evento onpropertychange e a especificação mais recente defineProperty. O problema é que você precisará tornar sua variável um objeto dom.
Detalhes completos:
http://johndyer.name/native-browser-get-set-properties-in-javascript/
A funcionalidade que você procura pode ser alcançada através do uso do método "defineProperty ()" - disponível apenas para navegadores modernos:
Eu escrevi uma extensão jQuery que possui algumas funcionalidades semelhantes se você precisar de mais suporte entre navegadores:
https://github.com/jarederaj/jQueue
Uma pequena extensão jQuery que lida com retornos de chamada da fila para a existência de uma variável, objeto ou chave. Você pode atribuir qualquer número de retornos de chamada a qualquer número de pontos de dados que possam ser afetados por processos em execução em segundo plano. O jQueue escuta e aguarda que esses dados especificados existam e, em seguida, dispara o retorno de chamada correto com seus argumentos.
//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();
}
};
ou
/* 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);
ou
/* 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);
Uma solução bastante simples e simplista é usar apenas uma chamada de função para definir o valor da variável global e nunca definir seu valor diretamente. Dessa forma, você tem controle total:
var globalVar;
function setGlobalVar(value) {
globalVar = value;
console.log("Value of globalVar set to: " + globalVar);
//Whatever else
}
Não há como impor isso, apenas requer disciplina de programação ... embora você possa usar grep
(ou algo semelhante) para verificar se em nenhum lugar o seu código define diretamente o valor deglobalVar
.
Ou você pode encapsulá-lo em um objeto e métodos getter e setter do usuário ... apenas um pensamento.
var
nos módulos ES6 - essa é a única solução.
Por favor, lembre-se de que a pergunta inicial era para VARIÁVEIS, não para OBJETOS;)
além de todas as respostas acima, criei uma pequena biblioteca chamada forWatch.js , que usa da mesma maneira para capturar e chamadas para alterações nas variáveis globais normais em javascript.
Compatível com variáveis JQUERY, não é necessário usar OBJETOS, e você pode transmitir diretamente um ARRAY de várias variáveis, se necessário.
Se puder ser útil ...:
https://bitbucket.org/esabora/forthewatch
Basicamente, basta chamar a função:
watchIt("theVariableToWatch", "varChangedFunctionCallback");
E desculpe antecipadamente, se não for relevante.
A maneira mais fácil que encontrei, partindo desta resposta :
// 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);
}
};
E depois:
state.pageNumber = 0;
// watch the console
state.pageNumber = 15;
// watch the console
No meu caso, eu estava tentando descobrir se alguma biblioteca que eu estava incluindo no meu projeto estava redefinindo o meu window.player
. Então, no início do meu código, eu apenas fiz:
Object.defineProperty(window, 'player', {
get: () => this._player,
set: v => {
console.log('window.player has been redefined!');
this._player = v;
}
});
Não é diretamente possível.
No entanto, isso pode ser feito usando o CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
O método abaixo aceita uma matriz de nomes de variáveis como uma entrada e adiciona um ouvinte de evento para cada variável e aciona o evento para qualquer alteração no valor das variáveis.
O método usa pesquisa para detectar a alteração no valor. Você pode aumentar o valor do tempo limite em milissegundos.
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);
}
Chamando o método:
watchVariables(['username', 'userid']);
Ele detectará as alterações nas variáveis username e userid.
Com a ajuda de getter e setter , você pode definir uma classe JavaScript que faz isso.
Primeiro, definimos nossa classe chamada 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;
}
}
Suponha que desejamos ouvir as money
alterações, vamos criar uma instância do MonitoredVariable
com dinheiro inicial 0
:
const money = new MonitoredVariable(0);
Então poderíamos obter ou definir seu valor usando money.val
:
console.log(money.val); // Get its value
money.val = 2; // Set its value
Como não definimos nenhum ouvinte para isso, nada de especial acontece após as money.val
alterações em 2.
Agora vamos definir alguns ouvintes. Temos quatro ouvintes disponíveis: beforeSet
, beforeChange
, afterChange
, afterSet
. O seguinte ocorrerá sequencialmente quando você usar money.val = newValue
para alterar o valor da variável:
Agora, definimos o afterChange
ouvinte que será acionado somente após a money.val
alteração (while afterSet
será acionado mesmo que o novo valor seja o mesmo que o antigo):
money.afterChange = (newValue, oldValue) => {
console.log(`Money has been changed from ${oldValue} to ${newValue}`);
};
Agora defina um novo valor 3
e veja o que acontece:
money.val = 3;
Você verá o seguinte no console:
Money has been changed from 2 to 3
Para obter o código completo, consulte https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab .
Este é um tópico antigo, mas me deparei com a segunda resposta mais alta (ouvintes personalizados) enquanto procurava uma solução usando o Angular. Enquanto a solução funciona, o angular tem uma maneira melhor construída de resolver esse problema usando @Output
emissores e eventos. Saindo do exemplo na resposta do ouvinte personalizado:
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);
}
Agora, isso será atualizado n
no pai cada vez que o botão filho for clicado. Stackblitz de trabalho
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]);}
});
}