引言:从理论到实践的跨越

在前一篇文章中,我们系统学习了JavaScript对象的基础知识。然而,编程学习的一个核心原则是理论知识必须通过实践来巩固。阅读代码和理解概念只是第一步,亲自动手编写代码、解决问题才是真正掌握知识的关键。

MDN为对象基础章节设计了四个循序渐进的技能测试题目,从基本的属性访问和方法调用开始,逐步深入到创建复杂的嵌套对象、使用this实现方法复用,最后引入构造函数来消除代码重复。


一、技能测试一:对象的属性访问与方法调用

1.1 任务描述

提供一个简单的 cat 对象字面量,包含 namebreedcolor 属性和一个 greeting 方法。要求完成三项操作。

const cat = {
  name: "Bertie",
  breed: "Cymric",
  color: "white",
  greeting: function () {
    console.log("Meow!");
  },
};

const catName;  // 等待赋值

// 在此处补全代码

1.2 三项操作要求

序号 要求 使用方式
name 属性的值存储到 catName 变量中 括号表示法
运行 greeting 方法 点表示法
color 属性值更新为 "black" 点表示法

1.3 代码实现

const cat = {
  name: "Bertie",
  breed: "Cymric",
  color: "white",
  greeting: function () {
    console.log("Meow!");
  },
};

// ① 使用括号表示法获取 name 属性值
const catName = cat["name"];

// ② 使用点表示法调用 greeting 方法
cat.greeting();

// ③ 更新 color 属性值
cat.color = "black";

1.4 设计意图分析

这道题在一组操作中有意识地引导同时使用点表示法括号表示法,强化对比认知:

方式 语法 本题用途 适用场景
点表示法 cat.greeting() 调用方法 & 更新属性 属性名已知的字面量
括号表示法 cat["name"] 获取属性值赋值给变量 属性名在变量中或动态计算

两种方式可以并存使用,开发者应根据具体场景选择最合适的方式。


二、技能测试二:构建复杂嵌套对象

2.1 任务描述

创建一个表示乐队的对象字面量。这个对象不仅包含基本属性,更关键的是包含一个 albums 数组——数组中的每个元素本身又是一个包含 namereleased 属性的对象。这种嵌套结构模拟了真实开发中常见的JSON数据组织形式。

let bandInfo;

// 在此处补全代码

2.2 构建嵌套对象

const band = {
  name: "The Beatles",
  nationality: "British",
  genre: "Rock",
  members: 4,
  formed: 1960,
  split: 1970,
  albums: [
    {
      name: "Please Please Me",
      released: 1963,
    },
    {
      name: "Abbey Road",
      released: 1969,
    },
  ],
};

数据结构可视化

band (Object)
├── name: "The Beatles"
├── nationality: "British"
├── genre: "Rock"
├── members: 4
├── formed: 1960
├── split: 1970
└── albums: (Array)
      ├── [0] (Object)
      │     ├── name: "Please Please Me"
      │     └── released: 1963
      └── [1] (Object)
            ├── name: "Abbey Road"
            └── released: 1969

2.3 链式访问:构建 bandInfo 字符串

这道题目的真正难点在于最后一步——构建 bandInfo 字符串,需要综合运用多种知识:

bandInfo = `${band.name}是一支来自${band.nationality}${band.genre}乐队,成立于${band.formed}年,共有${band.members}名成员。他们于${band.split}年解散。他们的首张专辑《${band.albums[0].name}》于${band.albums[0].released}年发行。`;

band.albums[0].name 链式访问路径拆解

band           → 整个 band 对象
.albums        → 访问对象属性 → 得到数组 [{...}, {...}]
[0]            → 数组索引访问 → 得到第一个专辑对象 {name: "Please...", ...}
.name          → 访问对象属性 → 得到 "Please Please Me"
步骤 语法 类型转换 结果
1 band.albums Object → Array [{...}, {...}]
2 band.albums[0] Array → Object {name: "Please Please Me", released: 1963}
3 band.albums[0].name Object → String "Please Please Me"

2.4 综合技能运用

技能 在本题中的应用
模板字符串 `...${}...` 拼接多段信息
点表示法 band.nameband.nationality
数组索引 band.albums[0] 访问第一个元素
嵌套链式访问 band.albums[0].name 穿透多层
对象字面量 创建 band 对象和 albums 子对象

三、技能测试三:this关键字实现方法复用

3.1 任务描述

重写 cat 对象的 greeting 方法,使其输出 "Hello, said Bertie the Cymric." 格式。核心约束:重写后的方法必须能在任何相同结构的 cat 对象上正常工作,不能硬编码具体的属性值。

const cat = {
  name: "Bertie",
  breed: "Cymric",
  color: "white",
  greeting: function () {
    // 在此处重写方法
  },
};

const cat2 = {
  name: "Elfie",
  breed: "Aphrodite Giant",
  color: "ginger",
  greeting: function () {
    // 一样的重写
  },
};

3.2 代码实现

const cat = {
  name: "Bertie",
  breed: "Cymric",
  color: "white",
  greeting: function () {
    console.log(`Hello, said ${this.name} the ${this.breed}.`);
  },
};

const cat2 = {
  name: "Elfie",
  breed: "Aphrodite Giant",
  color: "ginger",
  greeting: function () {
    console.log(`Hello, said ${this.name} the ${this.breed}.`);
  },
};

