js三座大山
一:函数式编程
js三座大山之函数1
js三座大山之函数2-作用域与动态this
二:面向对象编程
js三座大山之对象,继承,类,原型链
三:异步编程:
js三座大山之异步一单线程,event loope,宏任务&微任务
js三座大山之异步二异步方案
js三座大山之异步三promise本质
js三座大山之异步四-Promise的同步调用消除异步的传染性
js三座大山之异步五基于异步的js性能优化
js三座大山之异步六实现微任务的N种方式
js三座大山之异步七实现宏任务的N种方式
在上一篇中我们知道 为了解决js单线程引入的事件循环机制。以及了解了什么是同步任务 异步任务 异步宏任务 异步微任务这些异步编程的基础概念。下面介绍下异步编程都有哪些实现方案。
回调
例如网络请求传入一个回调函数 执行数据获取后的逻辑处理
ajax(url, (res1) => {
console.log(res1);
// 处理逻辑
});
回调函数的主要问题是可能会导致回调地狱(Callback Hell),即回调函数嵌套回调函数,使得代码难以阅读和维护。
ajax(url, (res1) => {
console.log(res1);
// 处理逻辑
ajax(url1, (res2) => {
console.log(res2);
setTimeout(() => {
// 处理逻辑
ajax(url2, (res3) => {
// 处理逻辑
console.log(res3);
});
}, 2000);
});
});
基于事件的发布订阅
先注册一系列回调事件 当异步任务完成 触发回调函数 执行操作。
Event.subscribe('key', fn1)
Event.subscribe('key', fn2)
ajax(url, (res1) => {
Event.publish('key', res1)
})
class Event{
store=new Map();
subscribe(name, callback){
if(!store[name]){
const s = new Set();
s.add(callback)
this.store.set(name, s)
return;
}
store[name].add(callback)
};
unSubscribe(name, callback){
if(!store[name]) return;
store[name].delete(callback)
}
publish(name, data){
if(!store[name]) return;
store[name].forEach(fn=>fn(data))
}
}
promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。Promise 对象就是为了解决回调地狱而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。
// 第一个例子改为promise实现
ajax(url)
.then((res1) => {
console.log(res1);
// 处理逻辑
return ajax(url1);
})
.then((res2) => {
console.log(res2);
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 2000);
});
})
.then(() => {
return ajax(url2);
})
.then((res3) => {
// 处理逻辑
console.log(res3);
});
可以看到,Promise 的写法只是回调函数的改进,使用then
方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
生成器Generators/yield:
Generator 最大的特点就是可以交出函数的执行权,可暂停可恢复
有以下三个应用场景:
1. 生成迭代器
const obj = {};
[...obj] // 报错 对象不可迭代
const obj = {
[Symbol.iterator]:function* (){
let i = 0;
while(i< 10){
yield i;
i++;
}
}
};
[...obj] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2. 惰性求值
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能.
function* gen() {
yield 123 + 456;
}
const g = gen() // 并不会计算求值
g.next() // 求值获得结果
上面代码中,yield
后面的表达式123 + 456
,不会立即求值,只会在next
方法将指针移到这一句时,才会求值。
3. 异步的解决方案
JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。
这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。
Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield
命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next
命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
function* load(){
console.log('loading');
const data = yield setTimeout(()=>{
it.next(123)
}, 2000)
console.log('get result', data)
}
var it = load();
it.next();
function action(){
console.log('other action ')
}
action();
结果
// loading
// other action
// get result 123
分析步骤:
- 解析脚本 load函数入栈 执行返回一个迭代器 调用第一次next后 打印loading 运行到yield语句 调用异步定时器 load函数被暂停 出栈并推入到冻结区域 执行栈为空
- 异步定时器任务被执行
- action函数入栈 action执行打印other action 执行结束 action出栈并销毁
- 异步定时器执行结束 异步任务进入任务队列
- 执行栈 运行任务队列的回调任务 callback入栈 callback调用了load
- load被恢复到执行栈 load继续执行 打印get result 123 结束后出栈并销毁
- callback执行结束 出栈销毁
await/async
await/async是基于promise的,是 Generator 函数的语法糖,将 Generator 函数做了进一步的封装 内部实现了自动调用next 直至拿到最终返回结果。相比于Generator, await/async有更好的语义,更贴近同步的方法实现了异步调用。是目前主导的异步实现方案
async function load(){
console.log('loading');
const data = await new Promise((resolve)=>{
setTimeout(()=>{
resolve(123)
}, 2000);
})
console.log('get result', data)
}
load();
function action(){
console.log('other action ')
}
action();
结果
// loading
// other action
// get result 123