《JavaScript权威指南第7版》第7章 数组

本章介绍了数组,它是JavaScript和大多数其他编程语言中的基本数据类型。数组是值的有序集合。每个值称为一个元素,每个元素在数组中都有一个数字位置,称为其索引。JavaScript 数组是非类型化的:数组元素可以是任何类型的,同一数组的不同元素可以是不同的类型。数组元素甚至可以是对象或其他数组,这允许您创建复杂的数据结构,例如对象数组和数组数组。JavaScript 数组从零开始,使用32位整型索引:第一个元素的索引为 0,最大可能的索引为4294967294(232−2),最大数组大小为4294967295个元素。JavaScript 数组是动态的:它们根据需要增长或缩小,并且在创建数组时不需要为数组声明固定大小,也不需要在大小更改时重新分配数组。JavaScript 数组可能是稀疏的:元素不需要有连续的索引,并且可能存在间隙。每个JavaScript 数组都有一个 length 属性。对于非稀疏数组,此属性指定数组中的元素个数。对于稀疏数组,length 大于任何元素的最大索引。

JavaScript 数组是 JavaScript 对象的一种特殊形式,数组索引实际上只不过是碰巧是整数的属性名。我们将在本章的其他地方详细讨论数组的特殊性。实现通常会优化数组,以便访问数字索引数组元素通常比访问常规对象属性快得多。

数组从 Array.prototype 继承属性,它定义了一套丰富的数组操作方法,如§7.8所述。这些方法大多是通用的,这意味着它们不仅适用于真正的数组,而且适用于任何“类数组对象”。最后,JavaScript字符串的行为类似于字符数组,我们将在§7.10中对此进行讨论。

ES6 引入了一组新的数组类,统称为“类型化数组”。与常规 JavaScript 数组不同,类型化数组具有固定长度和固定的数值元素类型。它们提供对二进制数据的高性能和字节级访问,这将在§11.2中进行介绍。

7.1 创建数组

有几种方法可以创建数组。下面的小节解释了如何使用以下方法创建数组:

  • 数组字面量
  • 在一个可迭代对象上使用 … 展开运算符
  • Array() 构造函数
  • Array.of() 和 Array.from() 工厂方法

7.1.1 数组字面量

到目前为止,创建数组的最简单方法是使用数组字面量,它只是一个方括号内以逗号分隔的数组元素列表。例如:

let empty = []; // 没有元素的数组
let primes = [2, 3, 5, 7, 11]; // 包含5个数字元素的数组
let misc = [ 1.1, true, "a", ]; // 3个不同类型的元素+尾随逗号

数组字面量中的值不必是常量;它们可以是任意表达式:

let base = 1024;
let table = [base, base+1, base+2, base+3];

数组字面量可以包含对象字面量或其他数组字面量:

let b = [[1, {
   x: 1, y: 2}], [2, {
   x: 3, y: 4}]];

如果一个数组字面量在一行中包含多个逗号,且这两个逗号之间没有值,则该数组是稀疏的(见§7.3)。忽略其值的数组元素不存在,但如果查询这些元素,则返回 undefined:

let count = [1,,3]; // 索引0和2处的元素。索引1处没有元素
let undefs = [,,]; // 没有元素但长度为2的数组

数组字面量语法允许可选的尾随逗号,因此[,,]的长度为2,而不是3。

7.1.2 展开运算符

在 ES6 及更高版本中,您可以使用“展开运算符”…,在数组字面量中包含一个数组的元素:

let a = [1, 2, 3];
let b = [0, ...a, 4]; // b == [0, 1, 2, 3, 4]

三个点“展开”数组 a,使其元素成为正在创建的数组字面量中的元素。这就好像 …a 被数组 a 的元素替换了,数组 a 是包含数组字面量的一部分。(请注意,尽管我们将这三个点称为展开运算符,但这不是一个真正的运算符,因为它只能用于数组字面量,以及我们在本书后面将看到的函数调用中。)

展开操作符是创建数组(浅)拷贝的一种简便方法:

let original = [1,2,3];
let copy = [...original];
copy[0] = 0; // 修改拷贝不会更改原始数组
original[0] // => 1

