异步和单线程
单线程
- JS是单线程语言,只能同时做一件事
- 浏览器和nodejs已支持启动进程,如Web Worker
- JS和DOM渲染共用一个线程,因此JS可修改DOM结构
异步
- 遇到等待(网络请求、定时任务)不能卡住
- 需要异步
- 回调callback函数形式
同步和异步
- 基于JS是单线程语言
- 异步不会阻塞代码执行,同步会
手写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
}
loadImg(url1).then(img1 => {
console.log(img1.width);
return img1
}).then(img1 => {
console.log(img1.height);
return loadImg(url2)
}).then(img2 => {
console.log(img2.width);
return img2
}).then(img2 => {
console.log(img2.height);
})
setTimeout笔试题
// 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
异步进阶
event loop(事件循环/事件轮询)
什么是event loop
- JS是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
JS如何执行
- 从前到后,一行一行执行
- 如果某一行报错,则停止执行下面的代码
- 先把同步代码执行完,再执行异步代码
总结event loop过程
- 同步代码,一行一行放在call stack执行
- 遇到异步(定时,网络请求),会先“记录”下,等待时机
- 时机到了,就移动到callback queue
- 如果call stack为空了,即同步代码执行完毕,event loop开始工作
- 轮询查找callback queue,如有,移动到call stack执行
- 然后继续轮询查找
DOM事件和event loop
- JS是单线程的
- 异步(setTimeout、Ajax等)使用回调,基于event loop
- DOM事件也使用回调,基于event loop
peomise进阶
三种状态
- pending
- fulfilled
- rejected
状态的变化和表现
- pending -> resolved,pending -> rejected
- 变化是不可逆的
- pending状态下,不会触发then和catch
- resolved状态下,会触发then
- rejected状态下,会触发catch
then和catch对状态的影响
- then正常返回resolved,里面有报错则返回rejected
- catch正常返回resolved,里面有报错则返回rejected
关于then和catch的面试题
// 第一题
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
背景
- 异步回调callback hell
- promise then catch链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
async/await与promise的关系
- async/await 是消灭回调的终极武器
- 但是是promise并不互斥
- 两者相辅相成
- 执行async函数,返回的是promise对象
- await相当于promise的then
- try...catch可捕获异常,代替了promise的catch
async/await是语法糖,本质上还是回调函数
async function async1 () {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 5 关键在这一步,它相当于放在 callback 中,最后执行
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
async1()
console.log('script end') // 4
// 即,只要遇到了 `await` ,后面的代码都相当于放在 callback 里。
微任务/宏任务
定义
- 宏任务:setTimeout,setInterval,Ajax,DOM事件
- 微任务:Promise,async/await
- 微任务执行时机比宏任务早
event loop和DOM渲染
- JS是单线程的,和DOM渲染共用一个线程
- JS执行的时候,要留一些时机供DOM渲染
执行过程
- 每次call stack清空,即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构有改变则重新渲染
- 然后再去触发下一次event loop
微任务和宏任务
- 区别:宏任务在DOM渲染之后触发;微任务在DOM渲染之前触发
- 根本区别:微任务是es6语法规定的,由JS引擎统一处理;宏任务是由浏览器规定的,由浏览器(nodejs)干预处理
JS异步面试题
执行顺序
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 () { // 宏任务 setTimeout
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