原文链接:http://javascript.info/array,translate with ❤️ by zhangbao.

对象用来一系列的键值集合。

但经常我们需要一个有序集合,比如第一个、第二个、第三个等等元素。例如,我们需要存储一系列的东西:用户,物品,HTML 元素等。

在这里使用一个对象是不方便的,因为它没有提供任何方法来管理元素的顺序。我们不能在现有的属性之间插入一个新的属性。对象并不是用于这种用途的。

有一个特殊的数据结构叫 Array,也就是数组,用来存储有序集合。

声明

创建一个空数组有两种语法:

  1. let arr = new Array();
  2. let arr = [];

我们几乎都用第二种语法。我们可以在括号中提供初始元素:

  1. let fruits = ["Apple", "Orange", "Plum"];

数组元素是编过号的,从零开始。

我们可以用方括号来表示一个元素:

  1. let fruits = ["Apple", "Orange", "Plum"];
  2. alert( fruits[0] ); // Apple
  3. alert( fruits[1] ); // Orange
  4. alert( fruits[2] ); // Plum

我们可以替换一个元素:

  1. fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]

或者在数组中添加一个新的:

  1. fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"]

数组中元素的总数可以通过 legth 属性获得:

  1. let fruits = ["Apple", "Orange", "Plum"];
  2. alert( fruits.length ); // 3

我们也可以使用 alert 来显示整个数组。

  1. let fruits = ["Apple", "Orange", "Plum"];
  2. alert( fruits ); // Apple,Orange,Plum

数组可以存储任何类型的元素。

例如:

  1. // mix of values
  2. let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
  3. // get the object at index 1 and then show its name
  4. alert( arr[1].name ); // John
  5. // get the function at index 3 and run it
  6. arr[3](); // hello

tip:尾逗号

一个数组,就像一个对象一样,可能以逗号结尾:

  1. let fruits = [
  2. "Apple",
  3. "Orange",
  4. "Plum",
  5. ];

“拖尾逗号”的风格写法,使得插入/删除条目变得更容易,因为所有的行都是一样的。

pop/push,shift/unshift 方法

队列)是数组最常见的用法之一。在计算机科学中,这意味着一个有序的元素集合,它支持两个操作:

  • push 在数组末尾添加元素。

  • shift 从数组头部删除一个元素,推进队列,使第 2 个元素成为第 1 个元素……。

数组 - 图1

数组支持这两种操作。

在实践中,我们经常遇到这种情况。例如,需要在屏幕上显示的一条消息队列。

数组还有另一个用例——名为)的的数据结构。

它支持两种操作:

  • push 在数组末尾添加元素。

  • pop 从数组末尾删除一个元素。

数组 - 图2

对于栈来说,最新的推送项目首先被弹出,这也被称为LIFO(后进先出)原则。对于队列,我们有FIFO(先入先出)。

JavaScript中 的数组既可以作为队列,也可以作为堆栈。它们允许从开始或结束添加/删除元素。

在计算机科学中,允许它的数据结构称为deque

处理数组末尾元素的方法:

pop

提取数组的最后一个元素并返回它:

  1. let fruits = ["Apple", "Orange", "Pear"];
  2. alert( fruits.pop() ); // remove "Pear" and alert it
  3. alert( fruits ); // Apple, Orange

push

把元素附加到数组的末尾:

  1. let fruits = ["Apple", "Orange"];
  2. fruits.push("Pear");
  3. alert( fruits ); // Apple, Orange, Pear

调用 fruits.push(…) 等同于 fruits[fruits.length] = …。

处理数组开头元素的方法:

shift

提取数组的第一个元素并返回它:

  1. let fruits = ["Apple", "Orange", "Pear"];
  2. alert( fruits.shift() ); // remove Apple and alert it
  3. alert( fruits ); // Orange, Pear

unshift

将元素添加到数组的开头:

  1. let fruits = ["Orange", "Pear"];
  2. fruits.unshift('Apple');
  3. alert( fruits ); // Apple, Orange, Pear

push 和 unshift 方法也可以同时添加多个元素:

  1. let fruits = ["Apple"];
  2. fruits.push("Orange", "Peach");
  3. fruits.unshift("Pineapple", "Lemon");
  4. // ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
  5. alert( fruits );

内部构件

