vue双向数据绑定原理

93 阅读4分钟

1. MVVM模型

MVVM,是Model-View-ViewModel简写,Model代表数据模型,View代表视图,ViewModel是View和Model之间的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化会通知ViewModel层更新数据

2. Vue双向数据绑定原理

vue2

通过数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图

Object.defineProperty语法

Object.defineProperty(obj, prop, descriptor)

参数说明:

  1. obj:要定义属性的对象
  2. prop:要定义或修改的属性名称
  3. descriptor:要定义或修改属性所拥有的特性
  • descriptor

    • 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false

    • 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中,在枚举对象属性时会被枚举到(for...in或 Object.keys方法), 默认为 false

    • value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined

    • 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。 默认为 false

数组

对于数组响应式处理,是通过覆盖数组原型的方法,扩展7个变更方法(push,pop,shift,unshift,splice,sort,reverse)可以额外的做更新通知,做出响应

由于JavaScript的限制,vue不能检测以下变动数组:

  • 利用索引直接修改数组,例如:vm.items[indexOfItem] = newValue,可通过Vue.set()
  • 修改数组长度时,例如:vm.items.length = newLength,可通过splice()

实际上Object.defineProperty是可以检测到数组索引的变化的,vue没有使用这个方式去监听数组索引的变化,是因为性能消耗太大,于是在性能与用户体验之间做了取舍,原因非常明显,在初始的过程中没有循环对所有数组索引监听,但是开发者需要监听哪个索引。Vue.set就帮你监听哪个,核心还是Object.defineProperty。只是尽可能的避免了无用的数组索引监听

Object.defineProperty虽然能检测索引的变化,但的确是监听不到数组的增加或删除的, Vue的解决方案,就是重写了数组的原型,更准确的表达是拦截了数组的原型

Object.defineProperty不能检测对象的新增或删除

vue3

Vue3的响应式是基于ES6的Proxy代理来实现的

实现原理:

  • 通过Proxy代理:拦截对象属性的操作,包括属性增删改查
  • 通过Reflect反射:对源对象的属性进行操作

image.png

Proxy

Proxy用来创建对象代理,第一个参数target为要拦截的目标对象,第二个参数handler为配置对象,指定拦截后的行为, get和set等方法就要写在配置对象中。

ProxyES6 新增的一个构造函数,用来创建一个 目标对象的代理对象,拦截对原对象的所有操作;用户可以通过注册相应的拦截方法来实现对象操作时的自定义行为

目前 Proxy 支持的拦截方法包含一下内容:

  • get(target, propKey, receiver):拦截对象属性的读取操作;
  • set(target, propKey, value, receiver):拦截对象属性的赋值操作;
  • apply(target, thisArg, argArray):拦截函数的调用操作;
  • construct(target, argArray, newTarget):拦截对象的实例化操作;
  • has(target, propKey):拦截 in 操作符;
  • deleteProperty(target, propKey):拦截 delete 操作符;
  • defineProperty(target, propKey, propDesc):拦截 Object.defineProperty 方法;
  • getOwnPropertyDescriptor(target, propKey):拦截 Object.getOwnPropertyDescriptor 方法;
  • getPrototypeOf(target):拦截 Object.getPrototypeOf 方法;
  • setPrototypeOf(target, proto):拦截 Object.setPrototypeOf 方法;
  • isExtensible(target):拦截 Object.isExtensible 方法;
  • preventExtensions(target):拦截 Object.preventExtensions 方法;
  • enumerate(target):拦截 for...in 循环;
  • ownKeys(target):拦截 Object.getOwnPropertyNamesObject.getOwnPropertySymbolsObject.keysJSON.stringify 方法。

Object,defineProperty 比起来,有非常明显的优势:

  • 拦截操作更加多样
  • 拦截定义更加直接
  • 性能更加高效

在 Vue 中体现最为明显的一点就是:Proxy 代理对象之后不仅可以拦截对象属性的读取、更新、方法调用之外,对整个对象的新增、删除、枚举等也能直接拦截,而 Object.defineProperty 只能针对对象的已知属性进行读取和更新的操作拦截。

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。