单线程和异步
单线程
js是单线程语言,只能同时做一件事
js引擎和Dom操作共用同一个线程
同步和异步
JS
是单线程语言,只能同时做一件事- 浏览器和
Node.js
已经支持JS启动进程,如:Web Worker
JS
和DOM
渲染共用同一个线程,因为JS可修改DOM结构
(JS
和DOM
只能同时有一个进行)- 遇到等待(网络请求,定时任务),不能卡主
- 需要异步
- 回调
callback
函数形式 - 异步不会阻塞代码执行,
- 同步会阻塞代码执行
同步
依次执行,会阻塞代码的执行,
比如:
console.log(1);
alert('阻塞了...');
console.log(2); // 只有当alert消失,才会到这一步
console.log(1);
alert('阻塞了...');
console.log(2); // 只有当alert消失,才会到这一步
异步
console.log('start');
setTimeout(function () {
console.log('async');
},0 );
while (1) {
}
// 最终只输出1
console.log('start');
setTimeout(function () {
console.log('async');
},0 );
while (1) {
}
// 最终只输出1
各自执行,互不影响,不会阻塞代码执行
一般都会以回调的形成存在
异步的代码会依次进入到队列当中,等同步任务执行完成再去队列依次执行
异步的应用
- 网络请求
如ajax
图片加载
- 定时任务
setTimeout
- 事件处理
onclick
单线程
js是单线程语言,只能同时做一件事
js引擎和Dom操作共用同一个线程
- js为什么是单线程
防止多个线程对于Dom节点造成不可控的修改,降低复杂度, JavaScript选择只用一个主线程来执行代码,以保证代码执行的一致性;
执行栈
js在解析同步任务的时候,会将这些任务按照执行顺序排列到一个地方,这个地方就叫做执行栈
调用一个函数总是会为其创造一个新的栈帧。
栈:后进先出
事件队列
js会将异步任务按照执行顺序,加入到与执行栈不同的另一个队列,也就是事件队列。
队列:先进先出
函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。
浏览器的事件循环
执行步骤:
- 1、js从上到下解析方法,主线程将同步任务添加到执行栈;
- 2、当碰到ajax、setTimeout等异步任务时,会暂时挂起,继续执行执行栈中的任务, 等异步任务返回结果,再按照执行顺序排列到事件队列中;
- 3、主线程先将执行栈中同步任务清空,检查事件队列是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,继续排到事件队列;
- 4、主线程每次清空执行栈,就去检查事件队列是否有任务,如果有就取出一个推到执行栈,这个过程是循环往复的...这个过程就叫做
EventLoop
事件循环。
宏任务和微任务
实际上异步任务之间也不相同,执行优先级也有区别。不同的异步任务被分为两类: 宏任务(macro task)和微任务(micro task)。我们将经常遇到的异步任务进行分类如下: 宏任务:setTimeout,setInterval,setImmediate,I/O(磁盘读写或网络通信),UI交互事件 微任务:process.nextTick,Promise.then
当执行栈中的任务清空,主线程会先检查微任务队列中是否有任务,如果有,就将微任务队列中的任务依次执行,直到微任务队列为空,之后再检查宏任务队列中是否有任务,如果有,则每次取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环... ...
同一次事件循环中,微任务永远在宏任务之前执行。
一次事件循环只执行处于 Macrotask 队首的任务,执行完成后,立即执行 Microtask 队列中的所有任务。
await后的内容为宏任务,await下的内容为异步任务
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
// 新版输出(新版的chrome浏览器优化了,await变得更快了,输出为)
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
// 注意一个点await async2() 执行完后面的任务才会注册到微任务中
// 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => **async1 end** => setTimeout
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
// 新版输出(新版的chrome浏览器优化了,await变得更快了,输出为)
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
// 注意一个点await async2() 执行完后面的任务才会注册到微任务中
// 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => **async1 end** => setTimeout
总结
- js解析方法时,将同步任务排队到执行栈中,异步任务排队到事件队列中。
- 事件队列分为:
- 宏任务:setTimeout,setInterval,setImmediate,I/O,UI交互事件
- 微任务:process.nextTick,Promise.then
- 浏览器环境中执行方法时,先将执行栈中的任务清空,再将微任务推到执行栈中并清空,之后检查是否存在宏任务,若存在则取出一个宏任务,执行完成检查是否有微任务,以此循环…
TIP
先将宏任务中的执行栈清空再去清空微任务队列,再执行下一个宏任务。
例题
例题1
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve()
.then(() => {
console.log('promise1')
})
.then(() => {
console.log('promise2')
})
console.log('end')
start
// 存宏任务和微任务,不会直接执行
end
// 栈空,执行微任务队列中的任务(经常会有坑,需要注意一下)
promise1
promise2
// 再下一轮eventloop执行宏任务
setTimeout
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve()
.then(() => {
console.log('promise1')
})
.then(() => {
console.log('promise2')
})
console.log('end')
start
// 存宏任务和微任务,不会直接执行
end
// 栈空,执行微任务队列中的任务(经常会有坑,需要注意一下)
promise1
promise2
// 再下一轮eventloop执行宏任务
setTimeout
例题2
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
// 从上往下看,定义的两个async函数先别管(执行再说)
script start
//setTimeout放到宏任务队列
// 执行了async1(异步函数)
async1 start
// await 后面是 async2(),执行 async2()异步函数
async2
// await 后面的代码是微任务,放入微任务队列
promise1
// .then()放到微任务队列
script end
// 同步代码(同时也是宏任务)执行完成,接着执行微任务
async1 end
promise2
// 微任务队列清空,执行宏任务
setTimeout
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
// 从上往下看,定义的两个async函数先别管(执行再说)
script start
//setTimeout放到宏任务队列
// 执行了async1(异步函数)
async1 start
// await 后面是 async2(),执行 async2()异步函数
async2
// await 后面的代码是微任务,放入微任务队列
promise1
// .then()放到微任务队列
script end
// 同步代码(同时也是宏任务)执行完成,接着执行微任务
async1 end
promise2
// 微任务队列清空,执行宏任务
setTimeout
例题3
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {
console.log('children3')
})
}, 0)
new Promise((resolve, reject) => {
console.log('children4')
setTimeout(() => {
console.log('children5')
resolve('children6')
}, 0)
}).then((res) => {
console.log('children7')
setTimeout(() => {
console.log(res)
}, 0)
})
start
// 遇到setTimeout,放入宏任务队列,称为宏任务1
children4
// 遇到setTimeout,放入宏任务队列,称为宏任务2
// 遇到.then !!!不会被放到微任务队列!!!,因为resolve 是放到 setTimeout中执行的。
// 执行完检查微任务队列是空的,于是执行宏任务1
children2
// 把Promise.resolve().then放入微任务队列
// 宏任务队列1空了,检查微任务队列,执行微任务
children3
// 微任务清空,执行宏任务2
children5
// .then放入微任务队列,宏任务2执行完,执行微任务
children7
// setTimeout放入宏任务中,微任务执行完后执行
children6
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {
console.log('children3')
})
}, 0)
new Promise((resolve, reject) => {
console.log('children4')
setTimeout(() => {
console.log('children5')
resolve('children6')
}, 0)
}).then((res) => {
console.log('children7')
setTimeout(() => {
console.log(res)
}, 0)
})
start
// 遇到setTimeout,放入宏任务队列,称为宏任务1
children4
// 遇到setTimeout,放入宏任务队列,称为宏任务2
// 遇到.then !!!不会被放到微任务队列!!!,因为resolve 是放到 setTimeout中执行的。
// 执行完检查微任务队列是空的,于是执行宏任务1
children2
// 把Promise.resolve().then放入微任务队列
// 宏任务队列1空了,检查微任务队列,执行微任务
children3
// 微任务清空,执行宏任务2
children5
// .then放入微任务队列,宏任务2执行完,执行微任务
children7
// setTimeout放入宏任务中,微任务执行完后执行
children6