数组是一个特殊的对象。使用方括号来获取数组元素的方式 arr[0] 实际上就是来自于对象语法。只不过这里使用数字当键罢了。

它们扩展了对象,并提供了操作有序列表需要的一些方法,还有 length 属性。但是它的核心还是一个对象。

记住,在 JavaScript 中,有 7 中基本数据类型。数组是一个对象,因此它的行为就像一个对象。

例如,数组是通过引用传递的。

  1. let fruits = ["Banana"]
  2. let arr = fruits; // copy by reference (two variables reference the same array)
  3. alert( arr === fruits ); // true
  4. arr.push("Pear"); // modify the array by reference
  5. alert( fruits ); // Banana, Pear - 2 items now

但是,让数组变得特别的是它们的内部表示。引擎试图将其元素存储在相邻的内存区域中,一个接一个,就像本章的插图所描述的那样,还有其他的优化,使数组工作得非常快。

但是,如果我们放弃使用数组作为“有序集合”,并开始使用它,就好像它是一个普通的对象一样,它们都会中断。

例如,技术上我们可以这样做:

  1. let fruits = []; // make an array
  2. fruits[99999] = 5; // assign a property with the index far greater than its length
  3. fruits.age = 25; // create a property with an arbitrary name

这是可能的,因为数组是它们的基础上的对象。我们可以向它们添加任何属性。

但是引擎会看到我们使用的是一个普通的对象。特定于数组的优化不适合这种情况,并且将被关闭,它们的好处将消失。

误用数组的方法:

  • 添加非数字属性,像 arr.test = 5。

  • 挖坑:添加 arr[0] 和 arr[1000](它们之间却无任何职值)。

  • 以反序添加数组元素,像 arr[1000],arr[999] 等。

请将数组看作是处理有序数据的特殊结构。他们提供了特殊的方法。在 JavaScript 引擎中小心地调优数组,以处理连续的有序数据,请用这种方式使用。如果你需要任意的键,你很可能需要一个普通的对象 {}。

性能

push/pop 方法很快,shift/unshift 方法很慢。

数组 - 图3

为什么在数组的末尾工作比开始时要快?让我们看看在执行过程中会发生什么:

  1. fruits.shift(); // take 1 element from the start

用数字 0 来删除和删除元素是不够的。其他元素也需要重新编号。

shift 操作必须做 3 件事:

  1. 删除索引 0 处的元素。

  2. 将所有元素向左移动,重新编号索引,1 到 0,2 到 1…….

  3. 更新 length 属性。

数组 - 图4

数组中的元素越多,移动它们的时间就越多,内存中的操作也越多。

类似的事情也发生在 unshift:在数组的开头添加一个元素,我们首先需要将现有的元素移动到右边,增加它们的索引。

那么 push/pop 方法呢?他们不需要移动任何东西。为了从末端提取一个元素,pop 方法会清除索引并缩短 length 属性值。

pop 方法的操作:

  1. fruits.pop(); // take 1 element from the end

数组 - 图5

pop 方法不需要移动任何东西,因为其他元素保留它们的索引。这就是为什么它的速度非常快。

push 方法与之类似。

循环

循环数组项的最古老的方法之一是for循环通过递增索引:

  1. let arr = ["Apple", "Orange", "Pear"];
  2. for (let i = 0; i < arr.length; i++) {
  3. alert( arr[i] );
  4. }

但是对于数组来说,还有另一种形式的循环形式 for..of。

  1. let fruits = ["Apple", "Orange", "Plum"];
  2. // iterates over array elements
  3. for (let fruit of fruits) {
  4. alert( fruit );
  5. }

for..of 它不提供当前元素的数量,只是它的值,但在大多数情况下,这就足够了。这是短的。

从技术上讲,因为数组是对象,所以它也可以用于 for..in:

  1. let arr = ["Apple", "Orange", "Pear"];
  2. for (let key in arr) {
  3. alert( arr[key] ); // Apple, Orange, Pear
  4. }

这种做法很坏。有几个潜在的问题:

  1. for..in 循环遍历所有属性,不仅是数字属性。

