生成器和迭代器

迭代器:迭代器是被设计专用于迭代的对象,带有特定接口。所有的迭代器对象都拥有 next() 方法,会返回一个结果对象。该结果对象有两个属性:对应下一个值的 value ,以及一个布尔类型的 done ,其值为 true 时表示没有更多值可供使用。迭代器持有一个指向集合位置的内部指针,每当调用了 next() 方法,迭代器就会返回相应的下一个值。

//手写迭代器对象
        function createInterator() {
            var i = 0;
            return {
                next: function() {
                    var done = (i >= items.length)
                    var value = !done ? item[i++] : undefined
                    return {
                        done: done,
                        value: value
                    }
                }
            }
        }
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器:生成器( generator )是能返回一个迭代器的函数。生成器函数由放在 function 关键字之后的一个星号( * )来表示,并能使用新的 yield 关键字。将星号紧跟在 function 关键字之后,或是在中间留出空格。

// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

yield 关键字只能用在生成器内部,用于其他任意位置都是语法错误,即使在生成器内部的函数中也不行。

可迭代对象:可迭代对象( iterable )是包含 Symbol.iterator 属性的对象。这
个 Symbol.iterator 知名符号定义了为指定对象返回迭代器的函数。在 ES6 中,所有的集合对象(数组、 Set 与 Map )以及字符串都是可迭代对象,因此它们都被指定了默认的迭代器。可迭代对象被设计用于与 ES 新增的 for-of 循环配合使用。

let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}

这个 for-of 循环首先调用了 values 数组的 Symbol.iterator 方法,获取了一个迭代器
(对 Symbol.iterator 的调用发生在 JS 引擎后台)。接下来 iterator.next() 被调用,迭代器结果对象的 value 属性被读出并放入了 num 变量。 num 变量的值开始为 1 ,接下来是 2 ,最后变成 3 。当结果对象的 done 变成 true ,循环就退出了,因此 num 绝不会被赋值为 undefined 。

创建可迭代对象:

let collection = {
	items: [],
	*[Symbol.iterator]() {
	for (let item of this.items) {
		yield item;
		}
	}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}

集合的迭代器
ES6 具有三种集合对象类型:数组、 Map 与 Set 。这三种类型都拥有如下的迭代器,有助于
探索它们的内容:
entries() :返回一个包含键值对的迭代器;
values() :返回一个包含集合中的值的迭代器;
keys() :返回一个包含集合中的键的迭代器。
你可以调用上述方法之一来提取集合中的迭代器。

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
for (let entry of colors.entries()) {
console.log(entry);
}
for (let entry of tracking.entries()) {
console.log(entry);
}
for (let entry of data.entries()) {
console.log(entry);
}
//[0, "red"]
//[1, "green"]
//[2, "blue"]
//[1234, 1234]
//[5678, 5678]
//[9012, 9012]
//["title", "Understanding ES6"]
//["format", "ebook"]

当 for-of 循环没有显式指定迭代器时,每种集合类型都有一个默认的迭代器供循环使用。values() 方法是数组与 Set 的默认迭代器,而 entries() 方法则是 Map 的默认迭代器。在for-of 循环中使用集合对象时,这些默认迭代器会让处理更容易一些。

//解构与 for-of 循环
//Map 默认迭代器的行为有助于在 for-of 循环中使用解构,正如此例:
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
// 与使用 data.entries() 相同
for (let [key, value] of data) {
console.log(key + "=" + value);
}
//此代码中的 for-of 循环使用了数组解构,来将 Map 中的每个项存入 key 与 value
//变量。使用这种方式,你能轻易同时处理键与值,而无须访问一个双项数组,或是回到
//Map 中去获取键或值。在 Map 上进行 for-of 循环时使用数组解构,能让这种循环像
//在处理 Set 或数组时一样有用。

字符串的迭代器
从 ES5 发布开始, JS 的字符串就慢慢变得越来越像数组。例如 ES5 标准化了字符串的方括号表示法,用于访问其中的字符(即:使用 text[0] 来获取第一个字符,以此类推)。不过方括号表示法工作在码元而非字符上,因此它不能被用于正确访问双字节的字符,正如此例所演示的:

var message = "A B" ;
for (let i=0; i < message.length; i++) {
console.log(message[i]);
}
//双字节字符被当作两个分离的码元来对待,此处blak被输出四次
//A
//(blank)
//(blank)
//(blank)
//(blank)
//B

var message = "A B" ;
for (let c of message) {
console.log(c);
}
//A
//(blank)
//(blank)
//B

Nodelist的迭代器:
随着默认迭代器被附加到 ES6 , DOM 关于 NodeList 的规定也包含了一个默认迭代器(此规定在 HTML 规范而非 ES6 规范中),其表现方式与数组的默认迭代器一致。这意味着你可以将 NodeList 用于 for-of 循环,或用于其他使用对象默认迭代器的场合。

var divs = document.getElementsByTagName("div");
for (let div of divs) {
console.log(div.id);
}

扩展运算符与非数组的可迭代对象

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
let map = new Map([ ["name", "Nicholas"], ["age", 25]]),
array = [...map];
console.log(array); // [ ["name", "Nicholas"], ["age", 25]]

