JavaScript继承与原型链:从青铜到王者的进化之路

82 阅读2分钟

一、为什么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)

答案分析

  1. obj.a → 1(构造函数继承)
  2. obj.b → 2(原型链继承)
  3. 最后比较 → true(原型引用相同)
  4. 潜在问题:修改Child.prototype会影响Parent.prototype

六、最佳实践建议

  1. 现代项目:坚持使用class语法
  2. 老项目:采用寄生组合继承
  3. 避免
    // 错误示范:破坏原型链
    Child.prototype = new Parent()
    
    // 错误示范:共享原型
    Child.prototype = Parent.prototype
    
  4. 调试技巧
    console.log(obj.__proto__.__proto__) // 查看原型链
    console.log(Object.getPrototypeOf(obj)) // 标准获取原型方法
    

结语

JavaScript的原型继承就像魔法:

  • 理解了是可控魔法
  • 不理解是黑魔法

记住这个终极口诀:

"普通属性靠实例,共享方法靠原型"