ES6的Class

ES6 引入了一个类的概念,其实类是个特殊的函数,可以看做是一个语法糖,它的大部分功能,ES5都能做到。新的 class 写法,只是让对象原型的写法更加清晰、更像面对想象编程的语法而已。

Class

类声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ES5
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return `(${this.x},${this.y})`;
}
// ES6 Class
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return `(${this.x},${this.y})`;
}
}

函数声明和类声明之间的一个重要区别是 函数声明会提升,类声明不会。需要先声明类,才能访问它。

在类的实例上调用方法,其实就是调用原型上的方法。

1
2
3
class B {}
let b = new Class B();
b.constructor === B.prototype.constructor;

b是B类的实例,它的 constructor 方法就是B类原型的 constructor 方法。
类内部的方法,是不可枚举的

constructor 方法

constructor 方法是类的默认方法,通过 new 命令生成实例对象时,自动调用该方法。一个类必须有 constructor 方法,如果没有显示定义,一个空的 constructor 方法会被默认添加。

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

1
2
3
4
5
6
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo;// false

**类必须使用 new 调用,否则会报错,这也是它与普通函数的一个主要区别,后者不用 new 也可以执行。

类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
point.hasOwnProperty('x');// true
point.hasOwnProperty('y');// true
point.hasOwnProperty('toString');// false
point.__proto__.hasOwnProperty('toString');// true

x 和 y 都是实例对象 point 自身的属性(因为定义在 this 上),所以 hasOwnProperty 方法返回 true,而 toString是原型对象的属性(因为定义在 point 类上),所以 hasOwnProperty 方法返回 false。

1
2
3
var p1 = new Point(2, 3);
var p2 = new Point(3, 2);
p1.__prototype === p2.__prototype__;// true

Class表达式

1
2
3
4
5
const MyClass = class Me {
getClassName() {
return Me.name;
}
}

上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。

静态方法

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

1
2
3
4
5
6
7
8
9
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod(); // 'hello'

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

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

1
2
3
4
5
6
7
8
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {}
Bar.classMethod();// 'hello'
}

静态方法也可以从 super 对象上调用。

1
2
3
4
5
6
7
8
9
10
11
12
class Foo {
static classMethod() {
return 'hello';
}
}

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

实例属性的新写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}

// 另一种写法

class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}

静态属性

静态属性指的是 Class 本身属性,即 Class.propName, 而不是定义在实例对象(this) 上的属性。

1
2
3
class Foo {}
Foo.prop = 1;
Foo.prop;// 1

Class 的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
this.color = color;
}
toString() {
return this.color + ' ' + super.toString();
}
}

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

1
2
3
4
5
6
7
8
9
class ColorPoint extends Point {

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

在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。

1
2
3
let cp = new ColorPoint(25, 8, 'red');
cp instanceof ColorPoint;// true
cp instanceof Point;// true

父类的静态方法,也会被子类继承。

Object.gerPrototypeOf()

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

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

super

super 关键字既可以当函数使用,也可以当做对象使用。

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

1
2
3
4
5
6
class A {}
class B extends A {
constructor() {
super();
}
}

子类B的构造函数之中的 super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

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

1
2
3
4
5
6
7
8
9
10
11
12
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A();
new B();

new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

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

1
2
3
4
5
6
7
8
9
10
11
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p());
}
}

子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。

这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.myMythod(2);// instance 2

super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m();// 3

静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。

类的 prototype 和 proto

  • 子类的 proto 属性,表示构造函数的继承,总是指向父类。
  • 子类的 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。
1
2
3
4
5
class A {}
class B extends A {}

B.__proto__ === A;
B.prototype.__proto__ === A.prototype;

之所以会产生这样的结果是因为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A {}
class B {}
// B的实例继承 A的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B继承A的静态属性
Object.setPrototypeOf(B, A);

const b = new B();

// Object.setPrototypeOf 实现
Object.setPrototypeOf = function(obj, proto) {
obj.__proto__ = proto;
return obj;
}
// 因此上述过程为
Object.setPrototypeOf(B.prototype, A.prototype);
//等同于
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;