Skip to content
This page has been auto-translated and may contain errors.View in English

数组

docs.scrimba.com

你有一个购物车、一个分数列表、一个群聊中所有人的名字。一个变量持有一个值,所以用这种方式存储一个列表意味着一堆独立的名字:price1price2price3,没有什么干净的方式来遍历它们或询问有多少个。数组就是解决方案。它在一个单一的名字下保存一个有序的值列表,并且它知道自己的长度,所以你可以添加到它、读取任何项目,以及在几行代码内遍历整个东西。

创建和读取数组

一个**数组**是一个有序的值列表。你用方括号写一个,值之间用逗号分隔:

js
const prices = [4, 9, 2, 7];

这是一个变量,prices,包含四个数字。要读取一个单一的值,你使用它的位置,称为它的索引,在方括号中。让几乎所有人最初犯错的事情是:计数从零开始,而不是一。

js
const names = ["玛拉", "夸梅", "由纪"];
console.log(names[0]); // 玛拉
console.log(names[1]); // 夸梅

要问一个数组有多少项,使用 .length

js
console.log(names.length); // 3

因为计数从零开始,最后一项位于 length - 1,而不是 length。所以最后一个名字在这里是 names[2],而 names[3] 会超出范围。

一个**数组是一个在一个名字下的值的有序集合。你用方括号字面量创建一个,并且你通过它的索引**(从零计数的位置)读取任何项目:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices[2]); // 2

零基索引是大多数早期差一错误的来源,所以现在值得在你的脑海中修复:第一项在索引 0,最后一项在 length - 1

js
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7,最后一项

读取一个不存在的索引不会抛出错误。prices[10] 给你 undefined,这是 JavaScript 说"这里没有什么"的方式。那个安静的结果很方便,偶尔也是个陷阱,因为索引中的拼写错误给你 undefined 而不是大声失败。

一个**数组**是用方括号字面量创建的有序的、索引寻址的集合。项目由从零计数的索引读取,.length 报告计数:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7,最后一项

两个属性值得准确命名。首先,.length 不是你提前声明的固定大小;它跟踪使用中的最高索引并在你添加和删除项目时更新。其次,超出范围的读取返回 undefined 而不是抛出,所以差一错误会在后来作为 undefined 流过你的逻辑而显现,而不是在错误点的异常。当你想让失败很明显时,自己守卫索引对 length 进行检查。

数组可以保存混合值类型([1, "two", true]),但一个工作的代码库几乎总是保持一个数组一种东西,因为读取数组的每一段代码然后知道它得到什么。一致性在这里是一个可读性决定,而不是一个语言规则。

Juno创建和读取数组 一个数组是一个方括号中的一个名字下的值列表。用 names[0] 读取一项,并记住计数从零开始,所以第一项是 [0],而不是 [1]。那个零让我困惑了一周,所以每当你的列表差一项时检查它。使用 .length 来计数项目,最后一个在 length - 1
Juno创建和读取数组 一个数组在一个名字下保存一个有序列表,你通过从零计数的索引读取项目。最后一项在 length - 1,这是大多数早期差一错误来自的地方。读取超过末尾返回 undefined 而不是出错,所以一个拼错的索引会默默进行而不是大声进行。当一个值莫名其妙地显示为 undefined 时,记住这一点。
Juno创建和读取数组 方括号字面量、零基索引和 .length 跟踪最高索引的使用而不是你声明的大小。超出范围的读取返回 undefined 而不是抛出,所以差一错误在它咬人之前会传播到下游。当你想让那个失败很明显时,自己守卫索引对 length 进行检查。并保持一个数组一种东西,不是因为语言强制,而是因为数组的每个读者然后知道它保存什么。

改变数组

数组是为了改变而制作的。你最常到达的两个在末尾添加或删除:push 添加一项,pop 删除最后一个。

js
const cart = ["苹果", "面包"];
cart.push("牛奶");
console.log(cart); // ["苹果", "面包", "牛奶"]
cart.pop();
console.log(cart); // ["苹果", "面包"]

匹配的对在开头工作:unshift 添加到前面,shift 删除第一项。

