前言
本文主要介绍的是js中迭代器的实现原理,以及源码分析;async
是如何以同步的方式编程的原理分析
前置知识
1. 线程、进程
进程是操作系统分配资源的最小单位,
线程事操作系统执行的最小单位。
我的理解就是进程是程序拥有CPU内相关资源的一段时间的描述,强调的是一个时间段内的环境。而线程则是在这个环境中做的某件事情的描述,强调的是做的过程。
2. 用户态与内核态
先说说内核,就是直接管理计算机硬件的软件
其实就是操作系统(内核)对cpu硬件资源管理上的权限分离。线程、进程的切换是用内核完成的,即内核态。而用户态就是我们程序运行的状态。
协程
接下来就讲讲我们的主角:协程。
先从比较开始,对比线程:
- 线程的切换在内核态,开销大;协程的切换在用户态,开销小
- 进程中的多线程可以利用cpu的多核,可以并行执行;线程中的多协程执行是串行的,分时复用的方式利用线程中的资源,一个协程挂起将阻塞整个线程
- 多线程在对于共享变量需要加锁机制;协程不需要
迭代器(iterator)
es5中的迭代实现
我们来看看es5中是怎么实现的迭代功能的for of
,看看babel转换的结果:
let arr = [1,2,3]
for(let it of arr){
console.log(it)
}
复制代码
babel转换结果如下,仅展示核心代码:
查看全部
var arr = [1, 2, 3];
var _iteratorNormalCompletion = true;
// 核心代码
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var it = _step.value;
console.log(it);
}
复制代码
其中最关键的就是实现了数组内部的一个函数:[Symbol.iterator]
-
先是调用
[Symbol.iterator]
生成一个_iterator
对象,并马上执行启动迭代器的方法_step
。 -
而执行
_step
就是执行iterator
对象的next()
方法,并将结果赋值给_step
。 -
以
_step
对象中的done
作为是否跳出循环的条件,true就跳出循环。对应代码:!(_iteratorNormalCompletion = (_step = _iterator.next()).done);
-
执行for of 中的代码,将
_step
对象的value作为本次循环值返回 -
重置循环条件的值,继续循环,对应代码:
_iteratorNormalCompletion = true
了解了for of
的实现原理,我们来实现一个可迭代的对象:
function iteratorObj( obj={} ){
return {
...obj,
// 1. 构造`Symbol.iterator`函数
[Symbol.iterator](){
const keys = Object.keys(obj)
let index = 0
// 2. 返回一个包含next函数的对象
return {
next: function(){
// 3. 执行next,返回对象
return {
value: obj[keys[index]] || undefined,
done: !obj[keys[index++]] // 4. 执行之后指针后移,继续迭代
}
},
}
}
}
}
// Test case
var itObj = iteratorObj({ a:'1',b:'2' })
for(let it of itObj){
console.log(it)
}
// 1
// 2
复制代码
es6的迭代器
将迭代器之前,我们来看看生成器generator
,看一段代码
function* fn(){
yield 1;
yield 2;
yield 3;
}
var gen = fn();
gen.next(); // {value: 1, done: false}
gen.next(); // {value: 2, done: false}
gen.next(); // {value: 3, done: false}
gen.next(); // {value: undefined, done: true}
复制代码
es6引入了生成器就是为了迭代而生的,下面我们用生成器来实现迭代功能,其实只要重写[Symbol.iterator]
函数,我们拿上一步的代码改造
function iteratorObj( obj={} ){
return {
...obj,
// 1. 构造`Symbol.iterator`生成器函数
*[Symbol.iterator](){
const keys = Object.keys(obj)
// 由链式变成了线性 遍历
for (let i of keys){
yield obj[i]
}
}
}
}
复制代码
异步编程
async/await 初探
es6新增的promise是现代异步编程的解决方案。但promise写法仍然避免不了回调嵌套。比如下面:
function requestSystemData(){
fetch(url).then((res)=>{
//.....
fetch(url2).then(res=>{
//...
fetch(url3).then(res=>{
// ...
})
})
})
}
复制代码
于是es7推出了async/await
,改写以上代码:
async function requestSystemData(){
const [res] = await fetch(url)
//...
const [res2] = await fetch(url2)
//...
const [res3] = await fetch(url3)
//...
}
复制代码
生成器(generator)
async
函数是什么?一句话,它就是 Generator
函数的语法糖。最关键的生成器generator
函数,下面我们就重点考察
先看看一个简单的生成器函数:
function * fn(){
console.log(1)
yield 1
console.log(2)
yield 2
console.log(3)
yield 3
}
var gen = fn();
gen.next() //{ value: 1, done: false }
gen.next() //{ value: 2, doen: false }
gen.next() //{ value: 3, doen: false }
gen.next() //{ value: undefined, doen: true }
复制代码
继续我们看看es5中是如何实现的:
"use strict";
var _marked = [fn].map(regeneratorRuntime.mark);
function fn() {
return regeneratorRuntime.wrap(function fn$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log(1)
_context.next = 2;
return 1;
case 2:
console.log(2)
_context.next = 4;
return 2;
case 4:
console.log(3)
_context.next = 6;
return 3;
case 6:
case "end":
return _context.stop();
}
}
}, _marked[0], this);
}
var gen = fn();
gen.next();
gen.next();
gen.next();
复制代码
我们简单分析一下,核心的就两点:
- 将生成器中的代码以
yield
为界分个代码块,并将yield
后面的作为返回值 - 利用
regeneratorRuntime.wrap
构造一个生成器可以迭代执行别分割的各个代码。
那么接下来就简单的模拟实现一下,忽略旁枝末节:
// 1. 拿到babel转换分割后的代码
function fn$ (_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 1;
case 2:
_context.next = 4;
return 2;
case 4:
_context.next = 6;
return 3;
case 6:
case "end":
return _context.stop();
}
}
}
// 2.实现上下文的包装函数
function regeneratorRuntime(){
const self = this
// 上下文对象
this.context = {
prev:0,
next:0,
done: false,
stop:function(){
self.context.done = true
},
}
this.wrap = function(fn){
return {
next: function(){
return {
value: fn(self.context),
done: self.context.done //如果是context.stop(),则返回
}
}
}
}
}
// Test case
var runtime = new regeneratorRuntime()
var gen = runtime.wrap(fn$)
console.log(gen.next()){ value: 1, done: false }
console.log(gen.next()) //{ value: 2, done: false }
console.log(gen.next()) //{ value: 3, done: false }
console.log(gen.next()) //{ value: undefined, done: true }
复制代码
小结:
generator
能够看似中断、恢复执行的关键,就是执行上下文的保存。说的具体就是生成器代码块执行位置(prev
,next
)的保存。- 分割的每块代码中,都将操作位置,指向下一块代码
_context.next = X
- 每次执行
next
,其实都是执行生成器中的同一份代码,只是根据保存在上下文中的位置信息(prev
,next
),执行不同的代码块
到了es6,v8底层支持了生成器的写法,所以也不再需要js层面的语法糖(babel转译)。
async/await实现
es2017推出了async
,其实就是generator
+ promise
的语法糖。
async function fn(){
console.log(1)
await new Promise(res=> setTimeout(function(){res(1)},500))
console.log(2)
await new Promise(res=> setTimeout(function(){res(2)},500))
console.log(3)
}
console.log(0)
fn()
console.log(4)
// 0
// 1
// 4
// 2
// 3
复制代码
下面我们用生成器、promise
语法来实现async
语法糖代码:
// 1. 将async转译成*函数
function fn*(){
console.log(1)
yiel new Promise(res=> setTimeout(function(){res(1)},500))
console.log(2)
yield new Promise(res=> setTimeout(function(){res(2)},500))
console.log(3)
}
//2. 语法糖转化代码
function generatorToAsync(fn){
return function(){
//生成生成器
var gen = fn();
//执行async返回的是一个Promise对象
return new Promise((resolve,reject)=>{
var step = function(){
var info = gen.next()
var { done, value } = info
if(done) return resolve(value)
//将yield后面的值,包装为Promise对象
return Promise.resolve(value).then(value=>{
step()
})
}
//启动生成器
step()
})
}
}
复制代码
至此,async函数实现了异步方法的同步编程,并且能够顺序执行函数内的所有异步方法。
总结
- 协程的作用在于,能够在开发层面的函数的中断、恢复。核心在于函数上下文的保存
- 生成器的实现原理在于:函数上下文的保存。在es5中是语法糖,js层面的构造了上下文对象保存函数执行的位置;而在es6中是v8底层的支持,v8层面的保存了函数上下文。
- 迭代器的实现依赖于生成器,
for of
的迭代功能在于实现[Symbol.iteratoe]
函数 async
是generator
加Promise
实现