js异步进阶(含微任务和宏任务讲解)面试常考点总结

js异步进阶

  • 请描述event loop(事件循环/事件轮询)的机制,可画图
  1. 先执行同步代码
  2. 遇到异步代码,先记录下,等待时机触发
  3. 时机到了,就会被放到callback queue
  4. 如果call stack为空(即同步代码执行完)event loop开始工作
  5. 轮询查找callback queue,如果有则移到call stack执行
  6. 然后轮询查找
  • 什么是宏任务和微任务,两者有什么区别
    见知识点-宏任务和微任务
  • Promise有哪三种状态,如何变化,知识点见promise
    pending、fulfilled、rejected,promise的状态变化不可逆,只能由pending变为fulfilled或rejected
  • Promise then和catch连接问题,知识点见promise,请写出输出顺序,代码如下:
// 第一题  1 3
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})

// 第二题 1 2 3
Promise.resolve().then(() => {
    console.log(1)
    throw new Error('error1')
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})

// 第三题 1 2
Promise.resolve().then(() => {
    console.log(1)
    throw new Error('error1')
}).catch(() => {
    console.log(2)
}).catch(() => {
    console.log(3)
})
  • async/await语法,代码如下:
// 第一题
async function fn() {
    return 100
} 

(function() {
    const a = fn() // ?? promise pending
    const b = await fn() // ?? promise resolved 100
})();

// 第二题,分别打印出什么
// start 100 200 error:uncaught(in promsie) 300
!(async function() {
    console.log('start')
    const a = await 100
    console.log('a', a)
    const b = await Promise.resolve(200)
    console.log('b', b)
    const c = await Promise.reject(300)
    console.log('c', c)
    console.log('end')
})();
  • promise和setTimeout执行顺序,代码如下:
// 100 400 300 200
console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
  • 头条经典面试题,代码如下:
// script start
// async1 start
// async2
// promise1
// 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')
// 宏任务最后执行,所以晚于promise then这个微任务的执行
setTimeout(() => {
    console.log('setTimeout')
})
async1()
new Promise(resolve => {
    console.log('promise1')
    resolve()
}).then(() => {
    console.log('promise2')
})
console.log('script end')

event loop(事件循环/事件轮询)

  • js是单线程运行
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理

js如何执行?

  1. 从前到后,一行一行执行
  2. 执行到某一行报错,则停止下面代码的执行
  3. 先把同步代码执行玩,再执行异步代码

异步代码执行?

  1. 先执行同步代码
  2. 遇到异步代码,先记录下,等待时机触发
  3. 时机到了,就会被放到callback queue
  4. 如果call stack为空(即同步代码执行完)event loop开始工作
  5. 轮询查找callback queue,如果有则移到call stack执行
  6. 然后轮询查找
const LOOP_MAX_COUNT = 10000
for (let index = 0; index < LOOP_MAX_COUNT; index++) {
    console.log(index);
}

// 这个定时器的执行也需要同步代码执行完之后才会执行,
// 即当同步代码执行完之后,这个定时器才会被移到callback queue,之后才会被event loop添加到call stack执行
setTimeout(function () {
    console.log('timeout')
}, 300)

dom事件和event loop的关系:
代码执行到dom事件时,会首先记录下这个回调,将其存储下来,当用户触发了该事件时,会将存储下来的回调函数放到call stack去执行。但注意dom事件不是异步,只是基于event loop实现的

注意:任何基于回调的代码,包括dom事件、定时器、网络请求,都是基于event loop的

Promise

  • promise的三种状态:
    pending、fulfilled、rejected,promise的状态变化不可逆,只能由pending变为fulfilled或rejected
  • promise状态的表现和变化:
  1. pending状态不会触发then或catch
  2. fulfilled状态,会触发后续的then的第一个回调函数
  3. rejected状态,会触发catch或then的第二个回调函数
  • then和catch对状态的影响:
  1. then正常返回resolved,里面报错则返回rejected

  2. catch正常返回resolved,里面报错则返回rejected(catch实质是then的第二个回调函数)

    举个例子:

