Menu

JavaScript 对象:属性、方法与用法

JavaScript 对象实际上是怎么工作的:创建对象、读写属性、添加方法,以及让对象代码保持清晰的常用写法。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

对象是一组带名字的值

数组按位置组织值,对象按名字组织值。当你要描述的东西由带标签的部分组成时,比如一个有名字和年龄的用户、一个有方法和 URL 的请求,就用对象。

创建对象最简短的方式是对象字面量:

每一项都是一个 key: value 键值对。键在底层是字符串(像 name 这样的合法标识符可以不加引号)。值可以是 JavaScript 能表示的任何东西:数字、字符串、布尔值、数组、函数,甚至其他对象。

最后一项后面加逗号没有问题。大多数团队都会保留它:以后添加新属性时,代码差异会更干净。

读写属性

访问属性有两种方式:点号和方括号。

点号更简洁,也是默认的选择。方括号的用武之地在于:键存放在变量中,或者键不是合法的标识符:

读取不存在的属性会返回 undefined,不会报错:

这可能会悄悄掩盖拼写错误。如果你希望出错时有明显的反应,就需要显式地检查。

添加和删除属性

对象是开放的,你可以随时添加键:

也可以用 delete 删除它们:

delete 是那种不会天天用到的运算符,但当你需要彻底删掉一个键(而不只是把它设为 undefined)时,它就是正确的工具。设置 user.email = undefined 会保留这个键,"email" in user 仍然是 true。

判断属性是否存在

有三种方式,含义略有不同:

  • in 检查键是否存在,包括从原型链继承来的键。
  • Object.hasOwn(obj, key) 只检查对象自身的键。想忽略继承来的内容时就用它。它取代了老的 hasOwnProperty 调用。
  • obj.key !== undefined 大多数时候都能用,但当属性被显式设为 undefined 时,结果就不准了。

拿不准的时候就选 Object.hasOwn,它做的通常正是你想要的。

方法:挂在对象上的函数

值为函数的属性就是方法。在对象字面量中定义方法有一种简写语法:

方法中的 this 指向调用这个方法的对象,在这里就是 user。greet 正是靠它知道该用谁的名字。

有一点要注意:箭头函数没有自己的 this,所以如果方法需要读取对象的其他属性,就不该用箭头函数:

凡是用到 this 的方法,都用简写方法语法(greet() { ... })。箭头函数很适合做回调,但不适合做对象方法。

嵌套对象

值也可以是对象,可以一层层嵌套下去:

读取嵌套属性靠链式访问:user.address.city。问题在于:只要中间任何一步是 null 或 undefined,就会得到 TypeError:

console.log(user.profile.city);
// TypeError: Cannot read properties of undefined (reading 'city')

可选链(user.profile?.city)是现代的解决办法:当某一环缺失时,它返回 undefined 而不是抛出异常。本章后面有专门的一页讲它。

遍历对象

需要逐个访问对象的每个键时,Object.keys、Object.values 和 Object.entries 这一组方法就是你要的:

Object.entries 最有用:它一次给出键和值,和数组解构配合得很好。

还有一种 for...in 循环,但它也会遍历继承来的属性,这通常不是你想要的:

for (const key in scores) {
  console.log(key);  // works, but includes inherited keys
}

除非你确实需要继承来的属性,否则优先使用 Object.keys / Object.entries。

值得了解的简写语法

两种随处可见的语法糖:

当变量名和键相同时,就可以使用属性简写。计算属性名([expr] 形式)让你可以动态构建属性名,在编写按名字更新字段的函数时很有用。

对象按引用比较

几乎每个人都会在这里栽一次跟头:

对对象使用 ===,检查的是两边是否指向内存中的同一个对象,而不是内容是否相同。两个属性完全相同的对象,仍然是两个不同的对象。

要判断“它们的结构是否相同”,你需要手动比较各个字段,或者使用深度比较的辅助函数。JSON.stringify(a) === JSON.stringify(b) 是一种简单粗暴的检查,对普通数据有效,但遇到函数、undefined 或循环引用就不行了。

即使用 const,对象也是可变的

const 把变量锁定在一个值上,但不会冻结这个值所指向的对象:

如果你确实需要不可变性,Object.freeze(user) 可以阻止进一步的修改(只作用于浅层,嵌套对象仍然可以修改)。实际上,大多数代码依靠约定而不是 Object.freeze:把 const 对象理解为“不要重新给这个变量赋值”,在设计层面处理不可变性。

下一章:数组

对象和数组是构成其他一切的两块基石。对象处理带标签的数据,数组处理有序的数据。下一章讲 JavaScript 数组如何工作,以及承担大部分工作的几个方法(push、slice、map 等)。

常见问题

JavaScript 中如何创建对象?

通常的方式是对象字面量:const user = { name: 'Ada', age: 30 }。花括号里是用逗号分隔的键值对。键是字符串(合法的标识符可以不加引号),值可以是任何东西:数字、字符串、数组、函数,以及其他对象。

点号访问和方括号访问有什么区别?

当键是已知的标识符时,user.name 和 user['name'] 作用相同。如果键存放在变量中(user[key])、包含点号无法解析的字符(空格、连字符),或者以数字开头,就用方括号。其他情况下点号更简洁。

如何判断对象上是否存在某个属性?

用 'name' in user 检查键是否存在,包括继承来的键。用 Object.hasOwn(user, 'name') 只检查对象自身的属性,它是 hasOwnProperty 的现代替代写法。检查 user.name !== undefined 也行,但如果属性被显式设为 undefined,结果就不准了。

JavaScript 中如何遍历对象?

for (const key in obj) 会遍历键,包括继承来的键。更常见的做法是把 Object.keys(obj)、Object.values(obj) 或 Object.entries(obj) 与 for...of 循环或 .forEach() 搭配使用。当你需要在同一次迭代中同时拿到键和值时,Object.entries 很方便。

Coddy 编程语言插图

和 Coddy 一起学编程

开始