JavaScript 对象基础

在本文中,我们将探讨基本的 JavaScript 对象语法,并回顾本课程中我们之前已经看到的一些 JavaScript 功能,重申一个事实,即你已经使用过的许多功能都是对象。

先决条件 对 HTML 和 CSS 的基本了解,熟悉 JavaScript 基础知识 (参见 第一步构建块)。
目标 了解在 JavaScript 中使用对象的 基础知识:创建对象、访问和修改对象属性以及使用构造函数。

对象基础

对象是相关数据和/或功能的集合。它们通常包含多个变量和函数 (在对象内部称为属性和方法)。让我们通过一个例子来理解它们的样子。

首先,在本地创建一个 oojs.html 文件的副本。这个文件包含很少的内容——一个 <script> 元素,供我们编写源代码。我们将以此为基础来探索基本的 对象语法。在处理此示例时,你应该打开你的 开发者工具 JavaScript 控制台,并准备输入一些命令。

与 JavaScript 中的许多事物一样,创建对象通常从定义和初始化变量开始。尝试在你文件中的 JavaScript 代码下方输入以下行,然后保存并刷新

js
const person = {};

现在打开浏览器的 JavaScript 控制台,在其中输入 person 并按 Enter/Return。你应该会得到类似于以下几行之一的结果

[object Object]
Object { }
{ }

恭喜,你刚刚创建了你的第一个对象。任务完成!但这只是一个空对象,所以我们不能真正对它做太多事情。让我们更新文件中的 JavaScript 对象,使其看起来像这样

js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio: function () {
    console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`);
  },
  introduceSelf: function () {
    console.log(`Hi! I'm ${this.name[0]}.`);
  },
};

保存并刷新后,尝试在浏览器开发者工具的 JavaScript 控制台中输入以下内容

js
person.name;
person.name[0];
person.age;
person.bio();
// "Bob Smith is 32 years old."
person.introduceSelf();
// "Hi! I'm Bob."

你现在已经在你的对象中包含了一些数据和功能,现在可以使用一些简单易懂的语法来访问它们了!

那么这里究竟发生了什么?好吧,对象由多个成员组成,每个成员都有一个名称 (例如,上面的 nameage),以及一个值 (例如,['Bob', 'Smith']32)。每个名称/值对必须用逗号隔开,每个名称和值用冒号隔开。语法始终遵循以下模式

js
const objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};

对象成员的值几乎可以是任何东西——在我们的 person 对象中,我们有一个数字、一个数组和两个函数。前两个项目是数据项,被称为对象的 属性。最后两个项目是允许对象对该数据执行某些操作的函数,被称为对象的 方法

当对象的成员是函数时,有一个更简单的语法。我们可以写 bio(),而不是 bio: function ()。就像这样

js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio() {
    console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`);
  },
  introduceSelf() {
    console.log(`Hi! I'm ${this.name[0]}.`);
  },
};

从现在开始,我们将使用这种更短的语法。

像这样的对象被称为 对象字面量——我们已经按照我们创建它的方式直接写出了对象的内容。这与从类实例化的对象不同,我们将在后面讨论。

当你想以某种方式传递一系列结构化的、相关的数据项时,例如将请求发送到服务器以将其放入数据库,使用对象字面量创建对象非常普遍。发送单个对象比单独发送多个项目效率高得多,并且当你想要通过名称标识单个项目时,它比数组更容易处理。

点表示法

在上面,你使用 点表示法访问了对象的属性和方法。对象名称 (person) 充当 命名空间——必须先输入它才能访问对象内部的任何内容。接下来,你写一个点,然后写你想访问的项目——这可以是简单属性的名称、数组属性的项目,或者对对象方法的调用,例如

js
person.age;
person.bio();

对象作为对象属性

对象属性本身可以是一个对象。例如,尝试将 name 成员从

js
const person = {
  name: ["Bob", "Smith"],
};

改为

js
const person = {
  name: {
    first: "Bob",
    last: "Smith",
  },
  // …
};

要访问这些项目,你只需要使用另一个点将额外的步骤链接到末尾。在 JS 控制台中尝试以下操作

js
person.name.first;
person.name.last;

如果你这样做,你还需要遍历你的方法代码,并将所有出现的

