当前位置: 首页 > Web前端 > vue.js

从javascript事件循环看Vue.nextTick的原理和执行机制

时间:2023-03-31 14:32:57 vue.js

Vue的特点之一就是响应式,但是有时候数据更新了,我们看到页面上的DOM并没有立即更新。如果我们需要在DOM更新后执行一段代码,可以使用nextTick来实现。让我们看一个示例exportdefault{data(){return{msg:0}},mounted(){this.msg=1this.msg=2this.msg=3},watch:{msg(){console.log(this.msg)}}}这里的结果是只输出一个3,而不是依次输出1、2、3。为什么是这样?Vue的官方文档解释说,Vue是异步执行DOM更新的。只要观察到数据变化,Vue就会启动一个队列,并缓冲在同一个事件循环中发生的所有数据变化。如果同一个观察者被多次触发,它只会被推入队列一次。这种缓冲时的重复数据删除对于避免不必要的计算和DOM操作很重要。然后,在下一个事件循环“tick”中,Vue刷新队列并执行实际(去重)工作。Vue内部尝试使用原生的Promise.then和MessageChannel来实现异步队列。如果执行环境不支持,它将使用setTimeout(fn,0)代替。如果出现这种情况,挂载的钩子函数的下一个变量a的值将被++循环执行1000次。每次++都会根据响应快慢触发setter->Dep->Watcher->update->run。如果此时不异步更新视图,每次++都会直接操作DOM,非常耗性能。因此,Vue实现了一个队列,在下一个Tick(或者当前Tick的microtask阶段),统一执行队列中Watcher的运行。同时,相同id的Watcher不会重复加入队列,所以1000次Watcher运行不会被执行。最后的结果是直接把a的值从1改成1000,性能大大提升。在vue中,数据监控是通过重写Object.defineProperty内部的set和get方法来实现的。Vue异步更新DOM。每当观察到数据变化时,vue都会启动一个队列,并把同一个事件循环中所有的数据变化都缓存起来,当下一次事件循环发生时,队列会被清空,DOM也会被更新。要理解vue.nextTick的执行机制,我们先来理解javascript的事件循环。js事件循环js任务队列分为同步任务和异步任务,所有同步任务都在主线程中执行。异步任务可能在macrotask或microtask中,异步任务进入EventTable和register函数。当指定的事情完成后,EventTable会把这个函数移到EventQueue中。主线程中的任务执行完后为空,会去EventQueue中读取对应的函数,进入主线程执行。上述过程会不断重复,这就是常说的事件循环(eventloop)。macro-task(宏任务):执行栈每次执行的代码都是宏任务(包括每次从事件队列中获取事件回调,放入执行栈执行)。为了让js内部的(宏)任务和DOM任务能够有序执行,浏览器会在一个(宏)任务执行结束后,下一个(宏)任务开始执行前重新渲染页面。宏任务主要包括:脚本(整体代码)setTimeout/setIntervalsetImmediate(Node.js环境)I/OUIrenderpostMessageMes??sageChannel微任务(micro-task):可以理解为当前任务执行后立即执行的任务结束。也就是说,在当前任务之后、下一个任务之前和渲染之前。所以它的响应速度会比setTimeout快(setTimeout是一个任务),因为不需要等待渲染。也就是说,一个macrotask执行完之后,所有在它执行过程中产生的microtask都会被执行(渲染之前)。microtask主要包括:process.nextTick(Node.js环境)PromiseAsync/AwaitMutationObserver(html5新特性)队列(microtask)的主线程执行完后执行microqueue(微任务),microtask(微任务)执行队列中的一个任务宏队列(macrotask)执行一次,执行完后执行微任务(microtask),执行完依次循环。..Vue.nextTick源码Vue使用双向数据绑定来驱动数据更新。虽然这样可以避免直接操作DOM,提高性能,但是有时候我们不可避免的需要操作DOM。这时候,Vue.nextTick(callback)出现了,它接受一个回调函数,这个回调函数会在DOM更新完成后被调用。无论是vue.nextTick还是vue.prototype.\$nextTick都是直接使用nextTick的闭包函数。exportconstnextTick=(function(){constcallbacks=[]letpending=falselettimerFuncfunctionnextTickHandler(){pending=falseconstcopies=callbacks.slice(0)callbacks.length=0for(leti=0;i{console.error(err)}timerFunc=()=>{p.then(nextTickHandler).catch(logError)if(isIOS)setTimeout(noop)}}elseif(typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||MutationObserver.toString()==='[objectMutationObserverConstructor]')){varcounter=1varobserver=newMutationObserver(nextTickHandler)vartextNode=document.createTextNode(String(counter))observer.observe(textNode,{characterData:true})timerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}}else{timeFunc=()=>{setTimeout(nextTickHandle,0)}}队列控制最好选择microtask,microtask最好选择Promise。但是如果当前环境不支持Promise,它会检测浏览器是否支持MO,如果支持,则创建一个文本节点,监听这个文本节点的change事件触发nextTickHandler的执行(即DOM更新完成回调)。此外,由于兼容性问题,Vue不得不将microtask降级为macrotask。为了延迟执行这个回调函数,vue首先使用promise来实现,然后是html5的MutationObserver,然后是setTimeout。前两者属于microtask,后者属于macrotask。我们来看最后一部分。返回函数queueNextTick(cb?:Function,ctx?:Object){让_resolve回调。push(()=>{if(cb)cb.call(ctx)if(_resolve)_resolve(ctx)})if(!pending){pending=truetimerFunc()}if(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}这个就是我们实际调用的nextTick函数,在一个事件循环中,会把调用nextTick的cb回调函数放入回调中,并且pending用于判断是否有队列正在执行回调。比如nextTick中可能有一个nextTick,此时应该属于下一个循环。最后几行代码是基于promise的,nextTick可以按照promise的方式来写(现在用的比较少)。应用场景Scenario1.点击按钮显示原本用v-show=false隐藏的输入框,并获得焦点。showInput(){this.showit=truedocument.getElementById("keywords").focus()}上面的写法是在第一个tick,因为获取不能到输入框,当然不能获取焦点。如果我们改成下面这样,就可以在DOM更新后获取输入框的焦点。showsou(){this.showit=truethis.$nextTick(function(){//DOM已更新document.getElementById("keywords").focus()})}场景二、获取元素属性,点击获取元素宽度。{{message}}

获取p元素宽度
getMyWidth(){this.showMe=true;this.message=this.$refs.myWidth.offsetWidth;//ErrorTypeError:this.$refs.myWidthisundefinedthis.$nextTick(()=>{//dom元素更新后执行,此时可以获取p元素的属性this.message=this.$refs.myWidth.offsetWidth;})}推荐文章总结javascript处理异步方法总结移动端H5开发常用技巧(干货满满的!)从零开始搭建webpack项目总结几种webpack打包优化方法总结前端性能优化方法总结vue知识体系进阶应用总结vue知识体系实用技巧几种常见的JS递归算法封装toast和dialog组件并发布到npm读取前端路由,后端路由,单页应用、多页应用及浅谈JavaScript防抖与节流