Class

Class的基本语法

ES5中,生成实例对象的传统方式是通过构造函数。

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(类)这个概念,作为对象的模板。通过class关键字,可以定义类。就像下面的代码:

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

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

constructor 方法

constuctor方法是类的默认方法,通过new生成对象实例时会自动调用该方法。若类中没有显式定义,一个空的constructor方法会被默认添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

constructor方法默认返回实例对象(即this),也可以指定返回另一个对象。

class Foo {
  constructor() {
    return Object.create(null);
  }
}
new Foo() instanceof Foo// false

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
类必须使用new调用,否则会报错。

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

Foo()   // // TypeError: Class constructor Foo cannot be invoked without 'new'

类的实例对象

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

//定义类
class Point {

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

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

}

var point = new Point(2, 3);

point.toString() // (2, 3)

Class表达式

函数有函数声明和函数表达式,类的创建同样,可以用class声明,也可以用class表达式来创建。
匿名类表达式

let PersonClass = class{
    constructor(name){
        this.name=name;
    }

    sayName(){
        console.log(this.name);
    }
}

具名类表达式

let PersonClass = class PersonClass2{
    constructor(name){
        this.name=name;
    }

    sayName(){
        console.log(this.name);
    }
}
console.log(typeof PersonClass);        // "function"
console.log(typeof PersonClass2);       // "undefined"

需要注意的是,这个类的名字是PersonClass而不是PersonClass2,PersonClass2只在 Class 的内部代码可用,指代当前类。请看下面代码:

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

试图在类的方法内部重写类名,会抛出错误。

class Foo{
    constructor(){
        Foo='bar'
    }
}
new Foo();  //报错

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

Class的静态方法

如果在一个方法前,加上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指的是类,而不是实例。

class Foo {
  static bar () {
    this.baz();
  }
  static baz () {
    console.log('hello');
  }
  baz () {
    console.log('world');
  }
}

Foo.bar() // hello

上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。

父类的静态方法,可以被子类继承。

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

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

new.target

ES6 为new命令引入了一个new.target属性,该属性一般用在在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,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继承

Class可以通过extends关键字实现继承。

class ColorPoint extends Point {
  constructor(x, y, color) {
    //  相当于Point.call(this, x, y);
    // 调用父类的constructor(x, y)
    super(x, y);
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法。因为子类没有自己的this对象,而是继承了父类的this。若不调用super方法,子类就得不到this对象。

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

在子类的构造函数中,只有调用super之后才可以使用this。因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类。

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; // 正确
  }
}

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

Object.getPrototypeOf(ColorPoint) === Point
// true

因此,可以使用这个方法判断,一个类是否继承了另一个类。

super关键字

super级可以当作函数使用,也可以当作对象使用。
super作为函数调用时,代表父类的构造函数。

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

上面代码中,子类B的构造函数之中的super(),代表调用父类A的构造函数。
注意: super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B。

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

class A {}

class B extends A {
  m() {
    super(); // 报错
  }
}

Super作为对象,在普通方法中指向父类的原型对象,在静态方法中指向父类。

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

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将指向父类。

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 1

var child = new Child();
child.myMethod(2); // instance 2

注意,使用super的时候,必须显式指定super是作为函数还是作为对象使用,否则会报错。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super); // 报错
  }
}
原文地址:https://www.cnblogs.com/renzhiwei2017/p/7404891.html