es6 generator 个人理解 细粒度操作(1)

概述

状态机,封装多个状态,轮着输出,可以暂停,可以终止。可以对函数行为精确操作。

  • 可以暂停的函数,

  • 分段执行

  • 分段输出

  • 可以遍历

  • 有记忆功能,下次遍历可以从上次继续下去

  • yield 只适合 genenrator函数,即带*的函数

  • yield作为输出,在函数内不能作为数据使用。比如1+yield 2

  • yield表达式本身没有返回值,或者说总是返回undefined

  • for…of循环不会执行return语句

表达式
var gfn = function * () {
	yield 1 + 1; // 不会立即求值,只有next被调用才执行
	yield 2;
	return 3;
}

yield 定义不同的状态,是一种暂停标志,暂时输出

next 是一个函数继续执行的命令

g函数和普通函数对比

普通函数,立即执行,只执行一个return

generator函数,不会立即执行,执行多个yield

返回值

返回内部状态指针,可遍历对象

每次调用next()返回yield的状态,直到return或者函数结束

具体是

  • value: 内部状态值,即yield 后的表达式结果,

  • done: true/false 是否遍历结束

外部操作状态
  • next()带一个参数,修改上个表达式的值,调整函数行为

    第一次使用next,没有上一个表达式,所以参数无效。

    // 无限循环执行
    function* g() {
      for(var i = 0; true; i++) {
        var reset = yield i;
        if(reset) { i = -1; }
      }
    }
    
    var gfn = g();
    
    gfn.next() // { value: 0, done: false }
    gfn.next() // { value: 1, done: false }
    gfn.next(true) // { value: 0, done: false }
    
    // 第一次可以调用值的写法
    // 包一层函数
    function wrapper(generatorFunction) {
      return function (...args) {
        let generatorObject = generatorFunction(...args);
        generatorObject.next();
        return generatorObject;
      };
    }
    
    const wrapped = wrapper(function* () {
      console.log(`First input: ${yield}`);
      return 'DONE';
    });
    
    wrapped().next('hello!')
    // First input: hello!
    
  • generator.throw() 扔出错误

    只被内部接收

    gfn.throw(new Error('我错了'))
    
  • generator.return() 终止循环

    gfn.return(true) // 返回true,和函数return一样,不过是可以外面处理
    
g函数里面调用g函数,使用yield*表达式,

yield* gfn 效果等同于for...of gfn

有return的话需要赋值

let res = yield* gfn

一般使用

function* a () {
	console.log('1')	
    console.log('2')
}
function *b() {
	console.log('a')
	yield* a()
    console.log('b')
}


可遍历对象即可使用

function* g(){
	yield* [1,2,3]
    yield* 'hello'
}
let gfn = g()
gfn.next() // 1
...
gfn.next() // 'h'
...
对象里面的g函数
let obj = {
	* gfn() {
		yield 1
	}
}
g函数改造成普通函数

g函数里面没有this,不能使用new

function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() { // 2. 创建构造函数,使其可以new
  // 1. 创建this, 使其可以使用this.a
  return gen.call(gen.prototype);
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3
状态机
// 一般的状态机,运行一次,改变一次执行状态
var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick!');
  else
    console.log('Tock!');
  ticking = !ticking;
}
//g 状态机,少了外部变量的定义,状态不会被非法篡改,更安全
var clock = function* () {
  while (true) { // 一直next()无限循环
    console.log('Tick!');
    yield;
    console.log('Tock!');
    yield;
  }
};
协程

多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行交换执行权的线程(或函数),就称为协程。

传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数

在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行

协程适合用于多任务运行的环境

有自己的执行上下文、可以分享全局变量

普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。

Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行

如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用yield表达式交换控制权

Generator 函数它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。

应用
  • 暂缓执行的函数
let zh = function *() {
	return 1 + 1
}
let zh1 = zh()
zh1.next()

面试可能会问你这个问题

  • 实现斐波那契数列?

抄给他,问他在项目中哪里用到过,反正我是没用到过。见过面试不问项目,问网上试题全抄全问,两个人问了我两个半钟,后面二面后端技术总监问管理问题半个钟,最后过了没去那家,因为人事压工资。说实在,喜欢那个不看电脑随意问问题的面试官,技术闲聊都会香。

function* fibonacci() {
  let [prev, curr] = [0, 1]; // 初始化数据
  for (;;) { // 死循环
    yield curr; // 输出1,1,2,3,5,8,13... 两个一直加
    [prev, curr] = [curr, prev + curr]; // [1, 0+1=1], 换位递进
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}
  • 嵌套数组的平铺, 等同于 flat()
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
  • 类数组结构
function* doStuff() {
  yield fs.readFile.bind(null, 'hello.txt');
  yield fs.readFile.bind(null, 'world.txt');
  yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
  // task是一个函数,可以像回调函数那样使用它
}
  • 部署iterator接口,让对象可以循环遍历,而不是使用for...in写法
function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
  • 流程控制
let steps = [step1Func, step2Func, step3Func];

function* iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yield step();
  }
}

上面代码中,数组steps封装了一个任务的多个步骤,Generator 函数iterateSteps则是依次为这些步骤加上yield命令。

将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。

let jobs = [job1, job2, job3];

function* iterateJobs(jobs){
  for (var i=0; i< jobs.length; i++){
    var job = jobs[i];
    yield* iterateSteps(job.steps);
  }
}
for(let step of iterateJobs(jobs)) {
	console.log(step.id)
    ...
}
  • 异步请求,如果不是很需要细粒度操作,一般还是使用async-await
function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url) {
  makeAjaxCall(url, function(response){
    it.next(response);
  });
}

var it = main();
it.next();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值