- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
对象(Object)是 JavaScript 中最重要的数据类型之一,它用于存储键值对(key-value pairs)集合,承载着语言中几乎所有复杂的数据结构。本文以 en.javascript.info 仓库中 1-js/04-object-basics/01-object/article.md 章节为核心,系统讲解对象的创建、属性访问、计算属性、属性名限制、存在性检测、遍历顺序等机制,并结合仓库中 2-hello-object、3-is-empty、5-sum-object、8-multiply-numeric 等配套练习任务与测试代码,帮助读者真正掌握对象这一"一切皆对象"语言的地基。
什么是对象:键值对的集合
JavaScript 中有八种数据类型,其中七种被称为"原始类型"(primitive),它们的值只包含单一的数据(如一个字符串、一个数字)。与之相对,对象用于存储各种数据的键值集合以及更复杂的实体。
一个对象可以通过花括号{…}创建,并可选地包含一个属性列表。属性是"key: value"对,其中key是字符串(也称为"属性名"),value可以是任意类型。
可以把对象想象成一个带标签文件的档案柜:每一份数据按 key 存放在对应的文件中,通过文件名可以轻松查找、添加或移除文件。
创建一个空对象("空档案柜")有两种语法:
let user = new Object(); // "对象构造函数"语法 let user = {}; // "对象字面量"语法通常使用花括号{...},这种声明方式被称为对象字面量(object literal)。
字面量与属性
我们可以在{...}中立即放入一些属性作为 "key: value" 对:
let user = { // 一个对象 name: "John", // 通过 key "name" 存储值 "John" age: 30 // 通过 key "age" 存储值 30 };一个属性在冒号":"之前是 key(也称为"名称"或"标识符"),冒号右侧是值。上面的user对象有两个属性:名为"name"值为"John"的属性,以及名为"age"值为30的属性。
可以随时添加、删除和读取其中的"文件"。属性值使用点号表示法(dot notation)访问:
// 获取对象的属性值: alert( user.name ); // John alert( user.age ); // 30属性的值可以是任意类型,例如添加一个布尔值:
user.isAdmin = true;要删除属性,使用delete运算符:
delete user.age;也可以使用多词属性名,但此时必须加引号:
let user = { name: "John", age: 30, "likes birds": true // 多词属性名必须加引号 };列表中的最后一个属性可以以逗号结尾:
let user = { name: "John", age: 30, }这被称为"尾随逗号"(trailing/hanging comma),它让添加、删除或移动属性变得更方便,因为所有行看起来都一样。
方括号表示法
对于多词属性,点号表示法无法工作:
// 这会产生语法错误 user.likes birds = trueJavaScript 会认为在访问user.likes,然后遇到意外的birds时报语法错误。点号要求 key 必须是合法的变量标识符,即:不含空格、不以数字开头、不包含特殊字符($和_除外)。
有一种替代方案——方括号表示法(square bracket notation),它适用于任意字符串:
let user = {}; // 设置 user["likes birds"] = true; // 获取 alert(user["likes birds"]); // true // 删除 delete user["likes birds"];注意方括号内的字符串必须正确加引号(任何引号类型都可以)。
方括号还提供了一种通过表达式获取属性名的方式——而不仅仅是字面字符串,例如从变量获取:
let key = "likes birds"; // 等同于 user["likes birds"] = true; user[key] = true;这里的变量key可以在运行时计算,或依赖于用户输入,然后用它来访问属性,这带来了极大的灵活性。例如:
let user = { name: "John", age: 30 }; let key = prompt("你想了解用户哪方面的信息?", "name"); // 通过变量访问 alert( user[key] ); // 如果输入 "name" 则显示 John点号表示法则不能这样使用:
let user = { name: "John", age: 30 }; let key = "name"; alert( user.key ) // undefined计算属性
在创建对象的字面量中也可以使用方括号,这被称为计算属性(computed properties):
let fruit = prompt("想买什么水果?", "apple"); let bag = { [fruit]: 5, // 属性名取自变量 fruit }; alert( bag.apple ); // 如果 fruit="apple" 则显示 5计算属性的含义很简单:[fruit]表示属性名应取自fruit变量。如果访问者输入"apple",bag将变成{apple: 5}。
本质上这与下面的写法效果相同:
let fruit = prompt("想买什么水果?", "apple"); let bag = {}; // 从 fruit 变量获取属性名 bag[fruit] = 5;方括号内还可以使用更复杂的表达式:
let fruit = 'apple'; let bag = { [fruit + 'Computers']: 5 // bag.appleComputers = 5 };方括号表示法比点号表示法强大得多——它允许任意属性名和变量。但它写起来也更繁琐。所以大多数时候,当属性名已知且简单时使用点号;需要更复杂的场景时再切换到方括号。
属性值简写
在实际代码中,我们经常用已有的变量作为属性值:
function makeUser(name, age) { return { name: name, age: age, // ...其他属性 }; } let user = makeUser("John", 30); alert(user.name); // John当属性名与变量名相同时,可以使用属性值简写(property value shorthand),把name: name直接写成name:
function makeUser(name, age) { return { name, // 等同于 name: name age, // 等同于 age: age // ... }; }同一个对象中也可以混用普通属性和简写:
let user = { name, // 等同于 name: name age: 30 };属性名的限制
变量不能使用语言保留字(如for、let、return等)作为名称,但对象属性没有这种限制:
// 这些属性都合法 let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6简言之,属性名没有任何限制,可以是任意字符串或 symbol(一种用于标识符的特殊类型,后续章节会讲到)。其他类型会自动转换为字符串。
例如,数字0作为属性键时会变成字符串"0":
let obj = { 0: "test" // 等同于 "0": "test" }; // 两个 alert 访问的是同一个属性(数字 0 被转换为字符串 "0") alert( obj["0"] ); // test alert( obj[0] ); // test(同一个属性)有一个小陷阱:特殊属性__proto__。我们无法将其设置为非对象值:
let obj = {}; obj.__proto__ = 5; // 赋值为数字 alert(obj.__proto__); // [object Object] - 值是一个对象,赋值未生效从代码可以看到,对原始值5的赋值被忽略了。__proto__的特殊性质将在 原型继承 章节详细讨论,原型方法 章节也会给出解决此类行为的建议方法。
属性存在性测试:"in" 运算符
与许多其他语言不同,JavaScript 对象的显著特性是可以访问任何属性——即使属性不存在也不会报错!读取不存在的属性只会返回undefined,因此可以轻松测试属性是否存在:
let user = {}; alert( user.noSuchProperty === undefined ); // true,表示"没有这个属性"此外还有一个专门的"in"运算符,语法为:
"key" in object例如:
let user = { name: "John", age: 30 }; alert( "age" in user ); // true,user.age 存在 alert( "blabla" in user ); // false,user.blabla 不存在注意in的左侧必须是属性名,通常是一个带引号的字符串。如果省略引号,则意味着变量应该包含要测试的实际名称:
let user = { age: 30 }; let key = "age"; alert( key in user ); // true,属性 "age" 存在为什么需要in运算符?与undefined比较还不够吗?大多数情况下比较undefined都能正常工作,但在一种特殊情况下会失败,而"in"能正确处理——那就是属性存在但存值为undefined:
let obj = { test: undefined }; alert( obj.test ); // undefined,所以——没有这个属性? alert( "test" in obj ); // true,属性确实存在!上面代码中obj.test属性在技术上存在,所以in运算符判断正确。这种情况很少发生,因为undefined不应被显式赋值,我们通常用null表示"未知"或"空"值。因此in运算符在代码中是相当少见的存在。
"for..in" 循环
要遍历对象的所有键,存在一种特殊的循环形式for..in,它与之前学过的for(;;)完全不同:
for (key in object) { // 对对象的每个属性键执行循环体 }例如,输出user的所有属性:
let user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { // 键 alert( key ); // name, age, isAdmin // 键对应的值 alert( user[key] ); // John, 30, true }注意,所有的 "for" 结构都允许在循环内部声明循环变量,如这里的let 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排在最前面。但运行代码会看到完全不同的结果:USA(1)排第一,然后是 Switzerland(41)等。电话区号按升序排列,因为它们都是整数,所以看到的是1, 41, 44, 49。
什么是"整数属性"?这里的"整数属性"指可以无损地转换成整数再转回来的字符串。因此"49"是整数属性名(转成数字再转回仍是它本身),而"+49"和"1.2"不是:
// Number(...) 显式转换为数字 // Math.trunc 是移除小数部分的内置函数 alert( String(Math.trunc(Number("49"))) ); // "49",相同,是整数属性 alert( String(Math.trunc(Number("+49"))) ); // "49",不同 "+49" ⇒ 不是整数属性 alert( String(Math.trunc(Number("1.2"))) ); // "1",不同 "1.2" ⇒ 不是整数属性另一方面,如果键不是整数,则按创建顺序列出:
let user = { name: "John", surname: "Smith" }; user.age = 25; // 再添加一个 // 非整数属性按创建顺序列出 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 }现在按预期工作了。
配套练习:动手验证对象机制
en.javascript.info 仓库为本章配备了多个由浅入深的编程任务,每个任务都配有标准解法和 Mocha 测试(_js.view目录下的test.js),非常适合用来验证本文所述的机制。
练习一:Hello, object(创建与增删改)
任务要求逐行完成:创建空对象user;添加属性name(值John)与surname(值Smith);将name改为Pete;最后删除name属性。标准解法(见 2-hello-object/solution.md)正好覆盖了对象字面量、点号赋值与delete运算符:
let user = {}; user.name = "John"; user.surname = "Smith"; user.name = "Pete"; delete user.name;练习二:检查空对象(for..in 的经典应用)
任务要求编写isEmpty(obj)函数,判断对象是否没有任何属性。解法思路是:循环遍历对象,只要循环启动就说明存在至少一个属性,立即return false(见 3-is-empty/solution.md):
function isEmpty(obj) { for (let key in obj) { // 如果循环启动了,就说明有属性 return false; } return true; }仓库中的 3-is-empty/_js.view/test.js 用 Mocha 断言验证了两种场景:空对象{}返回true,含anything: false属性的对象返回false(注意值为false也视为属性存在)。
练习三:对象属性求和(遍历取值)
任务要求对存有团队工资的对象求和,空对象时结果为0。解法利用for..in逐个累加(见 5-sum-object/solution.md):
let salaries = { John: 100, Ann: 160, Pete: 130 }; let sum = 0; for (let key in salaries) { sum += salaries[key]; } alert(sum); // 390练习四:数值属性翻倍(typeof 类型过滤)
任务要求编写multiplyNumeric(obj),将对象中所有数值型属性的值乘以 2,字符串等其他类型保持不变,且原地修改、无需返回。仓库提供了完整实现与测试(见 8-multiply-numeric/_js.view/solution.js 与 8-multiply-numeric/_js.view/test.js):
function multiplyNumeric(obj) { for (let key in obj) { if (typeof obj[key] == 'number') { obj[key] *= 2; } } }配套测试断言:width200 → 400、height300 → 600,而title字符串保持不变;并且调用multiplyNumeric({})的返回值必须是undefined(即函数不返回任何东西)。这个小练习同时演示了typeof类型检测、for..in遍历与通过obj[key]动态读写属性这三个核心知识点。
总结
对象是带有若干特殊特性的关联数组(associative arrays),它们以键值对存储属性:
- 属性键必须是字符串或 symbol(通常是字符串);
- 值可以是任意类型。
访问属性的方式:
- 点号表示法:
obj.property; - 方括号表示法
obj["property"]——方括号允许从变量取键,如obj[varWithKey]。
其他运算符:
- 删除属性:
delete obj.prop; - 检查是否存在具有给定键的属性:
"key" in obj; - 遍历对象:
for (let key in obj)循环。
本章研究的是"普通对象"(plain object),也就是Object本身。JavaScript 中还有很多其他类型的对象:
Array用于存储有序数据集合,Date用于存储日期和时间信息,Error用于存储错误信息,- ……等等。
它们都有各自特殊的特性,将在后续章节中学习。有时人们会说"Array 类型"或"Date 类型",但从形式上讲,它们并不是独立的类型,而是属于同一个"object"数据类型,并以各种方式扩展它。
JavaScript 中的对象非常强大,这里只是触及了这个庞大主题的表面。后续教程中我们会继续深入使用对象并了解更多相关知识。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
wxBot微信机器人多账号管理终极方案:三步实现高效自动化运营
wxBot微信机器人多账号管理终极方案:三步实现高效自动化运营 你是否在为管理多个微信账号而烦恼?想要实现自动化回复、批量消息发送,却苦于技术门槛太高?wxBo
即时通讯AI 应用5分钟上手Obsidian-DB-Folder:创建你的第一个笔记数据库
5分钟上手Obsidian DB Folder:创建你的第一个笔记数据库 Obsidian DB Folder是一款强大的Obsidian插件,它能让你在Obs
wechat_spider微信爬虫实战:HTTPS中间人代理架构深度解析与最佳实践
wechat_spider微信爬虫实战:HTTPS中间人代理架构深度解析与最佳实践 微信数据采集已成为众多开发者和数据分析师获取内容洞察的重要途径。wechat
网页爬虫后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考