qiuhongbingo / blog

Writing something in Issues.
https://github.com/qiuhongbingo/blog/issues
3 stars 0 forks source link

Proxy VS Object.defineProperty #19

Open qiuhongbingo opened 4 years ago

qiuhongbingo commented 4 years ago
/**
 * Object.defineProperty 不能监听数组的变化,需要进行数组方法的重写
 * Object.defineProperty 必须遍历对象的每个属性,且对于嵌套结构需要深层遍历
 * Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性,Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的
 * Proxy 支持代理数组的变化
 * Proxy 的第二个参数除了 set 和 get 以外,可以有 13 种拦截方法,比起 Object.defineProperty() 更加强大,这里不再一一列举
 * Proxy 性能将会被底层持续优化,而 Object.defineProperty 已经不再是优化重点
 */

let data = {
  stage: 'Git',
  course: {
    title: 'Proxy VS Object.defineProperty',
    author: ['Bingo']
  }
}

/**
 * 数组重写思路
 */
// const arrExtend = Object.create(Array.prototype)
// const arrMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
// arrMethods.forEach(method => {
//   const oldMethod = Array.prototype[method]
//   const newMethod = function(...args) {
//     oldMethod.apply(this, args)
//     console.log(`${method} 方法被执行了`)
//   }
//   arrExtend[method] = newMethod
// })
// Array.prototype = Object.assign(Array.prototype, arrExtend)

/**
 * 整体实现思路:
 * 对于数据键值为基本类型的情况,我们使用 Object.defineProperty
 * 对于键值为对象类型的情况,继续递归调用 observe 方法
 * 并通过 Proxy 返回的新对象对 data[key] 重新赋值,这个新值的 getter 和 setter 已经被添加了代理
 */
const observe = data => {
  if (!data || Object.prototype.toString.call(data) !== '[object Object]') return

  Object.keys(data).forEach(key => {
    let currentValue = data[key]
    // 事实上 proxy 也可以对函数类型进行代理。这里只对承载数据类型的 object 进行处理,读者了解即可。
    if (typeof currentValue === 'object') {
      observe(currentValue)
      data[key] = new Proxy(currentValue, {
        set(target, property, value, receiver) {
          // 因为数组的 push 会引起 length 属性的变化,所以 push 之后会触发两次 set 操作,我们只需要保留一次即可,property 为 length 时,忽略
          // 第一次:[Array(1), '1', 'Mia', Proxy]
          // 第二次:[Array(2), 'length', 2, Proxy]
          if (property !== 'length') console.log(`[Proxy] setting ${key} value now, setting value is`, value)
          return Reflect.set(target, property, value, receiver)
        }
      })
    } else {
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get() {
          console.log(`[Object.defineProperty] getting ${key} value now, getting value is:`, currentValue)
          return currentValue
        },
        set(newValue) {
          currentValue = newValue
          console.log(`[Object.defineProperty] setting ${key} value now, setting value is`, currentValue)
        }
      })
    }
  })
}

observe(data)

data.stage = 'GitHub' // [Object.defineProperty] setting stage value now, setting value is GitHub
data.course.author.push('Mia') // [Proxy] setting author value now, setting value is Mia