|
| 1 | +#### 什么是迭代器 |
| 2 | + |
| 3 | +迭代器是一种特殊的对象,它具有一些为迭代过程设计的接口。迭代器在结构上具有如下特征: |
| 4 | + |
| 5 | +1. 有一个`next`方法,每次调用都会返回一个`结果对象` |
| 6 | +2. 结果对象有两个属性:`value`和`done`。`value`表示下一个将要返回的值,而`done`是一个布尔状态,表达当前迭代是否已经结束。如果迭代还未完成,则`done`的值为`false`, 反之则为`true` |
| 7 | + |
| 8 | +#### 什么是可迭代对象 |
| 9 | + |
| 10 | +具有`Symbol.iterator`属性的对象,即为可迭代对象。在 ES6 中,所有`集合对象`,包括`数组`,`字符串`,`Set集合`,`Map 集合`。 |
| 11 | + |
| 12 | +既然说到了`Symbol.iterator`属性,那它是什么呢?其实它是一个特殊的函数。我们可以直接来使用这个属性来进行迭代: |
| 13 | + |
| 14 | + |
| 15 | +```javascript |
| 16 | +let values = [1, 2, 3]; |
| 17 | +let iterator = values[Symbol.iterator](); |
| 18 | + |
| 19 | +console.log(iterator.next()) // {value: 1, done: false} |
| 20 | +console.log(iterator.next()) // {value: 2, done: false} |
| 21 | +console.log(iterator.next()) // {value: 3, done: false} |
| 22 | +console.log(iterator.next()) // {value: undefined, done: true} |
| 23 | +console.log(iterator.next()) // {value: undefined, done: true} |
| 24 | +``` |
| 25 | + |
| 26 | +可以看到,`Symbol.iterator`其实就是一个函数,它的返回值是一个特殊的对象,这个对象有两个属性:`value`和`done`。当迭代完成时,`done`的值为`true`,而其值为`undefined`。迭代完成后可以继续调用,没有次数的限制,但其返回的结果对象为`{value: undefined, done: true}`。 |
| 27 | + |
| 28 | +#### 如何模拟实现一个迭代器 |
| 29 | + |
| 30 | +首先`Symbol.iterator`是一个函数,它应该接收一个可迭代对象。因此我们可以创建一个函数: |
| 31 | + |
| 32 | +```javascript |
| 33 | +function createIterator(items){} |
| 34 | +``` |
| 35 | + |
| 36 | +其次该函数调用的结果会返回一个拥有`next`方法的函数: |
| 37 | + |
| 38 | +```javascript |
| 39 | +function createIterator(items){ |
| 40 | + return { |
| 41 | + next: function(){} |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +而调用该方法会返回一个包含了`value`和`done`属性的结果对象。且该结果对象的`value`是`迭代`的,而`done`是表示当前迭代是否完成。因此我们需要一个能够记录当前迭代位置的指针,并且每一次的调用都去移动该指针。 |
| 47 | + |
| 48 | +```javascript |
| 49 | +function createIterator(items){ |
| 50 | + let i = 0; |
| 51 | + return { |
| 52 | + next: function(){ |
| 53 | + return i < items.length ? |
| 54 | + {value: items[i++], done: false} : |
| 55 | + {value: undefined, done: true}; |
| 56 | + } |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +我们来测试一下该函数: |
| 62 | + |
| 63 | +```javascript |
| 64 | +let iterator = createIterator([1,2,3]); |
| 65 | +console.log(iterator.next()) // {value: 1, done: false} |
| 66 | +console.log(iterator.next()) // {value: 2, done: false} |
| 67 | +console.log(iterator.next()) // {value: 3, done: false} |
| 68 | +console.log(iterator.next()) // {value: undefined, done: true} |
| 69 | +console.log(iterator.next()) // {value: undefined, done: true} |
| 70 | +``` |
| 71 | + |
| 72 | +#### 判断对象是否为可迭代对象 |
| 73 | + |
| 74 | +既然可迭代对象是有`Symbol.iterator`属性的,那么我们便可以根据该属性来判断对象是否为可迭代对象: |
| 75 | + |
| 76 | +```javascript |
| 77 | +function isIterable(object){ |
| 78 | + return typeof object[Symbol.iterator] === 'function' |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +#### 内置迭代器 |
| 83 | + |
| 84 | +ES6 为集合对象提供了内置迭代器,在大多数情况下我们无须自己手动实现。 |
| 85 | + |
| 86 | +`entries()`, `values()`, `keys()`都会返回一个迭代器,但是它们的表现不同。但要注意虽然字符串也是可迭代对象,但是它没有这三个内置的迭代器方法,不过它可以使用`for...of`进行迭代。 |
| 87 | + |
| 88 | +- `entries()`,值为键值对的组合 |
| 89 | +- `values()`,值为集合的值 |
| 90 | +- `keys()`,值为集合的键名 |
| 91 | + |
| 92 | +我们可以使用`for...of`来访问迭代器。 |
| 93 | + |
| 94 | +#### entries迭代器 |
| 95 | + |
| 96 | +`entries`会返回一个数组,数组中包含两个元素,分别表示集合中每个元素的键和值。 |
| 97 | + |
| 98 | +由于集合对象的不同,返回值也有所不同。 |
| 99 | + |
| 100 | +```javascript |
| 101 | +let map = new Map(); |
| 102 | +map.set('address', 'BeiJing'); |
| 103 | +map.set('phone', '110'); |
| 104 | + |
| 105 | +for(let m of map.entries()){ |
| 106 | + console.log(m) |
| 107 | +} |
| 108 | + |
| 109 | + |
| 110 | +//['address', 'BeiJing'] |
| 111 | +//['phone', '110'] |
| 112 | + |
| 113 | +``` |
| 114 | + |
| 115 | +返回结果中第一个元素为键名,第二个元素为值。 |
| 116 | + |
| 117 | +```javascript |
| 118 | +let set = new Set(['a', 'b', 'c']); |
| 119 | +for(let s of set.entries()){ |
| 120 | + console.log(s); |
| 121 | +} |
| 122 | + |
| 123 | +//['a', 'a'] |
| 124 | +//['b', 'b'] |
| 125 | +//['c', 'c'] |
| 126 | +``` |
| 127 | + |
| 128 | +Set 集合的返回结果中,第一个元素和第二个元素都是值,即将值作为了键。 |
| 129 | + |
| 130 | +```javascript |
| 131 | +let arr = ['a', 'b', 'c']; |
| 132 | +for(let item of arr.entries()){ |
| 133 | + console.log(item) |
| 134 | +} |
| 135 | + |
| 136 | +//[0, 'a'] |
| 137 | +//[1, 'b'] |
| 138 | +//[2, 'c'] |
| 139 | +``` |
| 140 | +数组集合的返回结果中,第一个元素为数字索引,第二个值为集合的值。 |
| 141 | + |
| 142 | +```javascript |
| 143 | +let str = 'abc'; |
| 144 | +for(let s of str.entries()){ |
| 145 | + console.log(s); |
| 146 | +} |
| 147 | +// TypeError: str.entries is not a function or its return value is not iterable |
| 148 | +``` |
| 149 | +即字符串是没有entries迭代器方法的。 |
| 150 | + |
| 151 | +而`values`与`keys`迭代器则分别是返回值和键。 |
| 152 | + |
| 153 | +#### 不同集合类型的默认迭代器 |
| 154 | + |
| 155 | +如果我们直接使用`for...of`而不指定迭代器时,不同类型的集合会默认调用不同的迭代器。`Map`的默认迭代器是`entries()`,也很容易理解,因为对于`Map`来说,键和值是映射关系。而对于`Set`和`数组`集合来说,默认迭代器是`values`。 |
| 156 | + |
| 157 | +对于`Map` |
| 158 | + |
| 159 | +```javascript |
| 160 | +let map = new Map(); |
| 161 | +map.set('address', 'BeiJing'); |
| 162 | +map.set('phone', '110'); |
| 163 | + |
| 164 | +for(let m of map){ |
| 165 | + console.log(m) |
| 166 | +} |
| 167 | + |
| 168 | +//['address', 'BeiJing'] |
| 169 | +//['phone', '110'] |
| 170 | +``` |
| 171 | + |
| 172 | +对于`Set` |
| 173 | + |
| 174 | +```javascript |
| 175 | +let set = new Set(['a', 'b', 'c']); |
| 176 | +for(let s of set){ |
| 177 | + console.log(s); |
| 178 | +} |
| 179 | + |
| 180 | +//a |
| 181 | +//b |
| 182 | +//c |
| 183 | +``` |
| 184 | + |
| 185 | +对于`数组` |
| 186 | +```javascript |
| 187 | +let arr = ['a', 'b', 'c']; |
| 188 | +for(let item of arr){ |
| 189 | + console.log(item) |
| 190 | +} |
| 191 | + |
| 192 | +//a |
| 193 | +//b |
| 194 | +//c |
| 195 | +``` |
| 196 | + |
| 197 | +迭代器的更多能力应该和生成器一起使用才能够体现出来,我们将在下一篇探讨迭代器与生成器的配合。 |
| 198 | + |
0 commit comments