1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

修改iterator

This commit is contained in:
Ruan Yifeng 2015-02-27 10:30:48 +08:00
parent caa4f9e993
commit d37c6ff2a4

View File

@ -2,11 +2,15 @@
## Iterator遍历器
### 语法
### 概念
遍历器Iterator是一种接口规格任何对象只要部署这个接口就可以完成遍历操作。它的作用有两个一是为各种数据结构提供一个统一的、简便的接口二是使得对象的属性能够按某种次序排列。在ES6中遍历操作特指for...of循环即Iterator接口主要供for...of循环使用
JavaScript原有的数据结构主要是数组Array和对象ObjectES6又添加了Map和Set用户还可以组合使用它们定义自己的数据结构。这就需要一种统一的接口机制来处理所有不同的数据结果
遍历器提供了一个指针指向当前对象的某个属性使用next方法就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中value属性是当前遍历位置的值done属性是一个布尔值表示遍历是否结束。下面是一个模拟next方法返回值的例子。
遍历器Iterator就是这样一种机制。它属于一种接口规格任何数据结构只要部署这个接口就可以完成遍历操作即依次处理该结构的所有成员。它的作用有两个一是为各种数据结构提供一个统一的、简便的接口二是使得数据结构的成员能够按某种次序排列。在ES6中遍历操作特指for...of循环即Iterator接口主要供for...of消费。
遍历器的遍历过程是这样的它提供了一个指针默认指向当前数据结构的起始位置。第一次调用遍历器的next方法可以将指针指向到第一个成员第二次调用就指向第二个成员直至指向数据结构的结束位置。每一次调用都会返回当前成员的信息具体来说就是返回一个包含value和done两个属性的对象。其中value属性是当前成员的值done属性是一个布尔值表示遍历是否结束。
下面是一个模拟next方法返回值的例子。
```javascript
@ -28,9 +32,10 @@ it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
```
上面代码定义了一个makeIterator函数它的作用是返回一个遍历器对象用来遍历参数数组。next方法依次遍历数组的每个成员请特别注意next返回值的构造。
下面是一个无限运行的遍历器例子。
上面代码定义了一个makeIterator函数它的参数是一个数组。调用该函数就会返回一个对象。这个对象具有一个next方法每次调用next方法它的内部指针就会指向数组的下一个成员并返回一个该成员信息的对象。请特别注意next方法的返回值的构造一个具有value和done两个属性的对象。通过这个返回值我们就可以知道当前成员的值是什么以及遍历是否结束。在这个例子中makeIterator函数用来生成遍历器它返回的那个具有next方法的对象就是遍历器调用遍历器的next方法就可以遍历事先给定的数组。
因为遍历器的作用,只是把接口规格加到数据结构之上。所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器,或者说用遍历器模拟出数据结构。下面是一个无限运行的遍历器例子。
```javascript
@ -53,15 +58,37 @@ it.next().value // '2'
```
上面的例子说明了next方法返回值的结构value和done两个属性
上面的例子idMaker函数返回的对象就是遍历器但是并没有对应的数据结构或者说遍历器自己描述了一个数据结构出来
### Iterator接口的部署
总之所谓Iterator接口就是指调用这个接口会返回一个遍历器对象。该对象具备next方法每次调用该方法会返回一个具有value和done两个属性的新对象指向部署了Iterator接口的数据结构的一个成员。
具有Iterator接口的对象都能被for...of循环遍历见后文的介绍。所谓Iterator接口就是指它会返回一个遍历器对象该对象具备next方法每次调用该方法会依次返回一个具有上节提到的value和done两个属性的新对象指向原对象的一个成员。
### 默认的Iterator接口
在ES6中有三类数据结构原生具备Iterator接口数组、类似数组的对象、Set和Map结构。除此之外其他数据结构主要是对象的Iterator接口都需要自己部署。其他对象需要手动部署Iterator接口让其返回一个遍历器
Iterator接口的开发目的就是为所有数据结构提供了一种统一的访问机制即for...of循环见后文的介绍。当使用for...of循环遍历某种数据结构时该循环会自动去寻找Iterator接口
一个对象如果要有Iterator接口必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。
ES6规定默认的Iterator接口部署在数据结构的Symbol.iterator属性。也就是说调用Symbol.iterator方法就会得到当前数据结构的默认遍历器。Symbol.iterator是一个表达式返回Symbol对象的iterator属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内请参考Symbol一节
在ES6中有三类数据结构原生具备Iterator接口数组、类似数组的对象、Set和Map结构。
```javascript
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
```
上面代码中变量arr是一个数组原生就具有遍历器接口部署在arr的Symbol.iterator属性上面。所以调用这个属性就得到遍历器。
上面提到原生就部署iterator接口的数据结构有三类对于这三类数据结构不用自己写遍历器for...of循环会自动遍历它们。除此之外其他数据结构主要是对象的Iterator接口都需要自己在Symbol.iterator属性上面部署这样才会被for...of循环遍历。
对象Object之所以没有默认部署Iterator接口是因为对象的哪个属性先遍历哪个属性后遍历是不确定的需要开发者手动指定。本质上遍历器是一种线性处理对于任何非线性的数据结构部署遍历器接口就等于部署一种线性转换。不过严格地说对象部署遍历器接口并不是很必要因为这时对象实际上被当作Map结构使用ES5没有Map结构而ES6原生提供了。
一个对象如果要有可被for...of循环调用的Iterator接口就必须在Symbol.iterator的属性上部署遍历器方法原型链上的对象具有该方法也可
```javascript
@ -75,18 +102,16 @@ class MySpecialTree {
```
上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式返回Symbol对象的iterator属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内请参考Symbol一节。这里要注意@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。
下面是一个例子。
上面代码是一个类部署Iterator接口的写法。Symbol.iterator属性对应一个函数执行后返回当前对象的遍历器。下面是一个例子。
```javascript
function O(value){
function Obj(value){
this.value = value;
this.next = null;
}
O.prototype[Symbol.iterator] = function(){
Obj.prototype[Symbol.iterator] = function(){
var iterator = {
next: next
@ -112,9 +137,9 @@ O.prototype[Symbol.iterator] = function(){
return iterator;
}
var one = new O(1);
var two = new O(2);
var three = new O(3);
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
@ -155,7 +180,7 @@ let obj = {
```
对于类似数组的对象部署Iterator接口有一个简便方法就是`Symbol.iterator`方法直接引用数值的Iterator接口。
对于类似数组的对象存在数值键名和length属性部署Iterator接口有一个简便方法就是`Symbol.iterator`方法直接引用数值的Iterator接口。
```javascript
@ -163,7 +188,7 @@ NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
```
如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。
如果Symbol.iterator方法返回的不是遍历器解释引擎将会报错。
```javascript
@ -175,7 +200,7 @@ obj[Symbol.iterator] = () => 1;
```
上面代码中变量obj的@@iterator方法返回的不是遍历器,因此报错。
上面代码中变量obj的Symbol.iterator方法返回的不是遍历器,因此报错。
### 原生具备iterator接口的数据结构
@ -277,7 +302,11 @@ for (let x of obj) {
## for...of循环
ES6中一个对象只要部署了@@iterator方法就被视为具有iterator接口就可以用for...of循环遍历它的值。也就是说for...of循环内部调用是原对象的`Symbol.iterator`方法。
ES6中一个数据结构只要部署了Symbol.iterator方法就被视为具有iterator接口就可以用for...of循环遍历它的成员。也就是说for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可以使用的范围包括数组、Set和Map结构、类似数组的对象比如arguments对象、DOM NodeList对象、后文的Generator对象以及字符串。
### 数组
数组原生具备iterator接口。
@ -298,8 +327,8 @@ for(let v of arr) {
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
console.log(element); // red green blue
console.log(index); // 0 1 2
});
```
@ -309,6 +338,7 @@ JavaScript原有的for...in循环只能获得对象的键名不能直接
```javascript
var arr = ["a", "b", "c", "d"];
for (a in arr) {
console.log(a); // 0 1 2 3
}
@ -321,13 +351,15 @@ for (a of arr) {
上面代码表明for...in循环读取键名for...of循环读取键值。如果要通过for...of循环获取数组的索引可以借助数组实例的entries方法和keys方法参见《数组的扩展》章节。
### Set和Map结构
Set和Map结构也原生具有Iterator接口可以直接使用for...of循环。
```javascript
var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
console.log(e);
}
// Gecko
// Trident
@ -346,7 +378,86 @@ for (var [name, value] of es6) {
```
上面代码演示了如何遍历Set结构和Map结构后者是同时遍历键名和键值。
上面代码演示了如何遍历Set结构和Map结构。值得注意的地方有两个首先遍历的顺序是按照各个成员被添加进数据结构的顺序。其次Set结构遍历时返回的是一个值而Map结构遍历时返回的是一个数组该数组的两个成员分别为当前Map成员的键名和键值。
```javascript
let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
console.log(pair);
}
// ['a', 1]
// ['b', 2]
```
### 计算生成的数据结构
ES6的数组、Set、Map都部署了以下三个方法调用后都返回遍历器。
- entries() 返回一个遍历器,用来遍历 [键名, 键值] 组成的数组。对于数组键名就是索引值对于Set键名与键值相同。Map结构的iterator接口默认就是调用entries方法。
- keys() 返回一个遍历器,用来遍历所有的键名。
- values() 返回一个遍历器,用来遍历所有的键值。
这三个方法调用后生成的遍历器,所遍历的都是计算生成的数据结构。
```javascript
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
```
### 类似数组的对象
类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList对象、arguments对象的例子。
```javascript
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
```
对于字符串来说for...of循环还有一个特点就是会正确识别32位UTF-16字符。
```javascript
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
```
### 对象
对于普通的对象for...of结构不能直接使用会报错必须部署了iterator接口后才能使用。但是这样情况下for...in循环依然可以用来遍历键名。
@ -372,26 +483,4 @@ for (e of es6) {
```
上面代码表示for...in循环可以遍历键名for...of循环会报错。
总结一下for...of循环可以使用的范围包括数组、类似数组的对象比如arguments对象、DOM NodeList对象、Set和Map结构、后文的Generator对象以及字符串。下面是for...of循环用于字符串和DOM NodeList对象的例子。
```javascript
// 字符串的例子
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
// DOM NodeList对象的例子
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
```
上面代码表示对于普通的对象for...in循环可以遍历键名for...of循环会报错。