拿Proxy能夠做哪些有意思的事兒

Proxy是什麼

起首,我們要清晰,Proxy是什麼意義,這個單詞翻譯過來,就是 代辦
可以理解為,有一個很火的明星,開通了一個微博賬號,這個賬號非常活潑,復興粉絲、隨處點贊之類的,但可以並非真的由本人在保護的。
而是在背地有一個其他人 or 團隊來運營,我們就可以稱他們為代辦人,由於他們宣布的微博就代表了明星本人的意義。
P.S. 強行舉例子,由於本人不追星,只是猜想可以會有如許的運營團隊

這個代入到JavaScript當中來,就可以理解為對對象或許函數的代辦操縱。

JavaScript中的Proxy

Proxy是ES6中供應的新的API,可以用來定義對象種種基礎操縱的自定義行動
(在文檔中被稱為traps,我以為可以理解為一個針對對象種種行動的鈎子)
拿它可以做許多有意義的事變,在我們須要對一些對象的行動舉行掌握時將變得非常有用。

Proxy的語法

建立一個Proxy的實例須要傳入兩個參數

  1. target 要被代辦的對象,可以是一個object或許function
  2. handlers對該代辦對象的種種操縱行動處置懲罰
let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)

proxy.a = 123

console.log(target.a) // 123

在第二個參數為空對象的狀況下,基礎可以理解為是對第一個參數做的一次淺拷貝
(Proxy必需是淺拷貝,假如是深拷貝則會失去了代辦的意義)

Traps(種種行動的代辦)

就像上邊的示例代碼一樣,假如沒有定義對應的trap,則不會起任何作用,相當於直接操縱了target
當我們寫了某個trap今後,在做對應的行動時,就會觸發我們的回調函數,由我們來掌握被代辦對象的行動。

最常常運用的兩個trap應當就是getset了。
從前JavaScript有着在定義對象時針對某個屬性舉行設置gettersetter

let obj = {
  _age: 18,
  get age ()  {
    return `I'm ${this._age} years old`
  },
  set age (val) {
    this._age = Number(val)
  }
}

console.log(obj.age) // I'm 18 years old
obj.age = 19
console.log(obj.age) // I'm 19 years old

