<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/* 六、
Generator 函数的语法之 next()、throw()、return()
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。
它们的作用都是让 Generator函数恢复执行,并且使用不同的语句替换 yield表达式。
*/
//(1)、 next()是将yield 表达式替换成一个值。
const g = function* (x, y) {
let result = yield x + y;
return result;
}
const gen = g(1, 2);
// console.log(gen.next()); // {value: 3, done: false}
// console.log(gen.next(1)) // {value: 1, done: true}
/* 解析:next没参数时,(yield x+y) = undefined; next参数为1时,(yield x+y) = 1,result = 1;
相当于将 “ let result = yield x + y ” 替换成 “ let result = 1 ”
*/
//(2)、 throw()是将yield 表达式替换成一个throw语句。
// console.log(gen.throw(new Error('出错了'))) // Uncaught Error: 出错了
/* 解析:相当于将 “ let result = yield x + y ” 替换成 “ let result = throw(new Error('出错了')) ”
*/
//(3)、 return()是将yield 表达式替换成一个return语句。
console.log(gen.return()) // {value: 2, done: true}
/* 解析:如果传参数2,(yield x+y) = return 2,如果没传参数,(yield x+y) = return undefined;
相当于将 “ let result = yield x + y ” 替换成 “ let result = return 2 ”
*/
/*(4)、 Generator 函数的语法之 yield* 表达式
如果在 Generator函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
所以 yield* 表达式,用来在一个 Generator函数里面执行另一个 Generator 函数。
*/
// 例1、
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()) {
console.log(v)
}
// x
// y
// 例2、
function* foo() {
yield 'a';
yield 'b';
}
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)
}
// "x"
// "a"
// "b"
// "y"
// 例3、yield* 命令可以很方便地取出嵌套数组的所有成员。
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
// e
// 例4、稍微复杂的例子,使用 yield* 语句遍历完全二叉树。
// 下面是二叉树的构造函数,三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// x下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用 generator函数。
// 函数体内采用递归算法,所以左树和右树要用 yield* 遍历
function* inorder(t) {
if (t) {
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array) {
// 判断是否为叶节点
if (array.length == 1) {
return new Tree(null, array[0], null);
} else {
return new Tree(make(array[0]), array[1], make(array[2]));
}
}
let treeList = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for (let node of inorder(treeList)) {
result.push(node);
}
console.log(result) //["a", "b", "c", "d", "e", "f", "g"]
/*(5)、作为对象属性的 Generator 函数:
如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
*/
let obj = {
*myGeneratorMethod() {
// ...
}
}
/* 等价于:*/
let obj = {
myGeneratorMethod: function* () {
// ...
}
}
/*(6)、Generator 函数的 this:
Generator 函数总是返回一个遍历器,es6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的 prototype对象上的方法。
*/
function* g() { }
g.prototype.hello = function () {
return 'hi!';
}
let obj = g();
console.log(obj instanceof g); // true
console.log(obj.hello()); // hi!
/*(7)、 Generator 与状态机:
Generator是实现状态机的最佳结构。比如,下面的 clock函数就是一个状态机。
*/
var ticking = true;
var clock = function () {
if (ticking) {
console.log('Tick!');
} else {
console.log('Tock!');
ticking = !ticking;
}
}
// 上面代码的 clock函数一共两种状态(Tick和 Tock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样:
var clock = function* () {
while (true) {
console.log('Tick');
yield;
console.log('Tock');
yield;
}
}
// 上面的 Generator 实现与 es5 实现对比,可以看到少了用来保存状态的外部变量ticking,
// 这样就更简洁,更安全(状态不会非法篡改)、更符合函数式编程的思想,在写法上也更优雅。
// Generator之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。
/*(8)、 Generator 与上下文:
javascript代码运行时,会产生一个全局的上下文环境,包含了当前所有的变量和对象。
然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,
变成当前(active)的上下文由此形成一个上下文环境的堆栈。
这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成
它下层的上下文,直至所有代码执行完成,堆栈清空。
Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,
但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行 next命令时,
这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
*/
function* gen() {
yield 1;
return 2;
}
let g_ = gen();
console.log(g_.next().value, g_.next().value, ); // 1 2
/* 上面代码中,第一次执行 g.next()时,Generator函数 gen的上下文会加入堆栈,
即开始运行 gen内部的代码。
等遇到yield 1 时,gen上下文退出堆栈,内部状态冻结。
第二次执行 g.next() 时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。
*/
/*(9)、 应用
Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator有多钟应用场景。
*/
/*(9.1)、异步操作的同步化表达
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,
等到调用next方法时再往后执行。
这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield表达式下面,
反正要等到调用 next方法时再执行。
所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
*/
// 例1、
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载 UI
loader.next();
// 卸载 UI
loader.next();
/* 上面代码中,第一次调用 loadUI 函数时,该函数不会执行,仅返回一个遍历器。
下一次对该遍历器调用 next方法,显示 Loading 界面,并且异步加载数据。
等到异步加载完成,再一次使用 next方法,则会隐藏 Loading 界面。
可以看到,这种写法的好处是所有 Loading 界面的逻辑,都被封装在一个函数,按部就班非常清晰。
*/
// 例2、Ajax是典型的异步操作,通过 Generator函数部署 Ajax操作,可以用同步的方式表达。
function* main() {
var result = yield request('http://some.url');
var response = JSON.parse(result);
console.log(response.value)
}
function request(url) {
makeAjaxCall(url, function (response) {
it.next(response);
})
}
var it = main();
it.next();
/* 上面代码的 main函数,就是通过 Ajax 操作获取数据。
可以看到,除了多了一个 yield,它几乎与同步操作的写法完全一样。
注意,makeAjaxCall 函数中的 next 方法,必须加上 response参数,因为yield表达式,本身是没有值的,总是等于undefined.
*/
// 例3、通过 Generator函数逐行读取文本文件。
function* numbers() {
let file = new FileReader('numbers.txt');
try {
while (!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}
// 上面代码打开文本文件,使用 yield表达式可以手动逐行读取文件。
/*(9.2)、控制流管理
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
*/
step1(function (value1) {
step2(value1, function (value2) {
step3(value2, function (value3) {
step4(value3, function (value4) {
// ...
})
})
})
})
// 采用 Promise 改写上面的代码:
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// ...
}, function (error) {
// ...
})
.done();
// 上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量Promise的语法。
// Generator 函数可以进一步改善代码运行流程。
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
// 然后,使用一个函数,按次序自动执行所有步骤。
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果 Generator 函数未结束,就继续调用。
if (!taskObj.done) {
task.value = taskObj.value;
scheduler(task);
}
}
</script>
</body>
</html>
15_22.Generator之next()、throw()、return()
最新推荐文章于 2022-10-03 18:27:24 发布