数组

在本课程中,我们将了解数组——一种在单个变量名下存储数据列表的巧妙方法。在这里,我们探讨了它为什么有用,然后学习如何创建数组、检索、添加和删除数组中存储的项,以及更多内容。

预备知识 理解 HTMLCSS 基础知识。熟悉基本数据类型,例如数字和字符串,如之前的课程所述。
学习成果
  • 什么是数组——一个包含变量列表的结构。
  • 数组的语法——[a, b, c] 和访问器语法 myArray[x]
  • 使用 myArray[x] = y 修改数组值。
  • 使用常见的属性和方法操作数组,例如 lengthpush()pop()join()split()
  • 高级数组方法,例如 forEach()map()filter()

什么是数组?

数组通常被描述为“类列表对象”;它们基本上是包含存储在列表中的多个值的单个对象。数组对象可以存储在变量中,并以与其他任何类型的值大致相同的方式处理,不同之处在于我们可以单独访问列表中的每个值,并使用该列表做一些超级有用和高效的事情,例如遍历它并对每个值执行相同的操作。也许我们有一个存储在数组中的一系列产品项及其价格,我们希望遍历所有这些项并将它们打印在发票上,同时将所有价格加起来并在底部打印出总价。

如果我们没有数组,我们就必须将每个项存储在单独的变量中,然后单独为每个项调用执行打印和添加的代码。这将大大延长编写时间,效率更低,并且更容易出错。如果我们要将 10 个项添加到发票中,就已经很烦人了,但是 100 个项,或者 1000 个呢?我们将在本文后面回到这个例子。

与之前的文章一样,让我们通过在浏览器开发者控制台中输入一些示例来学习数组的真正基础知识。

注意:Scrimba 的旁注:数组简介 scrim MDN 学习合作伙伴 提供了一个有用的交互式数组介绍,其中包含示例演练和测试您知识的挑战。

创建数组

数组由方括号和逗号分隔的项组成。

  1. 假设我们想将一个购物清单存储在数组中。将以下代码粘贴到控制台中

    js
    const shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
    console.log(shopping);
    
  2. 在上面的例子中,每个项都是一个字符串,但在数组中我们可以存储各种数据类型——字符串、数字、对象,甚至其他数组。我们也可以在单个数组中混合数据类型——我们不必将自己限制在一个数组中只存储数字,在另一个数组中只存储字符串。例如

    js
    const sequence = [1, 1, 2, 3, 5, 8, 13];
    const random = ["tree", 795, [0, 1, 2]];
    
  3. 在继续之前,创建一些示例数组。

查找数组的长度

您可以通过与查找字符串长度(以字符为单位)完全相同的方式来查找数组的长度(其中有多少项)——通过使用length属性。尝试以下操作

js
const shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
console.log(shopping.length); // 5

访问和修改数组项

数组是索引集合。数组中的项从零开始编号。这个数字称为项的索引。因此,第一个项的索引是 0,第二个项的索引是 1,依此类推。您可以使用方括号表示法并提供项的索引来访问数组中的单个项,就像您访问字符串中的字母一样。

  1. 在控制台中输入以下内容

    js
    const shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
    console.log(shopping[0]);
    // returns "bread"
    
  2. 您还可以通过为单个数组项赋予新值来修改数组中的项。试试这个

    js
    const shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
    shopping[0] = "tahini";
    console.log(shopping);
    // shopping will now return [ "tahini", "milk", "cheese", "hummus", "noodles" ]
    

    注意:我们之前说过,但只是提醒一下——JavaScript 数组的索引从零开始!

  3. 请注意,数组中的数组称为多维数组。您可以通过链接两组方括号来访问数组中(该数组本身在另一个数组中)的项。例如,要访问 random 数组中(见上一节)的第三个项中的某个项,我们可以这样做

    js
    const random = ["tree", 795, [0, 1, 2]];
    random[2][2];
    
  4. 在继续之前,尝试对您的数组示例进行更多修改。多玩玩,看看哪些有效,哪些无效。

查找数组中项的索引

如果您不知道某个项的索引,可以使用indexOf()方法。indexOf() 方法将项作为参数,并返回该项的索引,如果该项不在数组中,则返回-1

