核心概念
如果说继承到底是怎么实现的,其实很简单-JavaScript的原型链的机制。想象一下,每一个对象之间都有一个隐形的链条🔗到了一起,我们可以顺着这个⛓️看到甚至使用另一个对象的一些东西。 但是知道这个⛓️并不能保证学会继承,我们通过⛓️连接到了一起,但是你那里的一个属性或者方法是你的呢,还是我的呢,我如果用了,你还有吗,你如果还想要怎么办,这才是理解JavaScript继承的必须要掌握的内容,通过这些继承可以分为
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合继承
- class继承
关键原理
原型链继承
将子类的原型对象prototype
指向父类的一个实例
js
function Parent() {
this.parentProperty = true;
this.colors = ['red', 'blue', 'green']; // 引用类型属性
}
Parent.prototype.getParentProperty = function() {
return this.parentProperty;
};
function Child() {
this.childProperty = false;
}
// 实现继承的关键:将 Child 的原型指向 Parent 的实例
Child.prototype = new Parent();
// 修复 constructor 指向,这一步很重要
Child.prototype.constructor = Child;
const instance1 = new Child();
const instance2 = new Child();
instance1.colors.push('black');
console.log(instance1.getParentProperty()); // true
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
console.log(instance2.colors); // ['red', 'blue', 'green', 'black'] (注意:引用类型被共享了!)
缺点:
- 引用类型的属性被所有实例共享
- 无法在创建子类实例时向父类构造函数传参
逻辑图表
flowchart TD
subgraph G1 [原型链继承结构]
A[实例 instance]
B[子类构造函数 SubType]
C[子类原型对象 SubType.prototype]
D[父类实例 parentInstance]
E[父类原型对象 SuperType.prototype]
F[Object.prototype]
G[null]
A -- __proto__ --> C
B -- prototype --> C
C -- __proto__ --> E
D -- __proto__ --> E
E -- __proto__ --> F
F -- __proto__ --> G
end
subgraph G2 [属性/方法查找顺序]
H[访问 instance.someProperty]
I{实例自身存在?}
J{子类原型存在?}
K{父类原型存在?}
L[返回属性值]
M[返回 undefined]
H --> I
I -- 否 --> J
J -- 否 --> K
K -- 是 --> L
I -- 是 --> L
K -- 否 --> M
end
构造函数继承
在子类构造函数内部调用父类构造函数(使用 call
或 apply
方法改变执行上下文)
js
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 在子类构造函数中调用父类构造函数,并传递参数
this.age = age;
}
const instance1 = new Child('Alice', 10);
const instance2 = new Child('Bob', 12);
instance1.colors.push('black');
console.log(instance1.name); // 'Alice'
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
console.log(instance2.colors); // ['red', 'blue', 'green'] (引用类型属性是独立的)
console.log(instance1.sayName); // undefined (无法继承父类原型上的方法)
缺点:
- 无法继承父类原型上的方法和属性
逻辑图表
flowchart TD
subgraph Parent构造函数
Parent["Parent(name)"]
ParentProto["Parent.prototype"]
ParentMethod["sayName: function()"]
end
subgraph Child构造函数
Child["Child(name, age)"]
ChildProto["Child.prototype"]
end
subgraph instance1实例
instance1["instance1: Child"]
instance1Props["name: 'Alice'
age: 10
colors: ['red','blue','green','black']"] end subgraph instance2实例 instance2["instance2: Child"] instance2Props["name: 'Bob'
age: 12
colors: ['red','blue','green']"] end Parent -.->|prototype| ParentProto ParentProto -.->|constructor| Parent ParentProto --> ParentMethod Child -.->|prototype| ChildProto ChildProto -.->|constructor| Child instance1 --> |__proto__| ChildProto instance2 --> |__proto__| ChildProto Child --> |new 操作符创建| instance1 Child --> |new 操作符创建| instance2 Child --> |Parent.callthis, name| Parent
age: 10
colors: ['red','blue','green','black']"] end subgraph instance2实例 instance2["instance2: Child"] instance2Props["name: 'Bob'
age: 12
colors: ['red','blue','green']"] end Parent -.->|prototype| ParentProto ParentProto -.->|constructor| Parent ParentProto --> ParentMethod Child -.->|prototype| ChildProto ChildProto -.->|constructor| Child instance1 --> |__proto__| ChildProto instance2 --> |__proto__| ChildProto Child --> |new 操作符创建| instance1 Child --> |new 操作符创建| instance2 Child --> |Parent.callthis, name| Parent
组合继承
结合了原型链继承和构造函数继承。使用构造函数继承实例属性,使用原型链继承原型方法。
js
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent,继承实例属性
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent,继承原型方法
Child.prototype.constructor = Child; // 修复 constructor 指向
Child.prototype.sayAge = function() {
console.log(this.age);
};
const instance1 = new Child('Nicholas', 29);
const instance2 = new Child('Greg', 27);
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
instance1.sayName(); // 'Nicholas'
instance1.sayAge(); // 29
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // 'Greg'
缺点:
- 父类构造函数被调用了两次
逻辑图表
flowchart TD
subgraph Parent构造函数
ParentCTOR["Parent(name)"]
ParentProto["Parent.prototype"]
ParentMethod["sayName: function()"]
end
subgraph Child构造函数
ChildCTOR["Child(name, age)"]
ChildProto["Child.prototype"]
ChildMethod["sayAge: function()"]
end
subgraph instance1实例
instance1["instance1: Child"]
instance1Props["name: 'Alice'
age: 10
colors: ['red','blue','green','black']"] end subgraph instance2实例 instance2["instance2: Child"] instance2Props["name: 'Bob'
age: 12
colors: ['red','blue','green']"] end ParentCTOR -.->|prototype| ParentProto ParentProto -.->|constructor| ParentCTOR ParentProto --> ParentMethod ChildCTOR -.->|prototype| ChildProto ChildProto -.->|constructor| ChildCTOR ChildProto --> ChildMethod %% 关键继承关系 ChildCTOR --> |Parent.callthis, name| ParentCTOR ChildProto --> |Prototype| ParentProto instance1 --> |__proto__| ChildProto instance2 --> |__proto__| ChildProto ChildCTOR --> |new 操作符创建| instance1 ChildCTOR --> |new 操作符创建| instance2
age: 10
colors: ['red','blue','green','black']"] end subgraph instance2实例 instance2["instance2: Child"] instance2Props["name: 'Bob'
age: 12
colors: ['red','blue','green']"] end ParentCTOR -.->|prototype| ParentProto ParentProto -.->|constructor| ParentCTOR ParentProto --> ParentMethod ChildCTOR -.->|prototype| ChildProto ChildProto -.->|constructor| ChildCTOR ChildProto --> ChildMethod %% 关键继承关系 ChildCTOR --> |Parent.callthis, name| ParentCTOR ChildProto --> |Prototype| ParentProto instance1 --> |__proto__| ChildProto instance2 --> |__proto__| ChildProto ChildCTOR --> |new 操作符创建| instance1 ChildCTOR --> |new 操作符创建| instance2
原型式继承
基于一个现有对象创建一个新对象,而不必创建自定义类型。
js
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
const anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob'); // 修改会影响到原对象
const yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie'); // 修改会影响到原对象和其他实例
console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
其实就是Object.create()
js
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
const anotherPerson = Object.create(person); // 本质上与上面的 object 函数相同
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
缺点:
- 与原型链继承类似,引用类型的属性值始终会被所有实例共享
逻辑图表
flowchart TD
subgraph "现有对象 (parentObj)"
parent["parentObj"]
parentProps["name: 'Nicholas'
friends: ['Shelby', 'Court', 'Van']"] end subgraph "新对象 (childObj)" child["childObj"] childProps["新增的属性..."] end parent --> |Prototype| ObjectProto["Object.prototype"] ObjectProto --> |Prototype| null child --> |Prototype| parent child --> |自身属性| childProps
friends: ['Shelby', 'Court', 'Van']"] end subgraph "新对象 (childObj)" child["childObj"] childProps["新增的属性..."] end parent --> |Prototype| ObjectProto["Object.prototype"] ObjectProto --> |Prototype| null child --> |Prototype| parent child --> |自身属性| childProps
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象
js
function createAnother(original) {
const clone = Object.create(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式来增强这个对象
console.log('hi');
};
return clone;
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
const anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'
缺点 :与原型式继承相同,引用类型属性被共享 ;同时,为对象添加函数会导致函数难以复用
逻辑图表
flowchart TD
subgraph "原始对象 (originalObj)"
original["originalObj"]
originalProps["name: '李白'
friends: ['杜甫','陆游']"] end subgraph "寄生函数 (createEnhancedObj)" direction TB create["createEnhancedObj(original)"] createStep1["以 original 为原型
创建新对象 (clone)"] createStep2["增强 clone 对象
添加新方法/属性"] createStep3["返回增强后的 clone 对象"] create --> createStep1 --> createStep2 --> createStep3 end subgraph "新对象 (enhancedObj)" enhanced["enhancedObj"] enhancedProps["新增的属性或方法..."] end original --作为原型--> enhanced createStep3 --返回--> enhanced enhanced --自身属性--> enhancedProps
friends: ['杜甫','陆游']"] end subgraph "寄生函数 (createEnhancedObj)" direction TB create["createEnhancedObj(original)"] createStep1["以 original 为原型
创建新对象 (clone)"] createStep2["增强 clone 对象
添加新方法/属性"] createStep3["返回增强后的 clone 对象"] create --> createStep1 --> createStep2 --> createStep3 end subgraph "新对象 (enhancedObj)" enhanced["enhancedObj"] enhancedProps["新增的属性或方法..."] end original --作为原型--> enhanced createStep3 --返回--> enhanced enhanced --自身属性--> enhancedProps
寄生组合式继承
通过借用构造函数继承属性,但使用一种更高效的方式继承方法(不调用父类构造函数为子类原型赋值,而是直接获取父类原型的一个副本)
js
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype); // 创建父类原型的副本
prototype.constructor = child; // 修复副本的 constructor 指向
child.prototype = prototype; // 将副本赋值给子类的原型
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 只调用一次 Parent 构造函数
this.age = age;
}
// 实现继承:不再需要 new Parent()
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
console.log(this.age);
};
const instance = new Child('Nicholas', 29);
instance.sayName(); // 'Nicholas'
instance.sayAge(); // 29
这种方式只调用了一次父类构造函数 ,避免了在子类原型上创建不必要的属性,同时保持了原型链不变。被认为是引用类型最理想的继承范式
逻辑图表
flowchart TD
subgraph "父类构造函数 (Parent)"
ParentCTOR["Parent(name)"]
ParentProto["Parent.prototype"]
ParentMethod["sayName: function()"]
end
subgraph "子类构造函数 (Child)"
ChildCTOR["Child(name, age)"]
ChildProto["Child.prototype"]
ChildMethod["sayAge: function()"]
end
subgraph "实例 (instance)"
instance1["instance: Child"]
instance1Props["name: 'Nicholas'
age: 29
colors: ['red','blue','green','black']"] end ParentCTOR -.->|prototype| ParentProto ParentProto -.->|constructor| ParentCTOR ParentProto --> ParentMethod ChildCTOR -.->|prototype| ChildProto ChildProto -.->|constructor| ChildCTOR ChildProto --> ChildMethod %% 关键继承关系 ChildProto --> |Object.create
Prototype| ParentProto ChildCTOR --> |Parent.call
继承实例属性| ParentCTOR instance1 --> |__proto__| ChildProto ChildCTOR --> |new 操作符创建| instance1
age: 29
colors: ['red','blue','green','black']"] end ParentCTOR -.->|prototype| ParentProto ParentProto -.->|constructor| ParentCTOR ParentProto --> ParentMethod ChildCTOR -.->|prototype| ChildProto ChildProto -.->|constructor| ChildCTOR ChildProto --> ChildMethod %% 关键继承关系 ChildProto --> |Object.create
Prototype| ParentProto ChildCTOR --> |Parent.call
继承实例属性| ParentCTOR instance1 --> |__proto__| ChildProto ChildCTOR --> |new 操作符创建| instance1
Class继承
es6提供的语法糖,引入class
,extends
和super
关键字
js
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent { // 使用 extends 继承
constructor(name, age) {
super(name); // 必须在构造函数中首先调用 super(),它相当于 Parent.call(this, name)
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const instance1 = new Child('Tom', 18);
const instance2 = new Child('Jerry', 20);
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
instance1.sayName(); // 'Tom'
instance1.sayAge(); // 18
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // 'Jerry'
如果把这段语法糖换成底层的代码,类似于寄生组合继承