你可以不限次数地在数组字面量中使用扩展运算符,而且可以在任意位置使用扩展运算符将可迭代对象的多个项插入数组,这些项在新数组中将会出现在扩展运算符对应的位置。

let smallNumbers = [1, 2, 3],
bigNumbers = [100, 101, 102],
allNumbers = [0, ...smallNumbers, ...bigNumbers];
console.log(allNumbers.length); // 7
console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]

传递参数给迭代器
通过 next() 方法或者在生成器中使用yield 都可以。但你还能通过 next() 方法向迭代器传递参数。当一个参数被传递给next() 方法时,该参数就会成为生成器内部 yield 语句的值。这种能力对于更多高级功能(例如异步编程)来说是非常重要的。

function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

在迭代器中抛出错误

function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2 ,然后抛出错误
yield second + 3; // 永不会被执行
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // 从生成器中抛出了错误
//抛出错误之后调用next方法将不会
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2 ,然后抛出错误
yield second + 3; // 永不会被执行
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // 从生成器中抛出了错误

生成器的Return语句
阻断程序的执行。如果有值的话将会传给next

function *createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器委托
在某些情况下,将两个迭代器的值合并器一起会更有用。生成器可以用星号( * )配合yield 这一特殊形式来委托其他的迭代器。正如生成器的定义,星号出现在何处是不重要的,只要落在 yield 关键字与生成器函数名之间即可

function *createNumberIterator() {
yield 1;
yield 2;
}
function *createColorIterator() {
yield "red";
yield "green";
}
function *createCombinedIterator() {
yield *createNumberIterator();
yield *createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

任务运行器:
异步任务运行:
js的异步编程可以很容易实现简单的异步任务,但是复杂任务会让组织代码变得艰难。由于生成器在执行过程可以有效的暂停代码,所以可以实现相关的异步 编程。
由于yield能停止运行,并在重新开始运行前等待next()方法调用,所以在没有回调函数的情况下可以实现异步调用。

function run(taskDef) {
// 创建迭代器,让它在别处可用
let task = taskDef();
// 启动任务
let result = task.next();
// 递归使用函数来保持对 next() 的调用
function step() {
// 如果还有更多要做的
if (!result.done) {
	if (typeof result.value === "function") {
	result.value(function(err, data) {
		if (err) {
		result = task.throw(err);
		return;
		}
		result = task.next(data);
		step();
		});
	} else {
	result = task.next(result.value);
	step();
			}
		}
	}
// 开始处理过程
step();
}

当 result.value 是个函数时(使用 === 运算符来判断),它会被使用一个回调函数进行调用。该回调函数遵循了 Node.js 的惯例,将任何潜在错误作为第一个参数( err )传入,而处理结果则作为第二个参数。若 err 非空,也就表示有错误发生,需要使用该错误对象去调用 task.throw() ,而不是调用 task.next() ,这样错误就会在恰当的位置被抛出;若不存在错误, data 参数将会被传入 task.next() ,而其调用结果也会被保存下来。接下来,调用 step() 来继续处理过程。若 result.value 并非函数,它就会被直接传递给 next() 方法。

总结:
1.迭代器是 ES6 重要的一部分,并且也是语言若干关键元素的根源。在表面上,迭代器提供了一种使用简单接口来返回值序列的简单方式。然而,在 ES6 中使用迭代器,还有着种种更加复杂的方式。
2.Symbol.iterator 符号被用于定义对象的默认迭代器。内置对象与开发者自定义对象都可以使用这个符号,以提供一个能返回迭代器的方法。当 Symbol.iterator 在一个对象上存在时,该对象就会被认为是可迭代对象。
3.for-of 循环在循环中使用可迭代对象来返回一系列数据。与使用传统 for 循环进行迭代相比,使用 for-of 要容易得多,因为你不再需要追踪计数器并控制循环何时结束。 for-of循环会自动从迭代器中读取所有数据,直到没有更多数据为止,然后退出循环。为了让 for-of 更易使用, ES6 中的许多类型都具有默认的迭代器。所有的集合类型(也是数组、 Map 与 Set )都具有迭代器,让它们的内容更易被访问。字符串同样具有一个默认迭代器,能更加轻易地迭代字符串中的字符(而非码元)。扩展运算符能操作任意的可迭代对象,同时也能更简单地将可迭代对象转换为数组。转换工作会从一个迭代器中读取数据,并将它们依次插入数组。
4.生成器是一个特殊的函数,可以在被调用时自动创建一个迭代器。生成器的定义用一个星号( * )来表示,使用 yield 关键字能指明在每次成功的 next() 方法调用时应当返回什么值。
5.生成器委托促进了对迭代器行为的良好封装,让你能将已有的生成器重用在新的生成器中。通过调用 yield * 而非 yield ,你就能把已有生成器用在其他生成器内部。这种处理方式能创建一个从多个迭代器中返回值的新迭代器。
6.生成器与迭代器最有趣、最令人激动的方面,或许就是可能创建外观清晰的异步操作代码。你不必到处使用回调函数,而是可以建立貌似同步的代码,但实际上却使用 yield 来等待异步操作结束。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值