Можно ли реализовать динамические геттеры / сеттеры в 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"

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

Эта концепция возможна в PHP с использованием магических методов __ get () и __ set () (информацию об этом см. В документации PHP ), поэтому Я действительно спрашиваю, есть ли им эквивалентный JavaScript?

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

119
задан Roman C 19 March 2016 в 08:59
поделиться

4 ответа

Обновление 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.

Это позволяет вам создать объект с нужной вам функцией захвата и установки:

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

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

190
ответ дан 24 November 2019 в 01:51
поделиться

В современном 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
    });
});
46
ответ дан 3 July 2019 в 04:58
поделиться

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

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

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

6
ответ дан 24 November 2019 в 01:51
поделиться
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)

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

-5
ответ дан 24 November 2019 в 01:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: