【JS基础】关于JS对象继承总结

87 阅读3分钟

//父类

function Animal(name){
    this.type="Animal";
    this.name=name || "动物";
    this.sleep=function(){
        console.log(this.name+"正在睡觉")
    }
}

Animal.prototype.eat=function(food){
    console.log(this.name+"正在吃"+food)
}

#0001 原型链继承

//原型链继承
//原型链继承
function Cat(name){
    this.name = name;
}

Cat.prototype=new Animal();
Cat.prototype.constructor=Cat;//关键代码this指向Cat
//如果constructor不指向Cat那么就会指向父类


let cat=new Cat("罗小黑");

//继承的属性
console.log(cat.type);
console.log(cat.name);
console.log(cat.sleep());
console.log(cat.eat("大餐"))

优点

  1. 实现简单
  2. 继承关系纯粹
console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//true
  1. 可以直接访问父类原型链属性和函数
Animal.prototype.run=function(){
   console.log(this.name+"正在跑路")
}
Animal.prototype.size="big"

console.log(cat.run())//罗小黑正在跑路
console.log(cat.size)//big

缺点

  • 子类所有属性将共享父类属性
  • 创建子类实例时无法向父类构造函数传递参数
  • 无法实现多继承

#0002 构造继承

//构造继承
//主要思想是通过call改变this指向,调用父类构造函数,从而将父类实例绑定到子类
console.log("构造继承")

function Cat(name){
    Animal.call(this);//核心代码
    this.name = name || "Tom";
}
let cat=new Cat("Tommy")

console.log(cat.type);//Animal
console.log(cat.name);//Tommy
console.log(cat.sleep());//Tommy正在睡觉
console.log(cat.eat("大餐"))//cat.eat is not a function
//无法调用父类原型对象上的函数
//只是重新绑定this指向,没有调用父类原型对象上的函数
console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//false

优点

  1. 可以解决子类实例共享父类属性的问题 调用后的this指向子类Cat,相当于将父类的属性直接绑定到子类的this中,这样每个子类都拥有自己的继承过来的属性,彼此之间相互独立

  2. 创建子类的实例后可以向父类传递参数

function Cat(name,parentAge){
    Animal.call(this,parentAge);
    this.name=name;
}
let cat=new Cat("tommy",11);
console.log(cat.age)//11
  1. 可以实现多继承(调用call函数)

缺点

  1. 因为是直接绑定的this,造成子类和父类没有关系,因此不是真正的继承
console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//false
  1. 只能继承父类属性,不能继承原型链上的属性和函数
  2. 无法复用父类的实例函数(每个子类都会拥有父类的实例,影响内存)

#0003 复制继承

主要思想是遍历父类实例的属性和函数,并将其设置为子类的属性和函数


function Cat(name,age){
    let animal=new Animal(name,age);

    for(let key in animal){//核心代码
        if(animal.hasOwnProperty(key)){
            this[key]=animal[key];
        }else{
            Cat.prototype[key]=animal[key];
        }
    }
}

Cat.prototype.eat = function(food){
    console.log(this.name+"正在吃"+food);
}

let cat=new Cat("狗蛋",3);
console.log("复制继承")
console.log(cat.type);//Animal
console.log(cat.name);//狗蛋
console.log(cat.age)//3
console.log(cat.sleep());//狗蛋正在睡觉 
console.log(cat.eat("大餐"))//狗蛋正在吃大餐

console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//false

优点

  • 支持多继承
  • 能够同时继承实例属性和原型对象上的属性和函数
  • 可以向父类构造函数中传值

缺点

  • 父类所有属性子类都有一份因此会消耗内存
  • 实例只是子类的实例,并不是父类的实例

#0004 组合继承

组合继承总结了构造继承和原型继承,修改prototype的同时也绑定了this

//组合继承
//组合继承总结了构造继承和原型继承的两种方法

function Cat(name,age){
    Animal.call(this);
    this.name=name;
    this.age=age;
}

//核心代码
Cat.prototype=new Animal();
Cat.prototype.constructor=Cat;

let cat=new Cat("卡布达",12);

console.log("组合继承")
console.log(cat.type);//Animal
console.log(cat.name);//卡布达
console.log(cat.age)//12
console.log(cat.sleep());//卡布达正在睡觉 
console.log(cat.eat("大餐"))//卡布达正在吃大餐

console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//true

优点

  1. 既能访问实例属性也能访问原型链属性
  2. 既是子类实例也是父类实例
  3. 没有属性共享的问题
  4. 可以向父类构造函数传递参数

缺点

父类属性会绑定两次(call绑定一次,改写子类prototype),造成性能浪费

寄生组合继承

本质是对组合继承的优化,通过一个立即执行函数使得只取父类prototype属性,过滤掉父类实例属性

function Cat(name,age){
    Animal.call(this);
    this.name=name;
    this.age=age;
}

(function(){
    let Super=function(){}
    Super.prototype=Animal.prototype;
    Cat.prototype=new Super();
    Cat.prototype.constructor=Cat;    
    
})()

let cat=new Cat("蟑螂恶霸",12)

console.log("寄生组合继承")
console.log(cat.type);//Animal
console.log(cat.name);//蟑螂恶霸
console.log(cat.age)//12
console.log(cat.sleep());//蟑螂恶霸正在睡觉 
console.log(cat.eat("大餐"))//蟑螂恶霸正在吃大餐

console.log(cat instanceof(Cat))//true
console.log(cat instanceof(Animal))//true

The End

image.png