Vue2核心原理(简易版)-异步更新

791 阅读2分钟

Vue2核心原理(简易版)-异步更新

什么是异步更新?

首先什么是异步?JS是单线程执行的,但是我们执行某些操作的时候并不是立即返回结果,比如文件IO、Ajax、定时器等等,我们不可能让代码傻乎乎的在那里等它们,所以JS用了事件循环这个机制,来让这些操作异步执行。至于更新很好理解,放到数据上就是数据设置新的值,放在页面上就是页面重新渲染,在我们今天讲述的vue.js里,就是更新我们组件的state。

为什么要异步更新?

state更新会让模版重新编译,如果更新一次就重新编译、比较虚拟dom、渲染dom,会很耗费时间,也很浪费性能,我们并不需要vue.js在状态改变的每一次都即时改变页面的样子。所以我们把每一轮的能影响到页面更新的逻辑和行为都收集起来,放到最后去执行,筛去重复的部分,大大优化性能。

vue.js异步更新如何实现

首先,前面的内容讲到的依赖收集,每一个vue.js的state都是一个依赖项,它们有自己相对应的watchers,当自身发生改变,就会通知订阅它的观察者们需要更新了。那么在你每一轮的对state的逻辑操作过后,就会有一个调度器(scheduler)把所有的观察者更新任务不重复的添加进一个异步更新队列,丢入一个nextTick微任务中,等到宏任务执行完成,这个微任务中的所有观察者更新任务就会一起执行。

  • 调度器的具体实现
// observer/scheduler.js
import { nextTick } from "../utils";

let queue = [];
let has = {}; // 做列表的 列表维护存放了哪些watcher

// 动画  滚动的频率高,节流 requestFrameAnimation
function flushSchedulerQueue(){
    for(let i =0 ; i < queue.length; i++){
        queue[i].run(); // vm.name = 123?
    }
    queue = [];
    has = {};
    pending = false;
}

let pending = false;

// 要等待同步代码执行完毕后 才执行异步逻辑
export function queueWatcher(watcher) { // 当前执行栈中代码执行完毕后,会先清空微任务,在清空宏任务, 我希望尽早更新页面
    const id = watcher.id; // name 和 age的id 是同一个
    if (has[id] == null) {
        queue.push(watcher);
        has[id] = true;
        // 开启一次更新操作  批处理 (防抖)
        if(!pending){
            nextTick(flushSchedulerQueue, 0);
            pending = true;
        }
    }
}
  • nextTick具体实现,vue2兼容了各种浏览器的适配情况,按照微任务的优先级制作了一个timer方法。
// utils.js
const callbacks = [];
function flushCallbacks() {
  callbacks.forEach((cb) => cb());
  waiting = false;
}
let waiting = false;
function timer(flushCallbacks) {
  let timerFn = () => {};
  if (Promise) {
    timerFn = () => {
      Promise.resolve().then(flushCallbacks);
    };
  } else if (MutationObserver) {
    let textNode = document.createTextNode(1);
    let observe = new MutationObserver(flushCallbacks);
    observe.observe(textNode, {
      characterData: true,
    });
    timerFn = () => {
      textNode.textContent = 3;
    };
    // 微任务
  } else if (setImmediate) {
    timerFn = () => {
      setImmediate(flushCallbacks);
    };
  } else {
    timerFn = () => {
      setTimeout(flushCallbacks);
    };
  }
  timerFn();
}

// 微任务是在页面渲染前执行 我取的是内存中的dom,不关心你渲染完毕没有

export function nextTick(cb) {
  callbacks.push(cb); // flushSchedulerQueue / userCallback

  if (!waiting) {
    timer(flushCallbacks); // vue2 中考虑了兼容性问题 vue3 里面不在考虑兼容性问题
    waiting = true;
  }
}

完 🎉

下一讲,watch功能实现