图片 2

一篇文章理解JS继承,JS的六种继承方式

一篇小说精晓JS承接——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

2018/08/02 · JavaScript
· 继承

原版的书文出处:
那是您的玩具车吗   

说实在话,之前作者只供给了然“寄生组合承接”是最棒的,有个祖传代码模版用就行。方今因为部分事情,多少个星期以来径直朝思暮想想整理出来。本文以《JavaScript高等程序设计》上的内容为骨架,补充了ES陆Class的连锁内容,从自家觉着更便于精晓的角度将继续那件事叙述出来,希望大家能具有收获。

一而再是面向对象编制程序中又1要命关键的概念,JavaScript协理落到实处接二连三,不补助接口承接,落成持续首要借助原型链来完毕的。

壹. 继续分类

先来个全体影象。如图所示,JS中承接能够服从是不是选用object函数(在下文中会提到),将继续分成两部分(Object.create是ES伍新扩充的法子,用来标准化这几个函数)。

里头,原型链继承和原型式承袭有同样的利弊,构造函数承接与寄生式承继也相互照管。寄生组合承接基于Object.create,
同时优化了组合承接,成为了全面包车型客车继续情势。ES陆 Class
Extends的结果与寄生组合承继基本壹致,然而落到实处方案又略有不一致。

上面立即进入正题。

图片 1

原型链

贰. 接续方式

上海教室上半区的原型链承袭,构造函数承继,组合承继,网络内容相比较多,本文不作详细描述,只提议入眼。这里给出了笔者感觉最轻易驾驭的一篇《JS中的承袭(上)》。尽管对上半区的剧情不熟知,能够先看那篇作品,再回去继续读书;借使已经比较熟识,那有个别能够火速略过。另,上半区大气借出了yq前端的一篇一而再小说[1]。

首先得要领悟哪些是原型链,在1篇小说看懂proto和prototype的关联及界别中讲得尤其详细

二.1 原型式承接

基本:将父类的实例作为子类的原型

SubType.prototype = new SuperType() //
全部涉嫌到原型链传承的后续方式都要修改子类构造函数的针对性,不然子类实例的布局函数会指向SuperType。
SubType.prototype.constructor = SubType;

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

优点:父类方法能够复用

缺点:

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

原型链承袭基本思虑便是让一个原型对象指向另一个类其他实例

二.二 构造函数继承

主导:将父类构造函数的内容复制给了子类的构造函数。那是持有继续中绝无仅有八个不关乎到prototype的三番五次。

SuperType.call(SubType);

1
SuperType.call(SubType);

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

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

缺点:父类的法子不能够复用,子类实例的办法每一遍都以单身成立的。

function SuperType() {

二.叁 组合承袭

着力:原型式承继和构造函数承袭的组合,兼具了双边的长处。

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

二.四 原型式承袭

主导:原型式承继的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 五 通过新添Object.create()方法标准化了原型式承袭。这一个措施接收五个参数:3个用作新对象原型的指标和(可选的)八个为新目的定义额外属性的靶子。在传诵1个参数的景观下,
Object.create()与 object()方法的作为壹律。——《JAVASCript高等编制程序》

所以上文中代码能够转换为

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

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

}

二.伍 寄生式承接

主导:使用原型式继承取得三个目的对象的浅复制,然后增强这些浅复制的才具。

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

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 () {

二.陆 寄生组合承接

刚刚提起组合承接有1个会几回调用父类的构造函数形成浪费的短处,寄生组合承接就足以解决那个难点。

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承接的结果和寄生组合承接相似,本质上,ES陆持续是一种语法糖。可是,寄生组合承继是先创造子类实例this对象,然后再对其提升;而ES陆先将父类实例对象的属性和格局,加到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();
  }
}

ES陆得以完毕一连的求实原理:

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

ES陆三番五次与ES伍一而再的异议:

一样点:本质上ES6无冕是ES5继承的语法糖

不同点:

  • ES陆继续中子类的构造函数的原型链指向父类的构造函数,ES5中应用的是构造函数复制,未有原型链指向。
  • ES陆子类实例的营造,基于父类实例,ES5中不是。

}

3. 总结

  • ES6 Class extends是ES伍接续的语法糖
  • JS的两次三番除了构造函数字传送承之外都依照原型链塑造的
  • 可以用寄生组合继承落成ES六 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的有所实例都会共享那特特性,所以instance一修改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属性的别本了。

传递参数

依傍构造函数还有2个优势正是可以传递参数

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

问题

尽管仅仅依附构造函数,方法都在构造函数中定义,因而函数不能落成复用

组成承袭(原型链+构造函数)

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

思路便是行使原型链完成对原型属性和办法的接二连三,而透过借用构造函数来贯彻对实例属性的接续。

那样,既通过在原型上定义方法实现了函数复用,又能够保险每种实例都有它和睦的属性。

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’

发表评论

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