1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-25 11:12:21 +00:00
es6tutorial/docs/iterator.md
2014-12-28 23:34:33 +08:00

326 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Iterator和for...of循环
## Iterator遍历器
遍历器Iterator是一种接口规格任何对象只要部署这个接口就可以完成遍历操作。它的作用有两个一是为各种数据结构提供一个统一的、简便的接口二是使得对象的属性能够按某种次序排列。在ES6中遍历操作特指for...of循环即Iterator接口主要供for...of循环使用。
遍历器提供了一个指针指向当前对象的某个属性使用next方法就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中value属性是当前遍历位置的值done属性是一个布尔值表示遍历是否结束。下面是一个模拟next方法返回值的例子。
```javascript
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返回值的构造。
下面是一个无限运行的遍历器例子。
```javascript
function idMaker(){
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
var it = idMaker();
it.next().value // '0'
it.next().value // '1'
it.next().value // '2'
// ...
```
上面的例子只是为了说明next方法返回值的结构。Iterator接口返回的遍历器原生具备next方法不用自己部署。所以真正需要部署的是Iterator接口让其返回一个遍历器。在ES6中有三类数据结构原生具备Iterator接口数组、类似数组的对象、Set和Map结构。除此之外其他数据结构主要是对象的Iterator接口都需要自己部署。
下面就是如何部署Iterator接口。一个对象如果要有Iterator接口必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为`Symbol.iterator`的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。
```javascript
class MySpecialTree {
// ...
[Symbol.iterator]() {
// ...
return theIterator;
}
}
```
上面代码是一个类部署Iterator接口的写法。`Symbol.iterator`是一个表达式返回Symbol对象的iterator属性这是一个预定义好的、类型为Symbol的特殊值所以要放在方括号内请参考Symbol一节。这里要注意@@iterator的键名是`Symbol.iterator`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。
下面是为对象添加Iterator接口的例子。
```javascript
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
```
《数组的扩展》一章中提到ES6对数组提供entries()、keys()和values()三个方法,就是返回三个遍历器。
```javascript
var arr = [1, 5, 7];
var arrEntries = arr.entries();
arrEntries.toString()
// "[object Array Iterator]"
arrEntries === arrEntries[Symbol.iterator]()
// true
```
上面代码中entries方法返回的是一个遍历器iterator本质上就是调用了`Symbol.iterator`方法。
字符串是一个类似数组的对象因此也原生具有Iterator接口。
```javascript
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
```
上面代码中,调用`Symbol.iterator`方法返回一个遍历器在这个遍历器上可以调用next方法实现对于字符串的遍历。
可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。
```javascript
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...str] // ["bye"]
str // "hi"
```
上面代码中字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(...返回的值变成了bye而字符串本身还是hi。
部署`Symbol.iterator`方法的最简单实现还是使用下一节要提到的Generator函数。
```javascript
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
// 或者采用下面的简洁写法
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// hello
// world
```
如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。
```javascript
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
```
上面代码中变量obj的@@iterator方法返回的不是遍历器,因此报错。
## for...of循环
ES6中一个对象只要部署了@@iterator方法就被视为具有iterator接口就可以用for...of循环遍历它的值。也就是说for...of循环内部调用是原对象的`Symbol.iterator`方法。
数组原生具备iterator接口。
```javascript
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
```
上面代码说明for...of循环可以代替数组实例的forEach方法。
```javascript
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
```
JavaScript原有的for...in循环只能获得对象的键名不能直接获取键值。ES6提供for...of循环允许遍历获得键值。
```javascript
var arr = ["a", "b", "c", "d"];
for (a in arr) {
console.log(a); // 0 1 2 3
}
for (a of arr) {
console.log(a); // a b c d
}
```
上面代码表明for...in循环读取键名for...of循环读取键值。如果要通过for...of循环获取数组的索引可以借助数组实例的entries方法和keys方法参见《数组的扩展》章节。
Set和Map结构也原生具有Iterator接口可以直接使用for...of循环。
```javascript
var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
```
上面代码演示了如何遍历Set结构和Map结构后者是同时遍历键名和键值。
对于普通的对象for...of结构不能直接使用会报错必须部署了iterator接口后才能使用。但是这样情况下for...in循环依然可以用来遍历键名。
```javascript
var es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (e of es6) {
console.log(e);
}
// TypeError: es6 is not iterable
```
上面代码表示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");
}
```