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 操作需要做三件事情:
- 删除索引为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 循环迭代所有的属性,不仅是数值索引。
在浏览器或其他环境中,有所谓的“类数组”对象,看上去很象数组,有length和索引属性,但他们也可能有其他非数值属性和方法,通常这些不是我们所需的,但 for..in 循环将全部列出他们。所以如果我们循环类数组对象,那些额外的属性可能是问题。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