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


131

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

// 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"

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

この概念は、__get()および__set()マジックメソッドを使用してPHPで可能です(これらの情報については、PHPのドキュメントを参照してください)。

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


私はなんとかそれをすることができました、方法についてはここ私の答えをください。

回答:


215

2013年と2015年の更新 (2011年の元の回答については以下を参照)

これは、仕様書ES2015(別名「ES6」)のように変更:JavaScriptが今持っているプロキシを。プロキシを使用すると、他のオブジェクト(の正面)の真のプロキシであるオブジェクトを作成できます。以下は、文字列であるプロパティ値をすべて取得時にすべて大文字にする簡単な例です。

"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 (もちろん)取得するプロパティの名前です。これは通常文字列ですが、シンボルにすることもできます。
  • receiverthisプロパティがデータプロパティではなくアクセサである場合に、getter関数で使用されるオブジェクトです。通常の場合には、これは、プロキシまたはそれから継承ものですが、それはできトラップがによってトリガすることができるので、何もしますReflect.get

これにより、必要なキャッチオールゲッターおよびセッター機能を備えたオブジェクトを作成できます。

"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'を取得しています
[前] obj.foo = undefined
存在しないプロパティ 'foo'の設定、初期値:bar
[後] obj.foo = bar

「存在しない」メッセージfooがまだ存在しないときに取得しようとしたとき、およびそれを作成したときに取得したが、その後ではないことに注意してください。


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

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

あなたは、もちろん、それを行うための機能を実装することができ、私はあなたがおそらく使用したくない推測しているf = obj.prop("foo");のではなく、f = obj.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...
};

しかし、繰り返しますが、オブジェクトの使用方法がどのように変化するかを考えると、本当にそうしたいとは思いません。


1
に代わるものがありProxyますObject.defineProperty()。私は新しい答えに詳細を入れました。
アンドリュー

@Andrew-質問を誤解していると思います。回答についての私のコメントを参照してください。
TJクラウダー

4

この問題に対する独自のアプローチは次のとおりです。

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

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

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

あなたはそれがここで働いているのを見ることができます


これは機能しません。プロパティの名前を指定せずにゲッターを定義することはできません。
John Kurlak、2015年

@JohnKurlakこのjsFiddleを確認してください:jsfiddle.net/oga7ne4x動作します。プロパティ名を文字列として渡すだけです。
clami219

3
ああ、説明してくれてありがとう。独自のget()/ set()を作成するのではなく、get()/ set()言語機能を使用しようとしていると思いました。ただし、元の問題は実際には解決しないため、このソリューションはまだ好きではありません。
John Kurlak、2015年

@JohnKurlakさて、私はそれが独自のアプローチであると書いた。より伝統的なアプローチを使用する既存のコードがある場合の問題は解決しませんが、問題を解決する別の方法を提供します。しかし、ゼロから始めるのは良いことです。確かに反対票には値しません...
clami219

@JohnKurlak見栄えが良くなりましたか?:)
clami219 2015年

0

序文:

TJ Crowderの回答Proxy、OPが要求していた、存在しないプロパティのキャッチオールゲッター/セッターに必要となるについて言及しています。動的ゲッター/セッターでProxy実際に必要な動作によっては、実際にはa は必要ない場合があります。または、潜在的に、Proxy以下に示すものとの組み合わせを使用することもできます。

(PS私はProxy最近、Linux上のFirefox で徹底的に実験し、非常に機能的であることがわかりましたが、操作するのがやや混乱/困難であり、正しく動作します。さらに重要なことに、非常に遅いこともわかっています(少なくとも最適化されたJavaScriptが最近どのようになる傾向があるかについて)-私は、deca-multiplesの領域でより遅い話をしています。)


動的に作成されたゲッターとセッターを具体的に実装するには、Object.defineProperty()またはを使用できますObject.defineProperties()。これも非常に高速です。

要点は、次のようにオブジェクトにゲッターやセッターを定義できることです。

