JavaScript 对象基础知识

在本文中,我们将了解基本的 JavaScript 对象语法,并重新审视我们在课程前面已经见过的一些 JavaScript 功能,重申你已经处理过的许多功能都是对象这一事实。

¥In this article, we'll look at fundamental JavaScript object syntax, and revisit some JavaScript features that we've already seen earlier in the course, reiterating the fact that many of the features you've already dealt with are objects.

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

对象基础知识

¥Object basics

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

¥An object is a collection of related data and/or functionality. These usually consist of several variables and functions (which are called properties and methods when they are inside objects). Let's work through an example to understand what they look like.

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

¥To begin with, make a local copy of our oojs.html file. This contains very little — a <script> element for us to write our source code into. We'll use this as a basis for exploring basic object syntax. While working with this example you should have your developer tools JavaScript console open and ready to type in some commands.

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

¥As with many things in JavaScript, creating an object often begins with defining and initializing a variable. Try entering the following line below the JavaScript code that's already in your file, then saving and refreshing:

js
const person = {};

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

¥Now open your browser's JavaScript console, enter person into it, and press Enter/Return. You should get a result similar to one of the below lines:

[object Object]
Object { }
{ }

恭喜,你刚刚创建了第一个对象。任务完成!但这是一个空对象,所以我们不能用它做太多事情。让我们更新文件中的 JavaScript 对象,如下所示:

¥Congratulations, you've just created your first object. Job done! But this is an empty object, so we can't really do much with it. Let's update the JavaScript object in our file to look like this:

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 控制台中输入以下一些内容:

¥After saving and refreshing, try entering some of the following into the JavaScript console on your browser devtools:

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

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

¥You have now got some data and functionality inside your object, and are now able to access them with some nice simple syntax!

那么这是怎么回事呢?好吧,一个对象由多个成员组成,每个成员都有一个名称(例如上面的 nameage)和一个值(例如 ['Bob', 'Smith']32)。每个名称/值对必须用逗号分隔,并且每种情况下的名称和值都用冒号分隔。语法始终遵循以下模式:

¥So what is going on here? Well, an object is made up of multiple members, each of which has a name (e.g. name and age above), and a value (e.g. ['Bob', 'Smith'] and 32). Each name/value pair must be separated by a comma, and the name and value in each case are separated by a colon. The syntax always follows this pattern:

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

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

¥The value of an object member can be pretty much anything — in our person object we've got a number, an array, and two functions. The first two items are data items, and are referred to as the object's properties. The last two items are functions that allow the object to do something with that data, and are referred to as the object's methods.

当对象的成员是函数时,语法会更简单。我们可以写 bio() 来代替 bio: function ()。像这样:

