目錄
- 1. 概述
- 2. Proxy 實例的方法
- 2.1 handler.get()
- 2.2 handler.set()
- 2.3 handler.apply()
- 2.4 handler.has()
- 2.5 handler.construct()
- 2.6 handler.deleteProperty()
- 2.7 handler.defineProperty()
- 2.8 handler.getOwnPropertyDescriptor()
- 2.9 handler.getPrototypeOf()
- 2.10 handler.isExtensible()
- 2.11 handler.isExtensible()
- 2.12 handler.preventExtensions()
- 2.12 handler.setPrototypeOf()
- 3 Proxy.revocable()
- 4 參考鏈接
1. 概述
Proxy
用於修改某些操作的默認行爲,屬於一種“元編程”(meta programming)。先看下面一個例子。
var obj= new Proxy({}, {
get: function(target, pro){
console.info(`getting ${pro}....`);
return "jidi"
}
})
obj.name;
// getting name....
// "jidi"
上面代碼,爲一個空對象重新設置了屬性的讀取行爲,對設置了Proxy
的對象obj
,讀取他的屬性,都會返回jidi
。
ES6 原生提供Proxy
構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);
Proxy對象的所有用法都是上面這種形式。其中,new Proxy
表示生成一個Proxy
實例,target
表示要代理的對象,handler
攔截器,表示代理行爲,也是一個對象。
下面是另一個例子。
var obj = new Proxy({}, {
get: function (target, propKey) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey);
},
set: function (target, propKey, value) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value);
}
});
obj.count = 1; // setting count!
obj.count; // 1
obj.count++; // getting count! setting count!
obj.count; // 2
上面代碼中,給空對象設置代理,讀寫obj
的屬性都會執行代理行爲。
要使得Proxy起作用,必須針對Proxy
實例(上例是obj
對象)進行操作,而不是針對目標對象(上例是空對象{}
)進行操作。
如果handler
沒有設置任何操作,那就等同於直接通向原對象。
let target = {};
var obj = new Proxy(target, {});
obj.name = "jidi"
target.name; // "jidi"
上面代碼中,handler
是一個空對象,訪問obj
就等同於訪問target
。
Proxy
實例也可以作爲其他對象的原型對象。
// 定義Proxy實例
var proxy = new Proxy({}, {
get: function(){
return "jidi"
}
})
// 將Proxy實例設置爲原型對象
let x = {};
Object.setPrototypeOf(x, proxy);
x.y; // "jidi"
上面代碼中,對象x
本身沒有屬性y
,當執行x.y
時,javaScript引擎就會在對象x
的原型鏈上面去尋找,所以會在proxy
對象上讀取該屬性,導致代理生效,返回jidi
。
同一個handler
,可以設置多個操作。
let handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return { value: args[0] };
},
construct: function(target, args) {
return { value: args[1] };
}
};
var proxy = new Proxy(function(x, y) {
return x + y;
}, handler);
proxy(1, 2); // {value: 1}
new proxy(1, 2); // {value: 2}
proxy.prototype === Object.prototype; // true
proxy.foo === "Hello, foo"; // true
2. Proxy 實例的方法
Proxy ,一共支持13 種攔截操作 。
2.1 handler.get()
handler.get()
方法用於攔截對象的讀取屬性操作。
該方法會攔截目標對象的以下操作:
- 訪問屬性:
proxy[foo]
和proxy.bar
- 訪問原型鏈上的屬性:
Object.create(proxy)[foo]
語法:
var p = new Proxy(target, {
get: function(target, property, receiver) {
...
}
});
參數
以下是傳遞給get方法的參數,this
上下文綁定在handler
對象上.。
- target:目標對象
- property:被獲取的屬性名。
- receiver:
Proxy
或者繼承Proxy
的對象(可選)
返回值
get
方法可以返回任何值。
約束
如果違背了以下的約束,會拋出 TypeError
- 如果要訪問的目標屬性是不可寫以及不可配置的,則返回的值必須與該目標屬性的值相同。
- 如果要訪問的目標屬性沒有配置訪問方法,即
get
方法是undefined
的,則返回值必須爲undefined
。
示例一
let person = {
name:"jidi"
}
let proxy = new Proxy(person, {
get: function(target, property, receiver) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("該屬性不存在!");
}
}
})
proxy.name; // "jidi"
proxy.sex; // Uncaught ReferenceError: 該屬性不存在!
示例二
function getMemeberByIndexFromArray(...members) {
let handler = {
get:function(target, property, receiver){
// 得到數組下標
let index = Number(property);
if(index < 0 ){ // 下標爲負數,從後面取
property= String(target.length + index);
}
return Reflect.get(target, property, receiver);
}
}
let target = [...members];
return new Proxy(target, handler);
}
let x = getMemeberByIndexFromArray("a", "b", "c");
x[-1]; // "c"
上面代碼演示了,讀取數組索引爲負數的成員。
示例三
let proxy = new Proxy({}, {
get: function(target, property, receiver){
return receiver;
}
})
proxy.a === proxy; // true
// 以proxy爲原型構造對象
let x = Object.create(proxy);
x.a === x; // true
上面代碼,演示了handler.get()
第三個參數的使用。
示例四
var obj = {};
// 定義空對象屬性name,爲不可寫不可配置
Object.defineProperty(obj, "name", {
configurable: false,
enumerable: false,
value: "jidi",
writable: false
})
// get方法返回值與定義時的value不一致
var proxy = new Proxy(obj, {
get: function(target, property, receiver){
return 10;
}
})
proxy.name; // Uncaught TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value
// get方法返回值與定義時的value一致
var proxy = new Proxy(obj, {
get: function(target, property, receiver){
return "jidi";
}
})
proxy.name; // "jidi"
上面代碼演示了違反約束的情況。
2.2 handler.set()
handler.set()
方法用於攔截設置屬性值的操作。
該方法會攔截目標對象的以下操作:
- 指定屬性值:
proxy[foo] = bar
和proxy.foo = bar
- 指定繼承者的屬性值:
Object.create(proxy)[foo] = bar
Reflect.set()
語法
var proxy = new Proxy(target, {
set: function(target, property, value, receiver){
...
}
})
參數
以下是傳遞給set方法的參數,this
上下文綁定在handler
對象上。
target
:目標對象property
:被設置的屬性名.value
:被設置的新值receiver
:最初被調用的對象。通常是proxy
本身,但handler
的set
方法也有可能在原型鏈上或以其他方式被間接地調用(不一定是proxy
本身)。
返回值
set
方法返回一個布爾值,返回true
表示屬性設置成功,如果返回false
且設置屬性操作發生在嚴格模式下,會拋出TypeError
。
約束
如果違背以下的約束條件,proxy
會拋出一個TypeError
。
- 若目標屬性是不可寫及不可配置的,則不能改變它的值。
- 如果目標屬性沒有配置存儲方法,即
set
方法是undefined
的,則不能設置它的值。 - 在嚴格模式下,若
set
方法返回false
,則會拋出一個TypeError
異常。
示例一
var man= new Proxy({}, {
set: function(target, property, value, receiver){
if(property === "age"){
if(!Number.isInteger(value)){
throw new TypeError("年齡必須爲整數!")
}
if(value > 150){
throw new RangeError("年齡不合法!")
}
}
target[property] = value
return true
}
})
man.age = "jidi"; // Uncaught TypeError: 年齡必須爲整數! at Object.set
man.age = 1112; // Uncaught RangeError: 年齡不合法!at Object.set
man.age = 12; // 12
示例二
var proxy = new Proxy({}, {
set: function(target, property, value, receiver) {
target[property] = receiver;
return true;
}
})
proxy.name = "jidi"
proxy.name === proxy; // true
上面代碼,演示了第四個參數receiver
的使用。一般情況下,是proxy
實例本身。
示例三
var proxy = new Proxy({}, {
set: function(target, property, value, receiver) {
target[property] = receiver;
return true;
}
})
var obj = {};
// 將proxy設置爲空對象的原型對象
Object.setPrototypeOf(obj , proxy);
obj.name = "jidi";
obj.name === obj; // true
上面代碼,設置obj.name
屬性時,對象obj
並沒有name
屬性,會在它的原型鏈上去尋找該屬性。obj
的原型對象是proxy
,它是一個Proxy實例,會觸發set
方法,此時receiver
會指向原始賦值行爲對象obj
。
示例四
"use strict"; // 開啓嚴格模式
var proxy = new Proxy({}, {
set: function(target, property, value, receiver){
target[property] = value
return false
}
})
proxy.name = "jidi"; // Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'name '
上面代碼,演示了在嚴格模式中,set
函數返回false
報錯的情況。
2.3 handler.apply()
handler.apply()
方法用於攔截函數的調用。
handler.apply()
方法會攔截目標對象的以下操作。
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
語法
var proxy = new Proxy({}, {
apply: function(target, thisArg, argumentsList) {
...
}
})
參數
handler.apply()
方法接受三個參數,this
上下文綁定在handler
對象上.。
- target:目標對象(函數)
- thisArg:被調用時的上下文對象
- argumentsList:被調用時的參數數組
返回值
handler.apply()
方法可以返回任何值。
約束
如果違反了以下約束,將拋出一個TypeError
。
- target必須是一個函數對象。
示例
var target = function(x, y){
return x + y }
var proxy = new Proxy(target,
{
apply: function(target, thisArg, argumentsList) {
return [...argumentsList]
}
}
)
target(1, 3); // 4
proxy(1, 3); // [1, 3]
proxy.call(null, 1, 2); // [1, 2]
proxy.apply(null, [1, 2]); // [1, 2]
2.4 handler.has()
handler.has()
方法可以看作是針對 in
操作的鉤子。
handler.has()
方法可以攔截以下操作。
- 屬性查詢:
property in proxy
- 繼承的屬性查詢:
property in Object.create(proxy)
- with 檢查:
with(proxy) { (property ); }
Reflect.has()
語法
var proxy = new Proxy(targrt, {
has: function(target, property) {
...
}
})
參數
下面是傳遞給 has
方法的參數, this
上下文綁定在handler
對象上.。
- target:目標對象
- property:需要檢查是否存在的屬性
返回值
handler.has()
方法返回一個布爾值。
約束
如果違反了下面這些規則, proxy
將會拋出 TypeError
。
- 如果目標對象的某一屬性本身不可被配置,則該屬性不能夠被代理隱藏.
- 如果目標對象爲不可擴展對象,則該對象的屬性不能夠被代理隱藏
示例一
var target = {
name: "jidi",
age: 22
}
var proxy = new Proxy(target, {
has: function(target, property){
if(property === "age"){
return false;
}
return property in target;
}
})
"name" in target; // true
"age" in target; // true
"name" in proxy ; // true
"age" in proxy ; // false
上面代碼中,通過proxy
將age
屬性隱藏起來,不會被in
運算發現。
示例二
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p; // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
上面代碼演示違反約束的情況。
2.5 handler.construct()
handler.construct()
方法用於攔截 new
操作符。
該攔截器可以攔截以下操作。
new proxy(...args)
Reflect.construct()
語法
var proxy = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
...
}
});
參數
下面的參數將會傳遞給construct
方法,this
綁定在handler
上。
- target:目標對象
- argumentsList:
constructor
的參數列表 - newTarget:最初被調用的構造函數
返回值
construct
方法必須返回一個對象。
約束
如果違反以下約定,代理將會拋出錯誤TypeError
。
- 必須返回一個對象.
示例一
// 定義構造函數
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return { ...argumentsList }
}
})
new proxy("jidi", 22); // {0: "jidi", 1: 22}
示例二
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return newTarget;
}
})
new proxy("jidi", 22) === proxy; // true
上面代碼,演示了第三個參數newTarget
的使用,在上面例子中,newTarget
就是proxy
。
示例三
var proxy = new Proxy({}, {
construct: function(target, argumentsList, newTarget) {
return {};
}
});
new proxy(); // Uncaught TypeError: proxy is not a constructor
上面代碼未能正確初始化Proxy。Prxoy在初始化時,target
必須是一個有效的constructor
供new
操作符調用。
示例四
function Person(name, age) {
if(!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
var proxy = new Proxy(Person, {
construct: function(target, argumentsList, newTarget) {
return true;
}
})
new proxy("jidi", 22); // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('true')
上面代碼中,construct
方法返回值不是對象,直接報錯。
2.6 handler.deleteProperty()
handler.deleteProperty()
方法用於攔截對對象屬性的 delete
操作。
該方法會攔截以下操作.
- 刪除屬性:
delete proxy[foo]
和delete proxy.foo
Reflect.deleteProperty()
語法
var p = new Proxy(target, {
deleteProperty: function(target, property) {
...
}
});
參數
deleteProperty
方法將會接受以下參數。this
被綁定在handler
上。
- target:目標對象
- property:待刪除的屬性名
返回值
deleteProperty
必須返回一個 Boolean
類型的值,表示了該屬性是否被成功刪除。
約束
如果違背了以下約束,代理將會拋出一個 TypeError
。
- 如果目標對象的屬性是不可配置的,那麼該屬性不能被刪除。
示例一
var person = {
name:"jidi",
age: 22
}
var proxy = new Proxy(person, {
deleteProperty: function(target, property) {
if(property === "age") {
throw new ReferenceError("該屬性不能刪除!")
}
return delete target[property]
}
})
delete proxy.age; // Uncaught ReferenceError: 該屬性不能刪除! at Object.deleteProperty
delete proxy.name; // true
person; // {age: 22}
示例二
var person ={};
Object.defineProperty(person, "name", {
value: "jidi",
configurable: false
})
var proxy = new Proxy(person, {
deleteProperty: function(target, property) {
delete target[property]
return true;
}
})
delete proxy .name; // Uncaught TypeError: 'deleteProperty' on proxy: trap returned truish for property 'name' which is non-configurable in the proxy target
上面代碼演示了目標對象(target
)自身的不可配置(configurable = false
)的屬性,被deleteProperty
方法刪除的情況。
2.7 handler.defineProperty()
handler.defineProperty()
用於攔截對對象的 Object.defineProperty()
操作。
該方法會攔截目標對象的以下操作 :
Object.defineProperty()
Reflect.defineProperty()
proxy.property='value'
語法
var proxy = new Proxy(target, {
defineProperty: function(target, property, descriptor){
...
}
})
參數
下列參數將會被傳遞給 defineProperty
方法。this
綁定在 handler
對象上。
- target:目標對象
- property:屬性名
- descriptor:待定義或修改的屬性的描述符
返回值
defineProperty
方法必須以一個 Boolean
返回,表示定義該屬性的操作成功與否。
約束
如果違背了以下的約定,proxy
會拋出 TypeError
:
- 如果目標對象不可擴展,則不能增加目標對象上不存在的屬性,否則會報錯。
- 如果目標對象的某個屬性不可寫或不可配置,則不得改變這兩個設置。
- 在嚴格模式下,
false
作爲返回值將會拋出TypeError
異常.
示例
var person = {
name: "jidi"
}
var proxy = new Proxy(person, {
defineProperty: function(target, property, descriptor) {
if(property === "age") {
return false;
}
Object.defineProperty(target, property, descriptor)
return true
}
})
proxy.age = 12;
person; // { name: "jidi" }
proxy.sex = "男";
person; // { name: "jidi", sex: "男" }
上面例子中,如果設置屬性age
,defineProperty
返回false
,將不會成功。設置屬性sex
,目標對象會增加一個屬性sex
。
2.8 handler.getOwnPropertyDescriptor()
handler.getOwnPropertyDescriptor()
方法是 Object.getOwnPropertyDescriptor()
的鉤子。
該方法可以攔截以下操作:
Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
語法
var proxy = new Proxy(target, {
getOwnPropertyDescriptor: function(target, property) {
...
}
});
參數
下列參數將會被傳遞給 getOwnPropertyDescriptor
方法。this
綁定在 handler
對象上。
- target:目標對象
- property:返回屬性名稱的描述
返回值
getOwnPropertyDescriptor
方法必須返回一個屬性描述對象或 undefined
。
示例
var proxy = new Proxy({ name: "jidi"}, {
getOwnPropertyDescriptor: function(target, property) {
return { configurable: true, enumerable: true, value: 10 };
}
});
Object.getOwnPropertyDescriptor(proxy, "name"); // {value: 10, writable: false, enumerable: true, configurable: true}
2.9 handler.getPrototypeOf()
handler.getPrototypeOf
方法主要用來攔截獲取對象原型。
該方法會攔截下面這些操作:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
語法
var proxy = new Proxy(target, {
getPrototypeOf(target) {
...
}
});
參數
下列參數將會被傳遞給 getPrototypeOf
方法。this
綁定在 handler
對象上。
- target:被代理的目標對象
返回值
getPrototypeOf
方法的返回值必須是一個對象或者 null
。
約束
如果違背了以下的約定,proxy
會拋出 TypeError
:
getPrototypeOf()
方法返回的不是對象也不是null
。- 目標對象是不可擴展的,且
getPrototypeOf()
方法返回的原型不是目標對象本身的原型。
示例
var proto = {};
var proxy = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(proxy ) === proto // true
2.10 handler.isExtensible()
handler.isExtensible()
用於攔截對對象執行Object.isExtensible()
。
該方法會攔截目標對象的以下操作:
Object.isExtensible()
Reflect.isExtensible()
語法
var proxy = new Proxy(target, {
isExtensible: function(target) {
...
}
});
參數
下列參數將會被傳遞給isExtensible
方法。 this
綁定在handler
對象上。
- target:目標對象
返回值
isExtensible
方法必須返回一個 Boolean
值或可轉換成Boolean
的值。
約束
如果違背了以下的約束,proxy
會拋出TypeError
:
Object.isExtensible(proxy)
必須同Object.isExtensible(target)
返回相同值。也就是必須返回true
或者爲true
的值,返回false
和爲false
的值都會報錯。
示例一
var proxy = new Proxy({}, {
isExtensible: function(target) {
return true;
}
});
Object.isExtensible(proxy); // true
示例二
var proxy = new Proxy({}, {
isExtensible: function(target) {
return false;
}
});
Object.isExtensible(proxy ); // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true') at Function.isExtensible
2.11 handler.isExtensible()
ownKeys
方法用來攔截對象自身屬性的讀取操作。
該方法可以攔截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
for...in
循環
語法
var proxy = new Proxy(target, {
ownKeys: function(target) {
...
}
});
參數
下面的參數被傳遞給ownKeys
。this
被綁定在handler
上。
- target:目標對象
返回值
ownKeys
方法必須返回一個可枚舉對象
約束
如果違反了下面的約束,proxy
將拋出錯誤 TypeError
:
ownKeys
的結果必須是一個數組.- 數組的元素類型要麼是一個
String
,要麼是一個Symbol
- 結果列表必須包含目標對象的所有不可配置、自有屬性的
key
- 如果目標對象不可擴展,那麼結果列表必須包含目標對象的所有自有屬性的
key
,不能有其它值
示例
var proxy = new Proxy({}, {
ownKeys: function(target) {
return ['a', 'b', 'c'];
}
});
Object.getOwnPropertyNames(proxy ); // [ 'a', 'b', 'c' ]
2.12 handler.preventExtensions()
handler.preventExtensions()
方法用於設置對Object.preventExtensions()
的攔截。
該方法可以攔截以下操作:
Object.preventExtensions()
Reflect.preventExtensions()
語法
var proxy = new Proxy(target, {
preventExtensions: function(target) {
...
}
});
參數
以下參數傳遞給 preventExtensions
方法. this
會綁定到這個handler
- target: 所要攔截的目標對象.
返回值
preventExtensions
方法返回一個布爾值
約束
如果違反了下列約束, proxy
會拋出一個 TypeError
:
- 如果
Object.isExtensible(proxy)
是false
,Object.preventExtensions(proxy)
只能返回true
。
2.12 handler.setPrototypeOf()
handler.setPrototypeOf()
方法主要用來攔截 Object.setPrototypeOf()
這個方法可以攔截以下操作:
Object.setPrototypeOf()
Reflect.setPrototypeOf()
語法
var proxy = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
...
}
});
參數
以下參數傳遞給 setPrototypeOf
方法
- target:被攔截目標對象
- prototype:對象新原型或
null
返回值
如果成功返回 true
,否則返回 false
約束
如果違反了下列規則,則proxy
將拋出一個TypeError
:
- 如果
target
不可擴展, 原型參數必須與Object.getPrototypeOf(target)
的值相同
3 Proxy.revocable()
Proxy.revocable()
方法可以用來創建一個可撤銷的代理對象。一旦某個代理對象被撤銷,它將變得幾乎完全不可調用,在它身上執行任何的可代理操作都會拋出TypeError
異常。
語法
Proxy.revocable(target, handler);
參數
以下參數傳遞給 revocable
方法:
- target:將用 Proxy 封裝的目標對象。可以是任何類型的對象。
- handler:一個對象,其屬性是一批可選的函數,這些函數定義了對應的操作被執行時代理的行爲。
返回值
返回一個對象。該對象結構爲{"proxy": proxy, "revoke": revoke}
,其中:
- proxy:表示新生成的代理對象本身,和用
new Proxy(target, handler)
創建的代理對象沒什麼不同,只是它可以被撤銷掉。 - revoke:撤銷方法,調用的時候不需要加任何參數,就可以撤銷掉和它一起生成的那個代理對象。
示例
// 可撤銷的proxy對象
var revocable = Proxy.revocable({}, {
get(target, name) {
return "[[" + name + "]]";
}
});
var proxy = revocable.proxy;
proxy.name; // "[[name]]"
// 執行撤銷方法
revocable.revoke();
proxy.sex = "男"; // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.age; // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
4 參考鏈接
本篇博文是我自己學習筆記,原文請參考:ECMAScript 6 入門,MDN網站。
如有問題,請及時指出!
歡迎溝通交流,郵箱:[email protected]。