參考書《ECMAScript 6入門》
http://es6.ruanyifeng.com/
Proxy
1.概述
Proxy可以用來修改對象的默認操作
let obj = {name : "test"};
obj.name = "test";
obj['name'] = "test";
這兩種取值操作相當於調用了obj的內部默認get操作
let obj = {
name : "test",
get(){
return "123"
}
};
let obj = {
name : "test",
get : function(){
return "123"
}
};
obj.name = "test";
obj['name'] = "test";
obj.get();//"123"
//由此可見,通過在obj內部定義一個名字為get的方法並不能改變obj內部默認的get行為,而proxy可以做到這一點
//new Proxy(target,handler);
//target是目標處理對象,handler中定義要處理的操作
let obj = {
name : "test",
get(){
return "456"
}
};
let obj2 = new Proxy(obj,{
get(){
return "789";
}
});
obj2.name = "789";
obj2['name'] = "789";
//如果handle沒有設置任何攔截行為,new Proxy(target)就通向原對象
let obj = {name : "test"};
let proxy = new Proxy(obj,{});
proxy.name = "proxy test";
obj.name // "proxy test" 相當於淺拷貝
obj.name = "obj test";
proxy.name //"obj test" 相當於淺拷貝
let handler = {
get : function(){
return "this called get";
},
apply : function(){
return "this called apply";
},
construct : function(){
return {construct : "this called construct"};
}
}
let obj = new Proxy(function(){
return "this is target";
},handler);
obj();// "this called apply" obj()的默認行為是調用apply操作
new obj();//{construct: "this called construct"} new obj()的默認行為是調用construct操作
obj.prototype //"this called get" 點運算符的默認行為是調用get操作
obj['name'] // "this called get" 方括號運算的默認行為是調用get操作
2. proxy支持的攔截操作
(1)get(target,propkey,receiver):攔截對象屬性的讀取。target是目標對象,propkey是屬性名,receiver指當前的proxy實例
數組reduce的用法
array.reduce(function(previousValue,currentValue,currentIndex,array){});
如果一個屬性是不可配置的(configurable)和不可寫的(written),則不能使用proxy獲取該屬性的值
let obj = Object.defineProperty({},'color',{value:123,writable:false,configurable:false});
obj.color // 123
let proxy = new Proxy(obj,{
get : function(target,propkey){
return "color";
}
});
proxy.color //Uncaught TypeError: 'get' on proxy: property 'color' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '123' but got 'color')
(2)set(target,propkey,propValue,receiver):攔截對象屬性的設置。
target是目標對象,propkey是屬性名,propValue是屬性值,receiver是當前操作行為所指的對象,一般是Proxy實例本身
利用set攔截方式達到雙向數據綁定的效果
HTML代碼舉例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="../js/jquery-1.11.3.min.js"></script>
</head>
<body>
<input type="text"/>
<div id="test"></div>
</body>
<script>
var proxy = new Proxy({
data: "red"
}, {
set: function(target, propkey, propV, proxy) {
target[propkey] = propV;
$("#test").text(propV);
$('input').val(propV);
return true;
}
});
$('input').on('keyup',function(){
proxy.data = $('input').val();
});
</script>
</html>
當一個屬性設置為不可寫和不可設置時,set方法將不起作用
let obj = Object.defineProperty({},'color',{value:"red",configurable:false,writable:false});
let proxy = new Proxy(obj,{
set : function(target,pk,pv,receiver){
target[pk] = "test";
return true;
}
});
proxy.color = "balck";//VM583:1 Uncaught TypeError: 'set' on proxy: trap returned truish for property 'color' which exists in the proxy target as a non-configurable and non-writable data property with a different value
proxy.name = "123"; //"123"
(3).has(target,propkey):攔截propkey in proxy的遍歷操作,返回布爾值
let obj = {color:"red",testN : "name"}
let proxy = new Proxy(obj,{
has(target,pk){
console.log(pk);
if(pk === 'testN'){
return false;
}
return pk in target;
}
});
'color' in obj //true
'testN' in obj //true
'color' in proxy //true
'testN' in proxy //false
has不能攔截設置為禁止擴展或者設置為禁止配置的對象的hasProperty操作
let obj = {color:"test"};
Object.preventExtensions(obj);
let proxy = new Proxy(obj,{
has(){
return false;
}
});
color in proxy //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'color' but the proxy target is not extensible
let obj = Object.defineProperty({},'color',{value:123,configurable:false});
let proxy = new Proxy(obj,{
has(){
return false;
}
});
'color' in proxy //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'color' which exists in the proxy target as non-configurable
has不會攔截for循環遍歷中的in運算
let obj = {name : "test",color : "red"}
let handler = {
has(target,pk){
return "this called has"
}
}
let proxy = new Proxy(obj,handler);
for(let o in proxy){
console.log(o + " : " + proxy[o]);
}
//name : test
//color : red
(4).deleteProperty(target,propkey):攔截delete proxy[propkey]的操作,返回一個boolean值。target指目標對象,propkey指屬性名
let obj = Object.defineProperties({},{
"name" : {
value : "obj"
},
"speed" : {
value : 23,
writable : false
},
"type" : {
value : "abc"
},
"color" : {
value : "test",
configurable : false
},
"size" : {
value : 11,
configurable : true,
writable : false
}
});
Object.getOwnPropertyDescriptors(obj);
color:{value: "test", writable: false, enumerable: false, configurable: false}
name:{value: "obj", writable: false, enumerable: false, configurable: false}
size:{value: 11, writable: false, enumerable: false, configurable: true}
speed:{value: 23, writable: false, enumerable: false, configurable: false}
type:{value: "abc", writable: false, enumerable: false, configurable: false}
let handler = {
deleteProperty(target, pk){
if("name" === pk){
return false;
}
return true;
}
}
let proxy = new Proxy(obj,handler);
攔截刪除name屬性
delete proxy.name //false
color屬性是不可配置屬性,不能刪除
delete proxy.color //VM520:1 Uncaught TypeError: 'deleteProperty' on proxy: trap returned truish for property 'color' which is non-configurable in the proxy target
size是可配置但是不可寫屬性,能夠刪除
delete proxy.size //true
speed是不可寫屬性,不能刪除
delete proxy.speed //VM638:1 Uncaught TypeError: 'deleteProperty' on proxy: trap returned truish for property 'speed' which is non-configurable in the proxy target
type屬性定義時默認是不可配置,不可寫,不能刪除
delete proxy.type
proxy //Proxy {name: "obj", speed: 23, type: "abc", color: "test", size: 11}
(5).ownKeys(target):攔截對象自身讀取屬性的操作,如:
Object.getOwnPropertyNames(obj):返回一個數組,包含對象自身的所有屬性的鍵名,這個所有屬性包含不可枚舉屬性但是不包含symbol屬性的鍵名
Object.getOwnPropertySymbols(obj):返回一個數組,包含對象自身的所有Symbol屬性的鍵名
Object.keys(obj):返回對象自身的所有可枚舉屬性的鍵名
ownKeys攔截操作返回一個數組,且此數組的鍵名必須是字符串或者Symbol類型,否則會報錯;
當目標對象有不能配置的屬性時,ownKeys()攔截操作所返回的數組則必須包含目標對象的此屬性,否則會報錯;
當目標對象不可擴展時,ownKeys()攔截操作必須返回目標對象所有的屬性,否則會報錯;
let s = Symbol("for s");
let obj = {
"name":"asd",
"number" : 7,
"cat" : {
color : "black",
size : 10
},
[s] : "symbol s"//Symbol鍵名的屬性
}
Object.defineProperties(obj,{
"nonEm":{//不可枚舉屬性
value : "test nonEm",
enumerable : false,
configurable : true,
writable : true
},
"nonCn":{//不可配置屬性
value : "test nonCn",
enumerable : true,
configurable : false,
writable : true
}
}
);
object.keys(obj);//["name", "number", "cat", "nonCn"] 拿不到Symbol類型為鍵名和不可枚舉的
Object.getOwnPropertyNames(obj);// ["name", "number", "cat", "nonEm", "nonCn"] 拿不到Symbol類型為鍵名的
Object.getOwnPropertySymbols(obj);// [Symbol(for s)] 只獲取Symbol類型的鍵名
let handler = {
ownKeys(target){
return ['nonCn','cat','abc'];//必須包含'nonCn'此不可配置屬性的鍵名
}
}
let proxy = new Proxy(obj,handler);
Object.keys(proxy);//["nonCn", "cat"] 'abc' 對應的屬性不存在,所以不返回
Object.getOwnPropertyNames(proxy);//["nonCn", "cat", "abc"]
Object.getOwnPropertySymbols(proxy);//[]
Object.preventExtensions(obj);//目標對象設置了不可擴展,則ownKeys攔截必須返回所有鍵名,否則報錯
let proxy1 = new Proxy(obj,handler);
Object.keys(proxy1);//Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'name'
let handler2 = {
ownKeys(){
return ["name", "number", "cat", "nonCn","nonEm",s]//因為目標對象不可擴展,因此不能返回目標對象不存在的鍵名
}
}
let proxy2 = new Proxy(obj,handler2);
Object.keys(proxy2);//["name", "number", "cat", "nonCn"]
Object.getOwnPropertyNames(proxy4);//["name", "number", "cat", "nonCn", "nonEm"]
Object.getOwnPropertySymbols(proxy4);//[Symbol(for s)]
(6).getOwnPropertyDescriptor(target,propkey):攔截Object.getOwnPropertyDescriptor(proxy,propkey),返回屬性的描述對象或者undefined。
let obj = {color : "red"};
let handler = {
getOwnPropertyDescriptor(target,key){
return false;
}
}
let proxy = new Proxy(obj,handler);
proxy.color //'red'
Object.getOwnPropertyDescriptor(proxy,'color');//VM2595:1 Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property 'color'
(7)defineProperty(target,propkey,descriptor):攔截Object.defineProperty,Object.defineProperties,返回一個boolean值。
let proxy = new Proxy({},{
defineProperty(target,pk,descriptor){
console.log(target);//{}
console.log(pk);//name
console.log(descriptor);//{value: "123", writable: true, enumerable: true, configurable: true}
return false;
}
});
proxy.name = "123";//"123" 沒有報錯
proxy //Proxy {}
proxy.name //undefined 沒有成功的定義屬性值,攔截行為生效
如果目標對象的某個屬性不可寫(writable = false)或者不可配置(configurable = false),則defineProperty方法不能這兩個設置
let obj = Object.defineProperty({},'color',{
value : "red",
writable : false,
configurable : false
});
Object.preventExtensions(obj);
let proxy = new Proxy(obj,{
defineProperty(target,pk,descriptor){
target[pk]['confirgurable'] = true;
return true;
}
});
proxy.color //'red'
Object.getOwnPropertyDescriptor(proxy,'color')['configurable'] //false
Object.getOwnPropertyDescriptor(proxy,'color')['configurable'] = true //true
Object.getOwnPropertyDescriptor(proxy,'color')['configurable'] //false
//如果目標對象設置為禁止擴展,則不允許為proxy實例添加新的屬性
proxy.name = "123" //Uncaught TypeError: Cannot set property 'confirgurable' of undefined
(8)preventExtensions(target):攔截Object.preventExtensions(proxy),返回值必須在實際意義上與Object.isExtensible(target)一致
let obj = {}
Object.isExtensible(obj);//true
let handler = {
preventExtensions:function(target){
console.log("other action");
Object.preventExtensions(target);
return true;
}
}
let proxy = new Proxy(obj,handler);
Object.preventExtensions(proxy);
//"other action"
//proxy{} 最新瀏覽器返回的是對象不是boolean值
var p = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});
Object.preventExtensions(p);//報錯 攔截后Object.preventExtensions與Object.isExtensible(target)--true不一致,所以報錯
(9)getPrototypeOf(target):攔截獲取對象原型的操作。如:Object.prototype.__proto__,Object.prototype.isPrototypeOf(),Object.getPrototypeOf(),Reflect.getPrototypeOf(),instanceOf
返回值必須是對象或者null.
let arr = [1,2,3,4,5];
let handler = {
getPrototypeOf(target){
return {name : "test"};
}
}
let proxy = new Proxy(arr,handler);
Object.getPrototypeOf(proxy) //{name: "test"}
Reflect.getPrototypeOf(proxy) //{name: "test"}
proxy.__proto__ //{name: "test"}
let arr = {name : "asd"};
Object.preventExtensions(arr);
let handler = {
getPrototypeOf(target){
return {color : "black"};
}
}
let proxy = new Proxy(arr,handler);
Object.getPrototypeOf(proxy) //Uncaught TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
Reflect.getPrototypeOf(proxy) //Uncaught TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
proxy.__proto__ //Uncaught TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
(10)isExtensible(target):攔截Object.isExtensible(proxy),必須返回一個boolean值且與目標對象調用Object.isExtensible(target)一致。
let obj = {};
Object.isExtensible(obj) //true
let handler = {
isExtensible(target){
Object.preventExtensions(target);//攔截操作
return Object.isExtensible(target);
}
}
let proxy = new Proxy(obj,handler);
Object.isExtensible(proxy);//false
Object.isExtensible(obj);//false
(11)setPrototypeOf(target,propkey):攔截Object.setPrototypeOf(target,proto),返回一個布爾值。
let obj = {};
let obj1 = {name : "test"};
Object.getPrototypeOf(obj); //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
let handler = {
setPrototypeOf(target){
Object.setPrototypeOf(target,obj1);
return true;
}
}
let proxy = new Proxy(obj,handler);
Object.setPrototypeOf(proxy,null);
Object.getPrototypeOf(proxy);//{name: "test"}
(12)apply(target,objects,arguments):攔截proxy實例作為函數調用的操作,
target代表目標對象,objects代表目標對象的上下文對象,arguments代表參數數組
如Proxy(...arguments),Proxy.apply(obejct,...arguments),Proxy.call(...),直接使用Reflect.call()也會被攔截
let f1 = function add(a,b){
console.log(a+b);
}
let proxy = new Proxy(f1,{
apply : function(target,context,arguments){
return arguments + " this is apply";
}
});
proxy(1,2) //"1,2 this is apply";
proxy.apply(null,[1,2]) //"1,2 this is apply"
proxy.call(null,1,2) //"1,2 this is apply"
Reflect.apply(proxy,null,[1,2]) //"1,2 this is apply"
(13)construct(target,arguments):攔截Proxy實例作為構造器調用的操作,如new Proxy(...arguments)。
target是目標對象,arguments是構建函數的參數對象。
construct返回的必須是一個對象,否則會報錯。
let handler1 = {
construct : function(target,arguments){
return {value : arguments*22};
}
}
let handler2 = {
construct : function(target,arguments){
return arguments*22;
}
}
let p1 = new Proxy(function(){},handler1);
let p2 = new Proxy(function(){},handler2);
new p1(12);//{value: 264}
new p2(12);//VM460:1 Uncaught TypeError: 'construct' on proxy: trap returned non-object ('264')
3.Proxy.revocable():用於取消Proxy實例,執行此函數后,再訪問被取消的Proxy實例的屬性就會報錯。
使用場景:目標對象不能直接訪問,必須通過代理訪問,訪問結束就收回代理權,不能再訪問
let {proxy,revoke} = Proxy.revocable({},{
get(){
return {color : "red"};
}
});
proxy.name //{color : "red"}
Proxy.revoke(proxy);
proxy //Proxy {}
proxy.name //Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
4.this問題:在Proxy代理的情況下,目標對象this的關鍵字會指向Proxy 代理
let n = new Number();
n.toFixed(2)//'0.00'
let proxy = new Proxy(n,{});
proxy.toFixed(2); //Uncaught TypeError: Number.prototype.toFixed requires that 'this' be a Number
5.實現web服務的客戶端
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl+'/' + propKey);
}
});
}