单线程的JavaScript
JavaScript,自诞生以来就是单线程。作为Web脚本语言,它的主要用途就是为结构和样式提供行为以及操作DOM。它独有的语言性质决定了:Javascript只能是单线程,否则会带来很复杂的同步问题。
例如:两个线程同时在一个DOM节点上进行操作,那么这时浏览器应该以哪个线程为准呢?为了避免这些问题,从一开始,Javascript就是单线程,以前也是,现在也是,未来也不会改变。这是这门语言的核心特征。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
同步任务和异步任务
单线程,就是指一次只能完成一件任务,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。但如果有一个任务的执行时间很长,比如文件的读取或者数据的请求等等,那么后面的任务就要一直等待,这就会影响用户的使用体验。
为了解决这种情况,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
同步任务(Synchronous)
又叫做非耗时任务,指的是在主线程上排队执行的那些任 只有前一个任务执行完毕,才能执行后一个任务;程序的执行顺序与任务的排列顺序是一致的、同步的
异步任务(Asynchronous)
又叫做耗时任务,异步任务由JavaScript 委托给宿主环境进行执行 当前一个异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数;后一个任务则是不等前一个任务的回调函数的执行而执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的
事件循环(Event Loop)
事件循环(Event Loop),又叫事件队列
js代码所在环境的编译器
解析执行js代码的一种运行机制
,即代码执行规则
事件循环不属于js代码本身
的范畴,而是属于js编译器
的范畴;js代码行为应当遵循所在环境的编译器
的规则
(有小伙伴说:“这个js代码是个事件循环代码!”)
这种说法实际上是不合理的,再次声明:事件循环不属于js代码本身
的范畴!
微任务与宏任务
1、微任务与宏任务就属于js代码的范畴
2、js代码主要分为两大类: 同步代码、异步代码
3、异步代码又分为:微任务与宏任务
同步任务
:同步任务不需要进行等待,必须立即看到执行结果,比如console等
异步任务
:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求等
异步任务,又可以细分为宏任务和微任务
以下是几种常见任务
任务(代码) | 宏/微 任务 | 环境 |
---|---|---|
事件 | 宏任务 | 浏览器 |
网络请求(Ajax) | 宏任务 | 浏览器 |
setTimeout() 定时器 | 宏任务 | 浏览器 / Node |
fs.readFile() 读取文件 | 宏任务 | Node |
Promise.then() | 微任务 | 浏览器 / Node |
async/await | 微任务 | 浏览器 / Node |
事件循环执行机制
1.进入到script标签,就进入到了第一次事件循环
2.遇到同步代码,立即执行
3.遇到宏任务
,放入到宏任务队列里.
4.遇到微任务
,放入到微任务队列里.
5.执行完所有同步代码
6.执行微任务代码
7.执行宏任务代码
8.微任务代码执行完毕,本次队列清空
9.寻找下一个宏任务,重复步骤1
以此反复直到清空所有宏任务,这种不断重复的执行机制,就叫做事件循环
你能说出以下代码的执行流程吗?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
console.log(1);
async function fnOne() {
console.log(2);
await fnTwo(); // 右结合先执行右侧的代码, 然后等待
console.log(3);
}
async function fnTwo() {
console.log(4);
}
fnOne();
setTimeout(() => {
console.log(5);
}, 2000);
let p = new Promise((resolve, reject) => { // new Promise()里的函数体会马上执行所有代码
console.log(6);
resolve();
console.log(7);
})
setTimeout(() => {
console.log(8)
}, 0)
p.then(() => {
console.log(9);
})
console.log(10);
</script>
<script>
console.log(11);
setTimeout(() => {
console.log(12);
let p = new Promise((resolve) => {
resolve(13);
})
p.then(res => {
console.log(res);
})
console.log(15);
}, 0)
console.log(14);
</script>
</body>
</html>
部分内容参考于网络