Skip to main content
 首页 » 编程设计

Javascript数组

2022年07月19日155zengkefu

Javascript数组

对象允许存储键值集合,这非常好,但通常我们需要有序集合,通过它可以获得第一、第二、第三元素等。举例,我们需要存储一些列表:用户、货物以及HTML元素等。
这里使用对象则不方便,因为没有提供方法管理元素顺序。我们不能在已存在元素之间插入新的元素。对象不适合这样使用。
Javascript提供了一个特殊的Array数据结构,可以存储顺序集合。

申明数组

有两种语法可以创建空数组:
let arr = new Array();
let arr = [];

大多数时,第二种语法更常用。我们也可以在括号中初始化一些元素:

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

数组元素是可数的,从0开始。通过在方括号中指定数值,我们可以获取元素。

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

数组中所有元素的个数,通过length属性获取:

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 

结尾的逗号
数组和对象一样,可以以逗号结尾:

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

结尾逗号形式使得插入、删除元素更容易,因为所有行变得相似。

数组方法 pop/push, shift/unshift

队列是最常见的使用数组方式。计算机科学中,有序集合支持两种操作:
- push 在结尾追加一个元素。
- shift 获得开头第一个元素,推进后面所有元素,所以原来第二个元素变成第一个。

数组支持两种操作。

实际中,我们经常遇到,如一个消息队列需要显示在屏幕上。

还有其他数组使用场景,数据结构中称为堆栈。

栈支持两种操作:

  • push 在结尾增加元素。
  • pop 在结尾弹出元素。

所以新元素增加和弹出总是在结尾。
堆栈通常描绘为一堆卡片,新卡片增加到顶部,或从顶部拿走。

对堆栈来说,最新入栈元素首先出栈,也被称为LIFO(后进先出)原则。队列则为FIFO(先进先出)。

Javascript中数组可以同时表示队列和数组。允许增加、删除元素至开始或结尾位置。

在数组结尾操作的方法

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 

但是,使数组真正特别的是其内部表示。Javascript引擎尝试存储它的元素在连续的内存区域,一个接着一个,如下面的插图所示,还有其他一些优化,使其工作非常快。

如果我们停止使用数组作为有序集合,而作为普通对象使用,则上述优势被则不复存在。

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

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 

这时可能的,因为数组本质上是对象,我们能给他们增加属性。
但是Javascript引擎看到我们把数组作为普通对象使用,数组特定的优化则不适合这种场景被取消,优势消失。

这种方式是乱用数组:
- 增加非数值属性,如: arr.test = 5 .
- 留空隙,如: 增加 arr[0] ,然后增加 arr[1000] (他们之间保留为空)
- 使用相反的顺序填充: 如: arr[1000],arr[999] 等。

请考虑数组作为特殊的数据结构,作为顺序数据工作,并为之提供了特殊的方法。Javascript引擎对数组作为连续顺序数据集合有细心的优化,请用正确的方式使用。如果你需要任意键,很可能你需要普通对象 {} 。

性能

方法 push/pop 更快,而 shift/unshift 则慢。

为什么在数组结尾操作比在开头操作更快?让我们看看执行过程中发生了什么?

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

不仅只是删除或提取第一个元素,其他元素也需要重新编号。 shift 操作需要做三件事情:

  1. 删除索引为0的元素。
  2. 向左移动所有元素,重新编号,依次把1变成0,2变成1等。
  3. 更新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 
} 

但这实际上是一个坏注意,因为有潜在的问题:

  1. for..in 循环迭代所有的属性,不仅是数值索引。
    在浏览器或其他环境中,有所谓的“类数组”对象,看上去很象数组,有length和索引属性,但他们也可能有其他非数值属性和方法,通常这些不是我们所需的,但 for..in 循环将全部列出他们。所以如果我们循环类数组对象,那些额外的属性可能是问题。

  2. for..in 循环对普通对象有优化,不是数组,因为速度会慢10~100倍。当然,仍会很快。速度仅在瓶颈处会有问题,而不是无关紧要的事情上,但我们最好还是要知道两者差异。

一般情况下,我们不应该使用 for ..in 循环数组。

length

当我们修改数组是,length属性自动更新。准确的说,确实不是数组值的个数,而是数组索引最大值加一。

举例,给数组一个很大索引长度值,仅指定单个元素:

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 属性可以是数组的长度,更精确的说,是最大数值索引加一,其根据数据操作方法自动调整。
  • 如果手动缩短length属性,数组元素被彻底删除。

我们能使用数据作为队列,提供了下面一些方法:

  • push(…items) 在结尾增加元素.
  • pop() 从结尾删除元素,并返回该元素.
  • shift() 从开始处删除元素,并返回该元素.
  • unshift(…items) 在数据开头增加元素.

循环数组元素方式有:

  • for(let i=0; i<arr.length; i++) – 操作最快,兼容旧版本浏览器.
  • for(let item of arr) – 新的语法,仅能获得元素.
  • for(let i in arr) – 永远不要使用.

后面我们继续讲解数据的方法,关于增加、删除、抽取元素、排序方法等。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/72511044