- 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);
上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
基本上,ES6 的 class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的 class
改写,就是下面这样。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}}
上面代码定义了一个“类”,可以看到里面有一个 constructor
方法,这就是构造方法,而 this
关键字则代表实例对象。也就是说,ES5 的构造函数 Point
,对应 ES6 的 Point
类的构造方法
oint
类除了构造方法,还定义了一个 toString
方法。注意,定义“类”的方法的时候,前面不需要加上 function
这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法
class Point {
// ...
}
typeof Point // "function"
上面代码表明,类的数据类型就是函数,类本身就指向构造函数
类的属性名,可以采用表达式。
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}}
上面代码中, Square
类的方法名 getArea
,是从表达式得到的。
类内部,默认就是严格模式,所以不需要使用 use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
constructor 方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {}
// 等同于
class Point {
constructor() {}}
上面代码中,定义了一个空的类 Point
,JavaScript 引擎会自动为它添加一个空的 constructor
方法。
constructor
方法默认返回实例对象(即 this
)
类必须使用 new
调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new
也可以执行。
class Foo {
constructor() {
return Object.create(null);
}}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
生成类的实例对象的写法,与 ES5 完全一样,也是使用 new
命令。前面说过,如果忘记加上 new
,像函数那样调用 Class
,将会报错。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new Foo(); // ReferenceError
class Foo {}
- 类的继承
Class 可以通过extends
关键字实现继承 这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point {}
class ColorPoint extends Point {}
上面代码定义了一个 ColorPoint
类,该类通过 extends
关键字,继承了 Point
类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 Point
类。下面,我们在 ColorPoint
内部加上代码。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}}
上面代码中, constructor
方法和 toString
方法之中,都出现了 super
关键字,它在这里表示父类的构造函数,用来新建父类的 this
对象。
ES6 要求,子类的构造函数必须执行一次 super
函数
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}}
let cp = new ColorPoint(); // ReferenceError
上面代码中, ColorPoint
继承了父类 Point
,但是它的构造函数没有调用 super
方法,导致新建实例时报错。
需要注意的地方是,在子类的构造函数中,只有调用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; // 正确
}}
上面代码中,子类的 constructor
方法没有调用 super
之前,就使用 this
关键字,结果报错,而放在 super
方法之后就是正确的。