本文首发于 刘星的个人网站liuxing.io
简介
在 JavaScript 中,除了 Object 之外,数组(Array)应该是最常用的类型了。数组是一组有序的数据,使用方括号表示[1, 2, 3]
,可通过索引来访问每个元素(索引从 0 开始)。 JavaScript 中的数组的长度和元素类型都是非固定的。
数组的创建
首先我们来看看数组的几种创建方式:字面量方式、 Array 构造函数、 扩展运算符(...
)、ES6 新增的用于创建数组的静态方法from()
和of()
数组字面量
数组字面量(array literal)应该是 JavaScript 中最为常用的创建方式了,在初始化数组的时候相当方便。数组字面量是在中括号中包含以逗号分隔的元素列表。
const users = ['LiuXing', 'liuixng.io'];
console.log(users.length); // 2
上面这个例子中创建了一个包含两个字符串的数组。
构造函数
还可以使用 Array 构造函数来创建数组。可以给Array()
构造函数传入一个数字,创建一个长度为传入值得数组;也可以给 Array 构造函数传入要保存的元素,创建包含传入值的数组。
// 传入要保存的元素
const users = Array('LiuXing', 'liuxing.io'); ['LiuXing', 'liuxing.io']
console.log(users.length); // 2
const arr1 = new Array(); []
// 传入数字 直接创建一个长度为3的数组
const arr2 = Array(3); [,,]
console.log(users.length); // 3
在使用 Array 构造函数时,加不加 new 操作符,结果都一样。
扩展运算符
可以使用扩展运算符 ...
在一个数组字面量里包含另一个数组的元素。扩展运算符可以很方便的创建一个浅拷贝的数组。扩展运算符还可以用于任何可迭代的对象。
const original = [1, 2, 3];
const copy = [...original]
copy[0] = 0 // 修改copy不会法改变原数组
original[0] // 1
Array 静态方法
ES6 Array 新增了两个用于创建数组的静态方法: from()
和 of()
。from()
用于将类数组转换为数组,而 of()
用于将一组参数转换为数组。
Array.form()
从类数组对象或者可迭代对象中创建一个新的数组实例。
Array.form()
可以接收 3 个参数:
- 第一个必须参数为想要转换成数组的伪数组对象或可迭代对象:如 Map 和 Set、Arguments 对象
- 第二个是可选的映射函数参数,可以直接增强新数组的值,类似数组的 map 方法
- 第三个可选参数,用于指定映射函数中 this 的值,但这个重写的 this 值在箭头函数中不适用
console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
console.log(a2); // [1, 4, 9, 16]
Array.of()
根据一组参数来创建新的数组实例,支持任意的参数数量和类型。将按顺序成为返回数组中的元素。
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
数组的检测
本质上,数组属于一种特殊的对象。typeof
运算符会返回数组的类型是object
。有一个经典问题是判断一个对象是不是数组,通常可以通过 instanceof
、 constructor
已经 Object.prototype.toString
来判断,但是前两这个可能不准确,后者较麻烦。为解决这个小问题,JavaScript 提供了 Array.isArray()
方法,它返回一个布尔值,表示参数是否为数组。它可以弥补 typeof
运算符的不足。
Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false
但是在不支持该方法的环境中我们可以提供如下 Polyfill,也就是使用Object.prototype.toString
来判断
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
数组方法
数组的方法有很多,本文会将这些方法分为操作方法、排序方法、栈与队列方法、迭代方法、搜索方法及数组的转换方法一一讲解。在数组的操作方法中只有concat()
与slice()
不会改变原数组,其他方法均会改变原数组。所有的排序方法都会改变原数组。所有栈与队列方法也会改变原数组。
数组的操作方法
对于数组中的元素,我们有很多操作方法,如:concat()
用于连接两个数组。slice()
用于切片,splice()
在数组中间删除或插入元素...
contact()
concat
方法用于多个数组的合并。它将新数组的元素,添加到原数组元素的后部,然后返回一个新的结果数组,原数组不变。
['liu', 'xing'].concat(['love', 'dev'])
// ["liu", "xing", "love", 'dev']
如果传入一个或多个数组,则 concat()
会把这些数组的每一项都添加到结果数组。如果参数不是数组,则直接把它们添加到结果数组末尾
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
同时,concat
方法还可以很方便的创建一个当前数组的一个浅拷贝。
slice()
slice()
方法用于切片,即截取数组的中的部分元素,然后返回一个新数组,原数组不变。
slice()
方法可以接收一个或两个参数:返回元素的开始索引和结束索引。slice()
返回的结果包括开始索引,不包括结束索引。
arr.slice(start, end);
如果有两个参数,则 slice()
返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。
arr.slice(start, end);
如果只有一个参数,则 slice()会返回该索引到数组末尾的所有元素。
arr.slice(start, end);
如果不给slice()
传递任何参数,它就会从头到尾截取所有元素,等于返回了一个原数组的拷贝。
arr.slice(start, end);
如果slice()
方法的参数是负数,则表示倒数计算的位置
arr.slice(start, end);
splice()
或许最强大的数组方法就属 splice()
了,他是修改数组的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素。返回值是被删除的元素,该方法会改变原数组。
arr.splice(start, count, addElement1, addElement2, ...);
splice
的第一个参数是指定修改的开始位置(从 0 计数),第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。
主要有以下三种使用方式:
- 删除。需要给
splice()
传 2 个参数: 要删除的第一个元素的位置和要删除的元素数量。可以从
数组中删除任意多个元素,比如splice(0, 2)
会删除前两个元素。 - 插入。需要给
splice()
传 3 个参数: 开始插入位置索引、0(删除 0 个元素)和要插入的元素,可
以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数,乃至任意多
个要插入的元素。 - 替换。
splice()
在删除元素的同时可以在指定位置插入新元素,这样我们可以很方便实现元素的替换。如splice(2, 1, "liuxing")
会在位置 2 删除一个元素,然后从该位置开始向数组中插入"liuxing"。
copyWithin()
copyWithin()
方法可以浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
fill()
fill()
方法可以用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
排序方法
sort()
sort
方法可对数组成员进行排序,排序后原数组将被改变。默认排序顺序是在将元素转换(调用 String()
转型函数)为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的。如:
let values = [0, 3, 1, 2, 10];
values.sort();
console.log(values); // [0, 1, 10, 2, 3]
从上例可以看到,默认的排序方法,对数字的排序是有问题的,为此,sort()方法可以接收一个比较函数作为第二个参数,用于判断哪个值应该排在前面。比较函数可以接收两个参数,表示进行比较的两个数组成员。
- 如果第一个参数应该排在第二个参数前面,就返回负值;
- 如果两个参数相等,就返回 0;
- 如果第一个参数应该排在第二个参数后面,就返回正值。
格式如下
function compare(a, b) {
if (a < b ) { // 按某种排序标准进行比较, a 小于 b
return -1;
}
if (a > b ) {
return 1;
}
// a must be equal to b
return 0;
}
对于前面的实例数组的排序可以写成这样
let values = [0, 3, 1, 2, 10];
values.sort((a, b) => a - b);
console.log(values); // [0, 1, 2, 3, 10]
reverse()
顾名思义,reverse()
方法就是将数组元素反向排列。该方法将改变原数组。
let values = [1, 2, 3, 4, 5];
values.reverse();
console.log(values); // 5,4,3,2,1
栈与队列
JavaScript 的数组以及原生方法可以很好的模拟另外两种常用数据结构:栈与队列
栈是一种后进先出(LIFO,Last-In-First-Out)的结构,也就是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。JavaScript 数组提供了 push()和 pop()方法,以实现类似栈的行为。
push()
push()
方法用于向数组末尾添加元素(任意数量的元素),返回数组的最新长度,该方法会改变原数组。
let arr = [];
arr.push(1) // 1
arr.push('liuxing') // 2
arr // [1, 'liuxing', true, {}]
pop()
pop()
方法用于删除数组的最后一个元素,并返回该元素。该方法会改变原数组。
let arr = ['a', 'b', 'c'];
arr.pop() // 'c'
arr // ['a', 'b']
队列以先进先出(FIFO,First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。因为有了在数据末尾添加数据的 push()方法,所以要模拟队列就差一个从数组开头取得数据的方法了。这个数组方法叫 shift(),它会删除数组的第一项并返回它,然后数组长度减 1。使用 shift()和 push(),可以把数组当成队列来使用
shift()
shift()
方法用于删除数组的第一个元素,并返回该元素。该方法会改变原数组。
let arr = ['a', 'b', 'c'];
arr.shift() // 'a'
arr // ['b', 'c']
unshift()
unshift()
方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。该方法会改变原数组。
let arr = ['a', 'b', 'c'];
arr.unshift('liuixng'); // 4
arr // ['liuxing', 'a', 'b', 'c']
数组的迭代方法
ES6 数组提供三个新的用于检索数组内容的方法: entries()
,keys()
和values()
。它们都返回一个迭代器对象。keys()
返回数组索引的迭代器,values()
返回数组元素的迭代器,而 entries()
返回键/值对的迭代器。可以用for...of
循环进行遍历。
keys()
返回一个包含所有数组元素的索引的迭代器对象。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
values()
返回一个包含所有数组元素值得迭代器对象。
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
entries()
返回一个包含所有数组元素的键值对迭代器对象。
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
forEach()
forEach
方法迭代数组的每一个元素,并对每个元素都调用一次我们指定的回调函数。它不返回值,只用来操作数据。forEach()
方法相当于使用 for
循环遍历数组。
let numbers = [1, 2, 3, 4, 5];
numbers.forEach((item, index, array) => {
// 执行某些操作
console.log(item)
});
map()
map()
会将数组的每个元素都按顺序传入回调函数,然后返回由每次函数调用的结果组成的新数组。 注意:回调 函数只会在有值的索引上被调用。
let numbers = [1, 2, 3, 4, 5];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult); // [2,4,6,8,10]
flat()
flat()
用于打平数组。即从多维数组转化为一位数组。该方法接收一个参数(默认为 1),用于指定的深度遍历,然后将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
如果想要打平无论多少层的数组,可以传入Infinity
作为参数
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
flatMap()
flatMap()
与 map()
方法相似。只不过返回的数组会自动被打平。调用flatMap()
等同于调用map()
,但是flatMap()
效率更高。
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
filter()
filter()
方法用于过滤数组元素。它会对数组每一项都运行传入的函数,返回结果为true
的元素将组成一个新数组返回。该方法不会改变原数组。
let numbers = [1, 2, 3, 4, 5];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3, 4, 5]
some()
与 every()
十分相似,都用于判断数组中的元素是否符合某种条件(断言函数)。
- 对
every()
来说,传入的函数必须对每一项都返回true
,它才会返回true
; - 对
some()
来说,只要有一项让传入的函数返回true
,它就会返回true
。
some()
如果数组中至少有一个元素满足测试函数,则返回 true
,否则返回 false
。
let numbers = [1, 2, 3, 4, 5];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult); // false
every()
如果数组中的每个元素都满足测试函数,则返回 true
,否则返回 false。
let numbers = [1, 2, 3, 4, 5];
let someResult = numbers.some((item, index, array) => item > 2);
console.log(everyResult); // true
reduce()
和 reduceRight()
这两个方法都会迭代数组的所有项,并在此基础上累计构建一个最终返回值。reduce()
方法从数组第一项开始遍历到最后一项。而reduceRight()
从最后一项开始遍历至第一项。
reduce()
从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
let values = [1, 2, 3, 4, 5];
let sum = values.reduce(function(prev, cur, index, array){
console.log(prev, cur)
return prev + cur;
});
// 1 2
// 3 3
// 6 4
// 10 5
console.log(sum) // 15
reduceRught()
从右到左为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
let values = [1, 2, 3, 4, 5];
let sum = values.reduceRight(function(prev, cur, index, array){
console.log(prev, cur)
return prev + cur;
});
// 5 4
// 9 3
// 12 2
// 14 1
console.log(sum) // 15
在数组中搜索
JavaScript 提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索。
indexOf()
、lastIndexOf()
和includes()
是按严格相等的搜索方法,它们都这些方法都接收两个参数: 要查找的元素和一个可选的起始搜索位置。find()
和findIndex()
方法使用了断言函数。它们接收 3 个参数: 元素、索引和数组本身
indexOf()
indexOf
方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
,它还可以接受第二个参数,表示搜索的开始位置
let arr = Array.from('liuxing');
arr.indexOf('i') // 1
arr.indexOf('i', 2) // 4
lastIndexOf()
lastIndexOf
方法返回给定元素在数组中最后一次出现的位置(从后往前找),如果没有出现则返回-1
。
let arr = Array.from('liuxing');
arr.lastIndexOf('i') // 4
arr.lastIndexOf('i',3) // 1
includes()
includes()
方法用来判断一个数组是否包含一个指定的值,返回布尔值。如果包含则返回 true,否则返回 false。
let arr = Array.from('liuxing');
arr.includes('i') // true
arr.includes('io') // false
find()
find()
方法返回数组中满足断言函数的第一个元素的值。否则返回 undefined。
const people = [
{
name: "LiuXing",
age: 99
},
{
name: "XingLiu",
age: 9
}
];
people.find(element => element.name === 'LiuXing') // {name: "LiuXing", age: 99}
findIndex()
findIndex()
方法返回数组中满足断言函数的第一个元素的位置。否则返回 -1。
const people = [
{
name: "LiuXing",
age: 99
},
{
name: "XingLiu",
age: 9
}
];
people.findIndex(element => element.name === 'LiuXing') // 0
数组的转换方法
所有对象都有 toLocaleString()
、toString()
和 valueOf()
方法。其中,数组的valueOf()
返回的还是数组本身。而 toString()
返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。也就是说,对数组的每个值都会调用其 toString()
方法,以得到最终的字符串。
valueOf()
valueOf
方法是一个所有对象都拥有的方法,表示对该对象求值。数组的valueOf
方法返回数组本身。
let arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]
toString() 与 toLocaleString()
toString
方法也是对象的通用方法,toLocaleString()
与toString()
的区别在于toLocaleString()
与执行环境的地区对应,如日期对象。数组的toString
及toLocaleString()
方法返回数组的字符串形式。
let arr = [10000, 20000, 3000];
arr.toString() // 10000,20000,3000
arr.toLocaleString() // "10,000,20,000,3,000"
join()
join()
方法接收一个作为字符串分隔符的参数,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
let a = [1, 2, 3, 4, 5];
a.join(' ') // '1 2 3 4 5'
a.join() // "1,2,3,4,5"
总结
本文从数组的创建讲到数组的检测,最后讲了数组的各种方法,如数组的遍历方法、搜索方法等。这是一篇偏文档式的文章,需要经常复习。
参考链接
本文完
欢迎可以关注我的公众号,一起玩耍。有技术干货也有扯淡乱谈
左手代码右手砖,抛砖引玉
文档信息
版权声明:署名-非商业性使用-禁止演绎 4.0 国际(CC BY-NC-ND 4.0)
原文链接:https://www.liuxing.io/blog/j...
发表日期:2021年04月17日
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。