vue Vue $nextTick 深度解析

558 阅读1分钟

Vue.nextTick()定义:在下次dom更新循环结束之后,执行延迟回调

所以从定义上理解,它本身是一个异步更新队列,下面来看一下它的源码

function renderMixin (Vue) {    // install runtime convenience helpers    installRenderHelpers(Vue.prototype);    Vue.prototype.$nextTick = function (fn) {      return nextTick(fn, this)    };    Vue.prototype._render = function () {      var vm = this;      var ref = vm.$options;      var render = ref.render;      var _parentVnode = ref._parentVnode;      if (_parentVnode) {        vm.$scopedSlots = normalizeScopedSlots(          _parentVnode.data.scopedSlots,          vm.$slots,          vm.$scopedSlots        );      }      // set parent vnode. this allows render functions to have access      // to the data on the placeholder node.      vm.$vnode = _parentVnode;      // render self      var vnode;      try {        // There's no need to maintain a stack becaues all render fns are called        // separately from one another. Nested component's render fns are called        // when parent component is patched.        currentRenderingInstance = vm;        vnode = render.call(vm._renderProxy, vm.$createElement);      } catch (e) {        handleError(e, vm, "render");        // return error render result,        // or previous vnode to prevent render error causing blank component        /* istanbul ignore else */        if (vm.$options.renderError) {          try {            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);          } catch (e) {            handleError(e, vm, "renderError");            vnode = vm._vnode;          }        } else {          vnode = vm._vnode;        }      } finally {        currentRenderingInstance = null;      }      // if the returned array contains only a single node, allow it      if (Array.isArray(vnode) && vnode.length === 1) {        vnode = vnode[0];      }      // return empty vnode in case the render function errored out      if (!(vnode instanceof VNode)) {        if (Array.isArray(vnode)) {          warn(            'Multiple root nodes returned from render function. Render function ' +            'should return a single root node.',            vm          );        }        vnode = createEmptyVNode();      }      // set parent      vnode.parent = _parentVnode;      return vnode    };  }

这段代码其实要看的就是return nextTick(fn, this)这个函数, 它放到Mixin很是巧妙,每执行一个都可以声明到执行栈排到堆里面再销毁,互不影响。

下面再看nextTick函数

 function nextTick (cb, ctx) {    var _resolve;    callbacks.push(function () {      if (cb) {        try {          cb.call(ctx);        } catch (e) {          handleError(e, ctx, 'nextTick');        }      } else if (_resolve) {        _resolve(ctx);      }    });    if (!pending) {      pending = true;      timerFunc();    }    // $flow-disable-line    if (!cb && typeof Promise !== 'undefined') {      return new Promise(function (resolve) {        _resolve = resolve;      })    }  }

if (typeof Promise !== 'undefined' && isNative(Promise)) {    var p = Promise.resolve();    timerFunc = function () {      p.then(flushCallbacks);      // In problematic UIWebViews, Promise.then doesn't completely break, but      // it can get stuck in a weird state where callbacks are pushed into the      // microtask queue but the queue isn't being flushed, until the browser      // needs to do some other work, e.g. handle a timer. Therefore we can      // "force" the microtask queue to be flushed by adding an empty timer.      if (isIOS) { setTimeout(noop); }    };    isUsingMicroTask = true;  } else if (!isIE && typeof MutationObserver !== 'undefined' && (    isNative(MutationObserver) ||    // PhantomJS and iOS 7.x    MutationObserver.toString() === '[object MutationObserverConstructor]'  )) {    // Use MutationObserver where native Promise is not available,    // e.g. PhantomJS, iOS7, Android 4.4    // (#6466 MutationObserver is unreliable in IE11)    var counter = 1;    var observer = new MutationObserver(flushCallbacks);    var textNode = document.createTextNode(String(counter));    observer.observe(textNode, {      characterData: true    });    timerFunc = function () {      counter = (counter + 1) % 2;      textNode.data = String(counter);    };    isUsingMicroTask = true;  } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {    // Fallback to setImmediate.    // Techinically it leverages the (macro) task queue,    // but it is still a better choice than setTimeout.    timerFunc = function () {      setImmediate(flushCallbacks);    };  } else {    // Fallback to setTimeout.    timerFunc = function () {      setTimeout(flushCallbacks, 0);    };  }

这里有几个判断,

1、支持Promise的时候那么就用Promise.then的方式来延迟函数调用,Promise通过pending判断 将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数/

2、这里还有一个关键字**MutationObserver ,**MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。

  具体有一下几点变动的监听:

    childList:子元素的变动

    attributes:属性的变动

    characterData:节点内容或节点文本的变动

    subtree:所有下属节点(包括子节点和子节点的子节点)的变动

3,timerFunc函数 所以很明显,当判断出浏览器不支持上面两周方法的时候会通setTimeout延时更新

总结

在调用$nextTick函数的时候,Vue首先 会根据当前浏览器环境优先使用原生的 Promise.then 和 MutationObserver,如果都不支持,就会采用 setTimeout 代替,目的是 延迟函数到 DOM 更新后再使用

腾讯鹅厂首面提到的,结和宏任务,$nextTick遇到promise状况,微任务执行提到的。有兴趣的key看下对应的宏任务微任务介绍_juejin.cn/post/684490… _

promise详解_juejin.cn/post/685457…_

WEBGL探索之路 (二)--webgl场景构建 WEBGL探索之路 (一)--认识webgl js循环中如果操作了原数组会发生什么? 字节头条面试踩坑arguments js函数柯里化 强缓存与协商缓存属性定义集合 React Context 的理解以及应用 react shoudcomponentupdate 停止渲染 vue为什么不需要 深拷贝浅拷贝的十一种方法 import和require的区别比较 vue Vue $nextTick 深度解析 深度揭秘 Promise 微任务注册和执行过程 v8引擎如何执行一段js代码的? http3.0 2.0 1.0区别比较 宏观任务与微观任务详解