www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

中的区别,class创设对象和历史观格局生成对象的

2020-04-08 15:17 来源:未知

时间: 2019-12-23阅读: 74标签: 区别原文地址:-difference-of-extends-behavior-between-ES5-and-ES6/笔者注:一句话引发的基础知识回炉,基础不扎实,还要什么自行车

导语

class只是语法糖,并没有为js引入一种新的对象继承模式,之前通过原型链一样可以实现class的功能;

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

时间: 2018-02-02阅读: 2376标签: class

最近在看 React 方面的一些文章时,看到了这样一个问题,「为什么每个 class 中都要写 super, super 是做什么的?」, 刚看到这个问题时,直接就想到了继承行为在 javascript 中的表现。后面作者的一句话「super 不可以省略,省略的话会报错」。当时脑海中蹦出来一个念头,这个同学是不是写错了,super 不就是用来完成调用父类构造函数,将父类的实例属性挂在到 this 上吗?为什么不写还会报错?

定义class 

class 就像是特殊的函数,和创建函数一样,有类声明(class declarations),和类表达式( class expressions )两种创建类的方式:

JS语言传统创建对象的方法一般是通过构造函数,来定义生成的,下面是一个使用function生成的例子。(需要了解生成对象的方式,如工厂模式、原型模式等,以及优缺点,请参考文章:JavaScript中创建对象的7种模式)

后来自己亲自写了一个 Demo 尝试了一下,还真是会报错,到底是哪里出了问题,找到了阮老师的教程又打开仔细看了一遍,发现里面还真是有这样一句话:

类声明

使用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 {};    // 如果两个函数表达式重名了,那么不会报错
function Point(x,y){ this.x=x; this.y = y;}Point.prototype.toString = function(){ return '(' this.x ',' this.y ')';}var p= new Point(1,2);

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到this对象。

类表达式

类表达式是定义类的另外一种方式

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

上面的例子在ES6中定义如下:

原来如此,ES6 中this对象的构造方式发生了变化。

类体和方法定义

类的成员需要定义在一对大括号内{},大括号内的代码的大括号本身组成了类体。类成员包括类构造器类方法 (包括静态方法和实例方法)。

类体中的代码都强制在严格模式中执行,即默认”use strict”。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。

class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '('   this.x   ', '   this.y   ')'; }}

ES5 中的继承

构造器(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方法,导致新建实例时报错。

1.类Point中方法之间不用,号隔开,方法不用function进行定义,

// Shape - 父类(superclass)function Shape() { this.x = 0; this.y = 0;}// 父类的方法Shape.prototype.move = function(x, y) { this.x  = x; this.y  = y; console.info('Shape moved.');};// Rectangle - 子类(subclass)function Rectangle() { Shape.call(this); // call super constructor.}// 子类续承父类Rectangle.prototype = Object.create(Shape.prototype);Rectangle.prototype.constructor = Rectangle;var rect = new Rectangle();console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // trueconsole.log('Is rect an instance of Shape?', rect instanceof Shape); // truerect.move(1, 1); // Outputs, 'Shape moved.'
原型方法

定义类的方法时,方法名前面不需要加上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}

构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

如上所示: 展示了一个 ES5 中实现单继承的例子,在《Javascript 高级程序设计》一书中,给这种继承方式定义为「寄生组合式继承」。不管什么形式,什么命名,在 ES5 中实现继承始终就是要坚持一个原则:将实例属性放在构造函数中挂在this上,将一些方法属性挂在原型对象上,子类可共享。上面这种继承方式的关键在于两点:

静态方法

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"
class Point { constructor(){ // ... } toString(){ // ... } toValue(){ // ... }}// 等同于Point.prototype = { toString(){}, toValue(){}};

子类构造函数通过apply或者call的方式运行父类的构造函数,此举将父类的实例属性挂在子类的this对象上以父类的原型对象为基础,与子类的原型对象之间建立原型链关系,使用了Object.create,本质在于Child.prototype.__proto === Parent.prototype;ES6 中的继承

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

2.类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)

class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '('   this.x   ', '   this.y   ')'; }}class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color   ' '   super.toString(); }}
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 )不会继承父类的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.每一个类中都有一个constructor方法该方法返回实例对象

