JavaScript 对象基础

在本文中,我们将探讨 JavaScript 对象的基本语法,并回顾本课程中已经遇到的一些 JavaScript 特性,重申您已经处理过的许多特性都是对象。

预备知识 了解 HTMLCSS 基础,熟悉前面课程中介绍的 JavaScript 基础。
学习成果
  • 您需要了解在 JavaScript 中,大多数事物都是对象,而且您可能每次接触 JavaScript 时都使用了对象。
  • 基本语法:对象字面量、属性和方法,在对象中嵌套对象和数组。
  • 使用构造函数创建新对象。
  • 对象作用域和 this
  • 访问属性和方法 — 方括号和点语法。

对象基础

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

首先,在本地复制我们的 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: function () 写成 bio()。像这样

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
const person = {
  // …
  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!");

总结

现在您应该对如何在 JavaScript 中使用对象有了一个很好的理解——包括创建您自己的简单对象。您还应该意识到对象作为存储相关数据和功能的结构非常有用——如果您尝试将我们 person 对象中的所有属性和方法作为独立的变量和函数进行跟踪,那将是低效且令人沮丧的,而且我们还会面临与其他同名变量和函数冲突的风险。对象让我们能够将信息安全地锁定在自己的包中,免受伤害。

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