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

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

本文关键字:动态 getter setters 实现 JavaScript 是否      更新时间:2023-09-26

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

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

> 这在 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(当然(是要检索的属性的名称,它通常是一个字符串,但也可以是符号。
  • 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.example = ${obj.example}`);
obj.example = "value";
console.log(`[after] obj.example = ${obj.example}`);

上面的输出是:

获取不存在的属性"示例"[before] obj.example = undefined设置不存在的属性"示例",初始值:值[之后] obj.example = 值

请注意,当我们尝试检索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 函数(我没有打扰二传手逻辑(看起来像这样:

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

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

前言:

T.J. Crowder的回答提到了一个Proxy,对于不存在的属性,一个包罗万象的获取者/设置者将需要它,正如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.

这里需要注意的几点:

  • 您不能将属性描述符(上面未显示(中的 value 属性与 get 和/或 set 一起使用; 从文档中:

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

  • 因此,您会注意到我在Object.defineProperty()调用/属性描述符之外创建了一个val属性。这是标准行为。
  • 根据此处的错误,如果使用 getset,请不要在属性描述符中将writable设置为 true
  • 但是,您可能需要考虑设置configurableenumerable,具体取决于您的追求; 从文档中:

    配置

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

    • 默认为 false。

    <小时 />

    可枚举

    • true当且仅当此属性在枚举相应对象的属性期间显示时。

    • 默认为 false。

<小时 />

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

  • Object.getOwnPropertyNames(obj) :获取对象的所有属性,甚至是不可枚举的属性(AFAIK 这是这样做的唯一方法!
  • Object.getOwnPropertyDescriptor(obj, prop) :获取对象的属性描述符,即传递给上述Object.defineProperty()的对象。
  • obj.propertyIsEnumerable(prop); :对于特定对象实例上的单个属性,在对象实例上调用此函数以确定特定属性是否可枚举。

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

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

您可以在此处看到它的工作。

我正在寻找一些东西,我自己想通了。

/*
    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"}]}}}
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)

这对我有用