上图是本篇文章主要深入的内容
我们将跟随每种方法的缺点,这条线去深入js的继承
1.原型链继承
原型链可谓是继承的灵魂,所以在了解继承之前一定要对原型链要有个大概的认知。 那么先带大家重温下构造函数、原型和实例的关系吧:每个构造函数都有一个原型对象。原型上面有一个constructor属性指回构造函数,而实例有个__proto__指针指向原型。
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
// **原型链继承的核心代码**
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
let instance = new SubType()
console.log(instance.getSuperValue())
核心就是:SubType通过创建SuperType的实例并将其赋值给自己的原型SubType
原型链继承的缺点:
- 原型中包含的引用会在所有实例间共享,一个实例改变引用属性,其余实例的相同引用属性都会被改变
- 子类型在实例化时不能给父类型的构造函数传参 所以这两个缺点导致了原型链继承基本不会被单独使用。
举个例子来说明缺点一
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
2.盗用构造函数继承
使用apply()和call()方法以新创建的对象为上下文执行构造函数。
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//继承自SuperType **核心代码**
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
总结:盗用构造函数继承解决了原型继承的两个缺点。解决了引用值导致的继承问题,和支持了传递参数。 但是,盗用构造函数继承也有缺点:
- 无法继承父类的原型属性和方法。
- 必须在构造函数中定义,函数不能重用。
3.组合式继承
综合了原型链和盗用构造函数,基本思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数实现继承实例属性。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
// 继承属性 **核心代码**
SuperType.call(this, name);
this.age = age;
}
// 继承方法 **核心代码**
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27
缺点:1.效率问题,父类构造函数会被调用两次。2.有可能同样的属性名既会在实例属性上又会在原型属性上。
4.原型式继承
本质函数是:创建一个临时的构造函数,将传入的对象赋值给这个构造函数的原型,然后返回临时对象的实例
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
具体写法:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()将原型式继承的概念规范化了。
总结:1.弊端:和原型链是一样的 2.优点:不需要单独创建构造函数
5.寄生式继承
function createAnother(original){
var clone = object(original); // 通过调用 object() 函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强对象
alert("hi");
};
return clone; // 返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
总结:1.弊端:和原型链是一样的 2.优点:不需要单独创建构造函数
6.寄生组合式继承
和组合继承的区别就在于 原型链继承的时候,它是先创建父类原型的副本,将副本赋值给了子类型的原型。
function SuperType(name){
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
// 继承 **和组合式继承的区别就在这行**
SubType.prototype = Object.create(SuperType.prototype)
SubType.prototype.constructor = SubType
SubType.prototype.sayAge =function () {
console.log(this.age)
}
var instance = new SubType("Nick",29)
console.log(instance)
console.log(instance instanceof SubType)
console.log(instance instanceof SuperType)
优点:解决了组合式继承的两个缺点
组合式继承缺点:1.效率问题,父类构造函数会被调用两次。2.有可能同样的属性名既会在实例属性上又会在原型属性上。
总结:寄生式组合继承可以算是引用类型继承的最佳模式