js
const birds = ["Parrot", "Falcon", "Owl"];
console.log(birds.indexOf("Owl")); //  2
console.log(birds.indexOf("Rabbit")); // -1

添加项

要向数组末尾添加一个或多个项,我们可以使用push()。请注意,您需要包含一个或多个要添加到数组末尾的项。

js
const cities = ["Manchester", "Liverpool"];
cities.push("Cardiff");
console.log(cities); // [ "Manchester", "Liverpool", "Cardiff" ]
cities.push("Bradford", "Brighton");
console.log(cities); // [ "Manchester", "Liverpool", "Cardiff", "Bradford", "Brighton" ]

方法调用完成后,将返回数组的新长度。如果您想将新的数组长度存储在变量中,可以这样做

js
const cities = ["Manchester", "Liverpool"];
const newLength = cities.push("Bristol");
console.log(cities); // [ "Manchester", "Liverpool", "Bristol" ]
console.log(newLength); // 3

要向数组开头添加项,请使用unshift()

js
const cities = ["Manchester", "Liverpool"];
cities.unshift("Edinburgh");
console.log(cities); // [ "Edinburgh", "Manchester", "Liverpool" ]

删除项

要从数组中删除最后一个项,请使用pop()

js
const cities = ["Manchester", "Liverpool"];
cities.pop();
console.log(cities); // [ "Manchester" ]

pop() 方法返回被删除的项。要将该项存储在新变量中,您可以这样做

js
const cities = ["Manchester", "Liverpool"];
const removedCity = cities.pop();
console.log(removedCity); // "Liverpool"

要从数组中删除第一个项,请使用shift()

js
const cities = ["Manchester", "Liverpool"];
cities.shift();
console.log(cities); // [ "Liverpool" ]

如果您知道某个项的索引,可以使用splice()从数组中删除它

js
const cities = ["Manchester", "Liverpool", "Edinburgh", "Carlisle"];
const index = cities.indexOf("Liverpool");
if (index !== -1) {
  cities.splice(index, 1);
}
console.log(cities); // [ "Manchester", "Edinburgh", "Carlisle" ]

在此 splice() 调用中,第一个参数表示从哪里开始删除项,第二个参数表示应该删除多少个项。因此,您可以删除多个项

js
const cities = ["Manchester", "Liverpool", "Edinburgh", "Carlisle"];
const index = cities.indexOf("Liverpool");
if (index !== -1) {
  cities.splice(index, 2);
}
console.log(cities); // [ "Manchester", "Carlisle" ]

访问每个项

通常你会想访问数组中的每个项。你可以使用for...of语句来做到这一点

js
const birds = ["Parrot", "Falcon", "Owl"];

for (const bird of birds) {
  console.log(bird);
}

有时您会想对数组中的每个项执行相同的操作,从而得到一个包含已更改项的数组。您可以使用map()来做到这一点。下面的代码接受一个数字数组并将每个数字加倍

js
function double(number) {
  return number * 2;
}
const numbers = [5, 2, 7, 6];
const doubled = numbers.map(double);
console.log(doubled); // [ 10, 4, 14, 12 ]

我们将一个函数提供给 map(),并且 map() 会为数组中的每个项调用一次该函数,并传入该项。然后,它会将每个函数调用的返回值添加到新数组中,最后返回新数组。

有时您会希望创建一个新数组,其中只包含原始数组中符合某些测试的项。您可以使用filter()来做到这一点。下面的代码接受一个字符串数组,并返回一个只包含长度大于 8 个字符的字符串的数组

js
function isLong(city) {
  return city.length > 8;
}
const cities = ["London", "Liverpool", "Totnes", "Edinburgh"];
const longer = cities.filter(isLong);
console.log(longer); // [ "Liverpool", "Edinburgh" ]

map() 类似,我们将一个函数提供给 filter() 方法,并且 filter() 会为数组中的每个项调用此函数,并传入该项。如果该函数返回 true,则该项将添加到新数组中。最后,它返回新数组。

字符串和数组之间的转换

通常你会得到一些包含在很长字符串中的原始数据,你可能想把有用的项分离成更有用的形式,然后对它们做一些事情,比如把它们显示在数据表中。要做到这一点,我们可以使用split()方法。它最简单的形式是,它接受一个参数,即您想用来分隔字符串的字符,并将分隔符之间的子字符串作为数组中的项返回。

