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


Posted in Javascript onDecember 31, 2017

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()在此方法之前执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS 中document.URL 和 windows.location.href 的区别
Nov 11 Javascript
jQuery学习7 操作JavaScript对象和集合的函数
Feb 07 Javascript
深入理解javaScript中的事件驱动
May 21 Javascript
js setTimeout()函数介绍及应用以倒计时为例
Dec 12 Javascript
jquery 操作两个select实现值之间的互相传递
Mar 07 Javascript
arguments对象验证函数的参数是否合法
Jun 26 Javascript
JS随机调用指定函数的方法
Jul 01 Javascript
jquery常用函数与方法汇总
Sep 01 Javascript
微信小程序 后台登录(非微信账号)实例详解
Mar 31 Javascript
angular框架实现全选与单选chekbox的自定义
Jul 06 Javascript
JS实现放烟花效果
Mar 10 Javascript
js HTML DOM EventListener功能与用法实例分析
Apr 27 Javascript
Angular 开发学习之Angular CLI的安装使用
Dec 31 #Javascript
ReactNative实现Toast的示例
Dec 31 #Javascript
简单谈谈CommonsChunkPlugin抽取公共模块
Dec 31 #Javascript
AngularJS基于MVC的复杂操作实例讲解
Dec 31 #Javascript
jQuery实现页码跳转式动态数据分页
Dec 31 #jQuery
React数据传递之组件内部通信的方法
Dec 31 #Javascript
javascript 通过键名获取键盘的keyCode方法
Dec 31 #Javascript
You might like
用Socket发送电子邮件(利用需要验证的SMTP服务器)
2006/10/09 PHP
在命令行下运行PHP脚本[带参数]的方法
2010/01/22 PHP
PHP,ASP.JAVA,JAVA代码格式化工具整理
2010/06/15 PHP
解决文件名解压后乱码的问题 将文件名进行转码的代码
2012/01/10 PHP
php简单统计字符串单词数量的方法
2015/06/19 PHP
PHP常见漏洞攻击分析
2016/02/21 PHP
DLL+ ActiveX控件+WEB页面调用例子
2010/08/07 Javascript
基于jquery的多彩百分比 动态进度条 投票效果显示效果实现代码
2011/08/28 Javascript
在javascript中对于DOM的加强
2013/04/11 Javascript
JS自调用匿名函数具体实现
2014/02/11 Javascript
node.js中的url.format方法使用说明
2014/12/10 Javascript
javascript实现时间格式输出FormatDate函数
2015/01/13 Javascript
jQuery实现切换字体大小的方法
2015/03/10 Javascript
jstree的简单实例
2016/12/01 Javascript
Bootstrap源码解读按钮(5)
2016/12/23 Javascript
JS正则表达式学习之贪婪和非贪婪模式实例总结
2016/12/26 Javascript
详解NodeJs支付宝移动支付签名及验签
2017/01/06 NodeJs
vue.js或js实现中文A-Z排序的方法
2018/03/08 Javascript
vue读取本地的excel文件并显示在网页上方法示例
2019/05/29 Javascript
详解vue中在循环中使用@mouseenter 和 @mouseleave事件闪烁问题解决方法
2020/04/07 Javascript
[01:25:38]DOTA2-DPC中国联赛 正赛 VG vs LBZS BO3 第一场 1月19日
2021/03/11 DOTA
python基础教程之lambda表达式使用方法
2014/02/12 Python
Python3如何解决字符编码问题详解
2017/04/23 Python
python实现的Iou与Giou代码
2020/01/18 Python
Python JSON编解码方式原理详解
2020/01/20 Python
Python变量作用域LEGB用法解析
2020/02/04 Python
Matplotlib animation模块实现动态图
2021/02/25 Python
从Pytorch模型pth文件中读取参数成numpy矩阵的操作
2021/03/04 Python
css3气泡 css3关键帧动画创建的动态通知气泡
2013/02/26 HTML / CSS
英国第一的滑雪服装和装备零售商:Snow+Rock
2020/02/01 全球购物
个人借款担保书
2014/04/02 职场文书
《北大荒的秋天》教学反思
2014/04/14 职场文书
食品委托检验协议书范本
2014/09/12 职场文书
学习优秀党员杨宗兴先进事迹材料思想汇报
2014/09/14 职场文书
大学军训的体会
2014/11/08 职场文书
pytorch 两个GPU同时训练的解决方案
2021/06/01 Python