相关知识点
-
JS 是单线程运行的
-
异步要基于回调来实现
-
event loop 就是异步回调的实现原理
JS 是如何执行?
-
从前到后,一行一行执行
-
如果某一行执行报错,则停止下面代码的执行
-
下图所示,在JS引擎中,Call Stack
是调用栈,Web APIS
是在ES6规范之外,浏览器定义的,event loop
是事件轮询,callback Queue
回调函数队列。
执行顺序:
-
同步代码,一行一行的放在
Call Stack
中执行 -
遇到异步,先
Web APIS
记录下,然后等待时间(定时,网络请求等) -
等待完毕,就移动到
Callback Queue
-
如果
Call Stack
为空,即同步代码执行完毕,event loop
开始工作 -
轮询查找
Callback Queue
,如果有代码则移动到Call Stack中执行 -
然后继续轮询查找
Promise
Promise三种状态
-
pending( 等待中 )
-
resolved(成功)
-
rejected(失败)
pengding ——> resolved 或 pengding ——> rejected
Promise变化是不可逆的
状态的表现和变化
pengding 状态,不会触发 then
和 catch
resolved 状态,会触发后续的 then
回调函数
rejected 状态,会触发后续的 catch
回调函数
then 和 catch 对状态的影响
then
/ catch
正常返回 resolved ,里面有报错则返回rejected
const p1 = Promise.resolve().then(()=>{ return 100 }) console.log('打印一下p1',p1) p1.then(()=>{ console.log('在p1 中 resolved 触发 后续 then 回调') }) const p2 = Promise.resolve().then(()=>{ throw new Error('P2抛出来的错误') }) p2.then(()=>{ console.log('p2的状态是 reject 不会走then回调函数,所以不会打印这一行',p2) }).catch( err =>{ console.log('p2的打印的错误信息',err) } ) const p3 = Promise.reject('my error').catch(err =>{ console.log('p3抛出的错误',err) }) p3.then(()=>{ console.log('p3在catch之后状态是resolved,所以使用then') }) const p4 = Promise.reject('my error').catch(err =>{ throw new Error('P4抛出来的错误') }) p4.then(()=>{ console.log('p4在catch中抛出错误,所以现在的状态是reject,不会走then') }).catch(()=>{ console.log('p4的状态是reject') })
then/catch三道面试题
Promise.resolve().then(()=>{ console.log(1) // 1 }).catch(()=>{ console.log(2) }).then(()=>{ console.log(3) // 3 })
Promise.resolve().then(()=>{ console.log(1) // 1 throw new Error('erro1') }).catch(()=>{ console.log(2) // 2 }).then(()=>{ console.log(3) })
Promise.resolve().then(()=>{ console.log(1) // 1 throw new Error('erro1') }).catch(()=>{ console.log(2) // 2 }).catch(()=>{ console.log(3) })
async/await
异步回调容易导致callback hell,Promise then catch 链式调用虽然把层级铺开,但也是基于回调函数。
async/await 是用同步的语法来完成异步代码,彻底消灭回调函数。
async/await 和 Promise 的关系
async/await 可以彻底消灭异步回调,但和 Promise 并不互斥,两者是相辅相成的。
-
执行 async 函数,返回的是 Promise 对象
-
await 相当于 Promise 的then
-
try...catch 可以捕获异常,代替了Promise 的 catch
// 执行 async 函数,返回的是一个 Promise 对象 async function fn1(){ // return 100; //相当于 return Promise.resolve(100) return Promise.resolve(200) } const res1 = fn1() // console.log('res1',res1) res1.then(data=>{ console.log('data',data) // 200 })
// 如果 await 后面跟的是 Promise ,它就会当成 Promise 来使用;如果 await 后面跟的是一个值,会将其当封装成 Promise 的形式,去执行 Promise 的 then,然后返回 !(async function(){ const p1 = Promise.resolve(300) const data = await p1 // await 相当于 Promise then console.log('data',data) })() !(async function (){ const data1 = await 400 // await Promise resolve(400) console.log('data1',data1) })() !(async function (){ const data2 = await fn1() console.log('data2',data2) })()
// try...catch 可以捕获异常,代替了Promise 的 catch !(async function (){ const p4 = Promise.reject('err1') // rejected 状态 try { const res = await p4 console.log(res) } catch (ex) { console.log(ex) // try..catch 相当于 Promise catch } })() !(async function (){ const p4 = Promise.reject('err1') // rejected 状态 const res = await p4 // await -> then console.log('res',res) })()
async/await的题目
await的后面都可以看作是callback的内容
async function async1 (){ console.log('async1 start') // 第二步 await async2() console.log('async1 end') // 第五步 } async function async2 () { console.log('async2') // 第三步 } console.log('srcipt start') // 第一步 async1() console.log('srcipt end') // 第四步 同步代码执行完毕
async function async1 (){ console.log('async1 start') // 第二步 await async2() // 以下是 callback 的内容 console.log('async1 middle') // 第五步 await async3() console.log('async1 end') // 第七步 } async function async2 () { console.log('async2') // 第三步 } async function async3 () { console.log('async3') // 第六步 } console.log('srcipt start') // 第一步 async1() console.log('srcipt end') // 第四步 同步代码执行完毕
for...of
function muti(num) { return new Promise(resolve => { setTimeout(()=>{ resolve(num * num) },500) }) } const nums = [1,2,3] // 0.5秒之后执行三次 // nums.forEach(async(i)=>{ // const res = await muti(i) // console.log(res) // }) //每隔0.5s执行一次 !(async function(){ for(let i of nums ){ const res = await muti(i) console.log(res) } })()
微任务和宏任务
微任务和宏任务是异步里api的分类
异步的执行顺序和出场顺序有关,但不同类型的异步执行顺序和出场顺序无光。
-
宏任务:setTimeout、setInterval、Ajax、DOM事件
-
微任务:Promise、async/await
微任务执行时机比宏任务要早
console.log(100); // 宏任务 setTimeout(()=>{ console.log(200); }) // 微任务 Promise.resolve().then(()=>{ console.log(300); }) console.log(400);
event loop 和 DOM 渲染
步骤:
-
call stack 空闲的时候(同步代码执行完毕)
-
执行微任务
-
再进行dom渲染
-
执行宏任务
-
执行event loop
-
将call Queue 队列里的回调函数添加到call stack里执行
-
重复第一步
每次 call stack清空(即每次轮询结束),即同步任务执行完,都是DOM重新渲染的机会,DOM结构如有改变则重新渲染,然后再去触发下一次 event loop。
微任务和宏任务的区别:
-
宏任务:DOM渲染后触发,如setTimeout
-
微任务:DOM渲染前触发,如Promise
// 修改 DOM const $p1 = $('<p>一段文字</p>') const $p2 = $('<p>一段文字</p>') const $p3 = $('<p>一段文字</p>') $('#container') .append($p1) .append($p2) .append($p3) // // 微任务:渲染之前执行(DOM 结构已更新) // Promise.resolve().then(() => { // const length = $('#container').children().length // alert(`micro task ${length}`) // }) // 宏任务:渲染之后执行(DOM 结构已更新) setTimeout(() => { const length = $('#container').children().length alert(`macro task ${length}`) })
深入思考:为何两者会有以上区别,一个在渲染前,一个在渲染后?
-
微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
-
宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
题目
1.描述event loop(事件循环/事件轮询)的机制,可画图
上图代码执行顺序:
-
将 console.log("Hi") 推入调用栈,调用栈会执行代码
-
执行代码,控制台打印“Hi”,调用栈清空
-
执行 setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器放到Web APIs中,到时间后将回调函数放到回调函数队列中
-
执行完了setTimeout, 清空调用栈
-
console.log("Bye")进入调用栈,执行,调用栈清空
-
同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制
-
五秒之后,定时器将cb1推到回调函数队列中
-
事件循环将cb1放入调用栈
-
执行cb1,将console.log("cb1")放入调用栈
-
console.log(“cb1”)出栈,执行,打印“cb1”
-
cb1函数只有一行,已经执行完了,也就可以清空了
-