[toc]

泛型

有时,书写某个函数时,会丢失一些类型(多个位置的类型应该保值一直或者有关联的信息)。

泛型:是指附属于函数、类、接口、类型别名之上的类型

泛型相当于是一个类型变量,在定义时,无法预先知道具体的类型,可以用该变量来代替,只有到调用时,才能确定它的类型。很多时候,TS会智能的根据传递的参数,推导出泛型的具体类型。如果无法完成推导,并又没有传递具体的类型,默认为空对象。泛型可以设置默认值。

在函数中使用泛型

在函数名之后写上<泛型名称>

如何在类型别名、接口、类中使用泛型

在名称之后写上<泛型名称>

泛型约束

泛型约束,用于显示泛型的取值

  1. // 定义约束
  2. interface constraint {
  3. name:String
  4. }
  5. // 使用
  6. function test<T extends constraint>(obj:T):T{}

多泛型

  1. function mixinArr<T,K>(arr1:T[],arr2:K[]):(T|K)[]{}

深入理解类和接口

一、面向对象

概述

1. TS为前端面向对象开发带来了契机

JS语言没有类型检查,如果使用面向对象的方式开发,会产生大量的接口,而大量的接口会导致调用复杂度剧增,这种复杂度必须通过严格的类型检查来避免错误,机关可以使用注释或文档或记忆力,但是他们没有强约束力。

TS带来了完整的类型系统,因此开发复杂程序时,无论接口数量有多少,都可以获得完整的类型检查,并且这种检查是具有强约束力的。

2. 面向对象中有许多成熟的模式,能处理复杂问题

在过去的很多年中,在大型应用或复杂领域,面向搞对象已经积累了非常多的经验。

概念

Oriented Object,简称OO。基于事物。是一种编程思想,它提出一切以对象对切入点思考问题。

面向过程:以功能流程为思考切入点,不太适合大型应用。

函数式编程:以数学运算为思考切入点。

面向对象:以划分类为思考切入点。

类:可以产生对象的模板。

二、类的继承

继承的作用

继承可以描述类与类之间的关系。如果A和B都是类,并且可以描述为A是B,则A和B形成继承关系:

  • B是父类,A是子类
  • B派生A,A继承自B
  • B是A的基类,A是B的派生类

继承的作用:子类能具有父类的所有属性。

属性重写

重写(override):子类中覆盖父类属性

子类成员不能改变父类成员的属性。无论是属性还是方法,子类都可以对父类的相应成员进行重写,但是重写时,需要保证类型的匹配。

但是需要注意this的指向:在继承关系中,this的指向是动态的,调用方法时,根据具体的调用着确定this指向。

类型匹配

子类的对象,始终可以赋值给父类。面向对象中,这种现象叫做里式替换原则

如果需要判断一个数据的类型子类类型,可以使用instanceof

属性修饰符

  • readonly:只读修饰符
  • private:私有属性
  • public:公共属性
  • protected:受保护成员,只能在自身和子类中访问

单根性和传递性

单根性:每个类最多只能拥有一个父类。

传递性:如果A是B的父类,并且B是C的父类,则可以认为A也是C的父类。

三、抽象类

  1. // 定义
  2. abstract class Father {
  3. name:String = ''
  4. }
  5. // 继承
  6. class Child extends Father{
  7. }
  8. // 声明
  9. const a = new Child()

有时,某个类只表示是一个抽象概念,主要用于提取子类共有的成员,而不能直接创建它的对象。该类可以作为抽象类。给类前面加上abstract,表示该类是一个抽象类,不可以创建一个抽象类的对象。

抽象成员

  1. // 定义父类
  2. abstract class Father {
  3. // 定义一个抽象成员,子类中必须实现
  4. abstract name:String = ''
  5. }
  6. // 创建子类 三种实现方式
  7. class Child1 extends Father{
  8. name:string = 'child1'
  9. }
  10. class Child2 extends Father{
  11. name:string;
  12. constructor(){
  13. super();
  14. this.name = 'Child2'
  15. }
  16. }
  17. class Child3 extends Father{
  18. get name(){
  19. return 'Child3'
  20. }
  21. }

父类中,可能知道有些成员是必须存在的,但是不知道该成员的值或实现是什么,因此,需要有一种强约束,让继承该类的子类必须实现该成员。

抽象类中,可以有出现成员,这些抽象成员必须在子类中实现。

设计模式 - 模板模式

设计模式:面对一些常见的功能场景,有一些固定的,经过多年实践的成熟方法,这些方法称之为设计模式。

模板模式:有些方法,所有的子类的实现流程完全一致,只是流程中的某个步骤的具体实现不一致,可以将该方法提取到父类,在父类中完成整个流程的实现,遇到实现不一致的方法时,将该方法做成抽象方法。

四、静态成员

静态成员是指,附着在类上的成员(属于某个构造函数的成员)。使用static修饰的成员,是静态成员。

实例成员:对象成员,属于某个类的对象

静态成员:非实例成员,属于某个类

静态方法中的this

实例方法的this指向的是当前对象。

而静态方法中的this指向的是当前类。

设计模式 - 单例

单例模式: 某些类的对象,在系统中只能有一个,为了避免开发者造成随意创建多个类对象的错误,可以使用单例模式进行约束。

五、再谈接口

接口用于约束类、对象、函数,是一个类型契约。

不适合接口实现时:

  • 对能力(成员函数)没有强约束力
  • 容易将类型和能力耦合在一起

系统中缺少对能力的定义 — 接口。

面向对象领域中的接口的语义:表达了每个类是否拥有某种能力。某个类具有某种能力,其实,就是实现了某种接口。

类型保护函数:通过调用该函数,会触发TS的类型保护,该函数必须返回boolean。

接口和类型别名的最大区别:接口可以被类实现,而类型别名不可以。

六、索引器

实现方式:对象[值],使用成员表达式。

在TS中,默认情况下不对索引器做严格的类型检查,使用配置noImplicitAny开启对隐式any的检查。

隐式any:TS根据实际情况推导出的any类型

  1. // 需要在类中定义如下
  2. [prop:string]:any
  3. // 或
  4. [prop:string]: string | number | {():void}
  5. // 调用
  6. u["id"] = 1;

需要注意索引器是对类中的所有成员生效,所以当类中存在不同类型的成员变量时需要用上面的两种方式进行声明。在索引器中,键的类型可以是字符串,也可以是数字。在类中,索引器书写位置要在所有成员之前。

TS中索引器的作用

  • 在严格的检查下,可以实现为类动态添加成员
  • 可以实现动态的操作类成员

在JS中,所有成员名本质上,都是字符串,如果使用数字作为成员名,会自动转换为字符串。

在TS中,如果某个类中使用了两种类型的索引器,要求两种索引器的值类型必须匹配。

七、this指向

Understanding JavaScript Function Invocation and “this”

在JS中this指向的几种情况

明确:大部分时候,this的指向取决于函数的调用方式

  • 直接调用函数(全局调用),this指向全局对象或undefined(启用严格模式)
  • 如果使用对象.方法调用,this指向对象本身
  • 如果是dom事件处理函数,this指向事件处理对象

特殊情况:

  • 箭头函数,this在函数声明时确定指向,指向函数位置的this
  • 使用bind、apply、call手动绑定this对象
  • ES6中类中默认使用严格模式,this指向undefined

TS中的this

配置noImplicitThis为true,表示不允许this隐式的指向any。

在TS中,允许在书写函数时,手动声明该函数中this的指向,将this当做函数的第一个参数的位置,该参数只用于约束this,并不是真正的参数,也不会出现在编译结果中。