整合阮一峰教程,Js实现继承的几种方法及其优缺点

时间: 2019-08-12阅读: 86标签: 继承

在ES5持续的落到实处充裕有趣的,由于并未有古板面向对象类的定义,Javascript利用原型链的特点来兑现持续,那中间有无数的习性指向和内需小心的地点。

导语

class只是语法糖,并不曾为js引进生龙活虎种新的靶子世襲形式,以前经过原型链相仿能够兑现class的作用;

//定义类class Point {  constructor(x, y) {    this.x = x;    this.y = y;  }  toString() {    return '(' + this.x + ', ' + this.y + ')';  }}

要搞懂JS继承,大家率先要清楚原型链:每贰个实例对象都有一个__proto__质量(隐式原型),在js内部用来查找原型链;每二个布局函数都有prototype属性(呈现原型),用来展现更正对象的原型,实例.__proto__=布局函数.prototype=原型。原型链的特色正是:通超过实际例.__proto__招来原型上的性格,从子类向来升高查找对象原型的属性,继而产生多少个招来链即原型链。

原型链的特征和完毕已经在头里的生龙活虎篇收拾说过了,正是通过将子类布局函数的原型作为父类布局函数的实例,那样就连通了子类-子类原型-父类,原型链的风味正是逐层查找,从子类开端平素往上直到全体指标的原型Object.prototype,找到属性方法之后就能够结束查找,所以下层的习性方法会覆盖上层。

定义class 

