Class的基本语法

简介

类的由来

在JS中,生成实例对象的传统方法是通过构造函数的方式,如下:

function Point(x, y) {
	this.x = x;
	this.y = y;
}
Point.prototype.toString = function() {
	return "(" + this.x + ", " + this.y + ")";
};
var p = new Point(2, 3);

在ES6中,引入了Class(类)的概念,和Java的类的语法相似,上面的例子用ES6的class可以改写为:

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
	toString() {
		return "(" + this.x + ", " + this.y + ")";
	}
}
var p = new Point(2, 3);

在class中,有一个constructor()方法,这就是构造方法,this代表实例对象。
在定义方法时,前面不需要加上function,直接写方法名就行。另外,方法和方法之间不要加逗号。

alert(typeof Point);							//function
alert(Point == Point.prototype.constructor);	//true

上面代码说明,类的数据类型就是函数,类本身就指向构造函数。

构造函数的prototype属性,在类中依然存在。在类中的所有方法,实际上都定义在类的prototype属性上。例如,上面的Point类中的constructor()和toString()方法实际上都定义在Point.prototype上。
因此,调用实例上的方法就是调用原型上的方法。

alert(p.constructor === Point.prototype.constructor);	//true

由于类的方法都定义在prototype对象上,因此类的新方法可以直接添加在prototype对象上。Object.assign()方法可以很方便地一次向类中添加多个方法。

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}
Object.assign(Point.prototype, {
	toString() {
		return "(" + this.x + ", " + this.y + ")";
	},
	toValue() {
		...
	}
});

在类中的所有方法都是不可枚举的,这一点与ES5不同。

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
	toString() {
		return "(" + this.x + ", " + this.y + ")";
	}
}
Object.keys(Point.prototype);						//得到对象上可枚举的所有属性
//[]
Object.getOwnPropertyNames(Point.prototype);	//得到对象上的所有属性
//["constructor","toString"]
function Point(x, y) {
	this.x = x;
	this.y = y;
}
Point.prototype.toString = function() {
	return "(" + this.x + ", " + this.y + ")";
};
Object.keys(Point.prototype);
//["toString"]
Object.getOwnPropertyNames(Point.prototype);
//["constructor","toString"]

在ES5中,Point的原型对象上的toString()方法是可枚举的。

constructor方法

constructor()方法是类的默认方法,通过new生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有,JS引擎会自动添加一个空的constructor()方法。

class Point {}
//等同于
class Point {
	constructor() {}
}

constructor()方法默认返回实例对象(即this)。
下面的代码中constructor()方法返回一个新的对象,导致实例对象不是Point类的实例。

class Point {
	constructor() {
		return Object.create(null);
	}
}
alert(new Point() instanceof Point);	//false

类的实例

与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
	toString() {
		return "(" + this.x + ", " + this.y + ")";
	}
}
var p = new Point(2, 3);
alert(p.hasOwnProperty("x"));					//true
alert(p.hasOwnProperty("y"));					//true
alert(p.hasOwnProperty("toString"));				//false
//p.__proto__表示实例对象p的原型对象
alert(p.__proto__.hasOwnProperty("toString"));	//true

与 ES5 一样,类的所有实例共享一个原型对象。

var p1 = new Point(2, 3);
var p2 = new Point(2, 3);
alert(Object.getPrototypeOf(p1) === Object.getPrototypeOf(p2));	//true

取值函数(getter)和存值函数(setter)

与ES5一样,在类的内部可以对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {
	constructor() {}
	get prop() {
		return "getter";
	}
	set prop(value) {
		alert("setter: " + value);
	}
}
var mc = new MyClass();
alert(mc.prop);		//"getter"
mc.prop = 123;  	//"setter: 123"

set和get方法都是设置在属性的描述符(Descriptor)对象上,这与ES5相同。

var descriptor = Object.getOwnPropertyDescriptor(MyClass.prototype, "prop");
alert("get" in descriptor); 		//true
alert("set" in descriptor); 		//true

属性表达式

类的属性名,可以采用表达式。

var methodName = "toString";
class Point {
	[methodName]() {}
}

Class表达式

与函数一样,类也可以使用表达式的形式定义

const myClass = class Me {
	getClassName() {
		return Me.name;
	}
};
let mc = new myClass();
alert(mc.getClassName());		//Me
Me.name;						//UncaughtReferenceError: Me is not defined

如果类的内部没有用到的话,还可以省略Me。

const myClass = class { ... };

采用class表达式,还可以写出立即执行的class。

let person = new class {
	constructor(name) {
		this.name = name;
	}
	sayName() {
		alert(this.name);
	}
}("Tom");
person.sayName();		//"Tom"

注意点

严格模式

在类和模块的内部,默认就是严格模式。

不存在提升

类必须先定义,再使用。

name属性

name属性总是返回class后面的类名。

class Point {}
alert()Point.name); 	//Point
Generator方法

如果某个方法前加上星号(*),就表示该方法是一个Generator函数。

this的指向

???

静态方法

所有在类中定义的方法,实际上都定义在原型对象上,都会被实例继承。在方法前加上static关键字,表示该方法不会被实例继承,只能通过类来调用,这就称为“静态方法”。

class MyClass {
	static sayHello() {
		alert("hello");
	}
}
MyClass.sayHello();		//"hello"
let mc = new MyClass();
mc.sayHello();			//UncaughtTypeError: mc.sayHello is not a function

注意:如果静态方法中包含this,这个this指向的是类,而不是实例。
静态方法和非静态方法还可以重名。

class MyClass {
	static sayHello() {
		this.sayHi();
	}
	static sayHi() {
		alert("Hello");
	}
	sayHell() {
		alert("World");
	}
}
MyClass.sayHello();		//"Hello"

父类的静态方法可以被子类继承。

class Foo {
	static sayHello() {
		alert("Hello");
	}
}
class Bar extends Foo {
}
Bar.sayHello();		//"Hello"

静态方法也可以从super对象上调用。
???

静态属性

静态属性指的是Class本身的属性,而不是定义在实例对象上的属性。

class Foo {}
Foo.prop = 1;
alert(Foo.prop);		// 1

new.target属性

该属性一般用在构造函数中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined。

function Person(name) {
	if(new.target === Person) {
		this.name = name;
	} else {
		throw new Error("必须使用new命令生成实例");
	}
}
var person = new Person("Tom");					//正确
var notAPerson = Person.call(person, "Tom");		//报错

Class内部调用new.target,返回当前Class。

class Person {
	constructor(name) {
		alert(new.target === Person);
		this.name = name;
	}
}
var person = new Person("Tom");		//true