Skip to content
This page has been auto-translated and may contain errors.View in English

对象

docs.scrimba.com

假设你正在描述一个用户:姓名、电子邮件、年龄、是否已登录。你可以把这些分散到四个独立的变量中,但它们应该放在一起,把它们分开意味着每次提到"用户"时都要传递四样东西。对象让你把所有内容聚集在一个值中,其中每一部分都有一个你可以查找的名称。你处理的大多数数据(用户、产品、购物车)的形状正好是这样。

创建对象

对象 是一个命名值的集合,用花括号编写。在其中,你列出 key: value 对,用逗号分隔。键是名称;值是它保存的内容:

js
const user = {
  name: "李明",
  age: 27,
};

console.log(user.name); // 李明

你用一个点和键读取一个值:user.name。这称为点符号,也是你最常使用的方式。左边是键,右边是它存储的值。

还有第二种方式来读取值,使用方括号,将键作为字符串:

js
console.log(user["name"]); // 李明

大多数时候点符号读起来更干净,所以使用它。当键不是普通单词时(比如它包含空格),或者你不知道键是什么且它在另一个变量中时,括号才派上用场。

对象 在字符串键下组合相关值,写成花括号内的 key: value 对。数组 按位置保存有序列表,而对象按名称保存值:

js
const user = {
  name: "李明",
  age: 27,
  isActive: true,
};

console.log(user.name); // 李明
console.log(user["age"]); // 27

有两种访问方式。点符号(user.name)是默认方式:更短更清晰。括号符号(user["age"])做同样的事情但将键作为字符串,这解锁了点符号无法做的两件事。

当键不是有效标识符时需要使用括号形式,例如当它包含空格或以数字开头时,以及当键是动态的,存在于变量中而不是字面输入时:

js
const field = "name";
console.log(user[field]); // 李明,读取存储在 `field` 中的键
console.log(user.field); // undefined,查找名为"field"的字面键

这种区别会让人困惑:user[field] 使用 field 的值,而 user.field 查找拼写为 field 的键。读取不存在的键返回 undefined 而不是错误。

对象 是 JavaScript 在键下组合值的核心结构。键是字符串(或 Symbols,你现在可以先不管),值可以是任何东西,包括另一个对象或 函数。你将其写成花括号内的 key: value 对:

js
const user = {
  name: "李明",
  age: 27,
  isActive: true,
};

console.log(user.name); // 李明
console.log(user["age"]); // 27

点符号是习语性的默认方式。括号符号是相同的查找,键作为表达式计算,这就是为什么它恰好在两种情况下需要:当键不是有效标识符时(user["first name"]user["2fa"]),以及当键是动态时:

js
const field = "name";
console.log(user[field]); // 李明,先将 `field` 计算为 "name"

有两个行为值得记住。读取缺失的键返回 undefined,从不抛出,所以输入错误静默失败而不是大声失败。键在底层总是字符串:user[27]user["27"] 到达相同的位置,因为数字被强制转换为字符串键。当对象键和数组索引开始看起来相似时,这种强制转换是困惑的常见来源。

js
const product = {
  title: "台灯",
  price: 40,
  inStock: true,
};

console.log(`${product.title} costs ${product.price}`); // 台灯 costs 40

对象是像任何其他值一样的值。你可以将其存储在 const 中,将其放在数组中,将其传递给函数,并返回它。键给其部分命名,这正是重点:product.price 说明是什么,而存储在变量中的裸 40 则不能。

Juno创建对象 对象在名称下聚集相关值,写成花括号内的 key: value 对。用点和键读取值,如 user.name。仅当键中有奇怪字符或存在于变量中时才使用方括号形式。
Juno创建对象 对象按名称到达值,而数组按位置到达它们。点符号是你的默认方式;括号符号将键作为字符串,所以对于不是普通单词的键或存在于变量中的键使用它。记住 user[field] 读取存储在 field 中的键,而 user.field 查找字面 field 键,缺失的键返回 undefined 而不是错误。
Juno创建对象 键在底层是字符串,所以 user[27]user["27"] 到达同一位置,缺失的键返回 undefined 而不是抛出。点符号是习语性的;括号符号是相同的查找,键计算得出,对于非标识符键和动态键需要。输入错误时安静的 undefined 很方便,直到它隐藏了一个 bug。

修改对象

对象一旦创建就不是固定的。你可以添加新键、更改现有键或完全删除一个键。要添加或更新,分配给该键:

js
const user = {
  name: "李明",
};

user.age = 30; // 添加新键
user.name = "李明强"; // 更新现有的

console.log(user); // { name: "李明强", age: 30 }

如果键已存在,分配给它会替换该值。如果不存在,它会被创建。要删除键,使用 delete

js
delete user.age;
console.log(user); // { name: "李明强" }

这是让人惊讶的部分。一个 const 对象仍然可以修改其属性。 const 意味着名称 user 不能指向不同的对象,但它指向的对象是公平的游戏:

