0

0

Vue.nextTick 的实现方法详解

小云云

小云云

发布时间:2018-01-22 10:06:36

|

1661人浏览过

|

来源于php中文网

原创

本文主要介绍了vue.nexttick 的实现方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望能帮助到大家。

这是一篇继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

6种实现进度条的方法
6种实现进度条的方法

6种实现进度条的方法

下载


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官方示例


{{message}}

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

相关推荐:

详解Vue + Vuex 使用 vm.$nextTick实例详解

Vue中nextTick函数源码详解

Node.js中的process.nextTick使用实例

相关专题

更多
node.js调试
node.js调试

node.js调试可以使用console.log()输出调试信息、断点调试和第三方调试工具。详细介绍:1、console.log()输出调试信息,通过在代码中插入console.log()语句,开发人员可以在控制台输出变量的值、函数的执行结果等信息,以便观察代码的执行流程和状态;2、断点调试,可以在代码中设置断点,以便在特定位置暂停代码的执行,观察变量的值和执行流程等。

344

2023.09.19

JavaScript 全栈开发基础(Node.js + 前端)
JavaScript 全栈开发基础(Node.js + 前端)

本专题系统介绍 JavaScript 在全栈开发中的核心知识结构,涵盖 Node.js 基础、Express/Koa 接口构建、前端交互设计、模块化与包管理、数据库连接、前后端数据通信与部署流程。通过完整项目示例,帮助学习者掌握从浏览器到服务器的一体化开发能力,实现真正意义上的全栈入门。

88

2025.11.26

ajax教程
ajax教程

php中文网为大家带来ajax教程合集,Ajax是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。php中文网还为大家带来ajax的相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

145

2023.06.14

ajax中文乱码解决方法
ajax中文乱码解决方法

ajax中文乱码解决方法有设置请求头部的字符编码、在服务器端设置响应头部的字符编码和使用encodeURIComponent对中文进行编码。本专题为大家提供ajax中文乱码相关的文章、下载、课程内容,供大家免费下载体验。

159

2023.08.31

ajax传递中文乱码怎么办
ajax传递中文乱码怎么办

ajax传递中文乱码的解决办法:1、设置统一的编码方式;2、服务器端编码;3、客户端解码;4、设置HTTP响应头;5、使用JSON格式。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

109

2023.11.15

ajax网站有哪些
ajax网站有哪些

使用ajax的网站有谷歌、维基百科、脸书、纽约时报、亚马逊、stackoverflow、twitter、hacker news、shopify和basecamp等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.09.24

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

508

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

241

2023.07.28

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

74

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.2万人学习

麻省理工大佬Python课程
麻省理工大佬Python课程

共34课时 | 5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号