有可能在 JavaScript 中实现动态 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。

这个概念在 PHP 中使用 __get()__set()神奇方法是可能的(有关这些方法的信息,请参阅 PHP 文档) ,所以我真的在问是否有一个 JavaScript 等价于这些方法?

不用说,我希望有一个跨浏览器兼容的解决方案。

69248 次浏览

从 ES2015(又名“ ES6”)规范开始,这种情况发生了变化: JavaScript 现在有了 代理人。代理允许您创建真正代理其他对象的对象。下面是一个简单的例子,它在检索时将任何属性值(字符串)转换为所有大写,并为不存在的属性返回 "missing"而不是 undefined:

"use strict";
if (typeof Proxy == "undefined") {
throw new Error("This browser doesn't support Proxy");
}
let original = {
example: "value",
};
let proxy = new Proxy(original, {
get(target, name, receiver) {
if (Reflect.has(target, name)) {
let rv = Reflect.get(target, name, receiver);
if (typeof rv === "string") {
rv = rv.toUpperCase();
}
return rv;
}
return "missing";
}
});
console.log(`original.example = ${original.example}`); // "original.example = value"
console.log(`proxy.example = ${proxy.example}`);       // "proxy.example = VALUE"
console.log(`proxy.unknown = ${proxy.unknown}`);       // "proxy.unknown = missing"
original.example = "updated";
console.log(`original.example = ${original.example}`); // "original.example = updated"
console.log(`proxy.example = ${proxy.example}`);       // "proxy.example = UPDATED"

没有覆盖的操作有它们的默认行为。在上面的代码中,我们所覆盖的是 get,但是有一个完整的操作列表可以连接到。

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

  • target是被代理的对象(在我们的例子中是 original)。
  • name(当然)是要检索的属性的名称,它通常是一个字符串,但也可以是一个符号。
  • 如果属性是访问器而不是数据属性,那么在 getter 函数中应该使用 receiver作为对象。在正常情况下,这是代理或继承自它的东西,但它 可以是任何东西,因为陷阱可能被 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.example = ${obj.example}`);
obj.example = "value";
console.log(`[after] obj.example = ${obj.example}`);

以上的结果如下:

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

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


2011年的答案 (被上面的内容所淘汰,仍然适用于仅限于 ES5特性(如 Internet Explorer)的环境):

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

当然,您可以实现一个函数来执行此操作,但是我猜您可能不想使用 f = obj.prop("example");而不是 f = obj.example;obj.prop("example", value);而不是 obj.example = 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...
};

但是,我不能想象你真的想这么做,因为它改变了你使用对象的方式。

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)

这对我有用

以下可能是解决这一问题的原始办法:

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

你可以看到它的工作 给你

前言:

T.J。 Crowder 的回答 提到了一个 Proxy,这对于那些不存在的属性来说是一个全面的 getter/setter 所需要的,正如 OP 所要求的那样。根据动态 getter/setter 实际需要的行为,Proxy实际上可能不是必需的; 或者,您可能希望将 Proxy与下面我将展示的内容结合使用。

(附注: 我最近在 Linux 的 Firefox 上对 Proxy进行了彻底的试验,发现它非常强大,但是也有些令人困惑/难以正确使用。更重要的是,我还发现它的速度相当慢(至少相对于现在 JavaScript 的优化程度而言)——我说的是十倍的速度


要实现动态创建的 getter 和 setter,您可以使用 Object.defineProperty()Object.defineProperties()

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

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.

这里有几点需要注意:

  • 您不能在属性描述符(如上所示的 没有)中与 get和/或 set同时使用 value属性; 来自文档:

    对象中的属性描述符主要有两种类型: 数据描述符和访问器描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。访问器描述符访问器描述符是由一对 getter-setter 函数描述的属性。描述符必须是这两种风格中的一种; 它不能同时是这两种风格中的一种。

  • 因此,您会注意到我创建了 Object.defineProperty()调用/属性描述符的 val属性 在外面
  • 根据错误 给你,如果使用 getset,不要在属性描述符中将 writable设置为 true
  • 然而,你可能需要考虑设置 configurableenumerable,这取决于你想要什么;

    可配置的

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

    • 默认为 false。


    数不胜数

    • 当且仅当此属性在枚举相应对象上的属性时显示时,才使用 true

    • 默认为 false。


在这一点上,这些也可能是令人感兴趣的:

我在找一样东西,然后我自己发现了。

/*
This function takes an object and converts to a proxy object.
It also takes care of proxying nested objectsa and array.
*/
let getProxy = (original) => {


return new Proxy(original, {


get(target, name, receiver) {
let rv = Reflect.get(target, name, receiver);
return rv;
},


set(target, name, value, receiver) {


// Proxies new objects
if(typeof value === "object"){
value = getProxy(value);
}


return Reflect.set(target, name, value, receiver);
}
})
}


let first = {};
let proxy = getProxy(first);


/*
Here are the tests
*/


proxy.name={}                               // object
proxy.name.first={}                         // nested object
proxy.name.first.names=[]                   // nested array
proxy.name.first.names[0]={first:"vetri"}   // nested array with an object


/*
Here are the serialised values
*/
console.log(JSON.stringify(first))  // {"name":{"first":{"names":[{"first":"vetri"}]}}}
console.log(JSON.stringify(proxy))  // {"name":{"first":{"names":[{"first":"vetri"}]}}}