对于学过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时,则该类只能被继承。
文章后续还会更新,如有不足,欢迎批评指正!