1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00
es6tutorial/docs/iterator.md
2015-02-10 16:36:29 +08:00

398 lines
9.8 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方法返回值的结构value和done两个属性。
### Iterator接口的部署
具有Iterator接口的对象都能被for...of循环遍历见后文的介绍。所谓Iterator接口就是指它会返回一个遍历器对象该对象具备next方法每次调用该方法会依次返回一个具有上节提到的value和done两个属性的新对象指向原对象的一个成员。
在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`,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。
下面是一个例子。
```javascript
function O(value){
this.value = value;
this.next = null;
}
O.prototype[Symbol.iterator] = function(){
var iterator = {
next: next
};
var current = this;
function next(){
if (current){
var value = current.value;
var done = current == null;
current = current.next;
return {
done: done,
value: value
}
} else {
return {
done: true
}
}
}
return iterator;
}
var one = new O(1);
var two = new O(2);
var three = new O(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i)
}
// 1
// 2
// 3
```
上面代码首先在构造函数的原型链上部署Symbol.iterator方法调用该方法会返回遍历器对象iterator调用该对象的next方法在返回一个值的同时自动将内部指针移到下一个实例。
下面是另一个为对象添加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 };
}
}
};
}
};
```
对于类似数组的对象部署Iterator接口有一个简便方法就是`Symbol.iterator`方法直接引用数值的Iterator接口。
```javascript
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
```
如果`Symbol.iterator`方法返回的不是遍历器,解释引擎将会报错。
```javascript
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: [] is not a function
```
上面代码中变量obj的@@iterator方法返回的不是遍历器,因此报错。
### 原生具备iterator接口的数据结构
《数组的扩展》一章中提到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。
### Iterator接口与Generator函数
部署`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
```
## 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");
}
```