2022 年 6 月 19 日

对象

正如我们在 数据类型 一章中所了解的,JavaScript 中有八种数据类型。其中七种被称为“基本类型”,因为它们的值只包含一个内容(无论是字符串、数字还是其他内容)。

相比之下,对象用于存储各种数据的键控集合和更复杂的实体。在 JavaScript 中,对象几乎渗透到了语言的各个方面。因此,在深入了解其他任何内容之前,我们必须首先了解它们。

可以使用花括号 {…} 创建一个对象,其中包含一个可选的属性列表。属性是一个“键:值”对,其中是一个字符串(也称为“属性名称”),而可以是任何内容。

我们可以将对象想象成一个带签名的文件柜。每条数据都按键存储在它的文件中。通过名称查找文件或添加/删除文件非常容易。

可以使用以下两种语法之一创建空对象(“空文件柜”)

let user = new Object(); // "object constructor" syntax
let user = {};  // "object literal" syntax

通常使用花括号 {...}。该声明称为对象字面量

字面量和属性

我们可以立即将一些属性放入 {...} 中作为“键:值”对

let user = {     // an object
  name: "John",  // by key "name" store value "John"
  age: 30        // by key "age" store value 30
};

一个属性在冒号 ":" 之前有一个键(也称为“名称”或“标识符”),在其右侧有一个值。

user 对象中,有两个属性

  1. 第一个属性的名称为 "name",值为 "John"
  2. 第二个属性的名称为 "age",值为 30

由此产生的 user 对象可以想象为一个带有两个签名文件(标记为“name”和“age”)的橱柜。

我们可以随时添加、删除和读取其中的文件。

可以使用点表示法访问属性值

// get property values of the object:
alert( user.name ); // John
alert( user.age ); // 30

值可以是任何类型。让我们添加一个布尔值

user.isAdmin = true;

要删除属性,我们可以使用 delete 运算符

delete user.age;

我们还可以使用多字属性名称,但必须用引号引起来

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // multiword property name must be quoted
};

列表中的最后一个属性可以用逗号结尾

let user = {
  name: "John",
  age: 30,
}

这称为“尾随”或“悬挂”逗号。这样可以更轻松地添加/删除/移动属性,因为所有行都变得相似。

方括号

对于多字属性,点访问不起作用

// this would give a syntax error
user.likes birds = true

JavaScript 不理解这一点。它认为我们寻址 user.likes,然后在遇到意外的 birds 时给出语法错误。

点要求键是有效的变量标识符。这意味着:不包含空格,不以数字开头,不包含特殊字符(允许 $_)。

有一种替代的“方括号表示法”,它可以与任何字符串一起使用

let user = {};

// set
user["likes birds"] = true;

// get
alert(user["likes birds"]); // true

// delete
delete user["likes birds"];

现在一切都很好。请注意,括号内的字符串被正确引用(任何类型的引号都可以)。

方括号还提供了一种方法,可以将属性名称作为任何表达式的结果(而不是一个文字字符串)获得——就像从变量中获得以下结果一样

let key = "likes birds";

// same as user["likes birds"] = true;
user[key] = true;

在此,变量 key 可以在运行时计算或取决于用户输入。然后我们用它来访问该属性。这给了我们很大的灵活性。

例如

let user = {
  name: "John",
  age: 30
};

let key = prompt("What do you want to know about the user?", "name");

// access by variable
alert( user[key] ); // John (if enter "name")

点表示法不能以类似的方式使用

let user = {
  name: "John",
  age: 30
};

let key = "name";
alert( user.key ) // undefined

计算属性

在创建对象时,我们可以在对象字面量中使用方括号。这称为计算属性

例如

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
  [fruit]: 5, // the name of the property is taken from the variable fruit
};

alert( bag.apple ); // 5 if fruit="apple"

计算属性的含义很简单:[fruit] 表示属性名称应该从 fruit 中获取。

因此,如果访问者输入 "apple"bag 将变为 {apple: 5}

从本质上讲,这与以下内容相同

let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};

// take property name from the fruit variable
bag[fruit] = 5;

…但看起来更漂亮。

我们可以在方括号内使用更复杂的表达式

let fruit = 'apple';
let bag = {
  [fruit + 'Computers']: 5 // bag.appleComputers = 5
};

方括号比点表示法强大得多。它们允许使用任何属性名称和变量。但它们也更难写。

