写点什么

TypeScript | 第二章:类、接口和之间的关系

用户头像
梁龙先森
关注
发布于: 2020 年 12 月 22 日
TypeScript | 第二章:类、接口和之间的关系

TypeScript 系列学习笔记:

TypeScript | 第一章:环境搭建及基础数据类型

TypeScript | 第三章:函数、泛型和枚举

TypeScript | 第四章:命名空间和模块

TypeScript | 第五章:高级类型

TypeScript | 第六章:理解声明合并,以及编写声明文件

TypeScript | 第七章:配置文件说明

一、类

1. 类的基本实现

es 和 ts 中类成员的属性都是实例属性,而不是原型属性; 类成员方法都是原型方法。

class Dog{  constructor(name:string){        this.name = name;    }    name:string //成员属性添加类型注解  run(){}  //挂载到Dog.prototype上}
复制代码
 2. 类的继承
class Husky extends Dog{    constructor(name:string,color:string){        super(name) //强制规定,代表父类的实例        this.color = this.color;    }    color:string}
复制代码
 3. 类的成员修饰符
class Dog1{    //类不能被实例化、也不能被继承    private constructor(public color:string){//color变成实例属性
} //public:公有,默认,对所有可见 public name:string //private:私有,类本身访问,不能被类的实例/子类访问 private pri(){} //protected:受保护成员,只能在类和子类访问,不能被实例访问 protected pro(){} //只读属性,不可被更改,要赋初始值 readonly legs:number = 4 //静态成员:只能通过类名来调用,不能通过实例调用,可以被子类继承 static food:string = 'bones'}
复制代码
4. this
class WorkFlow {    constructor() {            }    step1(){        return this    }    step2(){        return this    }}
// 返回this,实现了方法链式调用new WorkFlow().step1().step2();

//继承的时候this表现出多态,可能是父类或者子类class MyFlow extends WorkFlow{ next(){ return this; //返回子类类型 }}// 子类-->父类new MyFlow().next().step1().next().step2();
复制代码
5. 存储器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

let passcode = "secret passcode";
class Employee { private _fullName: string;
get fullName(): string { return this._fullName; }
set fullName(newName: string) { // 当密码不对,则无法修改 if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } }}
let employee = new Employee();employee.fullName = "Bob Smith";if (employee.fullName) { alert(employee.fullName);}
复制代码
6. 静态属性

到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。

class Grid {    static origin = {x: 0, y: 0};    calculateDistanceFromOrigin(point: {x: number; y: number;}) {        let xDist = (point.x - Grid.origin.x);        let yDist = (point.y - Grid.origin.y);        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;    }    constructor (public scale: number) { }}
let grid1 = new Grid(1.0); // 1x scalelet grid2 = new Grid(5.0); // 5x scale
复制代码
7. 抽象类
  1. 抽象类做为其它派生类的基类使用,可以包含成员的实现细节。 

  2. abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。

  3. 能被继承,不能被实例;

  4. 抽离出事务的共性,有利于代码的复用和扩展;

  5. 抽象类可以实现多态:父类中定义抽象方法,在多个子类中对这个方法有不同实现, 程序运行时候根据不同对象执行不同操作,实现了运行时的绑定;

  6. 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 

  7. 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。

  8. 抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

abstract class Animal {    constructor() {            }    eat(){}    //抽象方法:不指定具体的实现,明确知道子类有具体的实现    abstract sleep():void}//new Animal() 报错:不能被实例

class Cat extends Animal { constructor(name:string) { super() this.name=name } name:string run(){} sleep(){ console.log('cat is sleep') }}
class Pig extends Animal{ constructor() { super(); } sleep(){console.log('pig is sleep')}}
//多态let animals:Animal[] = [new Cat('1'),new Pig()];animals.forEach(i=>{ i.sleep();})
复制代码

二、接口

在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

1. 接口的工作
interface Label{  name:string;}
// Label接口,用来描述入参的要求// 类型检查器会查看printLabel的调用,并要求入参必须包含一个name属性,且类型为string// 编译器只会检查那些必须的属性是否存在,并且类型是否匹配,然后有时候并不那么宽松,但可以使用类型断言function printLabel(label:Label){ console.log(label.name)}
let obj = {name:'张三',age:23}printLabel(obj)
复制代码
2. 可选属性
  1. 可以对可能存在的属性进行预定义

  2. 可以捕获引用了不存在的属性时的错误

interface SquareConfig{	color?:string;  // '?'表示可选属性,接口里的属性不全都是必需的  width?:number;}
function createSquare(config:SquareConfig): {color: string; area: number} { let newSquare = {color: "white", area: 100}; if (config.col) { // 好处2:捕获不存在的属性错误 Error:propery 'col' does not exit on type 'SquareConfig' newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare;}
let mySquare = createSquare({color: "black"});
复制代码
3. 只读属性

只允许对象在刚刚创建的时候修改其值。

interface Point{  readonly x:number}
let p:Point = {x:10} // 刚创建的时候修改值p.x = 1; // 报错
// 数组只读类型:ReadonlyArray<T>let arr:number[] = [1,2]let ro:ReadonlyArray<number> = arr; // ro只读ro[0] = 2; // 报错ro.push(3) // 报错ro.length // 报错let a = ro // 报错 --> 类型断言重写 let a = ro as number[]; // 正确
复制代码
4. 额外的属性检测

TypeScript 在进行检查的时候,对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。

// 借用2. 可选属性的例子let s1 = createSquare({clor: "black"}); // 报错:'clor' not expected in type 'SquareConfig'
// 最简单的方法: 绕开这些检查使用类型断言:let s2 = createSquare({clor: "black",color: "black",width:10} as SquareConfig)
// 最佳的方式:添加一个字符串索引签名,前提确定对象存在额外用途属性interface SquareConfig{ color?:string, width?:number, [propName:string]:any // 字符串索引签名}
// 惊人的方式:将入参对象赋值给另一个对象let squareOptions = { colour: "red", width: 100 }; // 赋值给另一个对象let mySquare = createSquare(squareOptions); // squareOptions不会经过额外属性检查
复制代码
5. 函数类型

定义一个只有参数列表和返回值类型的函数定义(调用签名),参数列表里的每个参数都需要名字和类型。

interface SearchFunc {  (source: string, subString: string): boolean;}
let mySearch: SearchFunc;// 函数的参数名不需要与接口定义的名字相匹配,但要求对对应位置上的参数类型是兼容的// 若参数名不指定类型,ts类型系统会自行推断// 函数返回值,根据接口定义的函数类型返回值推断mySearch = function(s: string, sub: string) { let result = source.search(subString); return result > -1;}
复制代码
6. 可索引的类型

可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

支持两种索引签名:字符串和数字。

可以同时使用 2 种索引类型,但 number 索引会转化为 string 索引再去索引,因此要保证 2 者的一致。

interface StringArray{	[index:number]:string // [index:number] 数字索引  :string索引返回类型}
// 数字索引let myArray: StringArray;myArray = ["Bob", "Fred"];let myStr: string = myArray[0];

class Animal{ name:string,}class Cat extends Animal{ age:string}// 字符串索引interface{ [name:string]:Animal}
// 字符串索引签名能够很好的描述dictionary模式interface NumberDictionary{ [index:string]:number // 返回值为number类型的字典}
// 防止给索引赋值,索引签名设置为只读interface ReadonlyStringArray{ readonly [index:number]:string}let arr:ReadonlyStringArray = ['1','2']arr[2] = '3' ; // 报错,索引是只读的
复制代码

三、类和接口的关系

1. 类和接口的关系图
2. 类实现接口
interface Human{    name:string,    eat():void}
//类实现接口,必须实现接口中声明的所有属性//接口只能约束类的公有成员class Asia implements Human{ constructor(name:string){ this.name = name; } name:string eat(){} sleep(){
}}
复制代码
3. 接口的继承
/** * 接口的继承 * 接口可以像类一样继承,一个接口可以继承多个接口*/interface Man extends Human{    run():void}
interface Child{ cry():void}
interface Boy extends Man,Child{}
let body:Boy = { name:'', run(){}, eat(){}, cry(){}}
复制代码
4. 接口继承类
/** * 接口继承类 * 相当于接口把类的成员都抽象出来,只有类的成员接口,没有具体的实现*/class Auto{    state = 1;    //2. 接口抽离类的成员,不仅抽离公共成员,还抽离私有成员和受保护成员    //private state2 = 0;}
//接口中,隐含了state属性interface AutoInterface extends Auto{
}
//实现接口,只要类的成员存在state属性即可class C implements AutoInterface{ state = 2; //若Auto配置了私有成员,此处报错,因为C不是Auto的子类,自然不能包含其它的非公有成员}
//Auto子类也可以实现AutoInterface这个接口class Bus extends Auto implements AutoInterface{ //不必实现state属性,auto的子类,自然继承了state属性}
复制代码

四、总结

自此我们学习了类、接口以及接口和类之间的关系,下一章:函数、泛型和枚举。


发布于: 2020 年 12 月 22 日阅读数: 109
用户头像

梁龙先森

关注

脚踏V8引擎的无情写作机器 2018.03.17 加入

还未添加个人简介

评论

发布
暂无评论
TypeScript | 第二章:类、接口和之间的关系