vue之nextTick全面解析


Posted in Javascript onMay 17, 2017

简介

vue是非常流行的框架,他结合了angular和react的优点,从而形成了一个轻量级的易上手的具有双向数据绑定特性的mvvm框架。本人比较喜欢用之。在我们用vue时,我们经常用到一个方法是this.$nextTick,相信你也用过。我常用的场景是在进行获取数据后,需要对新视图进行下一步操作或者其他操作时,发现获取不到dom。因为赋值操作只完成了数据模型的改变并没有完成视图更新。在这个时候我们需要用到本章介绍的函数。

为什么要用nextTick

请看如下一段代码

new Vue({
 el: '#app',
 data: {
  list: []
 },
 mounted: function () {
  this.get()
 },
 methods: {
  get: function () {
   this.$http.get('/api/article').then(function (res) {
    this.list = res.data.data.list
    // ref list 引用了ul元素,我想把第一个li颜色变为红色
    this.$refs.list.getElementsByTagName('li')[0].style.color = 'red'
   })
  },
 }
})

我在获取到数据后赋值给数据模型中list属性,然后我想引用ul元素找到第一个li把它的颜色变为红色,但是事实上,这个要报错了,我们知道,在执行这句话时,ul下面并没有li,也就是说刚刚进行的赋值操作,当前并没有引起视图层的更新。因此,在这样的情况下,vue给我们提供了$nextTick方法,如果我们想对未来更新后的视图进行操作,我们只需要把要执行的函数传递给this.$nextTick方法,vue就会给我们做这个工作。

源码解读

这个函数很简单,vue2.2.6版本 450行开始。

首先,这个函数是采用了一个单利模式还是什么创建的一个闭包函数

var callbacks = [];  // 缓存函数的数组
var pending = false; // 是否正在执行
var timerFunc; // 保存着要执行的函数

首先定义了一些变量供之后使用,下面是一个函数

function nextTickHandler () {
 pending = false;
 // 拷贝出函数数组副本
 var copies = callbacks.slice(0);
 // 把函数数组清空
 callbacks.length = 0;
 // 依次执行函数
 for (var i = 0; i < copies.length; i++) {
  copies[i]();
 }
}

这个函数就是$nextTick内实际调用的函数。

接下来,是vue分了三种情况来延迟调用以上这个函数,因为$nextTick目的就是把传进来的函数延迟到dom更新后再使用,所以这里依次优雅降序的使用js的方法来做到这一点。

1. promise.then延迟调用

if (typeof Promise !== 'undefined' && isNative(Promise)) {
 var p = Promise.resolve();
 var logError = function (err) { console.error(err); };
 timerFunc = function () {
  p.then(nextTickHandler).catch(logError);
  if (isIOS) { setTimeout(noop); }
 };
}

如果浏览器支持Promise,那么就用Promise.then的方式来延迟函数调用,Promise.then方法可以将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。

2. MutationObserver 监听变化

