JavaScript数组详解

1,136 阅读13分钟

数组(非稀疏)

数组是值的有序集合。每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。

稀疏数组

稀疏数组就是包含从0开始的不连续索引的数组。当前数组length大于元素个数。

a = new Array(5); // 数组没有元素,a.length=5
a = []; // 创建一个空数组,length=0
a[1000] = 0; // 赋值添加一个元素,但是设置length为1001

特性

  • 无类型

数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型。

  • 动态

根据需要它们会增长或缩减,并且在创建数组时无须声明一个固定的大小或者在数组大小变化时无须重新分配空间。

  • 稀疏

数组元素的索引不一定要连续的,它们之间可以有空缺。

ps: 每一个JavaScript数组都有一个length属性。针对非稀疏数组,该属性就是数组元素的个数。针对稀疏数组,length比所有元素的索引要大。

创建数组

  • 使用数组直接量,在方括号中将数组元素用逗号隔开即可。
var empty = [];  // 没有元素的数组
var primes = [2, 3, 5, 7, 11]  // 有5个数值的数组
var misc = [1.1, true, 'a',]  // 3个不同类型的元素和结尾的逗号

var base = 1024;
var table = [base, base+1, base+2, base+3];  // 值使用表达式

var b = [[1, {x:1, y:2}], [2, {x:3, y:4}]];  // 包含对象直接量或其他数组直接量

//如果省略数组直接量中的某个值,省略的元素将被赋予undefined值
var count = [1,,3];  // 数组有3个元素,中间的元素值为undefined
var undefs = [,,];  // 数组有2个元素,都是undefined
  • 使用构造函数Array()
var a = new Array(); // 没有任何元素的空数组,等同于数组直接量[]
var a = new Array(10); // length为10的空数组
var a = new Array(5, 4, 3, 2, 1, 'testing, testing');

数组元素的读和写

使用[]操作符来访问数组中的一个元素。数组的引用位于方括号的左边。方扩号中是一个返回非负整数值的表达式。

var a = ['world'];   // 从一个元素的数组开始
var value = a[0];    // 读第0个元素
a[1] = 3.14;         // 写第1个元素
i = 2;               
a[i] = 3;            // 写第2个元素
a[i + 1] = 'hello';  // 写第3个元素
a[a[i]] = a[0];      // 读第0个和第2个元素,写第3个元素

a[-1.23] = true;     // 这将创建一个名为"-1.23"的属性
a['1000'] = 0;       // 这是数组的第1001个元素
a[1.000]             // 和a[1]相等

数组元素的添加和删除

添加数组元素最简单的方法:为新索引赋值。

a = [];              // 开始是一个空数组
a[0] = 'zero';       // 然后添加元素 

使用push()方法在数组末尾添加一个或多个元素:

a = [];              // 开始是一个空数组
a.push('zero');      // a = ['zero']
a.push('one', 'two');// a = ['zero', 'one', 'two']

删除

  • delete
  • pop()
  • shift()
  • 设置length
var a = [1, 2, 3, 4, 5];
a.length = 3;       // a = [1, 2, 3]
a.length = 0;       // a = [];

数组方法

es3,

  • join()

Array.join()方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认使用逗号。

var a = [1, 2, 3];
a.join();           // => '1,2,3'
a.join(" ")         // => '1 2 3'
a.join("")          // => '123'
var b = new Array(10);
b.join("-");        // => '---------'
  • reverse()

Array.reverse()方法将数组中的元素颠倒顺序,返回逆序的数组。

var a = [1,2,3];
a.reverse();        // => [3,2,1]
  • sort()

Array.sort()方法将数组中的元素排序并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序排序。

var a = new Array('banana', 'cherry', 'apple');
a.sort();           // => ['apple', 'banana', 'cherry']

按照其他方式排序,

var a = [33, 4, 1111, 222];
a.sort();           // => [1111, 222, 33, 4],字母表顺序
a.sort(function(a, b) {
    return a - b;
});                 // => [4, 33, 222, 1111]
a.sort(function(b, a) {
    return b - a;
});                 // => [1111, 222, 33, 4]
  • concat()

Array.concat()方法创建并返回一个新数组,它的元素包括调用concat()的原始数组的元素和concat()的每个参数。如果这些参数中的任何一个自身是数组,则连接的是数组的元素,而非数组本身。

