É 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 variablemudanç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 variablemudanç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 aInternalrepresenta 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 addListenervez 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 Proxyobjeto são:
Proxyobjeto não está disponível em navegadores mais antigos (como o IE11) e o polyfill não pode replicar totalmente a Proxyfuncionalidade.Date) - o Proxyobjeto é 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 Proxyobjeto 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>
varwrequer 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 ado 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 apassar 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.
varnos 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 moneyalterações, vamos criar uma instância do MonitoredVariablecom 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.valalteraçõ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 = newValuepara alterar o valor da variável:
Agora, definimos o afterChangeouvinte que será acionado somente após a money.valalteração (while afterSetserá 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 3e 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 @Outputemissores 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 nno 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]);}
});
}