在 ECMAScript 6 引入的 JavaScript 类(class)是 JavaScript 现有的原型继承的语法糖。 类并不是 JavaScript 里加入的新的面向对象的继承模型。JavaScript 中的类只是能让我们用更简洁明了的语法创建对象及处理相关的继承。
定义类
类实际上是个“特殊的函数”,而且正如函数的定义方式有函数声明和函数表达式两种一样,类的定义方式也有两种,分别是:类声明和类表达式。
类声明
类声明是定义类的一种方式,就像下面这样,使用 class
关键字后跟一个类名(这里是 Polygon),就可以定义一个类。
1 class Polygon { 2 constructor(height, width) { 3 this.height = height; 4 this.width = width; 5 } 6 }
变量提升
类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会。也就是说,你必须先声明类,然后才能使用它,否则代码会抛出 ReferenceError
异常,像下面这样:
1 var p = new Polygon(); // ReferenceError 2 3 class Polygon {}
类表达式
类表达式是定义类的另外一种方式。在类表达式中,类名是可有可无的。如果定义了类名,则该类名只有在类体内部才能访问到。
1 // 匿名的 2 var Polygon = class { 3 constructor(height, width) { 4 this.height = height; 5 this.width = width; 6 } 7 }; 8 9 // 命名的 10 var Polygon = class Polygon { 11 constructor(height, width) { 12 this.height = height; 13 this.width = width; 14 } 15 };
注意: 类表达式和类声明一样也不会有提升的现象。
类体和方法定义
类的成员需要定义在一对花括号 {}
里,花括号里的代码和花括号本身组成了类体。类成员包括类构造器和类方法(包括静态方法和实例方法)。
严格模式
类体中的代码都强制在严格模式中执行。
构造器
构造器方法是一个特殊的类方法,其用于创建和初始化对象(用该类生成的)。一个类只能拥有一个名为constructor
的方法,否则会抛出 SyntaxError
异常。
在子类的构造器中可以使用 super
关键字调用父类的构造器。
原型方法
参见方法定义。
1 class Polygon { 2 constructor(height, width) { 3 this.height = height; 4 this.width = width; 5 } 6 7 get area() { 8 return this.calcArea() 9 } 10 11 calcArea() { 12 return this.height * this.width; 13 } 14 } 15 const square = new Polygon(10, 10); 16 17 // 100 18 console.log(square.area);
静态方法
static
关键字用来定义类的静态方法。静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法,需要注意的是静态方法不能被实例化的对象调用。静态方法经常用来作为工具函数。
1 class Point { 2 constructor(x, y) { 3 this.x = x; 4 this.y = y; 5 } 6 7 static distance(a, b) { 8 const dx = a.x - b.x; 9 const dy = a.y - b.y; 10 11 return Math.sqrt(dx*dx + dy*dy); 12 } 13 } 14 15 const p1 = new Point(5, 5); 16 const p2 = new Point(10, 10); 17 18 console.log(Point.distance(p1, p2));
使用 extends
创建子类
extends
关键字可以用在类声明或者类表达式中来创建一个继承了某个类的子类。
1 class Animal { 2 constructor(name) { 3 this.name = name; 4 } 5 6 speak() { 7 console.log(this.name + ' makes a noise.'); 8 } 9 } 10 11 class Dog extends Animal { 12 speak() { 13 console.log(this.name + ' barks.'); 14 } 15 } 16 17 var d = new Dog('Mitzie'); 18 // 'Mitzie barks.' 19 d.speak();
同样也可以用于原有的原型继承的“类”:
1 function Animal (name) { 2 this.name = name; 3 } 4 Animal.prototype.speak = function () { 5 console.log(this.name + ' makes a noise.'); 6 } 7 8 class Dog extends Animal { 9 speak() { 10 super.speak(); 11 console.log(this.name + ' barks.'); 12 } 13 } 14 15 var d = new Dog('Mitzie'); 16 d.speak();
需要注意的是类不能继承一般(非构造的)对象。如果你想要创建的类继承某个一般对象的话,你要使用 Object.setPrototypeOf()
:
1 var Animal = { 2 speak() { 3 console.log(this.name + ' makes a noise.'); 4 } 5 }; 6 7 class Dog { 8 constructor(name) { 9 this.name = name; 10 } 11 speak() { 12 super.speak(); 13 console.log(this.name + ' barks.'); 14 } 15 } 16 Object.setPrototypeOf(Dog.prototype, Animal); 17 18 var d = new Dog('Mitzie'); 19 d.speak();
Species
你可能想要数组类 MyArray
返回的是 Array
对象。这个 species 模式能让你重写默认的构造器。
例如,当使用像 map()
这样的方法来返回默认的构造器时,你想要这个方法返回父级的 Array
对象,而不是MyArray 对象。
Symbol.species
能实现:
1 class MyArray extends Array { 2 // Overwrite species to the parent Array constructor 3 static get [Symbol.species]() { return Array; } 4 } 5 var a = new MyArray(1,2,3); 6 var mapped = a.map(x => x * x); 7 8 console.log(mapped instanceof MyArray); // false 9 console.log(mapped instanceof Array); // true
使用 super
引用父类
super
关键字可以用来调用其父类的构造器或者类方法
class Cat { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Lion extends Cat { speak() { super.speak(); console.log(this.name + ' roars.'); } }
Mix-ins
抽象子类或者 mix-ins 是类的模板。 一个 ECMAScript 类只能仅有一个父类,所以想要从工具类来多重继承的行为是不可能的。子类继承的只能是父类提供的功能性。
在 ECMAScript 里一个将父类作为输入且将其子类作为输出的函数可以用来实现 mix-ins:
1 var calculatorMixin = Base => class extends Base { 2 calc() { } 3 }; 4 5 var randomizerMixin = Base => class extends Base { 6 randomize() { } 7 };
使用 mix-ins 的类可以像下面这样写:
1 class Foo { } 2 class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
相关:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes