说到vue 双向绑定原理是少不了的知识点,对于vue新版本有关注的小伙伴应该知道尤大大已经宣称在3.0中 双向绑定将会使用Proxy来代替2.x版本的Object.defineProperty,那么我们来看一下Proxy对比defineProperty优势在哪

首先这两种都是基于数据劫持实现的双向绑定

什么是数据劫持

当访问或者设置对象的属性的时候,触发相应的函数,并且返回或者设置属性的值。vue通过Object.defineProperty来劫持对象属性的getter和setter操作,当数据发生变化时发出通知。如下代码,实现了一个Observer

const data= {
    name: 'xiaoming',
    sex: '男'
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(key => {
  let val = data[key]
  Object.defineProperty(data, key, {
    enumerable: true, // 该属性可被枚举
    configurable: true, // 该属性可以被删除以及属性描述符可以被改变
    get () {
      console.log(`属性值${data}当前值为${val}`)
      return val
    },
    set (newValue) {
      console.log(`监听到属性值${data}由${val}变为${newValue}`)
      val = newValue
    }
  })
});

data.name // 属性值name当前值为xiaoming
data.name = 'huahua' // 监听到属性值name由xiaoming变为huahua
data.sex // 属性值sex当前值为男
data.sex = '女' // 监听到属性值sex由男变为女

数据劫持的优势

1.不需要进行显示调用,vue的双向绑定原理就是通过数据劫持+发布订阅来实现的,比如angular的脏检查需要通过显示调用markForCheck,react则需要通过setState来进行显示调用

2.通过属性的劫持可以精准获得变化的内容,这部分不需要额外的diff操作,减少性能消耗

vue实现双向绑定

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。这里需要注意,递归的时候由于属性的值可能也是一个对象,在递归处理对象属性值的时候 ,递归循环引用对象很容易出现递归爆栈问题,在vue中已经通过定义ob对象记录已经被设置过setter和getter方法的对象,避免了此问题,但如果需要扩展对象,必须手动给新属性设置setter和getter方法,这就是为什么不在data中预先声明好的属性无法进行双向绑定,需要通过this.$set()来设置

this.$set(this.data.key, value) !注意,这是1.0的写法

this.$set(this.data,”key”,value) !这个是2.0的写法

2.实现一个订阅者Watcher,作为连接Observer和Compile的桥梁,可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

Object.defineProperty缺陷

1.性能:上栗代码通过遍历对象的属性进行监听,但是属性值也是对象就需要深度遍历了,这时候显然能够劫持完整对象更好

2.无法监听数组:将上文中我们的示例代码 属性值改为数组,数组也算是一种特殊的对象,下标其实就是对象的属性,理论上是可以通过Object.defineProperty来处理的,那尤大大为什么没有采用这种方式呢,猜测源于数组的使用场景,数组的主要操作场景是遍历,如果每一个元素都挂载set和get方法,会产生巨大性能消耗,而且数组下标变化频繁,操作方法居多,一旦数组长度发生变化,在无法自动检测的状态下,手动更新会是一个相当繁琐的工作

那vue中是如何实现对数组的劫持呢,肯定不能直接篡改Array.prototype 对象,因为会影响所有的数组实例,尤大大通过原型继承得到一个新的原型对象,在此基础上,劫持了7种常用的数组操作进行了重写,分别是push() 、pop() 、shift()、 unshift() 、splice() 、sort()、 reverse(),Vue.set()对于数组的处理其实就是调用了splice方法

Proxy的优势

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,可以这样认为,Proxy是Object.defineProperty的全方位加强版

1.Proxy可以直接监听对象而非属性,并返回一个新对象

let obj = {
  a: 1,
  b: 2,
}

const p = new Proxy(obj, {
  get(target, key, value) {
    if (key === 'c') {
      return '我是自定义的一个结果';
    } else {
      return target[key];
    }
  },

  set(target, key, value) {
    if (value === 4) {
      target[key] = '我是自定义的一个结果';
    } else {
      target[key] = value;
    }
  }
})

console.log(obj.a) // 1
console.log(obj.c) // undefined
console.log(p.a) // 1
console.log(p.c) // 我是自定义的一个结果

obj.name = '李白';
console.log(obj.name); // 李白
obj.age = 4;
console.log(obj.age); // 4

p.name = '李白';
console.log(p.name); // 李白
p.age = 4;
console.log(p.age); // 我是自定义的一个结果

2.Proxy可以直接监听数组的变化

let obj = [1,2,3,4]

const p = new Proxy(obj, {
  get(target, key, value) {
    if (key === '2') {
      return '拦截获取';
    } else {
      return target[key];
    }
  },

  set(target, key, value) {
    if (value === 5) {
      target[key] = '拦截设置';
    } else {
      target[key] = value;
    }
  }
})

console.log(p[2]) // 拦截获取
p[0] = 5
console.log(p[0]) // 拦截设置

当然Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。想要进一步了解的可以移步Proxy

Logo

前往低代码交流专区

更多推荐