ES6 class类的概念

概述
JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。

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

基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。

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

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法。

Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

    (1)在 class 类上添加的属性都是在原型 prototype 上添加的

(2)new 实例的时候其实就是调用构造函数这个方法

(3)类的本质其实就是一个函数

(4)类中的this 指向实例对象

(5)添加的私有属性都在构造函数中添加

(6)每个构造方法都会默认返回实例对象this,如果人为改变 return 返回值,返回基本数据类型 字符串、数字、布尔等,不会改变return this 的值;如果返回应引用数据类型 对象 数组,那么return this 就会失效,返回你返回的结果

     (7) 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例

       (8)父类的静态方法,可以被子类继承。可以直接调用 。也是可以从super对象上调用的。
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'


class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"
     (8)Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class Animate {
    constructor() {
        // 默认返回实例对象 this
    }
}
class Dog extends Animate {
    constructor() {
        super()
    }
}
  子类必须在 constructor 方法中调用super方法,否则新建实例时会报错,子类就得不到 this 对象。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工。如果不调用 super 方法,子类就得不到this对象。

ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this上面(所以必须先调用super方法),然后再用子类的构造函数修改 this。

class Animate {
    constructor(x, y) {
        // 默认返回实例对象 this
    }
}
class Dog extends Animate {
    constructor(x, y, z) {
        this.z = z    //  ReferenceError
        super(x, y)   //  this 只能在super() 方法调用之后再使用
        this.z = z 
    }
}

如果在子类中也写入 num 方法,和父类中的方法重名,这样就会覆盖父类的 num 方法

class Animate {
    constructor() {
        // 默认返回实例对象 this
    }
    num() {
        console.log('我是父类的num方法')
    }
}

class Dog extends Animate {
    constructor() {
        super()
    }
    num() {
        console.log('我是子类的num方法')
    }
}
var dog = new Dog()
dog.num()     //  我是子类的num方法

如果不想覆盖而是想引用父类的 num 方法,那么就在子类的 num 方法中通过 super 来调用父类的 num 方法,super.num()

      (9)super 关键字详细解读

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

1.第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

class A {}

class B extends A {
  constructor() {
    super();
  }
}
···
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。

作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。(就是只能写在继承类的constructor里面)

2.第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

```javascript
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();
···
上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。

这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。