就像這段代碼形貌的一樣,我們設置了一個屬性_age,然後又設置了一個get ageset age
然後我們可以直接挪用obj.age來獵取一個返回值,也可以對其舉行賦值。
這麼做有幾個瑕玷:

  1. 針對每個要代辦的屬性都要編寫對應的gettersetter
  2. 必需還要存在一個存儲實在值的key(假如我們直接在getter裡邊挪用this.age則會湧現客棧溢出的狀況,由於不管什麼時候挪用this.age舉行取值都邑觸發getter

Proxy很好的處理了這兩個題目:

let target = { age: 18, name: 'Niko Bellic' }
let handlers = {
  get (target, property) {
    return `${property}: ${target[property]}`
  },
  set (target, property, value) {
    target[property] = value
  }
}
let proxy = new Proxy(target, handlers)

proxy.age = 19
console.log(target.age, proxy.age)   // 19,          age : 19
console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic

我們經由歷程建立getset兩個trap來一致管理一切的操縱,可以看到,在修正proxy的同時,target的內容也被修正,而且我們對proxy的行動舉行了一些特別的處置懲罰。
而且我們無需分外的用一個key來存儲實在的值,由於我們在trap內部操縱的是target對象,而不是proxy對象。

拿Proxy來做些什麼

由於在運用了Proxy后,對象的行動基礎上都是可控的,所以我們能拿來做一些之前完成起來比較複雜的事變。
在下邊列出了幾個簡樸的實用場景。

處理對象屬性為undefined的題目

在一些層級比較深的對象屬性獵取中,怎樣處置懲罰undefined一直是一個痛楚的歷程,假如我們用Proxy可以很好的兼容這類狀況。

(() => {
  let target = {}
  let handlers = {
    get: (target, property) => {
      target[property] = (property in target) ? target[property] : {}
      if (typeof target[property] === 'object') {
        return new Proxy(target[property], handlers)
      }
      return target[property]
    }
  }
  let proxy = new Proxy(target, handlers)
  console.log('z' in proxy.x.y) // false (實在這一步已針對`target`建立了一個x.y的屬性)
  proxy.x.y.z = 'hello'
  console.log('z' in proxy.x.y) // true
  console.log(target.x.y.z)     // hello
})()

我們代辦了get,並在裡邊舉行邏輯處置懲罰,假如我們要舉行get的值來自一個不存在的key,則我們會在target中建立對應個這個key,然後返回一個針對這個key的代辦對象。
如許就可以保證我們的取值操縱肯定不會拋出can not get xxx from undefined
然則這會有一個小瑕玷,就是假如你確切要推斷這個key是不是存在只可以經由歷程in操縱符來推斷,而不可以直接經由歷程get來推斷。

一般函數與組織函數的兼容處置懲罰

假如我們供應了一個Class對象給其他人,或許說一個ES5版本的組織函數。
假如沒有運用new關鍵字來挪用的話,Class對象會直接拋出非常,而ES5中的組織函數this指向則會變成挪用函數時的作用域。
我們可以運用apply這個trap來兼容這類狀況:

class Test {
  constructor (a, b) {
    console.log('constructor', a, b)
  }
}

// Test(1, 2) // throw an error
let proxyClass = new Proxy(Test, {
  apply (target, thisArg, argumentsList) {
    // 假如想要制止運用非new的體式格局來挪用函數,直接拋出非常即可
    // throw new Error(`Function ${target.name} cannot be invoked without 'new'`)
    return new (target.bind(thisArg, ...argumentsList))()
  }
})

proxyClass(1, 2) // constructor 1 2

我們運用了apply來代辦一些行動,在函數挪用時會被觸發,由於我們明白的曉得,代辦的是一個Class或組織函數,所以我們直接在apply中運用new關鍵字來挪用被代辦的函數。

以及假如我們想要對函數舉行限定,制止運用new關鍵字來挪用,可以用另一個trap:construct

function add (a, b) {
  return a + b
}

let proxy = new Proxy(add, {
  construct (target, argumentsList, newTarget) {
    throw new Error(`Function ${target.name} cannot be invoked with 'new'`)
  }
})

proxy(1, 2)     // 3
new proxy(1, 2) // throw an error

用Proxy來包裝fetch

在前端發送要求,我們如今經常常運用到的應當就是fetch了,一個原生供應的API。
我們可以用Proxy來包裝它,使其變得更易用。

let handlers = {
  get (target, property) {
    if (!target.init) {
      // 初始化對象
      ['GET', 'POST'].forEach(method => {
        target[method] = (url, params = {}) => {
          return fetch(url, {
            headers: {
              'content-type': 'application/json'
            },
            mode: 'cors',
            credentials: 'same-origin',
            method,
            ...params
          }).then(response => response.json())
        }
      })
    }

    return target[property]
  }
}
let API = new Proxy({}, handlers)

await API.GET('XXX')
await API.POST('XXX', {
  body: JSON.stringify({name: 1})
})

GETPOST舉行了一層封裝,可以直接經由歷程.GET這類體式格局來挪用,並設置一些通用的參數。

完成一個淺易的斷言東西

寫過測試的列位童鞋,應當都邑曉得斷言這個東西
console.assert就是一個斷言東西,接收兩個參數,假如第一個為false,則會將第二個參數作為Error message拋出。
我們可以運用Proxy來做一個直接賦值就可以完成斷言的東西。

let assert = new Proxy({}, {
  set (target, message, value) {
    if (!value) console.error(message)
  }
})

assert['Isn\'t true'] = false      // Error: Isn't true
assert['Less than 18'] = 18 >= 19  // Error: Less than 18

統計函數挪用次數

在做服務端時,我們可以用Proxy代辦一些函數,來統計一段時間內挪用的次數。
在後期做機能剖析時可以會可以用上:

function orginFunction () {}
let proxyFunction = new Proxy(orginFunction, {
  apply (target, thisArg. argumentsList) {
    log(XXX)

    return target.apply(thisArg, argumentsList)
  }
})

悉數的traps

這裏列出了handlers一切可以定義的行動 (traps)

詳細的可以檢察
MDN-Proxy

裡邊一樣有一些例子

trapsdescription
get獵取某個key
set設置某個key
has運用in操縱符推斷某個key是不是存在
apply函數挪用,僅在代辦對象為function時有用
ownKeys獵取目的對象一切的key
construct函數經由歷程實例化挪用,僅在代辦對象為function時有用
isExtensible推斷對象是不是可擴大,Object.isExtensible的代辦
deleteProperty刪除一個property
defineProperty定義一個新的property
getPrototypeOf獵取原型對象
setPrototypeOf設置原型對象
preventExtensions設置對象為不可擴大
getOwnPropertyDescriptor獵取一個自有屬性 (不會去原型鏈查找) 的屬性形貌

參考資料

  1. Magic Methods in JavaScript? Meet Proxy!
  2. How to use JavaScript Proxies for Fun and Profit
  3. MDN-Proxy
    原文作者:賈順名
    原文地址: https://segmentfault.com/a/1190000015009255
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