js中的继承主要是根据原型链来实现的
原型链继承
原型链继承的主要思想就是根据原型来继承多个引用类型的属性或方法
function Super() {
this.name = 'a'
}
function Sub() {}
// 继承Super
Sub.prototype = new Super();
let sub1 = new Sub()
console.log(sub1.name)
将子类的prototype
指向父类的实例,这样的话可以通过原型来来访问父类的属性和方法,但是这样需要重置Sub.prototype.constructor
,因为此时子类的peototype.constructor
指向的是父类的构造函数
父类的实例属性如果是引用类型的,那么子类其中一个实例更改数据,会影响到另一个实例的数据
function Super() {
this.color = ['red', 'green']
}
function Sub() {}
Sub.prototype = new Super();
let sub1 = new Sub();
let sub2 = new Sub();
sub1.color.push('yellow');
console.log(sub1.color); // ["red", "green", "yellow"]
console.log(sub2.color); // ["red", "green", "yellow"]
原型链继承的缺点
- 对于引用类型的数据,子类实例一改全改。
- 子类引用父类的原型方法时,无法传参。
借用构造函数继承
对于原型链继承的两个缺点,开发人员开始使用一种叫借用构造函数
的继承方法。函数只是在特定环境中执行代码的对象,因此可以通过apply/call
来改变父类的this指向。
function Super() {
this.color = ['red', 'green']
}
function Sub() {
Super.call(this)
}
let sub1 = new Sub();
let sub2 = new Sub();
sub1.color.push('yellow');
console.log(sub1.color); // ["red", "green", "yellow"]
console.log(sub2.color); // ["red", "green"]
通过更改this指向,使得每一个实例都有一份color属性的副本,就不会一改全改,而且借用构造函数继承也可以进行传参
function Super(name) {
this.name = name
}
function Sub() {
Super.call(this,'coder')
}
let sub1 = new Sub();
console.log(sub1.name); // 'coder'
借用构造函数继承的缺点
- 必须在构造函数中定义方法,函数无法重用
- 子类无法访问到父类原型上的方法
基于这两个缺点,借用构造函数继承往往无法单独使用
组合继承
组合继承结合了原型链继承和借用构造函数继承,即使用原型链继承原型上的属性和方法,使用借用构造函数来继承实例上的属性
function Super(name) {
this.name = name;
this.color = ['red', 'green']
}
Super.prototype.say = function() {
console.log(this.name);
}
function Sub() {
Super.call(this, 'tom'); // 借用构造函数继承实例属性
this.age = 12;
}
Sub.prototype = new Super(); // 原型链继承原型上的方法
let sub1 = new Sub();
sub1.color.push('yellow')
let sub2 = new Sub();
console.log(sub1.color); // ["red", "green", "yellow"]
console.log(sub2.color); // ["red", "green"]
sub2.say(); // tom
组合继承弥补了原型链继承和借用构造函数继承的缺点,同时也保留了isPrototypeOf
和instanceof
识别合成对象的能力,成为了当前js使用范围最广的一种继承方式
// prototypeObj.isPrototypeOf(object)
// isPrototypeOf检测prototypeObj是否出现在object的原型链上
console.log(Super.prototype.isPrototypeOf(sub2)); // true
console.log(sub2 instanceof Super); // true
组合继承会有效率问题,我们在父类函数执行console.log(1)
,会发现执行了三次
function Super(name) {
console.log(1)
this.name = name;
this.color = ['red', 'green']
}
第一次是在Sub.prototype = new Super()
时执行
第二次和第三次分别是在创建子类实例的时候执行
寄生式继承
寄生式继承是比较接近原型链继承的一种方法
创建一个实现继承的函数,以某种方法来增强对象,然后返回这个对象
let person = {
name:'Tom'
}
function createAnother(object) {
let obj = Object.create(object);
console.log(obj)
obj.say = function() {
console.log(this.name)
}
return obj
}
let sub = createAnother(person);
sub.say()
Object.create() 创建一个新对象,将新对象的原型指向现有的对象,然后返回这个新对象,会接收两个参数,第一个是要指向的对象,是必传参数,第二个参数类似于Object.defineProperty
的第三个参数,是可选参数
寄生式组合继承
基本思路:不通过调用父类来给子类原型赋值,而是通过取得父类的一个副本
function Super(name) {
console.log(1)
this.name = name;
this.color = ['red', 'green']
}
Super.prototype.say = function() {
console.log(this.name);
}
function Sub() {
Super.call(this, 'Tom');
this.age = 12;
}
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype); // 新对象指向父类的原型
subType.prototype = prototype; // 将新对象赋值给子类原型
subType.prototype.constructor = subType; // 由于改写了子类原型,所以需要重新复制constructor
}
inheritPrototype(Sub, Super)
let sub1 = new Sub();
sub1.color.push('yellow')
let sub2 = new Sub();
console.log(sub1.color); // ["red", "green", "yellow"]
console.log(sub2.color); // ["red", "green"]
sub2.say() // Tom
寄生式组合继承算是引用类型继承的最佳实践