js
name[0];
name[1];

改为

js
name.first;
name.last;

否则,你的方法将不再工作。

方括号表示法

方括号表示法提供了一种访问对象属性的替代方法。与其使用 点表示法 这样

js
person.age;
person.name.first;

你可以改用方括号

js
person["age"];
person["name"]["first"];

这看起来非常类似于你如何访问数组中的项目,并且它基本上是相同的——与其使用索引号来选择项目,你使用的是与每个成员的值关联的名称。难怪对象有时被称为 关联数组——它们以与数组将数字映射到值相同的方式将字符串映射到值。

点表示法通常比方括号表示法更受欢迎,因为它更简洁,更易于阅读。但是,在某些情况下,你必须使用方括号。例如,如果对象属性名保存在变量中,那么你不能使用点表示法来访问该值,但可以使用方括号表示法来访问该值。

在下面的示例中,logProperty() 函数可以使用 person[propertyName] 来检索 propertyName 中命名的属性的值。

js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
};

function logProperty(propertyName) {
  console.log(person[propertyName]);
}

logProperty("name");
// ["Bob", "Smith"]
logProperty("age");
// 32

设置对象成员

到目前为止,我们只关注检索 (或 获取) 对象成员——你还可以通过声明要设置的成员 (使用点表示法或方括号表示法) 来 设置 (更新) 对象成员的值,就像这样

js
person.age = 45;
person["name"]["last"] = "Cratchit";

尝试输入上面的行,然后再次获取成员以查看它们是如何改变的,如下所示

js
person.age;
person["name"]["last"];

设置成员不仅仅局限于更新现有属性和方法的值;你还可以创建全新的成员。在 JS 控制台中尝试以下操作

js
person["eyes"] = "hazel";
person.farewell = function () {
  console.log("Bye everybody!");
};

你现在可以测试你的新成员了

js
person["eyes"];
person.farewell();
// "Bye everybody!"

方括号表示法的一个有用方面是它不仅可以用来动态设置成员值,还可以用来设置成员名。假设我们希望用户能够通过在两个文本输入框中输入成员名和值,将其自定义值类型存储在他们的人员数据中。我们可以这样获取这些值

js
const myDataName = nameInput.value;
const myDataValue = nameValue.value;

然后,我们可以将这个新的成员名和值添加到 person 对象中,就像这样

js
person[myDataName] = myDataValue;

要测试这一点,尝试将以下几行添加到你的代码中,紧接在 person 对象的结束大括号下方

js
const myDataName = "height";
const myDataValue = "1.75m";
person[myDataName] = myDataValue;

现在尝试保存并刷新,然后在你的文本输入框中输入以下内容

js
person.height;

使用上述方法将属性添加到对象中,无法使用点表示法,点表示法只能接受文字成员名,不能接受指向名称的变量值。

什么是“this”?

你可能已经注意到我们在方法中有一些奇怪的地方。例如,看看这个方法

js
introduceSelf() {
  console.log(`Hi! I'm ${this.name[0]}.`);
}

你可能想知道“this”是什么。“this” 关键字通常指的是正在执行代码的当前对象。在对象方法的上下文中,this 指的是调用该方法的对象。

让我们用一对简化的 person 对象来说明我们的意思

js
const person1 = {
  name: "Chris",
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`);
  },
};

const person2 = {
  name: "Deepti",
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`);
  },
};

在这种情况下,person1.introduceSelf() 输出“Hi! I'm Chris.”;person2.introduceSelf() 输出“Hi! I'm Deepti.”。这是因为,当调用方法时,this 指的是调用该方法的对象,这使得相同的 方法定义可以用于多个对象。

当你手动写出对象字面量时,这并没有什么用处,因为使用对象的名称 (person1person2) 会产生完全相同的结果,但当我们开始使用 构造函数 从单个对象定义创建多个对象时,它将变得至关重要,而这正是下一节的主题。

介绍构造函数

当你只需要创建一个对象时,使用对象字面量是可以的,但是如果你必须创建多个对象,就像上一节中那样,它们就非常不合适。我们必须为我们创建的每个对象编写相同的代码,如果我们想要更改对象的某些属性——比如添加 height 属性——那么我们必须记住更新每个对象。

