ES5的继承
类式继承
function Parent(userName){
this.userName=userName;
}
Parent.getUser.prototype=function(){
console.log("父类:",this.userName);
}
function Son(userName){
Son.prototype=new Parent(userName);
//让子类原型指向父类的一个实例,从而实现子类能继承父类的实例属性+原型属性,但实现不了
}
let son=new Son('son');
son.getUser(); //出错,无此方法
console.log(son);
/*
Son {}
__proto__:
constructor: ƒ Son(userName)
__proto__: Object
*/
出错的原因:在子类的构造函数里改变子类原型,而此时实例已经创建,则它的__proto__仍指向老的原型对象,所以改变子类原型对象并不会对子类实例有所改变。
改造:将实例的__proto__重新指向 Son.prototype,则在构造函数里写上'this.__proto__= Son.prototype;' 但此时实例的constructor指向的是Parent,所以在改变实例的__proto__之前还需加上‘Son.prototype.constructor=Son;’
思考:为什么是子类原型指向父类的实例对象,而不是父类的原型对象呢?
这样就不是继承了,而是和父类一样。而且在子类原型创建的属性方法父类也会具有,违背了多态性的特点。
//改造后
function Son(userName){
Son.prototype=new Parent(userName);
Son.prototype.constructor=Son;
this.__proto__= Son.prototype;
}
但这样的写法太麻烦了,需要不断的重写属性,缘由就是在构造函数里改变了原型对象,那如果在实例对象创建之前调用呢?
function Parent(userName){
this.userName=userName;
}
Parent.getUser.prototype=function(){
console.log("父类:",this.userName);
}
function Son(userName){
this.userName=userName;
}
Son.prototype=new Parent(); //实例还没有创建,无法传入参数
Son.prototype.constructor=Son;
let son=new Son('son');
显而易见,这个方法无法在调用父类构造函数时就传递参数。
构造函数式继承
function Parent(userName){
this.userName=userName;
}
Parent.prototype.getUser=function(){
console.log("父类:",this.userName);
}
function Son(userName){
Parent.call(this,userName);
//相当于把父类构造函数当作普通函数调用,但将this绑定成子类实例,因此子类实例具有
//父类的实例属性/方法,但不具有原型上的属性/方法
}
let son=new Son('son');
console.log(son.userName);
console.log(son.getUser);//undefined
组合式继承(前两种方法结合)
function Parent(userName){
this.userName=userName;
}
Parent.prototype.getUser=function(){
console.log("父类方法:",this.userName);
}
function Son(userName,age){
Parent.call(this,userName); //第二次调用
this.age=age;
}
Son.prototype=new Parent(); //第一次调用
Son.prototype.constructor=Son;
let son=new Son('son');
son.getUser(); //父类方法:son
实现了子类继承父类的所有属性和方法,子类在原型上添加属性和方法也不会影响父类原型
问题:这样是调用了两次父类构造函数,而且第一次调用其实没有必要,因为无法传递参数给实例属性赋值,而且第二次调用时的作用就是去获取父类的实例属性。如果父类构造函数代码很多时,则调用两次对性能也会有所影响。
改造:创建一个空函数来代替父类构造函数,那么该空函数的原型要指向父类原型对象,然后子类原型对象指向该空函数实例,则是下面的第四种继承
寄生式组合式继承
function inherit(parent) {
function F() { } //空函数
F.prototype=parent.prototype;
return new F();
}
function Parent(userName){
this.userName=userName;
}
Parent.prototype.getUser=function(){
console.log("父类方法:",this.userName);
}
function Son(userName,age){
Parent.call(this,userName);
this.age=age;
}
Son.prototype=inherit(Parent);
Son.prototype.constructor=Son;
Son.prototype.getAge=function(){
console.log("子类的方法",this.age);
}
let son=new Son('son',10); //必须在inherit函数调用之后创建子类实例
son.getUser(); //父类方法:son
son.getAge(); //子类的方法 10
inherit方法其实是实现Object.creat()方法,所以可以直接调用。如下代码:
function inherit(parent,child) {
child.prototype=Object.create(parent.prototype);//返回一个实例,实例的__proto__指向父类原型
child.prototype.constructor=child;
}
function Parent(userName){
this.userName=userName;
}
Parent.prototype.getUser=function(){
console.log("父类方法:",this.userName);
}
function Son(userName,age){
Parent.call(this,userName);
this.age=age;
}
inherit(Parent,Son);
//Son.prototype.constructor=Son;
Son.prototype.getAge=function(){
console.log("子类的方法",this.age);
}
let son=new Son('son',10);
son.getUser(); //父类方法:son
son.getAge(); //子类的方法 10
这样的写法可以让子类继承父类原型上的属性方法,也具有父类的实例属性和方法,不会使得不共享的属性出现在原型链中。而且在子类的原型添加属性和方法也不会影响父类。
注意:在inherit()函数调用之后,如果再往父类原型上添加属性方法,子类是无法继承到的。而且在子类的原型上添加属性方法只能在调用inherit()函数之后,否则会被覆盖。
ES6继承
使用关键字class定义类、extends实现子类继承父类、constructor对应构造函数,super函数创建this
super函数是调用父类构造函数,返回父类实例,即父类的this对象。在子类构造函数中,只有调用super函数之后,才可使用this关键字
//父类
class Sup{
static getSup(){
console.log('父类静态方法')
}
//构造函数
constructor(name) {
this.name=name;
}
//实例方法
getName() {
console.log('name:',this.name)
}
}
//子类继承父类
class Sub extends Sup{
constructor(name,age) {
super(name); //调用父类构造函数
this.age=age;
}
getAge(){
console.log('age:',this.age);
}
}
let sub=new Sub('子类',18);
sub.getName(); //name: 子类
sub.getAge(); //age: 18
Sup.getSup(); //只能父类调用父类的静态方法