一、扩展运算符
1.1、含义
扩展运算符(spread)是三个点(...),它如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.quertSelectorAll('div')]
// [, ]
该运算符主要用于函数调用。
function push(array, ...items) {
array.push(...items)
}
function add(x, y) {
return x + y
}
var numbers = [4, 38]
add(...numbers) // 42
扩展运算符与正常的函数参数可以结合使用,非常灵活
function f(v, w, x, y, z) {}
var args = [0, 1]
f(-1, ...args, 2, ...[3])
扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b'
]
如果扩展运算符后面是一个空数组,则不产生任何效果
[...[], 1]
// [1]
1.2、替代数组的 apply 方法
由于扩展运算符可以展开数组,所以不再需要使用 apply 方法将数组转为函数的参数。
function f(x, y, z) { // .... }
var args = [0, 1, 2]
// ES5 写法
f.apply(null, args)
// ES6 写法
f(...args)
下面是一个 应用 Math.max 方法简化求出一个数组中的最大元素的例子:
// ES5
Math.max.apply(null, [14, 3, 7])
// ES6
Math.max(...[14, 3, 77])
另一个例子是通过 push 函数将一个数组添加到另一个数组的尾部:
var arr1 = [0, 1, 2]
var arr2 = [3, 4, 5]
// ES5
Array.prototype.push.apply(arr1, arr2)
// ES6
arr1.push(...arr2)
1.3、扩展运算符的应用
合并数组
扩展运算符提供了数组合并的新写法:
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组:
const [first, ...rest] = [1, 2, 3, 4, 5]
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = []
first // undefined
rest // []
如果扩展运算符用于数组数值,则只能将其放在参数的最后一位,否则会报错。
函数的返回值
JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。
vara dateFields = readDateFields(database)
var d = new Date(...dateFields)
字符串
扩展运算符还可以将字符串转为真正的数组。
[...'hello']
// ["h", "e", "l", "l", "o"]
这样写有一个重要的好处:能够正确识别 32 位的 Unicode 字符。因此,要正确返回字符串长度的函数可以像下面这样写。
function length(str) {
return [...str].length
}
length('x\uD83D\uDE80y') // 3
凡是涉及操作32位Unicode 字符的函数都有这个问题。因此,最好都用扩展运算符改写
let str = 'x\uD83D\uDE80y'
str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'
[...str].reverse().join('')
// 'y\uD83D\uDE80x'
实现了 Iterator 接口的对象
任何 Iterator 接口的对象都可以用扩展运算符转为真正的数组
var nodeList = document.querySelectorAll('div')
var array = [...nodeList]
querySelectorAll 方法返回的是一个 nodeList 对象,NodeList 对象实现了 Iterator
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组了。
Map 和 Set 结构、Generator 函数
扩展运算符呢内部调用的是数据结构的 Iterator 接口,因此只要具有Iterator 接口的对象,都可以使用扩展运算符,如Map 结构
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
])
let arr = [...map,keys()]
// [1, 2, 3]
Generator 函数运行后会返回一个遍历器对象,因此也可以使用扩展运算符
var go = function* () {
yield 1;
yield 2;
yield 3;
}
[...go()]
// [1, 2, 3]
对于没有 Iterator 接口的对象,使用扩展运算符将会报错
var obj = {a: 1, b: 2}
let arr = [...obj]
// TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
二、Array.from()
Array.from 方法用于将两类对象转为真正的对数组:
- 类似数组的对象 (array-like object)
- 可遍历(iterable)对象(包括 ES6 新增的数据结构 Set 和 Map)
下面是一个类似数组的镀锡,Array.from 将它转为真正的数组。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
// ES5
var arr1 = [].slice.call(arrayLike)
// ["a", "b", "c"]
// ES6
let arr2 = Array.from(arrayLike)
// ["a", "b", "c"]
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的 arguments 对象。Array.from 都可以将他们转为真正的数组。只要是部署了 Iterator 接口的数据结构,Array.from 都能将其转为数组
如果参数是一个真正的数组,Array.from 会返回一个一模一样的新数组
Array.from([1, 2, 3])
// [1, 2, 3]
值得提醒的是,扩展运算符(...)也可以将某些数据结构转为数组
function foo() {
return [...arguments]
}
// NodeList 对象
[...document.querySelectorAll('div')]
扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署该接口,就无法转换。
Array.from 方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有 length 属性。因此,任何有 length 属性的对象,都可以通过 Array.from 方法转为数组
这种情况下扩展运算符 无法转换
Array.from({length: 3})
// [undefined, undefined, undefined]
对于还没有 部署该方法的浏览器,可以用 Array.prototype。slice 方法代替
const toArray = (() => Array.from ? Array.from : obj => [].slice.call(obj) )()
Array.from 还可以接受第二个参数,作用类似于 数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
下面的例子是取出一组 DOM 节点的文本内容
let spans = document.querySelectorAll('span')
let names = Array.from(spans, s => s.innerText)
下面的例子将数组中布尔值为 false 的成员转为 0
Array.from([1, , , 2, 3], n => n || 0)
// [1, 0, 0, 2, 3]
如果 map 函数里面用到了 this 关键字,还可以传入 Array.from 第三个参数,用来绑定 this
Array.from() 可以将各种值转为真正的数组,并且提供了 map 功能。意味着,只要有一个原始的数据结构,就可以先对它的值进行处理,然后转成规范的数组结构,进而可以使用数量众多的数组方法。
Array.from({length: 2}, () => 'jack')
// ["jack", "jack"]
Array.from()另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode 字符,可以避免 JavaScript 将大于 \uFFFF 的 Unicode 字符算作 2 个字符的bug
function countSymbols(string) {
return Array.from(string).length
}
三、Array.of()
Array.of 方法用于将一组值转换为数组
Array.of(3, 11, 8) // [3, 11, 8]
Array.of(3) // [3]
Array.of(3).length // 1
这个方法的主要目的是弥补数组构造函数 Array() 的不足。因为参数个数的不同会导致 Array() 的行为有差异
Array() // []
Array(3) // [, , ,]
Array(3, 11) // [3, 11]
Array.of 基本上可用来代替 Array() 或 new Array(),并且不存在由于参数不同而导致的“重载”。它的行为非常统一。
四、数组实例的 copyWithin()
数组实例的 copyWithin 方法会在当前数组内部指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法会修改当前数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
他接受 3 个参数:
- target(必选):从该位置开始替换数据
- starat(可选):从该位置开始读取数据,默认为 0、如果为负值,表示倒数
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
上面代码表示,将从3号位置直到数组结束的成员(4 和 5)复制到从 0 号位置开始的位置,结果覆盖率原来的 1 和 2
下面是一些例子:
// -2 相当于 3 号位,-1 相当于 4号 位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
// 将 3号为复制到 0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
五、数组实例的 find() 和 findeIndex()
数组实例的 find 方法用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined。
find 方法的回调函数可以接受 3 个参数:
- 当前的值
- 当前的位置
- 原数组
[1, 5, 10, 15].find((value, index, arr) => value > 9)
// 10
findIndex 方法的用法 与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1
[1, 5, 10, 15].findIndex((value, index, arr) => value > 9)
// 2
这两个方法都可以接受第二个参数,用来绑定回调函数的this 对象。
另外,这两个方法都可以发现 NaN,弥补了数组的 IndexOf 方法的不足(无法发现 NaN.)
六、数组实例的 fill()
fill 方法使用给定值填充一个数组。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(1)
[1, 1, 1]
fill 方法还可以接受第二个 和 第二个参数,用于指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7, 1, 2)
// ["a", 7, "c"]
七、数组实例的 entries()、keys() 和 values()
ES6 提供了 3 个 新方法,用于遍历数组:
entries() —— 是对 键值对 的遍历
keys() —— 是对 键名 的遍历
values() —— 是对 键值 的遍历
他们都返回一个遍历器对象,可用for...of 循环遍历
for(let index of ['a', 'b'].keys()) {
console.log(index)
}
// 0
// 1
for(let elem of ['a', 'b'].values()) {
console.log(elem)
}
// "a"
// "b"
for(let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem)
}
// 0 "a"
// 1 "b"
如果不使用 for...of 循环,可以手动调用遍历器对象的 next 方法进行遍历
let letter = ['a', 'b', 'c']
let entries = letter.entries()
console.log(entries.next().value) // [0, "a"]
console.log(entries.next().value) // [1, "b"]
console.log(entries.next().value) // [2, "c"]
console.log(entries.next().value) // undefined
八、数组实例的 includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
改方法的第二个参数表示搜索的起始下标,默认为 0.如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但是数组长度为 3),则会重置为从 0 开始。
[1, 2, 3].includes(3, 3) // false
[1, 2, 3].includes(3, -1) // true
没有该方法之前,我们通常使用 数组的 indexOf 方法检查是否包含某个值
if (arr.indexOf(el) !=== -1) {
// todo
}
indexOf 方法有两个缺点:
- 不够语义化,表达起来不够直观;
- 其内部使用严格相等运算符(===) 进行判断,会导致对 NaN 的误判
[NaN].indexOf(NaN) // -1
下面的代码用来检查当前环境是否支持改方法,如果不支持,就部署一个简易的替代版本。
const contains = ( () =>
Array.prototype.includes
? (arr, value) => arr.includes(value)
: (arr, value) => arr.some(el => el === value)
)()
contains(['foo', 'bar'], 'baz')
// false
另外,Map 和 Set 数据结构有一个 has 方法,需要注意与 includes 区分。
- Map 结构的 has 方法是用来查找键名的,比如 Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)
- Set 结构的 has 方法是用来查找值的,比如 Set.prototype.has(value)、WeakSet.prototype.has(value)
九、数组的空位
数组的空位指数组的某一个位置没有任何值。比如,Array 构造函数返回的数组都是空位。
Array(3) // [, , ,]
空位 不是 undefined,一个位置的值等于 undefined 依然是有值的。空位是没有任何值的,in 运算符可以说明这一点。
0 in [undefined] // true
0 in [ , , ,] // false
上面的代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值
- forEach()、filter()、every() 和 some() 都会跳过空位。
- map() 会跳过空位,但会保留这个值
- join() 和 toString() 会将空位视为 undefined,而 undefined 和 null 会被处理成空字符串
// forEach 方法
[, 'a'].forEach( (x, i) => console.log(i) ) // 1
// map 方法
[, 'a'].map( x => 1) // [, 1]
// join 方法
[, 'a', undefined, null].join('#') // "#a##"
// toString 方法
[, 'a', undefined, null].toString() // ",a,,"
ES6 则是明确将空位转为 undefined
Array.from 方法沪江数组的空位转为 undefined。这个方法不会忽略空位。
Array.from(['a', , 'b'])
// ["a", undefined, "b"]
扩展运算符(...) 也会讲空位转为 undefined
[... ['a', , 'b']]
// ["a", undefined, "b"]
copyWithin() 会连空位一起复制。
[, 'a', 'b', , ].copyWithin(2, 0) // [, 'a', , 'a']
fill() 会将空位视为正常的数组位置
new Array(3).fill('a') // ["a", "a", "a"]
for...of 循环也会遍历空位。
let arr = [, ,]
for (let i of arr) {
console.log(1)
}
// 1
// 1
entries()、keys()、values()、find()、findIndex() 会将空位处理成 undefined
// entries()
[...[, 'a'].entries()] // [[0, undefined], [1, "a"]]
// keys()
[...[, 'a'].keys()] // [0, 1]
// values()
[...[, 'a'].values()] // [undefined, "a"]
// find()
[, 'a'].find(x => true) // undefined
// findIndex()
[, 'a'].findIndex(x => true) // 0
由于空位的处理规则非常不统一,所以建议避免出现空位。。