我们希望有一种方法来定义对象的“形状”——它可以拥有的方法集和属性——然后创建任意数量的对象,只需更新属性值不同的部分。

第一个版本只是一个函数

js
function createPerson(name) {
  const obj = {};
  obj.name = name;
  obj.introduceSelf = function () {
    console.log(`Hi! I'm ${this.name}.`);
  };
  return obj;
}

每次我们调用此函数时,它都会创建并返回一个新对象。该对象将有两个成员

  • 一个属性 name
  • 一个方法 introduceSelf()

请注意,createPerson() 接收一个参数 name 来设置 name 属性的值,但是使用此函数创建的所有对象 introduceSelf() 方法的值都将相同。这是创建对象的非常常见的模式。

现在我们可以根据需要创建任意数量的对象,重复使用该定义

js
const salva = createPerson("Salva");
salva.introduceSelf();
// "Hi! I'm Salva."

const frankie = createPerson("Frankie");
frankie.introduceSelf();
// "Hi! I'm Frankie."

这工作正常,但有点冗长:我们必须创建一个空对象,对其进行初始化,然后返回它。更好的方法是使用 构造函数。构造函数只是一个使用 new 关键字调用的函数。当你调用构造函数时,它将

  • 创建一个新对象
  • this 绑定到新对象,以便你可以在构造函数代码中引用 this
  • 运行构造函数中的代码
  • 返回新对象。

构造函数按照惯例以大写字母开头,并以它们创建的对象类型命名。因此,我们可以像这样重写我们的示例

js
function Person(name) {
  this.name = name;
  this.introduceSelf = function () {
    console.log(`Hi! I'm ${this.name}.`);
  };
}

要将 Person() 作为构造函数调用,我们使用 new

js
const salva = new Person("Salva");
salva.introduceSelf();
// "Hi! I'm Salva."

const frankie = new Person("Frankie");
frankie.introduceSelf();
// "Hi! I'm Frankie."

你一直在使用对象

当你一直在浏览这些示例时,你可能一直在想你一直在使用的点表示法非常熟悉。这是因为你一直在整个课程中使用它!每次我们处理使用内置浏览器 API 或 JavaScript 对象的示例时,我们都在使用对象,因为这些功能正是使用我们在本文中看到的相同类型的对象结构构建的,只是比我们自己的基本自定义示例更复杂。

因此,当你使用像这样的字符串方法时

js
myString.split(",");

你是在使用 String 对象上可用的方法。每次你在代码中创建字符串时,该字符串都会自动创建为 String 的实例,因此它在其中具有多个通用方法和属性。

当你使用像这样的行访问文档对象模型时

js
const myDiv = document.createElement("div");
const myVideo = document.querySelector("video");

您之前使用的是在 Document 对象上可用的方法。每个加载的网页都会创建一个 Document 实例,称为 document,它表示整个页面的结构、内容和其他功能,例如其 URL。同样,这意味着它具有在其上可用的多个常用方法和属性。

几乎所有其他内置对象或 API 都是如此 — ArrayMath 等等。

请注意,内置对象和 API 并不总是自动创建对象实例。例如,通知 API — 允许现代浏览器触发系统通知 — 要求您使用每个要触发的通知的构造函数实例化一个新的对象实例。尝试将以下内容输入您的 JavaScript 控制台

js
const myNotification = new Notification("Hello!");

测试你的技能!

您已到达本文的结尾,但您还记得最重要的信息吗?您可以在继续之前找到一些进一步的测试来验证您是否已保留这些信息 — 请参见 测试您的技能:对象基础

总结

恭喜您,您已完成我们的第一篇 JS 对象文章 — 您现在应该对如何在 JavaScript 中使用对象有了很好的了解 — 包括创建您自己的简单对象。您还应该认识到,对象作为存储相关数据和功能的结构非常有用 — 如果您尝试将我们 person 对象中的所有属性和方法作为单独的变量和函数进行跟踪,那将非常低效且令人沮丧,而且我们还将面临与其他具有相同名称的变量和函数冲突的风险。对象允许我们将信息安全地锁定在它们自己的包中,远离危险。

在下一篇文章中,我们将研究 **原型**,它是 JavaScript 允许对象从其他对象继承属性的基本方式。