欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Object.defineProperty与proxy进行对比

程序员文章站 2022-07-16 17:46:30
...

Object.defineProperty() 和 ES2015 中新增的 Proxy 对象,会经常用来做数据劫持.
数据劫持:在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果.数据劫持最典型的应用------双向的数据绑定

  • Vue 2.x 利用 Object.defineProperty(),并且把内部解耦为 Observer, Dep, 并使用 Watcher 相连
  • Vue 在 3.x 版本之后改用 Proxy 进行实现

Object.defineProperty

先来用Object.defineProperty实现一下对象的拦截。


<html>
<head>
</head>
<body>
<script>

let data = {
  m:234,
  n:[1,34,4,5676],
  h:{
    c:34
  }
}

function observer(data){
  if(typeof data === 'object'){
    Object.keys(data).forEach(key=>{
      defineReactive(data,key,data[key])
    })
  }
}

function defineReactive(obj,key,val){
  Object.defineProperty(obj,key,{
    get(){
      console.log('get',key)
      return val
    },
    set(newVal){
      console.log('set',newVal)
      if(newVal !== val ) val = newVal
    }
  })
}
observer(data) // 给data添加监听
data.m = 111 // 触发defineReactive()里的set()
console.log('data.m的值:', data.m) // 触发defineReactive()里的get()
data.h.c = 111 // 这样不会触发defineReactive()里的set(),但触发了data.h的get()
console.log('data.h.c的值:', data.h.c) // 触发了data.h的get()

</script>
</body>
</html>

Object.defineProperty与proxy进行对比

上面通过遍历data的数据,进行了一次简单的拦截;看似没有问题,但如果我们改变data.h.c是不会触发set钩子的,为什么?因为这里还没有实现递归,所以只拦截了最表面的一层,里面的则没有被拦截。

递归拦截对象


function defineReactive(obj,key,val){
  observer(val)
  Object.defineProperty(obj,key,{
    get(){
      console.log('get')
      return val
    },
    set(newVal){
      console.log('set')
      if(newVal !== val ) val = newVal
    }
  })
}

Object.defineProperty与proxy进行对比

递归拦截,只要在defineReactive函数再调一次observer函数把要拦截的值传给它就行。这样,就实现了对象的多层拦截。但是呢,现在是拦截不到数组的,当我们调用push,pop等方法它是不会触发set钩子的,为什么?因为Object.defineProperty压根就不支持数组的拦截。既然它不支持,那么我们只能拦截它的这些(‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, ‘reverse’)改变自身数据的方法了。

Object.defineProperty数组的拦截


function arrayMethods(){

  const arrProto = Array.prototype

  const arrayMethods = Object.create(arrProto)

  const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

  methods.forEach(function (method) {

      const original = arrProto[method]

      Object.defineProperty(arrayMethods, method, {

          value: function v(...args) {
              console.log('set arrayMethods')
              return original.apply(this, args)

          }

      })

  })
  return arrayMethods
}

以上就是对这些数组的原型方法进行了一个拦截,然后把它覆盖要拦截的数组的原型就行,下面简单修改一下observer


function observer(data){
  if(typeof data === 'object'){
    if(Array.isArray(data)){
      data.__proto__ = arrayMethods()
    }else{
      Object.keys(data).forEach(key=>{
        defineReactive(data,key,data[key])
      })
    }
  }
}

在vue中,还会判断该key有没有__proto__,如果没有就直接把这些方法放到这个key的自身上,如果有就直接覆盖这个__proto__。


function arrayMethods(){

  const arrProto = Array.prototype

  const arrayMethods = Object.create(arrProto)

  const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

  methods.forEach(function (method) {

      const original = arrProto[method]

      Object.defineProperty(arrayMethods, method, {

          value: function v(...args) {
              console.log('set arrayMethods')
              return original.apply(this, args)

          }

      })

  })
  return arrayMethods
}

function observer(data){
  if(typeof data === 'object'){
    if(Array.isArray(data)){
      data.__proto__ = arrayMethods()
    }else{
      Object.keys(data).forEach(key=>{
        defineReactive(data,key,data[key])
      })
    }
  }
}

function defineReactive(obj,key,val){
  observer(val)
  Object.defineProperty(obj,key,{
    get(){
      console.log('get')
      return val
    },
    set(newVal){
      console.log('set')
      if(newVal !== val ) val = newVal
    }
  })
}

observer(data)


以上,就完成了对对象和数组的拦截(说明:vue2.x中的实现会比这里复杂,但大概思路和大概实现是这样),看似辛苦点,换来一个完美的结果挺不错的。但真的是你想的那样吗?试一下调用data.n[1] = xxx,它是不会触发set钩子的,这也是在proxy出现之前,无能无力的,所以在vue中提供了 s e t , set, set,deleteAPI。

proxy

这里就不介绍proxy了,就当你对它有了解过了。直接上代码

  let data = {
  m:234,
  n:[1,34,4,5676],
  h:{
    c:34
  }
}

function defineReactive(obj){
  Object.keys(obj).forEach((key) => {
    if(typeof obj[key] === 'object'){
      obj[key] = defineReactive(obj[key])
    }
  })
  return new Proxy(obj,{
    get(target,key){
      console.log('get')
      return target[key]
    },
    set(target,key,val){
      console.log('set')
      return target[key] = val
    }
  })
}

 data = defineReactive(data)

就这么一点代码就实现了对对象和数组的拦截(说明:这里不是vue3的实现方法,vue3怎么实现的,我还不知道,还没看过它的源码,有兴趣自己去看一下,然后顺便告诉我一下怎么实现的,哈哈哈),Object.defineProperty实现不了的,它能实现;Object.defineProperty实现的了,它也能实现。无论你调push,pop等方法它能拦截,你调data.n[1] = xxx也能拦截,简直不要太爽

最后

这里只是通过对对象和数组的拦截,来体验了一下proxy的威力;proxy能做的远远不止这样。