TS像其他语言一样,也有类及接口的概念。扩展类时使用extends关键字,使用implements关键字指明该类满足某个接口。还可以有抽象类,一共有三种可见效:private、protected及public(默认)。
1 基础
子类可以扩大可见范围,但不能缩小范围(但是不能扩大private的范围)。子类的实例属性类型必须与父类保持一致。
class Person {constructor(protected name:string,private age:number) {}
}class Man extends Person {// age 为形参,父类的age因为是private,所以不能在子类中被定义constructor(public name:string,age: number) {super(name,age);}
}
子类只能把父类为protected的实例属性的可见效扩大为public。
1.1 以this为返回类型
this可以用作值,也能用作类型。
假如某个类有个实例方法,方法的返回值为其本身。如果将返回类型设置为这个类名,将会有两个问题:1)不能保证返回的是该类实例本身;2)如果该类被继承,则返回类型也要做相应的修改。 所以此时,将this作为返回类型将是最优解。
class List {protected array: any[] = [];add(item:any):this {this.array.push(item);return this;}showArray() {console.log(this.array);}
}class UniqueList extends List{add(item:any):this {let inHere = false;for (let it of this.array) {if (it === item) {inHere = true;break;}}if (!inHere) {this.array.push(item);}return this;}
}let list = new UniqueList();
list.add(1).add("hello").add(494).add("word").add(1).add(999).add("word");
list.showArray();
1.2 接口
与类型别名基本相似,但有细微的差别:
1)类型别名更为通用,右边可以是任何类型,而接口右边必须为结构。
2)扩展接口时,ts将检查扩展的接口是否可赋值给被扩展的接口,而类型不会。
3)同名接口自动合并,但类型别名不能相同。
type A = {val: numberfun1(arg:number,arg2: string):void
}// 报错,类型不能同名
// type A = {
//
// }// 类型合并不做检查
type B = A & {fun1(arg:string):void
}let b: B = {val: 20,fun1(arg: string | number,arg2?:string) {console.log("hello");}
}interface C {val: numberfun1(arg:number,arg2: string):void
}// 接口同名会被合并
interface C {fun2():void
}// 接口扩展时会检查是否安全
interface D extends C{
// fun1(arg:string):void //error Type (arg: string) => void is not assignable to type (arg: number, arg2: string) => void
}class X implements C {val: number;constructor(num: number) {this.val = num;}fun1(arg: number, arg2: string): void {console.log('hello X')}fun2(): void {}
}let x = new X(999);
console.log(x); // X { val: 999 }
对于第二点,我们在做扩展时常会用接口形式,因为这样做更安全。
对于接口中readonly属性,在被类实现时readonly会失效,但是如果不被类实现而直接实例化,则readonly有效。
interface A {readonly val: number
}class X implements A {val = 1
}let x = new X();
console.log(x);
x.val = 23 // 仍然可写
console.log(x);let a: A = {val:23};
a.val = 99; // 报错:Cannot assign to val because it is a read-only property.
接口 | 接口更通用、更轻量、只存在于编译阶段。 |
抽象类 | 抽象类更具体、功能更丰富、生成运行时代码。 |
表 接口与抽象类的比较
2 进阶
类是结构化类型。TS根据结构比较类,与类的名称无关。类与其他类型是否兼容,要看结构。
class A {fun() {console.log("a");}
}
class B {fun() {console.log("b");}
}let a:A = new A();
let b:B = new B();
let c = a;
a = b;
b = c;
console.log(a); // B {}
console.log(b); // A {}
a.fun(); // b
b.fun(); // a
2.1 多态
类和接口与函数一样,对泛型参数也有深层支持,包括默认类型和限制。
type ValueType<V> = {[key:string]:V
}class CustomMap<V> {private readonly value: ValueType<V>constructor(key:string,val: V) {this.value = {};this.value[key] = val;}put(key:string,val:V) {this.value[key] = val;}get(key:string):V{return this.value[key];}// 静态方法不能访问类型的范型static fun<K>(arg:K) {}
}
// 可以显式绑定具体类型也可以让TS自动推导
let map = new CustomMap("one","val1");
map.put("two","val2");
console.log(map.get("two")); // val2
console.log(map.get("three")) // undefined
2.2 类既声明值也声明类型
在ts中,多数时候,表达的要么是值要么是类型:
值:
let a=99; function fub() {};
类型:
type a = number;
interface B {}
类和枚举比较特殊,它们既可以表示类型也可以表示值。
class C{}
let c:C // 指C的实例类型
= new C(); // 指值C
一个类由静态类型(构造函数+静态方法及静态属性)与实例类型(实例属性及实例方法)组成。
class C {static readonly CLASS_NAME:string = "C";id:number=0;constructor() {}fun() {}static staticFun() {}
}interface InstanceC{ // C的实例类型id:numberfun():void
}interface StaticC { // C的静态类型readonly CLASS_NAME:stringnew(): CstaticFun():void
}let c = new C();
let instanceC:InstanceC;
let staticC:StaticC;instanceC = c;
// staticC = c; // 报错 Type C is missing the following properties from type StaticC: CLASS_NAME, staticFun
// instanceC = C; // 报错 Type typeof C is missing the following properties from type InstanceC: id, fun
staticC = C;// 实例不能访问静态
// c.CLASS_NAME; //报错 Property CLASS_NAME does not exist on type C
console.log(staticC.CLASS_NAME); // C
2.3 混入mixin
Vue中的混入是指将多个相同的逻辑抽离出来,各个组件只需引入mixins,就可以在各个组件使用这些逻辑。
JS和TS都没有mixin关键字,不过我们可以自己实现混入。
需求:要求每个组件打印其实例属性(public 可见性)JSON格式。
type ClassConstructor = new(...args:any[]) => {}function createMixinModule<C extends ClassConstructor>(Class:C) {return class extends Class {showFieldJson() {let str = Class.name + "("for (let key in this) {str += key + ":" + this[key] + ";"}str += ")";console.log(str);}}
}class ModuleA {constructor(public name: string, public val:number) {}
}class ModuleB {constructor(public type: number,public color: string) {}
}let A = createMixinModule(ModuleA);
let B = createMixinModule(ModuleB);
let a = new A("moduleA", 999);
let b = new B(-1,"red");
a.showFieldJson(); // ModuleA(name:moduleA;val:999;)
b.showFieldJson(); // ModuleB(type:-1;color:red;)
3 TS与设计模式
“不自己动手实现一两个设计模式怎么算得上是讨论面向对象编程呢?”
3.1 装饰模式
需求:对每一个发出的请求,都打印出请求path、方法及参数。
type ClassConstruct<T> = new(...args:any[]) => Tfunction createDecoratorRequest<C extends ClassConstruct<{request(path:string,method:string,params:{[key:string]:any}):void
}>>(Class:C) {return class extends Class {request(path:string,method:string,params:{[key:string]:any}) {console.debug("打印请求日志:",path,method,params);super.request(path,method,params);}}
}class CustomRequest {request(path:string,method:string,params:{[key:string]:any}) {console.log("请求完成");}
}let DecoratorRequest = createDecoratorRequest(CustomRequest);
let req = new DecoratorRequest();
req.request("http://localhost:8080/hello","GET",{id:13,value: "334"});
req.request("https://localhost:8080/file","POST",{name: "fire.txt"});// 打印请求日志: http://localhost:8080/hello GET { id: 13, value: '334' }
// 请求完成
// 打印请求日志: https://localhost:8080/file POST { name: 'fire.txt' }
// 请求完成
3.2 简单工厂模式
需求:输入手机品牌,生产出对于品牌的手机。
class Phone {}
class Apple extends Phone {}
class Huawei extends Phone {}type FactoryCreate = {(name:"apple") : Apple(name: "huawei") : Huawei
}let factoryCreate:FactoryCreate =
function createPhone(name:"apple"|"huawei"):Phone {if (name === "apple") return new Apple();return new Huawei();
}let phone1 = factoryCreate("apple");
let phone2 = factoryCreate("huawei");
3.3 建造者模式
需求:建造请求对象,要求执行顺序是:设置请求路径->设置请求方法->设置请求参数->发送。
class RequestBuilder {private path: string | null = nullprivate method: "GET" | "POST" = "GET"private params: {[key:string]:any} | null = nullprivate constructor() {}static setPath(path: string):{setMethod(method: "GET" | "POST"):{setParams(params: {[key:string]:any}):{send():void}}} {let request = new RequestBuilder();request.path = path;return request;}setMethod(method: "GET" | "POST"):{setParams(params: {[key:string]:any}):{send():void}} {this.method = method;return this;}setParams(params: {[key:string]:any}):{send():void} {this.params = params;return this;}send() {console.log(this.path,this.method,this.params,"发送成功");}}RequestBuilder.setPath("/hello").setMethod("GET").setParams({id:111,value:-12}).send();