JS的六种继承方式,构造函数

一篇文章掌握JS承袭——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

2018/08/02 · JavaScript
· 继承

原稿出处:
那是你的玩具车吗   

说实在话,从前本身只须求领会“寄生组合继承”是最棒的,有个祖传代码模版用就行。近期因为部分业务,多少个星期以来直接时刻不忘想整理出来。本文以《JavaScript高档程序设计》上的情节为骨架,补充了ES6
Class的相干内容,从作者觉着更易于精通的角度将一而再这事陈述出来,希望咱们能具备收获。

后续是面向对象编程中又一杰出关键的概念,JavaScript支持促成持续,不帮助接口继承,达成三番五次首要借助原型链来完成的。

1. 接续分类

先来个全部影像。如图所示,JS中继承能够根据是或不是使用object函数(在下文中会提到),将持续分成两片段(Object.create是ES5新添的格局,用来标准化这几个函数)。

内部,原型链传承和原型式承接有一样的利弊,构造函数承袭与寄生式承袭也彼此照顾。寄生组合承继基于Object.create,
同一时候优化了整合承接,成为了宏观的接续情势。ES6 Class
Extends的结果与寄生组合承继基本一致,不过贯彻方案又略有不一样。

下边立时步入正题。

图片 1

原型链

2. 接二连三格局

上航海用教室上半区的原型链承袭,构造函数继承,组合继承,网络内容非常多,本文不作详细描述,只提议入眼。这里给出了自己觉着最轻便精晓的一篇《JS中的继承(上)》。倘使对上半区的剧情不熟稔,能够先看那篇文章,再回来继续阅读;若是已经比较纯熟,这一部分能够相当的慢略过。另,上半区大气借出了yq前端的一篇三番两次小说[1]。

首先得要理解如何是原型链,在一篇文章看懂proto和prototype的关联及界别中讲得那多少个详细

2.1 原型式承继

主干:将父类的实例作为子类的原型

SubType.prototype = new SuperType() //
全体涉及到原型链承接的后续情势都要修改子类构造函数的指向,不然子类实例的布局函数会指向SuperType。
SubType.prototype.constructor = SubType;

1
2
3
SubType.prototype = new SuperType()
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
SubType.prototype.constructor = SubType;

可取:父类方法能够复用

缺点:

  • 父类的引用属性会被有着子类实例分享
  • 子类创设实例时不能够向父类传递参数

原型链承继基本思维就是让一个原型对象指向另四个等级次序的实例

2.2 构造函数承袭

宗旨:将父类构造函数的内容复制给了子类的构造函数。那是装有继续中有一无二二个不涉及到prototype的存续。

SuperType.call(SubType);

1
SuperType.call(SubType);

可取:和原型链承继完全翻转。

  • 父类的援引属性不会被分享
  • 子类营造实例时能够向父类传递参数

短处:父类的方法不可能复用,子类实例的法子每一趟都以单独成立的。

