ECMAScript 6之Proxy

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] = barproxy.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本身,但handlerset方法也有可能在原型鏈上或以其他方式被間接地調用(不一定是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

上面代碼中,通過proxyage屬性隱藏起來,不會被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必須是一個有效的constructornew操作符調用。

示例四

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: "男" }

上面例子中,如果設置屬性agedefineProperty返回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) {
  	...
  }
});

參數
下面的參數被傳遞給ownKeysthis被綁定在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)falseObject.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]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章