ES6入门 ___ 数组的扩展

一、扩展运算符

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 方法用于将两类对象转为真正的对数组:

  1. 类似数组的对象 (array-like object)
  2. 可遍历(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

由于空位的处理规则非常不统一,所以建议避免出现空位。。

你可能感兴趣的:(ES6入门 ___ 数组的扩展)