对于学过Java的同学,类的概念想必是十分熟悉了,但是对于一些刚刚接触JavaScript并且没有过面向对象语言学习的同学,类可能是一个较为陌生的概念,这里我们先做一些基础的介绍。
-
类
class
:它定义了一件事物的抽象特点,这里面就包括它的属性和方法。在下面的例子里,我就定义了一个类,名为Point
,其包含的属性即它的横纵坐标x
和y
,我也为它定义了一个方法名为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)
可以看到,二者直观上的区别体现在“类”写法上的不同,在使用上没有区别。
基本上ES6
的class
可以看作只是一个语法糖,它的绝大部分功能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
,在类的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,从而改变该属性的赋值和读取行为。
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
中并没有如其它语言那般的public
、private
等修饰符,而这些内容却在TypeScript
中实现了。
在TypeScript
中,每一个成员默认都是public
的,另外两个修饰符分别是private
和protected
。
public
:修饰的属性或方法是公有的,可以在任何地方被访问到;private
:修饰的属性或方法是私有的,不能在声明它的类的外部访问;protected
:修饰的属性或方法是受保护的,它和private
类似,区别是在子类中也是允许被访问的。
下面的代码你可以看到public
和private
的区别。
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'.
再来看看private
和protected
的区别:
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
时,则该类只能被继承。
文章后续还会更新,如有不足,欢迎批评指正!