数组 创建数组 有几种基本的方式可以创建数组。一种是使用Array 构造函数 ,比如:
let colors = new Array ()let colors = new Array (3 ) let names = new Array ('Greg' )
另一种创建数组的方式是使用数组字面量 (array literal)表示法。数组字面量是在中括号中包含以逗号分隔的元素列表,如下面的例子所示:
let colors = ['red' , 'blue' , 'green' ] let names = [] let values = [1 , 2 ]
Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:from()和 of()。from()用于将类数组结构转换为数组实例 ,而of()用于将一组参数转换为数组实例 。 Array.from()的第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个 length 属性和可索引元素的结构。这种方式可用于很多场合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 console .log(Array .from('Matt' )) const m = new Map ().set(1 , 2 ).set(3 , 4 )const s = new Set ().add(1 ).add(2 ).add(3 ).add(4 )console .log(Array .from(m)) console .log(Array .from(s)) const a1 = [1 , 2 , 3 , 4 ]const a2 = Array .from(a1)console .log(a1) alert(a1 === a2) const iter = { *[Symbol .iterator]() { yield 1 yield 2 yield 3 yield 4 }, }console .log(Array .from(iter)) function getArgsArray ( ) { return Array .from(arguments ) }console .log(getArgsArray(1 , 2 , 3 , 4 )) const arrayLikeObject = { 0 : 1 , 1 : 2 , 2 : 3 , 3 : 4 , length : 4 , }console .log(Array .from(arrayLikeObject))
Array.from()还接收第二个可选的映射函数参数。 这个函数可以直接增强新数组的值,而无须像调用 Array.from().map()那样先创建一个中间数组。 还可以接收第三个可选参数,用于指定映射函 数中 this 的值。但这个重写的 this 值在箭头函数中不适用。
const a1 = [1 , 2 , 3 , 4 ]const a2 = Array .from(a1, (x ) => x ** 2 )const a3 = Array .from( a1, function (x ) { return x ** this .exponent }, { exponent : 2 } )console .log(a2) console .log(a3)
Array.of()可以把一组参数转换为数组。这个方法用于替代在 ES6 之前常用的 Array.prototype.slice.call(arguments),一种异常笨拙的将 arguments 对象转换为数组的写法:
console .log(Array .of(1 , 2 , 3 , 4 )) console .log(Array .of(undefined ))
数组空位 使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)。ECMAScript 会将逗号之间相应索引位置的值当成空位,ES6 规范重新定义了该如何处理这些空位。 可以像下面这样创建一个空位数组:
const options = [, , , , ,] console .log(options.length) console .log(options)
ES6 新增的方法和迭代器与早期 ECMAScript 版本中存在的方法行为不同。ES6 新增方法普遍将这些空位当成存在的元素,只不过值为 undefined:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const options = [1 , , , , 5 ]for (const option of options) { console .log(option === undefined ) }const a = Array .from([, , ,]) for (const val of a) { console .log(val === undefined ) }console .log(Array .of(...[, , ,])) for (const [index, value] of options.entries()) { console .log(value) }
ES6 之前的方法则会忽略这个空位,但具体的行为也会因方法而异:
const options = [1 , , , , 5 ]console .log(options.map(() => 6 )) console .log(options.join('-' ))
注意 由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要 空位,则可以显式地用 undefined 值代替。
数组索引 要取得或设置数组的值,需要使用中括号并提供相应值的数字索引,如下所示:
let colors = ['red' , 'blue' , 'green' ] alert(colors[0 ]) colors[2 ] = 'black' colors[3 ] = 'brown'
数组 length 属性的独特之处在于,它不是只读的。通过修改 length 属性,可以从数组末尾删除或添加元素。
let colors = ['red' , 'blue' , 'green' ] colors.length = 2 alert(colors[2 ])
这里,数组 colors 一开始有 3 个值。将 length 设置为 2,就删除了最后一个(位置 2 的)值, 因此 colors[2]就没有值了。如果将 length 设置为大于数组元素数的值,则新添加的元素都将以 undefined 填充
let colors = ['red' , 'blue' , 'green' ] colors.length = 4 alert(colors[3 ])
这里将数组 colors 的 length 设置为 4,虽然数组只包含 3 个元素。位置 3 在数组中不存在,因此访问其值会返回特殊值 undefined。 使用 length 属性可以方便地向数组末尾添加元素
let colors = ['red' , 'blue' , 'green' ] colors[colors.length] = 'black' colors[colors.length] = 'brown' colors[99 ] = 'black' alert(colors.length)
这里,colors 数组有一个值被插入到位置 99,结果新 length 就变成了 100(99 + 1)。这中间的所有元素,即位置 3~98,实际上并不存在,因此在访问时会返回 undefined。
检测数组 在只有一个网页(因而只有一个全局作用域)的情况下,使用 instanceof 操作符就足矣:
if (value instanceof Array ){
使用 instanceof 的问题是假定只有一个全局执行上下文。如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,因此就会有两个不同版本的 Array 构造函数。如果要把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在第二个框架内本地创建的数组。 为解决这个问题,ECMAScript 提供了 Array.isArray()方法。这个方法的目的就是确定一个值是否为数组,而不用管它是在哪个全局执行上下文中创建的。
if (Array .isArray(value)){
迭代器方法 ES6 中,Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、 values() 和 **entries()**。 keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回索引/值对的迭代器
const a = ['foo' , 'bar' , 'baz' , 'qux' ]for (const [idx, element] of a.entries()) { console .log(idx) console .log(element) }
使用 ES6 的解构可以非常容易地在循环中拆分键/值对:
const a = ['foo' , 'bar' , 'baz' , 'qux' ]for (const [idx, element] of a.entries()) { console .log(idx) console .log(element) }
复制和填充方法 ES6 新增了两个方法:**批量复制方法 copyWithin(),以及 填充数组方法 fill()**。这两个方法的函数签名类似,都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方法不会改变数组的大小。 使用 fill()方法可以向一个已有的数组中插入全部或部分相同的值。开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算。也可以将负索引想象成数组长度加上它得到的一个正索引:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const zeroes = [0 , 0 , 0 , 0 , 0 ] zeroes.fill(5 )console .log(zeroes) zeroes.fill(0 ) zeroes.fill(6 , 3 )console .log(zeroes) zeroes.fill(0 ) zeroes.fill(7 , 1 , 3 )console .log(zeroes) zeroes.fill(0 ) zeroes.fill(8 , -4 , -1 )console .log(zeroes)
fill()静默忽略超出数组边界、零长度及方向相反的索引范围:
const zeroes = [0 , 0 , 0 , 0 , 0 ] zeroes.fill(1 , -10 , -6 )console .log(zeroes) zeroes.fill(1 , 10 , 15 )console .log(zeroes) zeroes.fill(2 , 4 , 2 )console .log(zeroes) zeroes.fill(4 , 3 , 10 )console .log(zeroes)
copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指 定索引开始的位置。开始索引和结束索引则与 fill()使用同样的计算方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let ints, reset = () => (ints = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]) reset() ints.copyWithin(5 )console .log(ints) reset() ints.copyWithin(0 , 5 )console .log(ints) reset() ints.copyWithin(4 , 0 , 3 ) alert(ints) reset() ints.copyWithin(2 , 0 , 6 ) alert(ints) reset() ints.copyWithin(-4 , -7 , -3 ) alert(ints)
copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let ints, reset = () => (ints = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]) reset() ints.copyWithin(1 , -15 , -12 ) alert(ints) reset() ints.copyWithin(1 , 12 , 15 ) alert(ints) reset() ints.copyWithin(2 , 4 , 2 ) alert(ints) reset() ints.copyWithin(4 , 7 , 10 ) alert(ints)
转换方法 所有对象都有 toLocaleString()、toString()和 valueOf()方法。 其中,valueOf() 返回的还是数组本身。而 toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。也就是说,对数组的每个值都会调用其 toString()方法,以得到最终的字符串。
let colors = ['red' , 'blue' , 'green' ] alert(colors.toString()) alert(colors.valueOf()) alert(colors)
继承的方法 toLocaleString()以及 toString()都返回数组值的逗号分隔的字符串。如果想使用不同的分隔符,则可以使用 join()方法。join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。
let colors = ['red' , 'green' , 'blue' ] alert(colors.join(',' )) alert(colors.join('||' ))
注意 如果数组中某一项是 null 或 undefined,则在 join()、toLocaleString()、 toString()和 valueOf()返回的结果中会以空字符串表示。
栈方法 数组对象可以像栈一样, 也就是一种限制插入和删除项的数据结构。栈是一种后进先出(LIFO,Last-In-First-Out)的结构,也就是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个 地方发生,即栈顶。ECMAScript 数组提供了 push()和 pop()方法,以实现类似栈的行为。push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。 pop()方法则用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项。
let colors = new Array () let count = colors.push('red' , 'green' ) alert(count) count = colors.push('black' ) alert(count) let item = colors.pop() alert(item) alert(colors.length)
队列方法 就像栈是以 LIFO 形式限制访问的数据结构一样,队列以先进先出(FIFO,First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。因为有了在数据末尾添加数据的 push() 方法,所以要模拟队列就差一个从数组开头取得数据的方法了。这个数组方法叫 shift(),它会删除数组的第一项并返回它,然后数组长度减 1。 使用 shift()和 push(),可以把数组当成队列来使用:
let colors = new Array () let count = colors.push('red' , 'green' ) alert(count) count = colors.push('black' ) alert(count) let item = colors.shift() alert(item) alert(colors.length)
ECMAScript 也为数组提供了 unshift()方法。unshift()就是执行跟 shift()相反的 操作:在数组开头添加任意多个值,然后返回新的数组长度 。 通过使用 unshift()和 pop(),可以在 相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据,
let colors = new Array () let count = colors.unshift('red' , 'green' ) alert(count) count = colors.unshift('black' ) alert(count) let item = colors.pop() alert(item) alert(colors.length)
排序方法 数组有两个方法可以用来对元素重新排序:reverse()和 sort()。reverse()方法就是将数组元素反向排列。
let values = [1 , 2 , 3 , 4 , 5 ] values.reverse() alert(values)
sort()会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。 为此,sort()会在每一项上调用 String()转型函数,然后比较字符串来决定顺序。即使**数组的元素都是数值, 也会先把数组转换为字符串再比较、排序。 **
let values = [0 , 1 , 5 , 10 , 15 ] values.sort() alert(values)
为此,sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。 比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相等,就返回 0;如果第一个参数应该排在第二个参数后面,就返回正值。 下面是使用简单比较函数的一个例子
function compare (value1, value2 ) { if (value1 < value2) { return -1 } else if (value1 > value2) { return 1 } else { return 0 } }
在给 sort()方法传入比较函数后,数组中的数值在排序后保持了正确的顺序。当然,比较函数也可以产生降序效果,只要把返回值交换一下即可:
function compare (value1, value2 ) { if (value1 < value2) { return 1 } else if (value1 > value2) { return -1 } else { return 0 } }let values = [0 , 1 , 5 , 10 , 15 ] values.sort(compare)console .log(values)
箭头函数简写
let values = [0 , 1 , 5 , 10 , 15 ] values.sort((a, b ) => (a < b ? 1 : a > b ? -1 : 0 ))console .log(values)
要比较数字而非字符串,比较函数可以简单的以 a 减 b,如下的函数将会将数组升序排列
function compareNumbers (a, b ) { return a - b }
sort 方法可以使用 函数表达式 方便地书写:
var numbers = [4 , 2 , 5 , 1 , 3 ] numbers.sort(function (a, b ) { return a - b })console .log(numbers)var numbers = [4 , 2 , 5 , 1 , 3 ] numbers.sort((a, b ) => a - b)console .log(numbers)
对象可以按照某个属性排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var items = [ { name : 'Edward' , value : 21 }, { name : 'Sharpe' , value : 37 }, { name : 'And' , value : 45 }, { name : 'The' , value : -12 }, { name : 'Magnetic' }, { name : 'Zeros' , value : 37 }, ] items.sort(function (a, b ) { return a.value - b.value }) items.sort(function (a, b ) { var nameA = a.name.toUpperCase() var nameB = b.name.toUpperCase() if (nameA < nameB) { return -1 } if (nameA > nameB) { return 1 } return 0 })
注意 reverse()和 sort()都返回调用它们的数组的引用。
如果数组的元素是数值,或者是其 valueOf()方法返回数值的对象(如 Date 对象),这个比较函数还可以写得更简单,因为这时可以直接用第二个值减去第一个值
function compare (value1, value2 ) { return value2 - value1 }
操作方法 对于数组中的元素,我们有很多操作方法。
concat()方法 concat()方法可以在现有数组全部元素基础上创建一个新数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组。如果传入一个或多个数组,则 concat()会把这些数组的每一项都添加到结果数组。 如果参数不是数组,则直接把它们添加到结果数组末尾。
let colors = ['red' , 'green' , 'blue' ]let colors2 = colors.concat('yellow' , ['black' , 'brown' ])console .log(colors) console .log(colors2)
打平数组 打平数组参数的行为可以重写,方法是在参数数组上指定一个特殊的符号:Symbol.isConcatSpreadable 。这个符号能够阻止 concat()打平参数数组。相反,把这个值设置为 true 可以强制打平类数组对象:
let colors = ['red' , 'green' , 'blue' ]let newColors = ['black' , 'brown' ]let moreNewColors = { [Symbol .isConcatSpreadable]: true , length : 2 , 0 : 'pink' , 1 : 'cyan' , } newColors[Symbol .isConcatSpreadable] = false let colors2 = colors.concat('yellow' , newColors)let colors3 = colors.concat(moreNewColors)console .log(colors) console .log(colors2) console .log(colors3)
slice()方法 slice()用于创建一个包含原有数组中一个或多个元素的新数组 。slice()方法可以接收一个或两个参数:返回元素的开始索引和结束索引 。 如果只有一个参数,则 slice()会返回该索引到数组末尾的所有元素。 如果有两个参数,则 slice()返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。 记住,这个操作不影响原始数组。
let colors = ['red' , 'green' , 'blue' , 'yellow' , 'purple' ]let colors2 = colors.slice(1 )let colors3 = colors.slice(1 , 4 ) alert(colors2) alert(colors3)
splice()方法 splice()的主要目的是在数组中间插入元素,但有 3 种不同的方式使用这个方法。
删除 。需要给 splice()传2 个参数:要删除的第一个元素的位置和要删除的元素数量 。可以从数组中删除任意多个元素,比如 splice(0, 2)会删除前两个元素。
插入 。需要给 splice()传3 个参数:开始位置、0(要删除的元素数量)和要插入的元素 ,可以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数,乃至任意多个要插入的元素。比如,splice(2, 0, “red”, “green”)会从数组位置 2 开始插入字符串 “red”和”green”。
替换 。splice()在删除元素的同时可以在指定位置插入新元素,同样要传入 3 个参数:开始位置、要删除元素的数量和要插入的任意多个元素 。要插入的元素数量不一定跟删除的元素数量一致。比如,splice(2, 1, “red”, “green”)会在位置 2 删除一个元素,然后从该位置开始向数组中插入”red”和”green”。
splice()方法始终返回这样一个数组,它包含从数组中被删除的元素(如果没有删除元素,则返回空数组)。以下示例展示了上述 3 种使用方式。
let colors = ['red' , 'green' , 'blue' ]let removed = colors.splice(0 , 1 ) alert(colors) alert(removed) removed = colors.splice(1 , 0 , 'yellow' , 'orange' ) alert(colors) alert(removed) removed = colors.splice(1 , 1 , 'red' , 'purple' ) alert(colors) alert(removed)
搜索和位置方法 ECMAScript 提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索。
严格相等 ECMAScript 提供了 3 个严格相等的搜索方法:indexOf()、lastIndexOf()和 includes()。其中,前两个方法在所有版本中都可用,而第三个方法是 ECMAScript 7 新增的。 这些方法都接收两个参数:要查找的元素和一个可选的起始搜索位置 。indexOf()和 includes()方法从数组前头(第一项) 开始向后搜索,而 lastIndexOf()从数组末尾(最后一项)开始向前搜索。 indexOf()和 lastIndexOf()都返回要查找的元素在数组中的位置,如果没找到则返回-1。 includes()返回布尔值,表示是否至少找到一个与指定元素匹配的项。 在比较第一个参数跟数组每一项时,会使用全等(===)比较,也就是说两项必须严格相等。
let numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ] alert(numbers.indexOf(4 )) alert(numbers.lastIndexOf(4 )) alert(numbers.includes(4 )) alert(numbers.indexOf(4 , 4 )) alert(numbers.lastIndexOf(4 , 4 )) alert(numbers.includes(4 , 7 )) let person = { name : 'Nicholas' }let people = [{ name : 'Nicholas' }]let morePeople = [person] alert(people.indexOf(person)) alert(morePeople.indexOf(person)) alert(people.includes(person)) alert(morePeople.includes(person))
断言函数 ECMAScript 也允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。断言函数的返回值决定了相应索引的元素是否被认为匹配。 断言函数接收3 个参数:元素、索引和数组本身 。其中元素是数组中当前搜索的元素,索引是当前元素的索引,而数组就是正在搜索的数组。断言函数返回真值,表示是否匹配 find()和 findIndex()方法使用了断言函数。这两个方法都从数组的最小索引开始。find()返回第一个匹配的元素 ,findIndex()返回第一个匹配元素的索引 。这两个方法也都接收第二个可选的参数, 用于指定断言函数内部 this 的值。
const people = [ { name : 'Matt' , age : 27 , }, { name : 'Nicholas' , age : 29 , }, ]console .log(people.find((element, index, array ) => element.age < 28 )) console .log(people.findIndex((element, index, array ) => element.age < 28 ))
找到匹配项后,这两个方法都不再继续搜索。
const evens = [2 , 4 , 6 ] evens.find((element, index, array ) => { console .log(element) console .log(index) console .log(array) return element === 4 })
迭代方法 ECMAScript 为数组定义了 5 个迭代方法。 每个方法接收两个参数:以每一项为参数运行的函数, 以及可选的作为函数运行上下文的作用域对象(影响函数中 this 的值)。 传给每个方法的函数接收 3 个参数:数组元素、元素索引和数组本身。
every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
forEach():对数组每一项都运行传入的函数,没有返回值。
map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。
这些方法都不改变调用它们的数组。 every()和 some()是最相似的,都是从数组中搜索符合某个条件的元素。对 every() 来说,传入的函数必须对每一项都返回 true,它才会返回 true;否则,它就返回 false。 而对 some() 来说,只要有一项让传入的函数返回 true,它就会返回 true。
let numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ]let everyResult = numbers.every((item, index, array ) => item > 2 ) alert(everyResult) let someResult = numbers.some((item, index, array ) => item > 2 ) alert(someResult)
filter()方法,这个方法基于给定的函数来决定某一项是否应该包含在它返回的数组中。比如,要返回一个所有数值都大于 2 的数组,可以使用如下代码:
let numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ]let filterResult = numbers.filter((item, index, array ) => item > 2 ) alert(filterResult)
map()方法也会返回一个数组。这个数组的每一项都是对原始数组中同样位置的元素运行传入函数而返回的结果。例如,可以将一个数组中的每一项都乘以 2,并返回包含所有结果的数组,如下所示
let numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ]let mapResult = numbers.map((item, index, array ) => item * 2 ) alert(mapResult)
forEach()方法。这个方法只会对每一项运行传入的函数,没有返回值。本质上,forEach()方法相当于使用 for 循环遍历数组。
let numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ] numbers.forEach((item, index, array ) => { })
归并方法 ECMAScript 为数组提供了两个归并方法:**reduce()**和 reduceRight()。这两个方法都会迭代数组的所有项,并在此基础上构建一个最终返回值。reduce()方法从数组第一项开始遍历到最后一项。 而 reduceRight()从最后一项开始遍历至第一项。 这两个方法都接收两个参数:对每一项都会运行的归并函数,以及可选的以之为归并起点的初始值。 传给 reduce()和 reduceRight()的函数接收 4 个参数: 上一个归并值、当前项、当前项的索引和数组本身 。这个函数返回的任何值都会作为下一次调用同一个函数的第一个参数。如果没有给这两个方法传入可选的第二个参数(作为归并起点值),则第一次迭代将从数组的第二项开始,因此传给归并函数的第一个参数是数组的第一项,第二个参数是数组的第二项。 可以使用 reduce()函数执行累加数组中所有数值的操作,比如:
let values = [1 , 2 , 3 , 4 , 5 ]let sum = values.reduce((prev, cur, index, array ) => prev + cur) alert(sum)
第一次执行归并函数时,prev 是 1,cur 是 2。第二次执行时,prev 是 3(1 + 2),cur 是 3(数组第三项)。如此递进,直到把所有项都遍历一次,最后返回归并结果。 reduceRight()方法与之类似,只是方向相反。
let values = [1 , 2 , 3 , 4 , 5 ]let sum = values.reduceRight(function (prev, cur, index, array ) { return prev + cur }) alert(sum)
究竟是使用 reduce()还是 reduceRight(),只取决于遍历数组元素的方向。除此之外,这两个方法没什么区别。