Skip to content

异步跟新

问题说明

上一篇已经完成了依赖收集与派发跟新, 但是存在一个问题就是, 跟新是同步的, 也就是当在页面中多次调用vm.xxxx=xxx时候, 每调用一次都会更新一次页面

更新页面是通过updateComponent完成的(也就是执行_render函数) ,即watcher中的get函数,而更新的逻辑是同步代码,所以为同步更新

异步更新队列

js
import { popTarget, pushTarget } from "./dep";
import { queueWatcher } from "./scheduler";

let uid = 0;
class Watcher {
  constructor(vm, exprFn, cb, options) {
    this.vm = vm;
    this.exprFn = exprFn; // 组件真是的渲染函数
    this.cb = cb; // 回调函数
    this.options = options; // 是否为渲染watcher
    this.uid = uid++; // 每一个组件都有一个渲染watcher, 这是唯一标识
    this.depsIds = new Set(); // 一个watcher上面有多个dep, 且不重复
    this.deps = [];

    this.get(); // 首先就让组件渲染函数执行
  }

  run() {
    this.get();
  }

  // 添加dep
  addDep(dep) {
    const depId = dep.uid;
    if (!this.depsIds.has(depId)) {
      this.depsIds.add(depId);
      this.deps.push(dep); // 将当前watcher与dep建立1对多的关系
      dep.subs.push(this); // dep与watcher也建立1对多的关系
    }
  }
  // 更新页面
  update() {
    queueWatcher(this);
  }

  get() {
    pushTarget(this);
    this.exprFn();
    popTarget();
  }
}

export default Watcher;

schedule

js
import { nextTick } from "../utils/index";

let queue = [];
const watcherIds = new Set();
let warting = false;

export function queueWatcher(watcher) {
  if (!watcherIds.has(watcher.uid)) {
    queue.push(watcher);
    watcherIds.add(watcher.uid);
  }

  if (warting === false) {
    warting = true;
    nextTick(flushQueue);
  }
}

// 清空渲染队列
function flushQueue() {
  queue.forEach((w) => w.run());
  queue = [];
  watcherIds.clear();
  warting = false;
}

nextTick

异步跟新的核心就是nextTick函数, 在浏览器事件循环中 ,异步任务执行时机总在同步任务执行完后才会执行, 异步任务又存在宏任务与微任务,微任务的优先级比宏任务高

Vue2中的nextTick函数做了很多兼容, 依次是 Promise -> MutationObserve -> setImmediate -> setTimeout

js
// nextTick
let callbackQueue = [];
let pending = false;

function flushCallBackQueue() {
  callbackQueue.forEach((cb) => cb());
  callbackQueue = [];
  pending = false;
}

let timerFunc;

if (Promise) {
  timerFunc = () => {
    Promise.resolve().then(flushCallBackQueue);
  };
} else if (MutationObserver) {
  let mutationObserve = new MutationObserver(flushCallBackQueue);
  let text = document.createTextNode("1");
  mutationObserve.observe(text, { characterData: true });
  timerFunc = () => {
    text.data = "2";
  };
  // @ts-ignore
} else if (typeof setImmediate === "object") {
  timerFunc = () => {
    // @ts-ignore
    setImmediate(flushCallBackQueue);
  };
} else {
  timerFunc = setTimeout(flushCallBackQueue);
}

// nextTick方法永不会调用, 内部也会调用, 应该有先后顺序, 因此需要用队列进行管理
export function nextTick(cb) {
  callbackQueue.push(() => {
    cb();
  });

  console.log(callbackQueue);
  if (pending === false) {
    pending = true;
    timerFunc();
  }
}

vm.$nextTick(cb)

js
// init.js
import { mergeOptions, nextTick } from "./utils/index";
export function initMixin(Vue) {
	// Vue.prototype._init = .....
	// Vue.prototype.$mount = ....
	
	Vue.prototype.$nextTick = nextTick;
}