前言

在我们开发的过程中,数组经常被用到,而且数组里面的方法很多,不容易全部记住。所以一直想找个机会做一个简单的总结,方便自己理解,恰逢马上要过年了,手上的活不是很多,所以在这个做一个简单的总结。


截止ES7规范,数组共包含33个标准的API方法和一个非标准的API方法,使用场景和使用方案纷繁复杂,其中有不少浅坑、深坑、甚至神坑。下面将从Array构造器及ES6新特性开始,逐步帮助你掌握数组。

创建数组

通常我们创建数组推荐使用对象字面量的方式,在初始化数组的时候非常的方便,但是对象字面量创建数组也有乏力的时候,比如你看下面的两段代码:

1
2
3
4
5
6
7
// 使用Array构造器
let a = Array(10); // [undefined * 10]

// 使用对象字面量
// 实际上 new Array === Array,加不加new 一点影响都没有。
let a = [];
a.length = 10; // [undefined * 10]

很明显,在这种情况下使用Array构造器更简单。
ES6 在原有的基础上,新增加了两种创建数组的方法。

ES6 中新增的构造函数方法

鉴于数组的常用性,在ES6中又新增了两个构造函数方法,方便我们快速构建数组,下面我们一起来看看。

Array.of()

Array.of()用于将参数依次转换成数组中的一项,然后返回这个新的数组。Array.of()的功能和Array构造器基本一致,唯一的区别就是单个参数的处理上

1
2
let a = Array(3)    // [ , , ]
let b = Array.of(3) // [3]

Array.from()

Array.from()基于其它对象快速便捷的创建数组,只要一个对象有迭代器,Array.from()都能将其转换成数组,当然是创建新数组,不改变原对象的值。
语法: array.from(obj, fn, this)
参数:
第一个参数:要转换成数组的真正对象,必需。
第二个参数:类似于数组的map方法,将每个元素通过自定义方法处理,将处理后的值放入数组,可选。
第三个参数:用来绑定this, 可选。

1
2
3
4
5
6
7
8
9
10
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5方法
let arr1 = [].slice.call(arrayLike) // ['a', 'b', 'c']
// ES6方法
let arr2 = Array.from(arrayLike) // ['a', 'b', 'c']

当Array.from()的参数是一个真正的数组,Array.from()会返回一个相同的新数组。
Array.from()和Set()连用可以快速的实现数组的去重

1
2
let array = [1,5,3,4,3,7,1,7,3,8,2,3,5,4]
let newArray = Array.from(new Set(array)) // [ 1, 5, 3, 4, 7, 8, 2 ]

下面我们来看看后面两个参数的用法

1
2
3
4
5
6
7
8
9
let arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
}
let a =Array.from(arrayLike, x => x * x) // [ 1, 4, 9 ]
// 等同于
let b = Array.from(arrayLike).map(x => x * x) // [ 1, 4, 9 ]

Array.from()还有一个很重要的扩展场景,比如说生成一个从0开始到指定数字的数组

1
Array.from({length: 10}, (v, i) => i); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

方法

数组的原型上提供了很多操作数组的方法,一般我们都是分为三类来说:即改变原数组的方法、不改变原数组的方法以及数组遍历的方法。

改变原数组的方法(9个)

基于ES6,改变原数组的方法一共有9中,分别为:splice()、sort()、pop()、shift()、push()、unshift()、reverse()以及ES6新增的两种cpoyWithin()和fill()。
对于这些能够改变原数组的方法,在使用的时候一定要注意,尤其是在数组遍历的时候。很可能出现改变了数组的长度,导致遍历的长度发生变化。

splice()添加/替换/删除数组元素

语法:array.splice(start, count, item1, …, itemX)
start: 必需,指定从当前位置开始修改数组元素,如果超过了数组长度,则从数组末尾开始添加元素。如果索引为负值,则从数组的末尾处开始计算。
count: 可选,要删除的数组元素数量,如果设置为0,表示不删除数组元素
item1,…,itemX:可选,表示要向数组中添加新的元素。
返回值:如果有元素被删除,则返回包含被删除元素的新数组。
eg1: 删除元素

1
2
3
let array = [1,2,4,5,1,8,3,6,9]
let arr = array.splice(0, 3) // [ 1, 2, 4 ]
console.log(array) // [ 5, 1, 8, 3, 6, 9 ]

eg2: 删除并添加

1
2
3
let array = [1,2,4,5,1,8,3,6,9]
let arr = array.splice(0, 2, 1, 4,6) // [ 1, 2 ]
console.log(array) // [ 1, 4, 6, 4, 5, 1, 8, 3, 6, 9 ]

eg3: 不删除只添加

1
2
3
let array = [5,1,8,3,6,9]
let arr = array.splice(0, 0, 1, 4,6) // []
console.log(array) // [ 1, 4, 6, 5, 1, 8, 3, 6, 9 ]

从以上三个例子我们可以看出:

  1. 如果有元素被删除,则返回包含被删除元素的新数组,如果没有元素被删除,返回一个空数组[]
  2. 如果item,…,itemX缺省,则表示默认只删除,不添加新的元素
  3. 操作的元素,包括开始的那个元素
  4. 添加是在开始元素前面添加的。

