浅谈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 相关文章推荐
javascript 静态对象和构造函数的使用和公私问题
Mar 02 Javascript
IE6、IE7中获取Button元素的值的bug说明
Aug 28 Javascript
jQuery LigerUI 使用教程表格篇(1)
Jan 18 Javascript
javascript loadScript异步加载脚本示例讲解
Nov 14 Javascript
判断访客终端类型集锦
Jun 05 Javascript
深入理解Java线程编程中的阻塞队列容器
Dec 07 Javascript
javascript创建对象、对象继承的实用方式详解
Mar 08 Javascript
基于jQuery插件jqzoom实现的图片放大镜效果示例
Jan 23 Javascript
微信小程序 五星评价功能的实现
Mar 09 Javascript
JavaScript中使用参数个数实现重载功能
Sep 01 Javascript
微信小程序实现多宫格抽奖活动
Apr 15 Javascript
解决VUE项目localhost端口服务器拒绝连接,只能用127.0.0.1的问题
Aug 14 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笔记 字符串处理
2010/10/19 PHP
浅析php插件 Simple HTML DOM 用DOM方式处理HTML
2013/07/01 PHP
Yii框架调试心得--在页面输出执行sql语句
2014/12/25 PHP
ThinkPHP3.2.3数据库设置新特性
2015/03/05 PHP
IE中jscript/javascript的条件编译
2006/09/07 Javascript
js身份证验证超强脚本
2008/10/26 Javascript
Jquery拖拽并简单保存的实现代码
2010/11/28 Javascript
jquery easyui combobox模糊过滤(示例代码)
2013/11/30 Javascript
JS获取月的最后一天与JS得到一个月份最大天数的实例代码
2013/12/16 Javascript
javascript实现浏览器窗口传递参数的方法
2014/09/03 Javascript
jQuery实现textarea自动增长宽高的方法
2015/12/18 Javascript
js实现网页图片延时加载 提升网页打开速度
2016/01/26 Javascript
Bootstrap Navbar Component实现响应式导航
2016/10/08 Javascript
angularjs中判断ng-repeat是否迭代完的实例
2018/09/12 Javascript
初探Vue3.0 中的一大亮点Proxy的使用
2018/12/06 Javascript
浅谈Vue.set实际上是什么
2019/10/17 Javascript
Node.js中文件系统fs模块的使用及常用接口
2020/03/06 Javascript
Python修改Excel数据的实例代码
2013/11/01 Python
python通过urllib2爬网页上种子下载示例
2014/02/24 Python
基于ID3决策树算法的实现(Python版)
2017/05/31 Python
django js实现部分页面刷新的示例代码
2018/05/28 Python
详解python3中的真值测试
2018/08/13 Python
django-rest-swagger的优化使用方法
2019/08/29 Python
Python读取文件内容为字符串的方法(多种方法详解)
2020/03/04 Python
Python selenium使用autoIT上传附件过程详解
2020/05/26 Python
Django nginx配置实现过程详解
2020/09/10 Python
采用专利算法搜索最廉价的机票:CheapAir
2016/09/10 全球购物
StubHub新加坡:购买和出售全球活动门票
2017/03/10 全球购物
夏洛特和乔治婴儿和儿童时装精品店:Charlotte and George
2018/06/06 全球购物
xml有哪些解析技术?区别是什么
2016/04/26 面试题
英语生日邀请函
2014/01/23 职场文书
大课间活动实施方案
2014/03/06 职场文书
总账会计岗位职责
2015/04/02 职场文书
运动会开幕式通讯稿
2015/07/18 职场文书
详解python网络进程
2021/06/15 Python
六个好看实用的 HTML + CSS 后台登录入口页面
2022/04/28 HTML / CSS