web-dev-qa-db-ja.com

JavaScriptで変数の変更を聞いている

特定の変数の値が変化したときに発生するJSのイベントを持つことは可能ですか? JQueryは受け入れられます。

389
rashcroft22

はい、 object.watch です(ただし非標準です)。 これは私の実装 です。現在のすべての主要ブラウザで動作します。

191
Eli Grey

はい、これは完全に可能です。

私はこれが古いスレッドであることを知っていますが、今度はこの効果はアクセサ(ゲッターとセッター)を使って可能になります。 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/

単一のリスナースロットではなく、リスナーの配列を使用することもできますが、できるだけ簡単な例を示したいと思います。

64
Akira

いいえ.

しかし、それが本当にそれほど重要であれば、2つの選択肢があります(最初のものはテスト済み、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);
34
Luke Schafer

この質問に対する回答の大部分は、時代遅れであるか、効果がないか、または膨大なサイズのライブラリを含める必要があるかのいずれかです。

  • Object.watchObject.observe はどちらも非推奨であり、使用しないでください。
  • onPropertyChange は、IEの一部のバージョンでのみ機能するDOM要素のイベントハンドラです。
  • Object.defineProperty を使用すると、オブジェクトプロパティを不変にすることができます。これにより、変更の試みを検出できますが、変更もブロックされます。
  • セッターとゲッターの定義 は機能しますが、多くのセットアップコードが必要で、新しいプロパティを削除または作成する必要がある場合はうまく機能しません。

今日では、Proxy objectを使用して、オブジェクトに対する変更を監視(および遮断)することができます。それは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など)では使用できず、 polyfillProxy機能を完全に複製することはできません。
  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}}]
28
Elliot B.

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

古いスレッドを立ち上げて申し訳ありませんが、Eli Greyの例がどのように機能するのかわからない人のための、ちょっとしたマニュアルです。

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

これが誰かに役立つことを願っています

22

As Luke Schaferの答えnote:これは彼の元々の投稿を指していますが、ここでの全ての点は編集後も有効です

しかし、私はいくつかの修正を提案するでしょう(そしてそれが私が投稿している理由です...)。

そのコードの問題点は、オブジェクト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
    };
})();

これで、Lukeが提案したのと同じ方法でリスナーの作成と追加ができますが、aから読み書きする方法が気付かれずに済むことはないので安心できます。

プログラムによるカプセル化フィールドの追加

それでもLukeの方針に沿って、カプセル化されたフィールドとそれぞれのゲッター/セッターを単純な関数呼び出しによってオブジェクトに追加する簡単な方法を提案します。

これは値型でのみ正しく動作することに注意してください。これが参照型で動作するには、ある種のディープコピーを実装する必要があります( を参照) 1 、など)。

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

あなたがjQuery {UI}(誰もが使うべきである:-))を使っているなら、隠された<input />要素と共に.change()を使うことができます。

7
Chuck Han

AngularJS(私はこれがJQueryではないことを知っていますが、それは助けになるかもしれません。[純粋なJSは理論上だけで良い]です):

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

"data"はスコープ内の変数の名前です。

Docへの リンクがあります。

6
ses

数年後のチューニングのために:

Onpropertychangeイベントと新しいspec definePropertyを使用する、ほとんどのブラウザ(およびIE6 +)用のソリューションが利用可能です。ちょっとした問題は、あなたの変数をDOMオブジェクトにする必要があるということです。

全詳細:

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

5
MandoMando

直接ではありません。ある種の "addListener/removeListener"インターフェースを持つペアのgetter/setter、またはNPAPIプラグインが必要です(しかしそれはまったく別の話です)。

2
jldupont

あなたが探している機能は、 "defineProperty()"メソッドを使うことによって達成することができます - それは現代のブラウザでのみ利用可能です:

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

クロスブラウザのサポートがさらに必要な場合は、同様の機能を持つjQuery拡張機能を書きました。

https://github.com/jarederaj/jQueue

変数、オブジェクト、またはキーの存在に対するキューイングコールバックを処理する小さなjQuery拡張。バックグラウンドで実行されているプロセスの影響を受ける可能性のある任意の数のデータポイントに、任意の数のコールバックを割り当てることができます。 jQueueは、指定されたこれらのデータが存在するようになるまで待機して待機してから、その引数を使用して正しいコールバックを起動します。

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

かなり単純で単純な解決策は、グローバル変数の値を設定するために関数呼び出しを使用することであり、その値を直接設定することは絶対にありません。この方法であなたは完全にコントロールすることができます:

var globalVar;

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

これを強制する方法はありません。プログラミング規律が必要なだけです...ただし、grep(または同様のもの)を使用して、コードが直接globalVarの値を設定していることを確認することはできません。

あるいは、オブジェクトとユーザーのgetterメソッドとsetterメソッドにカプセル化することもできます。

1
markvgti

最初の質問はOBJECTSではなくVARIABLESに関するものだったことを覚えておいてください;)

上記のすべての答えに加えて、forTheWatch.jsという小さなライブラリを作成しました。これは、通常のグローバル変数の変更をキャッチしてコールバックするために同じ方法を使用しますJavaScriptで。

JQUERY変数と互換性があり、OBJECTSを使用する必要はありません。必要に応じて、いくつかの変数のARRAYを直接渡すことができます。

役に立つ場合は...: https://bitbucket.org/esabora/forthewatch
基本的には、関数を呼び出すだけです。
watchIt("theVariableToWatch", "varChangedFunctionCallback");

関係ない場合は事前に申し訳ありません。

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

これは変数usernameとuseridへの変更を検出します。

0
Ketan Yekale

これは古いスレッドですが、Angularを使用した解決策を探している間に2番目に高い回答(カスタムリスナー)に出会いました。解決策はうまくいきますが、angleは@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);
}

これにより、子ボタンがクリックされるたびにnonが更新されます。 Working stackblitz

0
TabsNotSpaces