因此,在大多数情况下,当属性名称已知且简单时,将使用点。如果我们需要更复杂的内容,则切换到方括号。

属性值简写

在实际代码中,我们经常将现有变量用作属性名称的值。

例如

function makeUser(name, age) {
  return {
    name: name,
    age: age,
    // ...other properties
  };
}

let user = makeUser("John", 30);
alert(user.name); // John

在上面的示例中,属性与变量具有相同的名称。从变量创建属性的用例非常常见,因此有一个特殊的属性值简写可以缩短它。

我们可以像这样只写 name,而不是 name:name

function makeUser(name, age) {
  return {
    name, // same as name: name
    age,  // same as age: age
    // ...
  };
}

我们可以在同一个对象中同时使用普通属性和简写

let user = {
  name,  // same as name:name
  age: 30
};

属性名称限制

正如我们已经知道的那样,变量不能具有与语言保留字(如“for”、“let”、“return”等)相等的名称。

但对于对象属性,没有这样的限制

// these properties are all right
let obj = {
  for: 1,
  let: 2,
  return: 3
};

alert( obj.for + obj.let + obj.return );  // 6

简而言之,属性名称没有限制。它们可以是任何字符串或符号(一种特殊的标识符类型,将在后面介绍)。

其他类型会自动转换为字符串。

例如,数字 0 在用作属性键时会变成字符串 "0"

let obj = {
  0: "test" // same as "0": "test"
};

// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)

有一个名为 __proto__ 的特殊属性有一个小问题。我们不能将它设置为非对象值

let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended

从代码中可以看到,对基本类型 5 的赋值被忽略了。

我们将在 后续章节 中介绍 __proto__ 的特殊性质,并建议 修复 此类行为的方法。

属性存在性测试,“in” 运算符

与许多其他语言相比,JavaScript 中对象的显着特点是它可以访问任何属性。如果属性不存在,不会出现错误!

读取不存在的属性只会返回 undefined。因此,我们可以轻松测试属性是否存在

let user = {};

alert( user.noSuchProperty === undefined ); // true means "no such property"

为此,还有一个特殊运算符 "in"

语法是

"key" in object

例如

let user = { name: "John", age: 30 };

alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist

请注意,in 的左侧必须是属性名。这通常是一个带引号的字符串。

如果我们省略引号,这意味着变量应包含要测试的实际名称。例如

let user = { age: 30 };

let key = "age";
alert( key in user ); // true, property "age" exists

in 运算符为什么存在?与 undefined 进行比较难道不够吗?

嗯,大多数情况下,与 undefined 的比较都能正常工作。但有一种特殊情况会失败,而 "in" 则能正确工作。

这是当对象属性存在,但存储 undefined

let obj = {
  test: undefined
};

alert( obj.test ); // it's undefined, so - no such property?

alert( "test" in obj ); // true, the property does exist!

在上面的代码中,属性 obj.test 在技术上存在。因此,in 运算符可以正常工作。

这种情况很少发生,因为 undefined 不应显式分配。我们主要使用 null 表示“未知”或“空”值。因此,in 运算符是代码中的稀客。

“for..in” 循环

要遍历对象的全部键,有一种特殊的循环形式:for..in。这与我们之前学习的 for(;;) 结构完全不同。

语法

for (key in object) {
  // executes the body for each key among object properties
}

例如,我们输出 user 的所有属性

let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for (let key in user) {
  // keys
  alert( key );  // name, age, isAdmin
  // values for the keys
  alert( user[key] ); // John, 30, true
}

请注意,所有“for”结构都允许我们在循环内声明循环变量,如这里的 let key

此外,我们还可以在这里使用 key 以外的另一个变量名。例如,"for (let prop in obj)" 也被广泛使用。

按对象排序

对象是有序的吗?换句话说,如果我们遍历一个对象,我们是否会按照添加的顺序获得所有属性?我们可以依赖这个吗?

简短的回答是:“以特殊方式排序”:整数属性已排序,其他属性按创建顺序显示。详情如下。

例如,我们考虑一个带有电话区号的对象

let codes = {
  "49": "Germany",
  "41": "Switzerland",
  "44": "Great Britain",
  // ..,
  "1": "USA"
};

for (let code in codes) {
  alert(code); // 1, 41, 44, 49
}

该对象可用于向用户建议选项列表。如果我们主要为德国受众制作网站,那么我们可能希望 49 是第一个。

