|
1 | 1 | # 数组的扩展
|
2 | 2 |
|
| 3 | +## 扩展运算符 |
| 4 | + |
| 5 | +### 含义 |
| 6 | + |
| 7 | +扩展运算符(spread)是三个点(`...`)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。 |
| 8 | + |
| 9 | +```javascript |
| 10 | +console.log(...[1, 2, 3]) |
| 11 | +// 1 2 3 |
| 12 | + |
| 13 | +console.log(1, ...[2, 3, 4], 5) |
| 14 | +// 1 2 3 4 5 |
| 15 | + |
| 16 | +[...document.querySelectorAll('div')] |
| 17 | +// [<div>, <div>, <div>] |
| 18 | +``` |
| 19 | + |
| 20 | +该运算符主要用于函数调用。 |
| 21 | + |
| 22 | +```javascript |
| 23 | +function push(array, ...items) { |
| 24 | + array.push(...items); |
| 25 | +} |
| 26 | + |
| 27 | +function add(x, y) { |
| 28 | + return x + y; |
| 29 | +} |
| 30 | + |
| 31 | +var numbers = [4, 38]; |
| 32 | +add(...numbers) // 42 |
| 33 | +``` |
| 34 | + |
| 35 | +上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 |
| 36 | + |
| 37 | +扩展运算符与正常的函数参数可以结合使用,非常灵活。 |
| 38 | + |
| 39 | +```javascript |
| 40 | +function f(v, w, x, y, z) { } |
| 41 | +var args = [0, 1]; |
| 42 | +f(-1, ...args, 2, ...[3]); |
| 43 | +``` |
| 44 | + |
| 45 | +扩展运算符后面还可以放置表达式。 |
| 46 | + |
| 47 | +```javascript |
| 48 | +const arr = [ |
| 49 | + ...(x > 0 ? ['a'] : []), |
| 50 | + 'b', |
| 51 | +]; |
| 52 | +``` |
| 53 | + |
| 54 | +如果扩展运算符后面是一个空数组,则不产生任何效果。 |
| 55 | + |
| 56 | +```javascript |
| 57 | +[...[], 1] |
| 58 | +// [1] |
| 59 | +``` |
| 60 | + |
| 61 | +### 替代数组的 apply 方法 |
| 62 | + |
| 63 | +由于扩展运算符可以展开数组,所以不再需要`apply`方法,将数组转为函数的参数了。 |
| 64 | + |
| 65 | +```javascript |
| 66 | +// ES5 的写法 |
| 67 | +function f(x, y, z) { |
| 68 | + // ... |
| 69 | +} |
| 70 | +var args = [0, 1, 2]; |
| 71 | +f.apply(null, args); |
| 72 | + |
| 73 | +// ES6的写法 |
| 74 | +function f(x, y, z) { |
| 75 | + // ... |
| 76 | +} |
| 77 | +var args = [0, 1, 2]; |
| 78 | +f(...args); |
| 79 | +``` |
| 80 | + |
| 81 | +下面是扩展运算符取代`apply`方法的一个实际的例子,应用`Math.max`方法,简化求出一个数组最大元素的写法。 |
| 82 | + |
| 83 | +```javascript |
| 84 | +// ES5 的写法 |
| 85 | +Math.max.apply(null, [14, 3, 77]) |
| 86 | + |
| 87 | +// ES6 的写法 |
| 88 | +Math.max(...[14, 3, 77]) |
| 89 | + |
| 90 | +// 等同于 |
| 91 | +Math.max(14, 3, 77); |
| 92 | +``` |
| 93 | + |
| 94 | +上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用`Math.max`了。 |
| 95 | + |
| 96 | +另一个例子是通过`push`函数,将一个数组添加到另一个数组的尾部。 |
| 97 | + |
| 98 | +```javascript |
| 99 | +// ES5的 写法 |
| 100 | +var arr1 = [0, 1, 2]; |
| 101 | +var arr2 = [3, 4, 5]; |
| 102 | +Array.prototype.push.apply(arr1, arr2); |
| 103 | + |
| 104 | +// ES6 的写法 |
| 105 | +var arr1 = [0, 1, 2]; |
| 106 | +var arr2 = [3, 4, 5]; |
| 107 | +arr1.push(...arr2); |
| 108 | +``` |
| 109 | + |
| 110 | +上面代码的 ES5 写法中,`push`方法的参数不能是数组,所以只好通过`apply`方法变通使用`push`方法。有了扩展运算符,就可以直接将数组传入`push`方法。 |
| 111 | + |
| 112 | +下面是另外一个例子。 |
| 113 | + |
| 114 | +```javascript |
| 115 | +// ES5 |
| 116 | +new (Date.bind.apply(Date, [null, 2015, 1, 1])) |
| 117 | +// ES6 |
| 118 | +new Date(...[2015, 1, 1]); |
| 119 | +``` |
| 120 | + |
| 121 | +### 扩展运算符的应用 |
| 122 | + |
| 123 | +**(1)合并数组** |
| 124 | + |
| 125 | +扩展运算符提供了数组合并的新写法。 |
| 126 | + |
| 127 | +```javascript |
| 128 | +// ES5 |
| 129 | +[1, 2].concat(more) |
| 130 | +// ES6 |
| 131 | +[1, 2, ...more] |
| 132 | + |
| 133 | +var arr1 = ['a', 'b']; |
| 134 | +var arr2 = ['c']; |
| 135 | +var arr3 = ['d', 'e']; |
| 136 | + |
| 137 | +// ES5的合并数组 |
| 138 | +arr1.concat(arr2, arr3); |
| 139 | +// [ 'a', 'b', 'c', 'd', 'e' ] |
| 140 | + |
| 141 | +// ES6的合并数组 |
| 142 | +[...arr1, ...arr2, ...arr3] |
| 143 | +// [ 'a', 'b', 'c', 'd', 'e' ] |
| 144 | +``` |
| 145 | + |
| 146 | +**(2)与解构赋值结合** |
| 147 | + |
| 148 | +扩展运算符可以与解构赋值结合起来,用于生成数组。 |
| 149 | + |
| 150 | +```javascript |
| 151 | +// ES5 |
| 152 | +a = list[0], rest = list.slice(1) |
| 153 | +// ES6 |
| 154 | +[a, ...rest] = list |
| 155 | +``` |
| 156 | + |
| 157 | +下面是另外一些例子。 |
| 158 | + |
| 159 | +```javascript |
| 160 | +const [first, ...rest] = [1, 2, 3, 4, 5]; |
| 161 | +first // 1 |
| 162 | +rest // [2, 3, 4, 5] |
| 163 | + |
| 164 | +const [first, ...rest] = []; |
| 165 | +first // undefined |
| 166 | +rest // [] |
| 167 | + |
| 168 | +const [first, ...rest] = ["foo"]; |
| 169 | +first // "foo" |
| 170 | +rest // [] |
| 171 | +``` |
| 172 | + |
| 173 | +如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 |
| 174 | + |
| 175 | +```javascript |
| 176 | +const [...butLast, last] = [1, 2, 3, 4, 5]; |
| 177 | +// 报错 |
| 178 | + |
| 179 | +const [first, ...middle, last] = [1, 2, 3, 4, 5]; |
| 180 | +// 报错 |
| 181 | +``` |
| 182 | + |
| 183 | +**(3)函数的返回值** |
| 184 | + |
| 185 | +JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。 |
| 186 | + |
| 187 | +```javascript |
| 188 | +var dateFields = readDateFields(database); |
| 189 | +var d = new Date(...dateFields); |
| 190 | +``` |
| 191 | + |
| 192 | +上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数`Date`。 |
| 193 | + |
| 194 | +**(4)字符串** |
| 195 | + |
| 196 | +扩展运算符还可以将字符串转为真正的数组。 |
| 197 | + |
| 198 | +```javascript |
| 199 | +[...'hello'] |
| 200 | +// [ "h", "e", "l", "l", "o" ] |
| 201 | +``` |
| 202 | + |
| 203 | +上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符。 |
| 204 | + |
| 205 | +```javascript |
| 206 | +'x\uD83D\uDE80y'.length // 4 |
| 207 | +[...'x\uD83D\uDE80y'].length // 3 |
| 208 | +``` |
| 209 | + |
| 210 | +上面代码的第一种写法,JavaScript会将32位Unicode字符,识别为2个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。 |
| 211 | + |
| 212 | +```javascript |
| 213 | +function length(str) { |
| 214 | + return [...str].length; |
| 215 | +} |
| 216 | + |
| 217 | +length('x\uD83D\uDE80y') // 3 |
| 218 | +``` |
| 219 | + |
| 220 | +凡是涉及到操作32位 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。 |
| 221 | + |
| 222 | +```javascript |
| 223 | +let str = 'x\uD83D\uDE80y'; |
| 224 | + |
| 225 | +str.split('').reverse().join('') |
| 226 | +// 'y\uDE80\uD83Dx' |
| 227 | + |
| 228 | +[...str].reverse().join('') |
| 229 | +// 'y\uD83D\uDE80x' |
| 230 | +``` |
| 231 | + |
| 232 | +上面代码中,如果不用扩展运算符,字符串的`reverse`操作就不正确。 |
| 233 | + |
| 234 | +**(5)实现了 Iterator 接口的对象** |
| 235 | + |
| 236 | +任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。 |
| 237 | + |
| 238 | +```javascript |
| 239 | +var nodeList = document.querySelectorAll('div'); |
| 240 | +var array = [...nodeList]; |
| 241 | +``` |
| 242 | + |
| 243 | +上面代码中,`querySelectorAll`方法返回的是一个`nodeList`对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于`NodeList`对象实现了 Iterator 。 |
| 244 | + |
| 245 | +对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。 |
| 246 | + |
| 247 | +```javascript |
| 248 | +let arrayLike = { |
| 249 | + '0': 'a', |
| 250 | + '1': 'b', |
| 251 | + '2': 'c', |
| 252 | + length: 3 |
| 253 | +}; |
| 254 | + |
| 255 | +// TypeError: Cannot spread non-iterable object. |
| 256 | +let arr = [...arrayLike]; |
| 257 | +``` |
| 258 | + |
| 259 | +上面代码中,`arrayLike`是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用`Array.from`方法将`arrayLike`转为真正的数组。 |
| 260 | + |
| 261 | +**(6)Map 和 Set 结构,Generator 函数** |
| 262 | + |
| 263 | +扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。 |
| 264 | + |
| 265 | +```javascript |
| 266 | +let map = new Map([ |
| 267 | + [1, 'one'], |
| 268 | + [2, 'two'], |
| 269 | + [3, 'three'], |
| 270 | +]); |
| 271 | + |
| 272 | +let arr = [...map.keys()]; // [1, 2, 3] |
| 273 | +``` |
| 274 | + |
| 275 | +Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。 |
| 276 | + |
| 277 | +```javascript |
| 278 | +var go = function*(){ |
| 279 | + yield 1; |
| 280 | + yield 2; |
| 281 | + yield 3; |
| 282 | +}; |
| 283 | + |
| 284 | +[...go()] // [1, 2, 3] |
| 285 | +``` |
| 286 | + |
| 287 | +上面代码中,变量`go`是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 |
| 288 | + |
| 289 | +如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。 |
| 290 | + |
| 291 | +```javascript |
| 292 | +var obj = {a: 1, b: 2}; |
| 293 | +let arr = [...obj]; // TypeError: Cannot spread non-iterable object |
| 294 | +``` |
| 295 | + |
3 | 296 | ## Array.from()
|
4 | 297 |
|
5 | 298 | `Array.from`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
|
@@ -185,7 +478,7 @@ function ArrayOf(){
|
185 | 478 | }
|
186 | 479 | ```
|
187 | 480 |
|
188 |
| -## 数组实例的copyWithin() |
| 481 | +## 数组实例的 copyWithin() |
189 | 482 |
|
190 | 483 | 数组实例的`copyWithin`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
|
191 | 484 |
|
@@ -228,13 +521,13 @@ var i32a = new Int32Array([1, 2, 3, 4, 5]);
|
228 | 521 | i32a.copyWithin(0, 2);
|
229 | 522 | // Int32Array [3, 4, 5, 4, 5]
|
230 | 523 |
|
231 |
| -// 对于没有部署TypedArray的copyWithin方法的平台 |
| 524 | +// 对于没有部署 TypedArray 的 copyWithin 方法的平台 |
232 | 525 | // 需要采用下面的写法
|
233 | 526 | [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
|
234 | 527 | // Int32Array [4, 2, 3, 4, 5]
|
235 | 528 | ```
|
236 | 529 |
|
237 |
| -## 数组实例的find()和findIndex() |
| 530 | +## 数组实例的 find() 和 findIndex() |
238 | 531 |
|
239 | 532 | 数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。
|
240 | 533 |
|
@@ -298,9 +591,9 @@ new Array(3).fill(7)
|
298 | 591 |
|
299 | 592 | 上面代码表示,`fill`方法从1号位开始,向原数组填充7,到2号位之前结束。
|
300 | 593 |
|
301 |
| -## 数组实例的entries(),keys()和values() |
| 594 | +## 数组实例的 entries(),keys() 和 values() |
302 | 595 |
|
303 |
| -ES6提供三个新的方法——`entries()`,`keys()`和`values()`——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用`for...of`循环进行遍历,唯一的区别是`keys()`是对键名的遍历、`values()`是对键值的遍历,`entries()`是对键值对的遍历。 |
| 596 | +ES6 提供三个新的方法——`entries()`,`keys()`和`values()`——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用`for...of`循环进行遍历,唯一的区别是`keys()`是对键名的遍历、`values()`是对键值的遍历,`entries()`是对键值对的遍历。 |
304 | 597 |
|
305 | 598 | ```javascript
|
306 | 599 | for (let index of ['a', 'b'].keys()) {
|
|
0 commit comments