03JavaScript面试必备知识点异步

异步-基础

单线程和异步

单线程
	JS是单线程语言,只能同时做一件事
	浏览器和nodejs已支持JS启动进程,如Web Worker
	JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
为什么需要异步
	遇到等待(网络请求,定时任务)不能卡住
	需要异步
	回调callback函数形式

同步和异步的区别是什么

区别:
	基于JS是单线程语言
	异步不会阻塞代码执行
	同步会阻塞代码执行

异步的应用场景

网络请求,如ajax图片加载
定时任务,如setTimeout
//ajax
console.log('start');
$.get('./data.json', function (data1) {
  console.log(data1);
})
console.log('end');

//图片加载
console.log('start');
let img = document.createElement('img')
img.onload = function () {
  console.log('loaded');
}
img.src = '/xxx.png'
console.log('end');

//setTimeout
console.log(1);
setTimeout(function () {
  console.log(2);
}, 1000)
console.log(3);
setTimeout(function () {
  console.log(4);
}, 0)
console.log(5);
//运行结果 1 3 5 4 2


//setInterval
console.log(100);
setInterval(function () {
  console.log(200);
}, 1000)
console.log(300);

callback hell和Promise

//回调地狱
//获取第一份数据
$.get(url1, (data1) => {
  console.log(data1);
  //获取第二份数据
  $.get(url2, (data2) => {
    console.log(data2);
    //获取第三份数据
    $.get(url3, (data3) => {
      console.log(data3);
    })
  })
})

//Promise形式
function getData(url) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url,
      success(data) {
        resolve(data)
      },
      error(err) {
        reject(err)
      }
    })
  })
}
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 => {
  console.log(data1);
  return getData(url2)
}).then(data2 => {
  console.log(data2);
  return getData(url3)
}).then(data3 => {
  console.log(data3);
}).catch(err => console.error(err))

手写Promise加载一张图片

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
//     console.log(img.width)
//     return img
// }).then(img => {
//     console.log(img.height)
// }).catch(ex => console.error(ex))

const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))


异步-进阶

知识点

event loop
Promise进阶
asyna/await
微任务/宏任务

面试题

问答题:
	请描述event loop(事件循环/事件轮询)的机制,可画图
	什么是宏任务和微任务,两者有什么区别?
	Promise有哪三种状态?如何变化?
场景题
	Promise then和catch的连接
	async/await语法
	Promise和setTimeout的顺序
	async/await的顺序问题

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

什么是event loop
	JS是单线程运行的
	异步要基于回调实现
	event loop就是异步回调的实现原理
JS如何执行?
	从前向后,一行一行解释执行。如果某行出错,则停止下面代码运行
	先把同步代码执行完,再执行异步
event loop执行过程
	同步代码,一行一行放在Call Stack执行
	遇到异步,会先“记录”下,等待时机(定时、网络请求等)
	时机到了,就移动到Callback Stack中
	如果Call Stack为空(即同步代码执行完).Event Loop开始工作
	轮询查找Callback Queue,如果有则移动到Call Stack执行
	然后继续轮询查找(像永动机一样)

DOM事件和event loop的关系

异步(setTimeout,ajax等)和DOM事件,都使用回调,都是基于event loop的
 <button id='btn'>提交</button>
 <script>
   console.log('hi');
    btn = document.getElementById('btn')
    btn.addEventListener('click', function () {
      console.log('button clicked');
    })
   console.log('Bye');
 </script>

运行:hi Bye 当点击btn时才button clicked


Promise有哪三种状态?

pending resolved(fulfilled) rejected(过程中 成功 失败)
pending——>resolved 或pending——>rejected
变化不可逆
// 刚定义时,状态默认为 pending
// 执行 resolve() 后,状态变成 resolved
// 执行 reject() 后,状态变成 rejected
const p1 = new Promise((resolve, reject) => {

})
console.log('p1', p1);//pending

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()  //fulfilled
  })
})
console.log('p2', p2);//pending
setTimeout(() => {
  console.log('setTimeout-resolve', p2);//fulfilled
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject()
  })
})
console.log('p3', p3); //pending
setTimeout(() => {
  console.log('setTimeout-reject', p3); //rejected
})


// 直接返回一个 resolved 状态
Promise.resolve(100)
// 直接返回一个 rejected 状态
Promise.reject('some error')

状态和 then catch

状态变化会触发 then catch
	- pending 不会触发任何 then catch 回调
	- 状态变为 resolved 会触发后续的 then 回调
	- 状态变为 rejected 会触发后续的 catch 回调
//状态变化会触发 then catch
const p1 = Promise.resolve(100) //resolved ,执行then
p1.then(data => {
  console.log('data', data)
}).catch(err => {
  console.error('err', err)
})

const p2 = Promise.reject(300) //rejected,执行catch
p2.then(data => {
  console.log('data2', data)
}).catch(err => {
  console.error('err2s', err)
})
//data 100
//err2s 300

then catch 会继续返回 Promise ,此时可能会发生状态变化!!!

then正常返回resolved,里面有报错则返回rejected
catch正常返回resolved,里面有报错则返回rejected
// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
    return 100
})

// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
    throw new Error('err')
})

// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
})

// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
    throw new Error('err')
})

看一个综合的例子,即那几个面试题

// 第一题
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
//1 3


// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).then(() => {
    console.log(3)
})
//1  2  3


// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).catch(() => {
    console.log(3)
})
//1   2

async/await

- 语法介绍
- 和 Promise 的关系
- 异步本质
- for...of

有很多 async 的面试题,例如
- async 直接返回,是什么
- async 直接返回 promise
- await 后面不加 promise
- 等等

