TS基础语法指南

126 阅读15分钟

1. 概述

1.1 安装

npm i -g typescript

安装完成后查看版本:tsc -v

1.2 编译

  • 直接编译 tsc 指定的file文件路径 如 : tsc ./src/test.ts

tsc 命令参数说明。

  • --output 指定编译结果输出目录

    tsc --output ./dist ./src/test.ts
    
  • --target 指定编译结果的语言版本如es5、es6

    tsc --target es5 ./dist ./src/test.ts
    
  • --watch 指定是否监听文件变化并实时执行编译

    tsc --watch ./dist ./src/test.ts
    
  • --project/-p 指定tsc的配置文件。默认是当前tsc运行目录的同级的 tsconfig.json 文件。默认直接执行tsc即可。只有当文件不在同级或者文件名不是tsconfig.json的时候才需要以下命令运行时指定配置文件。

    tsc -p ./config/tsconfig.json
    
  • ==配置文件内容如下:==

    {
        "compilerOptions": {
            // 编译结果存放路径
            "outDir": "./dist", 
            // 编译结果的语言版本
            "target": "ES2015",
            // 是否实时监听编译
            "watch": true
        },
        // 指定编译ts的源文件目录
        "include": ["./src/**/*.ts"]
    }
    

1.3 编译中的语法转换

  • 编译ts的时候也会做语法转换,但是ts转换的只是语法,对于es6的api无法转换如Object.assing。因此需要引入lib来解决。

  • 配置文件配置如下:

{
    "compilerOptions": {
        // 编译结果存放路径
        "outDir": "./dist", 
        // 编译结果的语言版本
        "target": "ES2015",
        // 是否实时监听编译
        "watch": true,
        // 语法转换会将es6的api进行转换
        "lib": ["es6"]
    },
    // 指定编译ts的源文件目录
    "include": ["./src/**/*.ts"]
}

2. 类型系统

类型系统 = 类型标注 + 类型检查

2.1 基础类型

let a: String = '中国人'
let b: Number = 33
let c: Boolean = true

2.2 空和未定义类型

  • 声明为null或者undefined类型的变量不能修改

    let d: null = null
    let e: undefined = undefined
    
  • null和undefined类型是所有类型的子类型

    let f: number
    f = null
    f = undefined
    
  • 如果变量未赋值,默认值为undefined。如果类型也没声明,类型默认为any

    let g: number
    console.log(g) // 值为undefined
    let h //默认为any类型
    
  • null和undefined类型带来隐式问题,如:

    let a: number
    a = null
    a.toFixed(1) //报错
    

    解决办法: 配置strictNullChecks为true严格检查

2.3 对象类型

包括对象Objecti、Array

2.3.1 三种表示方法

  • 字面量

    let ab: {age: number, name: string} = {
        age: 10,
        name: '小明'
    }
    
  • 接口

    interface Student {
        age: number,
        name: string
    }
    let bc: Student = {
        age: 10,
        name: '小明'
    }
    
  • class类

    class Person {
        constructor(public age:number, public name: string) {
            
        }
    }
    let bc: Person = new Person(10, '小明')
    

class和interface区别: interface更适合高复用的类型抽象。class既可以定义类型又可以定义实体

2.3.2 包装对象

包装对象。声明为包装对象,可以字面和包装对象实例赋值给标注。而声明为字面量,实体值只能为字面量

let baozhuang: stirng
// 正确
baozhuang = '1'
// 错误
baozhuang = new String(1)
​
let baozhuang: String
// 正确
baozhuang = '1'
// 正确
baozhuang = new String(1)

2.4 数组类型

  • 泛型标注

    let a: Array<number> = []
    
  • 简单标注

    let a: string[] = []
    

2.5 元组类型

  • 特点:初始化指的类型位置和数字量必须与 定义的一致。后续为数组成员添加的必须是定义的类型中的其中一种
let a: [number,string] = [1,2]

2.6 枚举类型

  • 概念:表示一组具有关联意义的数据类型。key使用大写规范。

2.6.1 数字枚举类型

  • key不能是数字类型值

  • 第一个枚举值不赋值默认为o

    enmu HTTP {
        OK  // ok 为0
        NOTFOUND = 404
        MEHTOD
    }
    
  • 非第一个枚举值不赋值,则值默认为上一个数字枚举 +1

enmu HTTP {
    OK = 1
    NOTFOUND = 404
    MEHTOD // MEHTOD = 404(NOTFOUND) + 1 = 405
}

