迭代器和生成器
迭代器和生成器
迭代器(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 环境。
