什么是 tick js 引擎执行过程的运行机制 nextTick 是什么 数据从依赖收集到派发更新用到了 nextTick 机制,数据的变化到 DOM 的重新渲染是一个异步的过程,发生在下一个 tick nextTick 把要执行的任务都推到一个队列中,统一在下一个 nextTick 中执行 数据更改触发渲染 watcher 的 update,渲染 watcher 的 flushScheduler…

什么是 tick

js 引擎执行过程的运行机制

nextTick 是什么

数据从依赖收集到派发更新用到了 nextTick 机制,数据的变化到 DOM 的重新渲染是一个异步的过程,发生在下一个 tick

nextTick 把要执行的任务都推到一个队列中,统一在下一个 nextTick 中执行

数据更改触发渲染 watcher 的 update,渲染 watcher 的 flushSchedulerQueue(flushSchedulerQueue 做了队列排序,2、队列遍历 3、状态恢复等操作) 是在 nextTick 后,所以数据重新渲染是异步的

常见的一个场景:在服务端接口获取数据的时候,数据做了修改,进而引发了 DOM 重新渲染,如果想要获取变化后的 DOM,就需要在 nextTick 后执行,如下例所示

getData().then((res) => {
  this.result = res.data;
  this.$nextTick(() => {
    let $dom = this.$refs.dom;
    let html = $dom.innerHTML;
    console.log(html);
  });
});

在使用 nextTick 后发生了什么

  1. 把传入的回调函数压入 callbacks 数组中,根据 useMacroTask 是 true 执行 macroTimerFunch(宏观任务) ,为 false 执行 microTimerFunc(微观任务)
    1. useMacroTask 默认是 false,所以默认执行 microTimerFunc
    2. 如果支持原生 promise,就走 promise.then,如果不支持直接走 macroTimerFunch
  2. useMacroTask 的状态会在函数执行过程中对数据的任意修改中改变,由 false 变成 true,然后执行 macroTimerFunch
    1. 如果支持原生的 setImmediate,就走 setImmediate,如果支持原生的 MessageChannel,就走 postMessage,如果前面两个都不支持,就走 setTimeout(()=>{},0)
  3. 为什么不直接在 nextTick 中执行会动啊,是为了保证同一个 tick 多次执行 nextTick,不会开启多个异步任务,把这些异步任务都压成一个同步任务,在下一个 tick 执行完毕
  4. 当 nextTick 不传回调函数的时候,返回一个 promise,务状态直接变成 resolve,可以用 then

nextTick 源码

// src/core/util/next-tick.js
import { noop } from 'shared/util';
import { handleError } from './error';
import { isIOS, isNative } from './env';

const callbacks = [];
let pending = false;
// 对callbacks遍历,然后执行响应的回调
// 使用callbacks队列执行回调而不是直接执行回调的原因是,保证在同一个tick内多次执行nextTick,不会开启多个异步任务
// 而把这些异步任务都压成一个同步任务,在下一个tick执行完毕
function flushCallbacks() {
  pending = false;
  const copies = callbacks.slice(0);
  callbacks.length = 0;
  for (let i = 0; i < copies.length; i++) {
    copies[i]();
  }
}
// 申明了microTimerFunc和macroTimerFunc2个变量
// 这两个变量分别指向microtask 的函数和 macrotask 的函数
let microTimerFunc;
let macroTimerFunc;
let useMacroTask = false;
// 对于macrotask(宏观任务)
// 检查是否支持原生setImmediate,这是一个高版本IE和Edge才支持的特性
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  macroTimerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else if (
  // 是否支持原生的MessageChannel,
  typeof MessageChannel !== 'undefined' &&
  (isNative(MessageChannel) ||
    // PhantomJS
    MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = flushCallbacks;
  macroTimerFunc = () => {
    port.postMessage(1);
  };
} else {
  // 如过setImmediate和MessageChannel都不支持,那么就降级为setTimeout 0
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

// 对于microtask(微观任务)
// 判断是否支持原生Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve();
  microTimerFunc = () => {
    p.then(flushCallbacks);
    if (isIOS) setTimeout(noop);
  };
} else {
  // 如果不支持,直接指向macrotask(宏观任务)
  microTimerFunc = macroTimerFunc;
}
// 确保函数执行过程中对数据的任意修改,触发变化执行nextTick的时候强制走macroTimerFunc
// 比如v-on绑定一个dom事件,会强制走macrotask(宏观任务)
export function withMacroTask(fn: Function): Function {
  return (
    fn._withTask ||
    (fn._withTask = function() {
      useMacroTask = true;
      const res = fn.apply(null, arguments);
      useMacroTask = false;
      return res;
    })
  );
}
// 把传入的回调函数压入callbacks数组中,最后一次性的根据useMacroTask 条件执行macroTimerFunch还是microTimerFunc
// 而他们都会在下一个tick执行flushCallbacks
export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    pending = true;
    if (useMacroTask) {
      macroTimerFunc();
    } else {
      microTimerFunc();
    }
  }
  // 当nextTick不传cb参数的时候,任务状态直接变成resolve,可以用then
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise((resolve) => {
      _resolve = resolve;
    });
  }
}

暂无评论