Возможно ли реализовать динамические геттеры / сеттеры в JavaScript? - PullRequest
115 голосов
/ 25 октября 2011

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

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

Теперь, мой вопрос: возможно ли определить вроде универсальных геттеров и сеттеров, подобных этим? Т.е. создать методы получения и установки для любого имени свойства, которое не уже определено.

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

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

Ответы [ 4 ]

186 голосов
/ 25 октября 2011

2013 и 2015 Обновление (исходный ответ от 2011 г. см. Ниже) :

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

"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 - это объект, который следует использовать как 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когда он еще не существует, и снова, когда мы его создаем, но не после этого.


Ответ от 2011 (см. выше для обновлений 2013 и 2015 годов):

Нет, в JavaScript нет функции всеобъемлющего свойства.Используемый вами синтаксис аксессора описан в Разделе 11.1.5 спецификации и не предлагает никаких подстановочных знаков или чего-то подобного.

Конечно, вы можете реализоватьфункция, чтобы сделать это, но я предполагаю, что вы, вероятно, не хотите использовать f = obj.prop("foo"); вместо f = obj.foo; и obj.prop("foo", value); вместо obj.foo = value; (что было бы необходимо для функции, чтобы обрабатывать неизвестные свойства).

FWIW, функция получения (я не беспокоился о логике установки) выглядела бы примерно так:

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

Но, опять же, я не могу представить, что вы действительно хотите сделатьчто из-за того, как это меняет то, как вы используете объект.

47 голосов
/ 13 февраля 2012

В современном 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
    });
});
6 голосов
/ 30 мая 2014

Исходный подход к этой проблеме может быть следующим:

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

Вы можете видеть, что это работает здесь .

0 голосов
/ 29 ноября 2013
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)

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

...