class 就像特殊的函数,和创办函数同样,有类声明(class declarations),和类表明式( class expressions 卡塔尔(قطر‎二种创制类的办法:

1.原型链世袭

三个主导的依附原型链的三番两次进度大约是这样的:

类声明

利用class 关键字,后边跟上类名(class 关键字背后的是类名)

class Rectangle {  constructor(height, width) {    this.height = height;    this.width = width;  }}

类评释和不足为道函数注解区别的地点在于,类注脚并不曾函数升高(下边要介绍的类表明式也未尝函数进步):

var obj = new Myclass();        //报错class Myclass (){ }

类证明不可能函数进步是为着确认保证,子类世襲父类的那么些语句,不会提高至尾部,不然将会并发父类还没曾概念,子类将要延续, 看上面包车型客车例证:

      {            let B = class {};      // let 声明 不存在函数提升           class A extends B {    //如果存在类哈函数提升的话,这行会提升到第一行,父亲还没声明,儿子怎么继承?          }        }

类表明不能够和已经存在的类重名,(不管这些类早前是因此类注明的不二等秘书技宣示依旧通过类表明式的不二诀窍宣示的), 不然将会报错;

     class f1 {};        class f1 {};      var f2 = class {};      class f2 {};       // 报错了     class f3 {};        var f3 = class {};    // 报错了      var f4 = class {};      var f4 = class {};    // 如果两个函数表达式重名了,那么不会报错

我们应用原型世袭时,主要利用sub.prototype=new super,那样连通了子类-子类原型-父类。

//先来个父类,带些属性
function Super(){
    this.flag = true;
}
//为了提高复用性,方法绑定在父类原型属性上
Super.prototype.getFlag = function(){
    return this.flag;
}
//来个子类
function Sub(){
    this.subFlag = false;
}
//实现继承
Sub.prototype = new Super;
//给子类添加子类特有的方法,注意顺序要在继承之后
Sub.prototype.getSubFlag = function(){
    return this.subFlag;
}
//构造实例
var es5 = new Sub;

类表达式

类表明式是定义类的别的大器晚成种艺术

var myClass = class [className] [extends] {   // class body}

就好像函数表明式相似,在类表明式中,类名是不值风度翩翩提的。若定义的类名,则该类名独有的类的此中才具够访谈到。

// 方式一const MyClass = class {};// 方式二:给出类名const MyClass = class Me {    getClassName() {        return Me.name;    }};

只要class 后边没有名字,那么该类.name  正是 函数表达式的名字:

var Foo = class {  constructor() {}  bar() {    return 'Hello World!';  }};var instance = new Foo();instance.bar(); // "Hello World!"Foo.name; // "Foo"

设若 class 前边出名字,那么该名字只好在函数内被访问到,同期此类 . name 正是class 前面包车型大巴名字:

var Foo = class NamedFoo {  constructor() {}  whoIsThere() {    return NamedFoo.name;  }}var bar = new Foo();bar.whoIsThere(); // "NamedFoo"NamedFoo.name; // ReferenceError: NamedFoo is not definedFoo.name; // "NamedFoo"

行使类表明式,可以写出当下试行的Class。如下:

let person = new class {    constructor(name) {        this.name = name;    }    sayName() {        console.log(this.name);    }}('Zhang San');person.sayName(); // Zhang San
//父类,带属性 function Super(){ this.flag = true; } //为了提高复用性,方法绑定在父类原型属性上 Super.prototype.getFlag = function(){ return this.flag; } //来个子类 function Sub(){ this.subFlag = false; } //实现继承 Sub.prototype = new Super; //给子类添加子类特有的方法,注意顺序要在继承之后 Sub.prototype.getSubFlag = function(){ return this.subFlag; } //构造实例 var es5 = new Sub;

原型链完成的持续首要有多少个难题:
1、本来大家为了结构函数属性的卷入私有性,方法的复用性,提倡将质量申明在构造函数内,而将艺术绑定在原型对象上,可是未来子类的原型是父类的三个实例,自然父类的性情就改成子类原型的属性了;
那就会推动三个难点,我们理解布局函数的原型属性在享有布局的实例中是分享的,所以原型中属性的退换会反射到独具的实例上,那就违反了小编们想要属性私有化的初志;
2、创设子类的实例时,不可能向父类的布局函数字传送递参数

类体和方法定义

类的成员须求定义在豆蔻梢头对大括号内{},大括号内的代码的大括号本人组成了类体。类成员富含类布局器类方法 (富含静态方法和实例方法)。

类体中的代码都强制在严刻方式中进行,即私下认可”use strict”。寻思到未来怀有的代码,其实都以运作在模块之中,所以ES6其实把一切语言升级到了严刻形式。

缺欠:布局函数原型上的习性在颇负该布局函数布局的实例上是分享的,即属性未有私有化,原型上属性的变动会作用到全部的实例上。

function Super(){
    this.flag = true;
}
function Sub(){
   this.subFlag = false;
}
Sub.prototype = new Super;
var obj = new Sub();
obj.flag = flase;  //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响
var obj_2 = new Sub();
console.log(obj.flag)  //false;
构造器(constructor方法)

叁个类只可以具有叁个名称为constructor的方式(不然会报错),四个类的 constructor 方法独有在实例化的时候被调用。

如若未有显式定义constructor主意,这么些方法会被私下认可增加,即,不管有没有浮现定义,任何二个类都有constructor方法。

子类必需在constructor方法中调用super措施,不然新建实例时会报错。因为子类未有谐和的this对象,而是继续父类的this对象,然后对其進展加工,假设不调用super方法,子类就得不到this对象。

class Point {}class ColorPoint extends Point {    constructor() {}}let cp = new ColorPoint(); // ReferenceError

上边代码中,ColorPoint气吞山河了父类Point,可是它的布局函数未有调用super主意,引致新建实例时报错。

2.布局函数世袭

为了化解上述七个难题,有二个叫借用布局函数的点子
只要求在子类布局函数内部使用apply恐怕call来调用父类的函数就能够在促成属性持续的还要,又能传递参数,又能让实例不相互作用

原型方法

定义类的点卯时,方法名前面不须要加多function要害字。其余,方法之间没有必要用逗号分隔,加了会报错。

class Bar {    constructor() {}    doStuff() {}    toString() {}    toValue() {}}

上边包车型大巴写法就雷同上面:

Bar.prototype = {    doStuff() {},    toString() {},    toValue() {}};

故此,在类的实例上调用方法,实际上就是调用原型上的主意。既然类的主意都以概念在prototype上边,所以类的新措施能够加多在prototype目的方面。Object.assign措施可以很方便地三回向类增添多少个主意。

class Point {    constructor() {        // ...    }}Object.assign(Point.prototype, {    toString() {},    toValue() {}});

其余,类的中间装有定义的章程,都以恒河沙数的(non-enumerable)。

class Point {    constructor(x, y) {        // ...    }    toString() {        return '(' + x + ', ' + y + ')';    }}Object.keys(Point.prototype); // []Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]Object.getOwnPropertyDescriptor(Point, 'toString');// Object {writable: true, enumerable: false, configurable: true}

在构造子类布局函数时内部选拔call或apply来调用父类的布局函数

function Super(){
    this.flag = true;
}
function Sub(){
    Super.call(this)  //如果父类可以需要接收参数,这里也可以直接传递
}
var obj = new Sub();
obj.flag = flase;
var obj_2 = new Sub();
console.log(obj.flag)  //依然是true,不会相互影响
静态方法

static根本字用来定义类的静态方法。静态方法是指那三个无需对类进行实例化,使用类名就可以平昔访谈的章程。静态方法平常用来作为工具函数。

class Point {    constructor(x, y) {        this.x = x;        this.y = y;    }    static distance(a, b) {        const dx = a.x - b.x;        const dy = a.y - b.y;        return Math.sqrt(dx*dx + dy*dy);    }}const p1 = new Point(5, 5);const p2 = new Point(10, 10);console.log(Point.distance(p1, p2));

静态方法不能被实例世袭,是透过类名直接调用的。不过,父类的静态方法能够被子类世襲。

class Foo {  static classMethod() {    return 'hello';  }}class Bar extends Foo {}Bar.classMethod(); // "hello"
function Super(){ this.flag = true; } function Sub(){ Super.call(this) //如果父类可以需要接收参数,这里也可以直接传递 } var obj = new Sub(); obj.flag = flase; var obj_2 = new Sub(); console.log(obj.flag) //依然是true,不会相互影响

结缘借用布局函数和原型链的秘技,能够兑现相比周密的接续方法,可以称之为组合世袭:

extends关键字

extends重在字用于达成类之间的接轨。子类世袭父类,就持续了父类的富有属性和方法。 extends前边只能跟贰个父类。

class ColorPoint extends Point {  constructor(x, y, color) {    super(x, y); // 调用父类的constructor(x, y)    this.color = color;  }  toString() {    return this.color + ' ' + super.toString(); // 调用父类的toString()  }}

extends第一字不可能用于后续一个对象,假诺您想三番五次自一个惯常的指标,你必需运用 Object.setPrototypeof ( 卡塔尔国

利弊:实现了质量的私有化,可是子类不可能访谈父类原型上的特性。

function Super(){
    this.flag = true;
}
Super.prototype.getFlag = function(){
    return this.flag;     //继承方法
}
function Sub(){
    this.subFlag = flase
    Super.call(this)    //继承属性
}
Sub.prototype = new Super;
var obj = new Sub();
Super.prototype.getSubFlag = function(){
    return this.flag;
}
es5 的后续和 es6 的后续

es5中的原型链继承,正是经过将子类布局函数的原型作为父类布局函数的实例(sub.prototype=new super),那样就连通了子类-子类原型-父类;

//先来个父类,带些属性  function Super(){      this.flag = true;  }  //为了提高复用性,方法绑定在父类原型属性上  Super.prototype.getFlag = function(){      return this.flag;  }  //来个子类  function Sub(){      this.subFlag = false;  }  //实现继承  Sub.prototype = new Super;  //给子类添加子类特有的方法,注意顺序要在继承之后  Sub.prototype.getSubFlag = function(){      return this.subFlag;  }  //构造实例  var es5 = new Sub;  

然而这么的原型链世襲有毛病:大家的靶子是布局函数的习性私有化,方法复用化,所以大家把品质放在函数内,把措施放到原型上;不过原型链世襲分明,父类的品质和情势都放到了子类的原型上;

为了化解地点的做法,大家在es5中混杂使用 布局函数call 继承;

function Super(){      this.flag = true;  }  Super.prototype.getFlag = function(){      return this.flag;     //继承方法  }  function Sub(){      this.subFlag = flase      Super.call(this)    //继承属性  }  Sub.prototype = new Super;  var obj = new Sub();  Sub.prototype.constructor = Sub;  Super.prototype.getSubFlag = function(){      return this.flag;  }  

只是还应该有个小标题是,子类.prototype = new 父类,子类.prototype的constructor 就针对了父类,所以大家要重写一下:

Sub.prototype.constructor = Sub;

ES6的存在延续达成格局,其内部其实也是ES5组合世襲的办法,通过call结构函数,在子类中三回九转父类的习性,通过原型链来继承父类的主意。

我们将 extend 用babel 进行转码:

function _inherits(subClass, superClass) {     // 确保superClass为function    if (typeof superClass !== "function" && superClass !== null) {         throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);    }     // 把子类.prototype 继承了父类.prototype(new 父类), 同时把子类prototype的constructor进行了重写;    // 给subClass添加constructor这个属性    subClass.prototype = Object.create(superClass && superClass.prototype, {         constructor: {             value: subClass,             enumerable: false,             writable: true,             configurable: true         }     });    // 将父类设为子类的prototype    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;}

其间 子类. prototype = Object.create ( 父类.prototype 卡塔尔那句话,实际上和上边包车型客车代码相同:

子类.prototype = new 父类

分裂的是, 子类. prototype = Object.create ( 父类.prototype State of Qatar不会继续父类的constructor里面包车型客车品质,只会三回九转父类prototype上的议程;

三个关于 Object.create(car.prototype卡塔尔 用法的代码:

function Car (desc) {    this.desc = desc;    this.color = "red";} Car.prototype = {    getInfo: function() {      return 'A ' + this.color + ' ' + this.desc + '.';    }};//instantiate object using the constructor functionvar car =  Object.create(Car.prototype);car.color = "blue";alert(car.getInfo()); //displays 'A blue undefined.' ??!       // 看见了吧,只会继承方法,不能继承属性

当你只想世袭类的原型,而不想世襲类的constructor的时候,使用Object.create 是很棒的选用;

假设大家想子类继承父类的prototype ,同不经常间子类也要有协调的特性,请看下边包车型大巴代码:

var Car2 = Object.create(null); //this is an empty object, like {}Car2.prototype = {  getInfo: function() {    return 'A ' + this.color + ' ' + this.desc + '.';  }}; var car2 = Object.create(Car2.prototype, {  //value properties  color:   { writable: true,  configurable:true, value: 'red' },  //concrete desc value  rawDesc: { writable: false, configurable:true, value: 'Porsche boxter' },  // data properties (assigned using getters and setters)  desc: {     configurable:true,     get: function ()      { return this.rawDesc.toUpperCase();  },    set: function (value) { this.rawDesc = value.toLowerCase(); }    }}); car2.color = 'blue';alert(car2.getInfo()); //displays 'A RED PORSCHE BOXTER.'

每叁个属性又是一批属性的集结,又称descriptor, 分为 data descriptor 和 accessor(访问 卡塔尔(قطر‎ descriptor

越多的学识:

总来说之,extends做了两件事情,三个是透过Object.create(卡塔尔把子类的原型赋值为父类的实例, 完结了一而再三回九转方法,子类.prototype.__proto__也自动指向父类的原型,三个是手动纠正了子类的__proto__, 改过为指向父类,(本来在es5 中应该是指向Function.prototype);

在子类中必得实施的super()方法,实际上是用call 方法:

 var _this = _possibleConstructorReturn(this, (b.__proto__ || Object.getPrototypeOf(b)).call(this));

父类被当成普通函数来进行,进而将this绑定到子类上;

再正是,extend前边能够跟各个类型的值:

首先种相当情状,子类世袭Object类。

class A extends Object {}A.__proto__ === Object // trueA.prototype.__proto__ === Object.prototype // true

第三种独特情状,不设有任何世襲。

class A {}A.__proto__ === Function.prototype // trueA.prototype.__proto__ === Object.prototype // true

其三种新鲜景况,子类世襲null。

class C extends null {  constructor() { return Object.create(null); }}

3.结合世襲

此地还应该有个小意思,Sub.prototype = new Super; 会招致Sub.prototype的constructor指向Super;
可是constructor的概念是要指向原型属性对应的构造函数的,Sub.prototype是Sub布局函数的原型,所以应该加上一句修改:
Sub.prototype.constructor = Sub;

两条世襲链

二个持续语句同有时候设有两条世襲链:一条落实属性持续,一条实现方式继承.

class A extends B {}A.__proto__ === B;  //继承属性A.prototype.__proto__ === B.prototype;  //继承方法

ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型

其次条世袭链领会起来未有何样难题,es6 自个儿就是对es5 混合方式延续的包裹,在原型世袭上,es6选拔的是 

子类.prototype = Object.create (父类.prototype) // 相当于 new 父类

子类的原型是父类的实例(暂且那样敞亮,其实子类并不能够世袭父类的属性,只可以继续方法),所以子类.prototype(父类的实例)指向父类的prototype。

不过首先个世袭链就倒霉驾驭了,在ES5中子类.__proto__是指向Function.prototype的,因为每一个构造函数其实皆以Function那一个目的组织的。在ES6的后续中,有那般一句话:

 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;

es6 的 extends 把子类的__proto__本着父类能够实现属性的继承,在ES5中在还未用借用世襲的时候是因为父类属性被子类原型世襲,全体的子类实例实际上都是同贰天品质引用。

能够这么说,在ES5连任和构造实例,ES6构造实例的时候能够明白__proto__指向布局函数的原型的,不过在ES6继承中,__proto__指持续自哪个类或原型。也等于说,两条世袭链只存在于多少个类之间的关系,实例与结构函数之间的涉嫌,照旧es5的方式;

var p1 = new Point(2,3);var p2 = new Point(3,2);p1.__proto__ === p2.__proto__

那也代表,能够透过实例的__proto__属性为Class增加措施。

var p1 = new Point(2,3);var p2 = new Point(3,2);p1.__proto__.printName = function () { return 'Oops' };p1.printName() // "Oops"p2.printName() // "Oops"var p3 = new Point(4,2);p3.printName() // "Oops"

只是我们不引入那样做

应用布局函数和原型链的方式,能够相比完备的得以完毕持续

看完ES5的贯彻,再来看看ES6的接续完结方式,其里面其实也是ES5结缘世袭的章程,通过call借用布局函数,在A类布局函数中调用相关属性,再用原型链的总是完成形式的接轨

super 关键字

super重视字能够用来调用其父类的布局器或方法。super 作为艺术的时候,必得在 constructor 中调用,况且不能不在 constructor 里面被调用

class Cat {   constructor(name) {    this.name = name;  }  speak() {    console.log(this.name + ' makes a noise.');  }}class Lion extends Cat {  speak() {    super.speak();    console.log(this.name + ' roars.');  }}

super固然代表了父类A的布局函数,不过回去的是子类B的实例,即super内部的this指的是B,由此super(State of Qatar在这里地一定于A.prototype.constructor.call(this卡塔尔国。

第三种意况,super作为目的时,在平日方法中,指向父类的原型对象,能够调用原型上的章程(不过父类的私妻孥性和措施就调用不到了);在静态方法中,指向父类。

class A {  p() {    return 2;  }}class B extends A {  constructor() {    super();    console.log(super.p()); // 2  }}let b = new B();

ES6 规定,通过super调用父类的法寅时,super会绑定子类的this。

class A {  constructor() {    this.x = 1;  }  print() {    console.log(this.x);  }}class B extends A {  constructor() {    super();    this.x = 2;  }  m() {    super.print();  }}let b = new B();b.m() // 2

通过super对有个别属性赋值,super 的this 指向子类,如若要拜望,super 的 this 就改成了父类的prototype:

class A {  constructor() {    this.x = 1;  }}class B extends A {  constructor() {    super();    this.x = 2;    super.x = 3;    console.log(super.x); // undefined    console.log(this.x); // 3  }}let b = new B();

假设super作为对象,用在静态方法之中,那时super将针对父类,实际不是父类的原型对象。

class Parent {  static myMethod(msg) {    console.log('static', msg);  }  myMethod(msg) {    console.log('instance', msg);  }}class Child extends Parent {  static myMethod(msg) {    super.myMethod(msg);  }  myMethod(msg) {    super.myMethod(msg);  }}Child.myMethod(1); // static 1var child = new Child();child.myMethod(2); // instance 2

在乎,使用super的时候,必须显式钦点是用作函数、依旧作为对象使用,不然会报错。

class A {}class B extends A {  constructor() {    super();    console.log(super); // 报错  }}

 

ES5的存续,实质是先创立子类的实例对象this,然后再将父类的法子增加到this上边(Parent.apply(this卡塔尔)。ES6的三番陆次机制完全差别,实质是先创立父类的实例对象this(所以必需先调用super方法),然后再用子类的构造函数改过this。

唯有调用super之后,才方可接纳this关键字,不然会报错。那是因为子类实例的营造,是基于对父类实例加工,只有super方法本事回去父类实例。

function Super(){ this.flag = true; } Super.prototype.getFlag = function(){ return this.flag; //继承方法 } function Sub(){ this.subFlag = flase Super.call(this) //继承属性 } Sub.prototype = new Super; var obj = new Sub(); Sub.prototype.constructor = Sub; Super.prototype.getSubFlag = function(){ return this.flag; }
class B extends A {
  constructor() {
    return A.call(this);  //继承属性
  }
}
A.prototype = new B;  //继承方法

类的Getter和Setter方法

与ES5等同,在类内部能够利用getset主要字,对有些属性设置取值和赋值方法。

class Foo {    constructor() {}    get prop() {        return 'getter';    }    set prop(val) {        console.log('setter: ' + val);    }}let foo = new Foo();foo.prop = 1;// setter: 1foo.prop;// "getter"

地方代码中,prop质量有对应 的赋值和取值方法,因而赋值和读取行为都被自定义了。 
存值和取值方法是安装在性质的descriptor对象上的。

var descriptor = Object.getOwnPropertyDescriptor(Foo.prototype, 'prop');"get" in descriptor // true"set" in descriptor // true

地方代码中,存值和取值方法是概念在prop属性的陈诉对象上的,那与ES5相仿。

注:

ES6封装了class,extends关键字来落到实处再三再四,内部的贯彻原理其实依然是基于下面所讲的原型链,但是进过生机勃勃层封装后,Javascript的后续得以进一层简明文雅地落到实处

类的Generator方法

倘使类的某部方法名前丰硕星号(*),就意味着这一个主意是三个Generator函数。

class Foo {  constructor(...args) {    this.args = args;  }  * [Symbol.iterator]() {    for (let arg of this.args) {      yield arg;    }  }}for (let x of new Foo('hello', 'world')) {  console.log(x);}// hello// world

地点代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是叁个Generator函数。Symbol.iterator方法重回一个Foo类的默认遍历器,for...of循环会自动调用那个遍历器。

此处还应该有个小意思,Sub.prototype = new Super; 会引致Sub.prototype的constructor指向Super;但是constructor的定义是要指向原型属性对应的布局函数的,Sub.prototype是Sub布局函数的原型,所以应该加上一句校勘:Sub.prototype.constructor = Sub;

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 等同于parent.constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString(); // 等同于parent.toString()
  }
}
new.target属性

ES6为new命令引进了多少个new.target属性,(在布局函数中)重返new命令作用于的极度构造函数。即便布局函数不是因而new命令调用的(举个例子说calll),new.target会重回undefined,因而那几个性格能够用来鲜明布局函数是怎么调用的。

function Person(name) {  if (new.target !== undefined) {    this.name = name;  } else {    throw new Error('必须使用new生成实例');  }}// 另一种写法function Person(name) {  if (new.target === Person) {    this.name = name;  } else {    throw new Error('必须使用new生成实例');  }}var person = new Person('张三'); // 正确var notAPerson = Person.call(person, '张三');  // 报错

Class内部调用new.target,在new 二个实例的时候 ,再次来到当前Class。可是当子类世襲父类时,new.target会重返子类。

class Rectangle {  constructor(length, width) {    console.log(new.target === Rectangle);    // ...  }}class Square extends Rectangle {  constructor(length) {    super(length, length);   // 相当于执行父类中的constructor,  }}var obj = new Square(3); // 输出 false

选拔那几个特点,能够写出不可能独立使用、必需继续后工夫应用的类。

class Shape {  constructor() {    if (new.target === Shape) {      throw new Error('本类不能实例化');      // 抛出一个错误    }  }}class Rectangle extends Shape {  constructor(length, width) {    super();    // ...  }}var x = new Shape();  // 报错var y = new Rectangle(3, 4);  // 正确

null

4.寄生世袭

透过constructor来定义布局函数,用super调用父类的性质方法

快要sub.prototype=new super改为sub.prototype=Object.creat(supper.prototype卡塔尔(قطر‎,幸免了组合世袭中布局函数调用了五回的流弊。

ES6中Class充任了ES5中,布局函数在一而再再而三完结进程中的成效
蓬蓬勃勃致有原型属性prototype,以至在ES5中用来指向构造函数原型的__proto__性情,这些天性在ES6中的指向有局部主动的改换。
二个一而再语句同一时候设有两条世襲链:一条得以实现属性持续,一条完结格局世襲。

class A extends B {}
A.__proto__ === B;  //继承属性
A.prototype.__proto__ === B.prototype;  //继承方法

ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型
其次条世襲链明白起来未有啥难点,对应到ES5中的A.prototype = new B;A.prototype作为B构造的实例,指向布局函数B的原型B.prototype,

但是在ES5中A.__proto__是指向Function.prototype的,因为每贰个布局函数其实都以Function那个指标协会的,ES6中子类的__proto__本着父类能够兑现属性的继续,在ES5中在并未有用借用世襲的时候由于父类属性被子类原型世襲,全体的子类实例实际上都以同叁性情质援引。

在ES6中达成了子类世袭父类属性,在构造实例的时候会一贯得到子类的属性,没有需要查找到原型属性下面去,ES6新的静态方法和静态属性(只好在结构函数上访谈)也是透过那样类的直白接轨来兑现,至于惯常复用方法或然放到原型链上,道理和得以达成和ES5是相同的。

别的作者认为这里改过A.__proto__的针对是有意区分ES6中三翻五次和实例化,相同的时候创造子类和父类直接的关系,ES5的子类的布局函数通过子类的原型与父类的布局函数连接,不设有直接的涉及;

能够那样说,在ES5继续和组织实例,ES6布局实例的时候能够清楚__proto__原型指针是用来指向结构函数的原型的,然而在ES6世袭中,__proto__指继续自哪个类或原型,在A世襲B之后,构造叁个实例 var obj = new A; 会开采它富有的质量指向都以和ES5均等的。

有个风趣的地点:ES6三回九转是在父类创造this对象,在子类constructor中来修饰父类的this,ES5是在子类创立this,将父类的属性方法绑定到子类,由于原生的构造函数(Function,Array等)未有this,子类不可能通过call/apply(thisState of Qatar获得在那之中间属性,所以在ES5不能够继续,ES6达成后得以为原生构造函数装进一些有意思的接口,比方说阮风流罗曼蒂克峰先生的这几个给Array实例封装三个版本记录和回滚的议程:

class VersionedArray extends Array {
  constructor() {
    super();
    this.history = [[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new VersionedArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]
x.push(3);
x // [1, 2, 3]

x.revert();
x // [1, 2]

末段做贰个ES5和ES6的继续小结:
ES5最精华的后续方法是用结合世袭的章程,原型链世袭方法,借用函数继承属性,ES6也是基于这样的艺术,然则封装了更温婉精短的api,让Javascript越来越强盛,改善了部分属性指向,标准了后续的操作,区分开了后续完毕和实例布局,别的ES6无冕还可以达成越多的世袭必要和场景。

文/Daguo(简书笔者)
原稿链接:

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:整合阮一峰教程,Js实现继承的几种方法及其优缺点

相关阅读