js执行机制:事件循环实现异步

99 阅读5分钟

搞懂js运行机制,明确代码的输出内容和顺序

js是一门单线程语言

Event Loop 是js的执行机制

每个时间点,系统只会处理一个事件

js是单线程语言,按照语句出现的顺序执行的。所有异步,都是用同步的方法去模拟实现

1.进程和线程:

计算机的核心是CPU,承担所有的计算任务

单个cpu一次只能运行一个任务:cpu像一个工厂,即单个工厂开工

CPU总是运行一个进程,其他进程处于非运行状态:进程好比工厂的车间,是所能处理的单个任务

一个进程可以包括多个线程:一个车间有很多工人,协同完成一个任务,线程好比车间的工人

一个进程的内存空间是共享的,每个线程都可以使用这些共享内存,车间的空间是工人共享的,许多的房间是每个工人都可以进出的

一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这块的内存

防止多个线程同时读写某一块内存区域,防止另外进入,加上互斥锁(Mutual),排队等待再进去,

某些内存区域,只能供给固定数目的线程使用,多出来的需要排队等候信号量(Semaphore),保证多个线程不会互相冲突

2.macrotask(宏任务) 和 microtask(微任务)

Macrotasks包含生成dom对象、解析HTML、执行主线程js代码、更改当前URL还有其他的一些事件如页面加载、输入、网络事件和定时器事件。

macrotask代表一些离散的独立的工作。当执行完一个task后,浏览器可以继续其他的工作如页面重渲染和垃圾回收。

Microtasks则是完成一些更新应用程序状态的较小任务,如处理promise的回调和DOM的修改,这些任务在浏览器重渲染前执行。Microtask应该以异步的方式尽快执行,其开销比执行一个新的macrotask要小。Microtasks使得我们可以在UI重渲染之前执行某些任务,从而避免了不必要的UI渲染,这些渲染可能导致显示的应用程序状态不一致。

3.事件循环:

Event loop 是JS采用机制,用来解决单线程运行造成的一些问题

一般情况下一个进程一次只能执行一个任务

如果有多任务需要执行,不外乎三种解决方法:

1.排队

2.新建进程,使用fork命令,为每个任务新建一个进程

3.新建线程,因为进程耗费资源,多一个进程包含多个线程,由线程去完成任务

js是单线程执行,Event loop的提出:

简单说就是程序中设置两个线程:

一个是负责程序本身的运行,成为’主线程‘,

另一个是负责主线程与其他线程的通信(EVENT loop)的消息线程,不断重复执行

用户自定义的回调函数,通常在浏览器的默认动作之前触发

js按照语句出现的顺序执行

js同步及异步得执行过程:

事件循环:

同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册回调函数。

经过指定时间当指定的事情完成时,任务队列Event Table会将这个函数移入Event Queue回调函数队列。

主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,检查是否有等待被调用的函数,如果有则进入主线程执行。

上述过程会不断重复,也就是常说的Event Loop(事件循环)。

调用执行栈:所有代码都会被放到执行栈中执行。后进先出,调用栈内存放的是代码执行期间的所有执行上下文。

执行机制:

Snipaste_2023-04-04_14-41-08.jpg

任务队列

2.setTimeout

setTimeout(fn,0)指定某个任务在主线程最早可得得空闲时间执行,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行

3.setInterval

setInterval(fn,ms)循环的执行,会每隔指定的时间将注册的函数置入Event queue,如果前面的任务号是太久,同样需要等待。不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue

4.Promise 与process.nextTick(callback)

process.nextTick:在事件循环的下一次循环中调用callback

Promise,new Promiss直接执行,then函数发布到微任务Event Queue

立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务

promise中的代码是被当作同步任务立即执行的

在async/await中,await出现之前及本身,其中的代码也是立即执行的

await之后的代码是微任务,会放入到任务队列

左右等价

不同类型的任务会进入对应的Event Queue

除了广义的同步任务和异步任务,对任务更精细的定义:

宏任务和微任务:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise.then,process.nextTick

事件循环,宏任务,微任务的关系如下图:

Snipaste_2023-04-04_14-41-47.jpg

事件循环时,一轮循环结束,会执行所有微任务

事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从一个宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务

例1:

clipboard.png

clipboard111.png