1340.640.jpg

1.引言

之前在这篇文章JS核心知识点梳理——异步,单线程,运行机制里面讲过一些eventloop,宏任务,微任务的概念,但是当时理解有限,有些东西讲的有点问题,有些东西讲的角度不同,也不够细,最近也是做了大量的异步题目,脑子里建立了一套新的,可靠的模型。今天借这个机会再次深入讲一下,并通过做大量面试题加深理解

2.全面了解浏览器

首先还是得介绍一下浏览器中的进程,线程概念。重点做到合理理解JS是单线程这句话的意思

2.1浏览器有许多进程:

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  3. GPU进程:最多一个,用于3D绘制等
  4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的)

2.2在浏览器渲染进程中有许多线程:

  • 渲染引擎线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行(主线程)
  • 定时触发器线程:处理定时事件,比如setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

虽然JavaScript是单线程的(说的是JS引擎线程),可是浏览器内部不是单线程的。一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。
主线程和渲染引擎线程互斥,因为渲染的时候主线程可能通过dom操作渲染结果,所以主线程必须被阻塞

3.宏任务,微任务

直接记结论吧,没啥好说的

宏任务(macrotask):主代码块,setTimeout,setInterval等
微任务 (microtask):Promise,process.nextTick等

4.eventloop

js执行机制,也是Js是单线程但是不会阻塞的原因所在

我是这么理解的eventloop的(目前做的题都符合我这个模型)

1.主栈代码执行
2.遇到异步宏任务,给宏任务线程(比如定时器线程)处理,主栈继续往下执行同步代码
3.遇到异步微任务,给微任务线程处理,主栈代码继续往下执行同步代码
4.宏任务微任务线程一旦执行完,将相应的回调放到宏任务,微任务队列里面
5.主栈代码执行完 先看微任务队列有没有任务(也就是相应的回调),有就放到主栈里面执行执行完继续看微任务队列里面还有没有任务,直到微任务队列为空
6.查看宏任务队列里面有没有任务(回调),有的话取出来放到主栈执行
7.无线重复5-6 这个就是eventloop

5.面试题

5.1 求下面的打印结果

先来一道经典开胃菜,promise与setTimeout混合的题目
微任务里面套宏任务,宏任务里面套微任务
大家严格按照我章节4中提到的eventloop去分析,没什么难的

Promise.resolve().then((res) => {
    setTimeout(() => {
        console.log(1)
    })
    console.log(2)
})
setTimeout(() => {
        Promise.resolve().then(() => {
            console.log(3)
        })
        console.log(4)
    }
)

答案:2 4 3 1

5.2 求下面的打印结果

再来个类似的,我自己编的

console.log('1');
setTimeout(function() {
    new Promise(function(resolve) {
        console.log('2');
        resolve();
    }).then(function() {
        console.log('3')
    })
    console.log('4');
})
new Promise(function(resolve) {
    console.log('5');
    resolve();
}).then(function() {
    console.log('6')
})

setTimeout(function() { 
    new Promise(function(resolve) {
        console.log('7');
        resolve();
    }).then(function() {
        console.log('8')
    })
    setTimeout(function(){
        console.log('9')
    })
})

答案:1 5 6 2 4 3 7 8 9

5.3 求下面的打印结果

头条公司的面试题,这回加上了async await

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

简单解析:
简单来说,await前面的是同步,await后面的是微任务,如果不懂为什么这样的请看我这篇文章async-await
都知道async和await的表现,结合eventloop,宏任务,微任务应该不难得出答案吧
答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

5.4 求下面的打印结果

5.3的变种
无法也就是微任务里面套宏任务,宏任务里套微任务,类似5.1

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

答案:

script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout

5.5 求下面的打印结果

5.3的变种
比上一题还简单

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout2')
    },0)
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout3');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

答案

script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1

5.6求下面的打印结果

5.3变种,额外考察了点promise的原理 promise不懂的请移步到这promise

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

答案:

script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout

6.结语

建议大家一定要把我收集的面试题做了,这样才能彻底检验你是否弄懂了异步。
如果面试题哪里有不太明白的地方,请在留言区指出,我会添加详细的分析步骤。
如果你觉得本文对你有很大的帮助,求点赞,求收藏,求打赏,你们的支持是作者写作的最大动力!


Runningfyy
1.3k 声望661 粉丝