js的单线程
js最大的特点就是单线程,也就是说,同一个时间只能做一件事。那么如何提高效率?所以js语言设计者意识到后把所有任务分成两种:同步任务和异步任务
同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,会阻塞程序执行
同步阻塞示例
console.log('start');
alert('200')
console.log('end');
异步任务
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行,不会阻塞程序任务
前端使用异步的场景:
- 定时任务:setTimeout,setInterval
- 网络请求:ajax请求,动态图片加载
- 事件绑定
//加载示例
console.log('start');
var body = document.querySelector('body')
var img = document.createElement('img')
// 异步操作,回头才处理,继续下面的请求,不会阻塞代码执行
img.onload = function() {
console.log('loaded');
}
img.src = 'https://file.vetscloud.com/awen/local/withdrawal-bg.png'
body.appendChild(img)
console.log('end');
任务队列和事件循环(event loop)
同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入任务队列(Event Queue)。主线程内的任务执行完毕,就去任务队列(Event Queue)读取对应的函数,进入主线程执行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
宏任务,微任务
宏任务:当前调用栈中执行的代码成为宏任务,主要有:script(整体代码)、setTimeout、setInterval、UI交互事件、MessageChannel 等
微任务:宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调函数,主要有:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)等
宏任务,微任务的执行顺序如下图:
整体的运行流程如下图:
现在我们通过一个示例来看宏任务和微任务的执行
var p = new Promise(resolve => {
console.log(4);
resolve(5);
});
function fun1() {
console.log(1)
}
function fun2() {
setTimeout(() => {
console.log(2)
});
fun1();
console.log(3);
p.then(resolved => {
console.log(resolved)
})
}
fun2();
第一步:new Promis压放入执行栈中,然后执行里面的代码,打印4,执行resolve(5)注意这里跟new普通函数一样是正常执行的,不会加入到宏任务中;
第二步:执行fun2(), 遇到setTimeout,将它放入宏任务中; 接着执行fun1();打印出1 , 接着console.log(3);打印3
遇到Promise对象执行then()时,这里是异步操作,会将里面回调函数放入微任务中,等待执行 当执行栈被清空时,执行微任务中的console.log(resolved),打印出5, 当微任务清空后,再执行宏任务,即setTimeout到时间后会答应出2, 所以最后答案为:41352
总结
如果把js当作初始的宏任务,那么js在浏览器端的执行过程就是这样:
先执行一个宏任务, 然后执行所有的微任务
再执行一个宏任务,然后执行所有的微任务
反复进行,执行到执行栈和任务队列为空