[TOC]
# 題1、js 中哪些操作是異步的?
答: AJAX(與服務器通信)、setTimeout、setInterval、setImmediate (node)、process.nextTick(node) 。
# 題2、事件循環是怎么回事?
答: 事件循環(EventLoop),JS 代碼執行的順序。
JS 把代碼分為三部分:同步代碼、宏任務代碼、微任務代碼。
執行順序: 同步 -> 微任務 -> 宏任務。
微任務代碼: node 中的 process.nextTick 等
宏任務代碼: ajax、 setTimeout、 setInterval 等
JS 在執行代碼時分為三種任務:
* 同步代碼
* 異步代碼
* 宏任務(macrotask): script(整體代碼), AJAX、事件回調(鼠標鍵盤事件)、 setTimeout, setInterval, setImmediate(node獨有), I/O, UI rendering
* 微任務(microtask) (優先級更高) process.nextTick(node獨有), Promises.then, Object.observe(廢棄), MutationObserver
執行順序:
1. 先執行同步代碼
2. microtask (微任務) 中的代碼
3. 宏任務中的代碼
~~~
代碼演示:只能在 node 環境中執行:
?
// 同步代碼
console.log(1);
?
// 宏任務代碼
setTimeout(() => {
? ?console.log(2);
}, 0)
?
// 微任務代碼(node環境專有)
process.nextTick(() => {
? ?console.log(3)
})
?
// 同步代碼
console.log(4);
?
// 輸出結果: 1432
~~~
# 題3、什么是回調地獄?如何解決?
答:在寫異步代碼時都需要使用回調函數,當回調的層次多時,代碼不容易編寫,容易出錯。
解決方法: 使用 ES6 中新出的 promise 以及 ES8(ES2017)中新出的 async...await 改成同步寫法。
ES6 --> ES2015 ES7 --> ES2016 ES8 --> ES2017
~~~
// 代碼一、傳統多層嵌套回調函數(回調地獄)
setTimeout(() => {
? ?console.log(1)
? ?setTimeout(() => {
? ? ? ?console.log(2)
? ? ? ?setTimeout(() => {
? ? ? ? ? ?console.log(3)
? ? ? ? ? ?setTimeout(() => {
? ? ? ? ? ? ? ?console.log(4)
? ? ? ? ? }, 0)
? ? ? }, 0)
? }, 0)
}, 0)
?
// 代碼二、把所有的異步代碼封裝成 Promise 對象(ES6 的 Promise 寫法)
?
// 無法是哪種方法:首先要先把異步代碼封裝成一個 Promise 對象:
let p1 = function () {
? ?return new Promise(() => {
? ? ? ?// 異步代碼
? ? ? ?setTImeout(() => {
? ? ? ? ? ?console.log(1)
? ? ? }, 0)
? })
}
let p2 = function () {
? ?return new Promise(() => {
? ? ? ?// 異步代碼
? ? ? ?setTImeout(() => {
? ? ? ? ? ?console.log(2)
? ? ? }, 0)
? })
}
let p3 = function () {
? ?return new Promise(() => {
? ? ? ?// 異步代碼
? ? ? ?setTImeout(() => {
? ? ? ? ? ?console.log(3)
? ? ? }, 0)
? })
}
let p4 = function () {
? ?return new Promise(() => {
? ? ? ?// 異步代碼
? ? ? ?setTImeout(() => {
? ? ? ? ? ?console.log(4)
? ? ? }, 0)
? })
}
// 依次調用(避免回調地獄)
p1().then(() => {
? ?return p2()
}).then(() => {
? ?return p3()
}).then(() => {
? ?return p4()
})
?
// 代碼三、async...await (ES8出的)(以同步的方式寫代碼)
(async function () {
? ?await p1()
? ?await p2()
? ?await p3()
? ?await p4()
})()
?
~~~
# 題4、什么是 Promise ? 干什么用的?
答: ES6中新出的。用途:
1. 可以將一段 `異步的代碼` 封裝成一個 Promise 對象
2. 封裝完之后就可以使用 ES8 中的 async ... await ... 來使用同步的方式寫代碼了,不需要再寫回調函數
# 題5、(封裝 Promise簡易版)如何將異步代碼封裝成一個沒有返回值的 Promise 對象?
答: 簡易版本的封裝流程(沒有參數、沒有返回值):
1. 要定義一個函數
2. 在這個函數中創建一個 Promise 對象并返回 return new Promise
3. 把異步的代碼寫在 Promise 中
// 代碼演示:如何將異步代碼封裝成 Promise 對象
~~~
// 異步代碼
setTimeout(function () {
? ?console.log('hello')
}, 0)
?
// 把上面的異步代碼封裝成一個 Promise 對象
function hello() {
? ?return new Promise(function () {
? ? ? ?setTimeout(function () {
? ? ? ? ? ?console.log('hello1')
? ? ? }, 0)
? })
}
~~~
// 代碼演示:使用 Promise 對象
~~~
// 方法一、ES6 中原始語法
hello().then(function(){
? ?// 異步代碼執行完之后的操作
})
?
方法二、ES8 ?async ... awiat 高級語法
async function doHello() {
? ? await hello() ? ?// 執行異步
? ? // 后續操作
}
doHello()
~~~
# 題6、封裝帶返回值的 Promise 對象(完整)?
答:
~~~
// 模擬:一段代碼的代碼,這段代碼返回 100
setTimeout(function(){
? ?// 返回 100 這個數據
? ?return 100
}, 100)
?
// 封裝帶成功、失敗返回值的 Promise 對象
function getData() {
? ?// 參數一、resolve,函數,成功時調用它來設置返回值
? ?// 參數二、reject,函數,失敗時調用它業設置失敗的返回值
? ?return new Promise(function(resolve, reject){
? ? ? ?// 以下異步代碼執行完之后得到 100
? ? ? ?setTimeout(function(){
? ? ? ? ? ?// 模擬成功
? ? ? ? ? ?let ok = true
? ? ? ? ? ?// 判斷是否成功
? ? ? ? ? ?if(ok) {
? ? ? ? ? ? ? ?// 成功時返回 100
? ? ? ? ? ? ? ?resolve(100)
? ? ? ? ? } else {
? ? ? ? ? ? ? ?// 失敗時返回錯誤信息
? ? ? ? ? ? ? ?reject('用戶名不正確!') ? ? ? ? ? ? ? ?
? ? ? ? ? }
? ? ? }, 100)
? })
}
?
// ES6 使用
getData().then(res=>{
? ?console.log(res) ? // 100
}).catch(err=>{
? ?console.log(err) ? // 用戶名不正確!
})
?
// ES8 使用
async function doData() {
? ?let data = await getData().catch(err=>{console.log(err)})
? ?console.log(data) ? // 100
}
doData()
~~~