系列文章目录
JavaScript 知识梳理,收录了web前端面试 95%以上 的高频考点,满满的干货。给你做一个高效的知识梳理,为你的面试保驾护航!
内容 | 参考链接 |
---|---|
HTML & CSS 篇 | HTML & CSS 篇 |
JavaScript 篇(一) | JavaScript 篇(一)【JS的三座大山 】原型和原型链、作用域和闭包、异步 |
JavaScript 篇(二) | JavaScript 篇(二)【JS 异步进阶】 |
JavaScript-Web-API 篇(一) | JavaScript-Web-API 篇(一)DOM、BOM、事件 |
JavaScript-Web-API 篇(二) | JavaScript-Web-API 篇(二)AJAX、存储 |
HTTP 篇 | HTTP 篇 |
性能优化篇 | 性能优化篇(手写防抖、手写节流、XXS攻击、XSRF攻击) |
经典面试题-JS篇 | 经典面试题 HTML、CSS、JavaScript篇 |
文章目录
JS 异步进阶
什么是 event loop(事件循环/事件轮询)
- JS 是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
JS 如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
event loop 执行过程
event loop 过程 1
- 同步代码,一行一行放在 Call Stack 执行
- 遇到异步,会先记录下来,等待时机(定时、网络请求等)
- 时机到了,就移动到 Callback Queue
event loop 过程 2
- 如果 Call Stack 为空(即同步代码执行完)Event loop 开始工作
- 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
- 然后继续轮询查找(永动机一样)
图解代码执行过程:
console.log('Hi')
setTimeout(function cb1() {
console.log('cb1')
}, 2000)
console.log('Bye')
图片出处:https://coding.imooc.com/lesson/400.html#mid=35171
DOM 事件与 event loop
- JS 是单线程
- 异步(setTimeout,ajax等)使用回调,基于 event loop
- DOM 事件也使用回调,基于 event loop
Promise 三种状态
三种状态
- pending resolve rejected
- pending => resolve 或 pending => rejected
- 变化不可逆
状态的表现
- pending 状态,不会触发 then 和 catch
- resolved 状态,会触发后续的 then 回调函数
- rejected 状态,会触发后续的 catch 回调函数
成功的回调:只执行 then,不会执行 catch
// 输出结果:data 100
const p1 = Promise.resolve('100')
p1.then(data => {
console.log('data', data)
}).catch(err => {
console.log('err', err)
})
失败的回调:只执行 catch,不执行 then
// 输出结果:err2 -100
const p2 = Promise.reject('-100')
p2.then(data => {
console.log('data2', data)
}).catch(err => {
console.log('err2', err)
})
then 和 catch 改变状态
- then 正常返回 resolved,里面有报错则返回 rejected
- catch 正常返回 resolved,里面有报错则返回 rejected
第一种情况:
const p1 = Promise.resolve().then(() => {
return 100
})
console.log('p1', p1);
p1.then(() => {
console.log('200')
})
const p2 = Promise.resolve().then(() => {
throw new Error('then error')
})
console.log('p2', p2)
p2.then(() => {
console.log('300')
}).catch(err => {
console.log('error', err)
})
第二种情况:
const p3 = Promise.reject('my error').catch(err => {
console.log(err)
})
console.log('p3', p3)
p3.then(() => {
console.log(100)
})
const p4 = Promise.reject('my error').catch(err => {
throw new Error('catch err')
})
console.log('p4', p4)
p4.then(() => {
console.log(200)
}).catch(() => {
console.log('some err')
})
Promise 关于 then 和 catch 的面试题
示例 1 :resolve 成功输出 1 后又是一个成功的回调(fulfilled),之后执行 then,不会执行 catch
// 输出结果:1 3
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
示例 2:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),执行 then
// 输出结果:1 2 3
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
示例 3:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),之后就结束了
// 输出:1 2
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).catch(() => {
console.log(3)
})
async 和 await 基本使用
- 使用 await,必须有 async 包裹
!
的作用是防止此前代码结尾不加分号
// 加载图片
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
document.body.appendChild(img)
})
return p
}
const src = 'xxx.png'
!(async function() {
const img = await loadImg(src)
console.log(img.width, img.height) // 100 110
})()
async/await 和 Promise 的关系
- async/awiat 是消灭异步回调的终极武器
- 但和 Promise 并不互斥
- 反而,两者相辅相成
- 执行 async 函数,返回的是 Promise 对象
- await 相当于 Promise 的 then
- try…catch 可捕获异常,代替了 Promise 的 catch
示例 1:执行 async 函数,返回的是一个 Promise 对象
async function fn1(){
// return 100 //相当于 return Promise.resolve(100)
return Promise.resolve(200)
}
const res1 = fn1() // 执行 async 函数,返回的是一个 Promise 对象
console.log('res1', res1);
res1.then(data => {
console.log('data', data) // 200
})
示例 2:
async function fn() {
return Promise.resolve(200)
}
!(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 fn()
console.log('data2', data2);
})()
示例 3:try…catch 异常捕获
!(async function() {
const p4 = Promise.reject('error1')
try {
const res = await p4
console.log(res)
} catch (err) {
console.error(err) // try...catch 相当于 promise catch
}
})()
示例 4:
!(async function() {
const p5 = Promise.reject('err') // rejected 状态
const res = await p5 // await -> then
console.log('res', res) // 不会执行
})()
异步的本质
- 异步的本质是回调函数
- async/await 是消灭异步回调的终极武器
- JS 是单线程,还得是有异步,还得是基于 event loop
- async/await 是一个很好的语法糖
示例:await 后面的内容是 异步的
async function async1() {
console.log('async1 start') // 2
await async2() // undefined
// await 的后面,都可以看作是 callback 里的内容,即异步
// 类似,event loop,setTimeout()
console.log('async1 end') // 5
}
async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1
async1()
console.log('script end') // 4
输出顺序:
示例 2:注——await 后面的都是异步内容,等主线程执行完才会触发 event loop 机制执行
async function async1() {
console.log('async1 start') // 2
await async2() // undefined
// 下面三行都是异步回调 callback 的内容
console.log('async1 end') // 5
await async3()
console.log('async1 end 2') // 7
}
async function async2() {
console.log('async2') // 3
}
async function async3() {
console.log('async3') // 6
}
console.log('script start') // 1
async1()
console.log('script end') // 4
// 同步代码执行完,event loop
宏任务与微任务
- 宏任务:setTimeout、setInterval、Ajax、DOM事件
- 微任务:Promise、async/await
- 微任务执行时机比宏任务要早
Event Loop 和 DOM 渲染
- 每次 Call Stack 清空(即每次轮询结束)
- 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
- 然后再去触发下一次 Event Loop
图片出处:https://coding.imooc.com/lesson/400.html#mid=35181
宏任务和微任务的区别
- 宏任务:DOM 渲染后触发,如 setTimeout
- 微任务:DOM 渲染前触发,如 Promise
宏任务和微任务的根本区别
- 在 micro task queue 里执行当前的微任务
- 微任务比宏任务的执行时机更早
- 微任务发生在 DOM 渲染之前
- 宏任务发生在 DOM 渲染之后
JS 异步面试题
什么是宏任务和微任务、两者的区别
- 宏任务:setTimeout,setInterval,Ajax,DOM 事件
- 微任务:Promise,async/await
- 微任务执行时机比宏任务要早
场景题 — async、await 语法
- async 修饰的函数返回的是 promise 实例
- a 没有 await 修饰,输出的是 promise 实例
- b 有 await 修饰,相当于 then ,输出的是 100
示例 1:
async function fn() {
return 100
}
(async function() {
const a = fn()
const b = await fn()
console.log(a)
console.log(b)
})()
示例 2:失败后面的代码都不会执行
(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 的顺序问题
- 先执行主线程的任务
- 再执行微任务
- 再执行宏任务
示例:
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
async/await 的顺序问题
- 主线程 => 微任务 => 宏任务
- 初始化 promise 时,传入的函数会立刻被执行(主线程任务)
async function async1 () {
console.log('async1 start') // 2
await async2()
// 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,执行宏任务
手写 Promise 源码
- 有难度有深度
/**
* @description MyPromise
* @author 前端杂货铺
*/
class MyPromise {
state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
value = undefined // 成功后的值
reason = undefined // 失败后的原因
resolveCallbacks = [] // pending 状态下,存储成功的回调
rejectCallbacks = [] // pending 状态下,存储失败的回调
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallbacks.forEach(fn => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallbacks.forEach(fn => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'pending') {
const p1 = new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
this.rejectCallbacks.push(() => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
})
return p1
}
if (this.state === 'fulfilled') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
return p1
}
if (this.state === 'rejected') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
return p1
}
}
// 就是 then 的一个语法糖,简单模式
catch (fn) {
return this.then(null, fn)
}
}
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => resolve(value))
}
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
MyPromise.all = function (promiseList = []) {
const p1 = new MyPromise((resolve, reject) => {
const result = [] // 存储 promiseList 所有的结果
const length = promiseList.length
let resolvedCount = 0
promiseList.forEach(p => {
p.then(data => {
result.push(data)
// resolvedCount 必须在 then 里面做 ++
// 不能用 index
resolvedCount++
if (resolvedCount === length) {
// 已经遍历到了最后一个 promise
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
return p1
}
MyPromise.race = function (promiseList = []) {
let resolved = false // 标记
const p1 = new Promise((resolve, reject) => {
promiseList.forEach(p => {
p.then(data => {
if (!resolved) {
resolve(data)
resolved = true
}
}).catch((err) => {
reject(err)
})
})
})
return p1
}
不积跬步无以至千里 不积小流无以成江海
点个专注不迷路,持续更新中…