重拾js-进程/线程,js执行/Event Loop

126 阅读6分钟

简言

最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记消除自己以前学习知识点的误区

进程/线程

进程

cpu分配资源的最小单位,进程和进程之间相互独立,进程下管理着多个线程

线程

cpu调度资源的最小单位,具体的工作由线程来执行,多个线程共享cpu分配给进程的资源

浏览器新开tab是进程还是线程?

浏览器新开tab是进程,进程之间相互独立,不影响cpu分配给各自的资源。如果新开tab是线程的话,那么假如一个线程占用资源时间长,可能导致内存溢出,造成整个浏览器的崩溃

浏览器渲染原理

浏览器可以通过开启多个tab,构建多个页面,那么每个页面都构成实际都是一个独立的进程,进程下又控制着多个线程来完成页面的搭建工作

构建页面进程下大致可以归类为5类线程

  1. GUI线程

    解析HTML文档,构建CSS样式,并由构建的DOM tree和样式计算后生成的CSSOM tree生成render tree,再根据布局,分层,分块,栅格化,合成等流程将页面渲染到浏览器页面上;平时我们所说的减少回流重绘也是在这个过程中出现的;

    回流:布局阶段-改变了节点的位置大小等信息,例如position,width,height,font-size等

    重绘:改变样式信息,例如color,background

  2. JS线程(JS引擎)

    用于解析script,以及执行js代码,并处理来自于后续任务队列(task queue)中的待处理逻辑,不过JS线程执行会阻塞GUI线程,因为这是为了防止渲染错乱

image.png

  1. 定时器线程(setTimeout,setInterval)

    用于接收来自于JS线程的异步定时器任务,但是它只做定时器的计时,不做异步任务,当计时完成之后,将结果交给事件处理线程

  2. 异步网络线程

    用于接收来自JS线程的异步请求任务,执行异步请求操作,当结果返回之后将回调任务交给事件处理线程

  3. 事件处理线程

    将接收到的来自异步操作线程的任务按照接收顺序,以及添加到 task queue 中

下面是上述的图片过程展示

image.png

同步异步/Event Loop

看上面的图,是不是很像Event Loop事件循环的示意图?

接下来就讲一下js中的同步异步,以及Event Loop(事件循环)

同步/异步

js引擎是单线程,意味任务只能一个一个执行

但js引擎在执行js代码过程中实现了同步和异步,这就令js有了处理复杂逻辑时发生阻塞的解决办法

同步执行:代码从上到下依次执行,并且遇到复杂或高优先级任务时会阻塞后续代码执行(复杂循环,渲染任务等)

image.png

异步执行:代码从上到下依次执行,遇到复杂任务时,将复杂任务挂起(js引擎交由由异步线程执行),js引擎并继续执行后续同步代码代码;当异步任务执行结束之后,将回调结果交给js线程继续处理

image.png

Event Loop

而要支持js引擎不断去管理并处理异步线程回调结果,那么就需要 执行栈(Call Stack)任务队列(task queue) 的通力配合,因此形成了事件循环(Event Loop)

但我更愿意叫 事件的持续处理 - 强调事件的连贯而不是循环

Event Loop 分为两部分,执行栈(Call Stack)和任务队列(task queue),如果细分的话按下图所示

image.png

这张图中分为了5部分

  1. Memory Heap 堆内存
  2. js引擎
  3. Call Stack 执行栈
  4. 异步线程
  5. task queue 任务队列

Memory Heap(堆内存)

代码开始执行时会使用进程分配的内存资源存储如Object,Function,Array等结构化数据,并提供堆中的地址给执行栈中的逻辑使用

js 引擎

js引擎(js线程)用于创建执行代码的调用栈并添加js代码执行,当调用栈为空时检查任务队列是否有待处理任务,如果有,则依次从任务队列的队头取出任务添加到调用栈执行

Call Stack(调用栈)

调用栈的js代码同步执行,过程中如果遇到异步任务,则将异步任务添加到对应的异步线程中,继续执行剩余代码,直到将所有同步代码执行完毕

异步线程

异步线程接收到来自调用栈的异步任务后,进行异步处理,等待结果返回后,通过回调的方式将任务添加到任务队列的队尾

task queue

任务队列遵循先入先出原则,类似排队结账,后来者排队尾,先到者先结账。

任务队列存储的一定是异步任务的回调结果(异步I/O,Http,定时器等)

那既然Event Loop这么全面了,那为什么还要有宏任务和微任务呢?

这就要说到任务的紧急性了

看下面这个例子

setTimeout(()=>{
    console.log(1)
    
    setTimeout(()=>{
        console.log(2)
    })
})

setTimeout(()=> {
    console.log(3)
})
// 结果为 1 3 2

如果console.log(2)是一个紧急任务,必须要在console.log(3)之前执行怎么办?

这就是微任务出现的原因

为了解决处在任务队列中任务优先级,时效性的问题

setTimeout(()=>{
    console.log(1)
    
    new Promise((resolve)=>{
        resolve()
    }).then(()=>{
        console.log(2)
    })
})

setTimeout(()=> {
    console.log(3)
})

// 结果为 1 2 3

那么有微任务就一定有宏任务

宏任务:每个任务都可以看成是宏任务

  1. 可以形成宏任务的操作有:

    定时器,异步I/O,数据库读取操作等

  2. 可以形成微任务的操作有:

    Promise,defineProperty,Proxy等

当执行栈中的同步代码执行完成之后,js引擎会从task queue队头中取出第一个任务(宏任务)到执行栈中,宏任务中的代码同步执行,当遇到微任务操作时,会将微任务添加到微任务队列中,当同步代码执行完成之后,js引擎检查微任务队列中是否有未执行的任务,如果有则依次取出执行,当微任务队列被清空后,js引擎会清空调用栈,从任务队列中取出下一个宏任务