Прослушивание изменений переменных в JavaScript

353

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

  • 0
    @BenjaminGruenbaum, вероятно, вы хотите сказать, MutableObserver (для DOM). Объект только для объектов JS из того, что я помню.
  • 2
    @ HellBaby, этот вопрос о переменных, а не о DOM.
Показать ещё 3 комментария
Теги:
javascript-events

18 ответов

177

Да, object.watch (это нестандартно, хотя). Здесь моя реализация, которая работает в каждом текущем главном браузере.

  • 1
    Это отличный метод, однако он не может нормально работать в IE6. Это может быть нарушителем соглашения для некоторых.
  • 0
    Это здорово, но у меня работает только в Firefox :(
Показать ещё 12 комментариев
50

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

Я знаю, что это старый поток, но теперь этот эффект возможен с помощью аксессуаров (getters и seters): 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/

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

  • 4
    Это тонна кода на объект, есть ли способ сделать это более пригодным для повторного использования?
  • 0
    Привет, Акира! Я сделал нечто подобное - не могли бы вы рассмотреть, почему это не работает для меня?
Показать ещё 5 комментариев
34

Нет.

Но если это действительно так важно, у вас есть 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);
}

Во-вторых, я действительно забыл, я отправлю, пока я думаю об этом:)

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);
  • 2
    второе хорошо :)
  • 0
    хе-хе, я запомню в конце концов, сейчас я исправляю код
Показать ещё 2 комментария
23

Извините, что вызвал старую нить, но вот небольшое руководство для тех, кто (как и я!) не видит, как работает Eli Grey:

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

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

  • 5
    На данный момент часы не поддерживаются в Chrome или Safari, только Firefox
  • 4
    В сети разработчиков Mozilla это не рекомендуется. Object.prototype.watch () предназначался только для тестирования и не должен использоваться в рабочем коде. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Показать ещё 2 комментария
19

Использование 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>
  • 0
    Второй пример немного вводит в заблуждение, varw требует 2 аргумента, но часть вашего примера показывает, что функция varw только с параметром value.
15

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

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

Сегодня вы можете использовать объект Proxy для контроля (и перехвата) изменений, сделанных для объекта. Это цель, созданная для того, что пытается сделать OP. Вот базовый пример:

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), и полиполк не может полностью реплицировать функции 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}}]
  • 0
    Это не раскрывает вашу принадлежность к предлагаемой библиотеке «Observable Slim».
7

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

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

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

Существует ссылка на doc.

  • 0
    К сожалению, это заставляет вас привязывать переменную к области видимости
  • 0
    он запускается только при запуске $scope.$apply()
7

Как Ответ Люка Шафера (note): это относится к его оригинальному сообщению, но вся суть здесь остается в силе после редактирования), Я также предложил пару методов 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 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 незаметно!

Программный ввод инкапсулированных полей

Находясь на дорожке Luke, я предлагаю простой способ добавить инкапсулированные поля и соответствующие геттеры/сеттеры к объектам с помощью простого вызова функции.

Обратите внимание, что это будет работать только с типами значений. Для этого, чтобы работать со ссылочными типами, необходимо было бы выполнить некоторую глубокую копию (см. этот.

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);
  • 2
    +1 для инкапсуляции. Это была моя первая мысль, но я хотел в конечном итоге добавить метод create_gets_sets, и, поскольку он неразборчив, скрывать значения - это не здорово :), мы можем сделать еще один шаг и написать некоторые вещи, чтобы скрыть значения, но Я думаю, что код, который я разместил, достаточно запутанен для большинства людей ... может быть, если есть вызов для этого ...
6

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

  • 7
    Я не совсем понимаю. Как вы можете прикрепить переменную к скрытому элементу <input/> ?
  • 4
    Я думаю, что Чак предлагает, чтобы вы могли установить значение ввода, используя jquery, а затем и прослушиватель событий .change (). Если вы обновите значение ввода с помощью .val (), то сработает обратный вызов события .change ().
Показать ещё 8 комментариев
5

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

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

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

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

2

Функциональность, которую вы ищете, может быть достигнута с помощью метода defineProperty(), который доступен только для современных браузеров:

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

Я написал расширение jQuery, которое имеет некоторую аналогичную функциональность, если вам нужна более кросс-браузерная поддержка:

https://github.com/jarederaj/jQueue

Небольшое расширение jQuery, которое обрабатывает обратные вызовы в очереди существование переменной, объекта или ключа. Вы можете назначить любое количество обратные вызовы для любого количества точек данных, на которые может процессы, выполняющиеся в фоновом режиме. jQueue слушает и ждет эти данные, которые вы указываете, должны появиться, а затем правильный ответ с его аргументами.

2

Не напрямую: вам нужен пара getter/setter с каким-то интерфейсом addListener/removeListener... или плагин NPAPI (но эта другая история вообще).

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

Это старый поток, но я наткнулся на второй самый высокий ответ (пользовательские слушатели), ища решение с помощью 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 на родительском при каждом нажатии кнопки дочернего элемента. Рабочий блокblitz

0

Это невозможно.

Однако это можно сделать с помощью 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']);

Он обнаружит изменения в имени пользователя и пользователя.

0

Опять же, я модно опаздываю на эту вечеринку. Я знаю, что это не чистый JavaScript или jQuery; однако, я работал с проектом в knockout.js и нашел, что метод subscribe() отлично подходит для запуска события (или уведомления), когда значение чего-то меняется. Вот простой пример:

var foo = ko.observable(false);

foo.subscribe(function () {
    console.log('`foo` has changed: ' + foo());
    if (foo() === true) {
        // do something here
    }
});

Конечно, subscribe() работает над нокаутом "наблюдаемые" не по регулярным переменным. Вот ссылка на страницу документации нокаута, охватывающая наблюдаемые и подписные подписки, для всех, кого это интересует: http://knockoutjs.com/documentation/observables.html

0

Довольно простое и упрощенное решение - просто использовать вызов функции для установки значения глобальной переменной и никогда не устанавливать его значение напрямую. Таким образом, у вас есть полный контроль:

var globalVar;

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

Нет способа обеспечить это, он просто требует программирования дисциплины... хотя вы можете использовать grep (или что-то подобное), чтобы проверить, что нигде ваш код не устанавливает значение globalVar.

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню