1. 程式人生 > 程式設計 >理解JavaScript中的Proxy 與 Reflection API

理解JavaScript中的Proxy 與 Reflection API

一、建立 Proxy

let target = {}
let proxy = new Proxy(target,{})

proxy.name = "proxy"
console.log(proxy.name)  // proxy
console.log(target.name) // proxy

target.name = "target"
console.log(proxy.name)  // target
console.log(target.name) // target

在上面的例子中,由 Proxy 構造器建立的 proxy 物件會將自身的所有操作直接轉發給 target
proxy.name

被賦值為 "proxy" 時,target 物件也會建立 name 屬性並獲得同樣的值。實際上 proxy 物件本身並不建立和儲存 name 屬性,它只是轉發對應的操作給 target

類似的,proxy.name target.name 的值始終保持一致,因為它們實際上都指向了 target.name。這也意味著給 target.name 賦予一個新的值時,該變化也會反映到 proxy.name 上。

使用 set Trap 驗證屬性

Proxy 允許開發者主動攔截本該轉發給 target 物件的底層操作,這些攔截行為通過 trap 實現。每個 trap 都可以覆蓋 JavaScript 物件的某些內建行為,即 proxy 允許通過 trap

攔截並修改指向 target 物件的操作。

假設需要建立一個新新增的屬性值只能是數字型別的物件,就可以藉助 set trap 覆蓋預設的賦值行為。程式碼如下:

let target = {
 name: "target"
}

let proxy = new Proxy(target,{
 set(trapTarget,key,value,receiver) {
  if (!trapTarget.hasOwnProperty(key)) {
   if (isNaN(value)) {
    throw new TypeError("New property must be a number.")
   }
  }
  return Reflect.set(trapTarget,receiver)
 }
})

proxy.count = 1
console.log(proxy.count)  // 1
console.log(target.count) // 1

proxy.name = "proxy"
console.log(proxy.name)  // proxy
console.log(target.name)  // proxy

proxy.anotherName = "proxy"
// TypeError: New property must be a number.

set trap 中的四個引數含義如下:

  • trapTarget:接收新屬性的物件(即 proxy 指向的 target)
  • key:新屬性對應的 key
  • value:新屬性對應的 value
  • receiver:通常為 proxy 自身

Reflect.set() 是與 set trap 相對應的原始方法,表示被覆蓋前的預設的賦值行為。

使用 get Trap 令程式讀取不存在屬性時報錯

JavaScript 在讀取不存在的屬性時並不會報錯,而是返回 undefined

let target = {}
console.log(target.name) // undefined

可以藉助 get trap 修改讀取物件屬性時的預設行為:

let proxy = new Proxy({},{
 get(trapTarget,receiver) {
  if (!(key in receiver)) {
   throw new TypeError("Property " + key + " doesn't exist.")
  }
  return Reflect.get(trapTarget,receiver)
 }
})

proxy.name = "proxy"
console.log(proxy.name) // proxy

console.log(proxy.nme)
// TypeError: Property nme doesn't exist.

通過 deleteProperty Trap 防止刪除屬性

JavaScript 中使用 delete 操作符刪除物件的屬性:

let target = {
 name: "target",value: 42
}

Object.defineProperty(target,"name",{ configurable: false })
console.log("value" in target)  // true

let result1 = delete target.value
console.log(result1)       // true
console.log("value" in target)  // false

let result2 = delete target.name
console.log(result2)       // false
console.log("name" in target)   // true

使用 deleteProxy Trap 防止屬性被意外刪除:

let target = {
 name: "target",value: 42
}

let proxy = new Proxy(target,{
 deleteProperty(trapTarget,key) {
  if (key === "value") {
   return false
  } else {
   return Reflect.deleteProperty(trapTarget,key)
  }
 }
})

console.log("value" in proxy)  // true

let result1 = delete proxy.value
console.log(result1)       // false
console.log("value" in proxy)  // true

let result2 = delete proxy.name
console.log(result2)       // true
console.log("name" in proxy)   // false

二、Proxy 的現實應用

logging

function makeLoggable(target) {
 return new Proxy(target,{
  get: (target,property) => {
   console.log("Reading " + property)
   return target[property]
  },set: (target,property,value) => {
   console.log("Writing value " + value + " to " + property)
   target[property] = value
  }
 })
}

let ninja = { name: "Yoshi" }
ninja = makeLoggable(ninja)

console.log(ninja.name)
ninja.weapon = "sword"
// Reading name
// Yoshi
// Writing value sword to weapon

效能測試

function isPrime(number) {
 if (number < 2) { return false }

 for (let i = 2; i < number; i++) {
  if (number % i === 0) { return false }
 }
 return true
}

isPrime = new Proxy(isPrime,{
 apply: (target,thisArg,args) => {
  console.time("isPrime")
  const result = target.apply(thisArg,args)
  console.timeEnd("isPrime")
  return result
 }
})

console.log(isPrime(1358765377))
// isPrime: 6815.107ms
// true

自動新增屬性

function Folder() {
 return new Proxy({},property) => {
   console.log("Reading " + property)

   if(!(property in target)) {
    target[property] = new Folder()
   }
   return target[property]
  }
 })
}

const rootFolder = new Folder()
rootFolder.ninjasDir.firstNinjaDir.ninjaFile = "yoshi.txt"
// Reading ninjasDir
// Reading firstNinjaDir
console.log(rootFolder.ninjasDir.firstNinjaDir.ninjaFile)
// Reading ninjasDir
// Reading firstNinjaDir
// Reading ninjaFile
// yoshi.txt

參考資料

https://leanpub.com/understandinges6

https://www.manning.com/books/secrets-of-the-javascript-ninja-second-edition

以上就是理解JavaScript中的Proxy 與 Reflection API的詳細內容,更多關於JavaScript中的Proxy 與 Reflection API的資料請關注我們其它相關文章!