Skip to content

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 对象

仅用于培训和测试,通过使用本站代码内容随之而来的风险与本站无关。版权所有,未经授权请勿转载,保留一切权利。
ICP备案号:滇ICP备15009214号-13   公安网备:滇公网安备 53312302000061号