JS 是单线程
JavaScript 是一种单线程的编程语言,同一时间只能做一件事,所有任务都需要排队依次完成。
为什么 JS 不能有多个线程呢?
答:作为浏览器脚本语言,JS 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JS 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?为了避免这种复杂性,因此 JS 只能是单线程。
事件循环机制(Event Loop)
含义:告诉了我们 JS 代码的执行顺序,是指浏览器或 Node 的一种解决 JS 单线程运行时不会阻塞的一种机制。
Event Loop事件循环,其实就是 JS引擎 管理事件执行的一个流程,具体由运行环境确定。目前 JS 的主要运行环境有两个,浏览器和 Node。
1、同步任务与异步任务
同步任务
立即放入 JS引擎(JS 主线程)中执行,并原地等待结果。
异步任务
先放入宿主环境(浏览器/node),不必原地等待结果,并不阻塞主线程继续往下执行,异步结果在将来执行。
分类:异步任务分为宏任务和微任务。所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步任务则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。
宏任务
由宿主(浏览器、node)发起的任务
- script(代码块)
- setTimeout/setInterval 定时器
- setImmediate(node)
- 事件绑定的回调
- I/O
- UI交互事件 等等
微任务
由 JS引擎 发起的任务
- process.nextTick(node)
- Promise.then(回调) catch();Promise本身同步,then/catch的回调函数是异步的
- Async/Await;在 await 后面的代码都属于微任务,相当于调用 Promise.then方法
- Object.observe(已废弃) 等等
2、执行过程
- 同步任务由 js引擎 执行,异步任务交给宿主环境
- 所有同步任务在主线程中执行,形成一个执行栈(调用栈)
- 主线程之外,还存在一个‘任务队列’(task queue),当异步的代码运行完毕以后,会将代码中的回调送入到任务队列中(队列遵循先进先出得原则)
- 主线程的执行栈执行完毕后,会去任务队列看是否由异步任务,有就送到主线程的执行栈执行,反复循环查看执行,这个过程就是事件循环(Event Loop)
3、执行顺序
- 先执行同步代码,
- 遇到宏任务时要先达到触发条件,才将宏任务放入宏任务队列,
- 比如:事件绑定;两个定时器,2秒的在前,1秒的在后,先执行的是1秒的定时器
- 遇到微任务则将微任务放入微任务队列中,
- 当所有同步代码执行完后,再将微任务从队列中调入主线程执行,
- 微任务执行完毕后再将宏任务调入主线程执行
- 一直循环(同步任务 > 微任务 > 宏任务 > 同步任务...)直至所有任务执行完毕。
注意:当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;
4.、实例
// 假设这是我们要请求的数据
function getSomething (n) {
return new Promise(resolve => {
console.log('Promise 中是同步任务');
// 模拟1s后返回数据
setTimeout(() => resolve(222), 1000);
});
}
// 如果想要等数据返回后再执行后面的代码,那么就要使用 async/await
async function real () {
console.log(111);
// 这时something会等到异步请求的结果回来后才进行赋值,同时不会执行之后的代码
const something = await getSomething();
console.log(something)
console.log('await后面是微任务');
}
real()
console.log(333);
解析
在 await 后面的代码都属于微任务,相当于调用 Promise.then方法,所以要等 resolve 之后才会执行 await 后面的代码,await 里面的代码还是遵循事件循环。在同一作用域内看上去就是同步的,但如果上一个作用域中还有同步任务,还是会先执行。