Прослушивание изменений переменных в JavaScript - PullRequest
391 голосов
/ 19 ноября 2009

Возможно ли иметь событие в JS, которое срабатывает при изменении значения определенной переменной? JQuery принято.

Ответы [ 18 ]

191 голосов
/ 19 ноября 2009

Да, object.watch (хотя это нестандартно). Вот моя реализация , которая работает во всех современных основных браузерах.

65 голосов
/ 24 мая 2016

Да, теперь это вполне возможно!

Я знаю, что это старая ветка, но теперь этот эффект возможен при использовании методов доступа (геттеров и сеттеров): 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/

Вы также можете использовать массив слушателей вместо одного слота слушателя, но я хотел бы привести вам самый простой пример.

34 голосов
/ 19 ноября 2009

номер

Но, если это действительно так важно, у вас есть 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);
30 голосов
/ 14 июня 2018

Большинство ответов на этот вопрос либо устарели, либо неэффективны, либо требуют включения больших раздутых библиотек:

  • Object.watch и Object.observe устарели и не должны использоваться.
  • onPropertyChange - обработчик событий элемента DOM, который работает только в некоторых версиях IE.
  • Object.defineProperty позволяет сделать свойство объекта неизменным, что позволит вам обнаруживать предпринятые изменения, но также будет блокировать любые изменения.
  • Определение сеттеров и геттеров работает, но требует большого количества установочного кода и не очень хорошо работает, когда вам нужно удалить или создать новые свойства.

Сегодня теперь вы можете использовать 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 являются:

  1. Объект Proxy недоступен в старых браузерах (например, IE11), а polyfill не может полностью копировать функциональность Proxy.
  2. Прокси-объекты не всегда ведут себя должным образом со специальными объектами (например, 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}}]
23 голосов
/ 20 мая 2015

Использование 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);

Другой пример

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

21 голосов
/ 22 августа 2012

Извините, что поднял старую ветку, но вот небольшое руководство для тех, кто (как и я!) Не видит, как работает пример Эли Грея:

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

Надеюсь, что это может кому-то помочь

7 голосов
/ 25 мая 2011

Если вы используете jQuery {UI} (который должен использовать каждый :-)), вы можете использовать .change () со скрытым элементом .

7 голосов
/ 19 ноября 2009

Как ответ Люка Шефера ( примечание : это относится к его оригинальному сообщению; но весь смысл здесь остается в силе после редактирования ), я бы также предложите пару методов 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);
6 голосов
/ 12 декабря 2013

AngularJS (я знаю, что это не JQuery, но это может помочь. [Чистый JS хорош только в теории]):

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

где "data" - это имя вашей переменной в области.

Существует ссылка на документ.

5 голосов
/ 06 июня 2012

Для тех, кто настраивается через пару лет:

Доступно решение для большинства браузеров (и IE6 +), которое использует событие onpropertychange и более новую спецификацию defineProperty. Небольшая проблема в том, что вам нужно сделать вашу переменную объектом dom.

Полная информация:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...