Proxy与Object.defineProperty的对比

1,216 阅读3分钟

proxy

定义:用于修改某些操作的默认行为。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

解析:new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

let data = [1,2,3]
let p = new Proxy(data, {
  get(target, key) {
    console.log('获取值:', key)
    return target[key]
  },
  set(target, key, value) {
    console.log('修改值:', key, value)
    target[key] = value
    return true
  }
})

p.push(4)
输出:
获取值: push
获取值: length
修改值: 3 4
修改值: length 4

数组和对象都可以直接触发getter和setter, 但是数组会触发两次,因为获取push和修改length的时候也会触发。

缺陷:

  • 属性的新加或者删除也无法监听;
  • 数组元素的增加和删除也无法监听。

Object.defineProperty

原理:该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

Object.defineProperty(obj, prop, descriptor) 参数:

  • obj: 要在其上定义属性的对象。
  • prop: 要定义或修改的属性的名称。
  • descriptor: 将被定义或修改的属性的描述符。
const o = {
    name:'坚持就是胜利'
}
//obj指的就是o,prop指的就是o.name

Object.defineProperty监听对象变化

需要解决的问题:

  • obj对象有多个属性,可能需要循环添加Object.defineProperty里
  • obj的属性可能是对象或数组,可能需要递归
  • 用户可能给obj赋值新的属性,需要单独处理 解决上面问题: 定义一个对象
var obj = {
    name:'坚持就是胜利',
    age:12,
    love:['睡觉','吃饭']
}
  • 首先实现响应式函数defineProperty
function defineProperty(obj,key,val){
    observer(newVal)
    Object.defineProperty(obj,key,{
       get(){  // 读取方法
          return val
       },
       set(newVal){  // 赋值监听方法
         if(newVal === val)return
         observer(newVal)
         val = newVal
       }
    })
}
  • 其次做个遍历函数observer
function observer(obj){
   if(typeof obj !== 'object' || obj == null){
      return
   }
   for(const key in obj){
      // 给对象中的每一个方法都设置响应式
      defineProperty(obj,key,obj[key])
   }
}

Object.defineProperty对象的方法式监听不到数组的变更的, 需要重写Array的原型方法

const orginalproto = Array.prototype;
const arrayProto = Object.create(orginalProto);//先克隆一份Array的原型
const methodsToPatch = [
   'push',
   'pop',
   'shift',
   'unshift',
   'splice',
   'sort',
   'reverse'
]
methodsToPatch.forEach(method =>{
  arrayProto[method]  = function(){
     // 执行原始操作
     orginalProto[method].apply(this,arguments)
     console.log('监听赋值 成功',method)
  }
})

原理就是重写数组的七个原始方法,当使用者执行这些方法时,就可以监听到数据的变化。

function observer(obj){
   if(typeof obj !== 'object' || obj == null){
      return
   }
   if(Array.isArray(obj)){
   // 如果是数组,重写原型
      obj._proto_ = arrayProto
      // 传入的数据可能是多维度的,也需要执行响应式的
      for(let i = 0;i<obj.length;i++){
        observer(obj[i])
      }
   }else {
     for(const key in obj){
       // 给对象中的每一个方法都设置响应式
       definePrototype(obj,key,obj[key])
     }
   }
}

区别

  • Object.defineProperty

    • 只能监听对象(Object),不能监听数组的变化,无法触发push, pop, shift, unshift,splice, sort, reverse,必须重写数组方法。
    • 必须遍历对象的每个属性
    • 只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象
  • Proxy

    • Proxy可以直接监听对象而非属性
    • Proxy直接可以劫持整个对象,并返回一个新对象。
    • Proxy可以直接监听数组的变化
    • Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。