var a = [1, 2, 3];
a.concat(4, 5);             // => [1, 2, 3, 4, 5]
a.concat([4, 5]);           // => [1, 2, 3, 4, 5]
a.concat([4, 5],[6, 7]);    // => [1, 2, 3, 4, 5, 6, 7]
a.concat(4, [5, [6, 7]])    // => [1, 2, 3, 4, 5, [6, 7]]
  • slice()

Array.slice()方法返回指定数组的一个片段或子数组。它的两个参数分别指定了片段的开始和结束的位置。返回的数组包含第一个参数指定的位置和所有到但不含第二个参数指定的位置之间的所有数组元素。如果只指定一个参数,返回的数组将包含从开始位置到数组结尾的所有元素。参数中的负数,表示相对于数组中最后一个元素的位置。

var a = [1, 2, 3, 4, 5];
a.slice(0, 3);              // => [1, 2, 3]
a.slice(3);                 // => [4, 5]
a.slice(1, -1);             // => [2, 3, 4]
a.slice(-3, -2);            // => [3]
  • splice()

Array.splice()方法返回一个由删除元素组成的数组或者如果没有删除元素就返回一个空数组。splice()会修改调用的数组。它的第一个参数指定了插入和(或)删除的起始位置。第二个参数指定了应该从数组中删除的元素的个数。

// 删除
var a = [1, 2, 3, 4, 5, 6, 7, 8];
a.splice(4);                // =>[5,6,7,8]; a = [1,2,3,4]
a.splice(1, 2);             // =>[2,3]; a = [1,4]
a.splice(1, 1);             // =>[4]; a = [1]
// 插入
var a = [1, 2, 3, 4, 5];
a.splice(2, 0, 'a', 'b');   // =>[]; a = [1, 2, 'a', 'b', 3, 4, 5]
a.splice(2, 2, [1, 2], 3);  // =>['a', 'b']; a = [1, 2, [1, 2], 3, 3, 4, 5]
  • push()

Array.push()方法在数组的尾部添加一个或多个元素,并返回数组新的长度。

var stack = [];
stack.push(1, 2);           // => 2; stack=[1, 2]
stack.push([4, 5);          // => 3; stack=[1, 2, [4,5]]
  • pop()

Array.pop()方法删除数组的最后一个元素,减小数组长度并返回它删除的值。

var stack=[1, 2, 3, 4];
stack.pop();                // => 4; stack=[1, 2, 3]
  • unshift()

Array.unshift()方法在数组的头部添加一个或多个元素,并将已存在的元素移动到更高索引的位置来获得足够的空间,最后返回数组新的长度。

var a = [];
a.unshift(1);               // => 1; a=[1];
a.unshift(3, [4, 5]);       // => 3; a=[3, [4, 5], 1]
  • shift()

Array.shift()方法删除数组的第一个元素并将其返回,然后把所有随后的元素下移一个位置来填补数组头部的空缺。

var a = [1, 2, [3, 4]];
a.shift();                  // => 1; a=[2, [3, 4]]
a.shift();                  // => 2; a=[[3, 4]]
  • toString()

toString()方法将其每个元素转化为字符串(如有必要将调用元素的toString()方法)并且输出用逗号分隔的字符串列表。

[1, 2, 3].toString();       // => '1, 2, 3'
['a', 'b', 'c'].toString(); // => 'a, b, c'
[1, [2, 'c']].toString();   // => '1, 2, c'
  • toLocaleString()

toLocaleString()是toString()方法的本地化版本。它调用元素的toLocaleString()方法将每个元素转化为字符串,并且使用本地化(和自定义实现的)分隔符将这些字符串连接起来生成最终的字符串。

es5,这些数组方法都不会修改它们调用的原始数组。

  • forEach()

该方法从头至尾遍历数组,为每个元素调用指定的函数。

var data = [1, 2, 3, 4, 5];
// 计算数组元素的和值
var sum = 0;
data.forEach(function(value){
  sum += value;  
});
sum                         // => 15
// 每个数组元素的值自加1
data.forEach(function(v, i, a){
  a[i] = v + 1;  
});
data                        // => [2, 3, 4, 5, 6]
  • map()

该方法将调用的函数的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值。

var a = [1, 2, 3];
var b = a.map(function(x) { return x*x; }); // b=[1, 4, 9]
  • filter()

该方法返回的数组元素是调用的数组的一个子集。

var a = [5, 4, 3, 2, 1];
var smallvalues = a.filter(function(x) { return x < 3 }); // =>[2, 1]
var everyother = a.filter(function(x, i) { return i%2==0; }); // =>[5, 3, 1]
  • every()

逻辑判定,当且仅当针对数组中的所有元素调用判定函数都返回true,它才返回true。

var a = [1, 2, 3, 4, 5];
a.every(function(x) { return x < 10; }); // => true
a.every(function(x) { return x % 2 === 0}); // => false
  • some()

逻辑判定,当数组中至少有一个元素调用判定返回true,它就返回true;并且当且仅当数值中的所有元素调用判定函数都返回false,它才返回false.

var a = [1, 2, 3, 4, 5];
a.some(function(x) { return x%2 === 0; });  // => true
a.some(isNaN);      // => false: a不包含非数值元素
  • reduce()

该方法使用指定的函数将数组元素进行组合,生成单个值。

var a = [1, 2, 3, 4, 5];
var sum = a.reduce(function(x, y) { reutrn x + y }, 0);     // 数组求和
var product = a.reduce(function(x, y) { return x * y }, 1); // 数组求积
var max = a.reduce(function(x, y) { return (x>y)?x:y });    // 求最大值
  • reduceRight()

该方法的工作原理和reduce()一样,不同的是它按照数组索引从高到低(从右到左)处理数组,而不是从低到高。

  • indexOf()

该方法搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1。第一个参数是需要搜索的值,第二个参数是可选的:它指定数组中的一个索引,从哪里开始搜索。

var a = [0, 1, 2, 1, 0];
a.indexOf(1);       // => 1
a.lastIndexOf(1);   // => 3
a.indeOf(3);        // => -1
  • lastIndexOf()

该方法的工作原理和indexOf一样,搜索方向和indexOf()相反,从尾到头。

es6,

  • ...扩展运算

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

[...[], 1]          // => [1]

数组追加

// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2); // => [0, 1, 2, 3, 4, 5]

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);     // => [0, 1, 2, 3, 4, 5]

