js六种继承


共有六种继承方式:构造函数式继承、原型链式继承、组合继承、原型式继承、寄生式继承、寄生组合式继承

一、构造函数式继承

利用函数实现继承

    //父类构造函数
    function Father(){
    
    
        this.name = "Father";
        this.say = function(){
    
    
            console.log(this.name);
        }
    }

    //子类构造函数
    function Son(){
    
    
        Father.call(this);  // 重点
    }

重点:用call将父类构造函数引入子类函数(在子类函数中进行了父类的复制)

测试:

    let son1 = new Son();
    son1.say(); // Father

son1成功继承到了父类的name属性和say方法

但这不是传统意义上的继承,因为每一个Son子类调用父类生成的对象,都是各自独立的,也就是说,如果父类希望有一个公共的属性是所有子类实例共享的话,是没办法实现的。

优点:

  1. 可以继承多个构造函数(可以多个call)
  2. 所有基本属性独立,不会被其他实例影响

缺点:

  1. 无法继承父类构造函数的属性和方法(.prototype)
  2. 每个新实例都拷贝了一遍父构造函数,如果父类很大会占用内存
  3. 未实现公共方法的复用(函数复用),每次都会创建一个新函数而不是共用

二、原型链式继承

最原始的方式,通过prototype属性继承

    //父级 构造函数
    function Father() {
    
    
        this.arr = [1,2,3];
    }

    //父级 原型属性
    Father.prototype.name = "tingting"
    
    //子级 构造函数
    function Son() {
    
    
    }

    //子级 原型属性: 继承父级
    Son.prototype = new Father() // 重点
    
    //创建子级的实例对象
    let son = new Son();

    console.log(son.name); // tingting

重点:子类原型等于父类的实例 Son.prototype = new Father()

解释son实例如何找到name属性
如果一个对象某个属性找不到,会沿着它的原型往上去寻找,直到原型链的最后才会停止寻找

  • 首先在son对象自身查找,若未找到
  • 在Son.prototype中找 ( Father() ) ,若未找到
  • 在上一层Son.prototype.__proto__ (Father.prototype)
  • 直到找到需要的属性或方法,或达到原型链顶端Object.prototype

但所有的子实例的属性和方法,都在父类同一个实例上了,所以一旦某一个子实例修改了其中的方法,其他所有的子实例都会被影响
看下面代码

    function Father() {
    
    
        this.arr = [1,2,3];
    }
    
    function Son() {
    
    
    }

    Son.prototype = new Father() 
    
    let son1 = new Son();
    let son2 = new Son();
    son1.arr.push(4);

    console.log(son1.arr); // 1,2,3,4
    console.log(son2.arr);// 1,2,3,4

子实例son1修改arr后,son2实例的arr也被修改了

优点:

  1. 简单

缺点:

  1. 无法向父类构造函数传参
  2. 所有实例都会共享父类实例的属性(一个实例进行修改,那所有属性都会变化)

三、组合继承(原型链+构造函数)

将原型链继承和借用构造函数继承组合,整合了二者的优点,常用

    function Father(color) {
    
    
        this.color = color;
        
    }
    Father.prototype.print = function() {
    
    
        console.log(this.color);
    }
    function Son(color) {
    
    
        Father.call(this, color); // 借用构造函数继承
    }
    Son.prototype = new Father(); // 原型链继承let son1 = new Son('red');
    son1.print(); // redlet son2 = new Son('blue');
    son2.print(); // blue

在Son子类中,使用了Father.call来调用父类构造函数,同时又将Son.prototype赋给了父类实例
使用Father.call调用父类构造函数之后,以后所有通过new Son()创建出来的实例,单独拷贝了一份父类构造函数中定义的属性和方法;
再把父类的实例赋值给子类的原型prototype,所有子类实例就可以共享父类原型上的属性和方法;
因此,定义父类函时,将私有属性和方法放在构造函数里,将共享属性和方法放在原型上

优点:

  1. 可以传参
  2. 公用函数可以复用
    缺点:
  3. 调用了两次父类构造函数,占用内存

四、原型式继承

原型式继承本质其实就是一个浅拷贝,以一个对象为模板复制出新的对象

    // 封装一个函数容器,用来承载继承的原型
    function object(obj){
    
    
        function F(){
    
    }
        F.prototype = obj;
        return new F();
    }

    let father = {
    
    
        name: "father",
        arr: [1,2,3]
    }

    let son1 = object(father);

object函数中,定义一个构造函数,以obj为模板,让构造函数的原型对象(prototype)指向obj,再返回构造函数的一个实例,就可以访问到对象obj所有属性和方法。

Object.creat()

es5中新增了一个函数Object.create()直接实现了原型式继承
上面的代码可以改写为:


    let father = {
    
    
        name: "father",
        arr: [1,2,3]
    }

    let son1 = Object.create(obj);

本质还是原型链继承,同样会共享父类的属性

五、寄生式继承

寄生式继承就是把原型式继承再次封装,然后在对象上扩展新方法,再把新对象返回
可以理解为在原型式继承的基础上新增一些函数或属性

    function object(obj){
    
    
        function F(){
    
    }
        F.prototype = obj;
        return new F();
    }
    // 上面是原型式继承
    function create(obj){
    
    
        let clone = object(obj) // 或 Object.create(obj)

        clone.say = function(){
    
    
            console.log('123');
        }

        return clone;
    }
    // 用create函数封装了一遍,又增添了say方法
    let father = {
    
    
        name: "father",
        arr: [1,2,3]
    }

    let son = create(father);

    son.say(); // 123

clone给对象添加新的方法,最后返回clone,这样就继承了object返回的对象

优点:

  1. 不用创建自定义类型

缺点:

  1. 没用到原型,无法复用

六、寄生组合式继承(常用)

最后一个继承方式,是最完美的解决方案
是es6 class语法的实现原理,主要目的是为了解决组合式继承中每次都需要new Father导致执行多一次父类构造函数的缺点

    function Father(color) {
    
    
        this.color = color;
    }
    Father.prototype.print = function() {
    
    
        console.log(this.color);
    }
    function Son(color) {
    
    
        Father.call(this, color); 
    }

    Son.prototype = Object.create(Father.prototype); // 划重点let son1 = new Son('red');
    son1.print(); // redlet son2 = new Son('blue');
    son2.print(); // blue

这段和组合继承的不同之处只有一个,就是把原来的 Son.prototype = new Father();修改为Son.prototype = Object.create(Father.prototype);

原型式中提过Object.create方法是以传入的对象为原型,创建一个新对象;创建了这个新对象之后,又赋值给了Son.prototype,因此Son的原型最终指向的其实就是父类的原型对象,和new Father是一样的效果,且不会创建多余的实例占用内存,同时也可以实现多继承;

猜你喜欢

转载自blog.csdn.net/S_aitama/article/details/108757445