“展开”操作符适用于任何可迭代对象。(可迭代对象是 for/of 循环迭代的对象;我们第一次在§5.4.4中看到了它们,我们将在第12章中看到更多关于它们的内容。)字符串是可迭代的,因此您可以使用展开运算符将任何字符串转换为单字符串数组:

let digits = [..."0123456789ABCDEF"];
digits // => ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]

集合对象(§11.1.1)是可迭代的,因此从数组中删除重复元素的一个简单方法是将数组转换为集合,然后使用展开运算符立即将集合转换回数组:

let letters = [..."hello world"];
[...new Set(letters)] // => ["h","e","l","o"," ","w","r","d"]

7.1.3 Array() 构造函数

另一种创建数组的方法是使用 Array() 构造函数。可以用三种不同的方式调用此构造函数:

  • 无参调用:
let a = new Array();

此方法创建一个没有元素的空数组,它与数组字面量[]等效。

  • 用一个指定长度的数字参数调用它:
let a = new Array(10);

此技术创建具有指定长度的数组。当您预先知道需要多少元素时,可以使用Array()构造函数的这种形式预先分配数组。注意,数组中没有存储任何值,没有数组索引属性“0”、“1”,甚至没有定义数组等。

  • 为数组显式指定两个或多个数组元素或单个非数字元素:
let a = new Array(5, 4, 3, 2, 1, "testing, testing");

在这种形式下,构造函数参数成为新数组的元素。使用数组字面量几乎总是比 Array() 构造函数的用法更简单。

7.1.4 Array.of()

当使用一个数值参数调用 Array() 构造函数时,它将该参数用作数组长度。但当使用多个数值参数调用时,它将这些参数视为要创建的数组的元素。这意味着 Array() 构造函数不能用于创建具有单个数值元素的数组。

在ES6中 Array.of() 函数解决了这个问题:它是一个工厂方法,它创建并返回一个新数组,使用它的参数值(不管有多少个)作为数组元素:

Array.of() // => []; 返回不带参数的空数组
Array.of(10) // => [10]; 可以使用单个数字参数创建数组
Array.of(1,2,3) // => [1, 2, 3]

7.1.5 Array.from()

Array.from 是 ES6 中引入的另一种数组工厂方法。它期望可迭代或类数组的对象作为其第一个参数,并返回一个包含该对象元素的新数组。传入一个可迭代参数,Array.from(iterable)的工作原理与展开运算符 […iterable] 相同。这也是制作数组拷贝的一种简单方法:

let copy = Array.from(original);

Array.from() 也很重要,因为它定义了一种生成类数组对象的真正数组副本的方法。类数组的对象是非数组对象,这些对象具有数值长度属性,其值与名称恰好为整数的属性一起存储。在使用客户端 JavaScript 时,某些 web 浏览器方法的返回值类似于数组,如果首先将它们转换为真正的数组,则使用它们会更容易:

let truearray = Array.from(arraylike);

Array.from() 还接受可选的第二个参数。如果将函数作为第二个参数传递,则在生成新数组时,源对象中的每个元素都将传递给指定的函数,函数的返回值将存储在数组中,而不是原始值。(这与本章后面将介绍的数组的 map() 方法非常相似,但是在构建数组时执行映射比构建数组然后将其映射到另一个新数组更有效。)

7.2 数组元素的读和写

使用 [] 运算符访问数组的元素。对数组的引用应该出现在括号的左边。具有非负整数值的任意表达式应位于括号内。您可以使用此语法来读取和写入数组元素的值。因此,以下都是合法的 JavaScript 语句:

let a = ["world"]; // 从一个元素数组开始
let value = a[0]; // 读第0个元素
a[1] = 3.14; // 写第1个元素
let i = 2;
a[i] = 3; // 写第2个元素
a[i + 1] = "hello"; // 写入位置3的元素
a[a[i]] = a[0]; // 读第0个和第2个元素,写第3个元素

数组的特殊之处在于,当使用小于232–1的非负整数的属性名时,数组会自动为您维护 length 属性的值。例如,在前面的例子中,我们用一个元素创建了一个数组 a。然后我们在索引 1、2 和 3 处赋值。数组的 length 属性根据我们的操作发生了改变,因此:

a.length // => 4