注意:好的,这在技术上是一个字符串方法,而不是一个数组方法,但我们把它放在数组部分,因为它在这里很合适。

  1. 让我们玩玩这个,看看它是如何工作的。首先,在你的控制台中创建一个字符串

    js
    const data = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle";
    
  2. 现在让我们在每个逗号处分割它

    js
    const cities = data.split(",");
    cities;
    
  3. 最后,尝试查找新数组的长度,并从中检索一些项

    js
    cities.length;
    cities[0]; // the first item in the array
    cities[1]; // the second item in the array
    cities[cities.length - 1]; // the last item in the array
    
  4. 您还可以使用join()方法反向操作。尝试以下操作

    js
    const commaSeparated = cities.join(",");
    commaSeparated;
    
  5. 将数组转换为字符串的另一种方法是使用 toString() 方法。toString()join() 更简单,因为它不需要参数,但限制性更强。使用 join(),您可以指定不同的分隔符,而 toString() 总是使用逗号。(尝试使用与逗号不同的字符运行步骤 4。)

    js
    const dogNames = ["Rocket", "Flash", "Bella", "Slugger"];
    dogNames.toString(); // Rocket,Flash,Bella,Slugger
    

打印这些产品

轮到你了。在这个练习中,你将回到我们前面描述的例子——在发票上打印产品名称和价格,然后计算总价并在底部打印出来。按照以下步骤实现该逻辑。

  1. 单击下面代码块中的**“播放”**以在 MDN Playground 中编辑示例。
  2. // Part 1 注释下方,有一些字符串,每个字符串都包含由冒号分隔的产品名称和价格。我们希望你取消注释它们并将它们转换为一个名为 products 的数组。
  3. // Part 2 注释下方,开始一个 for...of() 循环来遍历 products 数组中的每个项。
  4. // Part 3 注释下方,我们希望你编写一行代码,将当前数组项 (name:price) 分割成两个单独的项,一个包含名称,一个包含价格。如果你不确定如何做到这一点,请查阅有用的字符串方法文章寻求帮助,或者更好地查阅本文的字符串和数组之间的转换部分。
  5. 作为上述代码行的一部分,您还需要将价格从字符串转换为数字。如果您不记得如何做到这一点,请查看第一篇字符串文章
  6. 在代码顶部有一个名为 total 的变量被创建并赋值为 0。在循环内部(// Part 4 下方),我们希望您添加一行代码,在每次循环迭代中将当前项价格添加到该总数中,以便在代码结束时将正确的总数打印到发票上。您可能需要一个赋值运算符来执行此操作。
  7. 我们希望您更改 // Part 5 之后的下一行,以便 itemText 变量等于“当前商品名称 — $当前商品价格”,例如“Shoes — $23.99”,以便每个商品都显示正确的信息。这是基本的字符串连接,如果您一直学习到这里,应该很熟悉。
  8. 最后,在 // Part 6 注释下方,您需要添加一个 } 来标记 for...of() 循环的结束。

如果你犯了错误,可以使用 MDN Playground 中的 _重置_ 按钮清除你的工作。如果你实在卡住了,可以在实时输出下方查看解决方案。

js
const list = document.querySelector(".output ul");
const totalBox = document.querySelector(".output p");
let total = 0;
list.textContent = "";
totalBox.textContent = "";
// Part 1
// "Underpants:6.99",
// "Socks:5.99",
// "T-shirt:14.99",
// "Trousers:31.99",
// "Shoes:23.99",

// Part 2

// Part 3

// Part 4

// Part 5
let itemText = 0;

const listItem = document.createElement("li");
listItem.textContent = itemText;
list.appendChild(listItem);

// Part 6

totalBox.textContent = `Total: $${total.toFixed(2)}`;
点击此处显示解决方案

你完成的 JavaScript 应该如下所示

js
const list = document.querySelector(".output ul");
const totalBox = document.querySelector(".output p");
let total = 0;
list.textContent = "";
totalBox.textContent = "";

const products = [
  "Underpants:6.99",
  "Socks:5.99",
  "T-shirt:14.99",
  "Trousers:31.99",
  "Shoes:23.99",
];

for (const product of products) {
  const subArray = product.split(":");
  const name = subArray[0];
  const price = Number(subArray[1]);
  total += price;
  const itemText = `${name} — $${price}`;

  const listItem = document.createElement("li");
  listItem.textContent = itemText;
  list.appendChild(listItem);
}

totalBox.textContent = `Total: $${total.toFixed(2)}`;

存储最近的 5 次搜索

让我们完成另一个练习,以保持练习的连续性。

push()pop() 等数组方法的一个很好的用途是,当您在 Web 应用中维护当前活动项的记录时。例如,在动画场景中,您可能有一个表示当前显示的背景图形的对象数组,并且出于性能或避免杂乱的原因,您可能只希望同时显示 50 个。当新对象创建并添加到数组中时,可以从数组中删除旧对象以保持所需的数量。

在这个例子中,我们将展示一个更简单的用法——这里我们给你一个假的搜索网站,带有一个搜索框。我们的想法是,当在搜索框中输入词语时,列表中会显示前 5 个以前的搜索词。当词语数量超过 5 个时,每次将新词语添加到顶部时,最旧的词语就会开始被删除,因此始终显示前 5 个词语。

注意:在真实的搜索应用中,您可能可以点击之前的搜索词以返回到之前的搜索,并且它会显示实际的搜索结果!我们现在只是为了简单起见。

要完成这个例子,我们需要你

  1. 单击下面代码块中的**“播放”**以在 MDN Playground 中编辑示例。
  2. // Part 1 注释下方添加一行代码,将当前输入到搜索框中的值添加到数组的开头。这可以使用 searchInput.value 获取。
  3. // Part 2 注释下方添加一行代码,移除数组末尾的当前值。

如果你犯了错误,可以使用 MDN Playground 中的 _重置_ 按钮清除你的工作。如果你实在卡住了,可以在实时输出下方查看解决方案。

js
const list = document.querySelector(".output ul");
const searchInput = document.querySelector(".output input");
const searchBtn = document.querySelector(".output button");

list.textContent = "";

const myHistory = [];
const MAX_HISTORY = 5;

searchBtn.addEventListener("click", () => {
  // we will only allow a term to be entered if the search input isn't empty
  if (searchInput.value !== "") {
    // Part 1

    // empty the list so that we don't display duplicate entries
    // the display is regenerated every time a search term is entered.
    list.textContent = "";

    // loop through the array, and display all the search terms in the list
    for (const itemText of myHistory) {
      const listItem = document.createElement("li");
      listItem.textContent = itemText;
      list.appendChild(listItem);
    }

    // If the array length is 5 or more, remove the oldest search term
    if (myHistory.length >= MAX_HISTORY) {
      // Part 2
    }

    // empty the search input and focus it, ready for the next term to be entered
    searchInput.value = "";
    searchInput.focus();
  }
});
点击此处显示解决方案

你完成的 JavaScript 应该如下所示

js
const list = document.querySelector(".output ul");
const searchInput = document.querySelector(".output input");
const searchBtn = document.querySelector(".output button");

list.textContent = "";

const myHistory = [];
const MAX_HISTORY = 5;

searchBtn.addEventListener("click", () => {
  // we will only allow a term to be entered if the search input isn't empty
  if (searchInput.value !== "") {
    myHistory.unshift(searchInput.value);

    // empty the list so that we don't display duplicate entries
    // the display is regenerated every time a search term is entered.
    list.textContent = "";

    // loop through the array, and display all the search terms in the list
    for (const itemText of myHistory) {
      const listItem = document.createElement("li");
      listItem.textContent = itemText;
      list.appendChild(listItem);
    }

    // If the array length is 5 or more, remove the oldest search term
    if (myHistory.length >= MAX_HISTORY) {
      myHistory.pop();
    }

    // empty the search input and focus it, ready for the next term to be entered
    searchInput.value = "";
    searchInput.focus();
  }
});

总结

阅读完本文后,我们相信您会同意数组似乎非常有用;您会在 JavaScript 中随处看到它们,通常与循环结合使用,以便对数组中的每个项执行相同的操作。我们将在模块后面的部分中向您介绍有关循环的所有知识。

在下一篇文章中,我们将为您提供一些测试,您可以使用它们来检查您对我们提供的数组信息的理解和记忆程度。

另见

Array

Array 对象参考页面提供了本页讨论的功能以及许多其他 Array 功能的详细参考指南。