js
const cart = { total: 0 };
cart.total = 25; // 好的,改变内部的内容
// cart = { total: 25 }; // 这会错误,重新分配名称

对象是可变的:你在创建后添加、更新和删除键。分配给键如果是新的就创建它,如果存在就覆盖它。删除键是 delete 的工作:

js
const user = { name: "李明" };

user.age = 30; // 添加
user.age = 31; // 更新
delete user.age; // 删除

console.log(user); // { name: "李明" }

抓住人的是 constconst 锁定名称,而不是内容。 绑定不能重新分配给新对象,但它持有的对象保持完全可编辑:

js
const cart = { total: 0, items: 0 };
cart.total = 25; // 允许,改变对象
cart.items += 1; // 允许
// cart = {}; // TypeError,重新分配 const 绑定

这就是为什么 const 是明智的默认值,即使对于你计划更改的对象。你几乎从不想重新指向变量,const 会在你意外这样做时阻止你,同时让对象本身保持开放。

对象是可变的。分配给键会进行 upsert(创建或覆盖),delete 删除它:

js
const user = { name: "李明", age: 30 };

user.role = "admin"; // 添加
delete user.age; // 删除

console.log(user); // { name: "李明", role: "admin" }

const 的区别是需要内化的,因为它真的是关于 const 保护什么。const 冻结绑定,从不冻结值。 名称不能重新分配;它引用的对象保持开放:

js
const cart = { total: 0 };
cart.total = 25; // 好的,改变引用的对象
// cart = { total: 25 }; // TypeError: Assignment to constant variable

如果你真的想锁定对象本身,Object.freeze(obj) 防止添加、更改或删除键,尽管它是浅冻结(嵌套对象保持可变)并在非严格代码中静默无效而不是出错。实际上你很少需要它。值得保持的习惯是默认使用 const:它表示绑定是稳定的并阻止意外重新分配,这是实际发生的错误,同时保持正常变异可用。

Juno修改对象 分配给键来添加或更新它,使用 delete 删除一个。惊喜是 const:它阻止你将 user 指向不同的对象,但你仍然可以更改它持有的对象内的内容。我花了一段时间才停止期望 const 锁定一切。
Juno修改对象 分配添加或覆盖键,delete 删除一个。const 锁定名称,而不是内容,所以 const 对象保持完全可编辑,而变量本身不能被重新指向。这正是为什么 const 即使对于你打算更改的对象也是一个很好的默认值。
Juno修改对象 分配 upsert 键,delete 删除一个,const 保护绑定而不是值,所以引用的对象保持可变。如果你必须锁定对象,Object.freeze 浅层执行并在严格模式外静默失败。很少用到;默认使用 const 已经阻止了实际出现的重新分配错误。

方法和 this

对象中的值可以是函数。当它是时,它被称为 方法:对象可以做的东西,而不是它存储的事实。你用相同的点加括号调用它:

js
const user = {
  name: "李明",
  greet() {
    return `Hi, I am ${this.name}`;
  },
};

console.log(user.greet()); // Hi, I am 李明

内部的新词是 this。当你调用 user.greet() 时,this 意味着"调用此方法的对象",这里是 user。所以 this.name 从同一对象读取 name 键。这就是方法如何到达其自己对象的值而不需要你传入它们。

当函数存储为对象上的值时,它是一个 方法。方法让对象在其数据旁携带行为,它们通过 this 到达该数据:

js
const cart = {
  items: ["台灯", "书桌"],
  total: 65,
  summary() {
    return `${this.items.length} items, ${this.total} total`;
  },
};

console.log(cart.summary()); // 2 items, 65 total

this 的规则涵盖了你几乎所有会遇到的情况:this 是调用时点左边的对象。 调用 cart.summary()thiscart,所以 this.itemsthis.total 从同一对象读取。

短语"在调用时"很重要。this 在你写方法时不是固定的,它由方法如何被调用决定。以 cart.summary() 的方式调用它,thiscart;把函数拉出来自己调用,this 不再指向 cart。你会在后面遇到那个边界,但对于用点调用的方法,this 的行为正是你所希望的。

存储在对象上的函数是 方法,它通过 this 访问对象的自身状态。在实践中重要的绑定规则:this 由函数的调用方式决定,而不是定义位置。 对于正常的方法调用,this 是点左边的对象:

js
const cart = {
  total: 65,
  discount: 0.1,
  finalPrice() {
    return this.total * (1 - this.discount);
  },
};

console.log(cart.finalPrice()); // 58.5

因为绑定是动态的,分离方法会破坏它:const fn = cart.finalPrice; fn(); 失去了 cart 上下文,this 不再指向你想要的位置。

实际问题是将箭头函数作为方法。箭头没有自己的 this;它从定义它的周围作用域捕获 this。所以用作方法的箭头读取外部 this(在顶级,而不是对象)而不是它所在的对象:

js
const user = {
  name: "李明",
  greet: () => `Hi, I am ${this.name}`, // 错误:`this` 不是 `user`
  greetOk() {
    return `Hi, I am ${this.name}`; // 正确:方法简写
  },
};

