当前位置: 移动技术网 > IT编程>脚本编程>vue.js > 浅谈Vuejs中nextTick()异步更新队列源码解析

浅谈Vuejs中nextTick()异步更新队列源码解析

2018年02月05日  | 移动技术网IT编程  | 我要评论

恶魔恐侯症,周玲安是处吗,beyond北京演唱会

vue官网关于此解释说明如下:

vue2.0里面的深入响应式原理的异步更新队列

官网说明如下:

只要观察到数据变化,vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 dom 操作上非常重要。然后,在下一个的事件循环“tick”中,vue 刷新队列并执行实际(已去重的)工作。vue 在内部尝试对异步队列使用原生的 promise.then 和 mutationobserver,如果执行环境不支持,会采用 settimeout(fn, 0) 代替。

例如,当你设置 vm.somedata = ‘new value' ,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 dom 状态更新后做点什么,这就可能会有些棘手。虽然 vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 dom,但是有时我们确实要这么做。为了在数据变化之后等待 vue 完成更新 dom ,可以在数据变化之后立即使用 vue.nexttick(callback) 。这样回调函数在 dom 更新完成后就会调用。例如

源码解析

方法原型以及解析注释如下:

var nexttick = (function () {
    var callbacks = []; // 存储需要触发的回调函数
    var pending = false; // 是否正在等待的标识(false:允许触发在下次事件循环触发callbacks中的回调, true: 已经触发过,需要等到下次事件循环)
    var timerfunc; // 设置在下次事件循环触发callbacks的 触发函数

    //处理callbacks的函数
    function nexttickhandler () {
      pending = false;// 可以触发timefunc
      var copies = callbacks.slice(0);//复制callback
      callbacks.length = 0;//清空callback
      for (var i = 0; i < copies.length; i++) {
        copies[i]();//触发callback回调函数
      }
    }

    //如果支持promise,使用promise实现
    if (typeof promise !== 'undefined' && isnative(promise)) {
      var p = promise.resolve();
      var logerror = function (err) { console.error(err); };
      timerfunc = function () {
        p.then(nexttickhandler).catch(logerror);
        // ios的webview下,需要强制刷新队列,执行上面的回调函数
        if (isios) { settimeout(noop); }
      };

      //如果promise不支持,但是支持mutationobserver(h5新特性,异步,当dom变动是触发,注意是所有的dom都改变结束后触发)
    } else if (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 ie11, ios7, android 4.4
      var counter = 1;
      var observer = new mutationobserver(nexttickhandler);
      //创建一个textnode dom节点,并让mutationobserver 监视这个节点;而 timefunc正是改变这个dom节点的触发函数
      var textnode = document.createtextnode(string(counter));
      observer.observe(textnode, {
        characterdata: true
      });
      timerfunc = function () {
        counter = (counter + 1) % 2;
        textnode.data = string(counter);
      };
    } else {// 上面两种不支持的话,就使用settimeout

      timerfunc = function () {
        settimeout(nexttickhandler, 0);
      };
    }
    //nexttick接受的函数, 参数1:回调函数 参数2:回调函数的执行上下文
    return function queuenexttick (cb, ctx) {
      var _resolve;//用于接受触发 promise.then中回调的函数
      //向回调数据中pushcallback
      callbacks.push(function () {
        //如果有回调函数,执行回调函数
        if (cb) { cb.call(ctx); }
        if (_resolve) { _resolve(ctx); }//触发promise的then回调
      });
      if (!pending) {//是否执行刷新callback队列
        pending = true;
        timerfunc();
      }
      //如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
      if (!cb && typeof promise !== 'undefined') {
        return new promise(function (resolve) {
          _resolve = resolve;
        })
      }
    }
  })();

 我在注释中解释了nexttick()函数的逻辑

上面处理回调的三个方式的使用优先级的原因:因为promise和mutationobserver和触发的事件在同一个事件循环里面(只不过是运行在微观队列里面),但是settimeout的回调函数是运行在下次时间循环里面。

优先使用promise的原因是mutationobserver在ios9.3.3以上版本的uiwebview中运行一段时间后就停止了。
上面代码的注释已经完全说明了代码逻辑。简单理解:将callback 推到队列里面,如果还没有执行过在下次事件循环执行触发callback函数。

注意: 如果使用nexttick()不设置回调函数,而是使用promise的方式设置回调函数,里面this并不是指向当前的vue实例,而是指向window(严格模式是undefined);
但是通过上面的分析可知:执行上下文是通过promise.then()里的回调函数的第一个参数传递的。

nexttick()被使用的地方

1、他是全局vue的一个函数,因此我们可以通过vue直接调用。

2、vue系统中,用于处理dom更新的操作

vue中有一个watcher,用于观察数据的变化,然后更新dom。前面我们就知道vue里面不是每一次数据改变都会触发更新dom,而是将这些操作都缓存在一个队列,在一个事件循环结束之后,刷新队列,统一执行dom更新操作。

function queuewatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i >= 0 && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(math.max(i, index) + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;
        nexttick(flushschedulerqueue);
      }
    }
  }

简单说明上面代码的逻辑,因为是watcher那里的代码,以后会分析到。这里nexttick()的作用,是在此次事件循环结尾的时候刷新watcher检查的dom更新操作。

3、局部vue触发$nexttick(),在dom更新后执行相应逻辑。

vue.prototype.$nexttick = function (fn) {
  return nexttick(fn, this)// 设置nexttick回调函数的上下文环境是当前vue实例
};

上面是renderminxin中的一段代码,也就是render模块初始化的代码。

总结

如果不了解它的代码,我们会产生理解误区。

1、nexttick()并不会重绘当前页面,并且它也不是在页面重绘才执行,而是在此次事件循环结束后一定会执行的。

2、此方法的触发并不是在页面更新完成才执行,第一条已经说了,但是为什么能在此方法中取到更新后的数据,那是因为dom元素的属性已经在watcher执行flush队列的时候改变了,因此是可以在此时获取的。

证明上述观点的实例:

h5有一个方法requestframeanimation(callback),此方法的回调是在页面重绘之前调用。通过实验,更新dom,nexttick()在此方法之前执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网