WEB前端基础必备:带你了解JavaScript中的Event Loop

image.png

前言

同步与异步问题应该是每一个前端工程师在实际开发中都会遇到的,除了会用,还要知道其原理才能在解决一些比较坑的地方(相信实际开发中应该不少)能够游刃有余。

本文将介绍:

1.进程与线程的概念。
2.浏览器中的Event Loop
3.Node的Event Loop与浏览器到底哪里不一样

本文是个人在工作之余的一篇总结,如果有错误、缺漏的地方,欢迎大家和我交流,感激不尽!

什么是进程?什么是线程?
我们常说javascript是单线程的?那么到底什么是进程?什么是线程?
本质上来说,进程和线程都是CPU内分配的一段工作时间

进程(process)是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位
线程(thread)是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源,是执行一段指令所需要的时间。

以Chrome浏览器为例,主要的进程有4个:

浏览器进程 (Browser Process):负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。
渲染进程 (Renderer Process):负责一个Tab内的显示相关的工作,也称渲染引擎。
插件进程 (Plugin Process):负责控制网页使用到的插件
GPU进程 (GPU Process):负责处理整个应用程序的GPU任务

而javaScript 引擎线程是工作在渲染进程下的。

JS单线程带来了什么好处
JS在运行的时候可能会阻止UI渲染。JS是可以修改DOM结构的,如果在UI渲染线程还在工作的时候,就可能导致不能安全的渲染UI。
优点:可以节约内存。可以节约上下文切换时间。没有锁的问题。
虽然javascript是单线,但是javascript中有同步和异步的概念,解决了js阻塞的问题。

执行栈(什么是执行栈)
同时我们要了解一下执行栈的概念,可以认为执行栈是一个存储函数调用的栈结构
当我们执行 JS 代码的时候就是往执行栈中放入函数

那么遇到异步代码的时候怎么办?
其实当遇到异步代码时,会被挂起并在需要执行的时候加入到Task(有多种task)队列中
Event Loop
在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
微任务: process.nextTick(), promise, MutationObserver
宏任务: script, setTimeout, setInterval, setImmediate, I/O, UI render

一道题引发的思考

console.log('script start');

async function async1() {
    await async2();
    console.log('async1 end');
};

async function async2() {
    console.log('async2 end');
};

async1()

setTimeout(() => {
    console.log('setTimeout')
}, 0)

new Promise((resolve, reject) => {
    console.log('promise start');
    resolve()
})
.then(() => console.log('promise end'))

console.log('script end')

浏览器会依次打印出: script start、async2 end、 promise start、script end、async1 end、promise end、setTimeout

这里我们各个阶段来分析一下:

首先执行全局script代码,这是第一轮事件。打印出 script start
执行async1方法,因为await后的方法会被立即执行(可以看作promise的executor,会与script代码一起执行),会打印出asycn2 end,并将await后的代码加入微任务队列。遇到setTimeout会被加入宏任务队列,继续往下执行直接打印出promise start.并将已经resolve的then回调函数加入微任务队列,最后执行script end。

清空由于上一个宏任务产生的微任务队列,根据代码上下文顺序打印出:async1 end、promise end

最后清空宏任务队列打印出:setTimeout

简单总结一下:一旦执行栈为空, Event loop就会从Task队列中拿出需要执行的代码并放到执行栈中执行。
因此浏览器的事件循环机制遵循以下步骤:

1.先执行全局script同步代码,这属于宏任务
2.执行栈为空,查询是否有异步代码需要执行
3.执行所有的微任务
4.执行完所有的微任务后,如果有必要的话会渲染页面
5.开始下一轮的Event-loop

所以本质上来说,JS中的异步其实还是同步
那么微任务快于宏任务吗
script(同步代码)也属于宏任务 接下来有异步代码的话就先执行微任务。所以微任务快于宏任务是建立在异步的前提下的。

Node中的 Event Loop 和 浏览器中的 有什么区别?
浏览器和Node中Event Loop其实是不相同的。

node中的Event Loop分6个步骤:

   ┌───────────────────────┐
┌─>│        timers         │<————— 执行 setTimeout()、setInterval() 的回调
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     idle, prepare     │<————— 内部调用(可忽略)
│  └──────────┬────────────┘     
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|             |                   ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │ - (执行几乎所有的回调)
│  │         poll          │<─────┤  connections, │ 
│  └──────────┬────────────┘      │   data, etc.  │ 
│             |                   |               | 
|             |                   └───────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|  ┌──────────┴────────────┐      
│  │        check          │<————— setImmediate() 的回调将会在这个阶段执行
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
└──┤    close callbacks    │<————— socket.on('close', ...)
   └───────────────────────┘

这里看着可能有点复杂,但是我们先记住以下两点:

在node中微任务一定比宏任务先执行
对于微任务来说,它会在以上6个阶段的每个阶段完成之前清空微任务队列

举个栗子,方便理解:

fs.readFile(__dirname, () => { // fs.readFile 称为I/O
    setTimeout(() => {
        console.log('setTimeout'); // 2
    }, 0)
    setImmediate(() => {
        console.log('setImmediate'); // 1
    })
});

我们分析一下以上代码:首先fs.readFile对应上图的poll阶段,因此会继续往下执行遇到check阶段执行其回调函数首次打印setImmediate,随后执行timers阶段执setTimeout回调打印setTimeout。

process.nextTick()执行顺序?
process.nextTick()是一个特殊的异步API,他不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。
同样,我们以一道题为例:

setTimeout(() => {
 console.log('timer1')
 Promise.resolve().then(function() {
   console.log('promise1')
 })
}, 0)
process.nextTick(() => {
 console.log('nextTick')
 process.nextTick(() => {
   console.log('nextTick')
   process.nextTick(() => {
     console.log('nextTick')
     process.nextTick(() => {
       console.log('nextTick')
     })
   })
 })
})

各个阶段进行分析:
在node中微任务一定比宏任务先执行,因而执行process.nextTick回调函数打印出nextTick,并继续执行内部的process.nextTick回调直到清空该队列,随后进入timers阶段按顺序打印出timer1、promise1
区别
浏览器和Node环境下,微任务任务队列的执行时机不同:

Node,微任务在事件循环的各个阶段之间执行
浏览器端,微任务在事件循环的宏任务执行完之后执行

总结

学习往往只停留在表面是不够的,写这篇文章的时候也参考了很多其他大佬的文章,但是观看还是不够的,还需要自己去实验,去重新捋一遍。如果大家有什么问题,欢迎大家加入我的前端学习交流群(109029339)一起学习讨论~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355