function SuperType() {

2.3 组合承袭

着力:原型式承接和构造函数字传送承的整合,兼具了两岸的优点。

function SuperType() { this.name = ‘parent’; this.arr = [1, 2, 3]; }
SuperType.prototype.say = function() { console.log(‘this is parent’) }
function SubType() { SuperType.call(this) // 第一遍调用SuperType }
SubType.prototype = new SuperType() // 第贰次调用SuperType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SuperType() {
    this.name = ‘parent’;
    this.arr = [1, 2, 3];
}
 
SuperType.prototype.say = function() {
    console.log(‘this is parent’)
}
 
function SubType() {
    SuperType.call(this) // 第二次调用SuperType
}
 
SubType.prototype = new SuperType() // 第一次调用SuperType

优点:

  • 父类的主意能够被复用
  • 父类的援引属性不会被分享
  • 子类营造实例时方可向父类传递参数

缺点:

调用了四回父类的构造函数,第三次给子类的原型增多了父类的name,
arr属性,第三遍又给子类的构造函数增加了父类的name,
arr属性,进而覆盖了子类原型中的同名参数。这种被掩瞒的情景导致了品质上的浪费。

  this.property = true

2.4 原型式承继

着力:原型式传承的object方法本质上是对参数对象的叁个浅复制。

优点:父类方法能够复用

缺点:

  • 父类的援用属性会被全体子类实例分享
  • 子类创设实例时不可能向父类传递参数

function object(o){ function F(){} F.prototype = o; return new F(); }
var person = { name: “Nicholas”, 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(“Barbie”); alert(person.friends);
//”Shelby,Court,Van,Rob,Barbie”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
 
var person = {
    name: "Nicholas",
    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("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
 

ECMAScript 5 通过新扩充Object.create()方法则范化了原型式承袭。那些情势接收四个参数:一个用作新对象原型的对象和(可选的)一个为新目的定义额外属性的目的。在扩散二个参数的情况下,
Object.create()与 object()方法的展现未有差距于。——《JAVASCript高端编制程序》

所以上文中代码能够改动为

var yetAnotherPerson = object(person); => var yetAnotherPerson =
Object.create(person);

1
var yetAnotherPerson = object(person); => var yetAnotherPerson = Object.create(person);

}

2.5 寄生式承袭

基本:使用原型式承接猎取二个目的对象的浅复制,然后巩固这几个浅复制的技艺。

利弊:仅提供一种思路,没什么可取

function createAnother(original){ var clone=object(original);
//通过调用函数创制三个新目的 clone.sayHi = function(){
//以某种方式来拉长那么些指标 alert(“hi”); }; return clone; //再次来到那个目的} var person = { name: “Nicolas”, friends: [“Shelby”, “Court”, “Van”]
}; var anotherPerson = createAnother(person); anotherPerson.sayHi();
//”hi”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createAnother(original){
    var clone=object(original);    //通过调用函数创建一个新对象
    clone.sayHi = function(){      //以某种方式来增强这个对象
        alert("hi");
    };
    return clone;                  //返回这个对象
}
 
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
 
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

SuperType.prototype.getSuperValue = function () {

2.6 寄生组合承继

刚刚谈起组合承袭有一个会三遍调用父类的构造函数产生浪费的毛病,寄生组合承接就可以缓和这么些难点。

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); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}

利弊:那是一种完美的接续格局。

  return this.property

2.7 ES6 Class extends

大旨:
ES6承继的结果和寄生组合继承相似,本质上,ES6承继是一种语法糖。但是,寄生组合承继是先创造子类实例this对象,然后再对其抓好;而ES6先将父类实例对象的质量和艺术,加到this上边(所以必得先调用super方法),然后再用子类的构造函数修改this。

class A {} class B extends A { constructor() { super(); } }

1
2
3
4
5
6
7
class A {}
 
class B extends A {
  constructor() {
    super();
  }
}

ES6达成三番五次的实际原理:

class A { } class B { } Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto; return obj; } // B 的实例承继 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype); // B 承接 A 的静态属性
Object.setPrototypeOf(B, A);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {
}
 
class B {
}
 
Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}
 
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
 
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
 

ES6连任与ES5承接的异同:

一样点:本质上ES6继续是ES5继续的语法糖

不同点:

  • ES6接续中子类的构造函数的原型链指向父类的构造函数,ES第55中学生运动用的是构造函数复制,没有原型链指向。
  • ES6子类实例的创设,基于父类实例,ES5中不是。

}

3. 总结

  • ES6 Class extends是ES5一连的语法糖
  • JS的持续除了构造函数承袭之外都基于原型链构建的
  • 能够用寄生组合继承完毕ES6 Class extends,可是还是会有细小的区别

function SubType() {

参谋小说:

[1]《js承接、构造函数继承、原型链承接、组合承袭、组合继承优化、寄生组合承继》

[2]《JavaScript高端编程》

1 赞 收藏
评论

图片 2

  this.subproperty = false

}

SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {

  return this.subproperty

}

var instance = new SubType()

console.log(instance.getSuperValue()) // true

代码定义了多少个门类SuperType和SubType,每种门类分别有叁性情情和一个办法,SubType继承了SuperType,而延续是透过创造SuperType的实例,并将该实例赋给SubType.prototype达成的。

兑现的精神是重写原型对象,代之以三个新品类的实例,那么存在SuperType的实例中的全部属性和措施,今后也设有于SubType.prototype中了。

我们知晓,在创制贰个实例的时候,实例对象中会有三个里面指针指向创造它的原型,实行关联起来,在那边代码SubType.prototype
= new
SuperType(),也会在SubType.prototype创立几当中间指针,将SubType.prototype与SuperType关联起来。

