Object.defineProperty & Proxy

240 阅读2分钟

为什么vue3响应式数据用proxy?

vue3之前vue使用Object.defineProperty进行数据劫持,实现vue的双向绑定。为什么vue3使用Proxy? Object.defineProperty有什么问题呢?

Object.defineProperty

# Object.defineProperty() | MDN

  • 作用:在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
  • 语法:Object.defineProperty(obj, prop, descriptor)

监听对象的一个属性

let name1 = 'rain';
const somebody = {name: 'rain'};
Object.defineProperty(somebody, 'name', {
    get: function() {
      console.log('获取');
      return name1;
    },
    set: function(newVal) {
      console.log('设置', newVal);
      name1 = newVal;
    }
})

console.log(somebody.name); // rain
somebody.name = 'sugar'
console.log(somebody.name); // sugar

监听对象上全部属性

定义defineProperty函数,Observe获取对象全部属性,进行监听。

const defineProperty = (obj, key, val) => {
  Object.defineProperty(obj, key, {
    get: function () {
      return val
    },
    set: function (newVal) {
      val = newVal
    }
  })
}
const Observe=(obj) => {
  Object.keys(obj).forEach(key => {
    defineProperty(obj, key, obj[key])
  })
}

const somebody = {name: 'rain', age: 18, sex: 'female'}
Observe(somebody)
// 触发get
console.log('somebody.name ====', somebody.name);
// 触发set
somebody.name = 'sugar'

监听对象上属性为对象时,递归

const defineProperty = (obj, key, val) => {
  // 重点1
  if (typeof val === 'object' && val !== null) {
    Observe(val)
  }
  Object.defineProperty(obj, key, {
    get: function () {
      return val
    },
    set: function (newVal) {
      // 重点2: 新属性为对象时也加上监听
      if (typeof newVal === 'object' && newVal !== null) {
        Observe(newVal)
      }
      val = newVal
    }
  })
}
const Observe = (obj) => {
  Object.keys(obj).forEach(key => {
    defineProperty(obj, key, obj[key])
  })
}

const somebody = {name: 'rain', age: 18, sex: 'female', family: {mother: 'mama', father: 'baba'}}
Observe(somebody)
// 触发get
console.log('somebody.family.mother ====', somebody.family.mother);
// 触发set
somebody.family.father = {name: 'papa', age: 48}
console.log('somebody.family.father ====', somebody.family.father.name);

对象属性值为数组呢?

const defineProperty = (obj, key, val) => {
  if (typeof val === 'object' && val !== null) {
    Observe(val)
  }
  Object.defineProperty(obj, key, {
    get: function () {
      console.log('获取key为:', key)
      return val
    },
    set: function (newVal) {
      console.log(`${key}属性被修改为${newVal}了`)
      val = newVal
    }
  })
}
const Observe = (obj) => {
  Object.keys(obj).forEach(key => {
    defineProperty(obj, key, obj[key])
  })
}

const somebody = {name: 'rain', age: 18, sex: 'female', family: [
  'mama', 'baba'
]}
Observe(somebody)
// 触发get
console.log('somebody.family ====', somebody.family);
// 触发set
somebody.family = ['mama', 'baba', 'didi']
// 重点:触发不了set方法
somebody.family.pop()
somebody.family.push('sister')

可得:Object.defineProperty不能够监听到数组length的变化

总结

  • 一次只能对一个属性监听,监听多个属性需要遍历
  • 数据层级比较深时,对要用递归,计算量大
  • 新增属性需要手动添加监听,不然没办法监听到
  • 不能够监听到数组length的变化,vue2是做了额外处理

Proxy

所以proxy出现来解决上述问题, proxy | MDN

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法:const p = new Proxy(target, handler)

const somebody = {name: 'rain', age: 18, sex: 'female', family: {people: []}}
const person = [
  '小花', '小美', '小好'
]
const hanlder = {
  get: function(obj, prop) {
    return prop in obj ? obj[prop] : '默认值';
  },
  set: function(obj, prop, value) {
     obj[prop] = value;
     // 表示成功
    return true;
  }
}

const somebodyProxy = new Proxy(somebody, hanlder)

// 触发get
console.log('somebody.name1 ====', somebodyProxy.name1); //  默认值
// 触发set
somebodyProxy.family.people = ['mom', 'dad']
// 数组
const personProxy = new Proxy(person, handler)
// 触发set
personProxy.pop()
personProxy.push('弟弟')
personProxy.push('妹妹')
console.log('somebody ====', somebodyProxy);
/** 打印结果,证明上述操作都可以监听到:
 * somebody.name1 ==== 默认值
 * somebody ==== {
 *   name: 'rain',
 *   age: 18,
 *   sex: 'female',
 *   family: { people: ['mom', 'dad'] }
 * }
 */

总结:

  • 不用遍历
  • 不用递归
  • 新添加属性可以监听
  • 数组也可以监听 上述问题都得以解决,当然要用Proxy啦