Skip to content

数据响应式

vue2.*

采用数据劫持结合发布订阅模式(PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 当把一个普通 js 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。 Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)-> 数据 model 变更的双向绑定效果。

问题

  • Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应
  • Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。

如何监听数组的变化?

vue 是在原型链中将数组给重写了。

vue3.*

vue3放弃了Object.defineProperty的方式采用了proxy来实现数据的响应式。

为什么使用proxy

  1. 解决Object.defineProperty的问题
  2. proxy不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性
  3. proxy的拦截方法多
  4. 新标准性能优化的可能性更大

proxy的问题

  • 只会代理对象的第一层? 判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理,这样就实现了深度观测。简单的说就是给所有层级都加上了proxy。
  • 监测数组的时候可能触发多次get/set,如何防止触发多次? 判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger

双向绑定

v-model的本质其实就是 :value + input方法的语法糖

实现过程

  1. 实现一个监听器 Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
  2. 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
  3. 实现一个解析器 Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

vue2与vue3的 diff算法对比

  • vue2 两端比较算法
  • vue3 是借鉴ivi算法和inferno算法

data 为什么必须是函数

组件中的 data 写成一个函数,数据以函数返回值形式定义。这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。

为什么还需要diff比较

大量的Watcher会导致性能卡顿,少量的Watcher会导致不够精确,所以vue是针对组件的进行Watcher,然后在组件内部再diff监听变化。

为什么没有shouldComponentUpdate

React中是不知道哪里发生变化了的,是通过diff比较,然后变化,在这个时候可能很多组件是不知道发生变化的,所以需要shouldComponentUpdate来优化,而vue是知道变化的,考虑手动优化的价值有限,所以没有引入shouldComponentUpdate