[ES6] Generator 函数

[ES6] Generator 函数

  • Generator 函数与普通函数的区别
  • 执行机制
  • Generator 函数返回的遍历器对象的方法
    • 循环遍历器 Iterator 对象的方法
    • next 方法
    • return 方法
    • throw 方法
      • next 方法的影响
      • yield 的影响
  • yield\* 表达式
  • 使用场景

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案

常用于异步编程的还包括 promise 对象的应用,与 async/await 指令

Generator 函数与普通函数的区别

  1. 在 function 后面,函数名之前有个 * ;
  2. 函数内部有 yield 表达式。

其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。

function* func() {
  console.log("one");
  console.log("more");
  yield "1"; //指针指到yield的位置,程序就会被挂起
  console.log("two");
  yield "2";
  console.log("three");
  return "3";
}

执行机制

调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。

let f = func();
const s = f.next(); // one
console.log(s); // {value: "1", done: false}
console.log("-----------");
f.next(); // two
// {value: "2", done: false}

f.next(); // three
// {value: "3", done: true}

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

调用 next 方法时,从 Generator 函数的头部开始执行,执行到 yield 就停下来,并将 yield 后边表达式的值 ‘1’,作为调用 next 方法返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false,若是函数被执行完成则设置 done 属性值是 true

每次调用 next 方法时,同上步,在下一个 yield 处挂起

直到 next 方法返回对象的 done 属性首次为 true,表示函数执行完成,此时根据函数是否有返回值 return,如果有则 value 为 return 值,若没有则 value 为 undefined

如果函数已经执行完成了,仍旧调用 next 方法,则返回 value 属性值是 undefined ,done 属性值是 true

Generator 函数返回的遍历器对象的方法

循环遍历器 Iterator 对象的方法

  • next()
  • for of

next 方法

一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步 yield 的返回值。

function* sendParameter() {
  console.log("start");
  var x = yield "2";
  console.log("one:" + x);
  var y = yield "3";
  console.log("two:" + y);
  console.log("total:" + (x + y));
}
//next 不传参

var send1 = sendParameter();
send1.next(); // start
// {value: "2", done: false}
send1.next(); // one:undefined
// {value: "3", done: false}
send1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}

//next 传参
var send2 = sendParameter();
send2.next(10); // start
// {value: "2", done: false}
send2.next(20); // one:20
// {value: "3", done: false}
send2.next(30);
// two:30
// total:50
// {value: undefined, done: true}
  • next 方法返回对象{value: , done: }
    • value 属性的值=yield 关键字后的值或者 return 的值,函数执行完成后再次调用 value= undefined
    • done:函数执行完成返回 true,否则 false
  • next 方法传参,影响的是通过 yield 表达式赋值的变量的值
    • let x=yield * n,参数会作为上一步通过 yield 赋值的变量 x 的值
      • generator.next(),yield=undefined
      • generator.next(value),yield=value

除了使用 next ,还可以使用 for... of 循环遍历 Generator 函数生产的 Iterator 对象

return 方法

return 方法返回给定值,并直接结束遍历 Generator 函数。

return 方法提供参数时,返回该参数;不提供参数时,返回 undefined(与 Generator 函数是否有 return 返回值无关)

function* foo() {
  yield 1;
  yield 2;
  yield 3;
}
var f = foo();
f.next(); // {value: 1, done: false}
f.return("foo"); // {value: "foo", done: true}
f.next(); // {value: undefined, done: true}

function* bamboo() {
  yield 1;
  yield 2;
  return "3";
}
bamboo.return(); // {value: undefined, done: true}

throw 方法

throw 方法可以在 Generator 函数体外面抛出异常,在函数体内部捕获,而捕获 throw 异常与指针所在 try-catch 息息相关

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log("catch inner", e);
  }
};

var i = g();
i.next();

try {
  i.throw("a"); // catch inner a
  i.throw("b");
  i.throw("c");
} catch (e) {
  console.log("catch outside", e); // catch outside b
}

遍历器对象调用 throw()方法:

  1. throw(“a”)时被 Generator 函数内部捕获;
  2. throw(“b”)时因为函数体内的 catch 函数已经执行过了,指针已经不在指向函数内部,不会再执行内部 catch,所以这个错误就抛出 Generator 函数体,被函数体外的 catch 捕获;
  3. throw(“c”)不能在被捕获,因为执行到 i.throw(“b”)后面的代码包括 throw(“c”)都不会执行了,直接跳转到 catch 分支了

next 方法的影响

当注释掉 next 之后发现,只有外部的 catch 可以捕获异常

通过以上示例发现,当注释掉 next 方法的时候 throw 异常并没有执行 Generator 函数内部的 try-catch!!!

经过分析认为,当执行 next()方法后,指针指向了 Generator 函数体内

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log("catch inner", e);
  }
};
/* ------- 测试-throw("c")可调用位置 --------*/
var i = g();
i.next();
try {
  try {
    i.throw("a"); // catch inner a
    i.throw("b"); //catch outside b
  } catch (e) {
    console.log("catch outside", e);
    i.throw("c"); //catch outside second c
  }
} catch (e) {
  console.log("catch outside second", e);
}
/* ------- 测试 -2. 不调用next()方法 --------*/
var i = g();
// i.next();
try {
  i.throw("a");
  i.throw("b");
  i.throw("c");
} catch (e) {
  console.log("catch outside", e); // catch outside a
}