在浏览器和其他环境中都有所谓的类数组对象,它们看起来像数组。也就是说,它们具有长度和索引属性,但它们也可能有其他非数值属性和方法,这是我们通常不需要的。for..in 循环中会列出它们。因此,如果我们需要使用类数组对象,那么这些额外的属性就会成为一个问题。

  1. for..in 语句针对普通对象而不是数组进行了优化,因此它的速度回慢 10-100 倍。当然,它仍然非常快。这种加速可能只会在瓶颈中起作用,或者只是无关紧要。但我们仍然应该意识到其中的区别。

一般来说,我们不应该用 for..in 遍历数组。

length 属性

当我们修改数组时,length 属性会自动更新。确切地说,它实际上不是数组中值的计数,而是最伟大的数字索引加 1。

例如,一个带有大索引的单一元素给出了一个很大的长度:

  1. let fruits = [];
  2. fruits[123] = "Apple";
  3. alert( fruits.length ); // 124

注意,我们通常不这样使用数组。

关于 length 属性的另一个有趣的地方是它是可写的。

如果我们手动增加它,没有什么有趣的事情发生。但是如果我们减少它,数组就会被截断。这个过程是不可逆转的,这里有个例子:

  1. let arr = [1, 2, 3, 4, 5];
  2. arr.length = 2; // truncate to 2 elements
  3. alert( arr ); // [1, 2]
  4. arr.length = 5; // return length back
  5. alert( arr[3] ); // undefined: the values do not return

所以,清除数组最简单的方法是:arr.length = 0;。

new Array()

还有一种语法可以创建一个数组:

  1. let arr = new Array("Apple", "Pear", "etc");

它很少被使用,因为方括号更短。此外,它还有一个棘手的特性。

如果用一个单独的参数来调用 new Array 时,那么它就会创建一个没有条目的数组,但是使用给定的长度。

让我们看看他是如何在脚上开枪的:

  1. let arr = new Array(2); // will it create an array of [2] ?
  2. alert( arr[0] ); // undefined! no elements.
  3. alert( arr.length ); // length 2

在上面的代码中,new Array(number) 创建出来的数组的所有元素都时 undefined。

为了避免这种意外,我们通常使用方括号,除非我们真的知道我们在做什么。

多维数组

数组也可以有数组的项。我们可以用它来做多维数组,来存储矩阵:

  1. let matrix = [
  2. [1, 2, 3],
  3. [4, 5, 6],
  4. [7, 8, 9]
  5. ];
  6. alert( matrix[1][1] ); // the central element

toString

阵列有自己的 toString 方法\实现,它返回一个逗号分隔的元素列表。

例如:

  1. let arr = [1, 2, 3];
  2. alert( arr ); // 1,2,3
  3. alert( String(arr) === '1,2,3' ); // true

或者,可以试试:

  1. alert( [] + 1 ); // "1"
  2. alert( [1] + 1 ); // "11"
  3. alert( [1,2] + 1 ); // "1,21"

数组没有 Symbol.toPrimitive,也没有可行的 valueOf,它们只实现了 toString 转换,所以 [] 变成了一个空字符串,[1]变成了“1”,[1,2]变成了“1,2”。

当使用两元“+”操作符与字符串相加时,它也会将其转换为字符串,因此下一步是这样的:

  1. alert( "" + 1 ); // "1"
  2. alert( "1" + 1 ); // "11"
  3. alert( "1,2" + 1 ); // "1,21"

总结

数组时一个特殊的对象,适合存储和管理有序集合项目。

  • 声明
  1. // square brackets (usual)
  2. let arr = [item1, item2...];
  3. // new Array (exceptionally rare)
  4. let arr = new Array(item1, item2...);

new Array(number) 创建一个指定长度的数组,没有元素。

  • length 属性值表示的是数组长度,或者更精确的说是最大的索引值加 1。它是由数组方法自动调整的。

  • 如果我们手工减小 length 属性值,就会发现截取了数组。

我们可以使用一个数组作为deque,它有以下操作:

  • push(…items) 向末尾添加元素。

  • pop() 弹出数组最后一个元素,并返回它。

  • shift() 删除数组最头的一个元素,并且返回它。

  • unshift(…items) 在数组开头添加元素。

循环一个数组:

  • for (let i = 0; i < arr.length; i++) 很快,兼容老浏览器。

  • for (let item of arr) 现代的遍历元素的方法。

  • for (let i in arr) 不要用。

我们将返回数组并研究更多操作数组的诸如添加、删除、提取元素和排序的方法,将在章节《数组方法》。

(完)