是否可以在 JavaScript 中实现动态 getter/setter?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7891937/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 03:58:15  来源:igfitidea点击:

Is it possible to implement dynamic getters/setters in JavaScript?

javascriptmetaprogramminggetter-setter

提问by daiscog

I am aware of how to create getters and setters for properties whose names one already knows, by doing something like this:

我知道如何通过执行以下操作为已经知道名称的属性创建 getter 和 setter:

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

Now, my question is, is it possible to define sort of catch-all getters and setters like these? I.e., create getters and setters for any property name which isn'talready defined.

现在,我的问题是,是否有可能定义像这样的包罗万象的 getter 和 setter?即,创建getter和setter的任何属性名称是不是已经定义。

The concept is possible in PHP using the __get()and __set()magic methods (see the PHP documentationfor information on these), so I'm really asking is there a JavaScript equivalent to these?

这个概念在 PHP 中使用 the__get()__set()magic 方法是可能的(有关这些的信息,请参阅PHP 文档),所以我真的在问是否有与这些等效的 JavaScript?

Needless to say, I'd ideally like a solution that is cross-browser compatible.

毋庸置疑,我最喜欢跨浏览器兼容的解决方案。

回答by T.J. Crowder

2013 and 2015 Update(see below for the original answer from 2011):

2013 年和 2015 年更新(有关 2011 年的原始答案,请参见下文)

This changed as of the ES2015 (aka "ES6") specification: JavaScript now has proxies. Proxies let you create objects that are true proxies for (facades on) other objects. Here's a simple example that turns any property values that are strings to all caps on retrieval:

这在 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"

Operations you don't override have their default behavior. In the above, all we override is get, but there's a whole list of operations you can hook into.

您未覆盖的操作具有其默认行为。在上面,我们覆盖的只是get,但是有一个完整的操作列表可以挂钩。

In the gethandler function's arguments list:

get处理函数的参数列表中:

  • targetis the object being proxied (original, in our case).
  • nameis (of course) the name of the property being retrieved, which is usually a string but could also be a Symbol.
  • receiveris the object that should be used as thisin the getter function if the property is an accessor rather than a data property. In the normal case this is the proxy or something that inherits from it, but it canbe anything since the trap may be triggered by Reflect.get.
  • target是被代理的对象(original在我们的例子中)。
  • name是(当然)正在检索的属性的名称,它通常是一个字符串,但也可以是一个符号。
  • receiverthis如果属性是访问器而不是数据属性,则是应该在 getter 函数中使用的对象。在正常情况下,这是代理或从它继承的东西,但它可以是任何东西,因为陷阱可能由Reflect.get.

This lets you create an object with the catch-all getter and setter feature you want:

这使您可以使用所需的全能 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}`);

The output of the above is:

上面的输出是:

Getting non-existent property 'foo'
[before] obj.foo = undefined
Setting non-existent property 'foo', initial value: bar
[after] obj.foo = bar

Note how we get the "non-existent" message when we try to retrieve foowhen it doesn't yet exist, and again when we create it, but not after that.

请注意,当我们尝试检索foo尚不存在的消息时,以及在创建它时再次获取“不存在”消息时,我们如何获得“不存在”消息,但之后不会。



Answer from 2011(see above for 2013 and 2015 updates):

2011 年的回答(有关 2013 年和 2015 年的更新,请参见上文)

No, JavaScript doesn't have a catch-all property feature. The accessor syntax you're using is covered in Section 11.1.5of the spec, and doesn't offer any wildcard or something like that.

不,JavaScript 没有包罗万象的属性特性。您使用的访问器语法包含在规范的第 11.1.5 节中,并且不提供任何通配符或类似的东西。

You could, of course, implement a function to do it, but I'm guessing you probably don't want to use f = obj.prop("foo");rather than f = obj.foo;and obj.prop("foo", value);rather than obj.foo = value;(which would be necessary for the function to handle unknown properties).

当然,您可以实现一个函数来执行此操作,但我猜您可能不想使用f = obj.prop("foo");而不是f = obj.foo;obj.prop("foo", value);而不是obj.foo = value;(这对于函数处理未知属性是必要的)。

FWIW, the getter function (I didn't bother with setter logic) would look something like this:

FWIW,getter 函数(我没有理会 setter 逻辑)看起来像这样:

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...
};

But again, I can't imagine you'd really want to do that, because of how it changes how you use the object.

但同样,我无法想象您真的想要这样做,因为它会改变您使用对象的方式。

回答by clami219

The following could be an original approach to this problem:

以下可能是解决此问题的原始方法:

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;
  }
}

In order to use it the properties should be passed as strings. So here is an example of how it works:

为了使用它,属性应该作为字符串传递。所以这里是它是如何工作的一个例子:

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

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

Edit:An improved, more object-oriented approach based on what I proposed is the following:

编辑:基于我提出的改进的、更面向对象的方法如下:

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

You can see it working here.

你可以看到它在这里工作。

回答by Bruno

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)

this works for me

这对我有用