总结 - JS实现继承的几种方式整理

目录

JS继承的实现方式

1. 原型链继承

2. 借用构造函数

3. 组合继承(常用)

4. 原型式继承

5. 寄生式继承

6. 寄生组合继承


JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?

JS继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

1. 原型链继承

核心: 将父类的实例作为子类的原型

扫描二维码关注公众号,回复: 2893023 查看本文章
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);                    //cat
console.log(cat.eat('fish'));             //cat正在吃:fish
console.log(cat.sleep());                 //cat正在睡觉
console.log(cat instanceof Animal);       //true
console.log(cat instanceof Cat);          //true

特点:

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

缺点:

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

原型链的问题
1. 包含引用类型值得原型

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType() {}

//继承了SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);             //red,blue,green,black
var instance2 = new SubType();
alert(instance2.colors);             //red,blue,green,black

这个例子中的 SuperType 构造函数定义了一个 colors 属性,该属性包含一个数组(引用类型值)。SuperType 的每个实例都会有各自包含自己数组的 colors 属性。当 SubType 通过原型链继承了SuperType 之后,SubType.prototype 就变成了 SuperType  的一个实例,因此它也拥有了一个它自己的 colors  属性一一就跟专门创建了一个 SubType.prototype.colors  属性一样。结果是,SubType的所有实例都会共享这一个colors属性。因此,当我们修改instance1.colors时,instance2.colors的值也会跟着修改。

2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。

推荐指数:★★(3、4两大致命缺陷)


2. 借用构造函数

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function SuperType() {
    this.colors = ["red", "blue", "green"];
}

function SubType() {
    //继承了SuperType
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);                 //red,blue,green,black
var instance2 = new SubType();
alert(instance2.colors);                 //red,blue,green

特点:

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

缺点:

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

推荐指数:★★(缺点3)


3. 组合继承(常用)

将原型链和借用构造函数的技术组合到一块 ,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

//添加原型方法
SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    //通过借用构造函数继承属性
    SuperType.call(this, name);
    this.age = age;
}

//通过原型链继承了SuperType的方法
SubType.prototype = new SuperType();

//添加原型方法
SubType.prototype.sayAge = function() {
    alert(this.age);
};

var instance1 = new SubType("Nike", 28);
instance1.colors.push("black");
alert(instance1.colors);                  //red,blue,green,black
instance1.sayName();                      //Nike
instance1.sayAge();                       //28
var instance2 = new SubType("Cindy", 34);
alert(instance2.colors);                  //red,blue,green
instance2.sayName();                      //Cindy
instance2.sayAge();                       //34

在这个SuperType 构造函定义了两个属name colors。SuperType定义一个方 sayName()SubType 构造函数在调SuperType 构造函数时传入了 name 参数,紧接又定它自己的属 age然后 SuperType 的实例赋值给 SubType 然后又在义了sayAge() 这样一来 可以让两个不 SubType 实例有自己属一一 colors 又可以使相同法了。

特点:

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

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

//添加原型方法
SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    //通过借用构造函数继承属性
    SuperType.call(this, name);                 //第二次调用SuperType
    this.age = age;
}

//通过原型链继承了SuperType的方法
SubType.prototype = new SuperType();             //第一次调用SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    alert(this.age);
};

在第一次调用 SuperType 构造函数时, SubType.prototype 会得到两个属性 :name 和 colors;它们都是 SuperType  的实例属性,只不过现在位于 SubType 的原型中。当调用SubType构造函数时,又会调用一次 SuperType  构造函数,这一次又在新对象上创建了实例属性 name  和 colors。于是,这两个属性就屏蔽了原型中的两个同名属性。

解决:寄生组合式继承

推荐指数:★★★★(仅仅多消耗了一点内存)


4. 原型式继承

借助原可以基有的对象创新对象。

由来:

在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var person = {
    name: "Nike",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var YetAnotherPerson = object(person);
YetAnotherPerson.name = "Linda";
YetAnotherPerson.friends.push("Sarbie");

alert(person.friends);             //Shelby,Court,Van,Rob,Sarbie

在这个例子中,可以作为另一个对象基础的是 person 对象,于是我们把它传入到 object()函数中,然后该函数就会返回一个新对象。这个新对象将 person 作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性,这意味person.friends 不仅属于 person 所有,而且也会被 anotherPerson 以及 yetAnotherPerson 共享。实际上,这就相当于又创建了person 对象的两个副本。


5. 寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。createAnother()函数接收了一个参数 ,也就是将要作为新对象基础的对象。然 后,把这个对象传递给 object()函数,将返回的结果赋值给 clone。再为 clone 对象添加一个新方法 sayHi(),最后返回 clone 对象。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
function createAnother(original) {
    var clone = object(original);         //通过调用函数创建一个新对象
    clone.sayHi = function() {            //以某种方式来增强这个对象
        alert("hi");
    };
    return clone;                         //返回这个对象
}

var person = {
    name: "Nike",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();                     //hi

这个例子中的代码基于 person 返回了一个新对象一 anotherPerson。新对象不仅具有 person的所有属性和方法,而且还有自己的sayHi()方法。


6. 寄生组合继承

即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
};

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype);     //创建对象
    prototype.constructor = subType;                 //增强对象
    subType.prototype = prototype;                   //指定对象
}

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
};

inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
    alert(this.age);
};

var instance1 = new SubType("Nike", 28);
instance1.colors.push("black");
alert(instance1.colors);                         //red,blue,green,black
instance1.sayName();                             //Nike
instance1.sayAge();                              //28
var instance2 = new SubType("Cindy", 34);
alert(instance2.colors);                         //red,blue,green
instance2.sayName();                             //Cindy
instance2.sayAge();                              //34

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

推荐指数:★★★★(实现复杂,扣掉一颗星)

猜你喜欢

转载自blog.csdn.net/weixin_37580235/article/details/81201260