2.6.2 字符串枚举类型

  • 如果前一个是字符串枚举类型,则后续枚举项必须赋值

    // 错误
        OK = 200 // ok 为0
        NOTFOUND = 'test'
        MEHTOD // MEHTOD必须手工赋值
    }
    

2.7 无值类型

  • 没有任何数据的类型。常用于标识函数返回值为undefined合作和函数不显示return如
function fn(): void {
    
}

2.8 Never 类型

  • 函数不可能有return ,因为抛错的时候使用
function fn(): void {
    throw new Error('ddddd')
}

2.9 任意类型

  • 变量申明为赋值且未标注类型

    let a: any
    
  • 任何类型值可以赋值给any类型

    let a: string = '1'
    let b: any
    b = a
    
  • any类型也可以赋值给任意类型

    let a: any
    let b: number
    b = a
    
  • any有任何属性和方法

注意: any类型可能出现隐身问题。如

let a: number
let b: any
a = b
a.toFixed(1) // 出错

解决方法:使用noImplicitAyn 配置为false

2.10 unknown类型

  • unknown类型只能赋值给 unknown和any

  • unknown没有任何属性和方法

  • unknown防止在其他地方乱用

    let a: unknown
    a.name // 报错
    

2.11 类型断言

  • 类型断言把宽泛的类型指定为具体的类型(类型缩小)
// <img id="why"/>

// 1.类型断言 as
const el = document.getElementById("why") as HTMLImageElement
el.src = "url地址"


// 2.另外案例: Person是Student的父类
class Person {

}

class Student extends Person {
  studying() {

  }
}

function sayHello(p: Person) {
  (p as Student).studying()
}

const stu = new Student()
sayHello(stu)

3. 接口

3.1 接口描述对象

  • 可选属性。 应用场景:适用指定某些属性是可选的

    interface Person {
        color?: string
    }
    
  • 只读属性,通过关键字readonly标识。属性值只能再初始赋值。后续不能修改

    interface Person {
        readonly color?: string
    }
    
  • 任意属性。应用场景:适用在属性的数量不定情况下

    interface Person {
        [key: string]: string
    }
    // keystring 指的是字符串形势的属性名
    

    注意:可选属性和任意属性同时存在的还是可能存在冲突,因为可选属性的值可能是undefined,这样就会和任意属性冲突,解决办法就是让可选属性增加联合类型的undefined类型

    // 报错
    interface Person {
        color?: string
        [key: string]: string
    }
    // 解决后,正确
    interface Person {
        color?: string
        [key: string]: string | undefined
    }
    

    注意:索引的类型,注意数值类型索引是字符串类型索引的子集。

    • 数字类型索引

       interface Person {
           [key: number]: string
       }
      
    • 字符串类型索引

      // 正确
      interface Person {
          [key: string]: string
      }
      let a: Person = {}
      a[0] = '333'
      
      // 错误
      interface Person {
          [key: number]: string
      }
      let a: Person = {}
      a['0'] = '333'
      

注意:字符串索引类型和数字类型同时存在时,则数值类型的值类型必须是字符串类型的值类型一致或者是它的子类型

  ```javascript
  class Parent {
      
  }
  class children extend Parent {
      
  }
  // 正确:因为children是parent的子类。
  interface Person {
      [key: string]: Parent,
      [key2: number]: children     
  }
  // 错误 因为number 不是string 的值子类型
  interface Person {
      [key: string]: string,
      [key2: number]: number     
  }
  ```

3.2 接口描述函数

interface Person {
    (a: number): number    
}
let fn: Person = function (a) {
    
}
// 注意:以下这样定义不是描述函数,而是定义一个对象里面包含函数的成员
interface Person {
    fn(a: number): number    // 一个具有number参数的返回值为number的fn函数
}
  • 应用场景1:回调函数约定

    interface Fn {
        (a: number, b: number): number    
    }
    function todo(callback: Fn): number {
        const a = callback(1,2)
        return a
    }
    
    todo(function(a, b){
        // 这样确保回到函数接收的参数正确,且todo内部需要得到的返回值也正确
        return a + b
    })
    
  • 应用场景2:事件绑定

    interface EventFN {
      (e: MouseEvent): void
    }
    function on (el: HTMLElement, event: string, callback: EventFN): void {
    
    }
    const div = document.getElementById('tt')
    // 这里可能是null 需要判断
    if (div) {
      on(div, 'click', function(e){
    
      })
    }
    