遍历器对象调用 next()方法区别:

  1. 调用 next()方法
  • throw(“a”) 异常,触发的是内部的 try-catch
  • 然后指针不在指向 Generator 方法内部,throw(“b”)抛出异常就会调用外部的 try-catch
  • throw(“c”)根本没有调用,因为 throw(“b”)跳过该方法

    throw(“c”)不想被跳过,不能再放置在 try 代码块,因为 try 代码块中触发异常位置之后的代码都不会在执行,将它放在不会跳过的位置,则会被调用

  1. 不调用 next()方法
  • throw(“a”)时,因为指针并没有指向 Generator 方法内部,因此调用外部的 try-catch
  • throw(“a”)时调用外部 catch 分支,所以跳过 throw(“b”)/ throw(“c”)

yield 的影响

/* ------- 测试 -3. 不使用yield --------*/
var g = function* () {
  try {
    // yield;
    const x = 1;
    // yield;
  } catch (e) {
    console.log("catch inner", e);
  }
};

var i = g();
i.next();
try {
  i.throw("a"); //catch outside a
  i.throw("b");
  i.throw("c");
} catch (e) {
  console.log("catch outside", e);
}

/* - 测试 -4. 同一 try 分支中多次使用 yield ----*/
var g = function* () {
  try {
    yield;
    const x = 1;
    yield;
  } catch (e) {
    console.log("catch inner", e);
  }
};

var i = g();
i.next();
try {
  i.throw("a"); //catch inner a
  i.throw("b"); //catch outside b
  i.throw("c");
} catch (e) {
  console.log("catch outside", e);
}

/* - 测试 -5. 不同 try 分支多次使用 yield ----*/
var g = function* () {
  try {
    try {
      yield;
      const x = 1;
    } catch (e) {
      console.log("catch inner", e);
    }
    yield;
    const y = 1;
  } catch (e) {
    console.log("catch inner second", e);
  }
};
//等价于
var g = function* () {
  try {
    yield;
    const x = 1;
  } catch (e) {
    console.log("catch inner", e);
  }
  try {
    yield;
    const y = 1;
  } catch (e) {
    console.log("catch inner second", e);
  }
};

var i = g();
i.next();
try {
  i.throw("a"); //catch inner a
  i.throw("b"); //catch inner second b
  i.throw("c"); //catch outside c
} catch (e) {
  console.log("catch outside", e);
}

遍历器对象使用 yield 的区别:

  1. 不使用 yield
  • 调用 next 方法,因为方法中不存在 yield,因此指针不在内部
  • throw(“a”) ,触发的是外部的 try-catch
  • 上一步导致跳过同一 try 代码块中的 throw(“b”)/ throw(“c”)
  1. 一个 try 分支中一次或多次使用 yield
  • yield 只要在同一个 try 代码块中,throw 异常第一次都是调用内部的 try-catch,指针指向外部
  • throw(“b”) 异常,调用外部的 try-catch
  1. 多个 try 分支中多次使用 yield
  • throw(“a”)后,同一个 try 代码块中 yield 都会被跳过,指针指向 Generator 函数下一个 try-catch 中的代码
  • throw(“b”) 异常,调用 Generator 函数的 下一个 try-catch
  • throw(“c”) 异常,调用外部的 try-catch

遍历器对象调用 throw 方法,就会调用指针所在的 try-catch ,如果 Generator 函数内部不存在 try-catch,就开始调用遍历器对象调用 throw 方法所在的函数体外 try-catch

指针与 Generator 函数返回遍历器对象的 next 方法和 Generator 函数体内 yield 相关

指针指向遍历器对象,如果 Generator 函数中存在 yield,调用 next 方法才会指向函数内部,否则仍然是指向遍历器对象

调用遍历器对象的 throw 方法,指针会按顺序指向Generator函数体内 的 yield,所以触发的是 yield 所在的 try-catch,而没有 yield 的 try-catch 自然也不会触发!!!!而当指针指向遍历器对象时,触发的就是调用 throw 方法时所在的 try-catch!!!!

yield* 表达式

yield* 表达式表示 yield 返回一个遍历器对象,用于在 Generator 函数内部,调用另一个 Generator 函数。

function* callee() {
  console.log("callee: " + (yield));
}
function* caller() {
  while (true) {
    yield* callee();
  }
}
const callerObj = caller();
callerObj.next();
//yield无值因此不执行console.log("callee: " + (yield))
//并且callee()函数没有返回值,所以value=undefined
// {value: undefined, done: false}
callerObj.next("a"); // callee: a
// {value: undefined, done: false}
callerObj.next("b"); // callee: b
// {value: undefined, done: false}

// 等同于
// function* caller() {
//   while (true) {
// //TypeError: callee is not iterable
//     for (var value of callee) {
//       yield value;
//     }
//   }
// }

使用场景

实现 Iterator,为不具备 Iterator 接口的对象提供遍历方法。

function* objectEntries(obj) {
  const propKeys = Reflect.ownKeys(obj);
  for (const propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

const jane = { first: "Jane", last: "Doe" };
for (const [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

Reflect.ownKeys() 返回对象所有的属性数组,不管属性是否可枚举,包括 Symbol。

jane 对象原生是不具备 Iterator 接口无法通过 for… of 遍历。用 Generator 函数加上了 Iterator 接口,所以就可以遍历 jane 对象了。

对于遍历对象的属性非常方便

你可能感兴趣的:(JS,es6,javascript,前端)