const p1 = Promise.resolve().then(_ => 100); // resolved
const p2 = Promise.reject().catch(_ => 100); // resolved
const p3 = Promise.resolve().then(() => { throw new Error('something goes wrong') }); // rejected
const p4 = Promise.reject().catch(() => { throw new Error('something goes wrong') }); // rejected

async/await

async/await是同步语法,彻底消灭了回调函数。举个例子:

// 如下的打印顺序是start 2s之后 async/await end
!(async () => {
    console.log('start')
    const res = await new Promise((resolve) => {
        setTimeout(() => {
            resolve('async/await')
        }, 2000)
    });
    console.log(res)
    console.log('end')
})()

async/await和Promise的关系

async/await是Promise的语法糖,执行async函数,返回的是Promise对象,await相当于promise的then,await必须要用try catch捕获异常,代替了Promise的catch

async function fn1() {
    return 100
}

const res = fn1();

res.then(data => console.log(data)) // 100

异步的本质

  1. async/await是消灭异步回调的终极武器
  2. js还是单线程,还得有异步,还是得基于event loop
  3. async/await只是一个语法糖
async function async1() {
    console.log('async1 start') // 2
    await async2() // 先执行async2(),然后await,后面的代码都可以看成是一个callback里的内容,即异步
    // 类似event loop
    // 这个时候后面的代码会被添加到callback queue等待被调用(同步代码执行玩之后调用)
    console.log('async 1 end'); // 5
}

async function async2() {
    console.log('async2'); // 3
}
console.log('script start'); // 1
async1()
console.log('script end'); // 4

for…of

  • 类似for…in的遍历
  • for…of常用于异步的遍历
function mutilple(num) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000);
    })
}
const nums = [1, 2, 3]
!(async () => {
    for (const num of nums) {
        const res = await mutilple(num);
        console.log(res)
    }
})()

宏任务和微任务

  • 什么是宏任务和微任务
    宏任务:setTimeout、setInterval、ajax、Dom事件
    微任务:Promise、async/await
    微任务执行时机比宏任务要早(先记住)
    举个例子,如下代码打印顺序:
console.log(100);
// 宏任务
setTimeout(() => {
    console.log(200);
});
// 微任务
setTimeout(() => {
    console.log(201);
});
Promise.resolve().then(() => console.log(300));
console.log(400)
  • event loop和DOM渲染
    每次call stack清空(即同步代码执行完),都是DOM重新渲染的机会,DOM结构如果有改变则会重新渲染。然后再去触发下一次event loop
  • 微任务和宏任务的区别
    宏任务:DOM渲染后触发,如setTimeout
    微任务:DOM渲染前触发,如Promise
    举个例子:
const p1 = document.createElement('p');
const container = document.querySelector('.container')
p1.innerText = '一段文字';
const p2 = p1.cloneNode(true);
const p3 = p1.cloneNode(true);
container.appendChild(p1)
container.appendChild(p2)
container.appendChild(p3)
// 同步代码完成,dom即将渲染
Promise.resolve().then(() => {
    console.log('length1', container.childElementCount)
    // 这个时候dom还未渲染
    alert('Promise then')
});

setTimeout(() => {
    console.log('length2', container.childElementCount)
    // 这个时候dom已经渲染完成
    alert('timeout')
});

  准确来说event loop执行过程是:在执行代码时,遇到异步代码先将回调放在异步队列,将promise、async/await等ES规范的微任务放进异步队列中的微任务队列;而将定时器、dom事件回调、网络请求等w3c规范的异步任务放到web APIs,等到一定时机的时候将其放入宏任务队列,当同步代码执行完之后,先执行当前的微任务,尝试DOM渲染,再执行当前的宏任务,等完成之后再进行event loop。用顺序表示为:

  1. 执行代码
  2. 遇到异步代码,将其分为微任务和宏任务进行不同队列的存放
  3. 同步代码执行完
  4. 将微任务队列的第一个放入call stack执行
  5. 尝试dom渲染
  6. dom渲染之后,将宏任务队列中的第一个放入call stack执行
  7. 宏任务执行完之后,进行下一次event loop

你可能感兴趣的:(原生js)