ECMAScript 2015 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。
定义类
类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。
类声明
定义一个类的一种方法是使用一个类声明。要声明一个类,你可以使用带有class
关键字的类名(这里是“Rectangle”)。
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } }
提升
函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError
:
let p = new Rectangle(); // ReferenceError class Rectangle {}
类表达式
一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或匿名的。赋予一个命名类表达式的名称是类的主体的本地名称。
/* 匿名类 */ let Rectangle = class { constructor(height, width) { this.height = height; this.width = width; } }; /* 命名的类 */ let Rectangle = class Rectangle { constructor(height, width) { this.height = height; this.width = width; } };
注意: 类表达式也同样受到类声明中提到的提升问题的限制。
类体和方法定义
一个类的类体是一对花括号/大括号 {}
中的部分。这是你定义类成员的位置,如方法或构造函数。
严格模式
类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。
构造函数
constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class
创建的对象。一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor
的方法,则将抛出 一个SyntaxError
。
一个构造函数可以使用 super
关键字来调用一个父类的构造函数。
原型方法
参见方法定义。
class Rectangle { // constructor constructor(height, width) { this.height = height; this.width = width; } // Getter get area() { return this.calcArea() } // Method calcArea() { return this.height * this.width; } } const square = new Rectangle(10, 10); console.log(square.area); // 100
静态方法
static
关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
class Point { constructor(x, y) { this.x = x; this.y = y; } static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); } } const p1 = new Point(5, 5); const p2 = new Point(10, 10); console.log(Point.distance(p1, p2));
用原型和静态方法包装
当一个对象调用静态或原型方法时,如果该对象没有“this”值(或“this”作为布尔,字符串,数字,未定义或null) ,那么“this”值在被调用的函数内部将为 undefined
。不会发生自动包装。即使我们以非严格模式编写代码,它的行为也是一样的,因为所有的函数、方法、构造函数、getters或setters都在严格模式下执行。因此如果我们没有指定this的值,this值将为undefined
。
class Animal { speak() { return this; } static eat() { return this; } } let obj = new Animal(); obj.speak(); // Animal {} let speak = obj.speak; speak(); // undefined Animal.eat() // class Animal let eat = Animal.eat; eat(); // undefined
如果我们使用传统的基于函数的类来编写上述代码,那么基于调用该函数的“this”值将发生自动装箱。
function Animal() { } Animal.prototype.speak = function() { return this; } Animal.eat = function() { return this; } let obj = new Animal(); let speak = obj.speak; speak(); // global object let eat = Animal.eat; eat(); // global object
实例属性
实例的属性必须定义在类的方法里:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
静态的或原型的数据属性必须定义在类定义的外面。
Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;
字段声明
公共和私有字段声明是JavaScript标准委员会TC39提出的实验性功能(第3阶段)。浏览器中的支持是有限的,但是可以通过Babel等系统构建后使用此功能。
公有字段声明
使用JavaScript字段声明语法,上面的示例可以写成:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
通过预先声明字段,类定义变得更加自我记录,并且字段始终存在。
正如上面看到的,这个字段可以用也可以不用默认值来声明。
私有字段声明
使用私有字段,可以按以下方式细化定义。
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。通过定义在类外部不可见的内容,可以确保类的用户不会依赖于内部,因为内部可能在不同版本之间发生变化。
私有字段仅能在字段声明中预先定义。
私有字段不能通过在之后赋值来创建它们,这种方式只适用普通属性。
更多信息,请看class fields.
使用 extends
创建子类
extends
关键字在类声明或类表达式中用于创建一个类作为另一个类的一个子类。
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak();// 'Mitzie barks.'
如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
也可以继承传统的基于函数的“类”:
function Animal (name) { this.name = name; } Animal.prototype.speak = function () { console.log(this.name + ' makes a noise.'); } class Dog extends Animal { speak() { super.speak(); console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak();//Mitzie makes a noise. Mitzie barks.
请注意,类不能继承常规(非可构造)对象。如果要继承常规对象,可以改用Object.setPrototypeOf()
:
var Animal = { speak() { console.log(this.name + ' makes a noise.'); } }; class Dog { constructor(name) { this.name = name; } } Object.setPrototypeOf(Dog.prototype, Animal);// If you do not do this you will get a TypeError when you invoke speak var d = new Dog('Mitzie'); d.speak(); // Mitzie makes a noise.
Species
你可能希望在派生数组类 MyArray
中返回 Array
对象。这种 species 方式允许你覆盖默认的构造函数。
例如,当使用像Symbol.species
符号可以让你这样做:
class MyArray extends Array { // Overwrite species to the parent Array constructor static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); console.log(mapped instanceof MyArray); // false 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中实现混合:
var calculatorMixin = Base => class extends Base { calc() { } }; var randomizerMixin = Base => class extends Base { randomize() { } };
使用 mix-ins 的类可以像下面这样写:
class Foo { } class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
规范
Specification | Status | Comment |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) Class definitions |
Standard | Initial definition. |
ECMAScript 2016 (ECMA-262) Class definitions |
Standard | |
ECMAScript 2017 (ECMA-262) Class definitions |
Standard | |
ECMAScript Latest Draft (ECMA-262) Class definitions |
Draft |
浏览器兼容
Desktop | Mobile | Server | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
classes |
Chrome Full support 49
|
Edge Full support 13 | Firefox Full support 45 | IE No support No | Opera Full support 36
|
Safari Full support 9 | WebView Android Full support 49
|
Chrome Android Full support 49
|
Firefox Android Full support 45 | Opera Android Full support 36
|
Safari iOS Full support 9 | Samsung Internet Android Full support 5.0
|
nodejs Full support 6.0.0
|
constructor |
Chrome Full support 49
|
Edge Full support 13 | Firefox Full support 45 | IE No support No | Opera Full support 36 | Safari Full support 9 | WebView Android Full support 49
|
Chrome Android Full support 49
|
Firefox Android Full support 45 | Opera Android ? | Safari iOS Full support 9 | Samsung Internet Android Full support Yes | nodejs Full support 6.0.0
|
extends |
Chrome Full support 49
|
Edge Full support 13 | Firefox Full support 45 | IE No support No | Opera Full support 36 | Safari Full support 9 | WebView Android Full support 49
|
Chrome Android Full support 49
|
Firefox Android Full support 45 | Opera Android ? | Safari iOS Full support 9 | Samsung Internet Android Full support Yes | nodejs Full support 6.0.0
|
Private class fields | Chrome Full support 74 | Edge No support No | Firefox No support No | IE No support No | Opera Full support 62 | Safari No support No | WebView Android Full support 74 | Chrome Android Full support 74 | Firefox Android No support No | Opera Android Full support 53 | Safari iOS No support No | Samsung Internet Android No support No | nodejs Full support 12.0.0 |
Public class fields | Chrome Full support 72 | Edge No support No | Firefox Full support 69 | IE No support No | Opera Full support 60 | Safari No support No | WebView Android Full support 72 | Chrome Android Full support 72 | Firefox Android No support No | Opera Android Full support 51 | Safari iOS No support No | Samsung Internet Android No support No | nodejs Full support 12.0.0 |
static |
Chrome Full support 49
|
Edge Full support 13 | Firefox Full support 45 | IE No support No | Opera Full support 36 | Safari Full support 9 | WebView Android Full support 49
|
Chrome Android Full support 49
|
Firefox Android Full support 45 | Opera Android ? | Safari iOS Full support 9 | Samsung Internet Android Full support Yes | nodejs Full support 6.0.0
|
Static class fields | Chrome Full support 72 | Edge No support No | Firefox No support No
|
IE No support No | Opera Full support 60 | Safari No support No | WebView Android Full support 72 | Chrome Android Full support 72 | Firefox Android No support No | Opera Android Full support 51 | Safari iOS No support No | Samsung Internet Android No support No | nodejs Full support 12.0.0 |
Legend
- Full support
- Full support
- No support
- No support
- Compatibility unknown
- Compatibility unknown
- See implementation notes.
- See implementation notes.
- User must explicitly enable this feature.
- User must explicitly enable this feature.
在Scratchpad中运行
一个类不能被重新定义。如果你正在使用Scratchpad中的代码(Firefox菜单工具> Web Developer> Scratchpad),并且您运行了两次具有相同名称的类的定义,那么你将遇到一个令人困惑的SyntaxError:let <class name>。
要重新运行定义,请使用Scratchpad菜单 执行>重新加载并运行。
请为这个bug投票 #1428672。