《TypeScript实战指南》—2.1.7 iterator和generator
2.1.7 iterator和generator
1. iterator
当一个对象实现了Symbol.iterator时,我们认为它是可迭代的。如array、map、set、string、int32Array、uint32Array等一些内置的类型,目前都已经实现了各自的Symbol.iterator。对象上的Symbol.iterator函数负责返回供迭代的值。
for..of语句会遍历可迭代的对象,调用对象上的Symbol.iterator方法。
比如下面是在数组上使用for..of的例子:
const array = [233, "hello", true];
for (let value of array) {
console.log(value); // 233, "hello", true
}
for..of和for..in都可以迭代一个数组,但它们之间的区别很大。最明显的区别莫过于它们用于迭代器的返回值并不相同,for..in迭代的是对象的键,而for..of迭代的是对象的值。
我们可以从下面的例子中看出两者之间的区别:
const array = [3, 4, 5];
for (let i in array) {
console.log(i); // 0, 1, 2
}
for (let i of array) {
console.log(i); // 4, 5, 6
}
另一个区别在于,for..in可以操作任何对象,提供了查看对象属性的一种方法。但是for..of关注迭代对象的值,内置对象Map和Set已经实现了Symbol.iterator方法,让我们可以访问它们的值:
const fruits = new Set(["apple", "pear", "mango"]);
fruits["peach"] = "Princess Peach! Make a wish!";
for (let fruit in fruits) {
console.log(fruit); // "peach"
}
for (let fruit of fruits) {
console.log(fruit); // "apple", "pear", "peach"
}
但这样的特性仅仅在 ES 6 及以上才上生效。
当我们将 TypeScript 的代码生成目标设定为ES5或ES3,迭代器就只允许在array类型上使用。在非数组值上使用for..of语句会得到一个错误。即便这些非数组值已经实现了Symbol.iterator属性,也是不可以的。
编译器会生成一个简单的for循环作为for..of循环,比如:
const numbers = [1, 2, 3];
for (let number of numbers) {
console.log(number);
}
生成的代码为:
var numbers = [1, 2, 3];
for (var _i = 0; _i < numbers.length; _i++) {
var number = numbers[_i];
console.log(number);
}
2. generator
function * 是用来创建 generator 函数的语法。(在 MDN 的文档中 generator 称为生成器。)
调用 generator 函数时会返回一个generator 对象。generator 对象遵循迭代器接口,即通常所见到的 next、return 和 throw 函数。
generator函数用于创建懒迭代器,例如下面的这个函数可以返回一个无限整数的列表:
function* infiniteList() {
let i = 0;
while(true) {
yield i++;
}
}
var iterator = infiniteList();
while (true) {
console.log(iterator.next()); // { value: xxxx, done: false }
}
当然,也可以设定某个条件终止它,而不只是永远循环下去。如下所示:
function* infiniteList(){
let i = 0;
while(i < 3)
yield i++;
}
let gen = infiniteList();
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }
可以说这个设定是generator中最令人兴奋的部分。它在实质上允许一个函数可以暂停执行,比如当我们执行了第一次的 gen.next() 后,可以先去做别的事,再回来继续执行 gen.next(),这样剩余函数的控制权就交给了调用者。
当你直接调用 generator 函数时,它并不会执行,它只会创建一个 generator 对象。
在下面的例子中,我们可以看到一个更灵活的使用方式:
function* generator(){
console.log('Execution started');
yield 0;
console.log('Execution resumed');
yield 1;
console.log('Execution end');
}
执行它,则会看到如下输出结果:
constiterator = generator();
console.log(iterator.next());
// "Execution started"
// { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
// "Execution resumed'"
// { value: 1, done: false }
console.log(iterator.next());
// "Execution end"
// { value: undefined, done: true }
console.log(iterator.next());
// { value: undefined, done: true }
从上面代码可以得知:
generator 对象只会在调用next时开始执行。
函数在执行到yield语句时会暂停并返回yield的值。
函数在next被调用时继续恢复执行。
所以实质上generator函数的执行与否是由外部的 generator 对象控制的。
不过除了 yield 传值到外部,我们也可以通过 next 传值到内部进行调用。下面的例子展示了iterator.next传值的方式:
function* generator() {
const who = yield;
console.log('hello '+ who); // bar!
}
const iterator = generator();
Console.log(iterator.next());
// {value: undefined, done: false}
console.log(iterator.next('TypeScript'));
// hello TypeScript
// {value: undefined, done: true}
以上便是 next 和 return 函数的内容,接下来我们来看一下 throw 函数如何处理迭代器内部报错。
下面是iterator.throw的例子:
function* generator() {
try {
yield 1;
}
catch(error) {
console.log(error.message);
}
}
const iterator = generator();
iterator.next()
// {value: 1, done: false}
iterator.throw(new Error('something incorrect'));
// something incorrect
// {value: undefined, done: true}
通过以上的案例我们可以得知,外部是可以对 generator 内部进行干涉的:
外部系统可以传递一个值到 generator 函数体中。
外部系统可以抛入一个异常到 generator 函数体中。
- 点赞
- 收藏
- 关注作者
评论(0)