TypeScript
类的定义
我们可以使用class关键字来定义一个类;
我们可以声明类的属性:在类的内部声明类的属性以及对应的类型
如果类型没有声明,那么它们默认是any的;
我们也可以给属性设置初始化值;
在默认的strictPropertyInitialization模式下面我们的属性是必须初始化的,如果没有初始化,那么编译时就会报错;
如果我们在strictPropertyInitialization模式下确实不希望给属性初始化,可以使用 name!: string语法;
类可以有自己的构造函数constructor,当我们通过new关键字创建一个实例时,构造函数会被调用;
构造函数不需要返回任何值,默认返回当前创建出来的实例;
类中可以有自己的函数,定义的函数称之为方法;
class Person {
//成员属性:声明成员属性
name!: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name);
}
}
//实例对象 instance
const p1 = new Person('kobe', 30);
类的继承
面向对象的其中一大特性就是继承,继承不仅仅可以减少我们的代码量,也是多态的使用前提。
我们使用extends关键字来实现继承,子类中使用super来访问父类。
我们来看一下Student类继承自Person:
Student类可以有自己的属性和方法,并且会继承Person的属性和方法;
在构造函数中,我们可以通过super来调用父类的构造方法,对父类中的属性进行初始化;
class Student extends Person {
constructor(name: string, age: number) {
super(name, age);
}
studying() {
console.log('在学习', this.name);
}
}
类的成员修饰符
在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected
public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
private 修饰的是仅在同一类中可见、私有的属性或方法;
protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
public是默认的修饰符,也是可以直接访问的,我们这里来演示一下protected和private。
class Person {
protected name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
//方法变成私有方法 只有在类内部才能访问
private eating() {
console.log('吃东西');
}
}
const p = new Person('kobe', 18);
只读属性readonly
如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly:
class Person {
readonly name: string
constructor(name: string) {
this.name = name
}
}
const p = new Person('kobe')
当我们改变p.name值时 会报错
getters/setters
在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程,这个时候我们可以使用存取器。
class Person {
private _name: string
set name(newName) {
this._name = newName
}
get name(){
return this._name
}
constructor(name: string) {
this.name = name
}
}
const p = new Person('kobe')
p.name = 'james'
console.log(p.name)
参数属性(Parameter Properties)
TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性
这些就被称为参数属性(parameter properties);
你可以通过在构造函数参数前添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类
属性字段也会得到这些修饰符;
class Person {
//语法糖
constructor(public name: string, private age: number, readonly height: number) {}
running() {
console.log(this.age, 'eating');
}
}
const p = new Person('kobe', 18, 1.88);
console.log(p.name, p.height);
console.log(p.name);
抽象类abstract
我们知道,继承是多态使用的前提。
所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
什么是 抽象方法? 在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法。
抽象方法,必须存在于抽象类中;
抽象类是使用abstract声明的类;
抽象类有如下的特点:
抽象类是不能被实例的(也就是不能通过new创建)
抽象方法必须被子类实现,否则该类必须是一个抽象类;
abstract class Shape {
abstract getArea();
}
class Rectangle extends Shape {
constructor(public width: number, public height: number) {
super();
}
getArea() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(public radius: number) {
super();
}
getArea() {
return this.radius ** 2 * Math.PI;
}
}
class Triangle extends Shape {
getArea() {
return 100;
}
}
//通用的函数
function calcArea(shape: Shape) {
return shape.getArea();
}
calcArea(new Rectangle(10, 20));
calcArea(new Circle(5));
calcArea(new Triangle());
类的类型
类本身也是可以作为一种数据类型的:
ts对于类型检测使用的是鸭子类型
鸭子类型:如果一只鸟 走起路来像鸭子 看起来像鸭子 那么你就可与认为它是鸭子
鸭子类型 只关心属性和行为 不关心具体的类型
class Person {
constructor(public name: string, public age: number) {}
}
class Dog {
constructor(public name: string, public age: number) {}
}
function printPerson(p: Person) {
console.log(p.name, p.age);
}
printPerson(new Person('james', 18));
printPerson({ name: 'kobe', age: 30 });
printPerson(new Dog('旺财', 3));
const person: Person = new Dog('果汁', 5);
对象类型的属性修饰符(Property Modifiers)
对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息。
可选属性(Optional Properties)
我们可以在属性名后面加一个 ? 标记表示这个属性是可选的;
只读属性(Readonly Properties)
在 TypeScript 中,属性可以被标记为 readonly,这不会改变任何运行时的行为;
但在类型检查的时候,一个标记为 readonly的属性是不能被写入的。
类的作用
1.可以创建类对应的实例对象
2.类本身可以作为这个实例的类型
3.类也可以当作有一个构造签名的函数
class Person {}
const name: string = 'aaa';
const p: Person = {};
function printPerson(p: Person) {}
function factory(ctor: new () => void) {}
factory(Person);
type IPerson = {
//属性?:可选的属性
name?: string;
//readonly:只读属性
readonly age: number;
};
interface IKun {
name?: string;
readonly slogan: string;
}
const p: IPerson = {
name: 'kobe',
age: 18,
};
索引签名(Index Signatures)
什么是索引签名呢?
有的时候,你不能提前知道一个类型里的所有属性的名字,但是你知道这些值的特征;
这种情况,你就可以用一个索引签名 (index signature) 来描述可能的值的类型;
interface ICollection {
// 索引签名
[index: string]: number;
length: number;
}
const names: number[] = [111, 222, 333];
console.log(names[0]);
console.log(names[1]);
console.log(names[2]);
一个索引签名的属性类型必须是 string 或者是 number。
虽然 TypeScript 可以同时支持 string 和 number 类型,但数字索引的返回类型一定要是字符索引返回类型的子类型;
要满足下面两个要求
1 数字类型必须是比字符串类型更加确定的类型(需要是字符串类型的子类型)
2 如果索引签名中有定义其他属性 其他属性返回的类型 必须符合string类型返回的属性
interface IIndexType {
[index: number]: string;
[key: string]: number | string;
aaa: number;
bbb: string;
// ccc: boolean; // 错误类型
}
对于如下情况
情况一 不会报错
interface IIndexType {
//返回值类型的目的是告知通过索引去获取到的值是什么类型
[index: number]: string;
}
const names: IIndexType = ['abc', 'cba', 'nba'];
情况二 不会报错
interface IIndexType {
//返回值类型的目的是告知通过索引去获取到的值是什么类型
[index: number]: string;
[index: string]: any;
}
const names: IIndexType = ['abc', 'cba', 'nba'];
情况三 会报错 names.forEach => names['forEach] => function 不符合
interface IIndexType {
//返回值类型的目的是告知通过索引去获取到的值是什么类型
[index: number]: string;
[index: string]: string;
}
const names: IIndexType = ['abc', 'cba', 'nba'];
接口类型
接口和类一样是可以进行继承的,也是使用extends关键字:
并且我们会发现,接口是支持多继承的(类不支持多继承)
interface IPerson {
name: string;
age: number;
}
interface IKun extends IPerson {
slogan: string;
}
//可以重其他的节后中继承过来属性
1 减少了相同代码的重复编写
2 如果使用第三方库 给我们定义了一些属性 自定义一个接口拥有第三方某一个类型中所有的属性可以使用继承来完成
接口的实现
接口定义后,也是可以被类实现的:
如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入;
这就是面向接口开发;
interface ISwim {
swimming: () => void;
}
interface IRun {
running: () => void;
}
//作用 接口被类实现
class Person implements ISwim, IRun {
swiming() {}
running() {}
}
严格的字面量赋值检测
对于对象的字面量赋值,在TypeScript中有一个非常有意思的现象:
为什么会出现这种情况呢
每个对象字面量最初都被认为是“新鲜的(fresh)”。
当一个新的对象字面量分配给一个变量或传递给一个非空目标类型的参数时,对象字面量指定目标类型中不存在的属性是错误的。
当类型断言或对象字面量的类型扩大时,新鲜度会消失。
TypeScript枚举类型
枚举类型是为数不多的TypeScript特性有的特性之一:
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型;
enum Direction {
LEFT,
RIGHT,
}
const d1: Direction = Direction.LEFT;
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log('角色向左移动一个格子');
break;
case Direction.RIGHT:
console.log('角色向右移动一个格子');
break;
}
}
//监听键盘的点击
turnDirection(Direction.LEFT);
枚举类型的值
枚举类型默认是有值的,比如上面的枚举,默认值是这样的:
当然,我们也可以给枚举其他值:
这个时候会从100进行递增;
我们也可以给他们赋值其他的类型:
enum Direction {
LEFT = 0,
RIGHT = 1,
TOP = 2,
BOTTOM = 3,
}
enum Direction {
LEFT = 100,
RIGHT,
TOP,
BOTTOM,
}
enum Direction {
LEFT,
RIGHT,
TOP = 'TOP',
BOTTOM = 'BOTTOM',
}