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;
}
}
接口设计原则:
- 最小暴露原则:仅暴露必要方法
- 输入验证:通过方法拦截非法数据
- 封装复杂逻辑:如单位转换等内部计算
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())); // 机械移动
鸭子类型精髓
"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称作鸭子。"
知识回顾
- 封装三要素:
- 私有字段:
#
开头,仅本类可见 - 公共接口:通过方法暴露有限操作
- 数据验证:在方法中实现合法性检查
- 私有字段:
- 继承机制:
- 通过
extends
继承父类属性和方法 - 构造函数必须调用
super()
- 可选择性重写父类方法
- 通过
- 鸭子类型:
- 不依赖继承关系
- 通过行为(方法存在性)判断兼容性
- 关键语法:
- 私有字段
#xxx
- 继承
extends
和super
- 方法重写
override
(TypeScript语法,ES无)
- 私有字段
课后练习
(单选)以下哪项是私有字段的正确写法?
- A.
private name;
- B.
#age = 18;
- C.
const #height = 170;
- D. B和C都对
- A.
(填空)在继承中,子类构造函数必须调用______方法初始化父类属性。
(代码纠错)修复以下继承代码,使输出“电子狗汪汪叫”:
javascriptclass Animal { speak() { return "动物叫"; } } class RobotDog extends Animal { speak() { return "电子" + speak(); } // 错误点 } console.log(new RobotDog().speak());
(操作题)设计一个“Shape”家族:
- 父类Shape包含
getType()
方法返回"形状" - 子类Circle添加
radius
属性,重写getType()
返回"圆形" - 子类Square添加
sideLength
属性,重写getType()
返回"正方形" - 编写
printShapeInfo()
函数接受任何Shape子类,输出类型和属性
- 父类Shape包含
备注: (1) 通过对比私有字段与公共属性,强化封装概念 (2) 用动物/机器人例子生动解释鸭子类型 (3) 继承章节需强调super的必要性