1 | setTimeout(function() { console.log(1) }, 0); |
- 执行
promise
实例内部的代码, 输出2
; - 顺序执行后面的代码
console.log(3)
, 输出3
; - 执行
console.log(5)
, 代码输出5
; - 执行
resolve
函数, 执行resolve
函数中的代码console.log(4)
, 输出4
; - 最后执行
setTimeout
中的代码, 代码执行console.log(1)
, 输出1
; 为什么会按照上面的顺序执行代码, 下面将要进行详细的讲解:
js 中的线程
在 js 中的线程和浏览器中的线程是不同的, 在 js 中是单线程, 在浏览器是多线程的。js 的单线程是指所有的 js 代码都是在 js 引擎上面的一个主线程上面运行的,js 同时只能执行一个任务, 其他的任务则会排队进行等待执行。这些任务被放在一个任务队列中等待执行。在浏览器中, 包括下面这些线程:- js 引擎线程(例如 v8 引擎)
- UI 渲染线程
- 浏览器事件触发线程
- 定时触发器线程
- http 请求线程
- UI渲染线程用于渲染页面、解析 HTMl CSS, 创建 DOM 树。当页面元素发生重构或者回流的时候, 这个线程就会执行, 重新渲染页面。
- js引擎用于执行 js 脚本代码,等待任务队列中的任务到来, 并且加以处理
- 浏览器事件触发线程用于控制用户, 响应交互,当 js 引擎执行代码遇到相关事件的时候, 会将对应的任务添加到事件线程中, 当任务符合触发条件被触发的时候, 触发的任务会被添加到任务队列的队尾, 等待 js 引擎执行完成主线程上面的任务之后执行。
- 定时触发器线程用于对于
setTimeout
或者setInterval
进行计数, 因为 js引擎是单线程的, 所以自然计数的任务就不能有 js 引擎来完成, 而是由浏览器单独开出一个定时触发器线程用于计数, 当计数完毕之后, 会将计数完成之后的函数添加到任务队列尾部, 等待 js引擎执行完成主线程上面的任务之后执行。这里也就是说有个常见的问题: setTimeout(() => {}, 0);回调是立即执行的吗?并不是, 因为, 需要js 引擎执行完主线程上面的任务之后, 才会执行 任务列表中的任务。
- http 请求线程, ajax 是委托给浏览器新开一个 http 线程
setTimeout
setTimeout
在 js 中的作用是用来延迟代码执行, 规定代码在延迟多少时间之后执行回调函数代码,在上面关于线程的讲解中, 我们知道浏览器的定时触发器线程会在延迟时间达到之后将回调事件添加到js引擎中的任务队列中, 而在 js 引擎中, 引擎会在执行完成主线程上面的任务之后执行任务队列中的事件, 因此,当代码中存在 setTimeout
的时候, 内部的回调函数会在其他代码执行完毕之后才执行, 尽管我们将延迟时间设为0的情况也是如此:有如下代码:1 | setTimeout(function () { console.log(2) }, 0); |
setTimeout
中的函数会等到 console.log(1)
执行完成之后执行结果。js 中的事件运行机制
我们知道js是单线程运行的, 那么具体的运行机制是如何的?我们需要知道下面这些概念:- js 中分为同步任务和异步任务
- 同步任务都是在主线程上面执行, 形成一个执行栈
- 在主线程之外, 事件触发线程管理着一个任务队列, 当异步任务有了运行结果时, 就在任务队列中添加一个事件
- 当执行栈中的所有的同步任务执行完毕之后, 任务队列中的任务将会添加到执行栈中, 开始执行
js 中的 macrotask
与 microtask
在 js 中, 存在两种任务类型: macrotask
(宏任务) 和 microtash
(微任务), 这两种任务类型的区别在于执行任务的时机是不同的。macrotask
: 宏任务可以理解为执行栈中执行的任务, 在执行任务期间不会中断任务, 浏览器为了能够使 js 内部task与 dom 能够有序的执行, 在执行完成任务之后会进行渲染,1
task ---> 渲染 ---> task
microtask
微任务会在宏任务执行完毕之后, 进行渲染之前执行
macrotask
与 microtask
中分别包含的几种任务类型:macrotask
: 代码块,setTimeout
,setInterval
等microtask
:Promise