对于学过Java的同学,类的概念想必是十分熟悉了,但是对于一些刚刚接触JavaScript并且没有过面向对象语言学习的同学,类可能是一个较为陌生的概念,这里我们先做一些基础的介绍。

  • class:它定义了一件事物的抽象特点,这里面就包括它的属性和方法。在下面的例子里,我就定义了一个类,名为Point,其包含的属性即它的横纵坐标xy,我也为它定义了一个方法名为toString,作用是返回它的坐标。
  • 对象object:是类的实例,通过new关键字创建。下面的例子中,p即是我创建的实例对象。

了解了基础之后,让我们来实际上手看看代码。

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);
console.log(p.toString());
//(1,2)

ES6中可以使用类class去进行构造。

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
	toString() {
		return '(' + this.x + ',' + this.y + ')';
	}
}
var p = new Point(1, 2);
console.log(p.toString());
//(1,2)

可以看到,二者直观上的区别体现在“类”写法上的不同,在使用上没有区别。

基本上ES6class可以看作只是一个语法糖,它的绝大部分功能ES5都可以做到。

我们注意到,在定义一个class的时候,我们在里面使用了一个constructor函数,这是一个构造函数。在通过new生成新的实例时,会自动调用这个构造函数。

提到类,就不得不提到其一个非常重要的特性------继承。

  • 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些新的、更具体的特性。

JavaScript中,使用extends关键字实现继承,在子类中使用super关键字来调用父类的构造函数和方法。

class A {
	constructor(name) {
		this.name = name;
	}
}
class B extends A {
	constructor(name, age) {
		super(name);
		this.age = age;
	}
}

constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于 A.prototype.constructor.call(this, props)

取值函数getter和存值函数setter,在类的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,从而改变该属性的赋值和读取行为。

class Point {
	constructor() {
		this.x = x;
		this.y = y;
	}
	set x(value) {
		console.log('new x =', value);
	}
	get x(){
		return 'x =' + this.x;
	}
}
var p = new Point(1, 2);
p.x = 2;
//new x = 2
console.log(p.x);
//x = 1;

静态方法static,类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,只能通过类来直接调用。也就是说,该方法只能由父类进行调用,而子类则不可以进行调用。

class Point {
	constructor() {
		this.x = x;
		this.y = y;
	}
	static toString() {
		return '(' + this.x + ', ' + this.y + ')';
	}
}
var p = new Point(1, 2);
console.log(p.toString());
// [object Object]
console.log(Point.toString());
// (undefined,undefined)

static创建的方法在创建类的时候就存在于内存中,只有当程序结束时才会销毁,这样的做就不需要在每一个实例化中反复创建该方法,可以节约内存。

如果你在此之前有接触过JavaScript,你就会知道在JavaScript中并没有如其它语言那般的publicprivate等修饰符,而这些内容却在TypeScript中实现了。

TypeScript中,每一个成员默认都是public的,另外两个修饰符分别是privateprotected

  • public:修饰的属性或方法是公有的,可以在任何地方被访问到;
  • private:修饰的属性或方法是私有的,不能在声明它的类的外部访问;
  • protected:修饰的属性或方法是受保护的,它和private类似,区别是在子类中也是允许被访问的。

下面的代码你可以看到publicprivate的区别。

class Point {
	public x;
	private y;
	constructor(x: number, y: number) {
		this.x = x;
		this.y = y;
	}
}
var p = new Point(1, 2);
console.log('p.x =', p.x);
// p.x = 1
p.x = 2;
console.log('p.x =', p.x);
// p.x = 2
console.log(p.y);
// 报错
// p.y = 2
p.y = 3;
// 报错
console.log(p.y);
// 报错
// p.y = 3

这里会发现一个问题,我们设置private是为了让有的属性无法直接存取,但是在这里问什么仍然输出了呢?

这是因为TypeScript无法直接在浏览器中运行,需要先编译为JavaScript代码,而编译之后的代码中,并没有限制private属性在外部的可访问性,上述的代码编译后如下:

var Point = (function () {
	function Point(x, y) {
		this.x = x;
		this.y = y;
	}
	return Point;
})();
var p = new Point(1, 2);
console.log('p.x =', p.x);
p.x = 2;
console.log('p.x =', p.x);
console.log(p.y);
p.y = 3;
console.log(p.y);

但是你仍然能够收到报错,上述代码的报错信息如下:

Property 'y' is private and only accessible within class 'Point'.

再来看看privateprotected的区别:

class Person {
	private name;
	constructor(name: string) {
		this.name = name;
	}
}
class Man extends Person {
	constructor(name: string) {
		super(name);
		console,log(this.name);
	}
}
// 报错
class Person {
	protected name;
	constructor(name: string) {
		this.name = name;
	}
}
class Man extends Person {
	constructor(name: string) {
		super(name);
		console,log(this.name);
	}
}

正如上文中所说,使用private修饰的属性或方法在子类中时不允许访问的,而使用protected修饰的,是允许在子类中进行访问的。也有特殊的情况,若private修饰的为构造函数constructor,则该类不允许被继承或实例化,若protected修饰的为构造函数constructor时,则该类只能被继承。

文章后续还会更新,如有不足,欢迎批评指正!