Можно ли реализовать динамические геттеры/сеттеры в JavaScript?

Я знаю, как создавать геттеры и сеттеры для свойств, имена которых уже известны, делая что-то вроде этого:

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

Теперь, на мой вопрос, можно ли как-то определить такие уловки и сеттеры-уловки? I.e., создайте геттеры и сеттеры для любого имени свойства, которое еще не определено.

Концепция возможна на PHP с использованием магических методов __get() и __set() (см. документацию по PHP для получения информации об этом), так что я действительно спрашиваю, есть ли там эквивалент JavaScript?

Излишне говорить, что мне идеально понравилось бы решение, совместимое с кросс-браузером.

Ответ 1

Обновление 2013 и 2015 годов (см. ниже первоначальный ответ от 2011 года):

Это изменилось как спецификация ES2015 (ака ES6): JavaScript теперь имеет прокси. Прокси позволяют создавать объекты, которые являются истинными прокси-серверами для (фасадов) других объектов. Вот простой пример, который превращает любые значения свойств, которые являются строками для всех кепок при извлечении:

var original = {
    "foo": "bar"
};
var proxy = new Proxy(original, {
    get: function(target, name, receiver) {
        var rv = target[name];
        if (typeof rv === "string") {
            rv = rv.toUpperCase();
        }
        return rv;
      }
});
console.log("original.foo = " + original.foo); // "bar"
console.log("proxy.foo = " + proxy.foo);       // "BAR"

"use strict";
(function() {
    if (typeof Proxy == "undefined") {
        console.log("This browser doesn't support Proxy");
        return;
    }
    var original = {
        "foo": "bar"
    };
    var proxy = new Proxy(original, {
        get: function(target, name, receiver) {
            var rv = target[name];
            if (typeof rv === "string") {
                rv = rv.toUpperCase();
            }
            return rv;
        }
    });
    console.log("original.foo = " + original.foo); // "bar"
    console.log("proxy.foo = " + proxy.foo);       // "BAR"
})();

Ответ 2

В современных Javascript (FF4 +, IE9 +, Chrome 5+, Safari 5.1+, Opera 11.60+) есть Object.defineProperty. Этот пример в MDN очень хорошо объясняет, как работает defineProperty, и делает возможным динамические геттеры и сеттеры.

Технически это не будет работать ни на одном динамическом запросе, как вы ищите, но если ваши действительные геттеры и сеттеры определяются, например, вызовом AJAX на сервер JSON-RPC, например, используйте это следующим образом:

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

Ответ 3

Ниже приведен оригинальный подход к этой проблеме:

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

Edit: Улучшенный, более объектно-ориентированный подход, основанный на том, что я предложил, выглядит следующим образом:

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

Вы можете увидеть, как он работает здесь.

Ответ 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)

это работает для меня