TS的类
在传统的JavaScript中,我们使用构造函数来实现类的概念,通过原型链的方式来实现继承。值得庆祝的是,终于我们等来了ES6,迎来了class
关键字。熟悉ES6的前端工程师肯定对此关键字不陌生,在TS中,对此部分进行了扩展,增加了许多新东西。
类的定义
我们使用class
关键字来定义一个类,我们可以给类添加属性和方法。
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I am ${this.name}, ${this.age} years old.`);
}
}
const p1 = new Person("xiaoming", 21);
p1.sayHello();
上面代码中,我们定义了一个Person类,里面有name, age俩个属性,constructor, sayHello俩个方法。我们通过new Person(...)
生成了一个对象p1,我们调用sayHello
方法,打印了个人信息。
这是一个简单的类,为我们展示了TS中类的基础使用。
类的继承
在ES6中,我们使用extends
关键字来实现继承。在TS中,我们同样这么做。
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I am ${this.name}, ${this.age} years old.`);
}
}
class Student extends Person {
stuId: string
constructor(name: string, age: number, stuId: string) {
super(name, age);
this.stuId = stuId;
}
sayHello() {
super.sayHello();
console.log("I am a student!");
}
}
const s1 = new Student("xiaoming", 21, "W12138");
s1.sayHello();
我在刚刚Person类的基础上面,通过继承方式实现了Student类,并且还增加了一个新属性stuId
。
在Student构造函数中,使用super(…)调用父类的构造函数,我们还在Student类里面重写了sayHello方法,里面通过super.sayHello()
调用父类的sayHello.
我们使用继承,提高了代码的利用率,我们不必重复写一些冗余代码,在设计模式中有重要的地位。
访问修饰符
访问修饰符是JavaScript没有的,所以我们详细介绍一下。
在TS中,总共有三种访问修饰符,protected private public
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问的
public
我们先来介绍最简单的public
class Person {
public name: string
public age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I am ${this.name}, ${this.age} years old.`);
}
}
我给name,age属性前面加上了public
访问属性关键字,其实不加的默认值也是它
public name: string
和name: string
等价
const p1 = new Person("cyl", 21);
console.log(p1.name);
因为name,age的访问属性是public
的,所以我们可以在外面直接通过实例.属性名
来访问。
现在想想前面的代码,我们为什么p1.sayhello ,new Person()
可以这样使用了?聪明的你,一定想到了吧!那是因为sayHello和constructor的访问属性是public
的。
private
private私有的,有一款手机游戏的英雄(吕布+皮肤特性)的大致台词是:
貂蝉私有变量,谁敢访问,提头来见。
class Person {
private name: string
public age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, I am ${this.name}, ${this.age} years old.`);
}
}
const p1 = new Person("cyl", 21);
console.log(p1.name);
我们将name的访问属性改成private
,那么我们在外面访问name属性时
说的非常清楚了,只允许在Person中访问,其子类也不行的,更别说外部了。
private constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
我们给构造器加上private
访问属性,我们还能在外面通过new
运算符生成实例吗?
也是不行的。
protected
protected
和private
一样同样是为了限制用户访问权限的,但是protected
的要求低一点,子类可以访问。
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
protected sayHello() {
console.log(`Hello, I am ${this.name}, ${this.age} years old.`);
}
}
class Student extends Person {
stuId: string
constructor(name: string, age: number, stuId: string) {
super(name, age);
this.stuId = stuId;
}
sayHello() {
super.sayHello();
console.log("I am a student!");
}
}
const s1 = new Student("xiaoming", 21, "W12138");
s1.sayHello();
const p1 = new Person("cyl", 21);
p1.sayHello();
我们给Person类里面的sayHello方法加上了protected
访问属性,在Student类里面重写sayHello方法时使用了父类的sayHello方法,这样并没有问题,但是我们通过new Person(...)
生成实例调用sayHello方法时报错了。
存取器
TS的存取器类似JS使用Object.defineProperty
设置setter,getter。当用户需要对对象属性的赋值取值操作进行额外处理的时候,可以借助存取器。
class Person {
firstName: string
lastName: string
private _fulllName: string
constructor(fn: string, ln: string) {
this.firstName = fn;
this.lastName = ln;
}
get fullName () {
this._fulllName = this.firstName + this.lastName;
return this._fulllName;
}
set fullName (name: string) {
let arr: string[] = name.split(" ");
this.firstName = arr[0];
this.lastName = arr[1];
}
}
const p = new Person("c", "yl");
console.log(p.fullName);
p.fullName = "xiao ming"
console.log(p.firstName, p.lastName);
虽然这个例子可能不是很恰当,但是我们能够认识到get,set存取器的用法了。当我们使用实例.fullName
时实际上调用的get fullName
函数,同样的使用实例.fullName = '....'
时实际调用的是set fullName(...)
静态属性
在TS中,我们可以通过static
关键字来声明静态属性或者静态方法,这些属性方法并不是在实例对象上面而是在类本身上面。
class Person {
firstName: string
lastName: string
static Id: number = 666
constructor(fn: string, ln: string) {
this.firstName = fn;
this.lastName = ln;
}
static getId () {
return Person.Id;
}
}
console.log(Person.getId())
我们直接使用Person.getId()
调用了Person
对象上面的静态方法,而在方法里面使用一个静态属性Id
我们成功的打印了666。
这里有俩个问题,你仔细想一想
- 假如把
getId
方法里面的Person.Id
换成this.Id
你觉得可行吗? - 如果我想使用
Person.firstName
你觉得这样可行吗?
第一个问题其实不难,其实就是this的指向问题Person.getId()
这样调用,this
指向的依然是Person
这个对象,所以没有什么问题。
那么额外想一想这个问题
class Person {
firstName: string
lastName: string
static Id: number = 666
constructor(fn: string, ln: string) {
this.firstName = fn;
this.lastName = ln;
}
static getId () {
return this.Id;
}
}
const f = Person.getId;
console.log(f());
将getId
函数里面使用this
,然后按照如下方法调用,会打印什么?想想呗!
第二个问题使用Person.firstName
会报错的,因为firstName
是类的实例部分,在没有通过new
运算符生成实例对象之前这些实例部分是不存在的。
抽象类
这应该是本小节最后一个内容了,也是非常重要的一个内容的。
abstract
关键字(抽象)
abstract class Animal {
eat () {
console.log("我能吃!")
}
abstract bark(): void
}
class Dog extends Animal {
bark () {
console.log("汪汪汪");
}
}
class Cat extends Animal {
bark () {
console.log("喵喵喵");
}
}
const d = new Dog();
d.bark();
const c = new Cat();
c.bark();
我们定义了一个抽象类Animal
它含有一个eat
方法和一个抽象方法bark
,注意这个bark
方法只是声明并没有实现。
Dog
和Cat
类都继承于Animal
类。因为继承于抽象类的缘故,我们在子类里面必须要重写实现父类定义的抽象方法。
还有一点值得注意的是:抽象类是不可以通过new
运算符生成实例的。
ok,类的知识就介绍到这里了,下一节我们将一起学习函数。