JavaScript事件循环(Event Loop)

134次阅读

共计 1757 个字符,预计需要花费 5 分钟才能阅读完成。

任务队列
首先我们要知道关于 JavaScript 的一些规则:

JavaScript 是被设计成单线程的
JavaScript 的任务分为同步任务和异步任务

同步任务都在主线程上执行,形成一个执行栈。当主线程执行完之后,运行微任务(micro-task)队列的任务直到为空,更新 UI 渲染(会根据浏览器的逻辑,决定要不要马上执行更新),然后再运行宏任务(macro-task)队列的任务直到为空 …… 流程如下:
(主线程上的执行栈同步任务,可以视为是第一个 macro-task 队列)
macro-task -> micro-task(如果存在) -> 更新 UI 渲染
如此无限循环上面的流程,是为 JavaScript 的 Event Loop 机制。
宏任务
宏任务(macro-task),宏任务队列可以有一个或者多个。每个任务都有一个任务源 (task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。
宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任务
微任务(micro-task),微任务在渲染更新前,macro-task 之后执行。关于 async 和 await,因为 async await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是 microtask。实际上 await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。
微任务:process.nextTick, Promise, Object.observer, MutationObserver,await.
举例
async function async1() {
console.log(‘async1 start’);
await async2();
console.log(‘async1 end’);
}
async function async2() {
console.log(‘async2’);
}

console.log(‘script start’);

setTimeout(function () {
console.log(‘setTimeout’);
}, 0)

async1();

new Promise(function (resolve) {
console.log(‘promise1’);
resolve();
}).then(function () {
console.log(‘promise2’);
});
console.log(‘script end’);
流程如下:

console 打印 script start

setTimeout,是异步宏任务,进入 macro-task setTimeout 队列
async1(), async await 函数,在 await 之前是同步任务,直接执行,打印 async1 start

await async2(),await 后面的表达式会先执行一遍,打印 async2

await 下面的代码视为 promise.then,进入 micro-task promise 队列,跳出 async1()
new Promise,promise 内,.then 之前的代码是直接执行的,所以打印 promise1

.then 内函数进入 micro-task promise 队列后
console,直接打印 script end

主线程执行栈运行完并清空了,micro-task 进入执行栈,分别按顺序执行打印 async1 end 和 promise2。
micro-task 队列清空,macro-task 进入执行栈,打印 setTimeout,程序运行完毕。

完整结果如下:
/**
*script start
*async1 start
*async2
*promise1
*script end
*async1 end
*promise2
*setTimeout
*/
该结果基于 chrome 版本 72.0.3626.121。因为 async await 标准有所改变,所以稍老版本的浏览器结果可能不一致。
参考:https://jakearchibald.com/201…https://github.com/Advanced-F…

正文完
 0

JavaScript事件循环(Event Loop)

134次阅读

共计 3391 个字符,预计需要花费 9 分钟才能阅读完成。

1、为什么要有事件循环?
因为 js 是单线程的,事件循环是 js 的执行机制,也是 js 实现异步的一种方法。
既然 js 是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理 js 任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:

同步任务
异步任务

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。
2、宏任务与微任务
JavaScript 中除了广泛的同步任务和异步任务,我们对任务有更精细的定义:

macro-task(宏任务): 包括整体代码 script,setTimeout,setInterval

micro-task(微任务): Promise,process.nextTick

不同的类型的任务会进入不同的 Event Queue(事件队列),比如 setTimeout、setInterval 会进入一个事件队列,而 Promise 会进入另一个事件队列。
一次事件循环中有宏任务队列和微任务队列。事件循环的顺序,决定 js 代码执行的顺序。进入整体代码 (宏任务 -<script> 包裹的代码可以
理解为第一个宏任务 ),开始第一次循环,接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列的任务执行完毕,
再执行所有的微任务。如:
<script>
setTimeout(function() {
console.log(‘setTimeout’);
})

new Promise(function(resolve) {
console.log(‘promise’);
}).then(function() {
console.log(‘then’);
})

console.log(‘console’);

/* —————————- 分析 start——————————— */

1、`<script>` 中的整段代码作为第一个宏任务,进入主线程。即开启第一次事件循环
2、遇到 setTimeout,将其回调函数放入 Event table 中注册,然后分发到宏任务 Event Queue 中
3、接下来遇到 new Promise、Promise,立即执行;将 then 函数分发到微任务 Event Queue 中。输出: promise
4、遇到 console.log,立即执行。输出: console
5、整体代码作为第一个宏任务执行结束,此时去微任务队列中查看有哪些微任务,结果发现了 then 函数,然后将它推入主线程并执行。
输出: then
6、第一轮事件循环结束
开启第二轮事件循环。先从宏任务开始,去宏任务事件队列中查看有哪些宏任务,在宏任务事件队列中找到了 setTimeout 对应的回调函数,
立即执行之。此时宏任务事件队列中已经没有事件了,然后去微任务事件队列中查看是否有事件,结果没有。此时第二轮事件循环结束;
输出:setTimeout

/* —————————- 分析 end——————————— */
</script>
3、分析更复杂的代码
<script>
console.log(‘1’);

setTimeout(function() {
console.log(‘2’);
process.nextTick(function() {
console.log(‘3’);
})
new Promise(function(resolve) {
console.log(‘4’);
resolve();
}).then(function() {
console.log(‘5’)
})
})
process.nextTick(function() {
console.log(‘6’);
})
new Promise(function(resolve) {
console.log(‘7’);
resolve();
}).then(function() {
console.log(‘8’)
})

setTimeout(function() {
console.log(‘9′);
process.nextTick(function() {
console.log(’10’);
})
new Promise(function(resolve) {
console.log(’11’);
resolve();
}).then(function() {
console.log(’12’)
})
})
</script>
一、第一轮事件循环
a)、整段 <script> 代码作为第一个宏任务进入主线程,即开启第一轮事件循环
b)、遇到 console.log,立即执行。输出:1
c)、遇到 setTimeout,将其回调函数放入 Event table 中注册,然后分发到宏任务事件队列中。我们将其标记为 setTimeout1
d)、遇到 process.nextTick,其回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 process1
e)、遇到 new Promise、Promise,立即执行;then 回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 then1。
输出: 7
f)、遇到 setTimeout,将其回调函数放入 Event table 中注册,然后分发到宏任务事件队列中。我们将其标记为 setTimeout2
此时第一轮事件循环宏任务结束,下表是第一轮事件循环宏任务结束时各 Event Queue 的情况