但是,如果我们运行代码,我们会看到完全不同的画面

  • 美国 (1) 排在第一位
  • 然后是瑞士 (41),依此类推。

电话区号按升序排列,因为它们是整数。因此,我们看到 1, 41, 44, 49

整数属性?那是什么?

这里的“整数属性”一词是指可以不改变地转换为整数和从整数转换的字符串。

因此,"49" 是一个整数属性名,因为当它转换为整数并返回时,它仍然相同。但 "+49""1.2" 不是

// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property

…另一方面,如果键是非整数,则按创建顺序列出,例如

let user = {
  name: "John",
  surname: "Smith"
};
user.age = 25; // add one more

// non-integer properties are listed in the creation order
for (let prop in user) {
  alert( prop ); // name, surname, age
}

因此,要解决电话代码的问题,我们可以通过将代码设为非整数来“作弊”。在每个代码前添加加号 "+" 就足够了。

像这样

let codes = {
  "+49": "Germany",
  "+41": "Switzerland",
  "+44": "Great Britain",
  // ..,
  "+1": "USA"
};

for (let code in codes) {
  alert( +code ); // 49, 41, 44, 1
}

现在按预期工作。

总结

对象是具有多个特殊功能的关联数组。

它们存储属性(键值对),其中

  • 属性键必须是字符串或符号(通常是字符串)。
  • 值可以是任何类型。

要访问属性,我们可以使用

  • 点表示法:obj.property
  • 方括号表示法 obj["property"]。方括号允许从变量中获取键,例如 obj[varWithKey]

其他运算符

  • 要删除属性:delete obj.prop
  • 要检查是否存在具有给定键的属性:"key" in obj
  • 要遍历对象:for (let key in obj) 循环。

我们在本章中学习的内容称为“普通对象”,或简称 Object

JavaScript 中还有许多其他种类的对象

  • Array 用于存储有序数据集合,
  • Date 用于存储有关日期和时间的信息,
  • Error 用于存储有关错误的信息。
  • …等等。

它们有我们稍后会学习的特殊功能。有时人们会说“Array 类型”或“Date 类型”,但从形式上讲它们不是它们自己的类型,而是属于单个“object”数据类型。并且它们以各种方式对其进行扩展。

JavaScript 中的对象非常强大。在这里,我们只是粗略地了解了一个非常庞大的主题。我们将在本教程的后续部分与对象密切合作,并了解更多有关它们的信息。

任务

重要性:5

编写代码,每行一个操作

  1. 创建一个空对象 user
  2. 添加属性 name,其值为 John
  3. 添加属性 surname,其值为 Smith
  4. name 的值更改为 Pete
  5. 从对象中删除属性 name
let user = {};
user.name = "John";
user.surname = "Smith";
user.name = "Pete";
delete user.name;
重要性:5

编写函数 isEmpty(obj),如果对象没有属性,则返回 true,否则返回 false

应该这样工作

let schedule = {};

alert( isEmpty(schedule) ); // true

schedule["8:30"] = "get up";

alert( isEmpty(schedule) ); // false

使用测试打开沙箱。

只需循环遍历对象,如果至少有一个属性,则立即返回 false

function isEmpty(obj) {
  for (let key in obj) {
    // if the loop has started, there is a property
    return false;
  }
  return true;
}

使用测试在沙箱中打开解决方案。

重要性:5

我们有一个对象,用于存储我们团队的薪水

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

编写代码以求出所有薪水的总和,并将其存储在变量 sum 中。在上面的示例中,应为 390

如果 salaries 为空,则结果必须为 0

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
};

let sum = 0;
for (let key in salaries) {
  sum += salaries[key];
}

alert(sum); // 390
重要性:3

创建一个函数 multiplyNumeric(obj),将 obj 的所有数字属性值乘以 2

例如

// before the call
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// after the call
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};

请注意,multiplyNumeric 无需返回任何内容。它应就地修改对象。

P.S. 在此处使用 typeof 检查数字。

使用测试打开沙箱。

function multiplyNumeric(obj) {
  for (let key in obj) {
    if (typeof obj[key] == 'number') {
      obj[key] *= 2;
    }
  }
}

使用测试在沙箱中打开解决方案。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 - 将它们包装在 <pre> 标记中,对于 10 行以上 - 使用沙箱 (plnkrjsbincodepen…)