js运行机制
1. js同步与异步
-
Js作为浏览器脚本语言,它的主要用途是与用户互动,以及操作DOM,因此js是单线程,也避免了同时操作一个DOM的矛盾问题
- 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
-
Js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
- 如果js只有同步任务,代码是一行一行从上往下执行的,其中一行代码解析过程中耗时较长会导致后面代码无法继续执行页面,等待前面代码执行结束才能继续向下解析,会带来不好的用户体验
- 所以出现了同步与异步任务:同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 其中异步任务又分为宏任务和微任务
举个例子:
因为js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
1 setTimeout(() => { 2 3 console.log('执行console1'); 4 5 },3000) 6 7 console.log('执行console2');
执行结果为 :先 执行console2 ,再 执行console1 原因很简单 因为setTimeout是异步的 ,所有先执行的同步代码 “执行console2”
上面的代码走完我们知道 setTimeout是经过指定时间后,把要执行的任务加入到Event Queue中,又因为单线程的任务需要一个一个的执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。所有有时候并不是设置了指定时间,到了时间就会立马执行的
Event Loop事件循环:
宏任务和微任务关系图:
因此任务执行过程如下:
-
整体会把所有代码分为两个部分:‘同步任务’,‘异步任务’
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外还存在一个任务队列,专门存放异步任务(宏任务和微任务)
-
宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
-
微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会讲这个函数移到Event Queue中
-
整体script作为第一个宏任务进入主线程,当主线程的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就会全部执行,如果没有就执行下一个宏任务
- 主线程不断重复上面的步骤,这就是Event Loop事件循环,只要主线程空了,就会去读取"任务队列"。这个过程会不断重复。
2. js宏任务与微任务
这里需要注意的是new Promise是会进入到主线程中立刻执行
宏任务:script、setTimeOut、setInterval、I/O等
微任务:promise.then、process.nextTick(node),Object.observe,MutationObserver
3. 案例:
1.
打印结果:1,3,5,4,2
整体script作为第一个宏任务进入主线程,遇到console,输出1
继续往下走遇到setTimeout,被分发到宏任务Event Queue中。我们暂且记为setTimeout1
遇到new Promise直接执行,输出3 ,.then 被分发到微任务Event Queue中,我们记为 then1
继续往下遇到console,输出5,此时代码已经走完,第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和3
我们发现 then1 微任务,执行输出4,此时第一轮事件循环结束,第一轮输出结果是1,3,5,4,那么第二轮事件循环从标记setTimeout1宏任务开始,输出2,第二轮事件循环结束
2.
打印结果: 同步宏任务promise、同步宏任务、同步微任务then、异步宏任务promise、异步宏任务、异步微任务then
(不管执行时间是0还是10000,setTimeout是宏任务必须等到主线程的所有代码执行结束才能执行)
3.
打印结果:script start、async1 statrt、async2、promise1、script end、asnyc1 end、promise2、setTimeout
注意:从上往下执行,首先new Promise是同步任务,会被放到主进程中去立即执行,而.then()函数是异步任务会放到异步队列中去,(当你的promise状态结束的时候,就会立即放进异步队列中去),到async关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数,await关键字要在async关键字函数的内部,await写在外面会报错,await如同他的语义,就是在等待,等待右侧的表达式完成,此时的await会让出线程,阻塞async内后续的代码,先去执行async外的代码,等外面的同步代码执行完毕,才会执行里面的后续代码,就算await的不是promise对象,是一个同步函数,也会等这样的操作