浅谈Vue.nextTick 的实现方法


Posted in Javascript onOctober 25, 2017

这是一篇继event loop和MicroTask 后的vue.nextTick API实现的源码解析。

预热,写一个sleep函数

function sleep (ms) {
 return new Promise(resolve => setTimeout(resolve, ms)
}
async function oneTick (ms) {
 console.log('start')
 await sleep(ms)
 console.log('end')
}
oneTick(3000)

解释下sleep函数

async 函数进行await PromiseFn()时函数执行是暂停的,我们也知道现在这个PromiseFn是在microTask内执行。当microTask没执行完毕时,后面的macroTask是不会执行的,我们也就通过microTask在event loop的特性实现了一个sleep函数,阻止了console.log的执行

流程

1执行console.log('start')
2执行await 执行暂停,等待await函数后的PromiseFn在microTask执行完毕
3在sleep函数内,延迟ms返回
4返回resolve后执行console.log('end')

nextTick API

vue中nextTick的使用方法

vue.nextTick(() => {
 // todo...
})

了解用法后看一下源码

const nextTick = (function () {
 const callbacks = []
 let pending = false
 let timerFunc // 定时函数

 function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0) // 复制
  callbacks.length = 0 // 清空
  for (let i = 0; i < copies.length; i++) {
   copies[i]() // 逐个执行
  }
 }

 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError) // 重点
  }
 } else if ('!isIE MutationObserver') {
  var counter = 1
  var observer = new MutationObserver(nextTickHandler) // 重点
  var textNode = document.createTextNode(string(conter))

  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  timerFunc = () => {
   setTimeout(nextTickHandler, 0) // 重点
  }
 }


 return function queueNextTick (cb, ctx) { // api的使用方式
  let _resolve
  callbacks.push(() => {
   if (cb) {
    try {
     cb.call(ctx)
    } catch (e) {
     err
    }
   } else if (_resolve) {
    _resolve(ctx)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve =resolve
   })
  }
 }
})() // 自执行函数

大致看一下源码可以了解到nextTick api是一个自执行函数

既然是自执行函数,直接看它的return类型,return function queueNextTick (cb, ctx) {...}

return function queueNextTick (cb, ctx) { // api的使用方式
  let _resolve
  callbacks.push(() => {
   if (cb) {
    try {
     cb.call(ctx)
    } catch (e) {
     err
    }
   } else if (_resolve) {
    _resolve(ctx)
   }
  })
  if (!pending) {
   pending = true
   timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve =resolve
   })
  }
 }

只关注主流程queueNextTick函数把我们传入的() => { // todo... } 推入了callbacks内

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve()
  var logError = err => { console.error(err) }
  timerFunc = () => {
   p.then(nextTickHandler).catch(logError) // 重点
  }
 } else if ('!isIE MutationObserver') {
  var counter = 1
  var observer = new MutationObserver(nextTickHandler) // 重点
  var textNode = document.createTextNode(string(conter))

  observer.observe(textNode, {
   characterData: true
  })
  timerFunc = () => {
   counter = (counter + 1) % 2
   textNode.data = String(counter)
  }
 } else {
  timerFunc = () => {
   setTimeout(nextTickHandler, 0) // 重点
  }
 }

这一段我们可以看到标注的三个点表明在不同浏览器环境下使用Promise, MutationObserver或setTimeout(fn, 0) 来执行nextTickHandler

function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0) // 复制
  callbacks.length = 0 // 清空
  for (let i = 0; i < copies.length; i++) {
   copies[i]() // 逐个执行
  }
 }

nextTickHandler就是把我们之前放入callbacks的 () => { // todo... } 在当前tasks内执行。

写一个简单的nextTick

源码可能比较绕,我们自己写一段简单的nextTick

const simpleNextTick = (function () {
 let callbacks = []
 let timerFunc

 return function queueNextTick (cb) {
  callbacks.push(() => { // 给callbacks 推入cb()
   cb()
  })

  timerFunc = () => {
   return Promise.resolve().then(() => {
    const fn = callbacks.shift()
    fn()
   })
  }
  timerFunc() // 执行timerFunc,返回到是一个Promise
 }
})()

simpleNextTick(() => {
 setTimeout(console.log, 3000, 'nextTick')
})

我们可以从这里看出nextTick的原理就是返回出一个Promise,而我们todo的代码在这个Promise中执行,现在我们还可以继续简化

const simpleNextTick = (function () {
 return function queueNextTick (cb) {
  timerFunc = () => {
   return Promise.resolve().then(() => {
    cb()
   })
  }
  timerFunc()
 }
})()

simpleNextTick(() => {
 setTimeout(console.log, 3000, 'nextTick')
})

直接写成这样。

const simpleNextTick = function queueNextTick (cb) {
  timerFunc = () => {
   return Promise.resolve().then(() => {
    cb()
   })
  }
  timerFunc()
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, 'nextTick')
})

这次我们把自执行函数也简化掉

const simpleNextTick = function queueNextTick (cb) {
   return Promise.resolve().then(cb)
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, 'nextTick')
})

现在我们直接简化到最后,现在发现nextTick最核心的内容就是Promise,一个microtask。

现在我们回到vue的nextTick API官方示例

<div id="example">{{message}}</div>
var vm = new Vue({
 el: '#example',
 data: {
  message: '123'
 }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
 vm.$el.textContent === 'new message' // true
})

原来在vue内数据的更新后dom更新是要在下一个事件循环后执行的。
nextTick的使用原则主要就是解决单一事件更新数据后立即操作dom的场景。

