Proxy是ES6的語法。對於ES6的箭頭函數、變量的解構賦值,咱們都能耳熟能詳,熟練使用。對於Proxy 這樣的特性卻不多用到,一方面考慮到兼容性的問題(babel已經解決了),另外一方面確實這些特性不那麼容易使用,也就是使用場景很少。數組
Proxy主要是用來修改某些操做的默認行爲,從這個角度來說,你能夠把他理解成一個攔截器。想要訪問對象,都要通過這層攔截。那麼咱們就能夠在這層攔截上作各類操做了。好比你設置一個對象的值的時候,對對象的值進行校驗等。babel
Proxy 支持的攔截操做一共 13 種:app
經過proxy能夠作什麼呢?函數
1.用來攔截和監聽對對象屬性的各類操做,能夠在各類操做以前作校驗或者打日誌等。這個很簡單了。this
2.中斷操做設計
目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。代理
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
3.能夠經過Proxy實現是對象的私有變量(真正的私有)日誌
// filter參數做爲一個過濾函數出入 function priavateProp(obj, filter){ const handler = { get(obj, prop) { if(!filter(prop)){ let val = Reflect.get(obj, prop) if(typeof val === 'function'){ val = val.bind(obj) } return val } }, set(obj, prop, val) { if(filter(prop)){ throw new Error(`cannot set property ${prop}`) } return Reflect.set(obj, prop, val) }, has(obj, prop) { return filter(prop) ? false : Reflect.has(obj, prop) }, ownKeys(obj) { return Reflect.ownKeys(obj).filter( prop => !filter(prop)) } } return new Proxy(obj, handler) } // 私有屬性過濾器 // 規則:以 _ 爲開頭的屬性都是私有屬性 function filter(prop){ return prop.indexOf('_') === 0 } const o = { _private: 'private property', name: 'public name', say(){ // 內部訪問私有屬性 console.log(this._private) } } const p = priavateProp(o, filter) console.log(p) // Proxy p._private // undefined JSON.stringify(p) // "{"name":"public name"}" // 只能內部訪問私有屬性 p.say() // private property console.log('_private' in p) // false // 不能遍歷到私有屬性 Object.keys(p) // ["name", "say"] // 私有屬性不能賦值 p._private = '000' // Uncaught Error: cannot set property _private
4.擴充操做符code
使用 Proxy 但是實現操做符的重載,但也只能對 in of delete new 這幾個實現重載對象
咱們劫持 in 操做符來實現 Array.includes 檢查值是否存在數組中
function arrIn(arr){ const handler = { has(arr, val) { return arr.includes(val) } } return new Proxy(arr, handler) } const arr = arrIn(['a', 'b', 'c']) 'a' in arr // true 1 in arr // false
5.追蹤數組和對象的變更。聽說下一版本的VUE就是經過這種方式來進行數據綁定的。
function trackChange(target, fn){ const handler = { set(target, key, value) { const oldVal = target[key] target[key] = value; fn(target, key, oldVal, value) }, deleteProperty(target, key) { const oldVal = target[key] delete target[key] fn(target, key, oldVal, undefined) return true; } } return new Proxy(target, handler) } const obj = trackChange({}, (target, key, oldVal, newVal) => { console.log(`obj.${key} value from ${oldVal} to ${newVal}`) }) obj.a = 'a111' // obj.a value from undefined to a111 obj.a = 'axxxxx' // obj.a value from a111 to axxxxx delete obj.b // obj.b value from undefined to undefined obj.c = 'c1' // obj.c value from undefined to c1 const arr = trackChange([1, 2, 3, 4, 5], (target, key, oldVal, newVal) => { let val = isNaN(parseInt(key)) ? `.${key}` : `[${key}]` const sum = arr.reduce( (p,n) => p + n) console.log(`arr${val} value from ${oldVal} to ${newVal}`) console.log(`sum [${arr}] is ${sum}`) }) arr[4] = 0 // arr[4] value from 5 to 0 // sum [1,2,3,4,0] is 10 delete arr[3] // arr[3] value from 4 to undefined // sum [1,2,3,,0] is 6 arr.length = 2 // arr.length value from 5 to 2 // sum [1,2] is 3
裏面有些疑點在此披露下:
拿這個deleteProperty(target, propKey)來講,我查了不少資料上說,若是這個方法拋出錯誤或者返回false,當前屬性就沒法被delete命令刪除。(暫時未找到標準上是怎麼解釋的)
var handler = { deleteProperty (target, key) { console.log('delete '+ key) return true }, }; function invariant (key, action) { if (key[0] === '_') { return false; } } var target = { _prop: 'foo' }; var proxy = new Proxy(target, handler); delete proxy._prop console.log(proxy._prop) console.log(target._prop)
上面的代碼返回true,並無刪除。其實若是你在這個函數裏面執行了刪除操做,返回什麼並不重要。也就是說這個函數返回值是true,或者false,並不能影響刪除的結果。除非你拋出來錯誤,那這個代碼就執行不了了,直接報錯了。試驗了defineProperty,也是這樣的。這些攔截函數,返回結果是不重要的,重要的是你在攔截相應的操做的時候,你作了什麼操做,好比你攔截的是刪除屬性,那你必需要有刪除屬性這個操做,你要是給這個屬性賦值,那確定達不到效果。這一點要搞清楚。
可是若是代理的是個數組,那他們return的值就必須得是true。
var handler = { set(target,key,value,proxy){ var oldVal = target[key]; var newVal = value; target[key] = value console.log(`arr ${key} changed from ${oldVal} to ${newVal}`) }, deleteProperty (target, key) { console.log('delete '+ key) delete target[key]; }, }; var arr = new Proxy([1,2,3], handler); arr.push(2);//'set' on proxy: trap returned falsish for property '3' arr.pop();//'deleteProperty' on proxy: trap returned falsish for property '2'
這就是set和deleteProperty不返回結果,爆出來的問題。每一個函數中返回true,返回結果就是
arr 3 changed from undefined to 2 arr length changed from 4 to 4 delete 3 arr length changed from 4 to 3
因此無論怎麼樣,對於這類返回boolean類型的方法,咱們必定要記得返回值。
還有一種規範的寫法,咱們通常都是經過Reflect進行攔截操做,而後將其結果返回。
var handler = { set(target,key,value,proxy){ var oldVal = target[key]; var newVal = value; console.log(`arr ${key} changed from ${oldVal} to ${newVal}`) return Reflect.set(target,key,value,proxy); }, deleteProperty (target, key) { console.log('delete '+ key) return Reflect.deleteProperty(target,key) }, }; var arr = new Proxy([1,2,3], handler); arr.push(2); arr.pop();
那什麼是Reflect呢?
Reflect對象與Proxy對象同樣,也是ES6爲了操做對象而提供的新API。Reflect對象的設計目的有這樣幾個。
它擁有的API和Proxy一一對應。