浅谈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 Lightbox 图片展示插件使用说明
Apr 25 Javascript
style、 currentStyle、 runtimeStyle区别分析
Aug 01 Javascript
js 获取和设置css3 属性值的实现方法
May 06 Javascript
JavaScript获取多个数组的交集简单实例
Nov 11 Javascript
浅析JavaScript访问对象属性和方法及区别
Nov 16 Javascript
任意Json转成无序列表的方法示例
Dec 09 Javascript
jQuery阻止移动端遮罩层后页面滚动
Mar 15 Javascript
jQuery使用unlock.js插件实现滑动解锁
Apr 04 jQuery
jQuery Ajax自定义分页组件(jquery.loehpagerv1.0)实例详解
May 01 jQuery
js封装成插件_Canvas统计图插件编写实例
Sep 12 Javascript
Windows下Node.js安装及环境配置方法
Sep 18 Javascript
vue中filters 传入两个参数 / 使用两个filters的实现方法
Jul 15 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获取网卡地址的代码
2008/04/09 PHP
ThinkPHP3.1新特性之动态设置自动完成及自动验证示例代码
2014/06/23 PHP
全面解读PHP的Yii框架中的日志功能
2016/03/17 PHP
php7下的filesize函数
2019/09/30 PHP
对laravel in 查询的使用方法详解
2019/10/09 PHP
Javascript公共脚本库系列(一): 弹出层脚本
2011/02/24 Javascript
JavaScript XML和string相互转化实现代码
2011/07/04 Javascript
最短的javascript:地址栏载入脚本代码
2011/10/13 Javascript
js数组循环遍历数组内所有元素的方法
2014/01/18 Javascript
window.location.href的用法(动态输出跳转)
2014/08/09 Javascript
jQuery中siblings()方法用法实例
2015/01/08 Javascript
jQuery 自定义下拉框(DropDown)附源码下载
2016/07/22 Javascript
简单实现jquery焦点图
2016/12/12 Javascript
详解nodejs中express搭建权限管理系统
2017/09/15 NodeJs
微信小程序分享功能之按钮button 边框隐藏和点击隐藏
2018/06/14 Javascript
微信小程序实践之动态控制组件的显示/隐藏功能
2018/07/18 Javascript
Vue-CLI3.x 设置反向代理的方法
2018/12/06 Javascript
vue-router启用history模式下的开发及非根目录部署方法
2018/12/23 Javascript
微信小程序五子棋游戏AI实现方法【附demo源码下载】
2019/02/20 Javascript
layer弹出层显示在top顶层的方法
2019/09/11 Javascript
vuex(vue状态管理)的特殊应用案例分享
2020/03/03 Javascript
JavaScript实现瀑布流布局的3种方式
2020/12/27 Javascript
Python基本数据类型详细介绍
2014/03/11 Python
实例解析Python中的__new__特殊方法
2016/06/02 Python
Django中和时区相关的安全问题详解
2020/10/12 Python
Django Admin后台模型列表页面如何添加自定义操作按钮
2020/11/11 Python
Pyecharts 中Geo函数常用参数的用法说明
2021/02/01 Python
详解CSS3 rem(设置字体大小) 教程
2017/11/21 HTML / CSS
英国假发网站:Hothair
2018/02/23 全球购物
办公室主任职责范文
2013/11/08 职场文书
师恩难忘教学反思
2014/04/27 职场文书
在校大学生自我评价范文
2014/09/12 职场文书
先进个人推荐材料
2014/12/29 职场文书
观看《信仰》心得体会
2016/01/15 职场文书
初三语文教学反思
2016/03/03 职场文书
redis实现排行榜功能
2021/05/24 Redis