3.3 接口合并

多个同名的接口可以合并。接口合并原则:

  • 非函数成员的属性同名需要保持类型一致

  • 函数如果同名,则会重载

    // 正确
    interface Person9 {
      width: number,
      height: number
    }
    interface Person9 {
      width: number,
      height: number,
      color: string    
    }
    
    let test2: Person9 = {
      width: 100,
      height: 200,
      color: 'red'
    }
    
    // 错误示例 因为同名的width 类型不一致
    interface Person9 {
        width: number,
        height: number
    }
    interface Person9 {
        width: string,
        height: number,
        color: string    
    }
    
    let test: Person9 = {
        width: 100,
        height: 200,
        color: 'red'
    }
    

4. 高级类型

  • 联合类型(类似或)

    // 语法:类型1 | 类型2
    function test1(ele: Element, attr: string, value: number | string): void {
    
    }
    
  • 交叉类型

    interface o1 {
      width: number,
      heigth: number
    }
    
    interface o2 {
      scale: number
    }
    // o1 和 o2 合并了成为一种新的类型
    let o3: o1 & o2 = {
      width: 222,
      heigth: 333,
      scale: 555
    }
    
  • 字面量类型

    使用场景:针对不指定类型,而是指定特定值的

    function zimian (a: 'left' | 'top'): void {
      
    }
    
  • 类型别名

    适用场景:当类型比较复杂的时候,可以使用

    type xx = 'left' | 'right' | 'top' | 'bootom'
    function lxbm (a: HTMLElement, p: xx): void {
      
    }
    
  • interface 和 type 的区别:

     interface:
    
       1.只能描述对象、classfunction的类型
       2.同名interface自动合并,有利于扩展
   
     type
       1.不能重名
       2.能描述所有数据
  • 类型推导

每次显示标注类型比较麻烦,ts支持在 初始化的时候 自动对类型进行推导。推导的发生的时机如下:

  • 初始化变量
  • 设置函数参数默认值
  • 返回函数值
```
let zdtd1 = 1 // 初始化赋值推导
function zdtd2(a=1) {} //函数参数默认值
function zdtd3(a: number) { return 1} // 返回值自动推导
```
  • 类型断言

    • 是一种预判的类型,有点像类型转换
    // 默认通用的HTMLElement中没有 src属性。精确的类型才有src属性。因此断言预判为HTMLImageElement
    let lxdy = <HTMLImageElement>document.getElementById('img')
    if (lxdy) {
      console.log(lxdy.src)
    }
    

5. 函数相关

5.1 函数基础

  • 函数标注

    • 直接标注

      function zjbz(a: number): void {
      
      }
      let zjbz2: (a: number) => void = function (a) {
        
      }
      
    • 类型别名

      type zjbz3 = (a:number) => void
      let zjbz4: zjbz3 = function (a){
        
      }
      
    • 接口

         interface zjbz4 {
           (a: number): void
         }
    
         let zjbz5 = function (a){
    
         }
    
  • 可选参数

    function zjbz6(a: number, val?: number): void {
    
    }
    // 可省略掉val参数
    zjbz6(1)
    zjbz6(1,9)
    
  • 参数默认值

    • 有默认值的参数也是可选的
    • 设置了默认值的参数可以根据类型自动推导
    // 函数:默认参数
    function zjbz7(a: number, val = 'left'): void {
    
    }
    // 省去了val,有默认值是可选参数
    zjbz7(10)
    // 结合联合类型带默认值。缩小值范围
    function zjbz8(a: number, val: 'left' | 'right' = 'left'): void {
    
    }
    zjbz8(8, 'left')
    
  • 剩余参数

    type dir = {
      [key: string]: string
    }
    function sycs(target: dir, ...others: Array<dir>) {
    
    }
    

5.2 函数的this指向

函数中的this类型标注

  • 普通函数

    interface hst1 {
      a: number,
      b: string,
      fn(a: number): void
    }
    const hst11 = {
      a: 1,
      fn(this: hst1,a) {
        console.log(this)
      }
    }
    
  • 箭头函数

    interface hst2 {
      a: number,
      b: string,
      fn(a: number): void
    }
    const hst22 = {
      a: 2,
      b: 3,
      fn(this: Window,a){
        return () => {
          console.log(this)
        }
      }
    }
    