¥When the object's members are functions there's a simpler syntax. Instead of bio: function () we can write bio(). Like this:

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]}.`);
  },
};

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

¥From now on, we'll use this shorter syntax.

像这样的对象被称为对象字面量 - 我们在创建对象时就按字面意思写出了对象内容。这与从类实例化的对象不同,我们稍后会讨论。

¥An object like this is referred to as an object literal — we've literally written out the object contents as we've come to create it. This is different compared to objects instantiated from classes, which we'll look at later on.

当你想要以某种方式传输一系列结构化的相关数据项(例如向服务器发送请求以将其放入数据库中)时,使用对象文字创建对象是很常见的。发送单个对象比单独发送多个项目要高效得多,并且当你想通过名称识别单个项目时,它比数组更容易使用。

¥It is very common to create an object using an object literal when you want to transfer a series of structured, related data items in some manner, for example sending a request to the server to be put into a database. Sending a single object is much more efficient than sending several items individually, and it is easier to work with than an array, when you want to identify individual items by name.

点符号

¥Dot notation

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

¥Above, you accessed the object's properties and methods using dot notation. The object name (person) acts as the namespace — it must be entered first to access anything inside the object. Next you write a dot, then the item you want to access — this can be the name of a simple property, an item of an array property, or a call to one of the object's methods, for example:

js
person.age;
person.bio();

对象作为对象属性

¥Objects as object properties

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

¥An object property can itself be an object. For example, try changing the name member from

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

to

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

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

¥To access these items you just need to chain the extra step onto the end with another dot. Try these in the JS console:

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

如果你这样做,你还需要检查方法代码并更改

¥If you do this, you'll also need to go through your method code and change any instances of

js
name[0];
name[1];

to

js
name.first;
name.last;

否则,你的方法将不再有效。

¥Otherwise, your methods will no longer work.

括号表示法

¥Bracket notation

括号表示法提供了另一种访问对象属性的方法。而不是像这样使用 点符号

¥Bracket notation provides an alternative way to access object properties. Instead of using dot notation like this:

js
person.age;
person.name.first;

你可以改为使用方括号:

¥You can instead use square brackets:

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

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

¥This looks very similar to how you access the items in an array, and it is basically the same thing — instead of using an index number to select an item, you are using the name associated with each member's value. It is no wonder that objects are sometimes called associative arrays — they map strings to values in the same way that arrays map numbers to values.

点表示法通常优于括号表示法,因为它更简洁且更易于阅读。然而,在某些情况下你必须使用方括号。例如,如果对象属性名称保存在变量中,则不能使用点表示法访问该值,但可以使用方括号表示法访问该值。

¥Dot notation is generally preferred over bracket notation because it is more succinct and easier to read. However there are some cases where you have to use square brackets. For example, if an object property name is held in a variable, then you can't use dot notation to access the value, but you can access the value using bracket notation.

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

¥In the example below, the logProperty() function can use person[propertyName] to retrieve the value of the property named in propertyName.

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

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

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

设置对象成员

¥Setting object members

到目前为止,我们只研究了检索(或获取)对象成员 - 你还可以通过声明要设置的成员(使用点或括号表示法)来设置(更新)对象成员的值,如下所示:

¥So far we've only looked at retrieving (or getting) object members — you can also set (update) the value of object members by declaring the member you want to set (using dot or bracket notation), like this:

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

尝试输入以上行,然后再次让成员查看他们的变化,如下所示:

¥Try entering the above lines, and then getting the members again to see how they've changed, like so:

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

设置成员不仅仅停留在更新现有属性和方法的值;还包括更新现有属性和方法的值。你还可以创建全新的成员。在 JS 控制台中尝试这些:

¥Setting members doesn't just stop at updating the values of existing properties and methods; you can also create completely new members. Try these in the JS console:

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

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

¥You can now test out your new members:

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

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

¥One useful aspect of bracket notation is that it can be used to set not only member values dynamically, but member names too. Let's say we wanted users to be able to store custom value types in their people data, by typing the member name and value into two text inputs. We could get those values like this:

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

然后我们可以将这个新成员名称和值添加到 person 对象,如下所示:

¥We could then add this new member name and value to the person object like this:

js
person[myDataName] = myDataValue;

要测试这一点,请尝试将以下行添加到代码中,就在 person 对象的右大括号下方:

¥To test this, try adding the following lines into your code, just below the closing curly brace of the person object:

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

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

¥Now try saving and refreshing, and entering the following into your text input:

js
person.height;

使用上面的方法向对象添加属性不可能使用点表示法,它只能接受文字成员名称,而不接受指向名称的变量值。

¥Adding a property to an object using the method above isn't possible with dot notation, which can only accept a literal member name, not a variable value pointing to a name.

"this" 是什么?

¥What is "this"?

你可能已经注意到我们的方法有些奇怪。看看这个例子:

¥You may have noticed something slightly strange in our methods. Look at this one for example:

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

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

¥You are probably wondering what "this" is. The this keyword typically refers to the current object the code is being executed in. In the context of an object method, this refers to the object that the method was called on.

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

¥Let's illustrate what we mean with a simplified pair of person objects:

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() 输出“嗨!我是克里斯。”;person2.introduceSelf() 输出“Hi!我是 Deepti。”发生这种情况的原因是,当调用该方法时,this 引用调用该方法的对象,这允许相同的方法定义适用于多个对象。

¥In this case, person1.introduceSelf() outputs "Hi! I'm Chris."; person2.introduceSelf() outputs "Hi! I'm Deepti." This happens because when the method is called, this refers to the object on which the method is called, which allows the same method definition to work for multiple objects.

当你手动写出对象文字时,这并不是很有用,因为使用对象的名称(person1person2)会导致完全相同的结果,但当我们开始使用构造函数从单个对象定义创建多个对象时,它将是必不可少的,这是下一节的主题。

¥This isn't hugely useful when you are writing out object literals by hand, as using the object's name (person1 and person2) leads to the exact same result, but it will be essential when we start using constructors to create more than one object from a single object definition, and that's the subject of the next section.

构造函数介绍

¥Introducing constructors

当你只需要创建一个对象时,使用对象文字是可以的,但如果你必须创建多个对象(如上一节中所示),那么它们就严重不足了。我们必须为我们创建的每个对象编写相同的代码,如果我们想更改对象的某些属性 - 就像添加 height 属性一样 - 那么我们必须记住更新每个对象。

¥Using object literals is fine when you only need to create one object, but if you have to create more than one, as in the previous section, they're seriously inadequate. We have to write out the same code for every object we create, and if we want to change some properties of the object - like adding a height property - then we have to remember to update every object.

我们想要一种方法来定义对象的 "shape"(方法集和它可以拥有的属性),然后创建任意数量的对象,只需更新不同属性的值。

¥We would like a way to define the "shape" of an object — the set of methods and the properties it can have — and then create as many objects as we like, just updating the values for the properties that are different.

第一个版本只是一个函数:

¥The first version of this is just a function:

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

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

¥This function creates and returns a new object each time we call it. The object will have two members:

  • 属性 name
  • 方法 introduceSelf()

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

¥Note that createPerson() takes a parameter name to set the value of the name property, but the value of the introduceSelf() method will be the same for all objects created using this function. This is a very common pattern for creating objects.

现在我们可以重用定义来创建任意数量的对象:

¥Now we can create as many objects as we like, reusing the definition:

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

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

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

¥This works fine but is a bit long-winded: we have to create an empty object, initialize it, and return it. A better way is to use a constructor. A constructor is just a function called using the new keyword. When you call a constructor, it will:

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

按照惯例,构造函数以大写字母开头,并根据它们创建的对象类型来命名。所以我们可以这样重写我们的例子:

¥Constructors, by convention, start with a capital letter and are named for the type of object they create. So we could rewrite our example like this:

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

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

¥To call Person() as a constructor, we use new:

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

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

你一直在使用对象

¥You've been using objects all along

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

¥As you've been going through these examples, you have probably been thinking that the dot notation you've been using is very familiar. That's because you've been using it throughout the course! Every time we've been working through an example that uses a built-in browser API or JavaScript object, we've been using objects, because such features are built using exactly the same kind of object structures that we've been looking at here, albeit more complex ones than in our own basic custom examples.

因此,当你使用如下字符串方法时:

¥So when you used string methods like:

js
myString.split(",");

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

¥You were using a method available on a String object. Every time you create a string in your code, that string is automatically created as an instance of String, and therefore has several common methods and properties available on it.

当你使用如下行访问文档对象模型时:

¥When you accessed the document object model using lines like this:

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

你正在使用 Document 对象上可用的方法。对于加载的每个网页,都会创建一个 Document 的实例,称为 document,它代表整个页面的结构、内容和其他功能(例如 URL)。同样,这意味着它有几个可用的常用方法和属性。

¥You were using methods available on a Document object. For each webpage loaded, an instance of Document is created, called document, which represents the entire page's structure, content, and other features such as its URL. Again, this means that it has several common methods and properties available on it.

对于你一直在使用的几乎任何其他内置对象或 API — ArrayMath 等,情况也是如此。

¥The same is true of pretty much any other built-in object or API you've been using — Array, Math, and so on.

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

¥Note that built in objects and APIs don't always create object instances automatically. As an example, the Notifications API — which allows modern browsers to fire system notifications — requires you to instantiate a new object instance using the constructor for each notification you want to fire. Try entering the following into your JavaScript console:

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

测试你的技能!

¥Test your skills!

你已读完本文,但你还记得最重要的信息吗?在继续之前,你可以找到一些进一步的测试来验证你是否已保留此信息 - 请参阅 测试你的技能:对象基础知识

¥You've reached the end of this article, but can you remember the most important information? You can find some further tests to verify that you've retained this information before you move on — see Test your skills: Object basics.

概括

¥Summary

恭喜,你已经完成了我们第一篇 JS 对象文章的结尾 — 你现在应该对如何在 JavaScript 中使用对象有一个很好的了解 — 包括创建你自己的简单对象。你还应该意识到,对象作为存储相关数据和功能的结构非常有用 - 如果你尝试将 person 对象中的所有属性和方法作为单独的变量和函数进行跟踪,这将是低效且令人沮丧的,我们' d 存在与其他同名变量和函数发生冲突的风险。对象让我们将信息安全地锁在自己的包中,避免受到伤害。

¥Congratulations, you've reached the end of our first JS objects article — you should now have a good idea of how to work with objects in JavaScript — including creating your own simple objects. You should also appreciate that objects are very useful as structures for storing related data and functionality — if you tried to keep track of all the properties and methods in our person object as separate variables and functions, it would be inefficient and frustrating, and we'd run the risk of clashing with other variables and functions that have the same names. Objects let us keep the information safely locked away in their own package, out of harm's way.

在下一篇文章中,我们将讨论原型,这是 JavaScript 让对象从其他对象继承属性的基本方式。

¥In the next article we'll look at prototypes, which is the fundamental way that JavaScript lets an object inherit properties from other objects.