es6 proxy淺析


Proxy

使用proxy,你可以把老虎偽裝成貓的外表,這有幾個例子,希望能讓你感受到proxy的威力。
proxy 用來定義自定義的基本操作行為,比如查找、賦值、枚舉性、函數調用等。

proxy接受一個待代理目標對象和一些包含元操作的對象,為待代理目標創建一個‘屏障’,並攔截所有操作,重定向到自定義的元操作對象上。

proxy通過new Proxy來創建,接受兩個參數:

  1. 待代理目標對象
  2. 元操作對象

閑話少說,直接看例子。

最簡單的只代理一個方功能,在這個例子里,我們讓get操作,永遠返回一個固定的值

let target = {
  name: 'fox',
  age: 23
}
let handler = {
  get: (obj, k) => 233
}
target = new Proxy(target, handler);
target.a // 233
target.b // 233
target.c // 233

無論你taget.xtarget[x]Reflect.get(target, 'x')都會返回233
當然,代理get僅僅是其中一種操作,還有:
- get
- set
- has
- apply
- construct
- ownKeys
- deleteProperty
- defineProperty
- isExtensible
- preventExtensions
- getPrototypeOf
- setPrototypeOf
- getOwnPropertyDescriptor

改變默認值為0

在其他語言中,如果訪問對象中沒有的屬性,默認會返回0,這在某些場景下很有用,很方便,比如坐標系,一般來說z軸默認是0.
但是在js中,對象中不存在的key的默認值是undefined,而不是合法的初始值。
不過可以使用proxy解決這個問題

const defaultValueObj = (target, defaultValue) => new Proxy(target, {
  get: (obj, k) => Reflect.has(obj, k) ? obj[k] : defaultValue
})

建議根據不同類型返回不同的默認值,Number => 0 String => '' Object => {} Array => []等等

數組負索引取值

js中,獲取數組的最后一個元素是相對麻煩的,容易出錯的。這就是為什么TC39提案定義一個方便的屬性,Array.lastItem去獲取最后一個元素。
其他語言比如python,和ruby提供了訪問數組最后一個元素的方法,例如使用arr[-1]代替arr[arr.length - 1]
不過,我們有proxy,負索引在js中也可以實現。

const negativeArray = els => new Proxy(els, {
  get: (target, k) => Reflect.get(target, +k < 0 ? String(target.length + +k) : k)
})

需要注意的一點是,get操作會字符串化所有的操作,所以我們需要轉換成number在進行操作,
這個運用也是negative-array的原理

隱藏屬性

js未能實現私有屬性,盡管之后引入了Symbol去設置獨一無二的屬性,但是這個被后來的Object.getOwnPropertySumbols淡化了
長期以來,人們使用下划線_來表示屬性的私有,這意味着不運行外部操作該屬性。不過,proxy提供了一種更好的方法來實現類似的私有屬性

const enablePrivate = (target, prefix = '_') => new Proxy(target, {
  has: (obj, k) => (!k.startsWith(prefix) && k in obj),
  ownKeys: (obj, k) => Reflece.ownKeys(obj).filter(k => (typeof k !== 'string' || !k.startsWith(prefix))),
  get: (obj, k, rec) => (k in rec) ? obj[k] : undefined
})

結果

let userData = enablePrivate({
  firstName: 'Tom',
  mediumHandle: '@tbarrasso',
  _favoriteRapper: 'Drake'
})

userData._favoriteRapper        // undefined
('_favoriteRapper' in userData) // false
Object.keys(userData)           // ['firstName', 'mediumHandle']

如果你打印該proxy代理對象,會在控制台看到,不過無所謂。

緩存失效

服務端和客戶端同步一個狀態可能會出現問題,這很常見,在整個操作周期內,數據都有可能被改變,並且很難去掌握需要重新同步的時機。
proxy提供了一種新的辦法,可以讓屬性在必要的時候失效,所有的訪問操作,都會被檢查判斷,是否返回緩存還是進行其他行為的響應。

const timeExpired = (target, ttl = 60) => {
  const created_at = Date.now();
  const isExpired = () => (Date.now - created_at) > ttl * 1000;
  return new Proxy(tarvet, {
    get: (target, k) => isExpired() ? undefined : Reflect.get(target, k);
  })
}

上面的功能很簡單,他在一定時間內正常返回訪問的屬性,當超出ttl時間后,會返回undefined。

let timeExpired = ephemeral({
  balance: 14.93
}, 10)

console.log(bankAccount.balance)    // 14.93

setTimeout(() => {
  console.log(bankAccount.balance)  // undefined
}, 10 * 1000)

上面的例子會輸出undefined在十秒后,更多的騷操作還請自行斟酌。

只讀

盡管Object.freeze可以讓對象變得只讀,但是我們可以提供更好的方法,讓開發者在操作屬性的時候獲取明確的提示

const nope = () => {
  throw new Error('不能改變只讀屬性')
}
const read_only = (obj) => new Proxy(obj, {
  set: nope,
  defineProperty: nope,
  deleteProperty: nope,
  preentExtensions: nope,
  setPrototypeOf: nope
});

枚舉

結合上面的只讀方法

const createEnum = (target) => read_only(new Proxy(target, {
  get: (obj, k) = {
    if (k in obj) {
      return Reflect.get(obj, k)
    }
    throw new ReferenceError(`找不到屬性${k}`)
  }
}))

我們得到了一個對象,如果你訪問不存在的屬性,不會得到undefined,而是拋出一個指向異常錯誤,折讓調試變得更方便。
這也是一個代理代理的例子,需要保證被代理的代理是一個合法的代理對象,這個有助於混合一些復雜的功能。

重載操作符

最神奇的可能就是重載某些操作符了,比如使用handler.has重載in
in用來判斷指定的屬性是否指定對象或者對象的原型鏈上,這種行為可以很優雅的被重載,比如創建一個用於判斷目標數字是否在制定范圍內的代理

const range = (min, max) => new Proxy(Object.create(null), {
  has: (obj, k) => (+k > min && +k < max)
})
const X = 10.5
const nums = [1, 5, X, 50, 100]

if (X in range(1, 100)) { // true
  // ...
}

nums.filter(n => n in range(1, 10)) // [1, 5]

上面的例子,雖然不是什么復雜的操作,也沒有解決什么復雜的問題,但是這種清晰,可讀,可復用的方式相信也是值得推崇的。
當然除了in操作符,還有delete 和 new;

其他

  • 兼容性一般,不過谷歌開發的proxy-polyfill目前已經支持get、set、apply、construct到ie9了
  • 目前瀏覽器沒有辦法判斷對象是否被代理,不過在node版本10以上,可以使用util.types.isProxy來判斷
  • proxy的第一個參數必須是對象,不能代理原始值
  • 性能,proxy的一個缺點就是性能,但是這個也因人/瀏覽器而異,不過,proxy絕對不適合用在性能關鍵點的代碼上,當然,你可以衡量proxy帶來的遍歷和可能損耗的性能,進行合理的中和,來達到最佳的開發體驗和用戶體驗


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM