web-dev-qa-db-fra.com

Écoute des modifications variables en JavaScript

Est-il possible d'avoir un événement dans JS qui se déclenche lorsque la valeur d'une variable donnée change? JQuery est accepté.

398
rashcroft22

Oui, object.watch (non standard cependant). Voici mon implémentation qui fonctionne dans tous les principaux navigateurs actuels.

190
Eli Grey

Oui, c'est maintenant tout à fait possible!

Je sais que c'est un vieux fil, mais cet effet est maintenant possible à l'aide d'accesseurs (getters et setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters

Vous pouvez définir un objet comme celui-ci, dans lequel aInternal représente le champ 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;
  }
}

Ensuite, vous pouvez enregistrer un auditeur en utilisant les éléments suivants:

x.registerListener(function(val) {
  alert("Someone changed the value of x.a to " + val);
});

Ainsi, chaque fois que quelque chose change la valeur de x.a, la fonction d'écoute est activée. Exécuter la ligne suivante amènera le popup d'alerte:

x.a = 42;

Voir un exemple ici: https://jsfiddle.net/5o1wf1bn/1/

Vous pouvez également utiliser un groupe d'écouteurs au lieu d'un seul logement d'écoute, mais je voulais vous donner l'exemple le plus simple possible.

69
Akira

La plupart des réponses à cette question sont soit obsolètes, inefficaces, soit nécessitent l’inclusion de bibliothèques volumineuses:

  • Object.watch et Object.observe sont tous deux obsolètes et ne doivent pas être utilisés.
  • onPropertyChange est un gestionnaire d'événement d'élément DOM qui ne fonctionne que dans certaines versions de IE.
  • Object.defineProperty vous permet de rendre une propriété d'objet immuable, ce qui vous permettrait de détecter les tentatives de modification, mais bloquera également toute modification.
  • Définir des setters et des getters fonctionne, mais cela nécessite beaucoup de code d'installation et cela ne fonctionne pas bien lorsque vous devez supprimer ou créer de nouvelles propriétés.

Aujourd'hui, , vous pouvez maintenant utiliser l'objet proxy pour surveiller (et intercepter) les modifications apportées à un objet. Il est conçu spécialement pour ce que le PO tente de faire. Voici un exemple de base:

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'

Les seuls inconvénients de l'objet Proxy sont les suivants:

  1. L'objet Proxy n'est pas disponible dans les navigateurs plus anciens (tels que IE11) et la fonctionnalité polyfill ne peut pas répliquer intégralement Proxy.
  2. Les objets mandataires ne se comportent pas toujours comme prévu avec des objets spéciaux (par exemple, Date) - l’objet Proxy est mieux associé à des objets ou des tableaux simples.

Si vous devez observer les modifications apportées à un objet imbriqué , vous devez utiliser une bibliothèque spécialisée telle que - Observable Slim (que j'ai publié) qui fonctionne comme ceci:

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}}]
34
Elliot B.

Non.

Mais, si c'est vraiment important, vous avez 2 options (la première est testée, la deuxième ne l'est pas):

Premièrement, utilisez les setters et les getters, comme ceci:

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

alors vous pouvez faire quelque chose comme:

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

puis définissez l'auditeur comme suit:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}

Deuxièmement, j'ai en fait oublié, je vais soumettre pendant que j'y réfléchis :)

EDIT: Oh, je m'en souviens :) Vous pourriez mettre une montre sur la valeur:

Étant donné myobj ci-dessus, avec 'a' dessus:

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);
33
Luke Schafer

Utilisation de 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>

Autre exemple

// 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>
23
Eduardo Cuomo

Désolé de faire apparaître un ancien fil de discussion, mais voici un petit manuel pour ceux qui (comme moi!) Ne voient pas comment fonctionne l'exemple d'Eli Grey:

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

J'espère que cela peut aider quelqu'un

21

Comme réponse de Luke Schafer (note: cela fait référence à son message original; mais le point entier reste valable après la modification), je suggérerais également une paire de méthodes Get/Set pour accéder à votre valeur.

Cependant, je suggérerais quelques modifications (et c'est pourquoi je poste ...).

Un problème avec ce code est que le champ a de l'objet myobj est directement accessible, il est donc possible d'y accéder/de changer sa valeur sans déclencher les écouteurs:

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!

Encapsulation

Donc, pour garantir que les auditeurs sont effectivement appelés, nous devrions interdire cet accès direct au champ a. Comment le faire Utilisez un fermeture!

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
    };
})();

Maintenant, vous pouvez utiliser la même méthode pour créer et ajouter les écouteurs que Luke a proposé, mais vous pouvez être assuré qu'il n'y a aucun moyen possible de lire ou d'écrire sur a en passant inaperçu!

Ajout de champs encapsulés par programme

Toujours sur la piste de Luke, je propose maintenant un moyen simple d’ajouter des champs encapsulés et les getters/régleurs respectifs aux objets au moyen d’un simple appel de fonction.

