Zdaję sobie sprawę, jak tworzyć metody pobierające i ustawiające dla właściwości, których nazwy już znamy, robiąc coś takiego:
// 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"
Teraz moje pytanie brzmi: czy jest możliwe zdefiniowanie czegoś w rodzaju metod pobierających i ustawiających typu catch-all, takich jak te? To znaczy, utwórz metody pobierające i ustawiające dla dowolnej nazwy właściwości, która nie jest jeszcze zdefiniowana.
Ta koncepcja jest możliwa w PHP przy użyciu metod magicznych __get()
i __set()
( informacje na ich temat można znaleźć w dokumentacji PHP ), więc naprawdę pytam, czy istnieje odpowiednik w języku JavaScript?
Nie trzeba dodawać, że najlepiej byłoby rozwiązanie, które jest kompatybilne z różnymi przeglądarkami.
javascript
metaprogramming
getter-setter
daiscog
źródło
źródło
Odpowiedzi:
Aktualizacja z 2013 i 2015 r. (Poniżej przedstawiono oryginalną odpowiedź z 2011 r.) :
Zmieniło się to od specyfikacji ES2015 (znanej również jako „ES6”): JavaScript ma teraz serwery proxy . Serwery proxy umożliwiają tworzenie obiektów, które są prawdziwymi proxy dla innych obiektów (fasady na). Oto prosty przykład, który zamienia wszystkie wartości właściwości, które są ciągami, na wszystkie kapsle podczas pobierania:
"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"
Operacje, których nie zastępujesz, mają swoje domyślne zachowanie. W powyższym, wszystko, co nadpisujemy, to
get
, ale istnieje cała lista operacji, do których można się podłączyć.Na
get
liście argumentów funkcji obsługi:target
to obiekt, który ma zostać przekazany (original
w naszym przypadku).name
jest (oczywiście) nazwą pobieranej właściwości, która zwykle jest ciągiem znaków, ale może być również symbolem.receiver
jest obiektem, który powinien być używany tak jakthis
w funkcji pobierającej, jeśli właściwość jest akcesorem, a nie właściwością danych. W normalnym przypadku jest to proxy lub coś, co po nim dziedziczy, ale może to być wszystko, ponieważ pułapka może zostać uruchomiona przezReflect.get
.Pozwala to na utworzenie obiektu z wybraną funkcją pobierania i ustawiania catch-all:
"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}`);
Wynik powyższego to:
Zwróć uwagę, jak otrzymujemy komunikat „nieistniejący”, kiedy próbujemy odzyskać,
foo
gdy jeszcze nie istnieje, i ponownie, kiedy go tworzymy, ale nie później.Odpowiedź z 2011 r. (Patrz powyżej dla aktualizacji z 2013 i 2015 r.) :
Nie, JavaScript nie ma właściwości typu catch-all. Składnia akcesora, której używasz, jest omówiona w sekcji 11.1.5 specyfikacji i nie oferuje żadnych symboli wieloznacznych ani czegoś podobnego.
Możesz oczywiście zaimplementować funkcję, aby to zrobić, ale przypuszczam, że prawdopodobnie nie chcesz używać
f = obj.prop("foo");
zamiastf = obj.foo;
iobj.prop("foo", value);
zamiastobj.foo = value;
(co byłoby konieczne, aby funkcja obsługiwała nieznane właściwości).FWIW, funkcja getter (nie zawracałem sobie głowy logiką ustawiającą) wyglądałaby mniej więcej tak:
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... };
Ale znowu nie mogę sobie wyobrazić, że naprawdę chciałbyś to zrobić, ponieważ zmienia to sposób korzystania z obiektu.
źródło
Proxy
:Object.defineProperty()
. Szczegóły umieściłem w mojej nowej odpowiedzi .Oryginalnym podejściem do tego problemu może być:
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; } }
Aby z niego skorzystać, właściwości należy przekazać jako ciągi. Oto przykład tego, jak to działa:
//To set a property obj.set('myProperty','myValue'); //To get a property var myVar = obj.get('myProperty');
Edycja: Ulepszone, bardziej zorientowane obiektowo podejście oparte na tym, co zaproponowałem, jest następujące:
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'));
Możesz zobaczyć, jak działa tutaj .
źródło
Przedmowa:
Odpowiedź TJ Crowdera wspomina o
Proxy
, które będzie potrzebne dla metody pobierającej / ustawiającej typu catch-all dla właściwości, które nie istnieją, o co prosił OP. W zależności od tego, jakie zachowanie jest faktycznie pożądane w przypadku dynamicznych metod pobierających / ustawiających, aProxy
może nie być konieczne; lub, potencjalnie, możesz chcieć użyć kombinacji aProxy
z tym, co pokażę poniżej.(PS
Proxy
Niedawno eksperymentowałem z Firefoksem w systemie Linux i stwierdziłem, że jest on bardzo wydajny, ale także nieco zagmatwany / trudny w obsłudze i poprawny. Co ważniejsze, odkryłem również, że jest dość powolny (przynajmniej w w odniesieniu do tego, jak obecnie wygląda zoptymalizowany JavaScript) - mówię w dziedzinie dziesięciokrotności wolniej).Aby konkretnie zaimplementować dynamicznie tworzone metody pobierające i ustawiające, możesz użyć
Object.defineProperty()
lubObject.defineProperties()
. Jest to również dość szybkie.Istota jest taka, że możesz zdefiniować metodę pobierającą i / lub ustawiającą na obiekcie w ten sposób:
let obj = {}; let val = 0; Object.defineProperty(obj, 'prop', { //<- This object is called a "property descriptor". //Alternatively, use: `get() {}` get: function() { return val; }, //Alternatively, use: `set(newValue) {}` set: function(newValue) { val = newValue; } }); //Calls the getter function. console.log(obj.prop); let copy = obj.prop; //Etc. //Calls the setter function. obj.prop = 10; ++obj.prop; //Etc.
Kilka rzeczy, na które należy zwrócić uwagę:
value
właściwości w deskryptorze właściwości ( nie pokazano powyżej) jednocześnie zget
i / lubset
; z dokumentów:val
własności zewnątrz wObject.defineProperty()
deskryptorze połączenia / nieruchomości. To jest standardowe zachowanie.writable
siętrue
w deskryptorze nieruchomości, jeśli używaszget
lubset
.configurable
aenumerable
jednak, w zależności od tego, co jesteś po; z dokumentów:W tej notatce mogą one również być interesujące:
Object.getOwnPropertyNames(obj)
: pobiera wszystkie właściwości obiektu, nawet te, których nie można policzyć (AFAIK to jest jedyny sposób!).Object.getOwnPropertyDescriptor(obj, prop)
: pobiera deskryptor właściwości obiektu, do którego został przekazanyObject.defineProperty()
powyżej.obj.propertyIsEnumerable(prop);
: dla indywidualnej właściwości w konkretnej instancji obiektu, wywołaj tę funkcję na instancji obiektu, aby określić, czy dana właściwość jest wyliczalna, czy nie.źródło
__get
i__set
.defineProperty
nie obsługuje tej sprawy. Z pytania: „To znaczy, utwórz metody pobierające i ustawiające dla dowolnej nazwy właściwości, która nie jest jeszcze zdefiniowana”. (ich podkreślenie).defineProperty
określa właściwości z wyprzedzeniem. Jedynym sposobem na zrobienie tego, o co prosił OP, jest proxy.obj.whateverProperty
taki sposób, że biblioteka może przechwycić to za pomocą ogólnego gettera i otrzymać nazwę właściwości użytkownik próbował uzyskać dostęp. Stąd wymóg dotyczący „przechwytujących wszystko i ustawiających”.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)
to działa dla mnie
źródło
Function()
ten sposób jest jak używanieeval
. Po prostu wstaw bezpośrednio funkcje jako parametrydefineProperty
. Lub, jeśli z jakiegoś powodu upierają się do dynamicznego tworzeniaget
iset
, a następnie użyć funkcji wyższego rzędu, który tworzy funkcję i zwraca go, jakvar get = (function(propName) { return function() { return this[propName]; };})('value');