let obj = {};
let val = 0;
Object.defineProperty(obj, 'prop', { //<- This object is called a "property descriptor".
  //Alternatively, use: `get() {}`
  get: function() {
    return val;
  },
  //Alternatively, use: `set(newValue) {}`
  set: function(newValue) {
    val = newValue;
  }
});

//Calls the getter function.
console.log(obj.prop);
let copy = obj.prop;
//Etc.

//Calls the setter function.
obj.prop = 10;
++obj.prop;
//Etc.

ここで注意すべきいくつかの点:

  • および/またはと同時にvalue、プロパティ記述子(上記に表示されていません)のプロパティを使用することはできません。ドキュメントから: getset

    オブジェクトに存在するプロパティ記述子には、データ記述子とアクセサー記述子という2つの主要な種類があります。データ記述子は、または書き込み可能であってもなくてもよい値を有する特性です。アクセサ記述子は、機能のゲッターセッター対によって記述特性です。記述子は、これら2つのフレーバーのいずれかでなければなりません。両方にすることはできません。

  • したがって、call / property記述子の外部valプロパティを作成したことに気付くでしょう。これは標準的な動作です。Object.defineProperty()
  • エラーごとの通り、ここで、設定しないでくださいwritabletrueあなたが使用している場合、プロパティ記述子にgetset
  • あなたは、設定を検討する必要がありますconfigurableenumerable、しかし、あなたは後にしているものに応じて。ドキュメントから:

    構成可能

    • true このプロパティ記述子のタイプが変更される可能性があり、プロパティが対応するオブジェクトから削除される可能性がある場合に限ります。

    • デフォルトはfalseです。


    列挙可能な

    • true 対応するオブジェクトのプロパティの列挙中にこのプロパティが表示される場合とその場合のみ。

    • デフォルトはfalseです。


このノートでは、これらも興味深いかもしれません:

  • Object.getOwnPropertyNames(obj):列挙できないものも含めて、オブジェクトのすべてのプロパティを取得します(これを行うには、これが唯一の方法です!)。
  • Object.getOwnPropertyDescriptor(obj, prop)Object.defineProperty()上記に渡されたオブジェクトであるオブジェクトのプロパティ記述子を取得します。
  • obj.propertyIsEnumerable(prop);:特定のオブジェクトインスタンスの個々のプロパティについて、オブジェクトインスタンスでこの関数を呼び出し、特定のプロパティが列挙可能かどうかを判断します。

2
質問を誤解しているようです。OPは特に、PHP やのようなすべてのcatchを要求しました。そのケースを処理しません。質問から:つまり、まだ定義されていないプロパティ名のゲッターとセッターを作成します。」(それらの強調)。プロパティを事前に定義します。OPが要求したことを行う唯一の方法はプロキシです。__get__setdefinePropertydefineProperty
TJクラウダー

@TJCrowderなるほど。あなたは技術的に正しいですが、質問はあまり明確ではありませんでした。私はそれに応じて私の答えを調整しました。また、実際には私たちの答えの組み合わせが必要な場合もあります(個人的にはそうです)。
アンドリュー

@Andrewが2011年にこの質問をしたとき、私が念頭に置いていたユースケースは、ユーザーが呼び出すことができるオブジェクトを返すことができるobj.whateverPropertyライブラリーであり、ライブラリーは汎用ゲッターでインターセプトし、プロパティ名にユーザーがアクセスしようとしました。したがって、「キャッチオールゲッターおよびセッター」の要件。
daiscog

-6
var x={}
var propName = 'value' 
var get = Function("return this['" + propName + "']")
var set = Function("newValue", "this['" + propName + "'] = newValue")
var handler = { 'get': get, 'set': set, enumerable: true, configurable: true }
Object.defineProperty(x, propName, handler)

これは私のために働く


13
Function()そのように使用することは、を使用するようなものですeval。関数をのパラメータとして直接置くだけですdefineProperty。それとも、何らかの理由であなたは、動的に作成するために主張している場合getset、そのような関数を作成し、それを返す高次関数を使用するvar get = (function(propName) { return function() { return this[propName]; };})('value');
クリス-L
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.