Proxy概述
Proxy用於修改某些操作的默認行為,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。
Proxy可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
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。
同一個攔截器函數,可以設置攔截多個操作。
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 args[1]; } }; var fproxy = new Proxy(function(x, y) { return x + y; }, handler); fproxy(1,2); // 1 new fproxy(1,2); // 2 fproxy.prototype; // Object.prototype fproxy.foo; // 'Hello, foo'
下面是Proxy支持的攔截操作一覽。
對於可以設置、但沒有設置攔截的操作,則直接落在目標對象上,按照原先的方式產生結果。
(1)get(target, propKey, receiver)
攔截對象屬性的讀取,比如proxy.foo和proxy['foo'],返回類型不限。最后一個參數receiver可選,當target對象設置了propKey屬性的get函數時,receiver對象會綁定get函數的this對象。
(2)set(target, propKey, value, receiver)
攔截對象屬性的設置,比如proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
(3)has(target, propKey)
攔截propKey in proxy的操作,返回一個布爾值。
has方法可以隱藏某些屬性,不被in操作符發現。
(4)deleteProperty(target, propKey)
攔截delete proxy[propKey]的操作,返回一個布爾值。
(5)enumerate(target)
攔截for (var x in proxy),返回一個遍歷器。
注意與Proxy對象的has方法區分,后者用來攔截in操作符,對for...in循環無效。
(6)ownKeys(target)
攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個數組。該方法返回對象所有自身的屬性,而Object.keys()僅返回對象可遍歷的屬性。
(7)getOwnPropertyDescriptor(target, propKey)
攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
(8)defineProperty(target, propKey, propDesc)
攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
(9)preventExtensions(target)
攔截Object.preventExtensions(proxy),返回一個布爾值。
(10)getPrototypeOf(target)
攔截Object.getPrototypeOf(proxy),返回一個對象。
(11)isExtensible(target)
攔截Object.isExtensible(proxy),返回一個布爾值。
(12)setPrototypeOf(target, proto)
攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。
如果目標對象是函數,那么還有兩種額外操作可以攔截。
(13)apply(target, object, args)
攔截Proxy實例作為函數調用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
apply方法可以接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。
(14)construct(target, args, proxy)
攔截Proxy實例作為構造函數調用的操作,比如new proxy(...args)。
construct方法用於攔截new命令。
Reflect概述
Reflect對象與Proxy對象一樣,也是ES6為了操作對象而提供的新API。Reflect對象的設計目的有這樣幾個。
(1) 將Object對象的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect對象上。現階段,某些方法同時在Object和Reflect對象上部署,未來的新方法將只部署在Reflect對象上。
(2) 修改某些Object方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。
// 老寫法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新寫法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure }
(3) 讓Object操作都變成函數行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數行為。
// 老寫法 'assign' in Object // true // 新寫法 Reflect.has(Object, 'assign') // true
(4)Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。這就讓Proxy對象可以方便地調用對應的Reflect方法,完成默認行為,作為修改行為的基礎。也就是說,不管Proxy怎么修改默認行為,你總可以在Reflect上獲取默認行為。
Reflect對象的方法
Reflect對象的方法清單如下,共14個。
- Reflect.apply(target,thisArg,args)
- Reflect.construct(target,args)
- Reflect.get(target,name,receiver)
- Reflect.set(target,name,value,receiver)
- Reflect.defineProperty(target,name,desc)
- Reflect.deleteProperty(target,name)
- Reflect.has(target,name)
- Reflect.ownKeys(target)
- Reflect.enumerate(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
上面這些方法的作用,大部分與Object對象的同名方法的作用都是相同的,而且它與Proxy對象的方法是一一對應的。下面是對其中幾個方法的解釋。
(1)Reflect.get(target, name, receiver)
查找並返回target對象的name屬性,如果沒有該屬性,則返回undefined。
(2)Reflect.set(target, name, value, receiver)
設置target對象的name屬性等於value。如果name屬性設置了賦值函數,則賦值函數的this綁定receiver。
(3)Reflect.has(obj, name)
等同於name in obj。
(4)Reflect.deleteProperty(obj, name)
等同於delete obj[name]。
(5)Reflect.construct(target, args)
等同於new target(...args),這提供了一種不使用new,來調用構造函數的方法。
(6)Reflect.getPrototypeOf(obj)
讀取對象的__proto__屬性,對應Object.getPrototypeOf(obj)。
(7)Reflect.setPrototypeOf(obj, newProto)
設置對象的__proto__屬性,對應Object.setPrototypeOf(obj, newProto)。
(8)Reflect.apply(fun,thisArg,args)
等同於Function.prototype.apply.call(fun,thisArg,args)。一般來說,如果要綁定一個函數的this對象,可以這樣寫fn.apply(obj, args),但是如果函數定義了自己的apply方法,就只能寫成Function.prototype.apply.call(fn, obj, args),采用Reflect對象可以簡化這種操作。
另外,需要注意的是,Reflect.set()、Reflect.defineProperty()、Reflect.freeze()、Reflect.seal()和Reflect.preventExtensions()返回一個布爾值,表示操作是否成功。它們對應的Object方法,失敗時都會拋出錯誤。
// 失敗時拋出錯誤 Object.defineProperty(obj, name, desc); // 失敗時返回false Reflect.defineProperty(obj, name, desc);
上面代碼中,Reflect.defineProperty方法的作用與Object.defineProperty是一樣的,都是為對象定義一個屬性。但是,Reflect.defineProperty方法失敗時,不會拋出錯誤,只會返回false。
