JavaScript篇:JavaScript继承全攻略:从原型链到class语法糖

55 阅读3分钟

🎓 作者简介前端领域优质创作者

🚪 资源导航: 传送门=>

🎬 个人主页:  江城开朗的豌豆

🌐 个人网站:    江城开朗的豌豆 🌍

📧 个人邮箱: YANG_TAO_WEB@163.com 📩

💬 个人微信:     y_t_t_t_ 📱

📌  座  右 铭: 生活就像心电图,一帆风顺就证明你挂了 💔

👥 QQ群:  906392632 (前端技术交流群) 💬

大家好,我是[小杨],今天我们来聊聊JavaScript中实现继承的几种方式。很多初学者对JS的继承机制感到困惑,其实只要理解了原型链,继承就不再是难题。下面我会用通俗易懂的方式,带你掌握5种实现继承的方法,并分析它们的优缺点。

1. 原型链继承:最基础的继承方式

function Person() {
  this.name = '我';
  this.hobbies = ['coding', 'reading'];
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

function Programmer() {
  this.skill = 'JavaScript';
}

// 关键点:让Programmer的原型指向Person实例
Programmer.prototype = new Person();

const me = new Programmer();
me.sayName(); // 输出"我"

存在问题

  • 引用类型属性会被所有实例共享(比如修改me.hobbies会影响所有实例)
  • 创建子类实例时无法向父类构造函数传参

2. 借用构造函数继承:解决引用共享问题

function Person(name) {
  this.name = name;
  this.hobbies = ['coding', 'reading'];
}

function Programmer(name, skill) {
  // 关键点:调用父类构造函数
  Person.call(this, name);
  this.skill = skill;
}

const me = new Programmer('我', 'JavaScript');
const you = new Programmer('你', 'Python');

me.hobbies.push('gaming');
console.log(you.hobbies); // ["coding", "reading"] 不受影响

优点

  • 每个实例都有独立的属性
  • 可以向父类传参

缺点

  • 方法都在构造函数中定义,无法复用
  • 无法继承父类原型上的方法

3. 组合继承(最常用):原型链+构造函数

function Person(name) {
  this.name = name;
  this.hobbies = ['coding', 'reading'];
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

function Programmer(name, skill) {
  Person.call(this, name); // 第二次调用Person
  this.skill = skill;
}

// 第一次调用Person
Programmer.prototype = new Person(); 
Programmer.prototype.constructor = Programmer;

const me = new Programmer('我', 'JavaScript');
me.sayName(); // "我"

特点

  • 实例属性独立
  • 方法可以复用
  • 可以继承原型方法

缺点

  • 父类构造函数被调用了两次

4. 原型式继承:Object.create的底层原理

const person = {
  name: '我',
  hobbies: ['coding', 'reading'],
  sayName: function() {
    console.log(this.name);
  }
};

// 关键点:以现有对象为原型创建新对象
const me = Object.create(person);
me.name = '新的我';
me.sayName(); // "新的我"

适用场景

  • 不需要构造函数的简单对象继承
  • ES5之前可以用以下polyfill:
function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

5. 寄生组合式继承(终极方案)

function inheritPrototype(child, parent) {
  // 关键点:创建父类原型的副本
  const prototype = Object.create(parent.prototype);
  prototype.constructor = child;
  child.prototype = prototype;
}

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

function Programmer(name, skill) {
  Person.call(this, name);
  this.skill = skill;
}

// 替换原来的原型赋值
inheritPrototype(Programmer, Person);

const me = new Programmer('我', 'JavaScript');
me.sayName(); // "我"

优点

  • 只调用一次父类构造函数
  • 原型链保持不变
  • 最理想的继承方式

6. ES6 class继承(语法糖)

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayName() {
    console.log(this.name);
  }
}

class Programmer extends Person {
  constructor(name, skill) {
    super(name); // 相当于Person.call(this, name)
    this.skill = skill;
  }
  
  showSkill() {
    console.log(`${this.name}的技能是${this.skill}`);
  }
}

const me = new Programmer('我', 'JavaScript');
me.sayName(); // "我"
me.showSkill(); // "我的技能是JavaScript"

特点

  • 语法简洁
  • 底层仍然是寄生组合式继承
  • 必须用new调用
  • 可以用super调用父类方法

7. 实际应用场景分析

7.1 需要方法复用 → 组合继承/寄生组合继承

7.2 简单对象扩展 → 原型式继承

7.3 现代项目开发 → ES6 class

8. 常见面试题解析

Q:以下代码输出什么?

function Parent() { this.name = 'parent'; }
function Child() {}

Child.prototype = new Parent();
const obj = new Child();
console.log(obj instanceof Parent); // true

A:输出true,因为obj的原型链上存在Parent的实例

Q:如何实现多重继承?

// 通过Mixin方式实现
class A { methodA() {} }
class B { methodB() {} }

class C {
  constructor() {
    Object.assign(this, new A());
    Object.assign(this, new B());
  }
}

9. 总结

  • 原型链继承:简单但有引用共享问题
  • 借用构造函数:解决引用问题但方法不能复用
  • 组合继承:结合两者优点,最常用
  • 寄生组合:最理想的继承方式
  • ES6 class:语法糖,开发首选

理解这些继承方式的实现原理,能帮助我们在不同场景选择最合适的方案。如果有任何问题,欢迎在评论区讨论!