因而instance指向SubType的原型,SubType的原型又指向SuperType的原型,继而在instance在调用getSuperValue()方法的时候,会顺着那条链向来往上找。

丰盛方式

在给SubType原型增加方法的时候,假设,父类上也是有同一的名字,SubType将会覆盖那些主意,到达重新的指标。
然则那些措施依旧存在于父类中。

牢记不可能以字面量的款式丰盛,因为,上面说过通超过实际例承袭本质上正是重写,再利用字面量方式,又是二遍重写了,但此次重写未有跟父类有其余涉及,所以就能够变成原型链截断。

function SuperType() {

  this.property = true

}

SuperType.prototype.getSuperValue = function () {

  return this.property

}

function SubType() {

  this.subproperty = false

}

SubType.prototype = new SuperType()

SubType.prototype = {

  getSubValue:function () {

  return this.subproperty

  }

}

var instance = new SubType()

console.log(instance.getSuperValue())  // error

问题

一味的利用原型链承继,首要难点源于包含引用类型值的原型。

function SuperType() {

  this.colors = [‘red’, ‘blue’, ‘green’]

}

function SubType() {

}

SubType.prototype = new SuperType()

var instance1 = new SubType()

var instance2 = new SubType()

instance1.colors.push(‘black’)

console.log(instance1.colors)  // [“red”, “blue”, “green”, “black”]

console.log(instance2.colors) // [“red”, “blue”, “green”, “black”]

在SuperType构造函数定义了二个colors属性,当SubType通过原型链承接后,这几个天性就能够出现SubType.prototype中,就跟特地创造了SubType.prototype.colors同样,所以会促成SubType的具有实例都会分享这特本性,所以instance1修改colors这么些引用类型值,也会展示到instance第22中学。

借用构造函数

此办法为了化解原型中带有援引类型值所推动的难点。

这种格局的考虑就是在子类构造函数的里边调用父类构造函数,能够借助apply()和call()方法来改动目的的实行上下文

function SuperType() {

  this.colors = [‘red’, ‘blue’, ‘green’]

}

function SubType() {

  // 继承SuperType

  SuperType.call(this)

}

var instance1 = new SubType()

var instance2 = new SubType()

instance1.colors.push(‘black’)

console.log(instance1.colors)  // [“red”, “blue”, “green”, “black”]

console.log(instance2.colors) // [“red”, “blue”, “green”]

在新建SubType实例是调用了SuperType构造函数,这样的话,就能在新SubType目的上推行SuperType函数中定义的保有目的起先化代码。

结果,SubType的每一种实例就集会场全数温馨的colors属性的别本了。

传送参数

借助构造函数还应该有二个优势正是能够传递参数

function SuperType(name) {

  this.name = name

}

function SubType() {

  // 继承SuperType

  SuperType.call(this, ‘Jiang’)

  this.job = ‘student’

}

var instance = new SubType()

console.log(instance.name)  // Jiang

console.log(instance.job)  // student

问题

万三头是信任构造函数,方法都在构造函数中定义,由此函数不能够达到规定的标准复用

结合承接(原型链+构造函数)

组成承接是将原型链承袭和构造函数结合起来,从而发挥双方之长的一种形式。

思路就是应用原型链达成对原型属性和办法的三番两次,而经过借用构造函数来贯彻对实例属性的三番五次。

那般,既通过在原型上定义方法实现了函数复用,又能够保障每一种实例都有它本身的脾性。

function SuperType(name) {

  this.name = name

  this.colors = [‘red’, ‘blue’, ‘green’]

}

SuperType.prototype.sayName = function () {

  console.log(this.name)

}

function SubType(name, job) {

  // 承袭属性

  SuperType.call(this, name)

  this.job = job

}

// 承袭方法

SubType.prototype = new SuperType()

SubType.prototype.constructor = SuperType

SubType.prototype.sayJob = function() {

  console.log(this.job)

}

var instance1 = new SubType(‘Jiang’, ‘student’)

instance1.colors.push(‘black’)

console.log(instance1.colors) //[“red”, “blue”, “green”, “black”]

instance1.sayName() // ‘Jiang’

instance1.sayJob()  // ‘student’

发表评论

电子邮件地址不会被公开。 必填项已用*标注