Javascript事件循环与Vue的批量异步升级策略
在详情事件循环之前,首先要明确以下几个关键概念。事件循环,同步和异步任务,宏任务,微任务。
一.事件循环
事件循环,是浏览器为了协调事件解决、脚本执行、网络请求和渲染等任务而制定的一套工作机制,它束缚了各种任务的执行顺序。
二.同步任务与异步任务
JavaScript是单线程的,因而所有的任务都要排队执行。为了避免某些任务过于耗时而影响整体性能因而任务有同步和异步之分。同步任务按顺序执行,而异步任务的执行顺序则不肯定是代码书写的顺序。例如:
console.log('a')setTimeout(() => { console.log('b')})console.log('c')Promise.resolve().then(()=>{console.log('d')}) // a c d b因而,为研究异步任务的执行顺序,还要引出宏任务和微任务的概念。
三.宏任务和微任务
宏任务:代表一个个离散的、独立工作单元。主要包括创立主文档对象、解析HTML、执行主线JS代码以及各种事件如页面加载、输入、网络事件和定时器等。浏览器完成一个宏任务,在下一个宏任务执行开始前,会对页面进行重新渲染。
微任务:是更小的任务,是在当前宏任务执行结束后立即执行的任务。假如存在微任务,浏览器会清空微任务之后再重新渲染。微任务的例子有 promise 回调函数、DOM发生变化等。
四.事件循环的具体过程。
JavaScript引擎首先执行主线代码,区分出同步和异步任务。同步任务按代码顺序依次执行。而异步任务则按照宏任务和微任务推入不同的任务队列。前面说到,微任务是在当前宏任务执行结束后立即执行的任务。因而当主线代码执行完毕,微任务队列进入主线程依次执行。若微任务中又包含下属的宏任务或者微任务,则继续推入相应的任务队列。当微任务队列的所有任务执行完毕,浏览器页面重新渲染,并再次进入宏任务队列依次执行。 下面是一个例子:
setTimeout(() => { // 宏任务1 new Promise(resolve => { console.log('promise1') resolve('setTimeout1') }).then((res) =>{ // 微任务2 console.log(res) }) });console.log('main') // 主线的同步任务,直接打印new Promise(resolve => { console.log('promise2') // 直接打印 resolve() }).then(() =>{ // 微任务1 console.log('then') setTimeout(() =>{ // 宏任务2 console.log('setTimeout') }) })console.log('over') // 直接打印上述代码的执行过程如下:
1 .执行主线代码,打印’main’,’promise2′,’over’,微任务队列有微任务1,宏任务队列有宏任务1。
- script宏任务执行完毕,微任务队列开始执行,打印’then’,宏任务2进入宏任务队列。此时微任务队列无任务,宏任务队列有宏任务1,宏任务2。
3 .此时微任务队列一律执行完毕,开始执行宏任务队列,队列有先入先出的性质,因而首先执行宏任务1,打印’promise1’,微任务2 promise.then()进入微任务队列。前面说到,微任务是在当前宏任务执行结束后立即执行的任务,因而立即执行微任务2,打印’setTimeout1’。之后微任务队列无新任务,执行宏任务2,打印’setTimeout’ .至此任务队列所有任务执行完毕,代码执行结束。
打印顺序为:
‘main’,’promise2′,’over’,’then’,’promise1′,’setTimeout1′,setTimeout’
五.Vue的批量异步升级策略
下面详情事件循环在Vue中的实际应用。
Vue有一套批量,异步的升级策略。为什么是批量,异步的呢。我们知道,在Vue中,一个组件对应一个渲染Watcher。在一个升级周期当中,可能有多个组件的数据发生了变化,那么相应的每个组件对应的渲染Watcher都要执行一次。假如一个组件发现变化,立即执行对应的渲染watcher,则浏览器将会渲染一次。当多个组件的数据发生变化时,显然这样的执行逻辑会非常低效,且会影响客户体验。最高效的方式一定是一次性的将所有发生变化的组件的Watcher一起执行。举个不恰当的例子。这个逻辑有些相似于洗衣服,每发现一件脏衣服就立即洗完显然是不明智的。更高效的方式是将脏衣服攒一攒,最后一起洗。那Vue是如何实现批量,异步升级的呢?
只需侦听到数据变化,Vue 将开启一个全局队列,该队列用于缓存在同一事件循环中发生数据变更的组件的渲染Watcher。假如同一个 watcher 被屡次触发,只会被推入到队列中一次。前面说到,浏览器每执行完一个宏任务及其相关的微任务,就会刷新一次。因而我们将该队列放在一个微任务中执行,当执行完主线代码,再将微任务队列中的watcher队列依次执行。最后浏览器刷新且只刷新一次就完成所有组件的升级。Vue 在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,假如执行环境以上都不支持,则会采用setTimeout(fn,0)代替。
下面用简化过的代码实现一下批量异步升级。
1.首先创立一个全局队列,用于存放渲染Watcher。还要创立一个全局哈希结构用于标记Watcher,便于去重。
let hash = {} // 存储watcher队列的标识let watcherQueue = [] /* 只需侦听到数据变化,将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。批量升级时假如同一个watcher被屡次触发,只会被推入到队列中一次。 */2.实现缓存Watcher和清空Watcher队列的方法
function flusQueue() { watcherQueue.forEach(watcher => watcher.run()) // 批量升级 升级完毕清空队列 hash = {} watcherQueue = []}function queueWatcher(watcher) { let id = watcher.id if (!hash[id]) { hash[id] = true watcherQueue.push(watcher) } //nextTick方法将flusQueue放入微任务中执行 nextTick(flusQueue)}3.实现nextTick
export function nextTick(cb) { let aysncFn = () => { cb(); } // 使用常见的微任务,要判断浏览器环境能否支持 if (Promise) { console.log('promise 异步执行') Promise.resolve().then(aysncFn) } if (MutationObserver) { let observe = new MutationObserver(aysncFn) let textNode = document.createTextNode(1) observe.observe(textNode, { characterData: true }) textNode.textContent = 2 return } if (setImmediate) { return setImmediate(aysncFn) } setTimeout(aysncFn, 0)}4.最后实现Watcher的缓存,我们知道在响应式数据发生变化的派发升级过程中会触发Watcher的update方法,我们就在该方法中实现缓存。
export default class Watcher { // 观察者 ... update() { queueWatcher(this) }... }1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Javascript事件循环与Vue的批量异步升级策略