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);
但是这么写,和传统的面向对象语言(如Java和C++)差异较大,所以ES6引入Class类这个概念,作为对象的模板。通过关键字class,可以定义类。基本上ES6的class类可以看做一个语法糖,它的绝大多数功能ES5都能实现,只是class的写法让对象原型的写法更加清晰、更像面向对象编程的语法而已。
构造方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。而this关键字则代表实例对象。这里的变量在类初始化的时候进行赋值。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
使用的时候,我们直接对类使用new命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
方法
我们也可以根据需要在类里面定义我们所需的方法。而类的所有方法都定义在类的prototype属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。
class的继承
Class 可以通过extends关键字实现继承。
class Point {
}
class ColorPoint extends Point {
}
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。
在子类中
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
我们通过super关键字,调用父类属性。值得注意的是:子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。
注意:作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
如果属性定义在父类的原型对象上,super就可以取到。
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
ES6 规定,通过super调用父类的方法时,super会绑定子类的this。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print(); //这句话等于console.log(this.x); 这里的this指向B,this.x=2
}
}
let b = new B();
b.m() // 2
由于绑定子类的this,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3; //赋值时,super相当于this 所以这句话可是为 this.x=3
console.log(super.x); // undefined 读取时,super指向父级A.prototype.x 不存在
console.log(this.x); // 3
}
}
let b = new B();
注意,使用super的时候,必须显式指定是作为函数:super()、还是作为对象:super. 使用,否则会报错。
摘自 Class 的基本语法