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

时间:2011-10-25 15:45:04

标签: javascript metaprogramming getter-setter

我知道如何通过这样的方式为名字已经知道的属性创建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。

使用__get()__set()魔术方法在PHP中可以实现这个概念(有关这些的信息,请参阅the PHP documentation),所以我真的要问是否有一个与这些相当的JavaScript ?

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

5 个答案:

答案 0 :(得分:181)

2013年和2015年更新 (请参阅以下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(当然)是要检索的属性的名称,通常是字符串,但也可以是符号。
  • 如果属性是访问者而不是数据属性,则
  • receiver是在getter函数中应该用作this的对象。在正常情况下,这是代理或从其继承的东西,但它可以是任何东西,因为陷阱可能由Reflect.get触发。

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

以上的输出是:

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

请注意,当我们尝试在foo尚不存在时检索“{1}}时,我们会收到”不存在“的消息,并在我们创建它时再次检索,但不会在此之后。


2011年回答 (见上文2013和2015年更新)

不,JavaScript没有全能属性。您正在使用的访问器语法包含在规范的Section 11.1.5中,并且不提供任何通配符或类似的东西。

当然,你可以实现一个函数来实现它,但我猜你可能不想使用f = obj.prop("foo");而不是f = obj.foo;obj.prop("foo", value);而不是{ {1}}(这是函数处理未知属性所必需的)。

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

obj.foo = value;

但同样,我无法想象你真的想要这样做,因为它改变了你如何使用这个对象。

答案 1 :(得分:46)

在现代Javascript(FF4 +,IE9 +,Chrome 5 +,Safari 5.1 +,Opera 11.60+)中,有Object.definePropertyThis example on the MDN很好地解释了defineProperty的工作原理,并使动态的getter和setter成为可能。

从技术上讲,这对于你正在寻找的任何动态查询都不起作用,但是如果你的有效getter和setter是由对JSON-RPC服务器的AJAX调用定义的例如,您可以通过以下方式使用它:

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

答案 2 :(得分:6)

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

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

您可以看到它正常工作here

答案 3 :(得分:-2)

前言:

T.J. Crowder's answer提到了Proxy,对于OP所要求的不存在的属性,如果要进行全面的获取/设置,将需要使用Proxy。根据动态获取器/设置器实际需要的行为,实际上Proxy不一定是必需的;或者潜在地,您可能希望结合使用Proxy和下面将要介绍的内容。

(最近我在Linux上的Firefox中对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. 进行了彻底的实验,发现PS的功能非常强大,但是使用起来却有些困惑/困难。更重要的是,我拥有还发现它相当慢(至少与当今优化JavaScript的趋势有关)-我是在十进制慢的领域中讨论。)


要专门实现动态创建的getter和setter,可以使用Object.defineProperty()Object.defineProperties()。这也相当快。

要点是,您可以像这样定义对象上的getter和/或setter:

value

这里需要注意的几件事:

  • 您不能与get和/或set同时使用属性描述符(上面显示的 not )中的val属性;从文档:

    对象中存在的属性描述符有两种主要形式:数据描述符和访问器描述符。 数据描述符是具有值的属性,该值可以写也可以不写。 访问器描述符是由一对getter-setter函数描述的属性。描述符必须是这两种形式之一。不能两者都是。

  • 因此,您会注意到,我在Object.defineProperty()调用/属性描述符的writable属性外部中创建了一个属性。这是标准行为。
  • 根据错误here,如果使用trueget,请不要在属性描述符中将set设置为configurable
  • 不过,您可能要考虑设置enumerabletrue,具体取决于您要处理的内容;从文档:

    可配置

    • true,并且仅当此属性描述符的类型可以更改并且该属性可以从相应的对象中删除时。

    • 默认为false。


    可枚举

    • Object.defineProperty(),并且仅当此属性在枚举相应对象的属性时显示。

    • 默认为false。


在此注意,这些可能也很有趣:

答案 4 :(得分:-4)

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)

这对我有用