Class
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
name
属性
class Point {}
Point.name // "Point"
2
const MyClass = class Me {
};
MyClass.name // "Me"
2
3
name
属性总是返回紧跟在class
关键字后面的类名。
constructor
方法
- 类必须有
constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加 constructor
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象- 类必须使用
new
调用,否则会报错
class Point {
}
// 等同于
class Point {
constructor() {}
}
2
3
4
5
6
7
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
2
3
4
5
6
7
8
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
2
3
4
5
6
7
8
Class 表达式
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
2
3
4
5
6
7
8
9
这个类的名字是MyClass
而不是Me
,Me
只在 Class 的内部代码可用,指代当前类。
// 如果类的内部没用到的话,可以省略 Me,也就是可以写成下面的形式
const MyClass = class { /* ... */ };
2
立即执行的 Class
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
2
3
4
5
6
7
8
9
10
11
方法
- 类的所有方法都是定义在类的
prototype
属性上面 - 类的内部所有定义的方法,都是不可枚举的(non-enumerable)
- 类的属性名,可以采用表达式
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
2
3
4
5
6
7
8
9
10
11
12
13
14
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
2
3
4
5
6
7
8
9
10
11
getter
/setter
定义在 Class 里的getter
/setter
方法,实际上定义在类的原型上的,通过属性的 Descriptor 对象可以获取到。
class CustomHTMLElement {
constructor(element) {
this.element = element;
}
get html() {
return this.element.innerHTML;
}
set html(value) {
this.element.innerHTML = value;
}
}
var descriptor = Object.getOwnPropertyDescriptor(
CustomHTMLElement.prototype, "html"
);
"get" in descriptor // true
"set" in descriptor // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
静态属性
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
2
3
4
5
只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。
目前有一个静态属性的提案open in new window,对实例属性和静态属性都规定了新的写法。
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 如果静态方法包含
this
关键字,这个this
指的是类,而不是实例 - 静态方法可以与非静态方法重名
- 父类的静态方法,可以被子类继承,也可以从
super
对象上调用
class Foo {
static bar () {
this.baz();
}
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // hello
2
3
4
5
6
7
8
9
10
11
12
13
class Foo {
static classMethodHello() {
return 'hello';
}
static classMethodWorld() {
return 'world';
}
}
class Bar extends Foo {
static classMethodHello() {
return super.classMethodHello() + ', from Bar';
}
}
Bar.classMethodHello() // 'hello, from Bar'
Bar.classMethodWorld() // 'world'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
不存在变量提升
// 类不存在变量提升(hoist),这一点与 ES5 完全不同
new Foo(); // ReferenceError
class Foo {}
2
3
new.target 属性
new
是从构造函数生成实例对象的命令。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, '张三'); // 报错
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- Class 内部调用
new.target
,返回当前 Class - 子类继承父类时,
new.target
会返回子类
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
var obj = new Rectangle(3, 4); // 输出 true
var obj = new Square(3); // 输出 false
2
3
4
5
6
7
8
9
10
11
12
13
14
Class 的 Generator 方法
详见Class 的 Generator 方法open in new window
私有方法
ES6 不提供私有方法,只能通过变通方法open in new window模拟实现。
私有属性
ES6 不支持私有属性,但目前有提案open in new window
Class 的继承
constructor
- 子类必须在
constructor
方法中调用super
方法,否则新建实例时会报错- 子类若没有定义
constructor
方法,constructor
方法会被默认添加
- 子类若没有定义
- 在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字,否则会报错- 调用
super
之前,子类还没有自己的实例对象,调用super
之后,基于父类的this
对象创建出子类的实例
- 调用
- ES 5 继承实质
- 先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)
- 先创造子类的实例对象
- ES 6 继承实质
- 先创造父类的实例对象
this
(所以必须先调用super
方法),然后再用子类的构造函数修改this
- 先创造父类的实例对象
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
2
3
4
5
6
7
8
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
2
3
4
5
6
7
8
9
静态方法的继承
父类的静态方法,也会被子类继承。
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
B.hello() // hello world
2
3
4
5
6
7
8
9
10
Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint) === Point // true
// 注意 __proto__ 不是标准属性
ColorPoint.__proto__ === Point // true
2
3
4
super
关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
当作函数使用
super
作为函数调用时,代表父类的构造函数super()
只能用在子类的构造函数之中,用在其他地方就会报错
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
2
3
4
5
6
7
8
9
10
11
12
super
虽然代表了父类A
的构造函数,但是返回的是子类B
的实例,即super
内部的this
指的是B
,因此super()
在这里相当于A.prototype.constructor.call(this)
。
当作对象使用
super
作为对象时,
- 在普通方法中,指向父类的原型对象
- 通过
super
调用父类的方法时,方法内部的this
指向子类 - 通过
super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性
- 通过
- 在静态方法中,指向父类
// 普通方法里
class A {
constructor() {
this.p = 3;
this.x = 4;
}
getNum() {
return 2;
}
getP() {
console.log(this.p);
}
}
A.prototype.proX = 1;
class B extends A {
constructor() {
super();
// 普通方法中:
// super.getNum() 就相当于 A.prototype.getNum()
// super.proX 相当于 A.prototype.proX
console.log(super.getNum()); // 2
console.log(super.proX) // 1
this.p = 33;
// 通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性
// super.x 读取的是 A.prototype.x,因此是 undefined
this.x = 44;
super.x = 444;
console.log(super.x) // undefined
console.log(this.x) // 444
}
getP() {
super.getP()
}
}
let b = new B();
b.getP() // 33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 静态方法里
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
其中,super
关键字还可以用在普通对象的方法之中,表示原型对象,详见“对象”一节。
super
类型
显式指定使用super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
class A {}
class B extends A {
constructor() {
super();
console.log(super); // 报错
}
}
2
3
4
5
6
7
8
class A {}
class B extends A {
constructor() {
super();
console.log(super.valueOf() instanceof B); // true
}
}
let b = new B();
2
3
4
5
6
7
8
9
10
上面代码中,super.valueOf()
表明super
是一个对象,因此就不会报错。同时,由于super
使得this
指向B
,所以super.valueOf()
返回的是一个B
的实例。
super
任意对象里使用最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super
关键字。
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
2
3
4
5
6
7
prototype
属性和__proto__
属性
大多数浏览器的 ES5 实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性。Class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。
子类的
__proto__
属性,表示构造函数的继承,总是指向父类。子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
2
3
4
5
6
7
8
这样的结果是因为,类的继承是按照下面的模式实现的。
class A {
}
class B {
}
// B 的实例继承 A 的实例(用于方法、属性的继承)
Object.setPrototypeOf(B.prototype, A.prototype);
// B 的实例继承 A 的静态属性(用于静态方法、属性的继承)
Object.setPrototypeOf(B, A);
const b = new B();
2
3
4
5
6
7
8
9
10
11
12
13
这两条继承链,可以这样理解:
- 作为一个对象,子类的原型(
__proto__
属性)是父类; - 作为一个构造函数,子类的原型对象(
prototype
属性)是父类的原型对象(prototype
属性)的实例。
原生构造函数的继承
ES5 无法继承原生的构造函数,ES 6 可以继承原生的构造函数。