js
cart.unshift("鸡蛋");
console.log(cart); // ["鸡蛋", "苹果", "面包"]

你也可以通过分配到它的索引来直接替换一项:

js
cart[0] = "香蕉";
console.log(cart); // ["香蕉", "苹果", "面包"]

要找到东西,includes 告诉你是或否,indexOf 告诉你它在哪里(或 -1 如果它不在那里):

js
console.log(cart.includes("苹果")); // true
console.log(cart.indexOf("面包")); // 2

一件值得及早知道的事情:一个 const 数组仍然可以改变。 const 阻止你将名字 cart 重新分配给一个全新的数组,但它指向的列表仍然对 pushpop 和索引分配开放。

数组是为了原地修改而构建的。两对方法在大部分工作中,一对在每个末尾:

js
const cart = ["苹果", "面包"];
cart.push("牛奶"); // 添加到末尾
cart.pop(); // 从末尾删除
cart.unshift("鸡蛋"); // 添加到开头
cart.shift(); // 从开头删除

pushpop 是便宜、常见的对;你经常到达它们。shiftunshift 接触前面,这意味着每个其他项目都必须向上或向下移动一个槽位,所以在一个大数组上它们做更多的工作。当顺序是你的选择时倾向末尾。

你可以通过分配到它的索引来覆盖任何位置,你可以找到两种方式的项目:

js
const names = ["玛拉", "夸梅", "由纪"];
names[1] = "普丽亚";
console.log(names.indexOf("由纪")); // 2,或如果不存在则为 -1
console.log(names.includes("玛拉")); // true

当你只关心某事是否存在时到达 includes,当你也需要位置时到达 indexOf。两者都用 === 比较,所以 indexOf("2") 不会找到数字 2

一个 const 数组仍然是完全可变的:const 锁定名字,而不是内容。 你不能将 cart 指向一个新数组,但你可以整天 push 到它持有的那个中。

数组是可变的,核心方法原地改变数组而不是返回一个新的。push/pop 在末尾工作,shift/unshift 在前面:

js
const cart = ["苹果", "面包"];
cart.push("牛奶"); // 返回新长度,改变 cart
const removed = cart.pop(); // 返回删除的项目,改变 cart

末尾和前面在成本上不相等。pushpop 接触一个槽位。shiftunshift 重新索引每个剩余元素,这是对数组长度的线性工作,所以一个反复 shift 一个大数组的热循环是默默地做远比它看起来更多的工作。当顺序在你的控制下时,保持搅动在末尾。

分配到一个索引原地写,搜索按需拆分:includes 用于存在,indexOf 用于位置(不存在时为 -1)。两者都使用严格相等(===),所以类型很重要:

js
const ids = [1, 2, 3];
console.log(ids.indexOf("2")); // -1,字符串不匹配数字
console.log(ids.includes(2)); // true

const 点值得准确声明,因为它是引用模型首次显现的地方:const 将名字绑定到一个数组并禁止重新绑定它,但对数组的内容什么也不说。cart.push(...) 改变对象名字指向的,这是与 cart = [...] 不同的操作。那个区别就是下一部分的全部主题。

Juno改变数组pushpop 在末尾添加和删除;unshiftshift 在开头做同样的事。分配到一个索引,如 cart[0] = "香蕉" 来替换一项,使用 includesindexOf 来找一个。让我惊讶的部分:一个 const 数组仍然可以改变,因为 const 只阻止你将整个数组换成一个新的。
Juno改变数组 两对涵盖大多数编辑:末尾的 push/pop,开头的 shift/unshift。前面对移动每个其他项目,所以当顺序是你的时倾向末尾。includes 检查存在,indexOf 给出位置,两者都用 === 匹配,所以 "2" 不会找到数字 2。而 const 锁定名字,永远不是内容,所以你可以整天 push 到一个 const 数组。
Juno改变数组 这些方法原地改变:push/pop 接触一个槽位,shift/unshift 重新索引整个数组,所以一个保持 shift 一个大数组的循环每次在做线性工作。includesindexOf 都使用 ===,所以一个字符串索引不会匹配一个数字。而 const 只禁止重新绑定名字;cart.push() 改变数组是与 cart = [] 不同的东西。那个间隙正是下一个引用故事。

