1. 程式人生 > >es6 javascript的Proxy及攔截操作一覽

es6 javascript的Proxy及攔截操作一覽

Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種 “ 超程式設計 ” ( meta programming ),即對程式語言進行程式設計。

Proxy 可以理解成,在目標物件之前架設一層 “ 攔截 ” ,外界對該物件的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。 Proxy 這個詞的原意是代理,用在這裡表示由它來 “ 代理 ” 某些操作,可以譯為 “ 代理器 ” 。

var obj = new Proxy({}, {
	get: function (target, key, receiver) {
		console.log(`getting ${key}!`);
		return Reflect.get(target, key, receiver);
	},
	set: function (target, key, value, receiver) {
		console.log(`setting ${key}!`);
		return Reflect.set(target, key, value, receiver);
	}
});
上面程式碼對一個空物件架設了一層攔截,重定義了屬性的讀取(get)和設定(set)行為。這裡暫時先不解釋具體的語法,只看執行結果。對設定了攔截行為的物件obj,去讀寫它的屬性,就會得到下面的結果。
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面程式碼說明, Proxy 實際上過載( overload )了點運算子,即用自己的定義覆蓋了語言的原始定義。
ES6 原生提供 Proxy 建構函式,用來生成 Proxy 例項。
var proxy = new Proxy(target, handler);
Proxy 物件的所有用法,都是上面這種形式,不同的只是handler引數的寫法。其中,new Proxy()表示生成一個 Proxy 例項, target 引數表示所要攔截的目標物件,handler引數也是一個物件,用來定製攔截行為。
下面是另一個攔截讀取屬性行為的例子。
var proxy = new Proxy({}, {
	get: function(target, property) {
		return 35;
	}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
上面程式碼中,作為建構函式, Proxy 接受兩個引數。第一個引數是所要代理的目標物件(上例是一個空物件),即如果沒有 Proxy 的介入,操作原來要訪問的就是這個物件;第二個引數是一個配置物件,對於每一個被代理的操作,需要提供一個對應的處理函式,該函式將攔截對應的操作。比如,上面程式碼中,配置物件有一個get方法,用來攔截對目標物件屬性的訪問請求。get方法的兩個引數分別是目標物件和所要訪問的屬性。可以看到,由於攔截函式總是返回35,所以訪問任何屬性都得到35。
注意,要使得 Proxy 起作用,必須針對 Proxy 例項(上例是 proxy 物件)進行操作,而不是針對目標物件(上例是空物件)進行操作。
如果handler沒有設定任何攔截,那就等同於直接通向原物件。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
上面程式碼中,handler是一個空物件,沒有任何攔截效果,訪問handeler就等同於訪問target。
一個技巧是將 Proxy 物件,設定到object.proxy屬性,從而可以在object物件上呼叫。
var object = { proxy: new Proxy(target, handler) };
Proxy 例項也可以作為其他物件的原型物件。
var proxy = new Proxy({}, {
	get: function(target, property) {
		return 35;
	}
});
let obj = Object.create(proxy);
obj.time // 35
上面程式碼中,proxy物件是obj物件的原型,obj物件本身並沒有time屬性,所以根據原型鏈,會在proxy物件上讀取該屬性,導致被攔截。
同一個攔截器函式,可以設定攔截多個操作。
var handler = {
	get: function(target, name) {
		if (name === 'prototype') {
			return Object.prototype;
		}
		return 'Hello, ' + name;
	},
	apply: function(target, thisBinding, args) {
		return args[0];
	},
	construct: function(target, args) {
		return {value: args[1]};
	}
};
var fproxy = new Proxy(function(x, y) {
	return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1,2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo // "Hello, foo"
下面是 Proxy 支援的攔截操作一覽。
對於可以設定、但沒有設定攔截的操作,則直接落在目標物件上,按照原先的方式產生結果。
( 1 ) get(target, propKey, receiver)
攔截物件屬性的讀取,比如proxy.foo和proxy['foo']。
最後一個引數receiver是一個物件,可選,參見下面Reflect.get的部分。
( 2 ) set(target, propKey, value, receiver)
攔截物件屬性的設定,比如proxy.foo = v或proxy['foo'] = v,返回一個布林值。
( 3 ) has(target, propKey)
攔截propKey in proxy的操作,以及物件的hasOwnProperty方法,返回一個布林值。
( 4 ) deleteProperty(target, propKey)
攔截delete proxy[propKey]的操作,返回一個布林值。
( 5 ) ownKeys(target)
攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個數組。該方法返回物件所有自
身的屬性,而Object.keys()僅返回物件可遍歷的屬性。
( 6 ) getOwnPropertyDescriptor(target, propKey)
攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述物件。
( 7 ) defineProperty(target, propKey, propDesc)
攔截Object.defineProperty(proxy, propKey, propDesc ) 、Object.defineProperties(proxy, propDescs),返回一個布林值。
( 8 ) preventExtensions(target)
攔截Object.preventExtensions(proxy),返回一個布林值。
( 9 ) getPrototypeOf(target)
攔截Object.getPrototypeOf(proxy),返回一個物件。
( 10 ) isExtensible(target)
攔截Object.isExtensible(proxy),返回一個布林值。
( 11 ) setPrototypeOf(target, proto)
攔截Object.setPrototypeOf(proxy, proto),返回一個布林值。
如果目標物件是函式,那麼還有兩種額外操作可以攔截。
( 12 ) apply(target, object, args)
攔截 Proxy 例項作為函式呼叫的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
( 13 ) construct(target, args)
攔截 Proxy 例項作為建構函式呼叫的操作,比如new proxy(...args)。