Obiekt Proxy jest używany w celu definiowania specyficznego zachowania dla podstawowych operacji (n.p. wyszukiwanie atrybutu, przypisanie, wyliczanie, wywołanie funkcji, etc).
Terminologia
- handler
- Zastępczy obiekt zawierający pułapki (traps).
- traps
- Metody zapewniające dostęp do atrybutów. Pojęcie to jest analogiczne do pułapek w systemie operacyjnym.
- target
- Obiekt wirtualizowany przez proxy. Często jest używany aby magazyować dane obiektu proxy. Niezmienniki (wartości które pozostają niezmienione) dotyczące nierozszerzalności obiektu lub niekonfigurowalnnych atrybutów są weryfikowane w oparciu o target.
Składnia
var p = new Proxy(target, handler);
Parametry
target
- Docelowy obiekt (może być obiektem dowolnego typu, włącznie z wbudowanymi tablicami, funkcjami, a nawet innyi obiektami proxy) przeznaczony do opakowania przez
Proxy
. handler
- Obiekt obsługujący którego atrybuty są funkcjami definiującymi zachowanie proxy podczas wykonania na nim operacji.
Metody
Proxy.revocable()
- Tworzy odwracalny obiekt
Proxy
.
Metody obiektu obsługującego
Obiekt obsługujący jest obiektem zastępczym zawierającym pułapki dla obieku Proxy
.
Wszystkie pułapki są opcjonalne. Jeśli pułapka nie została zdefiniowana, domyślnym zachowaniem jest przekazanie operacji do obiektu docelowego.
handler.getPrototypeOf()
- Pułapka na
Object.getPrototypeOf
. handler.setPrototypeOf()
- Pułapka na
Object.setPrototypeOf
. handler.isExtensible()
- Pułapka na
Object.isExtensible
. handler.preventExtensions()
- Pułapka na
Object.preventExtensions
. handler.getOwnPropertyDescriptor()
- Pułapka na
Object.getOwnPropertyDescriptor
. handler.defineProperty()
- Pułapka na
Object.defineProperty
. handler.has()
- Pułapka na operator
in
. handler.get()
- Pułapka na pobieranie wartości atrybutu.
handler.set()
- Pułapka na ustawianie wartości atrybutu.
handler.deleteProperty()
- Pułapka na operator
delete
. handler.ownKeys()
- Pułapka na
Object.getOwnPropertyNames
iObject.getOwnPropertySymbols
. handler.apply()
- Pułapka na wywołanie funkcji.
handler.construct()
- Pułapka na operator
new
.
Niektóre niestandardowe pułapki są przestarzałe i zostały usunięte.
Przykłady
Podstawowy przykład
W tym prostym przykładzie liczba 37
jest zwracana jako domyślna wartość kiedy nazwa atrybutu nie istnieje w obiekcie. W tym celu użyty jest handler get
.
var handler = { get: function(target, name) { return name in target ? target[name] : 37; } }; var p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37
Puste proxy przekazujące
W tym przykładzie używamy wbudowanego obiektu JavaScript do którego proxy przekaże wszystkie zaaplikowane na nim operacje.
var target = {}; var p = new Proxy(target, {}); p.a = 37; // operacja przekazana do obiektu target console.log(target.a); // 37. Operacja została prawidłowo przekazana
Zwróć uwagę, że ten przykład działa dla obiektów JavaScript jednak nie sprawdzi się w przypadku obiektów przeglądarki takich jak elementy DOM. Sprawdź jedno rozwiązanie.
Walidacja
Używając Proxy
, łatwo możesz zwalidować wartości przekazywane do obiektu. Poniższy przykład używa metody obsługującej set
.
let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } // domyślnym zachowaniem jest zapisanie wartości obj[prop] = value; // oznacza pomyślne wykonanie return true; } }; let person = new Proxy({}, validator); person.age = 100; console.log(person.age); // 100 person.age = 'young'; // rzuca wyjątek person.age = 300; // rzuca wyjątek
Rozszerzanie konstruktora
Funkcja proxy może w łatwy sposób rozszerzyć konstruktor innym konstruktorem. W tym przykładzie użyto funkcje obsługujące construct
oraz apply
.
function extend(sup, base) { var descriptor = Object.getOwnPropertyDescriptor( base.prototype, 'constructor' ); base.prototype = Object.create(sup.prototype); var handler = { construct: function(target, args) { var obj = Object.create(base.prototype); this.apply(target, obj, args); return obj; }, apply: function(target, that, args) { sup.apply(that, args); base.apply(that, args); } }; var proxy = new Proxy(base, handler); descriptor.value = proxy; Object.defineProperty(base.prototype, 'constructor', descriptor); return proxy; } var Person = function(name) { this.name = name; }; var Boy = extend(Person, function(name, age) { this.age = age; }); Boy.prototype.sex = 'M'; var Peter = new Boy('Peter', 13); console.log(Peter.sex); // "M" console.log(Peter.name); // "Peter" console.log(Peter.age); // 13
Manipulacja węzłami DOM
Czasami konieczne jest przełączenie atrybutu lub nazwy klasy dwóch innych elementów. Poniższy przykład pokazuje wykonanie funkcją obsługującą set
.
let view = new Proxy({ selected: null }, { set: function(obj, prop, newval) { let oldval = obj[prop]; if (prop === 'selected') { if (oldval) { oldval.setAttribute('aria-selected', 'false'); } if (newval) { newval.setAttribute('aria-selected', 'true'); } } // The default behavior to store the value obj[prop] = newval; // Indicate success return true; } }); let i1 = view.selected = document.getElementById('item-1'); console.log(i1.getAttribute('aria-selected')); // 'true' let i2 = view.selected = document.getElementById('item-2'); console.log(i1.getAttribute('aria-selected')); // 'false' console.log(i2.getAttribute('aria-selected')); // 'true'
Korekcja wartości i dodatkowych atrybutów
Obiekt proxy products
wylicza przekazaną wartość i konwertuje to tablicy w razie potrzeby. Obiekt dodatkowo obsługuje dodatkowy atrybut latestBrowser
zarówno jako getter i setter.
let products = new Proxy({ browsers: ['Internet Explorer', 'Netscape'] }, { get: function(obj, prop) { // An extra property if (prop === 'latestBrowser') { return obj.browsers[obj.browsers.length - 1]; } // The default behavior to return the value return obj[prop]; }, set: function(obj, prop, value) { // An extra property if (prop === 'latestBrowser') { obj.browsers.push(value); return true; } // Convert the value if it is not an array if (typeof value === 'string') { value = [value]; } // The default behavior to store the value obj[prop] = value; // Indicate success return true; } }); console.log(products.browsers); // ['Internet Explorer', 'Netscape'] products.browsers = 'Firefox'; // przekazano string (przez pomyłkę) console.log(products.browsers); // ['Firefox'] <- nie ma problemu, wartość jest typu array products.latestBrowser = 'Chrome'; console.log(products.browsers); // ['Firefox', 'Chrome'] console.log(products.latestBrowser); // 'Chrome'
Wyszukiwanie elementu tablicy po jego właściwości
Poniższe proxy rozszerza tablicę o różne użyteczne funkcjonalności. Jak widać, można elastycznie "definiować" właściwości bez użycia Object.defineProperties
. Ten przykład może być użyty aby znaleźć wiersz tabeli po jego komórce. W takim przypadku, celem będzie table.rows
.
let products = new Proxy([ { name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }, { name: 'Thunderbird', type: 'mailer' } ], { get: function(obj, prop) { // domyślnym zachowaniem jest zwrócenie wartości; prop jest zwykle typu integer if (prop in obj) { return obj[prop]; } // zwróć liczbę produktów; alias dla products.length if (prop === 'number') { return obj.length; } let result, types = {}; for (let product of obj) { if (product.name === prop) { result = product; } if (types[product.type]) { types[product.type].push(product); } else { types[product.type] = [product]; } } // znajdź product po nazwie if (result) { return result; } // znajdź produkty po typie if (prop in types) { return types[prop]; } // zwróć typy produktów if (prop === 'types') { return Object.keys(types); } return undefined; } }); console.log(products[0]); // { name: 'Firefox', type: 'browser' } console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' } console.log(products['Chrome']); // undefined console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }] console.log(products.types); // ['browser', 'mailer'] console.log(products.number); // 3
Pełna lista przykładów pułapek
W celu stworzenia pełnej listy przykładów pułapek
, w celach dydaktycznych, spróbujemy zastosować proxy nanie natywnym obiekcie który się szczególnie nadaje do tego typu operacji: globalny obiekt docCookies
stworzony przez "mały framework" opublikowany na stronie document.cookie
.
/* var docCookies = ... pobranie obiektu "docCookies" tutaj: https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support */ var docCookies = new Proxy(docCookies, { get: function (oTarget, sKey) { return oTarget[sKey] || oTarget.getItem(sKey) || undefined; }, set: function (oTarget, sKey, vValue) { if (sKey in oTarget) { return false; } return oTarget.setItem(sKey, vValue); }, deleteProperty: function (oTarget, sKey) { if (sKey in oTarget) { return false; } return oTarget.removeItem(sKey); }, enumerate: function (oTarget, sKey) { return oTarget.keys(); }, ownKeys: function (oTarget, sKey) { return oTarget.keys(); }, has: function (oTarget, sKey) { return sKey in oTarget || oTarget.hasItem(sKey); }, defineProperty: function (oTarget, sKey, oDesc) { if (oDesc && 'value' in oDesc) { oTarget.setItem(sKey, oDesc.value); } return oTarget; }, getOwnPropertyDescriptor: function (oTarget, sKey) { var vValue = oTarget.getItem(sKey); return vValue ? { value: vValue, writable: true, enumerable: true, configurable: false } : undefined; }, }); /* Cookies test */ console.log(docCookies.my_cookie1 = 'First value'); console.log(docCookies.getItem('my_cookie1')); docCookies.setItem('my_cookie1', 'Changed value'); console.log(docCookies.my_cookie1);
Specyfikacje
Specification | Status | Comment |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) The definition of 'Proxy' in that specification. |
Standard | Initial definition. |
ECMAScript 2016 (ECMA-262) The definition of 'Proxy' in that specification. |
Standard | |
ECMAScript 2017 (ECMA-262) The definition of 'Proxy' in that specification. |
Standard | |
ECMAScript Latest Draft (ECMA-262) The definition of 'Proxy' in that specification. |
Draft |
Kompatybilność przeglądarek
Desktop | Mobile | Server | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Proxy | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.apply | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.construct | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.defineProperty | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.deleteProperty | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.enumerate | Chrome No support No | Edge No support No | Firefox No support 37 — 47 | IE No support No | Opera No support No | Safari No support No | WebView Android No support No | Chrome Android No support No | Firefox Android No support 37 — 47 | Opera Android No support No | Safari iOS No support No | Samsung Internet Android No support No | nodejs No support No |
handler.get | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.getOwnPropertyDescriptor | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.getPrototypeOf | Chrome Full support 49 | Edge No support No | Firefox Full support 49 | IE No support No | Opera Full support 36 | Safari No support No | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 49 | Opera Android Full support 36 | Safari iOS No support No | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.has | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.isExtensible | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 31 | IE No support No | Opera Full support 36 | Safari ? | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 31 | Opera Android Full support 36 | Safari iOS ? | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.ownKeys | Chrome Full support 49 | Edge Full support 12 | Firefox
Full support
18
| IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android
Full support
18
| Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.preventExtensions | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 22 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 22 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.set | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 18 | IE No support No | Opera Full support 36 | Safari Full support 10 | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 18 | Opera Android Full support 36 | Safari iOS Full support 10 | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
handler.setPrototypeOf | Chrome Full support 49 | Edge Full support 12 | Firefox Full support 49 | IE No support No | Opera Full support 36 | Safari ? | WebView Android Full support 49 | Chrome Android Full support 49 | Firefox Android Full support 49 | Opera Android Full support 36 | Safari iOS ? | Samsung Internet Android Full support 5.0 | nodejs Full support 6.0.0 |
revocable | Chrome Full support 63 | Edge Full support 12 | Firefox Full support 34 | IE No support No | Opera Full support Yes | Safari Full support 10 | WebView Android Full support 63 | Chrome Android Full support 63 | Firefox Android Full support 34 | Opera Android Full support Yes | Safari iOS Full support 10 | Samsung Internet Android Full support 8.0 | nodejs Full support 6.0.0 |
Legend
- Full support
- Full support
- No support
- No support
- Compatibility unknown
- Compatibility unknown
- Non-standard. Expect poor cross-browser support.
- Non-standard. Expect poor cross-browser support.
- Deprecated. Not for use in new websites.
- Deprecated. Not for use in new websites.
- See implementation notes.
- See implementation notes.
Zobacz również
- "Proxies are awesome" Brendan Eich presentation at JSConf (slides)
- ECMAScript Harmony Proxy proposal page and ECMAScript Harmony proxy semantics page
- Tutorial on proxies
- SpiderMonkey specific Old Proxy API
Object.watch()
is a non-standard feature but has been supported in Gecko for a long time.
Licensing note
Some content (text, examples) in this page has been copied or adapted from the ECMAScript wiki which content is licensed CC 2.0 BY-NC-SA.