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