Skip to content

C13. 类的封装和继承

3.1. ⭐ 类拒绝外部访问私有数据

封装的核心是“数据隐藏”——类应该控制哪些属性可被外界直接访问。JavaScript通过**#符号**实现私有字段。

javascript
class BankAccount {
  #balance = 0; // 私有字段,外部无法直接访问
  constructor(initialDeposit) {
    this.#balance = initialDeposit;
  }
  getBalance() {
    return this.#balance;
  }
  deposit(amount) {
    this.#balance += amount;
  }
}

const account = new BankAccount(100);
console.log(account.#balance); // 报错:私有字段不可访问
console.log(account.getBalance()); // 正确输出:100

私有字段特性

  • 仅限本类方法访问
  • 子类也无法直接访问父类私有字段
  • 通过getBalance()等方法间接访问

3.2. 🌟 类给外部调用者提供公开接口

类通过公共方法暴露有限的操作权限,形成“安全通道”。

javascript
class TemperatureConverter {
  constructor(celsius) {
    this.celsius = celsius; // 公共属性
  }
  // 公开接口:获取华氏温度
  getFahrenheit() {
    return (this.celsius * 9/5) + 32;
  }
  // 公开接口:设置摄氏温度(带验证)
  setCelsius(newCelsius) {
    if (newCelsius < -273.15) {
      throw new Error("低于绝对零度");
    }
    this.celsius = newCelsius;
  }
}

接口设计原则

  1. 最小暴露原则:仅暴露必要方法
  2. 输入验证:通过方法拦截非法数据
  3. 封装复杂逻辑:如单位转换等内部计算

3.3. 🌟 子类可以继承父类并加以扩展

继承允许子类复用父类代码,并通过super调用父类方法。

javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return "动物发声";
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed; // 添加子类特有属性
  }
  speak() { // 重写父类方法
    return `${super.speak()} + 汪汪!`;
  }
}

const dog = new Dog("旺财", "中华田园犬");
console.log(dog.speak()); // 输出:动物发声 + 汪汪!

继承注意事项

  • 必须通过super()调用父类构造函数
  • 重写方法时可选择是否调用super.method()
  • 子类无法访问父类私有字段

3.4. ⭐ 特定方法和属性的约束形成鸭子类型

鸭子类型(Duck Typing):不检查类型本身,而是检查是否具备所需行为。例如:

javascript
// 不同类具备相同方法即可被统一处理
class Cat {
  move() { return "猫步"; }
}
class Robot {
  move() { return "机械移动"; }
}

function makeItMove(obj) {
  if (typeof obj.move === "function") { // 仅检查是否存在move方法
    return obj.move();
  }
  return "无法移动";
}

console.log(makeItMove(new Cat())); // 猫步
console.log(makeItMove(new Robot())); // 机械移动

鸭子类型精髓

"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称作鸭子。"

知识回顾

  1. 封装三要素
    • 私有字段#开头,仅本类可见
    • 公共接口:通过方法暴露有限操作
    • 数据验证:在方法中实现合法性检查
  2. 继承机制
    • 通过 extends 继承父类属性和方法
    • 构造函数必须调用 super()
    • 可选择性重写父类方法
  3. 鸭子类型
    • 不依赖继承关系
    • 通过行为(方法存在性)判断兼容性
  4. 关键语法
    • 私有字段 #xxx
    • 继承 extendssuper
    • 方法重写 override(TypeScript语法,ES无)

课后练习

  1. (单选)以下哪项是私有字段的正确写法?

    • A. private name;
    • B. #age = 18;
    • C. const #height = 170;
    • D. B和C都对
  2. (填空)在继承中,子类构造函数必须调用______方法初始化父类属性。

  3. (代码纠错)修复以下继承代码,使输出“电子狗汪汪叫”:

    javascript
    class Animal {
      speak() { return "动物叫"; }
    }
    class RobotDog extends Animal {
      speak() { return "电子" + speak(); } // 错误点
    }
    console.log(new RobotDog().speak());
  4. (操作题)设计一个“Shape”家族:

    • 父类Shape包含getType()方法返回"形状"
    • 子类Circle添加radius属性,重写getType()返回"圆形"
    • 子类Square添加sideLength属性,重写getType()返回"正方形"
    • 编写printShapeInfo()函数接受任何Shape子类,输出类型和属性

备注: (1) 通过对比私有字段与公共属性,强化封装概念 (2) 用动物/机器人例子生动解释鸭子类型 (3) 继承章节需强调super的必要性

Built by Vitepress | Apache 2.0 Licensed