如果需要删除一个已经存在的元素,请参考以下示例

1
2
let array = ['a','b','c','d','e']
let arr = array.splice(array.indexOf('c'), 1) // [ 'c' ]

push()向数组的末尾添加元素

定义:push()方法可向数组的末尾添加一个或者多个元素,并返回数组的长度
语法:array.push(item1,…,itemX)
item1,…,itemX表示要添加到数组末尾的元素

1
2
3
let array = [6,2,5,4]
let length = array.push('新元素1', '新元素2') //[ 6, 2, 5, 4, '新元素1', '新元素2']
console.log(length) // 6

push()也可以应用到类数组对象上,如果length不能被转成一个数值或者不存在length属性时,则插入的元素索引为0,且length属性不存在时,将会创建它。

1
2
3
4
var o = {0:"football", 1:"basketball"};
var i = Array.prototype.push.call(o, "golfball");
console.log(o); // Object {0: "golfball", 1: "basketball", length: 1}
console.log(i); // 1

实际上,push方法是根据length属性来决定从哪里开始插入给定的值。

1
2
3
4
var o = {0:"football", 1:"basketball",length:1};
var i = Array.prototype.push.call(o,"golfball");
console.log(o); // Object {0: "football", 1: "golfball", length: 2}
console.log(i); // 2

利用push根据length属性插入元素这个特点,可以实现数组的合并

1
2
3
4
5
var array = ["football", "basketball"];
var array2 = ["volleyball", "golfball"];
var i = Array.prototype.push.apply(array,array2);
console.log(array); // ["football", "basketball", "volleyball", "golfball"]
console.log(i); // 4

pop()删除数组最后一个元素

定义:pop()方法删除一个数组中最后一个元素,并且返回这个元素。
语法:array.pop()
参数:无

1
2
let array = [6,2,5,4]
let item = array.pop() // 4

类似于push()方法,pop()也可以作用于类数组对象上,也就是鸭子类型。

1
2
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane', length: 4 }
let item = Array.prototype.pop.apply(obj) // Jane

但如果类数组上不存在length属性,那么该对象将被添加length属性,length属性为0。

1
2
3
4
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane' }
let item = Array.prototype.pop.apply(obj) // Jane
console.log(obj) // { '0': 'Jeremy', '1': 'Tom', '2': 'Jerry', '3': 'Jane', length: 0 }
console.log(item) // undefined

push()和pop()方法允许将数组当做栈来操作,组合使用push()和pop()能够用JavaScript数组实现栈的先进先出。

1
2
3
4
5
6
7
8
let stack = [];
stack.push(1,2); // stack: [1,2]
stack.pop(); // stack: [1]
stack.push(3); // stack: [1,3]
stack.pop(); // stack: [1]
stack.push([1,2]); // stack: [1,[1,2]]
stack.pop(); // stack: [1]
stack.pop(); // stack: []

unshift()向数组的开头添加元素

定义:unshift()用于向数组的开头添加一个或多个元素,并返回数组的长度
语法:array.unshift(item1,…itemX)
参数:item1,…,itemX表示要添加到数组开头的元素

1
2
let array = [1,2,6,4]
array.unshift(5,2) // [ 5, 2, 1, 2, 6, 4 ]

通过上面一个例子你有没有发现一些特别的地方? 没错,当使用多个参数调用unshift()时它的行为令人惊讶。参数是一次性的插入(就像是splice()方法一样)而非一次一个的插入。这就意味着最终的数组中插入的元素的顺序和它们在参数列表中的顺序一致,而如果是一个一个的插入,那么它们的顺序就应该是反过来的。
unshift()方法类似于push()方法,同样受用于鸭子类型。

1
2
3
4
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane', length: 4 }
let item = Array.prototype.unshift.call(obj, 'Ben') // Jane
console.log(obj) // { '0': 'Ben','1': 'Jeremy','2': 'Tom','3': 'Jerry','4': 'Jane',length: 5 }
console.log(item) // 5

但是如果类数组对象上不存在length属性,shift会认为数组长度为0,此时将从对象下标为0的位置开始插入,相应位置属性将被替换,此时初始化类数组对象的length属性为插入元素个数。

1
2
3
4
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane' }
let item = Array.prototype.unshift.call(obj, 'Ben') // Jane
console.log(obj) // { '0': 'Ben', '1': 'Tom', '2': 'Jerry', '3': 'Jane', length: 1 }
console.log(item) // 1

shift()删除数组的第一个元素

定义:shift()方法用于删除数组的第一个元素并将其返回,然后把所有随后的元素下移一个位置来填补数组头部元素的空缺
语法:array.shift()
参数:无

1
2
3
let array = [1,5,2,5,6];
let element = array.shift(); // 1
console.log(array) // [ 5, 2, 5, 6 ]

同样受益于鸭子类型,对于类数组对象同样能够处理

1
2
3
4
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane', length:4 }
let item = Array.prototype.shift.apply(obj) // Jane
console.log(obj) // { '0': 'Tom', '1': 'Jerry', '2': 'Jane', length: 3 }
console.log(item) // Jeremy

如果类数组对象中不存在length属性,那么它将被添加上length属性,并初始化为0