记住数组是一种特殊的对象。用于访问数组元素的方括号与用于访问对象属性的方括号的工作原理相同。JavaScript 将指定的数字数组索引转换为字符串–索引 1 变成字符串“1”-- 然后将该字符串用作属性名。将索引从数字转换为字符串没有什么特别之处:您也可以使用常规对象进行转换:

let o = {
   }; // 创建普通对象
o[1] = "one"; // 用整数索引它
o["1"] // => "one"; 数字和字符串属性名称相同

明确区分数组索引和对象属性名是很有帮助的。所有索引都是属性名,但只有 0 到 232–2 之间整数的属性名才是索引。所有数组都是对象,您可以在它们上创建任何名称的属性。但是,如果使用的属性是数组索引,那么数组会根据需要更新其 length 属性。

请注意,您可以使用负数或非整数的数字为数组编制索引。执行此操作时,数字将转换为字符串,并将该字符串用作属性名称。由于名称不是非负整数,因此它被视为常规对象属性,而不是数组索引。另外,如果用一个恰好是非负整数的字符串为数组编制索引,则它的行为类似于数组索引,而不是对象属性。如果使用与整数相同的浮点数,则同样适用:

a[-1.23] = true; // 这将创建一个名为“-1.23”的属性
a["1000"] = 0; // 这是数组的第1001个元素
a[1.000] = 1; // 数组索引1。与a[1]=1相同;

数组索引只是一种特殊类型的对象属性名,这意味着 JavaScript 数组没有“越界”错误的概念。当您试图查询任何对象的不存在属性时,不会得到错误;你只是得到一个 undefined 值。对于数组和对象也是如此:

let a = [true, false]; // 此数组在索引0和1处有元素
a[2] // => undefined; 此索引中没有元素。
a[-1] // => undefined; 没有此名称的属性。

7.3 稀疏数组

稀疏数组是指元素没有从 0 开始的连续索引的数组。通常,数组的length属性指定数组中的元素数。如果数组是稀疏的,则 length 属性的值大于元素数。稀疏数组可以使用Array() 构造函数创建,也可以简单地通过给给大于当前数组长度的数组索引赋值来创建。

let a = new Array(5); // 没有元素,但是a.length是5。
a = []; // 创建一个没有元素且长度为0的数组。
a[1000] = 0; // 赋值添加一个元素,但将长度设置为1001。

稍后我们将看到,您还可以使用 delete 操作符使数组稀疏。

足够稀疏的数组通常以比密集数组更慢、更节省内存的方式实现,并且在这样的数组中查找元素所需的时间与常规对象属性查找的时间相同。

请注意,当您在数组文本中省略一个值时(在[1,3]中使用重复的逗号),得到的数组是稀疏的,并且省略的元素根本不存在:

let a1 = [,]; // 此数组没有元素,长度为1
let a2 = [undefined]; // 此数组有一个 undefined 的元素
0 in a1 // => false: a1没有索引为0的元素
0 in a2 // => true: a2在索引0处具有 undefined 的值

理解稀疏数组是理解 JavaScript 数组真正本质的一个重要部分。然而,在实践中,您将使用的大多数 JavaScript 数组都不是稀疏的。而且,如果必须使用稀疏数组,那么代码可能会像对待具有 undefined 元素的非稀疏数组一样对待它。

7.4 数组长度

每个数组都有一个 length 属性,正是这个属性使数组不同于常规 JavaScript 对象。对于密集(即非稀疏)的数组,length 属性指定数组中的元素数。它的值比数组中的最高索引多一个:

[].length // => 0: 数组没有元素
["a","b","c"].length // => 3: 最高索引为2,长度为3

当一个数组是稀疏的时,length 属性大于元素的个数,我们只能说长度保证大于数组中每个元素的索引。或者,换句话说,数组(稀疏或不稀疏)永远不会有索引大于或等于其长度的元素。为了保持这种不变性,数组有两种特殊的行为。我们上面描述的第一个:如果为大于或等于数组当前长度的索引 i 的元素赋值,length属性的值将设置为 i+1。

数组实现的第二个特殊行为是,如果将 length 属性设置为小于其当前值的非负整数 n,则索引大于或等于 n 的任何数组元素都将从数组中删除:

a = <
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值