这篇文章主要介绍“Vue异步更新机制和nextTick原理实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue异步更新机制和nextTick原理实例分析”文章能帮助大家解决问题。update
方法的实现:如果不是 computed watcher
也非 sync
会把调用 update 的当前 watcher 推送到调度者队列中,下一个 tick 时调用,看看 queueWatcher
:这里使用了一个 has
的哈希map用来检查是否当前 watcher 的 id 是否存在,若已存在则跳过,不存在则就 push 到 queue
队列中并标记哈希表 has,用于下次检验,防止重复添加。这就是一个去重的过程,比每次查重都要去 queue 中找要文明,在渲染的时候就不会重复patch
相同 watcher 的变化,这样就算同步修改了一百次视图中用到的 data,异步 patch
的时候也只会更新最后一次修改。这里的 waiting
方法是用来标记 flushSchedulerQueue
是否已经传递给 nextTick
的标记位,如果已经传递则只 push 到队列中不传递 flushSchedulerQueue
给 nextTick
,等到 resetSchedulerState
重置调度者状态的时候 waiting
会被置回 false
允许 flushSchedulerQueue
被传递给下一个 tick 的回调,总之保证了 flushSchedulerQueue
回调在一个 tick 内只允许被传入一次。来看看被传递给 nextTick
的回调 flushSchedulerQueue
做了什么:在 nextTick
方法中执行 flushSchedulerQueue
方法,这个方法挨个执行 queue
中的watcher的 run
方法。我们看到在首先有个 queue.sort()
方法把队列中的 watcher 按 id 从小到大排了个序,这样做可以保证:组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。一个组件的 user watchers (侦听器watcher)比 render watcher 先运行,因为 user watchers 往往比 render watcher 更早创建如果一个组件在父组件 watcher 运行期间被销毁,它的 watcher 执行将被跳过在挨个执行队列中的 for 循环中,index 这里没有将 length 进行缓存,因为在执行处理现有 watcher 对象期间,更多的 watcher 对象可能会被 push 进 queue。
那么数据的修改从 model 层反映到 view 的过程:数据更改 -> setter -> Dep -> Watcher -> nextTick -> patch -> 更新视图
这里就来看看包含着每个 watcher 执行的方法被作为回调传入 nextTick
之后,nextTick
对这个方法做了什么。不过首先要了解一下浏览器中的 EventLoop
、macro task
、micro task
几个概念解释一下,当主线程执行完同步任务后:引擎首先从 macrotask queue 中取出第一个任务,执行完毕后,将 microtask queue 中的所有任务取出,按顺序全部执行;然后再从 macrotask queue 中取下一个,执行完毕后,再次将 microtask queue 中的全部取出;循环往复,直到两个 queue 中的任务都取完。浏览器环境中常见的异步任务种类,按照优先级:macro task
:同步代码、setImmediate
、MessageChannel
、setTimeout/setInterval
micro task
:Promise.then
、MutationObserver
有的文章把 micro task
叫微任务,macro task
叫宏任务,因为这两个单词拼写太像了 -。- ,所以后面的注释多用中文表示~先来看看源码中对 micro task
与 macro task
的实现:macroTimerFunc
、microTimerFunc
flushCallbacks
这个方法就是挨个同步的去执行 callbacks 中的回调函数们, callbacks 中的回调函数是在调用 nextTick
的时候添加进去的;那么怎么去使用 micro task
与 macro task
去执行 flushCallbacks
呢,这里他们的实现 macroTimerFunc
、microTimerFunc
使用浏览器中宏任务/微任务的 API 对flushCallbacks
方法进行了一层包装。比如宏任务方法 macroTimerFunc=()=>{ setImmediate(flushCallbacks) }
,这样在触发宏任务执行的时候 macroTimerFunc()
就可以在浏览器中的下一个宏任务 loop 的时候消费这些保存在 callbacks 数组中的回调了,微任务同理。同时也可以看出传给 nextTick
的异步回调函数是被压成了一个同步任务在一个 tick 执行完的,而不是开启多个异步任务。注意这里有个比较难理解的地方,第一次调用 nextTick
的时候 pending
为 false ,此时已经 push 到浏览器 event loop 中一个宏任务或微任务的 task,如果在没有 flush 掉的情况下继续往 callbacks 里面添加,那么在执行这个占位 queue 的时候会执行之后添加的回调,所以 macroTimerFunc
、microTimerFunc
相当于 task queue 的占位,以后 pending
为 true 则继续往占位 queue 里面添加,event loop 轮到这个 task queue 的时候将一并执行。执行 flushCallbacks
时 pending
置 false,允许下一轮执行 nextTick
时往 event loop 占位。可以看到上面 macroTimerFunc
与 microTimerFunc
进行了在不同浏览器兼容性下的平稳退化,或者说降级策略:macroTimerFunc
:setImmediate -> MessageChannel -> setTimeout
。首先检测是否原生支持 setImmediate
,这个方法只在 IE、Edge 浏览器中原生实现,然后检测是否支持 MessageChannel,如果对 MessageChannel
不了解可以参考一下这篇文章,还不支持的话最后使用 setTimeout
;为什么优先使用 setImmediate
与 MessageChannel
而不直接使用 setTimeout
呢,是因为 HTML5 规定 setTimeout 执行的最小延时为4ms,而嵌套的 timeout 表现为10ms,为了尽可能快的让回调执行,没有最小延时限制的前两者显然要优于 setTimeout
。microTimerFunc
:Promise.then -> macroTimerFunc
。首先检查是否支持Promise
,如果支持的话通过 Promise.then
来调用 flushCallbacks
方法,否则退化为 macroTimerFunc
;vue2.5之后 nextTick
中因为兼容性原因删除了微任务平稳退化的 MutationObserver
的方式。最后来看看我们平常用到的 nextTick
方法到底是如何实现的:nextTick
在这里分为三个部分,我们一起来看一下;首先 nextTick
把传入的 cb
回调函数用 try-catch
包裹后放在一个匿名函数中推入callbacks数组中,这么做是因为防止单个 cb
如果执行错误不至于让整个JS线程挂掉,每个 cb
都包裹是防止这些回调函数如果执行错误不会相互影响,比如前一个抛错了后一个仍然可以执行。然后检查 pending
状态,这个跟之前介绍的 queueWatcher
中的 waiting
是一个意思,它是一个标记位,一开始是 false
在进入 macroTimerFunc
、microTimerFunc
方法前被置为 true
,因此下次调用 nextTick
就不会进入 macroTimerFunc
、microTimerFunc
方法,这两个方法中会在下一个 macro/micro tick
时候flushCallbacks
异步的去执行callbacks队列中收集的任务,而 flushCallbacks
方法在执行一开始会把 pending
置 false
,因此下一次调用 nextTick
时候又能开启新一轮的 macroTimerFunc
、microTimerFunc
,这样就形成了vue中的 event loop
。最后检查是否传入了 cb
,因为 nextTick
还支持Promise化的调用:nextTick().then(() => {})
,所以如果没有传入 cb
就直接return了一个Promise实例,并且把resolve传递给_resolve,这样后者执行的时候就跳到我们调用的时候传递进 then
的方法中。Vue源码中 next-tick.js
文件还有一段重要的注释,这里就翻译一下:在vue2.5之前的版本中,nextTick基本上基于 micro task
来实现的,但是在某些情况下micro task
具有太高的优先级,并且可能在连续顺序事件之间(例如#4521,#6690)或者甚至在同一事件的事件冒泡过程中之间触发(#6566)。但是如果全部都改成 macro task
,对一些有重绘和动画的场景也会有性能影响,如 issue #6813。vue2.5之后版本提供的解决办法是默认使用 micro task
,但在需要时(例如在v-on附加的事件处理程序中)强制使用 macro task
。为什么默认优先使用 micro task
呢,是利用其高优先级的特性,保证队列中的微任务在一次循环全部执行完毕。强制 macro task
的方法是在绑定 DOM 事件的时候,默认会给回调的 handler 函数调用withMacroTask
方法做一层包装 handler = withMacroTask(handler)
,它保证整个回调函数执行过程中,遇到数据状态的改变,这些改变都会被推到 macro task
中。以上实现在 src/platforms/web/runtime/modules/events.js 的 add
方法中,可以自己看一看具体代码。说这么多,不如来个例子,执行参见 CodePen执行以下看看结果:为什么是这样的结果呢,解释一下:同步方式: 当把data中的name修改之后,此时会触发name的 setter
中的 dep.notify
通知依赖本data的render watcher去 update
,update
会把flushSchedulerQueue
函数传递给 nextTick
,render watcher在 flushSchedulerQueue
函数运行时 watcher.run
再走 diff -> patch
那一套重渲染 re-render
视图,这个过程中会重新依赖收集,这个过程是异步的;所以当我们直接修改了name之后打印,这时异步的改动还没有被免费云主机域名 patch
到视图上,所以获取视图上的DOM元素还是原来的内容。setter前: setter前为什么还打印原来的是原来内容呢,是因为 nextTick
在被调用的时候把回调挨个push进callbacks数组,之后执行的时候也是 for
循环出来挨个执行,所以是类似于队列这样一个概念,先入先出;在修改name之后,触发把render watcher填入 schedulerQueue
队列并把他的执行函数 flushSchedulerQueue
传递给nextTick
,此时callbacks队列中已经有了 setter前函数
了,因为这个 cb
是在 setter前函数
之后被push进callbacks队列的,那么先入先出的执行callbacks中回调的时候先执行 setter前函数
,这时并未执行render watcher的 watcher.run
,所以打印DOM元素仍然是原来的内容。setter后: setter后这时已经执行完 flushSchedulerQueue
,这时render watcher已经把改动 patch
到视图上,所以此时获取DOM是改过之后的内容。Promise方式: 相当于 Promise.then
的方式执行这个函数,此时DOM已经更改。setTimeout方式: 最后执行macro task的任务,此时DOM已经更改。注意,在执行 setter前函数
这个异步任务之前,同步的代码已经执行完毕,异步的任务都还未执行,所有的 $nextTick
函数也执行完毕,所有回调都被push进了callbacks队列中等待执行,所以在setter前函数
执行的时候,此时callbacks队列是这样的:[setter前函数
,flushSchedulerQueue
,setter后函数
,Promise方式函数
],它是一个micro task队列,执行完毕之后执行macro task setTimeout
,所以打印出上面的结果。另外,如果浏览器的宏任务队列里面有setImmediate
、MessageChannel
、setTimeout/setInterval
各种类型的任务,那么会按照上面的顺序挨个按照添加进event loop中的顺序执行,所以如果浏览器支持MessageChannel
, nextTick
执行的是macroTimerFunc
,那么如果 macrotask queue 中同时有 nextTick
添加的任务和用户自己添加的 setTimeout
类型的任务,会优先执行 nextTick
中的任务,因为MessageChannel
的优先级比 setTimeout
的高,setImmediate
同理。关于“Vue异步更新机制和nextTick原理实例分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注百云主机行业资讯频道,小编每天都会为大家更新不同的知识点。
相关推荐: Springboot怎么集成minio实现文件存储
本篇内容主要讲解“Springboot怎么集成minio实现文件存储”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Springboot怎么集成minio实现文件存储”吧!MinIO 是一款基于Go语言的高性能对象存…
免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。