1
2
3
4
let obj = { 0: 'Jeremy', 1: 'Tom', 2: 'Jerry', 3: 'Jane'}
let item = Array.prototype.shift.apply(obj) // Jane
console.log(obj) // { '0': 'Jeremy', '1': 'Tom', '2': 'Jerry', '3': 'Jane', length: 0 }
console.log(item) // undefined

sort()将数组中的元素排序

定义:sort()方法将数组中的元素排序并返回排序后的数组
语法:array.sort(comparefn)
参数:comparefn可选,如果Array.sort()参数为空时,数组元素将按照各自转换为字符串的Unicode(万国码)位点顺序排序

1
2
3
4
5
6
// 字符串排列 看起来很正常
var a = ["Banana", "Orange", "Apple", "Mango"];
a.sort(); // ["Apple","Banana","Mango","Orange"]
// 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的
var a = [10, 1, 3, 20,25,8];
console.log(a.sort()) // [1,10,20,25,3,8];

如果定义了comparefn,数组将按照调用该函数的返回值来排序。若a和b是两个将要比较的元素:

  • 若比较函数返回值<0,那么a将排到b的前面;
  • 若比较函数返回值=0,那么a和b相对位置不变;
  • 若比较函数返回值>0,那么b排在a将的前面;、
    对于Array.sort()更深层次的内部实现我们不需要了解太多,只需要知道Array.sort()采用了快速排序的方式实现,但是比快速排序更加高级。感兴趣的童靴可以去看看深入了解javascript的sort方法
    下面我们来看看Array.sort()排序的常见用法
  • 数组元素为数字
1
2
3
4
5
6
7
let array = [3, 222, 44, 111]
// 转换为Unicode字符串之后比较顺序
array.sort(); // [ 111, 222, 3, 44 ]
// 数值顺序
array.sort((a,b) => a - b); // [ 3, 44, 111, 222 ]
// 数值倒序
array.sort((a,b) => b - a) // [ 222, 111, 44, 3 ]
  • 多条件排序
1
2
3
4
5
6
7
8
9
let array = [{id:1, key:5},{id:2,key:4},{id:1, key:2},{id:3,key:1}]
array.sort((a,b) => {
if (a.id === b.id) {
return a.key - b.key
} else {
return a.id - b.id
}
})
console.log(array) //[{id:1,key:2},{id:1,key:5},{id:2,key:4},{id:3,key:1}]
  • 自定义排序(例如不区分大小写排序)
1
2
3
4
5
6
7
let array = [undefined, 'Bug', 'cat', 'Dog', 'ant']
array.sort((a, b) => {
let s = a.toLowerCase();
let t = b.toLowerCase();
return s - t
})
console.log(array) // [ 'ant', 'Bug', 'cat', 'Dog', undefined ]

没错,如果数组中包括undefined元素,它们会被排到数组的尾部。
和其它数组方法相同,sort()也受益于鸭子类型

1
2
3
4
5
let obj = {0:'H',2:'l',1:'e',4:'o',3:'l',length:5}
let newObj = Array.prototype.sort.call(obj,function(a, b) {
return a.localeCompare(b);
})
console.log(newObj) //{ '0': 'e', '1': 'H', '2': 'l', '3': 'l', '4': 'o', length: 5 }

当类数组对象中没有length属性时,默认会按照对象的索引值进行排序,但不会给类数组对象添加length属性

1
2
3
4
5
let obj = {0:'H',2:'l',1:'e',4:'o',3:'l'}
let newObj = Array.prototype.sort.call(obj,function(a, b) {
return a.localeCompare(b);
})
console.log(newObj) //{ '0': 'H', '1': 'e', '2': 'l', '3': 'l', '4': 'o' }

reverse()颠倒数组中的元素

定义:Array.reverse()用于将数组中的元素进行一个倒序排序,最后返回新数组的引用。
方法:Array.reverse()
参数:无

1
2
3
let array = [1,2,3,4,5]
let newArray = array.reverse(); // [5,4,3,2,1]
console.log(array === newArray) // true

同数组的其它方法,reverse()也同样受益于鸭子类型

1
2
3
4
let obj = {0: 'Tom', 1: 'Jeremy', 2: 'Toni', 3: 'Ben', length:4}
let newObj = Array.prototype.reverse.apply(obj);
console.log(newObj) // { '0': 'Ben', '1': 'Toni', '2': 'Jeremy', '3': 'Tom', length: 4 }
console.log(obj === newObj) // true

当类数组对象没有length属性时,默认不会进行排序,也不会给类数组对象添加length属性

1
2
3
let obj = {0: 'Tom', 1: 'Jeremy', 2: 'Toni', 3: 'Ben'}
let newObj = Array.prototype.reverse.apply(obj);
console.log(newObj) // { '0': 'Tom', '1': 'Jeremy', '2': 'Toni', '3': 'Ben' }

如果 length 属性小于2 或者 length 属性不为数值,那么原类数组对象将没有变化。即使 length 属性不存在,该对象也不会去创建 length 属性。特别的是,当 length 属性较大时,类数组对象的『索引』会尽可能的向 length 看齐。

