Louis's blog
GitHub
GitHub
  • 瀏覽器運作原理
  • 閉包
  • this
  • arguments 和 array-like
  • apply、call、bind
  • 函式程式設計
  • 物件和原型
  • 原型鏈和繼承
  • ES6 和 Class
  • ES6 中新增的數據類型
  • ES6 語法糖
  • var、let、const
  • ES7~ES12
  • Strict Mode
  • 補充
  • Proxy 和 Reflect
  • Promise
  • Iterator
  • 生成器
  • async、await
  • Event loop
  • 錯誤處理
  • 模塊化
  • 套件管理工具
  • JSON
  • WebStorage
  • IndexDB
  • Cookie
  • BOM
  • DOM
  • 防抖(debounce)和節流(throttle)
  • Deep copy
  • 事件總線

Proxy 和 Reflect

在沒有 Proxy 時,是如何監聽物件被操作的呢?可以利用 Object.defineProperty 的訪問描述器:

const obj = {
  name: 'Louis',
  age: 26
}

// 監聽多屬性
Object.keys(obj).forEach((key) => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    get() {
      console.log('監聽obj屬性被訪問了')
      return value
    },
    set(newValue) {
      console.log('監聽obj屬性放修改了')
      value = newValue
    }
  })
})

不過 Object.defineProperty 在設計之初就並非是為了進行屬性監聽的,只能對物件既有的屬性做監聽,對於新增或是刪除屬性 Object.defineProperty 是無法做到的,所以 ES6 新增了 Proxy 。

Proxy 基本使用

Proxy 是一個類,是用於創建一個代理物件的,針對要監聽的原物件所有操作,都通過 Proxy 所創建的代理物件完成:

const obj = {
  name: 'Louis',
  age: 26
}

// 默認將所有操作映射到原物件
const objProxy = new Proxy(obj, {
  // 獲取值時的捕獲器
  get: function (target, key) {
    console.log(`監聽到${target}物件的${key}屬性被訪問了`)
    return target[key]
  },

  // 設置值時的捕獲器
  set: function (target, key, newValue) {
    console.log(`監聽到${target}物件的${key}屬性被設置了`)
    target[key] = newValue
  },

  // 監聽 in 的捕獲器
  has: function (target, key) {
    console.log(`監聽到${target}物件的${key}屬性的in操作`)
    return key in target
  },

  // 監聽刪除的捕獲器
  deleteProperty: function (target, key) {
    console.log(`監聽到${target}物件的${key}屬性的刪除操作`)
    delete target[key]
  }
})

objProxy.name = 'Renny'

// 輸出:Renny
console.log(obj.name)

以上列出 4 個常用的捕獲器,實際上捕獲器總共有 13 個:

  • Handler.getPrototypeOf:Object.getPrototypeOf 方法的捕獲器。
  • Handler.setPrototypeOf:Object.setPrototypeOf 方法的捕獲器。
  • Handler.isExtensible:Object.isExtensible 方法的捕獲器。
  • Handler.preventExtensible:Object.preventExtensible 方法的捕獲器。
  • Handler.getOwnPropertyDecriptor:Object.getOwnPropertyDecriptor 方法的捕獲器。
  • Handler.difineProperty:Object.difineProperty 方法的捕獲器。
  • Handler.ownKeys:Object.getOwnPropertyName 和 Object.getOwnPropertySymbols 方法的捕獲器。
  • Handler.apply:函數調用操作的捕獲器。
  • Handler.construct:new 操作符的捕獲器。

Reflect 基本使用

Reflect 也是 ES6 新增的一個 API,字面上的意思是反射,Reflect 出現的目的是替代 Object 本身的方法,目前最常見的使用場景是和 Proxy 一起使用:

const obj = {
  name: 'Louis',
  age: 26
}

const objProxy = new Proxy(obj, {
  get: function (target, key) {
    return Reflect.set(target, key)
  },
  set: function (target, key, newValue) {
    const result = Reflect.set(target, key, newValue)
    if (result) {
      // ...設置成功
    } else {
      // ...設置失敗
    }
  }
})

objProxy.name = 'Renny'

console.log(obj.name)

Proxy 中 receiver 參數

如果在物件中需要攔截 getter 和 setter 中的操作,需要使用 receiver 參數:

const obj = {
  _name: 'Louis',
  get name() {
    // 需要讓 this 變成 proxy 物件才能攔截
    return this._name
  },
  set name(newValue) {
    this._name = newValue
  }
}

const objProxy = new Proxy(obj, {
  get: function (target, key, receiver) {
    // receiver 是創建出來的代理物件
    // Reflect.get 的第三個參數可以改變 getter 和 setter 的 this 指向
    return Reflect.get(target, key, receiver)
  },
  set: function (target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
  }
})

console.log(objProxy.name)

Reflect.construct

執行某個構造函數中的內容,但是創建出來的是其他物件實例:

function Student(name, age) {
  this.name = name
  this.age = age
}

function Teacher() {}

// 執行 Student 函數中的內容,但是創建出來 Teacher 物件
const teacher = Reflect.construct(Student, ['Louis', 26], Teacher)

console.log(teacher.__proto__ === Teacher.prototype)
Edit this page
Last Updated:
Contributors: louis, louis61619
Prev
補充
Next
Promise