JavaScript对象继承常用详解
一、概念: JS继承是让** 一个对象共享另一个对象的属性和方法的核心机制,JS是基于原型(proptotype)的继承,和传统类继承不同。**
原型链核心,JS每个对象都有一个__propto 属性,指向它的原型对象,函数有proptotype属性,实例对象的 propto __指向构造函数的proptotype, 继承的本质是让子类的原型指向父类的实例/原型
5种常见的继承方式
1、原型链继承(基础)
核心: 子类原型=父类实例
javascript
// 父类
function Parent() {
this.name = '父类';
this.arr=[1,2,3]; // 引用类型属性
}
Parent.prototype.say = function () {
console.log('我是来自父类的name ' + this.name);
}
// 子类
function Child() {}
// 核心:子类原型继承父类实例
Child.prototype = new Parent();
const child1 = new Child();
const child2 = new Child();
child1.say(); // 我是父类 父类
child1.arr.push(4);
console.log(child1.arr); // [1, 2, 3, 4]
console.log(child2.arr); // [1, 2, 3, 4] // 子类实例共享父类实例的引用类型属性
缺点:
- 引用类型属性(数组/对象)会被所有子类实例共享,修改一个实例,所有实例都会变化,如修改child1.arr,push一个值4,child2.arr也会变更,
- 无法向父类构造函数传值
2、借用构造函数继承,解决第1方法的缺点
核心:在子类构造函数中,用call() 调用父类构造函数
javascript
function Parent(name) {
this.name = name;
this.arr = [1, 2, 3];
}
Parent.prototype.say = function () {
console.log('我是来自父类的方法 say');
}
function Child(name) {
Parent.call(this, name); // 通过 call 方法调用父类构造函数,传递子类实例作为 this
this.age = 18; // 子类特有属性
}
const child1 = new Child('子类1');
const child2 = new Child('子类2');
// 独立修改,不影响其他实例
child1.arr.push(4);
console.log(child1.arr); // [1, 2, 3, 4] // 子类实例共享父类实例的引用类型属性,
console.log(child2.arr); // [1, 2, 3] // 子类实例共享父类实例的引用类型属性,
console.log(child1.name,child2.name); // 子类1 子类2
console.log(child1.say); // undefined // 子类实例无法访问父类原型上的方法
console.log(child2.say); // undefined // 子类实例无法访问父类原型上的方法
缺点:
- 只能继承父类实例属性,无法继承父类原型上的方法
- 方法会重复创建,浪费内存
3、组合继承(最常用经典方案)
核心:原型链继承+借用构造函数继承,取长补短, 解决第2中方法的缺点
javascript
function Parent(name) {
this.name = name;
this.arr = [1, 2, 3];
}
Parent.prototype.say = function () {
console.log('我是来自父类的方法 say ' + this.name);
}
function Child(name, age) {
//1. 借用构造函数:继承实例属性+传参
Parent.call(this, name);
this.age = age; // 子类特有属性
}
//2. 原型链继承:继承原型方法
Child.prototype = new Parent();
Child.prototype.constructor = Child; // 修正子类原型的 constructor 属性
const child1 = new Child('子类1', 18);
const child2 = new Child('子类2', 20);
child1.say(); //
console.log(child1.name, child1.age); // 子类1 18
child1.arr.push(4);
console.log(child1.arr); // [1, 2, 3, 4] // 子类实例共享父类实例的引用类型属性,
console.log(child2.arr); // [1, 2, 3] // 子类实例共享父类实例的引用类型属性,独立属性,child1修改不影响child2
优点:
- 支持传参
- 引用类型(对象/数据)属性不共享
- 方法复用,不浪费内存
缺点:
- 父类构造函数会执行两次,new Parent()一次, call() 一次
4、寄生组合式继承(JS最佳实践)
核心:解决组合式继承父类构造函数执行两次的问题
javascript
function Parent(name) {
this.name = name;
this.arr = [1, 2, 3];
}
Parent.prototype.say = function () {
console.log('我是来自父类的方法 say ' + this.name);
}
function Child(name, age) {
Parent.call(this, name); // 仅执行一次父类构造函数,传递子类实例作为 this
this.age = age;
}
// 核心:创造父类原型的副本,不执行父类构造函数
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child1 = new Child('子类1', 18);;
child1.say(); // 我是来自父类的方法 say 子类1
console.log(child1.name, child1.age);// 子类1 18
优点:
- 父类构造函数只执行一次
- 完美支持传参、方法复用、属性独立
- ES6 class继承的底层实现原
5、ES6 class继承
核心:extends + super() ,语法简洁,底层还是寄生组合式继承
javascript
// 父类
class Parent {
constructor(name) {
this.name = name;
this.arr = [1, 2, 3];
}
say() {
console.log('我是来自父类的方法 say ' + this.name);
}
}
// 子类继承父类
class child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数,传递 name 参数,必须在this之前调用super()
this.age = age; // 子类特有属性
}
}
const child1 = new child('子类1', 18);
child1.say(); // 我是来自父类的方法 say 子类1
console.log(child1.name, child1.age); // 子类1 18