宏任务事件队列
微任务事件队列

第一轮事件循环
(宏任务已结束)
process1、then1

第二轮事件循环 (未开始)
setTimeout1

第三轮事件循环 (未开始)
setTimeout2

可以看到第一轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行
g)、执行 process1。输出:6
h)、执行 then1。输出:8
第一轮事件循环正式结束!

二、第二轮事件循环
a)、第二轮事件循环从宏任务 setTimeout1 开始。遇到 console.log,立即执行。输出: 2
b)、遇到 process.nextTick,其回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 process2
c)、遇到 new Promise,立即执行;then 回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 then2。输出: 5
此时第二轮事件循环宏任务结束,下表是第二轮事件循环宏任务结束时各 Event Queue 的情况


宏任务事件队列
微任务事件队列

第一轮事件循环 (已结束)

第二轮事件循环
(宏任务已结束)
process2、then2

第三轮事件循环 (未开始)
setTimeout2

可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行
d)、执行 process2。输出:3
e)、执行 then2。输出:5
第二轮事件循环正式结束!

三、第三轮事件循环
a)、第三轮事件循环从宏任务 setTimeout2 开始。遇到 console.log,立即执行。输出: 9
d)、遇到 process.nextTick,其回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 process3
c)、遇到 new Promise,立即执行;then 回调函数放入 Event table 中注册,然后被分发到微任务事件队列中。记为 then3。输出: 11
此时第三轮事件循环宏任务结束,下表是第三轮事件循环宏任务结束时各 Event Queue 的情况


