JS中的线程
| 名称 | 说明 |
|---|---|
| 进程(process) | 计算机已经运行的程序,是操作系统管理程序的一种方式 启动一个应用程序,就会默认启动一个进程(也可能是多个进程) 进程是线程的容器 |
| 线程(thread) | 操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中 每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程 |
CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换
当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码, 对于用户来说是感受不到这种快速的切换的
JS常见的宿主环境为node和浏览器,而node和浏览器是多进程的
当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出
而对于每一个页签进程,又都是多线程的。一个页签进程由JS解析线程,UI渲染线程,HTTP网络请求线程等多个线程共同组成
在这些线程中,用于解析和执行JS代码的线程 有且只有一个,而这个线程又被称之为JS主线程
所以JavaScript的代码执行是在一个单独的线程中执行的,JavaScript是单线程的
这就意味着JavaScript的代码,在同一个时刻只能做一件事,如果这件事是非常耗时的,就意味着当前的线程就会被阻塞
但是一个进程中是有多个线程的,所以真正耗时的操作,实际上并不是由JavaScript线程在执行的
而是由浏览器其他线程可以来完成这个耗时的操作,比如网络请求、定时器,我们只需要在特定的时候执行应该有的回调即可
而这类会交给浏览器其它线程来执行的任务就被称之为异步任务
console.log('定时器之前的代码')
// 虽然该定时器所需要等待的时候为0s
// 但是该定时器是异步操作,会被交给浏览器别的线程来进行执行
// 以便于其不会阻塞JS主线程代码的执行
// 因此即使该定时器的等待时间为0s,其依旧会被加入到对应的事件队列中
// 等到JS主线程中的所有代码执行完毕后,浏览器才会去事件队列中读取对应的事件并进行回调
setTimeout(() => {
// setTimeout这个函数是在主线程中执行的
// 但是setTImeout对应的回调函数会别加入到对应的事件队列中
console.log('定时器中的代码')
}, 0)
console.log('定时器之后的代码')
/*
=>
定时器之前的代码
定时器之后的代码
定时器中的代码
*/
这个过程会形成一个完整的闭环,这个闭环就被称之为事件循环
对应的用于存放异步任务的队列就被称之为事件队列
宏任务和微任务
事件循环中并非只维护着一个队列,事件队列由宏任务和微任务共同组成
| 任务 | 说明 |
|---|---|
| 宏任务队列(macrotask queue) | ajax、setTimeout、setInterval、DOM监听、UI Rendering等 |
| 微任务队列(microtask queue) | Promise的then回调、 Mutation Observer API、queueMicrotask()等 |
对应队列的执行顺序:
- main script中的代码优先执行
- 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的
- 如果不为空,那么就优先执行微任务队列中的任务(回调)
示例
setTimeout(function () {
console.log("setTimeout1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
/*
=>
promise1
2
then1
queueMicrotask1
then3
setTimeout1
then2
then4
setTimeout2
*/
async function async1 () {
console.log('async1 start')
// await后边的代码会等到await后边的promise的状态变成resolve的时候,才会执行
// 相当于 await后边的代码 是写在await后边promise(在这里就是async2)的then方法中的
await async2();
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
console.log('script end')
/*
=>
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
console.log('script start')
function requestData(url) {
// 执行到这行代码的时候
// 会执行Promise构造函数后并将Promise实例返回
// 所以会执行定时器,但此时定时器会开始计时,所以其内部代码并不会执行
// 而是会等到2s后在被放入宏任务事件队列
return new Promise(resolve => {
setTimeout(() => {
console.log('setTimeout')
resolve(url)
}, 2000)
})
}
function getData() {
console.log('getData start')
requestData('https://www.example.com').then(res => {
console.log(res)
})
console.log('getData end')
}
getData()
console.log('script end')
/*
=>
script start
getData start
getData end
script end
setTimeout
https://www.example.com
*/