一、单线程
- js 是单线程语言,只能同时做一件事。
- js 和 DOM 渲染共用同一个线程,因为 js 可能会修改 DOM 结构。
二、异步
- 为什么要使用异步:程序在遇到等待(网络请求,定时任务)时,不能卡住。
- 常见的异步模式有:回调函数、promise、async/await等。
详细说明链接:什么叫异步-优快云博客。
三、同步和异步的区别
- 异步不会阻塞代码执行。
- 同步会阻塞代码执行。
四、异步的应用场景
- 网络请求,如 ajax 图片加载。
- 定时任务,如 setTimeout。
五、Promise
1. Promise是什么
- Promise 是异步操作的一种解决方案。
- Promise把 callback 嵌套的形式,变成了串联的形式。
- 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题。
2. Promise 的三种状态
1. 三种状态
- Promise 有 3 种状态,一开始是 pending(未完成)。
- 执行 resolve,变成 fulfilled(resolved),已成功。
- 执行 reject,变成 rejected,已失败。
2. 状态的表现
- pending 状态,不会触发 then 和 catch。
- fulfilled(resolved)状态,会触发后续的 then 回调函数。
- rejected 状态,会触发后续的 catch 回调函数。
3. then 和 catch 改变状态
- then 正常返回 fulfilled(resolved),里面有报错则返回 rejected。
- catch 正常返回 fulfilled(resolved),里面有报错则返回 rejected。
4. then 和 catch 的链式调用题目
- Promise 的实例化为同步部分会立即执行,后面的 then 和 catch 才进行异步处理
3. 使用 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 }
详细说明链接:Promise_小豪boy的博客。
六、event loop(事件循环/事件轮询)
1. event loop是什么?
- js 是单线程运行的。
- 异步(setTimeout、ajax等)使用回调,基于 event loop。
- DOM 事件也使用回调,基于 event loop。
- event loop 就是异步回调的实现原理。
2. js 如何执行?
- 从前到后,一行一行执行。
- 如果某一行执行报错,则停止下面代码的执行。
- 先把同步代码执行完,再执行异步。
3. event loop 的过程
- 以下面代码为示例讲解:
先介绍几个概念:
- Call Stack:调用栈,它里面是一个个待执行的函数。
- Web APIs:浏览器提供的一套操作浏览器功能和页面元素的API。
- Callback Queue:回调函数队列,它里面是一个个等待被执行的回调函数。
1. 初始状态
2. 执行第一行代码
- 代码推入调用栈,执行代码,输出结果,清空调用栈。
3. 执行第二行代码
- 代码推入调用栈,执行代码(但是setTimeout是浏览器定义的,执行代码相当于把里面的参数传给了Web APIs中的定时器timer里面,让浏览器5秒钟后将回调函数推入Callback Queue里面),执行完成清空调用栈。
4. 执行最后一行代码
5. 同步代码执行完
- 会启动 event loop 机制,会循环的向 Callback Queue 中去查找是否有异步的回调函数,有就将其推入到 Call Stack 中去执行,没有就继续循环。
- 开始刚刚执行完代码还不足5秒钟,Callback Queue 中没有数据。
- 等待时机,5秒中后定时器将回调函数推入 Callback Queue 。
- 此时,event loop 会发现回调函数立即将其推入 Call Stack 中去执行,执行完成清空调用栈。
4. 小结
- 同步代码,一行一行放在 Call Stack 执行。
- 遇到异步,会先 “记录” 下,等待时机(定时、网络请求等)。
- 时机到了,就移动到 Callback Queue。
- 如 Call Stack 为空(即同步代码执行完)Event Loop 开始工作。
- 轮询查找 Callback Queue,如有则移动到 Call Stack 执行。
- 然后继续轮询查找(永动机一样)。
七、async/await
1. async/await是什么
- 异步回调 callback hell。
- Promise then catch 链式调用,但也是基于回调函数。
- async/await 是同步语法,彻底消灭回调函数。
- 本质上只是一个语法糖,具体实现还是得有异步,还是得基于 event loop。
- async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。
await
操作符用于等待一个Promise
对象, 它只能在异步函数async function
内部使用.
2. async/await 和 Promise 的关系
- 执行 async 函数,返回的是 Promise 对象。
- await相当于 Promise 的 then。
- try- catch可捕获异常,代替了 Promise的 catch。
3. async/await 的执行流程
- 只要遇到了 await,await 本身会立即执行,而 await 后面的代码都相当于放在 callback 里,进行异步处理。
async function async1() { console.log('async1 start') // 2 await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行 console.log('async1 end') // 5 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务) } async function async2() { console.log('async2') // 3 } console.log('script start') // 1 async1() console.log('script end') // 4
async function async1() { console.log('async1 start') // 2 await async2() // 下面三行都是异步回调 callback 的内容 console.log('async1 end') // 5 await async3() // 下面一行是异步回调 callback 的内容 console.log('async2 end') // 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
4. for of 异步循环
for...of
是ES6的标准,该方法遍历的是对象的属性所对应的值(value:键值)。// 定时算乘法 function multi(num) { return new Promise(resolve => { setTimeout(() => { resolve(num * num) }, 1000) }) } async function test() { const nums = [1, 2, 3] for (let x of nums) { // 在 for...of 循环体的内部,遇到 await 会挨个串行计算 const res = await multi(x) console.log(res); } } test()
八、宏任务 macroTack 和微任务 microTask
1. 什么是宏任务,什么是微任务
- 宏任务:setTimeout,setInterval,Ajax,DOM事件。
- 微任务:Promise,async/await。
- 微任务执行时机比宏任务要早。
2. event loop 和 DOM 渲染
- 每次Call Stack清空(即每次轮询结束),即同步任务执行完。
- 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染。
- 然后再去触发下一次 Event Loop。
3. 微任务和宏任务的区别
- 宏任务:DOM渲染后触发,如 setTimeout。
- 微任务:DOM渲染前触发,如 Promise。
4. 为何微任务执行更早
- 微任务是 ES 语法规定的。
- 宏任务是由浏览器规定的。
- 在执行 Promise 时,因为 Promise 是 ES 规范 不是 W3C 规范,所以不会经过 Web APIs,会等待时机直接进入 micro task queue(微任务队列)。
执行步骤为:
- Call Stack清空
- 执行当前的微任务
- 尝试 DOM 渲染
- 触发 Event Loop
async function async1() { console.log('async1 start') // 2 await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行 // 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务) 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) { // 返回 Promise 之后,即同步执行完成,then 是异步代码 console.log('promise1') // 4 resolve() }).then(function () { // 异步,微任务 console.log('promise2') // 7 }) console.log('script end') // 5 // 同步代码执行完,(event loop - call stack 被清空) // 执行微任务 // (尝试触发 DOM 渲染) // 触发 Event Loop,执行宏任务