vue2响应式原理--重点

127 阅读3分钟

总体流程

截取抖音上的渡一教育剪辑号的一张图:

image.png

Observer

使用Object.defineProperty将data里的数据循环递归遍历为他们添加访问器属性getter和setter,这是响应式的第一步,数据劫持。当我们访问或修改数据的时候,可以被Vue拦截到,下面是手写Vue2相应式。

      function isObject(val) {
            return typeof val === "object" && val !== null;
          }
      function observer(obj) {
        let key = Object.keys(obj);
        for (let item of key) {
          if (isObject(obj[item])) {
            observer(obj[item]);
          }
          Object.defineProperty(obj, item, {
            get() {
              console.log(`有人读取了属性${item}`);
              return obj[key];
            },
            set(val) {
              obj[key] = val;
              console.log(`有人修改了属性${item},修改为\${val}`);
            },
          });
        }
      }

两个注意点

  1. 这一套循环递归针对属性设置完了,后续再新加属性、删除属性,Vue监测不到。解决办法就是set和delete前面加个$
  2. 上面这个是对于对象的监控,不是数组!对于数组,Vue是这样搞的:
image.png

Vue把Array的方法重写了,搞了一个自己的对象,可以通知Vue数组发生变化了,为了让开发者使用Array的一些方法,把自己的__proto__指向Array.prototype了。但是还是有问题,单独对某个下标赋值,监控不到。

dep

data返回的是一个对象,dep的作用是为data的每一个属性添加一个dep,dep记录拿一些函数用到了这个属性,举个例子:

            const obj = {
                a: 1,   //dep=[render1,watch1,watch2]
                b: 2,   //dep=[render2,watch3]
                c: {
                  a: 1,
                  b: 2,
                },
              };

依赖收集

在Observer把对象变成响应式对象的时候,为每一个属性添加一个dep,为数组也添加了dep。当读取响应式对象的某个属性的时候,会收集依赖,哪个render或者watch、computed用到了这个属性。

派发更新

当改变属性的时候,就会去通知用到我的render或者watch、computed,数据发生改变了。

watcher

首次页面渲染:每个组件都有一个watch,里面包含了render函数,让render函数运行,会用到数据,被dep记录。watcher会把render先运行一次,收集依赖。

数据发生变化:数据变了,dep去通知用到自己那个属性的render,watch,computed函数。重新运行,重新渲染。

感觉watcher就是把render、watch、computed包起来,dep才可以知道谁在用它。

scheduler(调度器)

考虑一个问题: 如果数据一变,页面就立马去渲染,那效率太低了!所以Vue通过nextTick将用到了watcher(数据变化了的那些dep收集到的watcher)放到了微队列。主线程同步代码执行结束的时候拿出来运行

注意

1.微队列里面不只有watcher,还有一些微任务。

2.重新执行watcher,运行render、watch、computed函数,页面渲染,又用到了响应式数据,重新收集依赖,便于下次派发更新,循环往复。

检验:为什么Proxy比defineProperty好?

1.defineProperty是对象的基本方法,无法对数组的push,shift等方法监听,他只能配置set和get。但是proxy是对整个对象做代理,而且它可以检测到对象属性的删除添加,提供了更多的陷阱函数

2.defineProperty通过深度遍历,在created生命钩子之前就结束了,无法检测到对象的新增和删除,而且数组的方法那七个方法也检测不到。但是proxy对整个对象代理,可以检测到。