浅谈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 相关文章推荐
详解强大的jQuery选择器之基本选择器、层次选择器
Feb 07 Javascript
Checbox的操作含已选、未选及判断代码
Nov 07 Javascript
JS中的构造函数详细解析
Mar 10 Javascript
jQuery异步上传文件插件ajaxFileUpload详细介绍
May 19 Javascript
js实现无限级树形导航列表效果代码
Sep 23 Javascript
浅谈javascript的call()、apply()、bind()的用法
Feb 21 Javascript
javascript与jquery动态创建html元素示例
Jul 25 Javascript
jquery实现点击a链接,跳转之后,该a链接处显示背景色的方法
Jan 18 jQuery
JS构造一个html文本内容成文件流形式发送到后台
Jul 31 Javascript
解决vue中post方式提交数据后台无法接收的问题
Aug 11 Javascript
解决Vue-cli npm run build生产环境打包,本地不能打开的问题
Sep 20 Javascript
js实现省级联动(数据结构优化)
Jul 17 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类的使用 实例代码讲解
2009/12/28 PHP
PHP实现自动对图片进行滚动显示的方法
2015/03/12 PHP
PHP 实现页面静态化的几种方法
2017/07/23 PHP
PHP实现对数字分隔加千分号的方法
2019/03/18 PHP
php web环境和命令行环境下查找php.ini的位置
2019/07/17 PHP
php解决crontab定时任务不能写入文件问题的方法分析
2019/09/16 PHP
使用jquery实现图文切换效果另加特效
2013/01/20 Javascript
引入JS文件IE6报语法错误或缺少对象问题的解决方法
2014/01/09 Javascript
javascript页面加载完执行事件代码
2014/02/11 Javascript
jQuery制作的别致导航有阴影背景高亮模式窗口
2014/04/15 Javascript
javascript禁止访客复制网页内容的实现代码
2015/08/05 Javascript
Javascript自执行匿名函数(function() { })()的原理浅析
2016/05/15 Javascript
jquery获取复选框checkbox的值实现方法
2016/05/30 Javascript
封装获取dom元素的简单实例
2016/07/08 Javascript
Chrome不支持showModalDialog模态对话框和无法返回returnValue问题的解决方法
2016/10/30 Javascript
微信小程序动态显示项目倒计时效果
2017/06/13 Javascript
原生js二级联动效果
2017/06/20 Javascript
解决VUEX刷新的时候出现数据消失
2017/07/03 Javascript
js技巧之十几行的代码实现vue.watch代码
2018/06/09 Javascript
详解angularjs跨页面传参遇到的一些问题
2018/11/01 Javascript
vue插件mescroll.js实现移动端上拉加载和下拉刷新
2019/03/07 Javascript
node.js express框架简介与实现
2019/07/23 Javascript
jQuery - AJAX load() 实例用法详解
2019/08/27 jQuery
layui的select联动实现代码
2019/09/28 Javascript
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
2020/04/25 Javascript
基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能
2021/02/23 Vue.js
Python IDE PyCharm的基本快捷键和配置简介
2015/11/04 Python
Python读取stdin方法实例
2019/05/24 Python
pandas实现excel中的数据透视表和Vlookup函数功能代码
2020/02/14 Python
python对数组进行排序,并输出排序后对应的索引值方式
2020/02/28 Python
基于Python实现天天酷跑功能
2021/01/06 Python
html5视频播放_动力节点Java学院整理
2017/07/13 HTML / CSS
银行实习鉴定
2013/12/13 职场文书
社区清明节活动总结
2014/07/04 职场文书
奥巴马开学演讲观后感
2015/06/12 职场文书
某某店铺的开业庆典主持词范本
2019/11/25 职场文书