vue3的proxy与vue2的defineProperty的对比

1,764 阅读4分钟

这是我参与更文挑战的第27天,活动详情查看: 更文挑战

前言

vue3已经发布很久了,我们知道vue3在这个版本最大的变化是更改了双向绑定的实现,把defineProperty改成了proxy。那为什么vue要做这样的改变?proxy究竟有何优势?今天好好来说说。

defineProperty

es5新增了defineProperty, 可以对一个对象新增一个属性或者修改某个属性,然后返回该对象。

语法

Object.defineProperty(obj, prop, descriptor)

obj

目标对象

prop

属性名,一般是字符串,也可以是symbol

descriptor

对属性名的属性描述,对象格式

  • value: 属性的值

  • writable: 属性是否可被赋值, 默认是false

  • enumerable: 属性是否可被枚举,默认是false

  • configurable 属性是否可被配置,可删除,默认是false

  • set setter函数,当属性被修改时,会调用此函数,不能与writable同时使用。

  • get getter函数,当属性被访问时,会调用此函数,不能与value同时使用。

例子

// 针对name做getter/setter
let obj = Object.defineProperty({}, 'name', {
  get () {
    return '答案cp3'
  },
  set () {
    console.log('setter函数被调用了!')
  }
})
console.log(obj.name) // 答案cp3
obj.name = 'test' // setter函数被调用了!

可以看到,我们定义了set函数和get函数 获取属性的时候调用get函数, 设置属性的时候调用set函数。

proxy

es6新增了proxy, 实现对象的代理,对基本操作(增删改查等)的拦截。

语法

new Proxy(target, handler)

target

需要代理的目标对象

handler

对代理对象的各种操作行为,对象形式,key是各种函数。

支持以下方法:

  • get getter函数 当属性被访问时,会调用此函数
  • set setter函数 当属性被修改时,会调用此函数
  • has 当使用in操作符时会调用此函数
  • getPrototypeOf 获取原型链,拦截Object.getPrototypeOf
  • setPrototypeOf 设置原型链,拦截Object.setPrototypeOf
  • isExtensible 判断对象是否可以扩展,拦截Object.isExtensible
  • preventExtensions 设置对象不可扩展,拦截Object.preventExtensions
  • getOwnPropertyDescriptor 返回属性的属性描述,拦截Object.getOwnPropertyDescriptor
  • defineProperty 定义属性,拦截Object.defineProperty
  • deleteProperty 删除属性,拦截delete操作符
  • ownKeys 返回一个由可枚举元素组成的数组, 拦截Object.getOwnPropertyNamesObject.getOwnPropertySymbols
  • apply 拦截target是函数的调用
  • construct 拦截new操作符,这要求target必须是函数。

这些方法名和Reflect对象的方法名是一样的,我们可以在内部直接调用Reflect.

例子

// 基本用法
let obj = new Proxy({}, {
  get () {
    return '答案cp3'
  },
  set () {
    console.log('setter函数被调用了!')
  }
})
// 对{}做代理
console.log(obj.name) // 答案cp3
obj.name = 'test' // setter函数被调用了!
// 模拟私有属性
// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
  get (target, key) {
    if (key.startsWith('_')) {
      console.error('不支持访问私有属性!')
      return false
    }
    return Reflect.get(target, key)
  },
  set (target, key, val) {
    if (key.startsWith('_')) {
      console.error('不支持设置私有属性!')
      return false
    }
    return Reflect.set(target, key, val)
  },
  has (target, key) {
    if (key.startsWith('_')) {
      console.error('不支持检测私有属性!')
      return false
    }
    return Reflect.has(target, key)
  }
};
var target = { _name: '我是私有属性', name: '答案cp3'};
var proxy = new Proxy(target, handler);
console.log('_name' in proxy); // false
proxy._name = 123; // false
console.log(proxy._name);  // false


console.log('name' in proxy); // true
proxy.name = 123;    // true
console.log(proxy.name);  // 123

区别

下面来讲讲二者在vue的区别:

  1. defineProperty只能针对定义好的属性进行监听,无法对新增属性或者删除属性监听,而proxy是对整个对象进行监听,新增属性或者删除属性都能响应。

    下面用简单代码展示:

    let name = '答案cp3'
    let obj = { name }
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        Object.defineProperty(obj, key, {
          get () {
            console.log('get函数被调用了')
            return name
          },
          set (val) {
            console.log('set函数被调用了')
            name = val
          }
        })
      }
    }
    obj.name = 'cp3' // set函数被调用了
    obj.age = 18 // 没有打印set函数被调用了
    console.log(obj.name) // get函数被调用了
    console.log(obj.age) // 没有打印
    

    可以看到 新增的age属性是没有defineProperty成功的,所以vue只能提供$set方法来处理。

    对于proxy,就没有这个问题。通过例子来看看

    let name = '答案cp3'
    let obj = new Proxy({ name }, {
      get (target, key) {
        console.log('get函数被调用了')
        return target[key]
      },
      set (target, key, val) {
        console.log('set函数被调用了')
        target[key] = val
      }
    })
    obj.name = 'cp3' // set函数被调用了
    obj.age = 18 // set函数被调用了
    console.log(obj.name) // get函数被调用了
    console.log(obj.age) // get函数被调用了
    
  2. vue的defineProperty没有实现对数组的监听,只提供了push,pop, sort, reserve, splice, unshift, shift等七种方法能触发数组监听。proxy是对整个对象进行监听,新增元素或者删除元素都能响应。

    let arr = new Proxy([1,2,3], {
      get (target, key) {
        console.log('get函数被调用了')
        return Reflect.get(target, key)
      },
      set (target, key, val) {
        console.log('set函数被调用了')
        return Reflect.set(target, key, val)
      }
    })
    console.log(arr[0]) // get函数被调用了
    arr[3] = 4  // set函数被调用了
    arr.push(5)// set函数被调用了
    
  3. 另外对于defineProperty,只有2个get, set方法,而proxy上方法高达13种,基本能满足各种需求。

  4. proxy有个不好的问题,就是兼容性不好,因为它是es6新出的api。

最后

今天简单讲了下vue3的proxy与vue2的defineProperty的对比,proxy能实现的功能比defineProperty多很多,希望今天讲的对你们有帮助~