Appearance
Array的Iterators
TypeScript可以生成ES5和ES6的迭代器,并且只支持Array
。由于迭代器只允许在Array类型上使用。 在非数组值上使用for..of语句会得到一个错误,就算这些非数组值已经实现了Symbol.iterator属性。
编译器会生成一个简单的for循环做为for..of循环,比如:
ts
let numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}
编译后,将生成JS代码,而这就成为Typescript中Array的Generator:
js
var numbers = [1, 2, 3];
for (var _i = 0; _i < numbers.length; _i++) {
var num = numbers[_i];
console.log(num);
}
当目标为兼容ECMAScipt 2015的引擎时,编译器会生成相应引擎的for..of内置迭代器实现方式。所以,编译器设置目标为 ECMAScript 2015 或更高。
for..of
使用 for...of 循环: for...of 循环可以遍历任何可迭代对象,如数组、字符串、Map、Set等。调用对象上的Symbol.iterator方法。 下面是在数组上使用for..of的简单例子:
只允许在Array类型上使用,但无关Array子集的类型。
ts
let arr = [1, 2, 3];
for (let value of arr) {
console.log(value);
}
for..in
for..in可以操作任何对象
,它提供了查看对象属性的一种方法。 但是for..of关注于迭代对象的值。内置对象Map和Set已经实现了Symbol.iterator方法,让我们可以访问它们保存的值。
ts
let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals";
for (let pet in pets) {
console.log(pet); // "species"
}
for (let pet of pets) {
console.log(pet); // "Cat", "Dog", "Hamster"
}
如果进行对象遍历,应该使用for..in
而不是for...of
:
ts
const person = {
name: "张三",
age: 30,
job: "工程师"
};
for (const key in person) {
if (person.hasOwnProperty(key)) { // 检查属性是否在对象本身上,而不是原型链上
console.log(`${key}:${person[key]}`);
}
}
上面的方法,如果使用for...of
操作,会报错:
ts
const person = {
name: "张三",
age: 30,
job: "工程师"
};
for (const key of person) {
console.log(key); // 报错
}
迭代器
迭代器是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
下面是一个模拟next方法返回值的例子。
ts
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
var it = makeIterator(['a', 'b']);
it.next() // {value: "a", done: false}
it.next() // {value: "b", done: false}
it.next() // {value: undefined, done: true}
上面代码中,makeIterator函数是自定义遍历器生成函数,它的参数是一个数组,返回值是一个遍历器对象。该遍历器对象的根本特征就是具有next方法。每次调用next方法,都会返回数组的一个成员。
上面代码对Array对象部署了Iterator接口,因此就可以用for...of循环遍历数组。
ts
var myArray = ['a', 'b', 'c'];
var iterator = myArray[Symbol.iterator]();
iterator.next() // {value: "a", done: false}
iterator.next() // {value: "b", done: false}
iterator.next() // {value: "c", done: false}
iterator.next() // {value: undefined, done: true}
下面是另一个类似数组的对象部署Iterator接口的例子。
ts
let nodeList = document.querySelectorAll('div');
let iterator = nodeList[Symbol.iterator]();
iterator.next() // {value: div#foo, done: false}
iterator.next() // {value: div#bar, done: false}
iterator.next() // {value: div#baz, done: false}
iterator.next() // {value: undefined, done: true}
上面代码中,querySelectorAll方法返回的是一个类似数组的对象。该对象没有部署Iterator接口,无法使用for...of循环。这时,可以调用Symbol.iterator方法,为它加上遍历器接口。
ts
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
for (var x of document.querySelectorAll('div')) {
console.log(x);
}
下面是另一个类似数组的对象调用Symbol.iterator方法的例子。
ts
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let x of iterable) {
console.log(x); // 'a', 'b', 'c'
}
上面代码中,对象iterable是一个类似数组的对象,但是没有部署Iterator接口,无法用for...of循环遍历。这时,可以调用Symbol.iterator方法,为它加上遍历器接口。加上遍历器接口以后,iterable就可以用for...of循环遍历了。
对于那些没有部署Iterator接口的类似数组的对象(比如arguments对象、DOM NodeList对象),扩展运算符(...)和Array.from方法会自动为它们加上Iterator接口。
ts
// arguments对象
function foo() {
for (let v of arguments) {
console.log(v);
}
}
foo(1, 2, 3)
// 1 2 3
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
上面代码中,arguments对象和NodeList对象都没有部署Iterator接口,但是扩展运算符和Array.from方法会自动为它们加上Iterator接口。
默认 Iterator 接口
Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
一种数据结构只要部署了Iterator接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。
Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内。
原生具备 Iterator 接口的数据结构如下。
在JavaScript中,原生具备 Iterator 接口的数据结构主要包括以下几种,所以TypeScript则作为JS的超集,自然也存在。
- Array
- Map
- Set
- String
- TypedArray(Uint8Array等)
- 函数的 arguments 对象
- NodeList 对象