web-dev-qa-db-ja.com

JavaScriptで動的なゲッター/セッターを実装することは可能ですか?

私は、次のようなことを行うことで、名前がすでにわかっているプロパティのゲッターとセッターを作成する方法を知っています:

_// A trivial example:
function MyObject(val){
    this.count = 0;
    this.value = val;
}
MyObject.prototype = {
    get value(){
        return this.count < 2 ? "Go away" : this._value;
    },
    set value(val){
        this._value = val + (++this.count);
    }
};
var a = new MyObject('foo');

alert(a.value); // --> "Go away"
a.value = 'bar';
alert(a.value); // --> "bar2"
_

今、私の質問は、これらのようなキャッチオールゲッターとセッターの種類を定義することは可能ですか?つまり、が既に定義されていないプロパティ名のゲッターとセッターを作成します。

この概念は、PHP __get()および__set()マジックメソッドを使用して可能です( the PHP =ドキュメンテーション これらの情報については))、私は本当にこれらに相当するJavaScriptがあるのか​​と尋ねていますか?

言うまでもなく、クロスブラウザ互換のソリューションが理想的です。

117
megaflop

2013および2015 Update(2011年の元の回答については以下を参照)

これはES2015(別名「ES6」)仕様の時点で変更されました。JavaScriptは proxies になりました。プロキシを使用すると、他のオブジェクト(ファサード)の真のプロキシであるオブジェクトを作成できます。以下は、文字列であるプロパティ値を取得時にすべて大文字に変換する簡単な例です。

_"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let original = {
    "foo": "bar"
};
let proxy = new Proxy(original, {
    get(target, name, receiver) {
        let rv = Reflect.get(target, name, receiver);
        if (typeof rv === "string") {
            rv = rv.toUpperCase();
        }
        return rv;
      }
});
console.log(`original.foo = ${original.foo}`); // "original.foo = bar"
console.log(`proxy.foo = ${proxy.foo}`);       // "proxy.foo = BAR"_

オーバーライドしない操作には、デフォルトの動作があります。上記では、オーバーライドするのはgetだけですが、フックできる操作の完全なリストがあります。

getハンドラー関数の引数リスト:

  • targetはプロキシされるオブジェクトです(この例ではoriginal)。
  • nameは(もちろん)取得されるプロパティの名前で、通常は文字列ですが、Symbolの場合もあります。
  • receiverは、プロパティがデータプロパティではなくアクセサである場合に、getter関数でthisとして使用されるオブジェクトです。通常の場合、これはプロキシまたはそれを継承するものですが、トラップは_Reflect.get_によってトリガーされる可能性があるため、canになります。

これにより、必要なすべてのgetterおよびsetter機能を備えたオブジェクトを作成できます。

_"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let obj = new Proxy({}, {
    get(target, name, receiver) {
        if (!Reflect.has(target, name)) {
            console.log("Getting non-existent property '" + name + "'");
            return undefined;
        }
        return Reflect.get(target, name, receiver);
    },
    set(target, name, value, receiver) {
        if (!Reflect.has(target, name)) {
            console.log(`Setting non-existent property '${name}', initial value: ${value}`);
        }
        return Reflect.set(target, name, value, receiver);
    }
});

console.log(`[before] obj.foo = ${obj.foo}`);
obj.foo = "bar";
console.log(`[after] obj.foo = ${obj.foo}`);_

上記の出力は次のとおりです。

存在しないプロパティ「foo」の取得
 [before] obj.foo = undefined 
存在しないプロパティ 'foo'の設定、初期値:bar 
 [after] obj.foo =バー

fooを取得しようとしたときに「存在しない」メッセージを取得する方法に注意してください。


2011年からの回答(2013年および2015年の更新については上記を参照)

いいえ、JavaScriptには包括的なプロパティ機能がありません。使用しているアクセサ構文は、仕様の セクション11.1.5 でカバーされており、ワイルドカードなどは提供していません。

もちろん、それを行う関数を実装することもできますが、おそらく_f = obj.foo;_やf = obj.prop("foo");ではなくobj.prop("foo", value);を使用したくないと思います。 _obj.foo = value;_(関数が未知のプロパティを処理するために必要です)。

FWIW、ゲッター関数(セッターロジックを気にしませんでした)は次のようになります。

_MyObject.prototype.prop = function(propName) {
    if (propName in this) {
        // This object or its prototype already has this property,
        // return the existing value.
        return this[propName];
    }

    // ...Catch-all, deal with undefined property here...
};
_

しかし、繰り返しますが、オブジェクトの使用方法がどのように変化するかによって、本当にそれをやりたいと思うことは想像できません。

188
T.J. Crowder

最新のJavascript(FF4 +、IE9 +、Chrome 5 +、Safari 5.1 +、Opera 11.60+)では、Object.definePropertyMDNのこの例 は、definePropertyの仕組みを非常によく説明し、動的なゲッターとセッターを可能にします。

技術的には、これはany動的クエリでは機能しませんが、有効なgetterおよびsetterがたとえばAJAX JSON-RPCサーバーの呼び出し、たとえば、次の方法でこれを使用できます。

arrayOfNewProperties.forEach(function(property) {
    Object.defineProperty(myObject, property.name, {
        set: property.setter, get: property.getter
    });
});
47
David Ellis

以下は、この問題に対する独自のアプローチです。

var obj = {
  emptyValue: null,
  get: function(prop){
    if(typeof this[prop] == "undefined")
        return this.emptyValue;
    else
        return this[prop];
  },
  set: function(prop,value){
    this[prop] = value;
  }
}

これを使用するには、プロパティを文字列として渡す必要があります。そのため、次のように動作します。

//To set a property
obj.set('myProperty','myValue');

//To get a property
var myVar = obj.get('myProperty');

Edit:私が提案したものに基づいて改善された、よりオブジェクト指向のアプローチは次のとおりです。

function MyObject() {
    var emptyValue = null;
    var obj = {};
    this.get = function(prop){
        return (typeof obj[prop] == "undefined") ? emptyValue : obj[prop];
    };
    this.set = function(prop,value){
        obj[prop] = value;
    };
}

var newObj = new MyObject();
newObj.set('myProperty','MyValue');
alert(newObj.get('myProperty'));

こちら で動作していることがわかります。

6
clami219