原文链接:http://javascript.info/array,translate with ❤️ by zhangbao.
对象用来一系列的键值集合。
但经常我们需要一个有序集合,比如第一个、第二个、第三个等等元素。例如,我们需要存储一系列的东西:用户,物品,HTML 元素等。
在这里使用一个对象是不方便的,因为它没有提供任何方法来管理元素的顺序。我们不能在现有的属性之间插入一个新的属性。对象并不是用于这种用途的。
有一个特殊的数据结构叫 Array,也就是数组,用来存储有序集合。
声明
创建一个空数组有两种语法:
let arr = new Array();
let arr = [];
我们几乎都用第二种语法。我们可以在括号中提供初始元素:
let fruits = ["Apple", "Orange", "Plum"];
数组元素是编过号的,从零开始。
我们可以用方括号来表示一个元素:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum
我们可以替换一个元素:
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
或者在数组中添加一个新的:
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"]
数组中元素的总数可以通过 legth 属性获得:
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits.length ); // 3
我们也可以使用 alert 来显示整个数组。
let fruits = ["Apple", "Orange", "Plum"];
alert( fruits ); // Apple,Orange,Plum
数组可以存储任何类型的元素。
例如:
// mix of values
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// get the object at index 1 and then show its name
alert( arr[1].name ); // John
// get the function at index 3 and run it
arr[3](); // hello
tip:尾逗号
一个数组,就像一个对象一样,可能以逗号结尾:
let fruits = [
"Apple",
"Orange",
"Plum",
];
“拖尾逗号”的风格写法,使得插入/删除条目变得更容易,因为所有的行都是一样的。
pop/push,shift/unshift 方法
队列)是数组最常见的用法之一。在计算机科学中,这意味着一个有序的元素集合,它支持两个操作:
push 在数组末尾添加元素。
shift 从数组头部删除一个元素,推进队列,使第 2 个元素成为第 1 个元素……。
数组支持这两种操作。
在实践中,我们经常遇到这种情况。例如,需要在屏幕上显示的一条消息队列。
数组还有另一个用例——名为栈)的的数据结构。
它支持两种操作:
push 在数组末尾添加元素。
pop 从数组末尾删除一个元素。
对于栈来说,最新的推送项目首先被弹出,这也被称为LIFO(后进先出)原则。对于队列,我们有FIFO(先入先出)。
JavaScript中 的数组既可以作为队列,也可以作为堆栈。它们允许从开始或结束添加/删除元素。
在计算机科学中,允许它的数据结构称为deque。
处理数组末尾元素的方法:
pop
提取数组的最后一个元素并返回它:
let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.pop() ); // remove "Pear" and alert it
alert( fruits ); // Apple, Orange
push
把元素附加到数组的末尾:
let fruits = ["Apple", "Orange"];
fruits.push("Pear");
alert( fruits ); // Apple, Orange, Pear
调用 fruits.push(…) 等同于 fruits[fruits.length] = …。
处理数组开头元素的方法:
shift
提取数组的第一个元素并返回它:
let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.shift() ); // remove Apple and alert it
alert( fruits ); // Orange, Pear
unshift
将元素添加到数组的开头:
let fruits = ["Orange", "Pear"];
fruits.unshift('Apple');
alert( fruits ); // Apple, Orange, Pear
push 和 unshift 方法也可以同时添加多个元素:
let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );
内部构件
数组是一个特殊的对象。使用方括号来获取数组元素的方式 arr[0] 实际上就是来自于对象语法。只不过这里使用数字当键罢了。
它们扩展了对象,并提供了操作有序列表需要的一些方法,还有 length 属性。但是它的核心还是一个对象。
记住,在 JavaScript 中,有 7 中基本数据类型。数组是一个对象,因此它的行为就像一个对象。
例如,数组是通过引用传递的。
let fruits = ["Banana"]
let arr = fruits; // copy by reference (two variables reference the same array)
alert( arr === fruits ); // true
arr.push("Pear"); // modify the array by reference
alert( fruits ); // Banana, Pear - 2 items now
但是,让数组变得特别的是它们的内部表示。引擎试图将其元素存储在相邻的内存区域中,一个接一个,就像本章的插图所描述的那样,还有其他的优化,使数组工作得非常快。
但是,如果我们放弃使用数组作为“有序集合”,并开始使用它,就好像它是一个普通的对象一样,它们都会中断。
例如,技术上我们可以这样做:
let fruits = []; // make an array
fruits[99999] = 5; // assign a property with the index far greater than its length
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 方法很慢。
为什么在数组的末尾工作比开始时要快?让我们看看在执行过程中会发生什么:
fruits.shift(); // take 1 element from the start
用数字 0 来删除和删除元素是不够的。其他元素也需要重新编号。
shift 操作必须做 3 件事:
删除索引 0 处的元素。
将所有元素向左移动,重新编号索引,1 到 0,2 到 1…….
更新 length 属性。
数组中的元素越多,移动它们的时间就越多,内存中的操作也越多。
类似的事情也发生在 unshift:在数组的开头添加一个元素,我们首先需要将现有的元素移动到右边,增加它们的索引。
那么 push/pop 方法呢?他们不需要移动任何东西。为了从末端提取一个元素,pop 方法会清除索引并缩短 length 属性值。
pop 方法的操作:
fruits.pop(); // take 1 element from the end
pop 方法不需要移动任何东西,因为其他元素保留它们的索引。这就是为什么它的速度非常快。
push 方法与之类似。
循环
循环数组项的最古老的方法之一是for循环通过递增索引:
let arr = ["Apple", "Orange", "Pear"];
for (let i = 0; i < arr.length; i++) {
alert( arr[i] );
}
但是对于数组来说,还有另一种形式的循环形式 for..of。
let fruits = ["Apple", "Orange", "Plum"];
// iterates over array elements
for (let fruit of fruits) {
alert( fruit );
}
for..of 它不提供当前元素的数量,只是它的值,但在大多数情况下,这就足够了。这是短的。
从技术上讲,因为数组是对象,所以它也可以用于 for..in:
let arr = ["Apple", "Orange", "Pear"];
for (let key in arr) {
alert( arr[key] ); // Apple, Orange, Pear
}
这种做法很坏。有几个潜在的问题:
- for..in 循环遍历所有属性,不仅是数字属性。
在浏览器和其他环境中都有所谓的类数组对象,它们看起来像数组。也就是说,它们具有长度和索引属性,但它们也可能有其他非数值属性和方法,这是我们通常不需要的。for..in 循环中会列出它们。因此,如果我们需要使用类数组对象,那么这些额外的属性就会成为一个问题。
- for..in 语句针对普通对象而不是数组进行了优化,因此它的速度回慢 10-100 倍。当然,它仍然非常快。这种加速可能只会在瓶颈中起作用,或者只是无关紧要。但我们仍然应该意识到其中的区别。
一般来说,我们不应该用 for..in 遍历数组。
length 属性
当我们修改数组时,length 属性会自动更新。确切地说,它实际上不是数组中值的计数,而是最伟大的数字索引加 1。
例如,一个带有大索引的单一元素给出了一个很大的长度:
let fruits = [];
fruits[123] = "Apple";
alert( fruits.length ); // 124
注意,我们通常不这样使用数组。
关于 length 属性的另一个有趣的地方是它是可写的。
如果我们手动增加它,没有什么有趣的事情发生。但是如果我们减少它,数组就会被截断。这个过程是不可逆转的,这里有个例子:
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // truncate to 2 elements
alert( arr ); // [1, 2]
arr.length = 5; // return length back
alert( arr[3] ); // undefined: the values do not return
所以,清除数组最简单的方法是:arr.length = 0;。
new Array()
还有一种语法可以创建一个数组:
let arr = new Array("Apple", "Pear", "etc");
它很少被使用,因为方括号更短。此外,它还有一个棘手的特性。
如果用一个单独的参数来调用 new Array 时,那么它就会创建一个没有条目的数组,但是使用给定的长度。
让我们看看他是如何在脚上开枪的:
let arr = new Array(2); // will it create an array of [2] ?
alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
在上面的代码中,new Array(number) 创建出来的数组的所有元素都时 undefined。
为了避免这种意外,我们通常使用方括号,除非我们真的知道我们在做什么。
多维数组
数组也可以有数组的项。我们可以用它来做多维数组,来存储矩阵:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // the central element
toString
阵列有自己的 toString 方法\实现,它返回一个逗号分隔的元素列表。
例如:
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
或者,可以试试:
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
数组没有 Symbol.toPrimitive,也没有可行的 valueOf,它们只实现了 toString 转换,所以 [] 变成了一个空字符串,[1]变成了“1”,[1,2]变成了“1,2”。
当使用两元“+”操作符与字符串相加时,它也会将其转换为字符串,因此下一步是这样的:
alert( "" + 1 ); // "1"
alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21"
总结
数组时一个特殊的对象,适合存储和管理有序集合项目。
- 声明
// square brackets (usual)
let arr = [item1, item2...];
// new Array (exceptionally rare)
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) 不要用。
我们将返回数组并研究更多操作数组的诸如添加、删除、提取元素和排序的方法,将在章节《数组方法》。
(完)