同步和异步
-
同步任务:立即执行的任务在主线程上排队执行,形成一个执行栈,只有前一个任务执行完毕,才能继续执行下一个任务
-
同步代码:是指在程序中按照顺序执行的代码,前一个操作必须完成后才能进行下一个操作。每一行代码会等待前面的代码执行完毕后再继续往下执行
-
异步任务:不进入主线程,进入任务队列(宏任务队列和微任务队列)的任务;只有等主线程任务全部执行完毕,任务队列的任务才会进入主线程执行
-
异步代码:是指不按照代码的书写顺序执行的代码。它可以在执行某个操作的过程中,不必等待这个操作完成,就可以继续执行后面的代码。当异步操作完成时,会通过回调函数、Promise 等方式通知程序,触发下一步操作
进程和线程
进程是线程的容器
- 进程:启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
- 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程
- 例子解释:
- 操作系统类似于一个大工厂,工厂中里有很多车间,这个车间就是进程,每个车间可能有一个以上的工人在工厂,这个工人就是线程
- 操作系统类似于一个大工厂,工厂中里有很多车间,这个车间就是进程,每个车间可能有一个以上的工人在工厂,这个工人就是线程
进程
-
进程是操作系统中运行程序的基本单位,一个进程可以被看作是程序执行的实例
-
它拥有自己独立的内存空间、系统资源(文件句柄、网络连接等),并且能够执行任务
-
当你启动一个应用程序(如浏览器、文本编辑器等),操作系统就会为这个应用程序创建一个进程
线程
-
线程是进程中的一个执行单元,它是 CPU 调度的最小单位
-
一个进程可以包含多个线程,多个线程共享进程的资源(如内存、文件句柄等)
-
每个线程有自己的栈空间和程序计数器
浏览器中的JavaScript
线程
经常说JavaScript
是单线程(可以开启workers
)的,但是JavaScript
的线程有自己的容器进程:浏览器或者Node
浏览器是一个进程吗?它里面只有一个线程吗?
-
目前多数的浏览器其实都是多进程的,当我们打开一个
tab
页面时就会开启一个新的进程 -
为了防止一个页面卡死而造成所有页面无法响应
-
每个进程中又有很多的线程,其中包括执行
JavaScript
代码的线程
JavaScript
的代码执行是在一个单独的线程中执行的:
-
JavaScript
的代码,在同一个时刻只能做一件事 -
虽然
JavaScript
是单线程的,但许多耗时操作(如网络请求、文件操作、定时器等)是由浏览器或Node.js
底层的多线程机制处理的 -
JavaScript
主线程只负责逻辑控制,实际的耗时任务被分配给后台的其他线程,最终再回到主线程进行后续处理 -
这种机制避免了阻塞,保证了
JavaScript
的非阻塞、异步执行能力
事件循环
通过学习上面浏览器中的JavaScript
线程知识,我们知道了 JavaScript
是单线程的,但它通过分配其他线程来实现异步操作最后再回到主线程处理,从而避免了阻塞操作,这种机制就是事件循环
事件循环(Event Loop
)是 JavaScript
执行异步代码的核心机制(这里主要学习浏览器的事件循环,暂时不学Node
的事件循环)
那么这个过程是怎么操作的?继续往后学习↓↓↓
宏任务和微任务
这些耗时的任务被放在哪里了,执行顺序又是什么样的呐?
我们必须先要理解两个概念:宏任务(Macrotask
)和微任务(Microtask
)是 JavaScript
事件循环中两类异步任务,它们的执行顺序和优先级决定了异步代码的执行时机
宏任务
- 宏任务是指一些较大的异步操作,常见宏任务包括:
-
每个
<script>
标签中的代码块都是独立的宏任务 -
ajax
(网络请求) -
setTimeout
-
setInterval
-
DOM
监听(如点击滚动事件) -
UI Rendering
(UI
渲染)
-
微任务
- 微任务是指一些比宏任务更小、更细粒度的异步操作,常见微任务包括:
-
Promise
的then
回调 -
await
后面跟promise
或异步函数(也是promise
)时,await
后面的代码和前面的赋值也会被放入微任务队列 -
Mutation Observer API
(监听DOM变化) -
queueMicrotask()
-
process.nextTick
(Node.js
中)
-
总结
放在哪里:
- 这些宏任务和微任务都放在了各自的队列中,遵循先进先出(FIFO) 的原则
执行顺序:
-
微任务的执行优先级比宏任务更高,在一个微任务执行完毕后,
JavaScript
引擎会继续检查是否还有其他微任务,而不是立即切换到宏任务队列 -
在执行任何一个宏任务之前,都会先查看微任务队列中是否有任务需要执行
-
也就是宏任务执行之前,必须保证微任务队列是空的
-
如果不为空,那么就优先执行微任务队列中的任务(回调)
代码练习题
上面看了太多云里雾里的理论知识,我们下面做下练习题让整个知识体系串联起来
练习一
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
Promise.resolve()
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
事件循环的过程解释如下:
练习二
setTimeout(function () {
console.log('timer1')
}, 10)
setTimeout(function () {
console.log('timer2')
}, 0)
new Promise(function executor(resolve) {
console.log('promise 1')
resolve()
console.log('promise 2')
}).then(function () {
console.log('promise then')
})
console.log('end')
事件循环的过程解释如下:
练习三
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(() => {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1");
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
事件循环过程解释如下:
练习四
async function async1() {
console.log('async1 start')
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')
事件循环过程解释如下:
练习五
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise3');
resolve();
}).then(function () {
console.log('promise4');
});
console.log('script end');
事件循环过程解释如下:
练习六
async function async1() {
console.log('async1 start');
await async2();
setTimeout(function() {
console.log('setTimeout1')
}, 20)
console.log("async1 end")
}
async function async2() {
setTimeout(function() {
console.log('setTimeout2')
}, 0)
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout3');
}, 10)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
事件循环过程解释如下: