🎓 作者简介: 前端领域优质创作者
🚪 资源导航: 传送门=>
🎬 个人主页: 江城开朗的豌豆
🌐 个人网站: 江城开朗的豌豆 🌍
📧 个人邮箱: 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:语法糖,开发首选
理解这些继承方式的实现原理,能帮助我们在不同场景选择最合适的方案。如果有任何问题,欢迎在评论区讨论!