1
2
3
let obj = {0: 'Tom', 1: 'Jeremy', 2: 'Toni', length:100}
let newObj = Array.prototype.reverse.apply(obj);
console.log(newObj) // { '97': 'Toni', '98': 'Jeremy', '99': 'Tom', length: 100 }

copyWithin()将指定位置的元素复制到其它位置(ES6方法)

定义:Array.copyWithin()在当前数组内部,将指定位置的元素复制到其它位置,并返回这个数组。
方法:Array.copyWithin(target, start = 0, end = this.length)
参数:target:必需,被替换元素的索引,也就是从该位置开始替换元素。
start:可选,表示从该位置开始读取元素,默认为0,如果为负数,表示倒数,等同于start+length。
end: 可选,到该位置前停止读取元素,默认为数组长度。使用负数等同于end+length。

1
2
3
let array = [1, 5, 4, 7, 2, 5, 9]
array.copyWithin(2, -2, -1);
console.log(array) // [ 1, 5, 5, 7, 2, 5, 9 ]

通过这个例子我们可以看出:

  1. 数组的长度不会变化
  2. 读取了几个元素就从被替换处覆盖几个元素
    但是如果start大于end的值时会发生什么情况呢?
1
2
3
let array = [1, 5, 4, 7, 2, 5, 9]
array.copyWithin(2, -1, -2);
console.log(array) // [ 1, 5, 4, 7, 2, 5, 9 ]

没错,什么都没有发生,并没有发生元素覆盖的情况。
同数组的其它方法,Array.copyWithin()同样受益于鸭子类型。

1
2
3
let obj = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
let newObj = Array.prototype.copyWithin.call(obj,0,3)
console.log(obj) // { '0': 4, '1': 5, '2': 3, '3': 4, '4': 5, length: 5 }

fill()填充数组(ES6方法)

定义:fill()同样用于数组替换,但与copyWithin不同的是,fill()是将数组指定区间内的值替换成给定值。
方法:Array.fill(value, start = 0, end = this.length)
参数:value:必需,要填充数组的值。
start:可选,填充的开始位置,默认为0,如果为负数,表示倒数,等同于start+length。
end: 可选,填充的结束位置,默认为数组长度。使用负数等同于end+length。

1
2
3
let array = [1, 2, 5, 3, 7]
let newArray = array.fill(10, 0, 2)
console.log(newArray) // [ 10, 10, 5, 3, 7 ]

同样,fill()受益于鸭子类型

1
2
3
let obj = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
let newObj = Array.prototype.fill.call(obj,3,0,1);
console.log(newObj) // { '0': 3, '1': 2, '2': 3, '3': 4, '4': 5, length: 5 }

讲到这儿,操作会改变数组原始值的9种方法全部说完了。。。


接下来我们开始说说不会改变数组原始值的方法,同样有9个。

不改变原数组的方法(9个)

基于ES7,不会改变数组自身的方法同样有9个,它们分别为:concat()、slice()、join()、toString()、
toLocalString()、indexOf()、lastIndexOf()、未标准的toSource()以及ES7新增加的includes()。下面我们分别来说说这些方法。

concat()数组合并

定义:concat()方法将传入的数组或者元素与原数组合并,组成一个新的数组并返回
语法:array.concat(arrayX,arrayX,……,arrayX)
参数:arrayX可以是具体的值,也可以是数组对象,可以有多个。

1
2
3
4
5
6
let array = [1, 2, 3]
let newArray = array.concat([4, 5], [6, 7], 8, 9)
let newArray1 = array.concat([4, [5, 6]])
console.log(newArray) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
console.log(newArray1) // [ 1, 2, 3, 4, [ 5, 6 ] ]
console.log(array) // [ 1, 2, 3 ] 原数组并未改变

通过上个例子我们可以看出:concat()合并数组,会对数组进行一个浅拷贝,而且原数组并没有发生改变。
如果concat()中不传入参数,那么将基于原数组创建一个相同的新数组,指向新的地址空间

1
2
3
4
let array = [1, 2, 3]
let newArray = array.concat()
console.log(newArray) // [ 1, 2, 3 ]
console.log(array) // [ 1, 2, 3 ] 原数组并未改变

ES6扩展运算符

ES6中为我们提供了一个更加简洁易懂的方法–扩展运算符(…)可用于合并数组,…可以完全实现concat(),而且更加简洁和自定义。

1
2
3
4
let array1 = [1, 2, 3]
let array2 = [4,5]
let newArray = [...array1, ...array2]
console.log(newArray) // [ 1, 2, 3, 4, 5 ]

由于本篇博客主要讲述和数组相关的方法,所以对扩展运算符感兴趣的童靴可以看看另一篇博客ES6扩展运算符

slice()浅拷贝数组元素

定义:slice()方法将数组中的一部分元素浅拷贝后存入新的数组对象,并返回这个数组对象
方法:Array.slice(start, end)
参数:start: 可选,表示从该索引处开始提取元素,默认为0,如果为负值,则从length+start处开始复制。
end: 可选,表示复制的结束位置(不包括此处的元素),如果end小于start,将返回一个空的数组。
slice()方法参数为空时,同concat()方法一样,都是浅复制一个新的数组

  • 字符串也有一个slice()方法用来提取字符串,注意区分