既然我们知道了nextTick核心是利用microTasks,那么我们把简化过的nextTick和开头的sleep函数对照一下。

const simpleNextTick = function queueNextTick (cb) {
   return Promise.resolve().then(cb)
 }

simpleNextTick(() => {
 setTimeout(console.log, 3000, 'nextTick') // 也可以换成ajax请求
})
function sleep (ms) {
 return new Promise(resolve => setTimeout(resolve, ms) // 也可以换成ajax请求
}
async function oneTick (ms) {
 console.log('start')
 await sleep(ms)
 console.log('end')
}
oneTick(3000)

我们看出nextTick和我么写的oneTick的执行结果是那么的相似。区别只在于nextTick是把callback包裹一个Promise返回并执行,而oneTick是用await执行一个Promise函数,而这个Promise有自己包裹的webapi函数。

那在用ajax请求的时候我们是不是直接这样使用axios可以返回Promise的库

async function getData () {
  const data = await axios.get(url)
  // 操作data的数据来改变dom
  return data
}

这样也可以达到同nextTick同样的作用

最后我们也可以从源码中看出,当浏览器环境不支持Promise时可以使用MutationObserver或setTimeout(cb, 0) 来达到同样的效果。但最终的核心是microTask

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

Javascript 相关文章推荐
asp 取文本框名称代码
Dec 02 Javascript
defer属性导致引用JQuery的页面报“浏览器无法打开网站xxx,操作被中止”错误的解决方法
Apr 27 Javascript
JavaScript中的面向对象介绍
Jun 30 Javascript
js获取指定日期前后的日期代码
Aug 20 Javascript
父页面显示遮罩层弹出半透明状态的dialog
Mar 04 Javascript
jQuery中after()方法用法实例
Dec 25 Javascript
javascript截取字符串小结
Apr 28 Javascript
JS常见问题之为什么点击弹出的i总是最后一个
Jan 05 Javascript
jQuery解析返回的xml和json方法详解
Jan 05 Javascript
jQuery插件HighCharts绘制2D圆环图效果示例【附demo源码下载】
Mar 09 Javascript
anime.js 实现带有描边动画效果的复选框(推荐)
Dec 24 Javascript
微信小程序代码上传、审核发布小程序
May 18 Javascript
纯html+css+javascript实现楼层跳跃式的页面布局(实例代码)
Oct 25 #Javascript
浅谈Node.js之异步流控制
Oct 25 #Javascript
AngularJS 实现购物车全选反选功能
Oct 24 #Javascript
React Native时间转换格式工具类分享
Oct 24 #Javascript
vue+vuecli+webpack中使用mockjs模拟后端数据的示例
Oct 24 #Javascript
React Native AsyncStorage本地存储工具类
Oct 24 #Javascript
React Native验证码倒计时工具类分享
Oct 24 #Javascript
You might like
php遍历文件夹所有文件子文件夹函数代码
2013/11/27 PHP
PHP性能优化大全(php.ini)
2016/05/20 PHP
实例讲解PHP表单
2020/06/10 PHP
利用腾讯的ip地址库做ip物理地址定位
2010/07/24 Javascript
seajs1.3.0源码解析之module依赖有序加载
2012/11/07 Javascript
解决ExtJS在chrome或火狐中正常显示在ie中不显示的浏览器兼容问题
2013/01/11 Javascript
JS、CSS加载中的小问题探讨
2013/11/26 Javascript
JavaScript子类用Object.getPrototypeOf去调用父类方法解析
2013/12/05 Javascript
JS批量操作CSS属性详细解析
2013/12/16 Javascript
js窗口关闭提示信息(兼容IE和firefox)
2015/10/23 Javascript
使用React实现轮播效果组件示例代码
2016/09/05 Javascript
BackBone及其实例探究_动力节点Java学院整理
2017/07/14 Javascript
JS兼容所有浏览器的DOMContentLoaded事件
2018/01/12 Javascript
angular2中使用第三方js库的实例
2018/02/26 Javascript
Bootstrap Fileinput 4.4.7文件上传实例详解
2018/07/25 Javascript
javascript中join方法实例讲解
2019/02/21 Javascript
node使用request请求的方法
2019/12/20 Javascript
javascript中的with语句学习笔记及用法
2020/02/17 Javascript
详解为什么Vue中的v-if和v-for不建议一起用
2021/01/13 Vue.js
详解Python中的循环语句的用法
2015/04/09 Python
Python寻找路径和查找文件路径的示例
2019/07/10 Python
基于python的列表list和集合set操作
2019/11/24 Python
python实现的分析并统计nginx日志数据功能示例
2019/12/21 Python
Python selenium 加载并保存QQ群成员,去除其群主、管理员信息的示例代码
2020/05/28 Python
Python同时处理多个异常的方法
2020/07/28 Python
Python加载数据的5种不同方式(收藏)
2020/11/13 Python
【HTML5】Canvas绘制简单图片教程
2016/05/13 HTML / CSS
澳洲的服装老品牌:SABA
2018/02/06 全球购物
Pam & Gela官网:美国性感前卫女装品牌
2018/07/19 全球购物
环保倡议书100字
2014/05/15 职场文书
学校门卫岗位职责范本
2014/06/30 职场文书
在校大学生自我评价范文
2014/09/12 职场文书
2015年个人实习工作总结
2014/12/12 职场文书
公司给客户的感谢信
2015/01/23 职场文书
学生安全责任协议书
2016/03/22 职场文书
浅谈JS的二进制家族
2021/05/09 Javascript