5.3 函数重载

  • 应用场景:一个函数可能接收不同的参数和不同的返回值类型,则可以使用函数重载实现。
// 函数重载的规则
function abc(a: HTMLElement, attr: 'display', val: 'block' | 'inline') 
function abc(a: HTMLElement, attr: 'width' | 'height', val: string) 
function abc(a: HTMLElement, attr: any, val?: string): void {
   
    
}
// 相当于重载的函数式一个函数的不同规则实现,因为可能参数与参数之间存在某种关联的组合关系,如display只能和 'block'等组合。

6. 类

6.1 类基础

  • class

  • constructor

  1. 构造函数会在类实例化的时候调用
  2. 定义的构造函数会覆盖默认的构造函数
  3. 实例化如果没有送入参数可以省略圆括号。如 new User
  4. constructor不允许有return和返回值的类型标注
  • 成员属性、方法

  • this关键字

// 正常的定义
class User {
    // 属性
    age: number;
    name: string;
    constructor(name: string, age: number) {
        this.age  = age
        this.name = name
    }
}
// 简化写法。使用关键字 public 。ts会自动定义属性并赋值。public表示该类的所有地方都可以读写改属性
class User {
    // 属性
    constructor(public name: string,public age: number) {
        // 加上public等同于 定义属性 + 赋值。
    }
}