遍历数组

一旦你有一个列表,你通常想对每一项做什么。读过一个数组的最干净的方式是 for...of,它一次给你一项:

js
const prices = [4, 9, 2, 7];
for (const price of prices) {
  console.log(price);
}
// 4
// 9
// 2
// 7

你不管理一个索引或一个计数器。for...of 给你每个值有序,而你可以随意命名它(price)。如果你已经遇到循环,这是数组最友好的。

也有 forEach,它为每项运行一个小函数一次:

js
const names = ["玛拉", "夸梅", "由纪"];
names.forEach(function (name) {
  console.log("你好 " + name);
});

两者都遍历整个数组。当你想要干净地读取时到达 for...of,当你学习转换数组时你会再次遇到 forEach 和它的表亲。

读数组的现代默认值是 for...of。它直接给你每个值,不需要管理索引,没有差一滑动的机会:

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  total = total + price;
}
console.log(total); // 22

当你想要有序的值和干净的代码时使用 for...of。如果你也需要索引(比如,列出列表),经典计数 for 循环或 forEach 的第二个参数给你它。

forEach 运行一个回调,一个你交给它的函数,每项一次:

js
const names = ["玛拉", "夸梅", "由纪"];
names.forEach((name) => {
  console.log("你好 " + name);
});

forEach 为每项的一个副作用读得很好,如日志。它不能做的是 break 提早出来,所以当你需要中途停止时,使用 for...of。从一个旧数组构建一个新数组是一个不同的工作,有它自己的工具,涵盖在数组方法

对于一个简单的读取,for...of 是习惯用法的选择:它直接迭代值,回避索引算术,并支持 breakcontinue

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  if (price > 8) continue;
  total = total + price;
}
console.log(total); // 13,跳过了 9

forEach 每个元素取一个回调并为副作用读得很干净,但它有两个约束值得在你标准化前知道。它不能 break,所以它总是运行到末尾,而且它忽略一个返回值,所以它是当你试图产生一个结果时错误的工具。对于那个,转换方法(mapfilterreduce)存在,数组方法是它们所属的地方。

js
const names = ["玛拉", "夸梅", "由纪"];
names.forEach((name, index) => {
  console.log(index + ": " + name);
});

回调的第二个参数是索引,这是你在没有手动计数器的情况下恢复位置的方式。经验法则:当你需要控制流或累积到一个外部变量时 for...of,当一个每项副作用更清楚时 forEach,当你想要一个新数组返回而不是一个改变或一个日志时一个转换方法。

Juno遍历数组for...of 是遍历数组的干净方式:它一次给你每项有序地,你永远不接触一个索引。forEach 为每项运行一个小函数一次,这也很方便。两者都通过整个列表,所以当你想直接读过它时选择 for...of。一旦你开始转换它们你会看到更多数组工具。
Juno遍历数组for...of 是读数组的默认值:有序的值,不需要管理索引,当你需要提早停止时 break 工作。forEach 为每项运行一个回调并为日志等副作用读得很漂亮,但它不能 break,所以它总是运行到末尾。从一个旧数组构建一个新数组是一个单独的工作,有它自己的方法。有疑问时,for...of
Juno遍历数组for...of 直接迭代值并支持 breakcontinue,所以当你需要控制流或累积到一个外部变量时它赢。forEach 为每项副作用读得很干净但不能 break 且忽略返回值,这使它在产生一个结果时成为错误的工具。当你想要一个新数组返回时,到达转换方法而不是一个循环。它的第二个回调参数如果你需要位置给你索引。

数组按引用保存

这是让人们惊讶的行为,所以早点看到它值得。当你复制一个数组变量到另一个名字时,你没有得到第二个数组。两个名字指向同一个。

js
const original = [1, 2, 3];
const copy = original;
copy.push(4);
console.log(original); // [1, 2, 3, 4]

