一、简介

JavaScript类实质上是现有的基于原型继承的语法糖。类是一个特殊的函数,与函数声明和函数表达式一样,定义类也有两种方式:类声明和类表达式。

二、类定义

通过class关键字,可以定义类。类体是class后面大括号{}中的内容,在类体中可以定义类成员。类声明和类表达式的主体都执行在严格模式下,例如:构造函数、静态方法、原型方法、getter/setter

1、类声明

class Polygon{
	constructor(height, width){
		this.area = height * width;
	}
}

类声明和函数声明不同,类声明不会提升:

let p = new Rectangle();//Uncaught ReferenceError: Rectangle is not defined

class Rectangle{}

2、类表达式

  • 匿名类
let Foo = class{
	constructor(){}
	bar(){
		return 'Hello World!';
	}
}

console.log(new Foo().bar());//Hello World!
  • 命名类

如果想在类体内部也使用此类,那么就可以使用命名类表达式,但是这个类名只能在类体内部访问。

let Foo = class NamedFoo{
	whoIsThere(){
		console.log(Foo.name);//NamedFoo
		return NamedFoo.name;
	}
}

console.log(new Foo().whoIsThere());//NamedFoo

类表达式与类声明不同,类声明不允许再次声明已经存在的类,但类表达式可以:

let Rectangle = class Rectangle{};
Rectangle = class Rectangle{};

三、方法定义

1、构造函数

构造函数constructor是一个特殊的方法,用于创建和初始化类。一个类中只能有一个名为constructor的方法,在构造函数中也可以使用super关键字来调用父类的构造函数。

2、原型方法

通常定义给对象的方法为原型方法:

var obj = {
	foo: function(){
		
	}
};
//简写
let o = {
	foo(){
		
	}
};

类:

class Rectangle{
	//构造函数
	constructor(height, width){
		this.height = height;
		this.width = width;
	}
	//getter方法
	get area(){
		return this.calcArea();
	}
	//原型方法
	calcArea(){
		return this.height * this.width;
	}
}

console.log(new Rectangle(3, 5).area);//15

3、静态方法

使用static关键字定义的方法为静态方法,调用静态方法时不需要实例化类,但不能通过类的实例调用静态方法。

class Point{

	constructor(x, y){
		this.x = x;
		this.y = y;
	}

	static distance(a, b){
		return Math.hypot(a.x - b.x, a.y - b.y);
	}
}

let p1 = new Point(0, 0);
let p2 = new Point(3, 4);
console.log(Point.distance(p1, p2));//5

console.log(new Point(1, 2).distance(p1, p2));//Uncaught TypeError: (intermediate value).distance is not a function

4、this的自动装箱

在传统的基于函数的类中,调用方法时如果没有this值,则会基于调用该函数的this值做自动装箱。下面调用speak()eat()时的this为全局对象。

function Animal(){

}

Animal.prototype.speak = function(){
	console.log('speak', this);
}
Animal.eat = function(){
	console.log('eat', this);
}

let obj = new Animal();
let speak = obj.speak;
speak();//speak Window

let eat = Animal.eat;
eat();//eat Window

而使用新的方式定义的类中,调用方法时如果没有this值,则为undefined

class Animal{
	speak(){
		console.log('speak', this);
	}
	static eat(){
		console.log('eat', this);
	}
}

let obj = new Animal();
let speak = obj.speak;
speak();//speak undefined

let eat = Animal.eat;
eat();//eat undefined

四、继承

1、extendssuper

使用extends关键字可以定义类的继承关系。如果子类中存在构造函数,需要在使用this之前先调用super()

class Animal{
	constructor(name){
		this.name = name;
	}
	speak(){
		console.log(`${this.name} makes a noise.`);
	}
}

class Dog extends Animal{
	constructor(name, gender){
		super(name);
		this.gender = gender;
	}
	speak(){
		console.log(`${this.name} barks. I's ${this.gender}.`);
	}
}

new Dog('Mitzie', 'male').speak();//Mitzie barks. I's male.

使用extends也可以扩展传统的基于函数的类:

function Animal(name){
	this.name = name;
}
Animal.prototype.speak = function(){
	console.log(this.name + ' makes a noise.');
}

class Cat extends Animal{
	speak(){
		console.log("I'm a cat.");
		super.speak();
	}
}

new Cat('Tom').speak();

输出:

I'm a cat.
Tom makes a noise.

类不能继承普通(不可构造)对象,如果要继承,则须使用Object.setPrototypeOf()

var Animal = {
	speak(){
		console.log(this.name + ' makes a noise.');
	}
};

class Dog{
	constructor(name){
		this.name = name;
	}
}

Object.setPrototypeOf(Dog.prototype, Animal);

new Dog('Mitzie').speak();//Mitzie makes a noise.

2、Mix-in

由于一个类只能继承一个超类,因此可以用一个以超类作为输入且以一个继承此超类的子类作为输出的函数实现混合:

let calculatorMixin = Base => class extends Base{
	calc(){
		console.log('Method calc...');
	}
};
let randomizerMixin = Base => class extends Base{
	randomize(){
		console.log('Method randomize...');
	}
};

class Foo{}
class Bar extends randomizerMixin(calculatorMixin(Foo)){

};

let bar = new Bar();
bar.calc();//Method calc...
bar.randomize();//Method randomize...
附: