Javascript数组

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) – 永远不要使用.

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值