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

发布于 2024-12-12 00:43:47 字数 796 浏览 0 评论 0原文

我知道如何为已经知道名称的属性创建 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?即,为任何尚未定义的属性名称创建 getter 和 setter。

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

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

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

// 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't already defined.

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

冰葑 2024-12-19 00:43:47

从 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}`);

上面的输出是:

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

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

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, and returns "missing" instead of undefined for a property that doesn't exist:

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

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.

In the get handler function's arguments list:

  • target is the object being proxied (original, in our case).
  • name is (of course) the name of the property being retrieved, which is usually a string but could also be a Symbol.
  • receiver is the object that should be used as this in 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 can be anything since the trap may be triggered by Reflect.get.

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

"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}`);

The output of the above is:

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

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


Answer from 2011 (obsoleted by the above, still relevant to environments limited to ES5 features like Internet Explorer):

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

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

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

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.

瞎闹 2024-12-19 00:43:47

前言:

TJ Crowder 的回答提到了一个 Proxy,它将是正如OP所要求的那样,对于不存在的属性需要一个包罗万象的getter/setter。根据动态 getter/setter 实际需要的行为,代理实际上可能不是必需的;或者,您可能希望将代理与我将在下面向您展示的内容结合使用。

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


为了具体实现动态创建的 getter 和 setter,你需要可以使用 Object.defineProperty()< /a> 或 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 属性。这是标准行为。
  • 根据此处的错误,请勿将writable设置为true 如果您使用 getset,则在属性描述符中。
  • 不过,您可能需要考虑设置 configurableenumerable,具体取决于您的需求;来自文档:
    <块引用>

    可配置

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

    • 默认为 false。

    <小时>

    可枚举

    • true 当且仅当该属性在相应对象的属性枚举期间出现。

    • 默认为 false。


就此而言,这些也可能令人感兴趣:

Preface:

T.J. Crowder's answer mentions a Proxy, which will be needed for a catch-all getter/setter for properties which don't exist, as the OP was asking for. Depending on what behavior is actually wanted with dynamic getters/setters, a Proxy may not actually be necessary though; or, potentially, you may want to use a combination of a Proxy with what I'll show you below.

(P.S. I have experimented with Proxy thoroughly in Firefox on Linux recently and have found it to be very capable, but also somewhat confusing/difficult to work with and get right. More importantly, I have also found it to be quite slow (at least in relation to how optimized JavaScript tends to be nowadays) - I'm talking in the realm of deca-multiples slower.)


To implement dynamically created getters and setters specifically, you can use Object.defineProperty() or Object.defineProperties(). This is also quite fast.

The gist is that you can define a getter and/or setter on an object like so:

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.

Several things to note here:

  • You cannot use the value property in the property descriptor (not shown above) simultaneously with get and/or set; from the docs:

    Property descriptors present in objects come in two main flavors: data descriptors and accessor descriptors. A data descriptor is a property that has a value, which may or may not be writable. An accessor descriptor is a property described by a getter-setter pair of functions. A descriptor must be one of these two flavors; it cannot be both.

  • Thus, you'll note that I created a val property outside of the Object.defineProperty() call/property descriptor. This is standard behavior.
  • As per the error here, don't set writable to true in the property descriptor if you use get or set.
  • You might want to consider setting configurable and enumerable, however, depending on what you're after; from the docs:

    configurable

    • true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.

    • Defaults to false.


    enumerable

    • true if and only if this property shows up during enumeration of the properties on the corresponding object.

    • Defaults to false.


On this note, these may also be of interest:

岁月静好 2024-12-19 00:43:47

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

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

您可以在此处。

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.

我不咬妳我踢妳 2024-12-19 00:43:47

我一直在寻找一些东西,然后我自己找到了答案。

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

I was looking for something and I figured out on my own.

/*
    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"}]}}}
茶色山野 2024-12-19 00:43:47
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 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文