ES6 中的继承使用到了 extends 关键字,function 也变成了 class 关键字。class 的本质还是一个语法糖,这个大家都会脱口而出,但是在继承机制这里到底是如何做到的,我们看一下 babel 在此处是如何帮我们转译的,

两条继承链

一个继承语句同时存在两条继承链:一条实现属性继承,一条实现方法继承.

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"

但是我们不推荐这样做

4.类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

var ColorPoint =/*#__PURE__*/function (_Point) { _inherits(ColorPoint, _Point); function ColorPoint(x, y, color) { var _this; _classCallCheck(this, ColorPoint); _this = _possibleConstructorReturn(this, _getPrototypeOf(ColorPoint).call(this, x, y)); // 调用父类的constructor(x, y) _this.color = color; return _this; } _createClass(ColorPoint, [{ key: "toString", value: function toString() { return this.color   ' '   _get(_getPrototypeOf(ColorPoint.prototype), "toString", this).call(this); } }]); return ColorPoint;}(Point);
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()在这里相当于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方法才能返回父类实例。

用类进行实例和用普通的构造函数进行实例:

如上是经过babel转译后的代码,有几个关键点:

类的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一致。

1、用类进行实例的必须使用new否则就会报错

一、 _inherits()

类的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

Web前端,上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。

2、与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

function _inherits(subClass, superClass) { if (typeof superClass !== "function"  superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass  superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass);}
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

3、与ES5一样,类的所有实例共享一个原型对象。

首先完成extends对象的校验,必须是function 或者null,否则报错。其次完成以下事情:

4、Class不存在变量提升(hoist),这一点与ES5完全不同

ColorPoint.__proto__ === Point;ColorPoint.prototype.__proto__ === Point.prototype;
new Foo(); // ReferenceErrorclass Foo {}

二、 ColorPoint 构造函数中 _classCallCheck(), _possibleConstructorReturn()

上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); }}
{ let Foo = class {}; class Bar extends Foo { }}

主要是用来检测构造函数不能直接调用,必须是通过new的方式来调用。

上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。

function _possibleConstructorReturn(self, call) { if (call  (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self);}

Class的继承class中的继承使用extend1、子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。2、ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。3、

调用父类的构造函数,初始化一些实例属性,并将this返回。使用该返回的this赋值给子类的this对象,子类通过这一步返回的this对象,再该基础之上在添加一些实例属性。

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

这就是最大的不同之处。如果不经历这一步,子类没有this对象,一旦操作一个不存在的this对象就会报错。

class Point { constructor(x, y) { this.x = x; this.y = y; }}class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正确 }}

三、 _createClass()

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor;}

super.print.call(this)

最后一步完成原型属性与静态属性的挂载,如果是原型属性,挂在在Constructor上的prototype上,如果是静态属性或者静态方法,则挂在Constuctor 上。

类的prototype属性和__proto__属性

大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class A {}class B extends A {}B.__proto__ === A // trueB.prototype.__proto__ === A.prototype // true

上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。

这样的结果是因为,类的继承是按照下面的模式实现的。

实例的__proto__属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

原生构造函数的继承

原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript的原生构造函数大致有下面这些。

Boolean()Number()String()Array()Date()Function()RegExp()Error()Object()

以前,这些原生构造函数是无法继承的,比如,不能自己定义一个Array的子类。

function MyArray() { Array.apply(this, arguments);}MyArray.prototype = Object.create(Array.prototype, { constructor: { value: MyArray, writable: true, configurable: true, enumerable: true }});

上面代码定义了一个继承Array的MyArray类。但是,这个类的行为与Array完全不一致。

var colors = new MyArray();colors[0] = "red";colors.length // 0colors.length = 0;colors[0] // "red"

之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。

ES5是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。

ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。

class MyArray extends Array { constructor(...args) { super(...args); }}var arr = new MyArray();arr[0] = 12;arr.length // 1arr.length = 0;arr[0] // undefined

上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。

上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于Web前端,转载请注明出处:中的区别,class创设对象和历史观格局生成对象的