双向绑定流程:

  1. new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生在 Observer 中
  2. 同时对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 Compile 中
  3. 同时定义一个更新函数和 Watcher ,将来对应数据变化时 Watcher 会调用更新函数
  4. 由于 data 的某个 key 在一个视图中可能出现多次,所以每个 key 都需要一个管家 Dep 来管理多个 Watcher
  5. 将来 data 中数据一旦发生变化,会首先找到对应的 Dep ,通知所有 Watcher 执行更新函数

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

原理图:
image.png
observer 用来实现对每个 vue 中的 data 中定义的属性循环用 Object.defineProperty() 实现数据劫持,以便利用其中的 setter 和 getter ,然后通知订阅者,订阅者会触发它的 update 方法,对视图进行更新。

执行初始化,对 data 执行响应化处理:

  1. class Vue{
  2. constructor(options) {
  3. this.$options = options;
  4. this.$data = options.data;
  5. // 对 data 选项做响应式处理
  6. observe(this.$data);
  7. // 代理 data 到 vm 上
  8. proxy(this);
  9. // 执行编译
  10. new Compile(options.el, this);
  11. }
  12. }

对 data 选项执行响应化具体操作:

  1. function observe(obj) {
  2. if (typeof obj !== 'object' || obj == null) {
  3. return;
  4. }
  5. new Observer(obj);
  6. }
  7. class Observer {
  8. constructor(value) {
  9. this.value = value;
  10. this.walk(value);
  11. }
  12. walk(obj) {
  13. Object.keys(obj).forEach((key) => {
  14. defineReactive(obj, key, obj[key]);
  15. })
  16. }
  17. }

编译Compile:对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数。

observer 实现,主要是给每个 vue 的属性用 Object.defineProperty() 。

  1. function defineReactive(obj, key, val) {
  2. var dep = new Dep();
  3. Object.defineProperty(obj, key, {
  4. get: function() {
  5. // 添加订阅者 watcher 到主题对象 Dep
  6. if (Dep.target) {
  7. // JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
  8. dep.addSub(Dep.target);
  9. }
  10. return val;
  11. },
  12. set: function(newVal) {
  13. if (newVal === val) return;
  14. val = newVal;
  15. console.log(val);
  16. // 作为发布者发出通知
  17. dep.notify(); // 通知后 dep 会循环调用各自的 update 方法更新视图
  18. }
  19. })
  20. }
  21. function observe(obj, vm) {
  22. Object.keys(obj).forEach(function(key) {
  23. defineReactive(vm, key, obj[key]);
  24. })
  25. }

简单说一下 Vue2.x 响应式数据原理

Vue 在初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher),如果属性发生变化会通知相关依赖进行更新操作。

Vue3.x 响应式数据原理

Vue3.x 改用 Proxy 替代 Object.defineProperty 。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

监测数组的时候可能触发多次 get/set ,那么如何防止触发多次呢?
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。