资讯专栏INFORMATION COLUMN

js 中的Generator 函数

Ku_Andrew / 3423人阅读

摘要:函数返回的遍历器对象,还有一个方法,可以返回给定的值,并且终结遍历函数。这被称为表达式个人理解主要用作遍历具有遍历器接口的对象或函数。完整形式函数的函数总是返回一个遍历器,规定这个遍历器是函数的实例,也继承了函数的对象上的方法。

语法上

首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上

Generator 函数是一个普通函数,但是有两个特征。

 一是,function关键字与函数名之间有一个星号;
 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
调用上

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。我们必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行

function* helloWorldGenerator() {
  yield "hello";
  yield "world";
  return "ending";
}

var hw = helloWorldGenerator();
hw.next()
// { value: "hello", done: false }

hw.next()
// { value: "world", done: false }

hw.next()
// { value: "ending", done: true }

hw.next()
// { value: undefined, done: true }

调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

yield表达式

yield表达式与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。

语法注意点:
1.yield表达式只能用在 Generator 函数里面
2.yield表达式如果用在另一个表达式之中,必须放在圆括号里面
3.yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
例如:

function* demo() {
  foo(yield "a", yield "b"); // OK
  let input = yield; // OK
}
next 方法的参数

yield表达式本身没有返回值(就是说let a=yield ;会返回undefined),或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值 (注意,是整个表达式的返回值而不只是yield 后方的值,例如 let a=yield.......... 参数会是a 的值并且会覆盖表达式之前的值)

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    console.log(reset);
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() 


由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log("内部捕获到错误", e);
  }
};

var i = g();
i.next();
//外部抛出错误:
i.throw("a");

注意点:
1.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
2.throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
3.Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。
4.一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。

Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

yield* 表达式

语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式(个人理解yield 主要用作遍历具有遍历器(Iterator)接口的对象或函数)。
如:
用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* foo() {
  yield "a";
  yield "b";
}
// 直接调用没有效果
function* bar() {
  yield "x";
  foo();
  yield "y";
}
// 使用yield* foo();
function* bar() {
  yield "x";
  yield* foo();
  yield "y";
}

// 等同于
function* bar() {
  yield "x";
  yield "a";
  yield "b";
  yield "y";
}

// 等同于
function* bar() {
  yield "x";
  for (let v of foo()) {
    yield v;
  }
  yield "y";
}
for (let v of bar()){
  console.log(v);
}

如果被代理的 Generator 函数有return语句,那么就可以向代理它的 Generator 函数返回数据。

function* foo() {
  yield 2;
  yield 3;
  return "foo";
}

function* bar() {
  yield 1;
  var v = yield* foo();
  console.log("v: " + v);
  yield 4;
}

var it = bar();

it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
作为对象属性的 Generator 函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式。

let obj = {
  * myGeneratorMethod() {
    ···
  }
};

完整形式

let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};
Generator 函数的this

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

function* g() {}

g.prototype.hello = function () {
  return "hi!";
};

let obj = g();

obj instanceof g // true
obj.hello() // "hi!"

上面代码表明,Generator 函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。

function* g() {
  this.a = 11;
}

let obj = g();
obj.next();
obj.a // undefined

Generator 函数也不能直接跟new命令一起用

function* F() {
  yield this.x = 2;
  yield this.y = 3;
}

new F()

变通方法

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

function F() {
  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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103277.html

相关文章

  • 《Node.js设计模式》基于ES2015+的回调控制流

    摘要:以下展示它是如何工作的函数使用构造函数创建一个新的对象,并立即将其返回给调用者。在传递给构造函数的函数中,我们确保传递给,这是一个特殊的回调函数。 本系列文章为《Node.js Design Patterns Second Edition》的原文翻译和读书笔记,在GitHub连载更新,同步翻译版链接。 欢迎关注我的专栏,之后的博文将在专栏同步: Encounter的掘金专栏 知乎专栏...

    LiuRhoRamen 评论0 收藏0
  • js 处理异步操作的几种方式

    摘要:如果我们只有一个异步操作,用回调函数来处理是完全没有任何问题的。事件监听使用事件监听的方式番禺广州上述代码需要实现一个事件监听器。只处理对象广州番禺函数将函数的自动执行器,改在语言层面提供,不暴露给用户。 概论 由于 JavaScript 是一门单线程执行的语言,所以在我们处理耗时较长的任务时,异步编程就显得尤为重要。js 处理异步操作最传统的方式是回调函数,基本上所有的异步操作都可以...

    Meils 评论0 收藏0
  • 那些年在异步代码上所做的努力

    摘要:当迭代器运行后,会返回第一次运行到或者时的返回值以格式进行返回。而现在了后面的方法必须是总结总结一下异步代码的发展过程回调函数最基本的解决方法,将异步结束函数以参数的方式传递到异步函数中,也就是使用回调函数的方式来实现异步逻辑。 介绍 写过JS代码的同学应该都知道,JS是单线程的,当出现异步逻辑时,就需要使用一些技巧来实现。最常见的方法就是使用回调方法。 回调方法 比如我们要实现一个功...

    rubyshen 评论0 收藏0
  • JS基础】从JavaScript中的for...of说起(上) - iterator 和 gene

    摘要:当这个迭代器的方法被首次后续调用时,其内的语句会执行到第一个后续出现的位置为止,后紧跟迭代器要返回的值。在这个回调函数里,我们使用第一个请求返回的,再次发起一个请求。 写在前面 本文首发于公众号:符合预期的CoyPan 后续文章:【JS基础】从JavaScript中的for...of说起(下) - async和await 先来看一段很常见的代码: const arr = [1, 2, ...

    wslongchen 评论0 收藏0
  • ES6&ES7中的异步之Generator函数与异步编程

    摘要:传统的异步方法回调函数事件监听发布订阅之前写过一篇关于的文章,里边写过关于异步的一些概念。内部函数就是的回调函数,函数首先把函数的指针指向函数的下一步方法,如果没有,就把函数传给函数属性,否则直接退出。 Generator函数与异步编程 因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。 传统的异步方法 回调函数 事件监听 发布/订阅 Promise 之前写过一篇关...

    venmos 评论0 收藏0

发表评论

0条评论

Ku_Andrew

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<