前端八股文之事件循环js版

JavaScript 是一门单线程语言,可是浏览器又能很好的处理异步请求,这应该如何理解?

线程与进程

讲到这里不得不提到线程与进程了,我听到小码哥很形象的比喻

  • 进程相当于一列火车,这列火车的车厢相当于进程
  • 线程是在进程下进行的,单纯的车厢无法运行
  • 一个进程可包含多个线程,好比一列火车有多个车厢
  • 不同进程之间的数据很难共享,比喻一列火车的乘客很难换到另外一列火车上
  • 同一进程下,不同线程间的数据很容易共享,车厢的乘客可以换成到其他车厢
  • 进程比线程消耗更多的计算机资源,可以理解多列火车和多个车厢的比较
  • 进程之间不会互相影响,但一个线程挂掉了,整个进程都挂掉了
浏览器执行线程
  1. 浏览器是多进程的,浏览器每一个 tab 标签都代表一个独立的进程,其中浏览器渲染进程(浏览器内核)属于浏览器多进程中的一种,主要负责页面渲染,脚本执行,事件处理等
  2. 其包含的线程有:GUI 渲染线程(负责渲染–解析 HTML,CSS 构成 DOM 树)、JS 引擎线程、事件触发线程、定时器触发线程、http 请求线程等主要线程
关于执行中的线程:

主线程:也就是 js 引擎执行的线程,这个线程只有一个,页面渲染、函数处理都在这个主线程上执行。
工作线程:也称幕后线程,这个线程可能存在于浏览器或js引擎内,与主线程是分开的,处理文件读取、网络请求等异步事件
在这里插入图片描述

任务队列( Event Queue )

所有的任务可以分为同步任务和异步任务
同步任务:顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;
异步任务:就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。
主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。这也是开场提到了,js单线程如何理解处理异步任务;
上述过程的不断重复就是我们说的 Event Loop (事件循环)。在事件循环中,每进行一次循环操作称为tick

在这里插入图片描述
宏任务主要包含:主代码块 > setImmediate(Node.js 环境) > MessageChannel > setTimeout / setInterval;I/O;UI 交互事件
微任务主要包含:process.nextTick(Node.js 环境) > Promise > MutationObserver

举例 学以致用
题目1
console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
遇到 console.log,输出 script end

至此,Event Queue 中存在三个任务:宏任务:setTimeout 微任务:then1、then2

执行微任务,首先执行then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了所有微任务
执行下一个宏任务 setTimeout 任务,输出 setTimeout 至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout

题目2
console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

首先,事件循环从宏任务 (macrotask) 队列开始,最初始,宏任务队列中,只有一个 script(整体代码)任务;
当遇到任务源 (task source) 时,则会先分发任务到对应的任务队列中去。所以,就和上面例子类似,首先遇到了console.log,输出 script start;
接着往下走,遇到 setTimeout 任务源,将其分发到任务队列中去,记为 timeout1; 接着遇到 promise,new promise 中的代码立即执行,输出 promise1, 然后执行 resolve ,遇到 setTimeout ,将其分发到任务队列中去,记为 timemout2, 将其 then 分发到微任务队列中去,记为 then1;
接着遇到 console.log 代码,直接输出 script end 接着检查微任务队列,发现有个 then1 微任务,执行,输出then1 再检查微任务队列,发现已经清空,则开始检查宏任务队列,执行 timeout1,输出 timeout1; 接着执行 timeout2,输出 timeout2 至此,所有的都队列都已清空,执行完毕。
其输出的顺序依次是:script start, promise1, script end, then1, timeout1, timeout2

** 转载自知乎-废柴码农https://zhuanlan.zhihu.com/p/87684858写得很好,通俗易懂

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值