JS 语言传统方法通过构造函数定义并生成新对象,ES6 引入了 Class 这个概念作为对象的模板,通过 class 关键字可以定义类。
基本语法
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {return '(' + this.x + ',' + this.y + ')';
}
Point.protortype.doStuff = function () {console.log('stuff');
}
// 等同于
class Point {constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {return '(' + this.x + ',' + this.y + ')';
}
doStuff() {console.log('stuff');
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面的代码表明,类的数据类型就是函数,类本身指向构造函数。类的所有方法都定义在类的 prototype 属性上。在类的实例上调用方法,就是在调用原型上的方法。constructor 方法是类的默认方法,通过 new 命令生成对象实例时自动调用该方法。如果没有显示定义,一个空的 construtor 方法会被默认添加。
类的实例对象
生成实例对象的写法和 ES5 一样,也是使用 new 命令,不同于 ES5,如果忘记加 new 将会报错。
class Point {
}
// 等同于
class Point {constructor() {}}
var b = new Point(2, 3); // 生成 Point 实例
var c = Pint(2, 3) // 报错
Class 表达式
与函数一样,Class 可以使用表达式的形式定义。
const MyClass = class Me {getClassName() {return Me.Name;}
}
需要注意的是这个类的名字是 MyClass,而不是 Me,Me 只在 Class 内部有定义指代当前类。如果 Class 内部没有用到,那么可以省略 Me。
let int = new MyClass();
int.getClassName() // Me
Me.Name // 报错
const MyClass = class {getClassName() {return Me.Name;}
}
# 不存在变量提升
类不存在变量提升,与 ES5 完全不同。
new Foo() // 报错
class Foo {}
# Class 的实例属性
Class 的实例属性可以用等式写入类的定义中。
class MyClass {
myProp = 42;
constructor() {
console.log(this.myProp); // 42
}
}
new.target 属性
ES6 引入了 new.target 属性,(在构造函数中)返回 new 命令所作用的构造函数。如果构造函数不是通过 new 命令调用,那么 new.target 将会返回 undefined,可以利用这个属性确定构造函数是怎么调用的。
function Person(name) {if (new.target !== undefined) {this.name = name;} else {throw new Error('必须使用 new 生成实例')}
/* 另一种写法
if (new.target == Person) {this.name = name;} else {throw new Error('必须使用 new 生成实例')}*/
}
var Person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
Class 内部调用 new.target, 返回当前 Class。
class Rectangle {constructor(length, width) {console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
var obj = new Rectangle(3, 4); // 输出 true
值得注意的是,之类基础父类时 new.target 会返回之类。利用这个特点,可以写出不能独立使用而是必须继承后才能使用的类。
class Shape {constructor() {if (new.target === Shape) {throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {constructor(length, width) {super();
//...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(); // 正确
Class 的继承
Class 可以通过 extends 关键字实现继承,之类必须在 constructor 方法中调用 super 方法,否则新建实例会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象。如果子类没有定义 constructor 方法,那么 constructor 方法(调用 super)会被默认添加。
class Point {/* ... */}
class ColorPoint extends Point {constructor() {// 此处应调用 super();
}
}
let cp = new ColorPoint(); // 报错
另一个注意点是,在子类的构造函数中只有在 调用 super 之后 才可以使用 this 关键字,否则会报错。
class Point {constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {constructor(x, y , color) {
this.color = color; // 报错
super(x, y);
this.color = color; // 正确
}
}
super 关键字
用作函数
super 既可以当作函数使用,也可以当作对象使用。第一种情况,super 作为函数调用时代表父类的构造函数。但是返回的之类的实例,即 super 内部的 this 指向实例。因此 super()相当于 A.prototype.constructor.call(this)。
class A {constructor() {console.log(new.target.name);
}
}
class B extends A {constructor() {super(); // 作为函数时只能在子类的构造函数中
}
}
new A // A
new B // B
作为对象
第二种情况,super 作为对象时在在普通方法中指向父类的原型对象;在静态方法中指向父类。
class A {p() {return 2;}
}
class B extends A {constructor() {super();
console.log(super.p()); // 2 这里相当于 A.prototype.p()}
}
let b = new B();
this 指向
ES6 规定,通过 super 调用父类的方法时,super 会绑定子类的 this。如果通过 super 对某个属性赋值,这时的 super 就是 this,赋值的属性会变成子类实例的属性。
class A {constructor() {this.x = 1;}
print() {console.log(this.x);
}
}
class B extends A {constructor() {super();
this.x = 2;
super.x = 3;
}
m() {super.print();
}
}
let b = new B();
b.m() // 3
ps … 参考资料《ES6 标准入门》(第三版)