[...'hello']            // => [ "h", "e", "l", "l", "o" ]

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // => [1, 2, 3]
  • Array.from()

该方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

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']

Array.from('hello')             // => ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b', 'a']);
Array.from(namesSet)            // => ['a', 'b']

任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

Array.from({ length: 3 });      // => [undefined, undefined, undefined]

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x);    // => [1, 4, 9]
  • Array.of()

该方法用于将一组值,转换为数组。

Array.of(3, 11, 8)  // => [3,11,8]
Array.of(3)         // => [3]
Array.of(3).length  // => 1

Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。

Array.of()          // => []
Array.of(undefined) // => [undefined]
Array.of(1)         // => [1]
Array.of(1, 2)      // => [1, 2]
  • copyWithin()

在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // => [4, 2, 3, 4, 5]

// -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}

// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);  // => Int32Array [3, 4, 5, 4, 5]

// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);   // => Int32Array [4, 2, 3, 4, 5]
  • find()

find()方法用于找出第一个符合条件的数组元素。它的参数是一个回调函数,所有数组元素依次执行该回调函数,直到找出第一个返回值为true的元素,然后返回该元素。如果没有符合条件的元素,则返回undefined。

[1, 4, -5, 10].find((n) => n < 0);  // => -5
  • findIndex()

该方法返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // => 26

可以发现NaN,弥补了数组的indexOf方法的不足。

[NaN].indexOf(NaN);     // => -1

[NaN].findIndex(y => Object.is(NaN, y));    // => 0
  • fill()

该方法使用给定值,填充一个数组。

['a', 'b', 'c'].fill(7);    // => [7, 7, 7]
new Array(3).fill(7);       // => [7, 7, 7]

fill()方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

['a', 'b', 'c'].fill(7, 1, 2);  // => ['a', 7, 'c']

注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr     // => [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr     // => [[5], [5], [5]]
  • entries(),keys() 和 values()

用于遍历数组。们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

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"
  • includes()

该方法返回一个布尔值,表示某个数组是否包含给定的值。

[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
  • flat()

该方法将嵌套的数组“拉平”,变成一维的数组。

[1, 2, [3, 4]].flat();          // => [1, 2, 3, 4]
[1, 2, [3, [4, 5]]].flat();     // => [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2);    // => [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity);   // => [1, 2, 3]
[1, 2, , 4, 5].flat();          // => [1, 2, 4, 5]
  • flatMap()

该方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。

[[2, 4], [3, 6], [4, 8]].flat();        // => [2, 4, 3, 6, 4, 8]
[2, 3, 4].flatMap((x) => [x, x * 2]);   // => [2, 4, 3, 6, 4, 8]