Push 到 copy 改变了 original,因为那里只曾经有一个数组。= 给第二个名字同一个列表,而不是它的一个新副本。这是人们说数组按引用保存时的意思:变量持有一个指向列表的指针,而不是列表本身。

要得到一个真实的、单独的副本,用 ... 传播数组到一个新的:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3],未被触及

一个数字或一个字符串按值保存:复制变量复制值。一个数组不同。它按引用保存,所以复制变量复制指针,两个名字最终指向同一个数组。

js
const original = [1, 2, 3];
const shared = original;
shared.push(4);
console.log(original); // [1, 2, 3, 4],同一个数组

这是同一个事实在早前 const 注释后面:一个数组是一个对象,一个变量持有对那个对象的引用,而不是对象本身。通过任一名字改变通过改变那个它们共享的一个数组。

当你想要一个独立副本时,传播它到一个新数组。... 复制每项越过:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3],单独的

这是一个副本:新数组是它自己的列表,但如果项目本身是数组,两个副本会仍然共享那些内部数组。用简单的数字和字符串,浅是你需要的全部。

原始值(数字、字符串、布尔值)按值保存;数组是对象并按引用保存。一个"包含"一个数组的变量实际上持有对它的一个引用,所以分配复制引用,指向那个数组的每个名字看到每个改变:

js
const original = [1, 2, 3];
const alias = original;
alias.push(4);
console.log(original); // [1, 2, 3, 4]
console.log(original === alias); // true,同一个数组

这就是为什么 const 和可变性是单独的关注:const 修复引用,改变改变引用指向的什么。它也是倾向于不可变性的论证,产生一个新数组而不是改变一个原地。共享可变状态意味着一个改变它接收的数组的函数默默地为那个引用的每个其他持有者改变它,这是一类很难追踪的错误。返回一个新数组保持每个调用者的数据是它自己的。

传播操作符制作一个副本:一个新的外部数组,其中复制了同样的项目。

js
const original = [1, 2, 3];
const copy = [...original];
console.log(copy === original); // false,不同的数组

浅是值得注意的词。新数组是独立的,但嵌套在它内部的数组仍然由引用共享,所以一个深结构需要比传播更多。与数组的原始值,这就是你到目前为止拥有的全部,传播是一个完整的副本。

至于首先选择结构:当你有一个相似的东西的有序列表且位置或计数很重要时一个数组是正确的工具,一个价格的队列,一个名字的序列。当你反而想通过一个有意义的键而不是一个位置查找值时,这是一个对象是什么,以及这个数组工作很多最终在的地方是一个函数

JunoArrays are held by reference 复制一个用 = 的数组变量不会制作第二个数组;两个名字指向同一个,所以通过任一的改变显示在两个中。这个比一次以上咬了我。当你想要一个真实单独的副本时,用 [...original] 传播它,这构建一个你可以独自改变的新鲜数组。
JunoArrays are held by reference 数字和字符串按值复制;数组按引用复制,所以 const b = a 给你一个数组的两个名字,通过任一改变打到两个。这是 const 数组仍然可变的同样原因。对一个独立副本使用 [...original],记住它是浅的,这对数字和字符串的数组恰好是你想要的。
JunoArrays are held by reference 数组是由引用保存的对象,所以分配复制指针,每个名字看到每个改变。那分裂 const(修复引用)从可变性(改变目标),以及它倾向于一个新数组对一个原地改变的情况:一个改变一个接收的数组的函数为持有那个引用的每个人改变它。传播制作一个浅副本,在顶层独立但共享任何嵌套数组。当顺序和计数很重要且一个对象当你想通过键查找东西时到达一个数组。

数组从这里去哪

一个数组给你一个在一个名字下的有序列表:用 [] 制作它,从零通过索引读它,用 pushpop 改变它,用 for...of 遍历它。一个通过这个章节进行的观念是数组按引用保存,所以复制变量共享列表而不是复制它。

自然下一步是把一个数组变成另一个,过滤一个列表,转换每一项,加它全部,不手工写循环。那是数组方法。当你需要通过一个名字而不是一个位置查找值时,对象是那个结构。