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



само собой разумеется, я бы в идеале хотел решение, совместимое с кросс-браузером.

470   4  

4 ответов:

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

операции, которые вы не переопределяете, имеют поведение по умолчанию. В приведенном выше, все мы переопределяем это get, но есть целый список операций, которые вы можете подключить.

на get список аргументов функции обработчика:

  • target проксируется ли объект (original в нашем случае).
  • name (конечно) наименование имущества восстановленный.
  • receiver либо сам прокси, либо то, что наследует от него. В нашем случае, receiver и ===proxy, а если proxy были использованы в качестве прототипа, receiver может быть объектом-потомком, следовательно, он находится в сигнатуре функции (но в конце, поэтому вы можете легко оставить его, если, как в нашем примере выше, вы на самом деле не используете его).

это позволяет создать объект с функцией catch-all getter и setter вы хочу:

var obj = new Proxy({}, {
    get: function(target, name) {
        if (!(name in target)) {
            console.log("Getting non-existant property '" + name + "'");
            return undefined;
        }
        return target[name];
    },
    set: function(target, name, value) {
        if (!(name in target)) {
            console.log("Setting non-existant property '" + name + "', initial value: " + value);
        }
        target[name] = value;
        return true;
    }
});

console.log("[before] obj.foo = " + obj.foo);
obj.foo = "bar";
console.log("[after] obj.foo = " + obj.foo);

"use strict";
(function() {
    if (typeof Proxy == "undefined") {
        console.log("This browser doesn't support Proxy");
        return;
    }

    var obj = new Proxy({}, {
        get: function(target, name) {
            if (!(name in target)) {
                console.log("Getting non-existant property '" + name + "'");
                return undefined;
            }
            return target[name];
        },
        set: function(target, name, value) {
            if (!(name in target)) {
                console.log("Setting non-existant property '" + name + "', initial value: " + value);
            }
            target[name] = value;
            return true;
        }
    });

    console.log("[before] obj.foo = " + obj.foo);
    obj.foo = "bar";
    console.log("[after] obj.foo = " + obj.foo);
})();

(обратите внимание, как я ушел receiver выкл функции, так как мы не используем его. receiver является дополнительным четвертым arg на set.)

вывод выше:

Getting non-existant property 'foo'
[before] obj.foo = undefined
Setting non-existant property 'foo', initial value: bar
[after] obj.foo = bar

обратите внимание, как мы получаем" несуществующее " сообщение при попытке получить foo, когда он еще не существует, и снова, когда мы его создаем, но не впоследствии.


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

нет, JavaScript не имеет функции свойства catch-all. Синтаксис метода доступа, который вы используете, описан в 11.1.5 спецификации, и не предлагает никаких подстановочных знаков или что-то в этом роде.

можно, конечно, реализовать функцию, чтобы сделать это, но я предполагаю, что вы вероятно не хотите использовать f = obj.prop("foo");, а не f = obj.foo; и obj.prop("foo", value);, а не obj.foo = 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...
};

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

в современном 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
    });
});

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

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

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

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)

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

Comments

    Ничего не найдено.