前端面试之异步
2020-09-15 本文已影响0人
小雪狸
++本文系慕课网学习笔记++
什么是单线程,和异步有什么关系
解答思路:
- 单线程就是只能做一件事,两端 JS 不能同时执行
- 原因就是为了避免 DOM 渲染的冲突
- 异步是一种“无奈”的解决方案,虽然有很多问题
单线程 - 只有一个线程,只能做一件事
// 循环运行期间, JS 执行和 DOM 渲染暂时卡顿
var i, sum = 0
for (i=0; i<1000000000; i++) {
sum += i
}
console.log(sum);
// alert 不处理,JS 执行和 DOM 渲染暂时卡顿
console.log(1)
alert('Hello')
console.log(2)
- 原因 =- 避免 DOM 渲染的冲突
- 浏览器需要渲染 DOM
- JS 可修改 DOM 结构
- JS 执行的时候,浏览器 DOM 渲染会暂停
- 两段 JS 也不能同时执行(都修改一个 DOM 就冲突了)
- webworker 支持多线程,但是不能访问 DOM
- 解决方案 - 异步
console.log(100)
setTimeout(function() {
console.log(200) // 先不管它,先让其他 JS 代码执行,1000ms后执行
}, 1000)
console.log(300)
console.log(400)
console.log(100)
$.ajax({
url: 'xxx',
success: function(result) { // ajax 加载完才执行,先让其他 JS 代码执行
console.log(result)
}
})
console.log(300)
console.log(400)
- 问题
- 没有按照书写方式执行,可读性差
- callback 中不容易模块化
什么是 event-loop 事件轮询
解答思路:
- 事件轮询,JS 异步的解决方案
- 什么是异步队列,何时被放入异步队列
- 轮询的过程
- 文字解释
- 事件轮询, JS 实现异步的具体解决方案
- 同步代码,直接执行
- 异步函数先放在异步队列中
- 待同步函数执行完毕,轮询执行 异步队列的函数
- 实例分析
// setTimeout(function() {
// console.log(100)
// }, 1000)
// console.log(200)
// 主进程
console.log(200)
// 异步队列
function() {
console.log(100)
}
// setTimeout(function() {
// console.log(1)
// }, 100)
// setTimeout(function() {
// console.log(2)
// })
// console.log(3)
// 主进程
console.log(3)
// 异步队列
// 立刻被放入
function() {
console.log(2)
}
// 100ms 后被放入
function() {
console.log(1)
}
是否用过 jquery 的 Deferred
解答思路:
- 可以 jQuery 1.5 对 ajax 的改变举例
- 说明如何简单的封装,使用 Deferred
- 说明 promise 和 Deffered 的区别(reject 和 resolve)
- jQuery 1.5 的变化
- 无法改变 JS 异步和单线程的本质
- 只能从写法上杜绝 callback 的形式
- 它是一种语法糖形式,但是解耦的代码
- 很好的体现了开放封闭原则(扩展开放, 修改封闭)
<!-- jQuery 1.5 之前 -->
var ajax = $.ajax({
url: 'data.json',
success: function() {
console.log('success1')
console.log('success2')
console.log('success3')
},
error: function() {
console.log('error')
}
})
console.log(ajax) // 返回一个 XHR 对象
<!-- jQuery 1.5 之后 -->
var ajax = $.ajax('data.json')
ajax.done(function() {
console.log('success1')
})
.fail(function() {
console.log('error')
})
.done(function() {
console.log('success2')
})
console.log(ajax) // 返回一个 deferred 对象
// 很像 Promise 的写法
var ajax = $.ajax('data.json')
ajax.then(function() {
console.log('success1')
}, function() {
console.log('error1')
})
.then(function() {
console.log('success2')
}, function() {
console.log('error2')
})
- 使用 jQuery Deferred
- 总结, dtd 的 API 可以分成两类,用意不同
- 第一类: dtd.resolve dtd.reject(主动触发)
- 第二类:dtd.then dtd.done dtd.fail(被动监听)
- 这两类应该跟开使用,否则后果很严重
function waitHandle() {
var dtd = $.Deferred() // 创建一个 Deferred 对象
var wait = function(dtd) { // 要求传入一个 Deferred 对象
var task = function() {
console.log('执行完成')
dtd.resolve() // 表示异步任务已经完成
// dtd.reject() // 表示异步任务失败或出错
}
setTimeout(task, 200)
return dtd // 要求返回 Deferred 对象
}
// 这里一定要有返回值
return wait(dtd)
}
var w = waitHandle()
w.then(function() {
console.log('ok1')
}, function() {
console.log('err1')
})
.then(function() {
console.log('ok2')
}, function() {
console.log('err2')
})
- 引入 Promise 概念
function waitHandle() {
var dtd = $.Deferred()
var wait = function(dtd) {
var task = function() {
console.log('执行完成')
dtd.promise() // 注意这里返回的是 Promise,而不是直接返回 Deferred 对象
}
setTimeout(task, 200)
return dtd
}
return wait(dtd)
}
Promise 的基本使用和原理
解答思路:
- 基本语法
- 如何捕获异常
- 多个串联-链式执行的好处
- Promise.all 和 Promise.race
- Promise 标准, 状态变化, then函数
- 基本语法回顾
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
var img = document.createElement('img')
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject()
}
img.src = src
})
return promise
}
var src = "http://www.xxxx.xxx.png"
var result = loadImg(src)
result.then(function(img) {
console.log(img.width)
}, function() {
console.log('failed')
}).then(function(img) {
console.log(img.height)
})
- 异常捕获
// 规定 then 函数只接受一个参数,最后统一用 catch 捕获异常
result.then(function(img) {
console.log(img.width)
}).then(function(img) {
console.log(img.height)
}).catch(function(ex) {
console.log(ex)
)}
- 多个串联
var src1 = 'xxx1.jpg'
var src1 = 'xxx2.jpg'
var result1 = loadImg(src1)
var result2 = loadImg(src2)
// 链式操作
result1.then(function(img) {
console.log('第一个图片加载完成')
return result2
}).then(function(img) {
console.log('第二个图片加载完成')
}).catch(function(ex) {
console.log(ex)
)}
- Promise.all 和 Promise.race
- race 先执行, all后执行
// Promise.all 接收一个 promise 对象的数组
// 待全部完成之后,统一执行 success
Promise.all([result1, result2]).then(datas => {
console.log(datas[0])
console.log(datas[1])
})
// Promise.all 接收一个包含多个 promise 对象的数组
// 只要有一个完成,就执行 success
Promise.race([result1, result2]).then(data => {
// data 即是最先执行完成的 promise 的返回值
console.log(data)
})
- promise 标准
- 关于“标准”的闲谈
- 任何技术推广使用都需要一套标准来支撑
- 如 html js css http 等,无规矩不成方圆
- 任何不符合标准的东西,终将被用户抛弃
- 不要挑战标准,不要自造标准
- 状态变化
- 三种状态:pending fulfilled rejected
- 初始状态是 pending
- pending 变为 fulfilled ,或者 pending 变为 rejected
- 状态变化不可逆
- then
- Promise 实例必须实现 then 这个方法
- then() 必须可以接收两个函数作为参数
- then() 返回的必须是一个 Promise 实例
- 关于“标准”的闲谈
介绍一下 async/await(Promise 的区别、联系)
解答思路:
- 基本语法
- 使用了Promise, 并没有和 Promise 冲突
- 完全是同步的写法, 再也没有回调函数
- 但是改变不了 JS 单线程、异步的本质
- then 知识将 callback 拆分了
- async/await 是最直接的同步写法
- 使用 await,函数必须用 async 标识
- await 后面跟的是一个 Promise 实例
- 需要 babel-polyfill
const load = async function() {
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()
总结一下当前 JS 解决异步的方案
解答思路:
- jQuery Deferred
- Promise
- Async/await
- Generator(原理比较复杂,不是异步的直接替代方式,有更好更简洁的解决方案 async/await, koa也早已“弃暗投明”)