1
2
3
4
let array = [1, 2, 5, 6, 2, 4, 3]
let newArray = array.slice(1,3)
console.log(newArray) //[ 2, 5 ]
console.log(array) // [ 1, 2, 5, 6, 2, 4, 3 ] 不改变原数组

浅复制是指当对象被复制时,只是复制了对象的引用,指向的仍是同一个对象

1
2
3
4
5
6
let a= ['hello','world'];
let b=a.slice(0,1); // ['hello']
a[0]='改变原数组';
console.log(a,b); // ['改变原数组','world'] ['hello']
b[0]='改变拷贝的数组';
console.log(a,b); // ['改变原数组','world'] ['改变拷贝的数组']

由于slice是浅复制,复制的只是一个对象的引用,所以当元素只是简单的数据类型时,改变之后不会互相干扰。但是如果元素是复杂数据类型(对象、数组)的话,改变一个其它的也会受影响。

1
2
3
4
5
6
7
let a= [{name:'OBKoro1'}];
let b=a.slice();
console.log(b,a); // [{"name":"OBKoro1"}] [{"name":"OBKoro1"}]
a[0].name='改变原数组';
console.log(b,a); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
b[0].name='改变拷贝数组',b[0].koro='改变拷贝数组';
[{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]

原因在定义上面说过了的:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。
slice()方法同样受益于鸭子类型

1
2
3
var o = {0:{"color":"yellow"}, 1:2, 2:3, length:3};
var o2 = Array.prototype.slice.call(o,0,1);
console.log(o2); // [{color:"yellow"}]

join()数组转字符串

定义:join()通过传入指定的分隔符将数组中的元素分割成一个字符串,并返回生成的字符串
方法:Array.join(separator)
参数:separator:可选,指定要使用的分隔符,默认使用逗号作为分隔符。

1
2
3
4
let array = ['hello', 'world']
console.log(array.join()) // hello,world
console.log(array.join('')) // helloworld
console.log(array.join('+')) // hello+world

上述例子中,数组元素都是基本类型,但是如果数组元素是引用类型的话,会怎么样呢?

1
2
3
4
5
6
7
8
9
10
// 数组元素包含对象
let array = [{0: 'antd', 1: 'element', 2: 'iView'}, 'hello', 'world']
console.log(array.join()) // [object Object],hello,world
console.log(array.join('')) // [object Object]helloworld
console.log(array.join('+')) // [object Object]+hello+world
// 对象转字符串推荐用JSON.stringify()
console.log(JSON.stringify(array)) // [{"0":"antd","1":"element","2":"iView"},"hello","world"]
// 数组元素包含数组
let array1 = [['antd', 'element', 'iView'], 'hello', 'world']
console.log(array1.join()) // antd,element,iView,hello,world

所以,join()方法在数组元素包含数组时,会将里面的数组也调用join(),如果数组元素包含对象时,对象会被转为[object Object]字符串。
同上,join 一样受益于鸭子类型

1
2
3
let obj = {0: 'antd', 1: 'element', 2: 'iView', length: 3}
let newObj = Array.prototype.join.call(obj, ',')
console.log(newObj) // antd,element,iView

toString()数组转字符串

定义:toString()可将数组转为用逗号分隔的字符串。
语法:array.toString()
参数:无
说明:该方法的效果和join()方法的效果一样,相对于join()没有任何优势,因此不推荐使用。

1
2
3
4
5
6
7
8
let array = ['hello', 'world']
console.log(array.toString()) // hello,world
let array = ['hello', 'world']
console.log(array.toString()) // hello,world

// 不适用于鸭子类型,但可以通过改造实现
let obj = {0: 'hello', 1: 'world', length: 2}
console.log(Array.prototype.toString.call(obj)) // [object Object]

toLocalString() 数组转字符串

定义:和toString()类似,该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。
语法:array.toLocalString()
参数:无
数组中的元素将调用各自的 toLocaleString 方法:

  • Object:Object.prototype.toLocaleString()
  • Number:Number.prototype.toLocaleString()
  • Date:Date.prototype.toLocaleString()
1
2
let array = [ {0: 'antd', 1: 'element'}, 13, 'aaa', new Date() ]
console.log(array.toLocaleString()) // [object Object],13,aaa,2019-3-31 18:45:07

其鸭子类型同toString()

indexOf()查找元素在数组中的索引

定义:indexOf()用于查找元素在数组中的第一个索引,如果不存在,则返回-1
语法:Array.indexOf(element, formIndex=0)
参数:element: 必需,表示需要查找的元素
formIndex: 可选,开始查找的位置,默认值为0,如果超出数组长度,则返回-1。如果为负值,假设数组长度为length,则从数组的第formIndex+length处开始往数组末尾查找,如果length + fromIndex<0 则整个数组都会被查找。
注意,indexOf()使用的是严格意义上的相等,即数组元素要完全匹配才能搜索成功

1
2
3
4
let array = ['antd', 'element-ui', 'iView']
console.log(array.indexOf('antd')) // 0
console.log(array.indexOf('antd', 1)) // -1
console.log(array.indexOf('iview', 1)) // -1

另外,如果数组元素是引用类型,也不能够正确的搜索

1
2
3
4
let array = [{key:0, value: 'antd'}, {key: 1, value: 'element-ui'}, {key: 2, value:'iView'}, {key: 3, value: 'Vue'}]
console.log(array.indexOf({key: 1, value: 'element-ui'})) // -1
// 数组元素是对象时获取索引用findIndex()
console.log(array.findIndex(item => item.key === 1)) // 1

indexOf适用于鸭子类型

1
2
var o = {0:'abc', 1:'def', 2:'ghi', length:3};
console.log(Array.prototype.indexOf.call(o,'ghi',-4)); //2

lastIndexOf()查找元素在数组中的索引

定义:lastIndexOf()方法用于查找元素在数组中最后一次出现时的索引,如果没有,则返回-1。并且它是indexOf的逆向查找,即从数组最后一个往前查找。
语法:array.lastIndexOf(element, fromIndex=length-1)
参数:element:必需,表示需要查找的元素。
fromIndex: 开始查找的位置,默认值length-1。如果超出了数组长度,由于逆向查找,所以会查找整个数组长度。如果为负值,则从数组的第 length + fromIndex项开始往数组开头查找,如果length + fromIndex<0 则数组不会被查找。
同indexOf()一样,lastIndexOf()也是严格匹配数组元素。使用方法也同indexOf(),这里不做过多描述。

includes()查找数组是否包含某个元素(ES7方法)

定义:includes()用于判断某个元素是否存在于当前数组,如果存在则返回true,否则返回false
语法:array.includes(element, formIndex = 0)
参数:element:必需,需要查找的元素
formIndex:默认值为0,表示需要搜索的起始位置,可以为负值,正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。
** includes方法是为了弥补indexOf方法的缺陷而出现的: **

  1. indexOf方法不能识别NaN
  2. indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于-1,表达不够直观
1
2
3
4
5
6
7
let array = ['antd', 1, 2, null, undefined, NaN];
console.log(array.includes(1)); // true
console.log(array.includes(NaN)); // true
console.log(array.includes(2,-4)); // true
console.log(array.includes(null)); //true
console.log(array.includes(undefined)); // true
console.log(array.includes('antd')); // true

该方法同样适用于鸭子类型

1
2
3
let obj = {0: 'antd', 1: 'element-ui', 2: 'iView', length:3}
let bool = Array.prototype.includes.call(obj, 'antd')
console.log(bool) // true

注意使用此方法时需要注意浏览器兼容性

toSource()返回数组的源代码

定义:toSource()返回一个字符串,代表该数组的源代码。该方法是非标准的,所以请尽量不要在生产环境中使用
语法:array.toSource()
参数:无

1
2
let array = ['a', 'b', 'c']
console.log(array.toSource) // ['a', 'b', 'c']

至此,不会改变原数组的九个方法也都说完了


下面我们来看看数组遍历的方法

遍历方法(12个)

基于ES6,不会改变数组的遍历方法一共有12个:

1
2
3
4
ES5:
map、forEach、every 、some、 filter、reduce、reduceRight、
ES6:
find、findIndex、keysvalues、entries

关于数组的遍历,我们在使用的过程中需要注意以下几点:

  • 遍历的效率
  • 在遍历的时候,尽量不要修改后面需要遍历的值
  • 在遍历的时候,尽量不要修改数组的长度
    在遍历的过程中,也有这样的一些规则:
  • 对于空数组是不会执行回调函数的
  • 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
  • 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历。
  • 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。

forEach()数组遍历

定义:forEach()对数组中的每个元素都执行一次指定的函数
语法:array.forEach(function(value, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisArg: 可选,用来当做function函数内的this对象,默认为undefined。
在使用forEach()时需要注意,forEach()无法中途退出循环,只能使用return来达到for循环中continue的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let array = [{0: 'antd'}, {1: 'element-ui'}, {2: 'iView'}]
let result = array.forEach((item, index, arr) => {
console.log({item, index})
array.push({3: 'Vue'}) // 添加到尾端,不会被遍历
item['key'] = index *2;
return item;
}, array)
console.log(array)
console.log(result) // 即使return一个值,返回的也还是undefined
// {item, index}
// { item: { '0': 'antd' }, index: 0 }
// { item: { '1': 'element-ui' }, index: 1 }
// { item: { '2': 'iView' }, index: 2 }
// array 会改变原数组
// [ { '0': 'antd', key: 0 },
// { '1': 'element-ui', key: 2 },
// { '2': 'iView', key: 4 },
// { '3': 'Vue' },
// { '3': 'Vue' },
// { '3': 'Vue' } ]

得益于鸭式辨型,虽然forEach不能直接遍历对象,但它可以通过call方式遍历类数组对象。

1
2
3
4
5
6
7
8
9
var o = {0:1, 1:3, 2:5, length:3};
Array.prototype.forEach.call(o,function(value, index, obj){
console.log(value,index,obj);
obj[index] = value * value;
},o);
// 1 0 Object {0: 1, 1: 3, 2: 5, length: 3}
// 3 1 Object {0: 1, 1: 3, 2: 5, length: 3}
// 5 2 Object {0: 1, 1: 9, 2: 5, length: 3}
console.log(o); // Object {0: 1, 1: 9, 2: 25, length: 3}

map()创建新数组&&遍历数组

定义:map()方法对原数组中的每个元素都执行一次指定的函数,并返回函数执行结果形成的新数组。
语法:array.map(function(value, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisArg: 可选,用来当做function函数内的this对象,默认为undefined。
map()方法的使用和forEach()类似,只是map()会返回新的数组,而forEach()返回的总是undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let array = [{0: 'antd'}, {1: 'element-ui'}, {2: 'iView'}]
let result = array.map((item, index, arr) => {
console.log({item, index})
array.push({3: 'Vue'}) // 添加到尾端,不会被遍历
item['key'] = index *2;
return index;
}, array)
console.log(array)
console.log(result) // [ 0, 1, 2 ] 返回了一个新数组
// {item, index}
// { item: { '0': 'antd' }, index: 0 }
// { item: { '1': 'element-ui' }, index: 1 }
// { item: { '2': 'iView' }, index: 2 }
// array 会改变原数组
// [ { '0': 'antd', key: 0 },
// { '1': 'element-ui', key: 2 },
// { '2': 'iView', key: 4 },
// { '3': 'Vue' },
// { '3': 'Vue' },
// { '3': 'Vue' } ]

map()同样得益于鸭子类型:

1
2
3
4
5
6
7
8
9
10
11
let o = {0:1, 1:3, 2:5, length:3};
let result = Array.prototype.map.call(o,function(value, index, obj){
console.log(value,index,obj);
obj[index] = value * value;
return index
},o);
// 1 0 Object {0: 1, 1: 3, 2: 5, length: 3}
// 3 1 Object {0: 1, 1: 3, 2: 5, length: 3}
// 5 2 Object {0: 1, 1: 9, 2: 5, length: 3}
console.log(o); // Object {0: 1, 1: 9, 2: 25, length: 3}
console.log(result) // [ 0, 1, 2 ]

every()检测数组所有元素是否都符合条件

定义:every()检测数组内的所有元素是否都符合函数定义的条件
语法:array.every(function(value, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisArg: 可选,用来当做function函数内的this对象,默认为undefined。
every()方法使用传入的函数测试所有元素,只有当数组内的所有元素都符合条件(即返回值为true)时,every()方法才返回true,只要有一个不符合条件,就返回false。

1
2
3
let array = [1,3,2,4,7];
console.log(array.every(item => item < 10)) // true
console.log(array.every(item => item < 5)) // false

和大多数数组方法类似,every()方法也受益于鸭子类型

1
2
3
4
5
let obj = { 0: 5, 1: 7, 2:8, length:3 }
let bool1 = Array.prototype.every.call(obj, item => item < 10)
let bool2 = Array.prototype.every.call(obj, item => item < 8)
console.log(bool1) // true
console.log(bool2) // false

some()判断数组是否有符合条件的元素

定义:some()方法测试是否至少有一个元素通过由提供的函数实现的测试。
语法:array.some(function(value, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身
  thisArg: 可选,用来当做function函数内的this对象,默认为undefined。

some()方法刚好和every()方法相反,some()方法测试数组时,只要有一个元素满足条件,some()方法就返回true,如果数组中没有符合条件的元素才返回false。

1
2
3
4
5
let array = [1,5,7,2,5,4];
let bool1 = array.some(item => item === 2)
let bool2 = array.some(item => item === 10)
console.log(bool1) // true
console.log(bool2) // false

同样,some()方法也受益于鸭子类型

1
2
3
4
5
let obj = { 0: 5, 1: 7, 2:8, length:3 }
let bool1 = Array.prototype.some.call(obj, item => item === 5)
let bool2 = Array.prototype.some.call(obj, item => item === 10)
console.log(bool1) // true
console.log(bool2) // false

filter()过滤原数组,返回新数组

定义:filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
语法:array.filter(function(value, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisArg: 可选,用来当做function函数内的this对象,默认为undefined。

1
2
3
4
5
let array = [1,5,7,2,5,4];
let newArr1 = array.filter(item => item < 4)
console.log(newArr1) // [ 1, 2 ]
// 不改变原数组
console.log(array) // [ 1, 5, 7, 2, 5, 4 ]

同样,filter()也受益于鸭子类型

1
2
3
4
let obj = { 0: 5, 1: 7, 2:8, length:3 }
let newObj = Array.prototype.filter.call(obj, item => item < 8)
console.log(newObj) // [ 5, 7 ]
console.log(obj) // { '0': 5, '1': 7, '2': 8, length: 3 }

reduce()累加器合并数组中的每个元素

定义:reduce()方法接收一个函数作为累加器,升序执行,最终将结果计算为一个值。
语法:array.reduce(function(total,currentValue,currentIndex,arr),initialValue)。
参数:function:必需,数组中每个元素需要调用的函数

1
2
3
4
5
// 回调函数的参数
1. total(必须),初始值, 或者上一次调用回调返回的值
2. currentValue(必须),数组当前元素的值
3. index(可选), 当前元素的索引值
4. arr(可选),数组对象本身

initialValue: 可选,传递给函数的初始值。
回调函数第一次执行时:

  • 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
  • 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
  • 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。
    同样,reduce()方法也受益于鸭子类型。
1
2
3
4
5
let obj = { 0: 5, 1: 7, 2:8, length:3 }
let newObj = Array.prototype.reduce.call(obj,
(total, num) => total + num, 0)
console.log(newObj) // 20
console.log(obj) // { '0': 5, '1': 7, '2': 8, length: 3 }

reduceRight()从右至左累加

这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。

find()&findIndex()根据条件查找数组元素(ES6)

定义:find()用于查找并返回数组中符合条件的第一个元素,如果没有符合的元素,则返回undefined。
    findIndex()用于查找数组中符合条件的第一个元素的索引,如果没有符合条件的元素,则返回-1。
语法:let element = arr.find(function(currentValue, index, arr), thisArg)
    let index = arr.findIndex(function(currentValue, index, arr), thisArg)
参数:function:必需,数组中每个元素都需要调用的函数

1
2
3
4
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisArg: 可选,用来当做function函数内的this对象,默认为undefined。
这两个方法的诞生主要是为了弥补indexOf()的不足。

1
2
3
4
5
6
7
8
9
10
11
12
// find
let element = [1, 4, -5, 10].find((n) => n < 0); // 返回元素-5
let element1 = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n)); // 返回元素NaN
// findIndex
// 可以识别NaN
let index = [1, 4, -5, 10].findIndex((n) => n < 0); // 返回索引2
let index1 = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n)); // 返回索引4
// 可以识别引用类型
let array = [{key:0, value: 'antd'}, {key: 1, value: 'element-ui'}, {key: 2, value:'iView'}, {key: 3, value: 'Vue'}]
console.log(array.indexOf({key: 1, value: 'element-ui'})) // -1
// 数组元素是对象时获取索引用findIndex()
console.log(array.findIndex(item => item.key === 1)) // 1

同样,find()&findIndex()也受益于鸭子类型

1
2
3
4
5
6
7
8
9
let obj = { 0: 5, 1: 7, 2:8, length:3 }
let newObj = Array.prototype.find.call(obj,
(item, index, array) => item === 5 )
let index = Array.prototype.findIndex.call(obj,
(item, index, array) => item === 5)
console.log(newObj) // 5
console.log(index) // 0
// 不改变原数组
console.log(obj) // { '0': 5, '1': 7, '2': 8, length: 3 }

keys遍历键名(ES6)

定义:keys()方法返回一个包含数组所有元素索引值的Array.Iterator对象。
语法:array.keys()。
参数:无

1
2
3
4
5
6
7
let array = ['antd', 'element-ui', 'iView']
let iterator = array.keys()
console.log(iterator) // Object [Array Iterator] {}
console.log(iterator.next()) // { value: 0, done: false }
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log([...array.keys()]) // [ 0, 1, 2 ]

前面我们说到可以利用Array.from生成一个从0到指定数字的新数组,其实使用keys更加容易

1
2
[...Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[...new Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

由于Array的特性,new Array 和 Array 对单个数字的处理相同,因此以上两种均可行。
同样,keys()也受益于鸭子类型

1
2
3
4
5
let obj = {0:"a", 1:"b", 2:"c", length:3};
let iterator = Array.prototype.keys.call(obj);
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2

values()遍历键值(ES6)

定义:values()方法返回一个新的Iterator对象,该对象包括每个索引对应的值。
语法:array.values()
参数:无

1
2
3
4
5
6
7
let arr = ['w', 'y', 'k', 'o', 'p'];
let eArr = arr.values();
console.log(eArr.next().value); // w
console.log(eArr.next().value); // y
console.log(eArr.next().value); // k
console.log(eArr.next().value); // o
console.log(eArr.next().value); // p

同样,values()方法也受益于鸭子类型

1
2
3
4
5
let obj = {0:"a", 1:"b", 2:"c", length:3};
let iterator = Array.prototype.values.call(obj);
console.log(iterator.next().value); // a
console.log(iterator.next().value); // b
console.log(iterator.next().value); // c

entries()遍历键值对(ES6)

定义:entries()返回一个新的Array.Iterator对象,该对象包含数组每个元素的键值对。
语法:array.entries()
参数:无

1
2
3
4
5
6
let array = ['antd', 'element-ui', 'iView']
let iterator = array.entries()
console.log(iterator) // Object [Array Iterator] {}
console.log(iterator.next().value) // [ 0, 'antd' ]
console.log(iterator.next().value) // [ 1, 'element-ui' ]
console.log(iterator.next().value) // [ 2, 'iView' ]

同样,entries()也受益于鸭子类型

1
2
3
4
5
let obj = {0:"a", 1:"b", 2:"c", length:3};
let iterator = Array.prototype.entries.call(obj);
console.log(iterator.next().value); // [ 0, 'a' ]
console.log(iterator.next().value); // [ 1, 'b' ]
console.log(iterator.next().value); // [ 2, 'c' ]

注意:以上方法凡是明确指出ES6的都是ES6方法,需要注意浏览器兼容情况,在使用前请先确定浏览器兼容


本文完


终于写完了,通过几天的总结,对数组的操作方法熟悉了很多,达到了自己写这篇博客的初衷。

参考文章

ECMAScript6 入门 数组的扩展
Array.prototype
深入了解javascript的sort方法
详解JS遍历