Notez que cela ne fonctionnera correctement qu'avec types de valeur. Pour que cela fonctionne avec types de référence, une sorte de copie profonde doit être implémenté (voir celui-ci , par exemple).

function addProperty(obj, name, initial) {
    var field = initial;
    obj["get_" + name] = function() { return field; }
    obj["set_" + name] = function(val) { field = val; }
}

Cela fonctionne comme avant: nous créons une variable locale sur une fonction, puis nous créons une fermeture.

Comment l'utiliser? Facile:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
7
Bruno Reis

AngularJS (Je sais que ce n'est pas JQuery, mais cela pourrait aider. [Pure JS est bon en théorie seulement]):

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

où "data" est le nom de votre variable dans la portée.

Il y a un lien vers doc.

6
ses

Si vous utilisez jQuery {UI} (que tout le monde devrait utiliser :-)), vous pouvez utiliser .change () avec un élément <input /> caché.

6
Chuck Han

Pour ceux qui accordent dans quelques années plus tard:

Une solution disponible pour la plupart des navigateurs (et IE6 +) utilise l'événement onpropertychange et la spécification plus récente defineProperty. Le problème est que vous devrez transformer votre variable en objet dom.

Tous les détails:

http://johndyer.name/native-browser-get-set-properties-in-javascript/

5
MandoMando

La fonctionnalité que vous recherchez peut être obtenue en utilisant la méthode "defineProperty ()" - qui n'est disponible que pour les navigateurs modernes:

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

J'ai écrit une extension jQuery qui a des fonctionnalités similaires si vous avez besoin de davantage de support pour plusieurs navigateurs:

https://github.com/jarederaj/jQueue

Une petite extension jQuery qui gère la mise en file d'attente des rappels à l'existence d'une variable, d'un objet ou d'une clé. Vous pouvez affecter un nombre quelconque de rappels à un nombre quelconque de points de données susceptibles d'être affectés par des processus exécutés en arrière-plan. jQueue écoute et attend la création des données que vous spécifiez, puis déclenche le rappel correct avec ses arguments.

3
jarederaj

Pas directement: vous avez besoin d'un paire getter/setter avec une interface "addListener/removeListener" de quelque sorte ... ou un plugin NPAPI (mais c'est une autre histoire).

2
jldupont
//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);
1
Avatar

Une solution plutôt simple et simpliste consiste simplement à utiliser un appel de fonction pour définir la valeur de la variable globale, sans jamais définir directement sa valeur. De cette façon, vous avez un contrôle total:

var globalVar;

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

Il n'y a aucun moyen d'appliquer cela, il faut juste une discipline de programmation ... bien que vous puissiez utiliser grep (ou quelque chose de similaire) pour vérifier que nulle part votre code ne définit directement la valeur de globalVar.

Ou vous pouvez l'encapsuler dans un objet et dans les méthodes de lecture et de définition de l'utilisateur ... juste une pensée.

1
markvgti

S'il vous plaît, souvenez-vous que la question initiale était pour VARIABLES, pas pour OBJECTS;)

en plus de toutes les réponses ci-dessus, j'ai créé une petite bibliothèque appelée pour TheWatch.js , qui utilise la même méthode pour intercepter et rappeler les modifications des variables globales normales. en javascript.

Compatible avec les variables JQUERY, pas besoin d'utiliser OBJECTS, et vous pouvez passer directement un tableau de plusieurs variables si nécessaire.

Si cela peut être utile ...: https://bitbucket.org/esabora/forthewatch
En gros, il suffit d’appeler la fonction:
watchIt("theVariableToWatch", "varChangedFunctionCallback");

Et désolé par avance si non pertinent.

0

Ce n'est pas directement possible.

Toutefois, cela peut être fait avec CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

La méthode ci-dessous accepte un tableau de noms de variables en tant qu'entrée, ajoute un écouteur d'événements pour chaque variable et déclenche l'événement pour toute modification de la valeur des variables.

La méthode utilise l'interrogation pour détecter le changement de la valeur. Vous pouvez augmenter la valeur du délai d'attente en millisecondes.

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

En appelant la méthode:

watchVariables(['username', 'userid']);

Il détectera les modifications apportées aux variables nom d'utilisateur et ID utilisateur.

0
Ketan Yekale

C'est un vieux fil mais je suis tombé sur la deuxième réponse la plus élevée (auditeurs personnalisés) tout en recherchant une solution utilisant Angular. Tandis que la solution fonctionne, angular dispose d'une méthode plus intégrée pour résoudre ce problème à l'aide de @Output et des émetteurs d'événements. Déconnexion de l'exemple dans la réponse d'écoute personnalisée:

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

Ceci va maintenant mettre à jour non parent chaque fois que l'on clique sur le bouton enfant. Travailler stackblitz

0
TabsNotSpaces