一、理解
对于继承最基础的理解,就是解决了一个问题:假定我们已有一个构造函数(工厂函数)A,现在想要声明一个构造函数B,而B是对A的补充,除了具备A拥有的所有属性和方法外,还具备B自身独有的一些属性和方法,此时我们可以让B直接继承A的属性和方法,再对B进行补充,就可以完美达到目的。
二、三种继承方式
1.组合继承
// 声明一个工厂函数Parent
function Parent(value){
this.name = value;
}
// 在原型上挂载方法
Parent.prototype.getName = function(){
console.log(this.name);
}
// 声明一个工厂函数Child
function Child(value, age){
Parent.call(this,value); // 这一步执行后继承Parent的属性
this.age = age; // 添加Child独有的属性
};
Child.prototype = new Parent(); // 继承Parent的原型,此时Child实例可以访问Parent原型上的属性和方法
Child.prototype.getAge = function(){ // 对Child的原型进行补充
console.log(this.age);
};
Child.prototype.constructor = Child; // 将Child的构造器指向自己
// 创建一个Child实例
const child1 = new Child("杰克", 18);
// 打印该实例结果如下图
// 此时可以看到实例child1正确地继承了Parent的属性和原型,能够直接调用Child原型和Parent原型上的方法。
child1.getName(); // "杰克"
child1.getAge(); // 18
child1 instanceof Child; // true
child1 instanceof Parent; // true
child1 instanceof Object; // true
这种继承方式的优点在于构造函数可以传参,也不会和父类的引用属性共享,可以复用父类函数,也也存在一个缺点:继承父类的时候会调用父类的构造函数,导致子类型的实例会被多余地添加父类属性(参考上图,Child实例的原型指向Parent,Parent中添加了一个值为undefined的属性name),存在内存的浪费。
2.寄生组合继承
第1点的末尾提到组合继承的缺点是由于继承途中调用了父类的构造函数导致的,寄生组合继承则优化了这一点。
// 声明一个工厂函数Parent
function Parent(value){
this.name = value;
}
// 在原型上挂载方法
Parent.prototype.getName = function(){
console.log(this.name);
}
// 声明一个工厂函数Child
function Child(value, age){
Parent.call(this,value); // 这一步执行后继承Parent的属性
this.age = age; // 添加Child独有的属性
};
Child.prototype = Object.create(Parent.prototype, {
constructor:{
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
Child.prototype.getAge = function(){ // 对Child的原型进行补充
console.log(this.age);
};
// 创建一个Child实例
const child1 = new Child("杰克", 18);
// 打印该实例结果如下图
child1.getName(); // "杰克"
child1.getAge(); // 18
child1 instanceof Child; // true
child1 instanceof Parent; // true
child1 instanceof Object; // true
此时实例的原型上将不再会生成多余的父类属性,同时能直接找到子类的构造函数。
3.Class继承
Class继承是ES6中被提出来的新规范,可以直接使用class关键字进行继承操作。
// 声明一个父类
class Parent{
constructor(value){
this.name = value;
};
getName(){
console.log(this.name);
}
}
// 声明一个子类继承父类
class Child extends Parent{
constructor(value, age){
super(value); // spuer函数在这里的作用是把Parent的构造函数中的this指向改为Child实例,传递的参数则是Parent的构造函数所需的参数
this.age = age;
}
getAge(){
console.log(this.age);
}
}
// 创建一个Child实例
const child1 = new Child("Susan", 20);
// 打印该实例结果如下图
child1.getName(); // "Susan"
child1.getAge(); // 20
child1 instanceof Child; // true
child1 instanceof Parent; // true
child1 instanceof Object; // true
Class继承的核心在于使用extends表明继承自哪个父类,且在子类的构造函数中必须调用super函数,这句代码可以看作
Parent.prototype.constructor.call(this,value)
。由于JS中并不存在类,所以class的本质其实仍是函数。
三、补充
-
__proto__
称作隐式原型,对象才有该属性,指向当前对象的原型对象(父类)。 -
prototype
称作显式原型,显式原型指向当前构造函数的原型对象(父类)。
Child.prototype; // Parent{}
child1.__proto__; // Parent{}
Child.prototype === child1.__proto__; // true
- 原型:简单理解原型就是当前实例对象继承来源的构造函数。
- 原型链:原型链是一个虚拟的模型,说的是当访问实例的某个属性或方法时,会依照实例本身、隐式原型(父类)、隐式原型的父类(爷类)…的顺序逐步往上查找,这个过程中访问的这些节点构成我们所说的原型链。