语法介绍

用同步的方式,编写异步。
function loadImg(src) {
    const promise = new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
    return promise
}

async function loadImg1() {
    const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
    const img1 = await loadImg(src1)
    return img1
}

async function loadImg2() {
    const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
    const img2 = await loadImg(src2)
    return img2
}

(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    try {
        // 加载第一张图片
        const img1 = await loadImg1()
        console.log(img1)
        // 加载第二张图片
        const img2 = await loadImg2()
        console.log(img2)
    } catch (ex) {
        console.error(ex)
    }
})()

和 Promise 的关系

async/await和Promise的关系
	async/await是消灭异步回调的终极武器,但是和Promise并不互斥,两者相辅相成
	执行async函数,返回的是Promise对象
	await相当于Promise的then
	try...catch可捕获异常,代替了Promise的catch
  • async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
async function fn2() {
    return new Promise(() => {})
}
console.log( fn2() )

async function fn1() {
    return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
  • await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
  • await 后续跟非 Promise 对象:会直接返回
(async function () {
    const p1 = new Promise(() => {})
    await p1
    console.log('p1') // 不会执行
})()

(async function () {
    const p2 = Promise.resolve(100)
    const res = await p2
    console.log(res) // 100
})()

(async function () {
    const res = await 100
    console.log(res) // 100
})()

(async function () {
    const p3 = Promise.reject('some err')
    const res = await p3
    console.log(res) // 不会执行
})()
  • try…catch 捕获 rejected 状态
(async function () {
    const p4 = Promise.reject('some err')
    try {
        const res = await p4
        console.log(res)
    } catch (ex) {
        console.error(ex)
    }
})()

总结来看:

  • async 封装 Promise
  • await 处理 Promise 成功
  • try…catch 处理 Promise 失败

异步本质

await 是同步写法,但本质还是异步调用。
JS还是单线程,还是得有异步,还是基于event loop
async function async1 () {
  console.log('async1 start')
  await async2()
  console.log('async1 end') // 关键在这一步,它相当于放在 callback 中,最后执行
}

async function async2 () {
  console.log('async2')
}

console.log('script start')
async1()
console.log('script end')

/*
先执行同步代码,后异步。async1 end最后执行
script start
async1 start
async2
script end
async1 end
*/

即,只要遇到了 await ,后面的代码都相当于放在 callback 里。


for…of

// 定时算乘法
function multi(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
// function test1 () {
//     const nums = [1, 2, 3];
//     nums.forEach(async x => {
//         const res = await multi(x);
//         console.log(res);
//     })
// }
// test1();

// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
    const nums = [1, 2, 3];
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res)
    }
}
test2()

微任务和宏任务

宏任务macroTask和微任务microTask
	什么是宏任务,什么是微任务
	eventloop和DOM渲染
	微任务和宏任务的区别

介绍

- 宏任务:setTimeout setInterval Ajax DOM 事件
- 微任务:Promise async/await(对于前端来说)
- 微任务比宏任务执行的更早
console.log(100)
//宏任务
setTimeout(() => {
    console.log(200)
})
//微任务
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200

event loop 和 DOM 渲染

event loop过程
	每次Call Stack清空(即每次轮询结束),即同步任务执行完
	都是DOM重新渲染的机会,DOM结果如有改变则重新渲染
	然后再去触发下一次Event Loop
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
            .append($p1)
            .append($p2)
            .append($p3)

console.log('length',  $('#container').children().length )
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预

// 另外,按照 event loop 触发 DOM 渲染时机,setTimeout 时 alert ,就能看到 DOM 渲染后的结果了
setTimeout(function () {
    alert('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了')
})

宏任务和微任务的区别

- 宏任务:DOM 渲染后再触发
- 微任务:DOM 渲染前会触发
// 修改 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}`)
})

再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?

从event loop解释,为何微任务执行更早?
- 微任务:微任务是由ES6语法规定的,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
- 宏任务:宏任务是由浏览器规定的,JS 引擎不处理,浏览器(或 nodejs)干预处理。
- 微任务存放到micro task 队列,宏任务Callback队列

在这里插入图片描述


场景题-async/await语法

//第一题
async function fn() {
  return 100
}
(async function () {
  const a = fn() // ??               // promise
  const b = await fn() // ??         // 100
  console.log(a);
  console.log(b);
})()
//第二题
!(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) //c报错,往后不执行
  console.log('c', c)
  console.log('end')
})() // 执行完毕,打印出那些内容?
/*
start
a 100
b 200
c报错,往后不执行
*/

//第三题 必会!!!
async function async1() {
  console.log('async1 start')   // 2
  await async2() //执行,返回 Promise

  // await后面做回调内容,微任务
  console.log('async1 end')     // 6
}

async function async2() {
  console.log('async2')       // 3
}

console.log('script start')   // 1

setTimeout(function () { // 异步,宏任务
  console.log('setTimeout')     // 8
}, 0)

async1()

//初始化Promise时,传入的函数会立即执行
new Promise(function (resolve) {
  console.log('promise1')       // 4
  resolve()
}).then(function () { // 异步,微任务
  console.log('promise2')       // 7
})

console.log('script end')       // 5
//同步代码执行完毕(event loop -call stack被清空)
//执行微任务
//(尝试触发DOM渲染)
//触发Event loop,执行宏任务
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

场景题-Promise和setTimeout的顺序

console.log(100)
setTimeout(() => {
  console.log(200)
})
Promise.resolve().then(() => {
  console.log(300)
})
console.log(400)
// 100 400 300 200
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值