您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

干货分享,带你了解Vue中的Vue.nextTick

2024/7/17 20:51:03发布34次查看
本篇文章给大家分享一下vue纯干货,介绍一下你不知道的vue.nexttick,希望对大家有所帮助!
用过vue的朋友多多少少都知道$nexttick~ 在正式讲解nexttick之前,我想你应该清楚知道 vue 在更新 dom 时是异步执行的,因为接下来讲解过程会结合组件更新一起讲~ 事不宜迟,我们直进主题吧(本文以v2.6.14版本的vue源码进行讲解)【相关推荐:vuejs视频教程】
一、nexttick小测试你真的了解nexttick吗?来,直接上题~
<template> <div id="app"> <p ref="name">{{ name }}</p> <button @click="handleclick">修改name</button> </div></template><script> export default { name: 'app', data () { return { name: '井柏然' } }, mounted() { console.log('mounted', this.$refs.name.innertext) }, methods: { handleclick () { this.$nexttick(() => console.log('nexttick1', this.$refs.name.innertext)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innertext) this.$nexttick(() => console.log('nexttick2', this.$refs.name.innertext)) } }}</script>
请问上述代码中,当点击按钮“修改name”时,'nexttick1','sync log','nexttick2'对应的this.$refs.name.innertext分别会输出什么?注意,这里打印的是dom的innertext~(文章结尾处会贴出答案)
如果此时的你有非常坚定的答案,那你可以不用继续往下看了~但如果你对自己的答案有所顾虑,那不如跟着我,接着往下看。相信你看完,不需要看到答案都能有个肯定的答案了~!
二、nexttick源码实现源码位于core/util/next-tick中。可以将其分为4个部分来看,直接上代码
1. 全局变量callbacks队列、pending状态
const callbacks = [] // 存放cb的队列let pending = false // 是否马上遍历队列,执行cb的标志
2. flushcallbacks遍历callbacks执行每个cb
function flushcallbacks () { pending = false // 注意这里,一旦执行,pending马上被重置为false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() // 执行每个cb }}
3. nexttick的异步实现根据执行环境的支持程度采用不同的异步实现策略
let timerfunc // nexttick异步实现fnif (typeof promise !== 'undefined' && isnative(promise)) { // promise方案 const p = promise.resolve() timerfunc = () => { p.then(flushcallbacks) // 将flushcallbacks包装进promise.then中 } isusingmicrotask = true} else if (!isie && typeof mutationobserver !== 'undefined' && ( isnative(mutationobserver) || mutationobserver.tostring() === '[object mutationobserverconstructor]')) { // mutationobserver方案 let counter = 1 const observer = new mutationobserver(flushcallbacks) // 将flushcallbacks作为观测变化的cb const textnode = document.createtextnode(string(counter)) // 创建文本节点 // 观测文本节点变化 observer.observe(textnode, { characterdata: true }) // timerfunc改变文本节点的data,以触发观测的回调flushcallbacks timerfunc = () => { counter = (counter + 1) % 2 textnode.data = string(counter) } isusingmicrotask = true} else if (typeof setimmediate !== 'undefined' && isnative(setimmediate)) { // setimmediate方案 timerfunc = () => { setimmediate(flushcallbacks) }} else { // 最终降级方案settimeout timerfunc = () => { settimeout(flushcallbacks, 0) }}
这里用个真实案例加深对mutationobserver的理解。毕竟比起其他三种异步方案,这个应该是大家最陌生的const observer = new mutationobserver(() => console.log('观测到文本节点变化'))const textnode = document.createtextnode(string(1))observer.observe(textnode, { characterdata: true})console.log('script start')settimeout(() => console.log('timeout1'))textnode.data = string(2) // 这里对文本节点进行值的修改console.log('script end')
知道对应的输出会是怎么样的吗?script start、script end会在第一轮宏任务中执行,这点没问题
settimeout会被放入下一轮宏任务执行
mutationobserver是微任务,所以会在本轮宏任务后执行,所以先于settimeout
结果如下图:
4. nexttick方法实现cb、promise方式
export function nexttick (cb?: function, ctx?: object) { let _resolve // 往全局的callbacks队列中添加cb callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleerror(e, ctx, 'nexttick') } } else if (_resolve) { // 这里是支持promise的写法 _resolve(ctx) } }) if (!pending) { pending = true // 执行timerfunc,在下一个tick中执行callbacks中的所有cb timerfunc() } // 对promise的实现,这也是我们使用时可以写成nexttick.then的原因 if (!cb && typeof promise !== 'undefined') { return new promise(resolve => { _resolve = resolve }) }}
深入细节,理解pending有什么用,如何运作?案例1,同一轮tick中执行2次$nexttick,timerfunc只会被执行一次
this.$nexttick(() => console.log('nexttick1'))this.$nexttick(() => console.log('nexttick2'))
用图看看更直观?
三、vue组件的异步更新这里如果有对vue组件化、派发更新不是十分了解的朋友,可以先戳这里,看图解vue响应式原理了解下vue组件化和派发更新的相关内容再回来看噢~
vue的异步更新dom其实也是使用nexttick来实现的,跟我们平时使用的$nexttick其实是同一个~
这里我们回顾一下,当我们改变一个属性值的时候会发生什么?
根据上图派发更新过程,我们从watcher.update开时讲起,以渲染watcher为例,进入到queuewatcher里
1. queuewatcher做了什么?// 用来存放wathcer的队列。注意,不要跟nexttick的callbacks搞混了,都是队列,但用处不同~const queue: array<watcher> = []function queuewatcher (watcher: watcher) { const id = watcher.id // 拿到wathcer的id,这个id每个watcher都有且全局唯一 if (has[id] == null) { // 避免添加重复wathcer,这也是异步渲染的优化做法 has[id] = true if (!flushing) { queue.push(watcher) } if (!waiting) { waiting = true // 这里把flushschedulerqueue推进nexttick的callbacks队列中 nexttick(flushschedulerqueue) } }}
2. flushschedulerqueue做了什么?function flushschedulerqueue () { currentflushtimestamp = getnow() flushing = true let watcher, id // 排序保证先父后子执行更新,保证userwatcher在渲染watcher前 queue.sort((a, b) => a.id - b.id) // 遍历所有的需要派发更新的watcher执行更新 for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null // 真正执行派发更新,render -> update -> patch watcher.run() }}
最后,一张图搞懂组件的异步更新过程
四、回归题目本身相信经过上文对nexttick源码的剖析,我们已经揭开它神秘的面纱了。这时的你一定可以坚定地把答案说出来了~话不多说,我们一起核实下,看看是不是如你所想!
1、如图所示,mounted时候的innertext是“井柏然”的中文
2、接下来是点击按钮后,打印结果如图所示
没错,输出结果如下(意不意外?惊不惊喜?)
sync log 井柏然
nexttick1 井柏然
nexttick2 jngboran
下面简单分析一下每个输出:
this.$nexttick(() => console.log('nexttick1', this.$refs.name.innertext))this.name = 'jngboran'console.log('sync log', this.$refs.name.innertext)this.$nexttick(() => console.log('nexttick2', this.$refs.name.innertext))
sync log:这个同步打印没什么好说了,相信大部分童鞋的疑问点都不在这里。如果不清楚的童鞋可以先回顾一下eventloop,这里不多赘述了~
nexttick1:注意其虽然是放在$nexttick的回调中,在下一个tick执行,但是他的位置是在this.name = 'jngboran'的前。也就是说,他的cb会比app组件的派发更新(flushschedulerqueue)更先进入队列,当nexttick1打印时,app组件还未派发更新,所以拿到的还是旧的dom值。
nexttick2就不展开了,大家可以自行分析一下。相信大家对它应该是最肯定的,我们平时不就是这样拿到更新后的dom吗?
最后来一张图加深理解
写在最后,nexttick其实在vue中也算是比较核心的一个东西了。因为贯穿整个vue应用的组件化、响应式的派发更新与其息息相关~深入理解nexttick的背后实现原理,不仅能让你在面试的时候一展风采,更能让你在日常开发工作中,少走弯路少踩坑!好了,本文到这里就暂告一段落了,如果读完能让你有所收获,就帮忙点个赞吧~画图不易、创作艰辛鸭~
(学习视频分享:vuejs教程、web前端)
以上就是干货分享,带你了解vue中的vue.nexttick的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product