[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()
~~~