本文主要介紹了ES6中Proxy
和Reflect
的精華知識,並附有恰當實例。Proxy
意爲代理器,經過操做爲對象生成的代理器,實現對對象各種操做的攔截式編程。Reflect
是一個包攬更爲嚴格、健全的操做對象方法的模塊。由於Proxy
所能代理的方法和Reflect
所包括的方法基本對應,並且在攔截方法裏應該使用對應的Reflect
方法返回結果,因此將二者合併在一塊兒分享。編程
先想個問題,如何管控對象某一屬性的讀取和修改(不涉及閉包建立私有屬性)?
先建立不該被直接改動的容器屬性:_屬性名,再設置相應的setter
和getter
函數或建立相應的操做方法。數組
--- 設置 setter 和 getter 函數 let obj = { _id: undefined, get id() { return this._id; }, set id(v) { this._id = v; } }; obj.id = 3; // 至關:obj._id = 3 console.log(obj.id); // console.log(obj._id); --- 建立獲取及修改方法 let obj = { _id: undefined, id() { if (!arguments.length) return this._id; this._id = arguments[0]; } }; obj.id(3); // 至關:obj._id = 3 console.log(obj.id()); // console.log(obj._id);
這樣有明顯的缺陷:要爲每一個須要管控的屬性進行重複設置,並且實際上容器屬性能夠被任意修改。
若是要求升級,咱們須要監聽查看、刪除、遍歷對象屬性的操做,怎麼辦?ES6以前只能涼拌,ES6以後Proxy
代你辦。閉包
let obj = { id: 0, name: 'Wmaker' }; let objProxy = new Proxy(obj, { get(target, attr) { console.log(`Get ${attr}`); return target[attr]; }, set(target, attr, val) { console.log(`Set ${attr}`); target[attr] = val; return true; } }); objProxy.id; // 打印出:Get id,至關:obj.id; objProxy.name; // 打印出:Get name,至關:obj.name;
如前節示例可知,Proxy
是生成代理器的構造函數。傳入的第一個參數爲須要被代理的對象,第二個參數是須要攔截操做的配置對象(以後會列出全部可攔截的操做及其意義和注意事項)。配置對象中的每一個攔截操做,都有默認格式的傳入參數,有些也要求有特定的返回值(下面會羅列出某些規律)。 app
生成的代理器是一個與被代理對象關聯的代理實例,能夠像操做普通對象同樣對待它。便是說,能夠被delete
掉某個屬性,能夠被遍歷或獲取原型。全部做用於代理器的操做,都至關直接做用於被代理對象,還可爲其配置攔截操做。說的激憤點,蒼老師能作好的,咱們的小澤老師怎麼會差呢?另外可代理的不僅僅是對象,屬於對象的函數、數組都是無條件接受的。 函數
爲對象生成代理器以後,依然能夠操做原對象,但這天然是不建議的。this
參數
不一樣攔截函數的傳入參數不盡相同,但通常爲被代理對象,該操做須要的參數等和代理器對象。
沒必要記憶每一個攔截函數的參數,爲腦瓜減減負擔,使用時先打印出arguments
查看便會一目瞭然。prototype
返回值
在攔截方法裏,應儘可能使用Reflect
對應的方法進行操做,並返回該方法的返回值。一方面是簡單,更重要的是在不一樣方法或某些環境下,對返回值有硬性的要求,不然直接報錯。好比construct()
必須返回一個對象,不然報錯。再好比set()
在嚴格模式下,必須返回true
或可轉化成true
的值,不管操做是否成功。代理
"use strict"; --- 錯誤的作法 let obj = { id: 0 }; let objProxy = new Proxy(obj, { set(target, attr, val) { console.log(`Set ${attr}`); return target[attr] = val; } }); objProxy.id = 1; // 操做成功。 objProxy.id = 0; // 操做已經成功,但報錯,再也不往下執行。 --- 推薦的作法 let obj = { id: 0 }; let objProxy = new Proxy(obj, { set(target, attr, val) { console.log(`Set ${attr}`); return Reflect.set(target, attr, val); } }); objProxy.id = 1; // 操做成功。 objProxy.id = 0; // 操做成功。
攔截方法的返回值並不會直接反映到外部,JS
會進行某些驗證和排除。
好比即使在ownKeys()
中返回所有的值,但實際到外部的只有相應的系列。code
兩次打印的結果不相等。 let obj = { id: 0, [Symbol.iterator]() {} }; let objProxy = new Proxy(obj, { ownKeys(target) { let res = Reflect.ownKeys(target); console.log('1', res); return res; } }); console.log('2', Object.keys(objProxy));
限制性的延續
當被代理對象自身已有某些限制,好比不可擴展或屬性不可寫不可配置等。對象自己的操做已經受到了限制,這時若是執行相應的代理操做,天然會報錯。好比當屬性不可寫時,若是代理並執行了set()
操做,則會直接報錯。對象
let obj = { id: 0 }; Object.defineProperty(obj, 'name', { value: 'Wmaker' }); let objProxy = new Proxy(obj, { set(target, attr, val) { return Reflect.set(target, attr, val); } }); objProxy.id = 1; // 操做成功。 objProxy.name = 'Limo'; // 報錯。
this
有些原生對象的內部屬性或方法,只有經過正確的this
才能訪問,因此沒法進行代理。
好比日期對象,new Proxy(new Date(), {}).getDate()
,報錯提示:這不是個Date
對象。
也有變通的辦法,好比對於須要正確 this 的方法能夠這樣作。 let p = new Proxy(new Date(), { get(target, attr) { const v = Reflect.get(target, attr); return typeof v === 'function' ? v.bind(target) : v; } }); p.getTime(); // 沒有錯誤。
處於配置對象中的this
直接指向配置對象,不是被代理對象或代理器對象。
處於被代理對象中的this
,分爲存在於方法和存在於getter/setter
中兩種。二者獲取this
的方式不一樣,咱們以實例說明。
--- 例一,沒有 set 攔截操做。 let obj = { get id() { console.log('o', this); }, fn() { console.log('o', this); } }; let objProxy = new Proxy(obj, {}); objProxy.id; // 打印出 objProxy 。 objProxy.fn(); // 打印出 objProxy 。 --- 例二,有 set 攔截操做。實際使用了 target[attr] 獲取屬性值。 let obj = { get id() { console.log('o', this); }, fn() { console.log('o', this); } }; let objProxy = new Proxy(obj, { get(target, attr) { console.log('p', this); return target[attr]; } }); objProxy.id; // 打印出配置對象和 obj。 // 由於實際是經過被代理對象即 target 訪問到 id 值的。 objProxy.fn(); // 打印出配置對象和 objProxy。 // 能夠等價的將方法轉化成 (objProxy.fn).call(objProxy)。 // 雖然方法也是經過 target 訪問到的,但對於方法來講,環境變量一開始就肯定了。
原型爲代理器
若是對象的原型爲代理器,當操做進行到原型時,實際是操做原型的代理器對象。這時,其行爲和普通代理器一致。
let obj = Object.create(new Proxy({}, { get(target, attr) { console.log('In proxy.'); return Reflect.get(target, attr); } })); obj.name; // 打印出 In proxy. 。 // 當在實例上找不到對應屬性時,轉到了原型上,這時便被攔截了。
這裏僅僅是羅列出某些重點,詳細的請看手冊(標準和行爲處於變更中)。
get
攔截屬性的讀取操做,包括數組取值。
set
攔截屬性的賦值操做,包括數組賦值。
嚴格模式下,必須返回可轉化爲true
的值。
嚴格模式下,若是代理對象有某些限制(屬性不可寫等),執行相應的攔截操做天然會報錯。
apply
攔截函數的調用、call
和apply
操做。
has
攔截判斷對象是否具備某屬性。
只對in
和Reflect.has()
方法有效,for in
屬於遍歷系列。
construct
攔截new
命令,必須返回一個對象。
deleteProperty
攔截delete
屬性操做。
嚴格模式下,必須返回可轉化爲true
的值。
嚴格模式下,若是代理對象有某些限制(屬性不可寫等),執行相應的攔截操做天然會報錯。
defineProperty
攔截Object.defineProperty()
,不會攔截defineProperties
。
嚴格模式下,若是代理對象有某些限制(屬性不可配置等),執行相應的攔截操做天然會報錯。
getOwnPropertyDescriptor
攔截Object.getOwnPropertyDescriptor()
,不會攔截getOwnPropertyDescriptors
。
必須返回一個對象或undefined
,即返回與原方法相同的返回值,不然報錯。
getPrototypeOf
攔截獲取對象原型,必須返回一個對象或者null
。
若是對象不可擴展,必須返回真實的原型對象。
直接訪問__proto__
,經過各種方法等等都會觸發攔截。
setPrototypeOf
攔截Object.setPrototypeOf()
方法。
isExtensible
攔截Object.isExtensible()
操做。
返回值必須與目標對象的isExtensible
屬性保持一致,不然會拋出錯誤。
preventExtensions
攔截Object.preventExtensions()
,返回值會自動轉成布爾值。
ownKeys
攔截對象自身屬性的遍歷操做。
好比keys()
,getOwnPropertyNames()
,getOwnPropertySymbols()
等等。
返回值必須是數組,項只能是字符串或Symbol
,不然報錯。
返回值會根據調用方法的需求進行過濾,好比Object.keys()
裏不會有symbole
。
若是目標對象自身包含不可配置的屬性,則該屬性必須被返回,不然報錯。
若是目標對象不可擴展,返回值必須包含原對象的全部屬性,且不能包含多餘的屬性,不然報錯。
此靜態方法也用於生成代理器對象的,但它還會返回一個回收代理器的方法。
使用場景:不容許直接訪問目標對象,必須經過代理訪問。並且一旦訪問結束,就收回代理權,不容許再次訪問。
let {proxy, revoke} = Proxy.revocable(obj, {}); revoke(); 此時其內部屬性值 [[IsRevoked]] 爲 true,不能再操做代理器,不然報錯。
最終目的是成爲語言內部方法的宿主對象。
好比說defineProperty, getPrototypeOf, preventExtensions
都很明顯屬於內部方法,不該掛在Object
下。
提供替代命令式操做的相應函數。
使用Reflect.has(obj, attr)
替代in操做
。
使用Reflect.deleteProperty(obj, attr)
替代delete操做
。
使函數的行爲更加完善和嚴格。
在沒法定義屬性時,Reflect.defineProperty
返回false
而不是拋出錯誤。
在要求類型是對象的參數爲非對象時,會直接報錯而不是調用Object()
進行轉化。
與Porxy
可攔截的方法對應,方便在實現自定義行爲時,直接調用以完成默認行爲。
這裏僅僅是羅列出某些重點,詳細的請看手冊。
Reflect.get
Reflect.get(obj, attr, receiver)
返回屬性值,沒有則返回undefined
。 receiver
是設置getter
和setter
裏的this
指向,默認指向obj
。
Reflect.set
Reflect.set(obj, attr, value, receiver)
設置屬性值,返回布爾值。
注意:當該屬性不是getter
時,傳入receiver
等同於設置receiver
對象上的屬性值。
Reflect.has
Reflect.has(obj, attr)
等同attr in obj
。
Reflect.deleteProperty
Reflect.deleteProperty(obj, attr)
等同delete obj[attr]
。
Reflect.construct
Reflect.construct(func, args) args
等同於使用apply
方法傳入的參數數組。
提供了不使用new
來調用構造函數的方法,等同new func(...args)
。
Reflect.getPrototypeOf
做用及參數等同Object.getPrototypeOf(obj)
。
Reflect.setPrototypeOf
做用及參數等同Object.setPrototypeOf(obj, newProto)
。
Reflect.apply
做用及參數等同Function.prototype.apply.call(func, thisArg, args)
。
Reflect.defineProperty
做用及參數等同Object.defineProperty(obj, attr, descriptor)
。
Reflect.getOwnPropertyDescriptor
做用及參數等同Object.getOwnPropertyDescriptor(obj, attr)
。
Reflect.isExtensible
Reflect.isExtensible(obj)
返回一個布爾值,表示當前對象是否可擴展。
Reflect.preventExtensions
Reflect.preventExtensions(obj)
設置對象爲不可擴展,返回布爾值。
Reflect.ownKeys
Reflect.ownKeys(obj)
返回對象自己的全部屬性。
等同於Object.getOwnPropertyNames
與Object.getOwnPropertySymbols
之和。