js 对象7种继承

162 阅读5分钟

1. 实例继承 (子对象 = new 父对象())

function Animal(name) {
    this.name = name;
    this.say = function () {
        console.log("my name is ",this.name);
    }
}
Animal.prototype.eat = function (food) {
    console.log(this.name + '吃:' + food);
};

function Cat(name, age) {
    var o = new Animal(name); //先创建子类型实例
    o.age = age;
    return o;
}
var cat = new Cat("jason",18);
cat.say()

特点
不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  • 实例是父类的实例,不是子类的实例
  • 不支持多继承

2. 拷贝继承( for in 父实例属性)

遍历所有属性,统一赋值给子对象

function Animal(name) {
    this.name = name;
    this.say = function () {
        console.log("my name is ",this.name);
    }
}
// 原型对象方法
Animal.prototype.eat = function (food) {
    console.log(this.name + '吃:' + food);
};
function Cat(name, age) {
    var animal = new Animal(name);
    for (var p in animal) {
        Cat.prototype[p] = animal[p];
    }
    this.age = age
}
var cat = new Cat("jason");
cat.say()
cat.eat("香蕉")

特点
支持多继承

缺点:

  • 效率较低,拷贝导致内存占用高
  • 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

3. 原型链式继承 (子对象.prototype = new 父对象())

将父类的实例作为子类的原型

function Animal(name) {
    this.name = name;
    this.say = function () {
        console.log("我叫"+this.name);
    }
}
Animal.prototype.eat = function (food) {
    console.log(this.name + '正在吃:' + food);
};
//写法1
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是Animal
var cat = new Cat();
cat.name="jason"
cat.say()

//写法2 Object.create 创建原型在Animal父对象实例的子空对象
var cat = Object.create(new Animal())
cat.name="jason"
cat.say()

Object.create() 底层实现

Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
  };

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。
  2. 无法实现多继承
  3. 来自原型对象的引用属性是所有实例共享的
  4. 创建子类实例时,无法向父类构造函数传参

4. 寄生式继承 (子对象.prototype = new 父对象() , 子对象 新增方法)

是原型是继承的扩展,在继承的对象上,新增原型属性与方法

function Animal(name) {
    this.name = name;
    this.say = function () {
        console.log("我叫"+this.name);
    }
}
Animal.prototype.eat = function (food) {
    console.log(this.name + '正在吃:' + food);
}; 

var cat = Object.create(new Animal())
cat.name="jason"
cat.jump = function (food) {//寄生的地方,新增自己的方法
    console.log(this.name + '跳起来' );
};
cat.say()  
cat.jump()  

5. 构造函数继承 (父构造函数.call(this))

通过使用call改变父构造函数的执行上下文的this为当前的对象实例,达到继承的效果

function Animal(name) {
    this.name = name;
    this.say = function () {
        console.log("my name is ",this.name);
    }
}
// 原型对象方法
Animal.prototype.eat = function (food) {
    console.log(this.name + '吃:' + food);
};

function Cat(name, age) {
    Animal.call(this, name);
    //这里等价于 执行了
    //cat.name = name;
    //cat.say = function () {
   //     console.log("my name is ",cat.name);
   // }

    this.age = age;
}
var cat = new Cat("jason");
cat.say()  //这里正常
cat.eat() //这里会报错 缺点 原型对象不能调用

特点:

  • 解决了原型链继承中,子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承(call 多个父类对象)

缺点:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

6. 组合继承 (原型链+ 父构造函数call)

子对象.prototype = new 父对象 + 父构造函数.call(this))

function Animal(name) {
    // 属性
    this.name = name || 'Animal';
    // 实例方法
    this.sleep = function () {
        return this.name + ' 正在睡觉!';
    }
}
// 原型方法
Animal.prototype.eat = function (food) {
    return this.name + ' 正在吃: ' + food;
}; 

function Cat(name){
    Animal.call(this);
    this.name = name;
}
Cat.prototype = new Animal();
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是Animal
// Test Code
var cat = new Cat("jason"); 
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true 

特点:

  • 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  • 既是子类的实例,也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺点:  调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

7. 寄生+组合继承(中间层Middle实例做原型链+ 父构造函数call)

  1. 通过新增中间层Middle实例
  2. 让自对象原型指向Middle实例, MIddle原型再指向父对象原型。
  3. 这个过程也叫原型式继承

解决了原型链继承时候,要先实例父类的问题,导致父对象属性被多次实例化。

function Animal(name) {
    // 属性
    this.name = name || 'Animal';
    // 实例方法
    this.sleep = function () {
        return this.name + ' 正在睡觉!';
    }
}
Animal.prototype.eat = function (food) {
    return this.name + ' 正在吃: ' + food;
};

function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
}

// 创建一个没有实例方法的类
var Middle = function () {};
Middle.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Middle(); 
Cat.prototype.construtor = Cat; //这里要修正构造函数的指向,不然就是 Middle
// 等价于下面这种情况 
//inheritPrototype1(Cat,Animal)
//inheritPrototype2(Cat,Animal)
//inheritPrototype3(Cat,Animal)
//inheritPrototype4(Cat,Animal)


var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true 

优化和封装middle指向方法

//实现1 把上面内容包装一下
function inheritPrototype1(sub, sup) {
   var Middle = function() {}
   Middle.prototype = sup.prototype;
   sub.prototype = new Middle();
   sub.prototype.construtor = sub; //这里要修正构造函数的指向
} 
//实现2 用Object.create()简化
  function inheritPrototype2(subType, superType) {
    var MiddleObj = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本  
    subType.prototype = MiddleObj; // 指定对象,将新创建的对象赋值给子类的原型
    subType.prototype.construtor = subType; //这里要修正构造函数的指向
} 

//实现3 用Object.create()简化2
  function inheritPrototype3(subType, superType) {
      subType.prototype = Object.create(superType, { constructor: subType })
  }

//实现4 直接修改原型链指向
  function inheritPrototype4(subType, superType) {
    subType.prototype.__proto__ = superType.prototype;
  }

//实现5 使用setPrototypeOf直接修改原型链指向
  function inheritPrototype5(subType, superType) {
      Object.setPrototypeOf(subType.prototype, superType.prototype);
  }

Object.create() 底层实现

Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
};

缺点:原型链上多加了一层,而且又使用的父函数call(this),逻辑太复杂

8. ES6 class extends继承

class Animal {
    constructor(name) {
        this.name = name || 'Animal';
        this.sleep = function () {
            return this.name + ' 正在睡觉!';
        }
    }
    eat(food) {
        return this.name + ' 正在吃: ' + food;
    };
}

class Cat extends Animal {
    constructor(name, age) {
        super(name);
        this.age = age; // 新增的子类属性  
    }
    eat(food) {
        const result = super.eat(food); // 通过 super 调用父类方法
        return this.age + ' ' + result;
    }
}
const cat = new Cat('miao', 3);
console.log(cat.name,cat.age)
console.log(cat.eat("香蕉"))