JavaScript 语言中,生成实例对象的传统方法是通过构造函数,ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,原型继承实际上在底层被隐藏起来了,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
构造方法
构造函数 constructor 是用于创建和初始化类中创建的一个对象的一种特殊方法。
语法: constructor([arguments]) { ... }
-
在一个类中只能有一个名为 “constructor” 的特殊方法。 一个类中出现多次构造函数 (constructor)方法将会抛出一个 SyntaxError 错误。
-
如果没有显式指定构造方法,则会添加默认的 constructor 方法。
为了证明类没有任何特别之处,我们来看看下面的代码:
//ES5
function Plane(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
// methods "inherited" by all instances
Plane.prototype.startEngines = function () {
console.log('starting engines...');
this.enginesActive = true;
};
const richardsPlane = new Plane(1);
richardsPlane.startEngines();
const jamesPlane = new Plane(4);
jamesPlane.startEngines();
//ES6
class Plane {
constructor(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
startEngines() {
console.log('starting engines…');
this.enginesActive = true;
}
}
typeof Plane; // function
Class 的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。
class Foo {
static bar () {
this.baz();
}
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // hello
父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
extends继承
extends关键字用来创建一个普通类或者内建对象的子类。
基本使用
class Tree {
constructor(size = '10', leaves = {spring: 'green', summer: 'green', fall: 'orange', winter: null}) {
this.size = size;
this.leaves = leaves;
this.leafColor = null;
}
changeSeason(season) {
this.leafColor = this.leaves[season];
if (season === 'spring') {
this.size += 1;
}
}
}
class Maple extends Tree {
constructor(syrupQty = 15, size, leaves) {
super(size, leaves);
this.syrupQty = syrupQty;
}
changeSeason(season) {
super.changeSeason(season);
if (season === 'spring') {
this.syrupQty += 1;
}
}
gatherSyrup() {
this.syrupQty -= 3;
}
}
const myMaple = new Maple(15, 5);
myMaple.changeSeason('fall');
myMaple.gatherSyrup();
myMaple.changeSeason('spring');
Tree 和 Maple 都是 JavaScript 类。Maple 类是 Tree 的子类,并使用关键字 extends 将自己设为子类。要让子类可以访问到父类,需要使用关键字 super。注意到 super 有两种使用方式吗?在 Maple 的构造方法中,super 被用作函数。在 Maple 的changeSeason() 方法中,super 被用作对象!
注意:在子类构造函数中,在使用 this 之前,必须先调用super。
子类的constructor方法中必须要调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
Object.getPrototypeOf方法可以用来从子类上获取父类。
使用 extends与内置对象
通过extends还可以继承内置对象,下面的例子中继承了内置的Date对象:
class myDate extends Date {
constructor() {
super();
}
getFormattedDate() {
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear();
}
}
super
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数,在前面的例子中,ES6要求,子类的构造函数必须执行一次super函数。作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。