//代码1
const test = () => {
let t = +new Date()
while (true) {
if (+new Date() - t >= 2000) {
break
}
}
}
console.log(1)
test()
console.log(2)
console.log(3)
// 执行结果 1 2 3
// 代码2
console.log(1)
setTimeout(() => {
console.log(2)
}, 2000)
console.log(3)
// 执行结果 1 3 2
代码 1 都是同步任务,代码 2 有异步任务 setTimeout;所有执行结果不同
同步:主线程上排队执行的任务,只有前面的任务执行完,才能执行后面的任务
异步:是指不进入主线程,而是进入任务队列
的任务,只有任务队列
通知主线程,某个 任务可以执行了,该任务才会进入主线程执行
如果按照 js 是单线程的,上面的代码 2 应该是 123 才符合单线程的表现;现在为什么是 132 呢,那异步怎么实现的呢?
打个比方:假如 cpu 是一个工厂,进程就是一个车间;线程就是工人,一个进程有多个线程,它们之间资源共享,也就内存空间共享。
linux 下 查看进程:
ps (process status) 列出系统中当前运行进程的快照
top (table of processes) 动态实时查看进程
kill 9 进程 pid 杀死进程
通过浏览器的内核多线程实现异步
我们打开一个浏览器就会启动以下进程,我们所要关心的是渲染进程,渲染进程是浏览器的核心进程
GUI 线程:负责渲染页面,解析 html、css;构建 DOM 树和渲染树
js 引擎线程: js 引擎线程负责解析和执行 js 程序,我们经常听到的 chrome 的 v8 引擎就是跑在 js 引擎线程上的,js 引擎线程只有一个,所有说 js 的单线程语言的原因,那其实语言没有单线程多线程之说,因为解释这个语言的是 的线程是单线程;js 引擎线程与 gui 线程互斥,当浏览器执行 javaScript 程序的时候,GUI 渲染线层会保存在一个队列当中;直到 js 程序执行完成,才会接着执行;如果 js 的执行时间过长,会影响页面的渲染不连贯,所有我们要尽量控制 js 的大小
定时触发线程:为什么 setTimeout 不阻塞后面程序的运行,那其实 setTimeout 不是由 js 引擎线程完成的,是由定时器触发线程完成的,所以它们可以是同时进行的,那么定时器触发线程在这定时任务完成之后会通知事件触发线程往任务队列里添加事件
事件触发线程:将满足触发条件的事件放入任务队列,一些异步的事件会放到异步队列中
异步 HTTP 请求线程:用与处理 ajax 请求的,当请求完成时如果有回调函数就通知事件触发线程往任务队列中添加任务
代码在执行栈中执行,然后 1=>2=>3=>4
执行过程解释:
console.log(1)
先入栈执行,执行完出栈setTimeout
调用 setTimeout 这个 webapi,通知定时触发线程定时 2 秒钟console.log(3)
入栈执行,执行完出栈const test = () => {
let t = +new Date()
while (true) {
if (+new Date() - t >= 5000) {
break
}
}
}
setTimeout(() => {
console.log(2)
}, 2000)
test()
// 等到5秒钟后才打印出了2
为什么呢?
因为 test 是会耗时 5 秒钟的同步任务,异步任务只能等待同步任务执行完之后才能执行,也就是说只能等 5 秒钟后才能检查的任务队列里的任务。
以前 js 是在浏览器环境中运行,由于 chrome 对 v8 做了开源;所以 js 有机会在服务端运行;浏览器和 node 都是 js 的运行环境,它们相当于是一个宿主,宿主能提供一个能力能帮助 js 实现 Event Loop
所有任务都在一个线程上完成,一旦遇到大量任务或遇到一个耗时的任务,网页就可能出现假死,也无法响应用户的行为
Event Loop 是一个程序结构,用于等待和发送信息的事件。 简单说就是在程序中设置 2 个线程,一个负责程序本身的运行,称为“主线程”;另一个负责主线程和其他进程(主要是各种 I/O 操作)的通信 被称为“Event Loop 线程”(也可以翻译为消息线层)
js 就是采用了这种机制,来解决单线程带来的问题。
console.log(1)
setTimeout(function() {
console.log(2)
}, 0)
Promise.resolve().then(function() {
console.log(3)
})
console.log(4)
// 1 4 3 2
1 和 4 是同步任务肯定是最先执行,现在要看异步任务,现在要看的是promise
的回调为什么在定时器前面执行,那为什么promise
后放入,为什么先执行呢?那是因为 Event Loop 的机制是有微任务的说法的;现在往下看。
宏任务(task):
......
微任务(microtask):
线程都有自己的数据存储空间,上图可以看见堆
和栈
,堆的空间比较大,所以存储一些对象;栈的空间比较小, 所以存储一些基础数据类型、对象的引用、函数的调用;函数调用就入栈,执行完函数体里的代码就自动从栈中移除这个函数,这就是我们所说的调用栈; 栈是一个先进后出的数据结构,当最里面的函数出栈的时候,这个栈就空了;当我们调用时候会调用一些异步函数, 这个异步函数会找他们的异步处理模块,这个异步模块包括定时器、promise、ajax 等,异步处理模块会找它们各自 对应的线程,线程向任务队列中添加事件,看我们的蓝色箭头,表示在任务队列中添加事件,橘色的箭头是从任务队列中取事件,取出这个事件去执行对应的回调函数;
有 3 个点要注意
执行步骤:
注意点:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
new Promise(resolve => {
console.log('promise inner1')
resolve()
}).then(() => {
console.log('promise then1')
})
}, 0)
new Promise(resolve => {
console.log('promise inner2') //同步执行的
resolve()
}).then(() => {
console.log('promise then2')
})
// 打应结果
/*
start
promise inner2
promise then2
setTimeout
promise inner1
promise then1
*/
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
return Promise.resolve().then(_ => {
console.log('async2 promise')
})
}
console.log('start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
/*
start
async1 start
promise1
async2 promise
promise2
async1 end
setTimeout
*/
执行步骤:
先执行主线程的同步任务(也是宏任务), 先执行start
然后遇到了 setTimeout,把它放到下一次宏任务中执行,我们叫它宏 2;然后调用 async1()函数,执行了async1 start
;又调用了 async2 函数执行 Promise.resolve().then;由于 then 的回调函数是微任务,就把它放到微任务队列中,我们叫它微 1;遇见 await 是等待的意思,需要把第一轮的微任务执行完,在执行 await 下面的内容,我们在执行 new Promise(),打印了promise1
,又调用了 resolve()改变了 promise 状态,这个 then 的回调我们叫它微 2;第一轮宏任务执行完毕。
第一轮宏热任务执行完毕后,我们检查微任务队列中的微任务,把它全部执行完,就打印了async2 promise
、promise2
第一轮微任务执行完,就执行 await 后面的内容async1 end
await 后面的内容执行完后又执行宏任务setTimeout
setTimeout(() => {
console.log(1)
new Promise(r => {
r()
}).then(res => {
console.log(2)
Promise.resolve().then(() => {
console.log(3)
})
})
})
setTimeout(() => {
console.log(4)
new Promise(r => {
r(1)
}).then(res => {
console.log(5)
})
})
/*
1
2
3
4
5
*/
1
;然后遇到 promise 就把 then 的回调放到微任务队列;宏 1 执行完以后,就去检查微任务队列中有任务么,有就全部执行,所以我们就执行了 2,在 then 的回调中又产生了微任务,所以我们又执行了 3,记住一点,要把本次宏任务下所产生的微任务全部执行完才会执行下一个宏任务,记住是产生的,没有产生的不会执行。