fangchaolong
文章57
标签24
分类10
defineProperty和proxy的区别

defineProperty和proxy的区别

vue3.0中,响应式数据部分弃用了 Object.defineProperty,使用 Proxy 来代替它。本文将主要通过以下方面来分析为什么vue选择弃用 Object.defineProperty。


Object.defineProperty(obj,"name",{
  set:function(val){
      if(var==='lisi'){
            console.log("誓死不叫这么土的名字")
      }else{
              objCopy.name = val
      }
  },
  get:function(){
    return objCopy.name.replace(/san/,'先生')
   }
})
这个对每个data中的属性进行遍历绑定。
而,
var objCopy = new Proxy(obj,{
          get:function(target,key){
                if(key=='name'){
                      return target[key].replace(/san/,'先生');
                }
          },
          set:function(target,key,value){
                if(key == 'name'){
                    value=='lisi'?target[key]:target[key] = value;
                }else{
                    target[key] = value;
                }
          }
   })

get,set方法的target参数即obj对象,key参数是要操作的属性,value参数是赋值动作时的值。
此后,可通过objCopy.name的方式访问obj的name属性,也可以通过objCopy.name=’lisi’的方式设置obj的name属性,而且不用给每个属性都设置set,get方法,不会触发循环调用,很是爽的。
这样我们通过对objCopy对象的操作就实现了对obj对象的操作,objCopy对象就是obj对象的代理对象。
vue3.0使用了Proxy替换了原先遍历对象使用Object.defineProperty方法给属性添加set,get访问器的笨拙做法。
也就是说不应遍历了,而是直接监控data对象了。

Object.defineProperty VS Proxy

1. Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。
由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性。而 Proxy 可以直接代理对象。

2. Object.defineProperty对新增属性需要手动进行Observe。
由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。

也正是因为这个原因,使用vue给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。

下面看一下vue的 set 方法是如何实现的,set 方法定义在 core/observer/index.js ,下面是核心代码。



/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array | Object, key: any, val: any): any {
  // 如果target是数组,且key是有效的数组索引,会调用数组的splice方法,
  // 我们上面说过,数组的splice方法会被重写,重写的方法中会手动Observe
  // 所以vue的set方法,对于数组,就是直接调用重写splice方法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 对于对象,如果key本来就是对象中的属性,直接修改值就可以触发更新
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // vue的响应式对象中都会添加了__ob__属性,所以可以根据是否有__ob__属性判断是否为响应式对象
  const ob = (target: any).__ob__
  // 如果不是响应式对象,直接赋值
  if (!ob) {
    target[key] = val
    return val
  }
  // 调用defineReactive给数据添加了 getter 和 setter,
  // 所以vue的set方法,对于响应式的对象,就会调用defineReactive重新定义响应式对象,defineReactive 函数
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

在 set 方法中,对 target 是数组和对象做了分别的处理,target 是数组时,会调用重写过的 splice 方法进行手动 Observe 。

对于对象,如果 key 本来就是对象的属性,则直接修改值触发更新,否则调用 defineReactive方法重新定义响应式对象。

如果采用 proxy 实现,Proxy 通过 set(target, propKey, value, receiver) 拦截对象属性的设置,是可以拦截到对象的新增属性的。

不止如此,Proxy 对数组的方法也可以监测到,不需要像上面vue2.x源码中那样进行 hack。

3. Proxy支持13种拦截操作,这是defineProperty所不具有的

  1. get(target, propKey, receiver):拦截对象属性的读取,比如 proxy.foo 和 proxy[‘foo’]。

  2. set(target, propKey, value, receiver):拦截对象属性的设置,比如 proxy.foo = v 或 proxy[‘foo’] = v ,返回一个布尔值。

  3. has(target, propKey):拦截 propKey in proxy 的操作,返回一个布尔值。

  4. deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作,返回一个布尔值。

  5. ownKeys(target):拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) 、for…in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。

  6. getOwnPropertyDescriptor(target, propKey):拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。

  7. defineProperty(target, propKey, propDesc):拦截 Object.defineProperty(proxy, propKey, propDesc) 、Object.defineProperties(proxy, propDescs) ,返回一个布尔值。

  8. preventExtensions(target):拦截 Object.preventExtensions(proxy) ,返回一个布尔值。

  9. getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy) ,返回一个对象。

  10. isExtensible(target):拦截 Object.isExtensible(proxy) ,返回一个布尔值。

  11. setPrototypeOf(target, proto):拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

  12. apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args) 、proxy.apply(…) 。

  13. construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args) 。

4. 新标准性能红利

Proxy 作为新标准,长远来看,JS引擎会继续优化 Proxy ,但 getter 和 setter 基本不会再有针对性优化。

5. Proxy兼容性差

可以看到,Proxy 对于IE浏览器来说简直是灾难。

并且目前并没有一个完整支持 Proxy 所有拦截方法的Polyfill方案,有一个google编写的 proxy-polyfill 也只支持了 get,set,apply,construct 四种拦截,可以支持到IE9+和Safari 6+。

总结

  1. Object.defineProperty 对数组和对象的表现一直,并非不能监控数组下标的变化,vue2.x中无法通过数组索引来实现响应式数据的自动更新是vue本身的设计导致的,不是 defineProperty 的锅。

  2. Object.defineProperty 和 Proxy 本质差别是,defineProperty 只能对属性进行劫持,新增属性需要手动 Observe 的问题。

  3. Proxy 作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的polifill方案。

无以生计,卖文苟延

微信
支付宝