宏任务事件队列
微任务事件队列

第一轮事件循环 (已结束)

第二轮事件循环 (已结束)

第三轮事件循环 (未开始)
(宏任务已结束)
process3、then3

可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行
d)、执行 process3。输出:10
e)、执行 then3。输出:12

4、参考文章
https://juejin.im/post/59e85e…

正文完
 0

Javascript 时间循环event loop

134次阅读

共计 1769 个字符,预计需要花费 5 分钟才能阅读完成。

都知道 javascript 是单线程,那么问题来了,既然是单线程顺序执行,那怎么做到异步的呢?
我们理解的单线程应该是这样的,排着一个个来,是同步执行。

现实中 js 是这样的
setTimeout(function() {
console.log(1);
});
new Promise(function(resolve, reject) {
console.log(2)
resolve(3)
}).then(function(val) {
console.log(val);
})
console.log(4)
// 执行结果为 2、4、3、1
结果告诉我们,js 是单线程没错,不过不是逐行同步执行。
那我们就来解析一下既然有异步,那顺序是怎样的?这些执行顺序规则就是理解 eventLoop 的要点,继续往下。

上图为我录制的 chrome 控制代码台执行顺序,虽然能看出执行顺序但我们还是懵逼的,我们不知道规则,不懂就要问。
搜索了很多官方、个人博客得到了一堆词:js 引擎、主线程、事件表、事件队列、宏任务、微任务,彻底懵逼。。。
不急不急一个个来,我们进入刨根问底状态
js 引擎
总结一句话就是解析优化代码 ** 制定执行规则 具体规则往下看
主线程
总结一句话执行 js 引擎优化并排列顺序后的代码
事件表(event table)
执行代码过程中,异步的回调,例如(setTimeout,ajax 回调)注册回调事件到 event table
事件队列
当事件回调结束,事件表(event table)会将事件移入到事件队列(event queue)
宏任务和微任务
宏任务包含的事件

事件
浏览器
node

I/O

setTimeout

setInterval

setImmediate

requestAnimationFrame

微任务包含的事件

事件
浏览器
node

I/O

process.nextTick

MutationObserver

Promise.then catch finally

很多博客是这样说的:浏览器会不断从 task 队列中按顺序取 task 执行,每执行完一个 task 都会检查 microtask 队列是否为空(执行完一个 task 的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有 microtask。然后再进入下一个循环去 task 队列中取下一个 task 执行
说实话不是太理解,那么我就以自己的方式去学习和理解
为了更好的理解我们再看代码
console.log(‘1’);
setTimeout(function() {
console.log(‘2’);
new Promise(function(resolve) {
console.log(‘3’);
resolve();
}).then(function() {
console.log(‘4’)
})
})
new Promise(function(resolve) {
console.log(‘5’);
resolve();
}).then(function() {
console.log(‘6’)
})

setTimeout(function() {
console.log(‘7’);
new Promise(function(resolve) {
console.log(‘8’);
resolve();
}).then(function() {
console.log(‘9’)
})
})
// 执行结果:1、5、6、2、3、4、7、8、9
有图为证我没骗你再来个动图我们具体看看浏览器的执行顺序
首先 js 引擎,区分是直接执行(同步代码),再执行异步代码,如果是异步再区分是宏任务还是微任务,分别放入两个任务队列,然后开始执行,每执行完一个宏任务,扫一遍微任务队列并全部执行,此时形成一次 eventLoop 循环。以此规则不停的执行下去就是我们所听到的事件循环。
我再补充一点,可以理解 js 引擎一开始把整个 script 当做一个宏任务,这样里边的就更容易理解了,开始就执行 script 宏任务,解析到宏任务里边又包含同步代码和异步代码(宏任务和微任务)依次执行顺序形成 eventLoop。
欢迎吐槽点赞评论!
文章参考学习:https://www.jianshu.com/p/12b…https://juejin.im/post/59e85e…https://segmentfault.com/a/11…

正文完
 0