迭代器和生成器
迭代器和生成器
迭代器(Iterators)和生成器(Generators)是 TypeScript 中用来处理集合数据的特性,它们遵循 ES6 规范。
迭代器(Iterators):
- 迭代器是一个对象,它定义了一个序列,并且可以按需返回序列中的下一个值。
- 它必须实现一个
next()
方法,该方法返回一个包含两个属性的对象:value
(下一个值)和done
(布尔值,表示是否有更多的值可供迭代)。 - 当没有更多值时,
done
属性为true
;此时value
可能不会被设置。 - TypeScript 支持通过
for...of
循环来遍历实现了迭代器协议的对象。
let someArray = [1, 2, 3]; for (let value of someArray) { console.log(value); // 输出: 1, 2, 3 }
生成器(Generators):
- 生成器是一种特殊类型的函数,可以使用
function*
声明,它可以使用yield
关键字暂停和恢复其执行状态。 - 当生成器函数被调用时,它并不立即执行,而是返回一个迭代器,通过这个迭代器可以控制函数的执行。
- 每次调用迭代器的
next()
方法时,生成器函数会执行到下一个yield
表达式,并返回一个{ value: Any, done: Boolean }
对象,其中value
是yield
表达式的结果,done
指示生成器是否已经产出了它的最后一个值。
function* generator() { yield 1; yield 2; yield 3; } let iterator = generator(); console.log(iterator.next().value); // 输出: 1 console.log(iterator.next().value); // 输出: 2 console.log(iterator.next().value); // 输出: 3 console.log(iterator.next().done); // 输出: true
- 生成器是一种特殊类型的函数,可以使用
迭代器和生成器提供了强大的方式来处理数据集合,尤其对于需要进行懒序列处理或复杂逻辑处理的场景。在 TypeScript 中,你可以利用它们来编写更加高效和易于管理的异步代码。
可迭代性
可迭代性(Iterability)在 TypeScript 中指的是对象的能力,意味着它们可以定义一个迭代的过程。这种能力允许你使用比如for...of
循环遍历对象中的值。在 JavaScript 中,数组就是最常见的可迭代对象。
要使类型支持迭代,在 TypeScript 中你需要使用到Symbol.iterator
属性,这是一个返回迭代器的函数。迭代器是一个具有next()
方法的对象,每次调用next()
都会返回一个形如{done: boolean, value: any}
的对象。当done
为true
时表示迭代结束了。
以下是一些关于可迭代性的重点:
实现可迭代协议:为了让自定义对象可迭代,你必须在对象上实现
[Symbol.iterator]()
方法。Array 和 Set:在 JavaScript 和 TypeScript 中,数组(
Array
)和集合(Set
)都已经内置了迭代器,所以可以直接在for...of
循环中使用。字符串也是可迭代的:字符串是字符的一个序列,你可以使用
for...of
循环来遍历一个字符串中的所有字符。
以下是一些例子:
// 数组的可迭代性
let numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num); // 输出 1, 2, 3
}
// 字符串的可迭代性
let greeting = "hello";
for (let char of greeting) {
console.log(char); // 输出 'h', 'e', 'l', 'l', 'o'
}
// 自定义可迭代对象
class MyCollection {
private items: number[];
constructor(items: number[]) {
this.items = items;
}
// 实现 [Symbol.iterator] 方法
[Symbol.iterator]() {
let i = 0;
return {
next: () => {
if (i < this.items.length) {
return { done: false, value: this.items[i++] };
} else {
return { done: true };
}
},
};
}
}
// 使用自定义的可迭代对象
let collection = new MyCollection([100, 200, 300]);
for (let item of collection) {
console.log(item); // 输出 100, 200, 300
}
注意:虽然for...in
循环能够遍历对象的键,但它不是基于迭代器协议的,而且通常不建议用于数组的迭代,因为它并不仅限于元素索引的遍历,还会遍历对象的所有可枚举属性。
for..of 语句
迭代器和生成器 > 可迭代性 > for..of 语句
for..of
语句用于遍历可迭代对象的元素。在 JavaScript 中,一些内置类型如数组、字符串、Map 和 Set 都是可迭代的。- 使用
for..of
循环时,它会自动请求下一个值,直到遍历完所有值。 - TypeScript 中的
for..of
提供了一种简单且清晰的方法来遍历各种集合。
例子:
let someArray = [1, "string", false];
// 遍历数组
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
// 遍历字符串中的字符
for (let char of "hello") {
console.log(char); // 'h', 'e', 'l', 'l', 'o'
}
- 当你使用
for..of
语句来遍历对象时,你会得到对象中每个元素的值。 - 不同于
for..in
,它遍历的是对象的键,而for..of
遍历的是对象的值。这使得for..of
更适合需要操作数据值时的情况。
例子:
let pets = new Set(["Cat", "Dog", "Hamster"]);
// 遍历集合(Set)
for (let pet of pets) {
console.log(pet); // "Cat", "Dog", "Hamster"
}
let numbers = new Map<number, string>([
[1, "One"],
[2, "Two"],
[3, "Three"],
]);
// 遍历映射(Map)
for (let [num, numString] of numbers) {
console.log(`${num} stands for ${numString}`);
// "1 stands for One", "2 stands for Two", "3 stands for Three"
}
- 如果一个对象没有实现
Symbol.iterator
属性,则它不是可迭代的,不能使用for..of
进行遍历。 - 为了让 TypeScript 知道一个对象是可迭代的,你可以实现该对象的迭代器接口(
Iterable
接口)。
例子:
// 自定义迭代器
class ComponentCollection {
private components: string[];
constructor() {
this.components = ["Component1", "Component2", "Component3"];
}
[Symbol.iterator]() {
let pointer = 0;
let components = this.components;
return {
next(): IteratorResult<string> {
if (pointer < components.length) {
return {
done: false,
value: components[pointer++],
};
} else {
return {
done: true,
value: null,
};
}
},
};
}
}
let myComponents = new ComponentCollection();
// 使用 for..of 遍历自定义的类实例
for (let comp of myComponents) {
console.log(comp);
}
- 在上面的例子中,我们创建了一个名为
ComponentCollection
的类,并使其成为可迭代的,通过添加一个[Symbol.iterator]()
方法。这样就可以使用for..of
来遍历myComponents
实例中的组件了。
for..of vs. for..in 语句
迭代器和生成器 > 可迭代性 > for..of 语句 > for..of vs. for..in 语句
for..of
语句用于遍历可迭代对象(如数组、字符串、Map、Set 等)的元素。示例:
let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, 'string', false }
for..in
语句用于遍历一个对象的属性名(也就是对象的键)。示例:
let list = { a: 1, b: 2, c: 3 }; for (let i in list) { console.log(i); // 'a', 'b', 'c' }
for..of
提供了一种访问数据元素值的简洁方法,而不需要使用索引。for..in
循环出的是对象的键,适合需要处理对象属性时使用,但如果用在数组上,它可能返回预期之外的键(如数组对象的自定义属性或者原型链上的属性),并且它的顺序可能会变。示例:
let array = [10, 20, 30]; array.foo = "Hello"; // 给数组添加一个属性 for (let i in array) { console.log(i); // 输出 '0', '1', '2', 'foo' } for (let i of array) { console.log(i); // 输出 10, 20, 30 }
当要遍历数组和想要获取元素值时,推荐使用
for..of
。当要遍历对象属性时,可以使用
for..in
,但要注意它还会枚举原型链上的属性。
代码生成
for...of
语句是 TypeScript 和 JavaScript 中用于遍历可迭代对象(如数组、字符串、Map、Set 等)的元素的一种简洁方法。与 for...in
语句不同,它不是用来遍历对象的键,而是用来遍历可迭代对象的值。
在讨论代码生成时,我们指的是 TypeScript 编译器将 for...of
循环转换成 JavaScript 代码的过程。TypeScript 需要根据目标版本的 JavaScript 来决定如何生成代码,因为不是所有版本的 JavaScript 都支持 for...of
循环或迭代器协议。
当目标是 ES5 或更低版本,
for...of
会被编译成一个基于for
循环和额外辅助函数的形式。例子:
let arr = [1, 2, 3]; for (let value of arr) { console.log(value); // 输出: 1, 2, 3 }
上面的代码会被编译成类似以下的 ES5 代码:
var arr = [1, 2, 3]; for (var _i = 0, _a = arr; _i < _a.length; _i++) { var value = _a[_i]; console.log(value); }
如果目标是 ES6 或更高版本,则
for...of
可以直接在生成的 JavaScript 代码中使用,因为这些版本原生支持for...of
循环和迭代器。使用 ES6 的例子与上述 TypeScript 代码相同,但生成的代码将保持
for...of
形式,因为 ES6 支持它。
了解 for...of
语句如何被编译能够帮助你理解兼容性问题以及 TypeScript 如何处理新特性与旧环境的关系。记住,即使你的 TypeScript 代码中使用了最新的特性,编译器也会帮你转换成目标环境支持的 JavaScript 代码。
目标为 ES5 和 ES3
在 TypeScript 中,代码生成是指 TypeScript 编译器如何将 TypeScript 代码转换为 JavaScript 代码,因为浏览器和其他 JavaScript 环境并不直接执行 TypeScript。
当你使用 TypeScript 写for..of循环时,TypeScript 需要将这个循环转化成一个在目标 JavaScript 环境中可运行的形式。
如果你设定编译器的目标(target)为ES5或ES3,它不能直接使用 ES6 的for..of语法,因为 ES5 或 ES3 标准中没有原生支持这个特性。
TypeScript 会将for..of循环转化为一个旧版本的 JavaScript 循环结构,通常是一个基于迭代器的 while 循环。例如:
let someArray = [1, "string", false]; for (let item of someArray) { console.log(item); // 1, "string", false }
编译到 ES5 后,上面的代码可能会变成类似这样:
var someArray = [1, "string", false]; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for ( var _iterator = someArray[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var item = _step.value; console.log(item); // 1, "string", false } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } }
注意,在这个例子中,
Symbol.iterator
和相应的逻辑用来模仿 ES6 的迭代器协议,但如果目标是 ES3,甚至连 Symbol 都无法使用,所以 TypeScript 会采用不同的方法来确保兼容。这种转换意味着可以在老版 JavaScript 环境中使用 TypeScript 编写的现代代码,但可能会导致生成的代码更长、更难阅读。
对于新手来说,重要的是了解虽然你可能正在使用最新的 TypeScript 或 JavaScript 特性,但编译后的代码需要能在目标环境(如 ES5 浏览器)中运行。这就是编译器设定目标版本的原因。
目标为 ECMAScript 2015 或更高
TypeScript 中的
for..of
语句用于对集合进行迭代,它会遍历集合的元素。与 JavaScript 中的for..in
不同,for..of
提供了一种访问可迭代对象的值的直接方法。当你将 TypeScript 代码编译成 JavaScript 时,可以指定目标版本。如果目标是 ECMAScript 2015(也就是 ES6)或更高版本,则生成的
for..of
代码将会使用原生 ES6 语法结构。如果目标版本低于 ES2015,TypeScript 编译器将使用等价的 ES5 代码来模拟
for..of
的行为。
下面是几个for..of
的例子:
// 假设我们有一个数字数组
let numbers = [1, 2, 3];
// 使用 for..of 遍历数组中的每个数字
for (let number of numbers) {
console.log(number); // 输出: 1, 2, 3
}
// 假设我们有一个字符串类型的数组
let pets = ["cat", "dog", "rabbit"];
// 使用 for..of 遍历数组中的每个字符串
for (let pet of pets) {
console.log(pet); // 输出: cat, dog, rabbit
}
在上述例子中,如果编译目标是 ES2015 或更高版本,编译后的 JavaScript 代码将保持for..of
结构。如果目标是低于 ES2015 的版本,如 ES5,则 TypeScript 编译器将转换为使用for
循环和数组的.forEach
方法或其他等效的 JavaScript 代码以便兼容老版本 JS 环境。