A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© charcoal 中级黑马   /  2021-6-20 10:13  /  2095 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

前言
本文主要介绍的是js中迭代器的实现原理,以及源码分析;async是如何以同步的方式编程的原理分析
前置知识
1. 线程、进程
进程是操作系统分配资源的最小单位, 线程事操作系统执行的最小单位。
我的理解就是进程是程序拥有CPU内相关资源的一段时间的描述,强调的是一个时间段内的环境。而线程则是在这个环境中做的某件事情的描述,强调的是做的过程。
2. 用户态与内核态先说说内核,就是直接管理计算机硬件的软件其实就是操作系统(内核)对cpu硬件资源管理上的权限分离。线程、进程的切换是用内核完成的,即内核态。而用户态就是我们程序运行的状态。
协程接下来就讲讲我们的主角:协程。先从比较开始,对比线程:
  • 线程的切换在内核态,开销大;协程的切换在用户态,开销小
  • 进程中的多线程可以利用cpu的多核,可以并行执行;线程中的多协程执行是串行的,分时复用的方式利用线程中的资源,一个协程挂起将阻塞整个线程
  • 多线程在对于共享变量需要加锁机制;协程不需要



迭代器(iterator)es5中的迭代实现
我们来看看es5中是怎么实现的迭代功能的for of,看看babel转换的结果:
[JavaScript] 纯文本查看 复制代码
let arr = [1,2,3][/size][/font][/color][/align]for(let it of arr){
  console.log(it)
}


babel转换结果如下,仅展示核心代码
[JavaScript] 纯文本查看 复制代码
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的实现原理,我们来实现一个可迭代的对象:


[JavaScript] 纯文本查看 复制代码
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,看一段代码

[JavaScript] 纯文本查看 复制代码
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]函数,我们拿上一步的代码改造

[JavaScript] 纯文本查看 复制代码
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写法仍然避免不了回调嵌套。比如下面:

[JavaScript] 纯文本查看 复制代码
function requestSystemData(){
    fetch(url).then((res)=>{
        //.....
       fetch(url2).then(res=>{
           //...
           fetch(url3).then(res=>{
               // ...
           })
       })
    })
}


于是es7推出了async/await,改写以上代码

[JavaScript] 纯文本查看 复制代码
async function requestSystemData(){
    const [res] = await fetch(url)
    //...
    const [res2] = await fetch(url2)
    //...
    const [res3] = await fetch(url3)
    //...
}

生成器(generator)
async 函数是什么?一句话,它就是 Generator 函数的语法糖。最关键的生成器generator函数,下面我们就重点考察
先看看一个简单的生成器函数
[JavaScript] 纯文本查看 复制代码
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中是如何实现的:

[JavaScript] 纯文本查看 复制代码
"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();
继续我们看看es5中是如何实现的:

[JavaScript] 纯文本查看 复制代码
"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构造一个生成器可以迭代执行别分割的各个代码。
那么接下来就简单的模拟实现一下,忽略旁枝末节:

[JavaScript] 纯文本查看 复制代码
// 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的语法糖。

[JavaScript] 纯文本查看 复制代码
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语法糖代码:
[JavaScript] 纯文本查看 复制代码
// 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实现






0 个回复

您需要登录后才可以回帖 登录 | 加入黑马