JavaScript面试必备知识点
异步-基础
单线程和异步
单线程
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