현재 번역은 완벽하지 않습니다. 한국어로 문서 번역에 동참해주세요.
Proxy 객체는 개별적인 행동을 정의하는데 사용되는데, 기초적인 동작(예: property lookup, assignment, enumeration, function invocation 등)을 위함이다.
Terminology (용어)
- handler
- trap들을 가지고 있는 Placeholder 객체.
- traps
- 프로퍼티에 접근할 수 있는 메소드. 운영체제에서 trap 이라는 컨셉과 유사하다.
- target
- proxy를 시각화하는 개체이다. 이것은 종종 proxy를 위한 저장 backend로 사용된다. 확장되지 않거나(non-extensibility) 설정 프로퍼티(configurable properties)에 관한 Invariants(변하지 않고 남아있는 객체) 는 target에 대하여 검증 받는다.
Syntax (문법)
var p = new Proxy(target, handler);
Parameters
target- proxy와 함께 감싸진 target 객체 (native array, function, 다른 proxy을 포함한 객체)
handler- 프로퍼티들이 function 인 객체이다. 동작이 수행될 때, handler는 proxy의 행동을 정의한다.
Methods
Proxy.revocable()- 폐기할 수 있는(revocable) Proxy 객체를 생성.
Methods of the handler object
handler객체는 Proxy를 위한 trap들을 포함하고 있는 placeholder 객체이다.
All traps are optional. If a trap has not been defined, the default behavior is to forward the operation to the target.
handler.getPrototypeOf()- A trap for
Object.getPrototypeOf. handler.setPrototypeOf()- A trap for
Object.setPrototypeOf. handler.isExtensible()- A trap for
Object.isExtensible. handler.preventExtensions()- A trap for
Object.preventExtensions. handler.getOwnPropertyDescriptor()- A trap for
Object.getOwnPropertyDescriptor. handler.defineProperty()- A trap for
Object.defineProperty. handler.has()- A trap for the
inoperator. handler.get()- A trap for getting property values.
handler.set()- A trap for setting property values.
handler.deleteProperty()- A trap for the
deleteoperator. handler.ownKeys()- A trap for
Object.getOwnPropertyNames. handler.apply()- A trap for a function call.
handler.construct()- A trap for the
newoperator.
Some non-standard traps are obsolete and have been removed.
Examples
Basic example
프로퍼티 이름이 객체에 없을때, 기본값을 숫자 37로 리턴받는 간단한 예제이다. 이것은 get handler 를 사용하였다.
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
No-op forwarding proxy
이 예제에서는, native JavaScript를 사용하겠다. proxy는 적용된 모든 동작으로 보낼 것이다.
var target = {};
var p = new Proxy(target, {});
p.a = 37; // target으로 동작이 전달
console.log(target.a); // 37. 동작이 제대로 전달됨
Validation (검증)
Proxy에서, 객체에 전달된 값을 쉽게 검증할 수 있다. 이 예제는 set handler 를 사용하였다.
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');
}
}
// The default behavior to store the value
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Throws an exception
person.age = 300; // Throws an exception
Extending constructor (생성자 확장)
function proxy는 쉽게 새로운 생성자와 함께 생성자를 확장할 수 있다. 이 예제에서는 construct 와 apply handlers 를 사용하였다.
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
Manipulating DOM nodes (DOM nodes 조작)
가끔씩, 두 개의 다른 element의 속성이나 클래스 이름을 바꾸고 싶을 것이다. 아래는 set handler 를 사용하였다.
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;
}
});
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'
Value correction and an extra property (값 정정과 추가적인 property)
products 라는 proxy 객체는 전달된 값을 평가하고, 필요할 때 배열로 변환한다. 이 객체는 latestBrowser 라는 추가적인 property를 지원하는데, getter와 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;
}
// 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;
}
});
console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // pass a string (by mistake)
console.log(products.browsers); // ['Firefox'] <- no problem, the value is an array
products.latestBrowser = 'Chrome';
console.log(products.browsers); // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'
Finding an array item object by its property (property로 배열의 객체를 찾기)
proxy 는 유용한 특성을 가진 배열로 확장할 것이다. Object.defineProperties를 사용하지 않고, 유연하게 property들을 유연하게 "정의"할 수 있다. 이 예제는 테이블의 cell을 이용해서 row(열)을 찾는데 적용할 수 있다. 이 경우, target은 table.rows가 될 것이다.
let products = new Proxy([
{ name: 'Firefox', type: 'browser' },
{ name: 'SeaMonkey', type: 'browser' },
{ name: 'Thunderbird', type: 'mailer' }
],
{
get: function(obj, prop) {
// The default behavior to return the value; prop is usually an integer
if (prop in obj) {
return obj[prop];
}
// Get the number of products; an alias of 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];
}
}
// Get a product by name
if (result) {
return result;
}
// Get products by type
if (prop in types) {
return types[prop];
}
// Get product types
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
A complete traps list example (완벽한 traps 리스트 예제)
이제 완벽한 traps 리스트를 생성하기 위해서, non native 객체를 프록시화 할 것이다. 이것은 특히, 다음과 같은 동작에 적합하다 : the "little framework" published on the document.cookie page 에 의해 생성된 docCookies 는 글로벌 객체
/*
var docCookies = ... get the "docCookies" object here:
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);
Specifications
| Specification | Status | Comment |
|---|---|---|
| ECMAScript 2015 (6th Edition, ECMA-262) The definition of 'Proxy' in that specification. |
Standard | Initial definition. |
| ECMAScript 2017 Draft (ECMA-262) The definition of 'Proxy' in that specification. |
Draft |
Browser compatibility
| Feature | Chrome | Edge | Firefox (Gecko) | Internet Explorer | Opera | Safari |
|---|---|---|---|---|---|---|
| Basic support | 49.0 | 13 (10586) | 18 (18) | No support | 36 | 10.0 |
| Feature | Android | Chrome for Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
|---|---|---|---|---|---|---|
| Basic support | ? | 49.0 | 18 (18) | 13 (10586) | ? | 10.0 |
Gecko specific notes
- At present,
Object.getPrototypeOf(proxy)unconditionally returnsObject.getPrototypeOf(target), because the ES6 getPrototypeOf trap is not yet implemented (bug 888969, bug 888969). Array.isArray(proxy)unconditionally returnsArray.isArray(target)(bug 1111785, bug 1111785).Object.prototype.toString.call(proxy)unconditionally returnsObject.prototype.toString.call(target), because ES6 Symbol.toStringTag is not yet implemented (bug 1114580).
See also
- "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.