6.2 类的继承

  • 继承语法

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        
    }
    
  • 子类没有重写constructor方法,则会默认的constructor中调用super

  • 子类重写了constructor方法,则需要主动地调用super继承父类的属性

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            super(age, name)
        }
    }
    
  • 子类自由在调用了super后才可以访问到this

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            console.log(this) // 报错
            super(age, name)
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            super(age, name)
            console.log(this) // 正确
        }
    }
    
  • 通过super访问父类的属性和方法

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
        getAge() {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            super(age, name)
        }
        getStudentAge() {
            return super.getAge()
        }
    }
    
  • 子类重写父类方法

    当参数的个数和类型一致,才重写

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
        getAge(lastYear: number) {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            super(age, name)
        }
        getAge(lastYear: number) {
            return this.lasyYear
        }
    }
    
  • 子类重载父类方法

    // 父类
    class Parent {
        constructor(public age: number, public name: string){
            
        }
        getAge(lastYear: number) {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        constructor(age: number, name: string, public score: number) {
            super(age, name)
        }
        // 重载
        getAge(lastYear: number,gender: string, color: string);
        getAge(lastYear: number,gender: string);
        getAge(lastYear: number,gender: string, color?: string) {
            super.getAge(lastYear)
            if (gender) {
                console.log(gender)
            }
            return this.lasyYear
        }
    }
    

6.3 访问修饰符和寄存器

6.3.1 修饰符

  • public

    访问级别:外部、自身、子类

    class Parent {
        constructor(public age: number, public name: string){
            
        }
        // 方法模式是public
        getAge(lastYear: number) {
            return this.age
        }
    }
    
  • protected

    访问级别:自身、子类

    class Parent {
        constructor(public age: number, public name: string){
            
        }
        // 方法模式是public
        getAge(lastYear: number) {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        method1() {
            this.age // 可以访问
        }
    }
    
  • private

    访问级别:自身

    class Parent {
        constructor(public age: number, public name: string){
            
        }
        // 方法模式是public
        getAge(lastYear: number) {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        method1() {
            this.age // 出错,不可以访问
        }
    }
    
  • readonly

    访问级别:都可以,只不过是只读的

    class Parent {
        constructor(public age: number, public name: string){
            
        }
        // 方法模式是public
        getAge(lastYear: number) {
            return this.age
        }
    }
    // 子类 继承父类
    class Child extends Parent {
        method1() {
            this.age = 222 // 不可以修改。只读
        }
    }
    

6.3.2 寄存器

  • 应用场景:对属性赋值具有约束性,或者对访问属性需要经过一定处理返回。(如输入密码需要指定几位,返回密码需要返回带*)
class Parent {
    constructor(public age: number, public name: string){
        
    }
    // password属性的set方法
    set password(password: string){
        if (password.length >= 6) {
            this.password = password
        }
    }
    get password(): string {
        return '*****'
    }
}

6.4 类的静态成员

  • 判断类的静态成员方法:

    • 区分是实例化对象上的特征,还是全局的特征与实例无关(静态)。
    • 当成员方法没有依赖this,那么它就可以鬼为静态
  • 使用方法

    // 定义
    class Parent {
        // 属性
        static readonly test: number;
        constructor(public age: number, public name: string){
            
        }
        // 方法
        static method1(){
            
        }
    }
    // 使用
    Parent.stest;
    Parent.method1()
    

6.5 抽象

  • 应用场景:基类的某些行为(实例方法)无法确定,而是由继承的子类实现。
// 定义
abstract class Parent {
    age: number; 
    constructor(age: number){
        this.age = age
    }
    // 该方法又子类继承,但是又无法确定具体的行为,即是有结构没有实现
    abstract getAge(): void;
}
class Child extends Parent {
    constructor(age: number){
        super(age);
    }
    // 子类方法实现
    getAge(){
        
    }
}
  • 抽象abstract的注意事项

    • 当类的方法被定义为抽象后,其所在的类也需要被定义为abstract
    • abstract的方法,只能是定义不能有实现
    • 如果一个类是abstract,则不能通过new实例化
    • 如果一个子类继承了抽象类,则子类必须实现抽象类中的所有抽象方法,否则该子类还得声明为抽象类

6.6 类和接口

  • 概述

    • 接口:是定义了对象的结构和契约。当一个抽象类全都是抽象(没有实现或者初始化)它也和接口一样。
    • 抽象类:更适合带有部分初始化的实现,里面抽象的方法由具体的类实现。
  • 类和接口区别:

    • 抽象类编译后产生实体代码,而接口则不会
    • ts只支持单继承,一个子类只能有一个父类,但是一个类可以实现多个接口
    • 接口不能有实现,抽象类可以有实现。
  • 类中使用接口。使用implements

    个人理解:这个就是类对某种共性的特征(表现为方法或者属性)进行了约束。意义在于限定类在设计的过程当中遵循某种规则

    疑问(需验证):

    1. 为什么不直接使用抽象类来约束。

      因为有时候不希望所有子类都被影响到,可能希望某几个子类有共同的某个约束。

    类实现接口,通过此关键字实现,implements有几个特征

    • 如果一个类implements了一个接口,则必须实现改接口中定义的契约
    • 多个接口可以使用逗号分隔,
    • implements与extends可以同时存在

    参考文章理解:www.cnblogs.com/xjy20170907…

    实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

    举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它

    interface Alarm {
       alert();
    }
    
    class Door {
    }
    
    class SecurityDoor extends Door implements Alarm {
       alert() {
           console.log('SecurityDoor alert');
       }
    }
    
    class Car implements Alarm {
       alert() {
           console.log('Car alert');
       }
    }
    

7. 泛型

7.1 基本使用

  • 概括:在定义这个函数时, 我不决定这些参数的类型,而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
function foo<T>(num1: T): T {
	return num
}

调用方式一:明确的传入类型
	foo<number>(20)
	foo<{name: string}>({name: rth})
	foo<any[]>([20])

调用方式二:类型推导
	foo(50)
	foo('abc')

7.2 泛型接受类型参数

  • 参数名可以自定义:一般用T, O代表对象
function foo<T, E, O>(arg1: T, arg2: E, arg3: O, ...args: T[]) {}

foo<number,string, boolean>(10, 'abc', true)

7.3 泛型接口的使用

interface IPerson<T1=string, T2=number>{
	name: T1
	age: T2
}
const p: IPerson = {
	name: 'rth',
	age: 18
}

7.4 泛型类的使用

class Point<T> {
	x: T
	y: T
	z: T
	constructor(x: T, y: T, z: T) {
		this.x = x
		this.y = y
		this.z = z
	}
}
const p1 = new Point("1", "2", "3")
const p2 = new Point<string>("1", "2", "3")
const p3: Point<string> = new Point("1", "2", "3")

const names1: string[] = ["abc", "cba", "nba"]
const names1: Array<string> = ["abc", "cba", "nba"]  // 不推荐(react jsx <>会冲突)

7.5 泛型的类型约束

interface Ilength {
	length: number
}
function getLength<T extends Ilength>(arg: T) {
	return arg.length
}
getLenth("abc")
getgetLength(["abc", "cba"])
getLength({length: 100})

7.6 非空判断运算符

  • 类似三元运算符,为true就返回左边,false返回右边
const flag = "" ?? true
console.log(flag)

最后

  • 非常感谢你看到最后,最后再说两点~

    ①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
    ②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
    (文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)