一、为什么JavaScript需要继承?
想象你正在开发一个游戏,有角色
、英雄
、怪物
等不同实体。如果每个角色都重新定义移动
、攻击
等方法,代码会变得冗长难维护。继承正是解决这个问题的银弹!
// 糟糕的写法 - 重复代码
const hero = {
name: '勇者',
move() { /* 50行移动逻辑 */ },
attack() { /* 50行攻击逻辑 */ }
}
const monster = {
name: '史莱姆',
move() { /* 重复50行移动逻辑 */ },
attack() { /* 重复50行攻击逻辑 */ }
}
二、原型继承:JS的独门秘籍
1. 原型链基础
JavaScript通过原型链实现继承,每个对象都有隐藏的[[Prototype]]
属性(可通过__proto__
访问)
const parent = { familyName: '张' }
const child = { firstName: '小明' }
child.__proto__ = parent // 设置原型继承
console.log(child.familyName) // '张'(通过原型链查找)
2. 构造函数模式
ES5时代的主流继承方案:
function Character(name) {
this.name = name
}
// 方法定义在原型上节省内存
Character.prototype.move = function() {
console.log(`${this.name}移动中`)
}
function Hero(name) {
Character.call(this, name) // 继承属性
}
// 继承方法
Hero.prototype = Object.create(Character.prototype)
Hero.prototype.constructor = Hero
const hero = new Hero('亚瑟')
hero.move() // "亚瑟移动中"
三、现代JS继承方案
1. Class语法糖
ES6引入的class本质仍是原型继承,但语法更清晰:
class Character {
constructor(name) {
this.name = name
}
move() {
console.log(`${this.name}移动中`)
}
}
class Hero extends Character {
constructor(name, skill) {
super(name) // 必须首先调用super()
this.skill = skill
}
useSkill() {
console.log(`释放${this.skill}`)
}
}
const hero = new Hero('盖伦', '德玛西亚正义')
hero.move() // "盖伦移动中"
2. 寄生组合继承(终极方案)
最完美的ES5继承实现:
function inherit(child, parent) {
const prototype = Object.create(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
function Monster(name) {
Character.call(this, name)
}
inherit(Monster, Character)
Monster.prototype.roar = function() {
console.log('吼~~~')
}
四、7种继承方式大比拼
继承方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
原型链继承 | 简单 | 引用属性共享 | 简单对象继承 |
构造函数继承 | 属性独立 | 方法无法复用 | 需要隔离属性的场景 |
组合继承 | 属性方法均可继承 | 两次调用父类构造函数 | 通用场景 |
原型式继承 | 轻量 | 同原型链继承缺点 | 对象浅拷贝 |
寄生式继承 | 增强对象 | 方法不能复用 | 需要扩展对象的场景 |
寄生组合继承 | 最完美ES5方案 | 实现稍复杂 | 追求完美的ES5项目 |
Class继承 | 语法简洁 | 本质仍是原型继承 | ES6+项目 |
五、终极面试题解析
题目:以下代码输出什么?
function Parent() { this.a = 1 }
Parent.prototype.b = 2
function Child() { Parent.call(this) }
Child.prototype = Parent.prototype
const obj = new Child()
console.log(obj.a, obj.b, obj.__proto__ === Child.prototype)
答案分析:
obj.a
→ 1(构造函数继承)obj.b
→ 2(原型链继承)- 最后比较 → true(原型引用相同)
- 潜在问题:修改Child.prototype会影响Parent.prototype
六、最佳实践建议
- 现代项目:坚持使用class语法
- 老项目:采用寄生组合继承
- 避免:
// 错误示范:破坏原型链 Child.prototype = new Parent() // 错误示范:共享原型 Child.prototype = Parent.prototype
- 调试技巧:
console.log(obj.__proto__.__proto__) // 查看原型链 console.log(Object.getPrototypeOf(obj)) // 标准获取原型方法
结语
JavaScript的原型继承就像魔法:
- 理解了是
可控魔法
- 不理解是
黑魔法
记住这个终极口诀:
"普通属性靠实例,共享方法靠原型"