else if (typeof MutationObserver !== 'undefined' && (
 isNative(MutationObserver) ||
 MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {

 var counter = 1;
 var observer = new MutationObserver(nextTickHandler);
 var textNode = document.createTextNode(String(counter));
 observer.observe(textNode, {
  characterData: true
 });
 timerFunc = function () {
  counter = (counter + 1) % 2;
  textNode.data = String(counter);
 };
}

MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。

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

  1. childList:子元素的变动
  2. attributes:属性的变动
  3. characterData:节点内容或节点文本的变动
  4. subtree:所有下属节点(包括子节点和子节点的子节点)的变动

可以看出,以上代码是创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起dom节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有dom渲染完后,执行函数,达到我们延迟的效果。

3.setTimeout延迟器

else {
  timerFunc = function () {
   setTimeout(nextTickHandler, 0);
  };
 }

利用setTimeout的延迟原理,setTimeout(func, 0)会将func函数延迟到下一次函数调用栈的开始,也就是当前函数执行完毕后再执行该函数,因此完成了延迟功能。

闭包函数

return function queueNextTick (cb, ctx) {
  var _resolve;
  callbacks.push(function () {
   if (cb) { cb.call(ctx); }
   if (_resolve) { _resolve(ctx); }
  });
  // 如果没有函数队列在执行才执行
  if (!pending) {
   pending = true;
   timerFunc();
  }
  // promise化
  if (!cb && typeof Promise !== 'undefined') {
   console.log('进来了')
   return new Promise(function (resolve) {
    _resolve = resolve;
   })
  }
 }

这个return的函数就是我们实际使用的闭包函数,每一次添加函数,都会想callbacks这个函数数组入栈。然后监听当前是否正在执行,如果没有,执行函数。这个很好理解。下面一个if是promise化。

this.$nextTick(function () {

})
// promise化
this.$nextTick().then(function () {

}.bind(this))

以上代码中第二种写法我们不常见把,直接调用$nextTick函数然后用promise格式去书写代码,不过这个then里面需要手动绑定this,vue内部没有给做处理。

结尾

这就是一个this.$nextTick的实现,其中利用了优雅降序的巧妙手法,使代码尽可能优化。而且还提供了promise的写法,虽然我们不经常用,但是有总比没有好。

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

Javascript 相关文章推荐
jQuery Tab插件 用于在Tab中显示iframe,附源码和详细说明
Jun 27 Javascript
javascript学习笔记(三)BOM和DOM详解
Sep 30 Javascript
jQuery简单实现禁用右键菜单
Mar 10 Javascript
仅一个form表单 js实现注册信息依次填写提交功能
Jun 12 Javascript
javascript的replace方法结合正则使用实例总结
Jun 16 Javascript
jQuery插件FusionCharts绘制2D柱状图和折线图的组合图效果示例【附demo源码】
Apr 10 jQuery
详解Vue2.0 事件派发与接收
Sep 05 Javascript
基于 flexible 的 Vue 组件:Toast -- 显示框效果
Dec 26 Javascript
详解微信小程序审核不通过的解决方法
Jan 17 Javascript
基于vue1和vue2获取dom元素的方法
Mar 17 Javascript
JS获取子节点、父节点和兄弟节点的方法实例总结
Jul 06 Javascript
基于javascript的无缝滚动动画1
Aug 07 Javascript
Vue.js学习教程之列表渲染详解
May 17 #Javascript
JavaScript运动框架 解决速度正负取整问题(一)
May 17 #Javascript
深入理解Commonjs规范及Node模块实现
May 17 #Javascript
JavaScript限制在客户区可见范围的拖拽(解决scrollLeft和scrollTop的问题)(2)
May 17 #Javascript
vue学习笔记之vue1.0和vue2.0的区别介绍
May 17 #Javascript
Angular.JS中的this指向详解
May 17 #Javascript
websocket+node.js实现实时聊天系统问题咨询
May 17 #Javascript
You might like
无需重新编译php加入ftp扩展的解决方法
2013/02/07 PHP
php出现内存位置访问无效错误问题解决方法
2014/08/16 PHP
RR vs IO BO3 第一场2.13
2021/03/10 DOTA
ExtJS 2.0 GridPanel基本表格简明教程
2010/05/25 Javascript
jQuery源码解读之removeAttr()方法分析
2015/02/20 Javascript
jQuery设置和移除文本框默认值的方法
2015/03/09 Javascript
Java File类的常用方法总结
2015/03/18 Javascript
js控制文本框输入的字符类型方法汇总
2015/06/19 Javascript
jQuery鼠标经过方形图片切换成圆边效果代码分享
2015/08/20 Javascript
javascript简单比较日期大小的方法
2016/01/05 Javascript
浅谈javascript的call()、apply()、bind()的用法
2016/02/21 Javascript
限制只能输入数字的实现代码
2016/05/16 Javascript
javascript获取select标签选中的值
2016/06/04 Javascript
两种JavaScript的AES加密方式(可与Java相互加解密)
2016/08/02 Javascript
微信小程序之页面跳转和参数传递的实现
2017/09/29 Javascript
微信小程序实现页面跳转传值的方法
2017/10/12 Javascript
vue中遇到的坑之变化检测问题(数组相关)
2017/10/13 Javascript
vue动态改变背景图片demo分享
2018/09/13 Javascript
vue使用map代替Aarry数组循环遍历的方法
2020/04/30 Javascript
vue实现的多页面项目如何优化打包的步骤详解
2020/07/19 Javascript
Python的ORM框架中SQLAlchemy库的查询操作的教程
2015/04/25 Python
Django发送html邮件的方法
2015/05/26 Python
Python用zip函数同时遍历多个迭代器示例详解
2016/11/14 Python
python实现自动发送邮件发送多人、群发、多附件的示例
2018/01/23 Python
Django之路由层的实现
2019/09/09 Python
Python小整数对象池和字符串intern实例解析
2020/03/21 Python
使用phonegap操作数据库的实现方法
2017/03/31 HTML / CSS
毕业生就业推荐信范文
2013/12/01 职场文书
学校门卫工作职责
2013/12/07 职场文书
大一期末自我鉴定
2013/12/13 职场文书
《三亚落日》教学反思
2014/04/26 职场文书
机械工程师岗位职责
2014/06/16 职场文书
员工培训协议书
2014/09/15 职场文书
搞笑老公保证书
2015/02/26 职场文书
2015年乡镇组织委员工作总结
2015/10/23 职场文书
母婴行业实体、电商模式全面解析
2019/08/01 职场文书