一尘不染

是否可以在JavaScript中实现动态获取器/设置器?

javascript

我知道如何通过执行以下操作来为名称已经知道的属性创建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"

现在,我的问题是,是否可以定义像这样的所有获取方法和设置方法?即,创建getter和setter的任何属性名称 是不是 已经定义。

这个概念可以在PHP中使用__get()__set()魔术方法(有关这些信息,请参见PHP文档),因此我真的在问是否存在与这些方法等效的JavaScript?

不用说,理想情况下,我想要一个跨浏览器兼容的解决方案。


阅读 282

收藏
2020-04-25

共1个答案

一尘不染

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 (当然)是要检索的属性的名称,通常是一个字符串,但也可以是一个符号。
  • receiver``this如果属性是访问器而不是数据属性,则是应该在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 =未定义
设置不存在的属性'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,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...
};

但是再次,我无法想象您真的想这样做,因为它如何改变您使用对象的方式。

2020-04-25