要遵循的规则:当函数需要 this 意味着对象时使用方法简写(greet() {}),为实际想要周围 this 的回调保留箭头,这是大多数其他地方。

Juno方法和 this 存储在对象上的函数是方法,用点和括号调用,如 user.greet()。在其内部,this 意味着调用该方法的对象,所以 this.name 读取该对象自己的 name。这就是方法如何获取其自己对象的值。
Juno方法和 this 方法是存在于对象上的函数,this 是你调用它时点左边的对象。所以 cart.summary() 使 this 意味着 cart。问题是 this 在调用时决定,而不是在你写方法时,这在你从对象拉出方法的那一刻很重要。
Juno方法和 thisthis 由函数的调用方式绑定,而不是它所在的地方,所以从对象分离方法会失去其对象。当你需要 this 意味着对象时使用方法简写,为你实际想要周围 this 的回调保留箭头函数,因为箭头捕获外部的。那个箭头作为方法的混合是经典的,它静默失败。

对象和数组一起

大多数真实数据是对象列表:许多用户、许多产品,每个对象一条具有相同键的记录。你把对象放在 数组 中:

js
const products = [
  { title: "台灯", price: 40 },
  { title: "笔记本", price: 6 },
  { title: "笔", price: 3 },
];

console.log(products[0].title); // 台灯

你通过其位置到达一个对象(products[0]),然后用点访问其字段(.title)。连在一起,products[0].title 读取第一个产品的标题。

要从每个对象读取一个字段,循环遍历数组:

js
for (const product of products) {
  console.log(product.title);
}
// 台灯
// 笔记本
// 笔

循环的每一圈,product 是列表中的一个对象,product.title 读取其标题。这种形状,你循环遍历的对象数组,是你将进行的大多数数据工作。

对象数组是大多数真实数据的形状:每个对象一条记录,每个都共享相同的键。组合数组访问和点访问到达任何单个字段:

js
const products = [
  { title: "台灯", price: 40 },
  { title: "笔记本", price: 6 },
  { title: "笔", price: 3 },
];

console.log(products[1].price); // 6

因为它是一个数组,数组方法 在其上工作,这就是它们赚取其地位的地方。用 map 从每个对象提取一个字段,用 filter 保持匹配的记录,用 reduce 将它们组合成一个值:

js
const titles = products.map((product) => product.title);
console.log(titles); // ["台灯", "笔记本", "笔"]

const total = products.reduce((sum, product) => sum + product.price, 0);
console.log(total); // 49

回调每次迭代接收一个对象,你通过键读取其字段。一旦这点明确,"对于每条记录,取这个字段"和"在所有记录中累计这个字段"就成为单行代码。

对象数组是结构化数据的规范形状,数组方法 是你使用它的方式。每个回调接收一条记录;你通过键读取其字段并转换、过滤或聚集:

js
const orders = [
  { id: 1, total: 40, paid: true },
  { id: 2, total: 6, paid: false },
  { id: 3, total: 3, paid: true },
];

const paidTotal = orders
  .filter((order) => order.paid)
  .reduce((sum, order) => sum + order.total, 0);

console.log(paidTotal); // 43

链接从上到下读作管道:保持已支付的订单,然后累计它们的总计。这干净地组合因为每一步接受一个对象数组并返回另一个数组(或,对于 reduce,一个单一值)。

当数据增长时要记住的一件事:这些对象由引用保持,下面的高级部分精确说明了这一点。在 mapforEach 回调内变换对象改变数组中的原始记录,不是副本,这有时是你想要的,经常是一个惊喜。当你打算转换而不接触源时,构建新对象而不是编辑你被给予的对象。

Juno对象和数组一起 大多数真实数据是对象数组:一个列表,其中每一项都是一条具有相同键的记录。用 products[0] 到达一个,然后用 products[0].title 到达一个字段。循环遍历数组接触每条记录,你有你将进行的大多数数据工作的模式。
Juno对象和数组一起 对象数组是大多数真实数据到达的形状,数组方法为它构建。map 从每条记录提取字段,filter 保持匹配的,reduce 在它们中累计,回调每圈被给予一个对象。"对于每条记录,取这个字段"停止成为循环,成为单行。
Juno对象和数组一起 数组方法链成记录上的管道:过滤下来,然后减少到一个值,每一步通过键读取字段。注意这些对象由引用保持,所以在 mapforEach 回调内变换一个在原地编辑原始的。当你打算转换而不接触源时,构建新对象而不是编辑你被给予的对象。

对象进一步学习

对象和 数组 是你将不断使用的两个结构,你处理的大多数数据是两者的某种嵌套:具有数组字段的对象、对象数组和两者的组合。将它们与 数组方法 配对,你可以将几乎任何数据形成你需要的形式。

下一步是在页面中把对象投入使用。当你到达 DOM 时,你读取和更改的元素也是对象,带有你设置的键和你调用的方法,所以这里的一切直接转移过去。