一、js的单线程概念
众所周知,js语言是一个单线程语言,单线程也就是说同一时间只能做一件事,这个特点与他的用途有关,JS语言是运行在浏览器端的脚本语言,主要是与客户进行交互和操作DOM,所以单线程也就很好理解了。如果是多线程则会出现问题,例如,有两个线程同时对同一DOM进行添加和删除,那浏览器到底该以哪个线程为主呢?所以,单线程就避免了这个问题。但是,单线程是不是运行效率太低了?为了解决这个问题,设计师引出了同步与异步任务队列的概念。
二、同步与异步任务队列
同步任务(synchronous)就是在主线程上排队的任务,只有一个任务执行完成,才会执行下一个任务。异步任务(asynchronous)是指不进入主线程,而是进入“任务队列”的任务,只有到了该执行的时机,任务队列通知主线程可以执行该异步任务了,才会进入主线程进行执行。异步任务有:延时器setTimeout与定时器setInterval,DOM时间,es6的promise,AJAX请求(包括封装后的请求),而异步任务又分为宏任务和微任务。
console.log('1')
setTimeout(function(){
console.log('2')
},0)
while(true) {}
如上代码:打印的是1。因为console.log('1')和while循环都是主线程上的代码,setTimeout是异步任务队列中的代码,所以从上到下先执行主线程的代码,先打印1,因为while是个死循环,所以主线程一直在执行,而不会执行异步任务。只有在主线程是空闲的时候,才会去执行异步任务。
事件循环:js主线程不断去读取异步任务的过程称为事件循环,即js主线程一到空闲时间,就会去读取异步任务队列,,执行异步任务。且这是一个循环的过程。且任务队列遵循的是“先进先出”的原则执行。
js的运行:
- js进行预解析,代码中的所有函数,变量声明进行提前。但是不赋值
- js运行(从上到下)执行,然后按照如上的事件循环机制进行运行
- 异步任务的执行时机:根据实际情况来即可,比如两个延时器,一个延时5s执行,一个延时6s执行,那肯定是延时5s的先执行,又如一个ajax请求和一个延时器2s,如果ajax请求在2s内就返回结果了,那他的回调肯定是比延时器先执行,如果ajax请求在2s后才返回结果,那肯定是延时器先执行。
三、宏任务与微任务
宏任务与微任务同属于异步任务
宏任务包括:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务包括: new Promise().then(回调), process.nextTick, Object.observe(已废弃), MutationObserver(html5新特性)
同时有宏任务和微任务,那是先执行哪个呢?
我们先看看如下代码会打印什么?
// 1
console.log(1)
// 2
Promise.resolve().then(function () {
console.log(2)
})
// 3
new Promise(function(resolve, reject){
console.log(3)
resolve()
}).then(function () {
// 4
console.log(4)
// 5
setTimeout(function () {
console.log(5)
})
})
// 6
console.log(6)
// 7
setTimeout(function () {
// 9
console.log(9)
Promise.resolve().then(function () {
console.log(7)
setTimeout(function () {
console.log(8)
})
})
})
我们先把如上代码分成几个部分:
我们先看看主线程部分的代码有哪些?
首先1、6肯定是,这两个不包含任何异步操作,3因为是new Promise,是构造函数,所以也是,但是3中的.then()是属于回调,是异步函数里面的微任务
所以我们也可以分出异步有哪些?
异步包括:2、7以及3里面的.then()
按照先执行主线程的同步操作可知:肯先是先执行,1、3、6,然后在执行微任务,即执行2、4。打印完4后要注意,这里遇到了一个宏任务--setTimeout,程序会先把该宏任务放入队列,然后继续执行,7部分的setTimeout,所以打印9,在这里面又遇到了微任务,直接执行。所以打印7,然后再执行又遇到了setTimeout,把他放入队列,然后执行完微任务后,再去集中执行宏任务,5部分的setTimeout先入队列,所以先执行,所以先打印5,再打印8
所以最终打印结果就是:1,3,6,2,4,9,7,5,8
总结:有宏任务先执行宏任务,setTimeout也是宏任务,里面代码没有立即执行是因为这是延时器,需要特定时机执行,会先放入任务队列。执行完第一波宏任务后如果没有宏任务则开始执行微任务,执行完所有微任务队列后再去执行宏任务队列(执行微任务队列途中遇到宏任务,可以立即执行的则立即执行,如定时器则加入宏任务队列),如此循环。即:第一波宏任务——第一波微任务——第二波宏任务——第二波微任务.......