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
。