cat.greeting();   // "Hello, said Bertie the Cymric."
cat2.greeting();  // "Hello, said Elfie the Aphrodite Giant."

3.3 this 的运行时绑定机制

cat.greeting() 被调用
  ↓
this = cat(调用者)
  ↓
this.name  → "Bertie"
this.breed → "Cymric"
  ↓
输出:"Hello, said Bertie the Cymric."

cat2.greeting() 被调用
  ↓
this = cat2(调用者)
  ↓
this.name  → "Elfie"
this.breed → "Aphrodite Giant"
  ↓
输出:"Hello, said Elfie the Aphrodite Giant."
对比 硬编码写法 this 写法
cat 的方法 cat.name → 只能用于 cat this.name → 自适应调用者
cat2 的方法 必须重写为 cat2.name 完全相同的代码
复用性 ❌ 不可复用 ✅ 代码完全相同

相同的代码产生了不同的输出——这正是面向对象编程中多态的体现。

3.4 暴露的问题

greeting 方法的定义在两个对象中完全重复了。这违反了编程中的重要原则——DRY(Don’t Repeat Yourself)。代码重复会带来维护问题:修改格式需要在两处分别修改,容易遗漏和出错。


四、技能测试四:构造函数消除代码重复

4.1 问题根源

在任务三中,每个 cat 对象都需要复制一份完全相同的 greeting 方法代码。当需要维护或创建更多 cat 时,问题会急剧恶化:

修改 greeting 格式 → 需要修改每一个 cat 对象
新增 cat3  → 又要复制粘贴一遍
新增 cat4  → 再复制粘贴一遍
...        → 代码膨胀、难以维护

4.2 解决方案:构造函数

构造函数允许定义一个"模板",然后使用 new 关键字创建任意数量的对象实例。按照惯例,构造函数以大写字母开头

function Cat(name, breed, color) {
  this.name = name;
  this.breed = breed;
  this.color = color;
  this.greeting = function () {
    console.log(`Hello, said ${this.name} the ${this.breed}.`);
  };
}

const cat = new Cat("Bertie", "Cymric", "white");
const cat2 = new Cat("Elfie", "Aphrodite Giant", "ginger");

cat.greeting();   // "Hello, said Bertie the Cymric."
cat2.greeting();  // "Hello, said Elfie the Aphrodite Giant."

4.3 new 关键字的工作机制

new Cat("Bertie", "Cymric", "white")
  ↓
① 创建一个空对象 → {}
② 将 this 绑定到新对象 → this = {}
③ 执行构造函数代码:
    this.name = "Bertie"
    this.breed = "Cymric"
    this.color = "white"
    this.greeting = function() { ... }
④ 自动返回新对象 → return this(无需手动 return)

4.4 重构前后对比

维度 重构前(对象字面量×N) 重构后(构造函数)
创建方式 const cat = { name: "...", ... } const cat = new Cat("...")
greeting 定义 每个对象各写一遍 只写一次
修改格式 需要在每处修改 只改一处
新增实例 复制粘贴全部代码 一行代码 new Cat(...)
代码量 O(n) 随实例数增长 O(1) 恒定

4.5 构造函数的本质价值

构造函数 = 对象创建模板

  Cat(name, breed, color)
          |
          |  new 关键字
          |
    ┌─────┼─────┬─────┐
    ▼     ▼     ▼     ▼
   cat   cat2  cat3  catN

  每个实例自动拥有相同的结构和方法
  数据不同但行为一致

这种集中管理、分散使用的模式是面向对象编程的核心优势——一处定义,处处生效。


五、四道题目学习路径总结

测试 核心技能 复杂度 关键收获
测试一 属性访问 + 方法调用 点表示法与括号表示法的场景选择
测试二 嵌套对象创建与链式访问 ⭐⭐ 多层数据结构穿透与模板字符串
测试三 this 关键字运行时绑定 ⭐⭐⭐ 编写与具体数据解耦的通用方法
测试四 构造函数消除代码重复 ⭐⭐⭐ DRY原则 + 抽象思维 + 批量创建

学习路径图

测试一:基本操作
  │  属性读取、方法调用、值更新
  │  点表示法 ↔ 括号表示法
  ▼
测试二:嵌套结构
  │  对象包含数组、数组包含对象
  │  链式访问:band.albums[0].name
  ▼
测试三:this 复用
  │  方法代码与具体数据分离
  │  暴露问题:代码重复
  ▼
测试四:构造函数
  │  消除重复、模板化创建
  │  DRY原则 → 一处定义,处处生效

总结

JavaScript的对象系统远比这四道题所展示的要丰富和复杂——原型链、类语法、getter/setter、属性描述符等高级主题还在前方等待探索。但扎实掌握这些基础知识至关重要,因为它们是理解更高级概念的基石。

知识要点 一句话总结
点 vs 括号表示法 属性名已知用点,属性名为变量用括号
嵌套链式访问 obj.prop[index].nested 逐层穿透
this 指向调用方法的对象,实现方法复用
构造函数 new ClassName() 批量创建同类型对象,消除重复

编程概念的学习需要经历认识 → 理解 → 应用 → 反思的完整循环。当我们能够熟练使用对象字面量组织数据、使用this编写通用方法、使用构造函数创建实例家族时,就已经具备了继续深入学习的坚实基础。

更多推荐