XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • leaflet改变坐标原点
  • 补间动画gsap与tween
  • 文字转语音mp3文件
  • JavaScript引入
  • JavaScript高级程序设计阅读笔记
  • Javascript函数参数按值传递传递
  • JS防抖和节流
  • 手写JS常用方法
  • 手写Promise
  • JS Event Loop
  • 重排和重绘
  • em、px、rem、vh、vw 区别
  • css也需要性能优化
  • 图解 js 原型链
  • js函数参数按值传递
  • BOM浏览器对象模型
  • DOM
  • 事件
  • js对象数组sort按需排序
  • 文件上传功能技术选型和前后端实现
  • 前端图片处理
  • 让你的JavaScript代码简单又高效
  • BEM:class命名规范
  • 前端规范-CSS属性那么多(杂),怎么排序
  • TypeScript Tips
  • jsx
  • canvas基础
  • 前端日志
  • 浏览器存储
  • CSS世界阅读笔记
  • CSS揭秘阅读笔记
  • js变量命名常用规范词
  • 你不知道的JavaScript阅读笔记
  • js对象的底层数据结构
  • js判断数据类型
  • JS浮点数精度问题和解决办法
  • js作用域
  • js堆栈溢出和内存泄漏
  • 浏览器是如何渲染页面的
  • 疑难杂症和踩坑问题合集
  • 免费在线API收集
  • 原生JS实现Ajax请求
  • cookie、session、localStorage、sessionStorage的区别
  • Sass与Less
  • arrayBuffer、blob、file对象
  • TypeScript基础
    • 前端
    XingYun
    2022-04-22
    目录

    TypeScript基础

    TypeScript 基础相关

    # TS 和 JS

    TS 和 JS 之间的关系其实就是 Less/Sass 和 CSS 之间的关系

    就像 Less/Sass 是对 CSS 进行扩展一样, TS 也是对 JS 进行扩展

    就像 Less/Sass 最终会转换成 CSS 一样, 我们编写好的 TS 代码最终也会换成 JS

    TypeScript 是 JavaScript 的超集,因为它扩展了 JavaScript,有 JavaScript 没有的东西。

    硬要以父子类关系来说的话,TypeScript 是 JavaScript 子类,继承的基础上去扩展。

    TypeScript 是 JavaScript 的一个超集

    TypeScript 最终将转换为 JavaScript 运行

    # 类型

    基础类型

    //  Boolean 类型
    let isDone: boolean = false
    // ES5:var isDone = false;
    
    //  Number 类型
    let count: number = 10
    // ES5:var count = 10;
    
    // String 类型
    let name: string = 'semliker'
    // ES5:var name = 'semlinker';
    
    // Symbol 类型
    const sym = Symbol()
    let obj = {
      [sym]: 'semlinker'
    }
    console.log(obj[sym]) // semlinker
    
    // Array 类型
    let list: number[] = [1, 2, 3]
    // ES5:var list = [1,2,3];
    let list: Array<number> = [1, 2, 3] // Array<number>泛型语法
    // ES5:var list = [1,2,3];
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    Enum 类型

    使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。

    数字枚举

    enum Direction {
      NORTH,
      SOUTH,
      EAST,
      WEST
    }
    
    let dir: Direction = Direction.NORTH
    
    1
    2
    3
    4
    5
    6
    7
    8

    默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,
    Direction.SOUTH 的值为 1
    Direction.EAST 的值为 2
    Direction.WEST 的值为 3

    编译后的代码

    'use strict'
    var Direction
    ;(function(Direction) {
      Direction[(Direction['NORTH'] = 0)] = 'NORTH'
      Direction[(Direction['SOUTH'] = 1)] = 'SOUTH'
      Direction[(Direction['EAST'] = 2)] = 'EAST'
      Direction[(Direction['WEST'] = 3)] = 'WEST'
    })(Direction || (Direction = {}))
    var dir = Direction.NORTH
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:

    let dirName = Direction[0] // NORTH
    let dirVal = Direction['NORTH'] // 0
    
    1
    2

    字符串枚举

    在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

    enum Direction {
      NORTH = 'NORTH',
      SOUTH = 'SOUTH',
      EAST = 'EAST',
      WEST = 'WEST'
    }
    
    1
    2
    3
    4
    5
    6

    复制代码
    以上代码对应的 ES5 代码如下:

    'use strict'
    var Direction
    ;(function(Direction) {
      Direction['NORTH'] = 'NORTH'
      Direction['SOUTH'] = 'SOUTH'
      Direction['EAST'] = 'EAST'
      Direction['WEST'] = 'WEST'
    })(Direction || (Direction = {}))
    
    1
    2
    3
    4
    5
    6
    7
    8

    Any 类型

    在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。

    let notSure: any = 666
    notSure = 'semlinker'
    notSure = false
    
    let value: any
    
    value.foo.bar // OK
    value.trim() // OK
    value() // OK
    new value() // OK
    value[0][1] // OK
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 引入了 unknown 类型。

    Unknown 类型

    let value: unknown
    
    value = true // OK
    value = 42 // OK
    value = 'Hello World' // OK
    value = [] // OK
    value = {} // OK
    value = Math.random // OK
    
    1
    2
    3
    4
    5
    6
    7
    8

    对 value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?

    let value: unknown
    
    let value1: unknown = value // OK
    let value2: any = value // OK
    let value3: boolean = value // Error
    let value4: number = value // Error
    let value5: string = value // Error
    let value6: object = value // Error
    let value7: any[] = value // Error
    let value8: Function = value // Error
    
    value.foo.bar // Error
    value.trim() // Error
    value() // Error
    new value() // Error
    value[0][1] // Error
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    将 value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

    Tuple 类型

    众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。

    let tupleType: [string, boolean]
    tupleType = ['semlinker', true]
    
    1
    2

    Void 类型

    某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:

    // 声明函数返回值为void
    function warnUser(): void {
      console.log('This is my warning message')
    }
    
    1
    2
    3
    4

    需要注意的是,声明一个 void 类型的变量没有什么作用,因为在严格模式下,它的值只能为 undefined:

    let unusable: void = undefined
    
    1

    Null 和 Undefined 类型 和 js 一致

    Object 类型

    Object 类型:它是所有 Object 类的实例的类型,它由以下两个接口来定义:

    Object 接口定义了 Object.prototype 原型对象上的属性;

    // node_modules/typescript/lib/lib.es5.d.ts
    interface Object {
      constructor: Function
      toString(): string
      toLocaleString(): string
      valueOf(): Object
      hasOwnProperty(v: PropertyKey): boolean
      isPrototypeOf(v: Object): boolean
      propertyIsEnumerable(v: PropertyKey): boolean
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    ObjectConstructor 接口定义了 Object 类的属性。

    // node_modules/typescript/lib/lib.es5.d.ts
    interface ObjectConstructor {
      /** Invocation via `new` \*/
      new (value?: any): Object
      /** Invocation via function calls \*/
      (value?: any): any
      readonly prototype: Object
      getPrototypeOf(o: any): any
      // ···
    }
    declare var Object: ObjectConstructor
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    Object 类的所有实例都继承了 Object 接口中的所有属性。

    Never 类型

    never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

    // 返回never的函数必须存在无法达到的终点
    function error(message: string): never {
      throw new Error(message)
    }
    
    function infiniteLoop(): never {
      while (true) {}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:

    type Foo = string | number
    function controlFlowAnalysisWithNever(foo: Foo) {
      if (typeof foo === 'string') {
        // 这里 foo 被收窄为 string 类型
      } else if (typeof foo === 'number') {
        // 这里 foo 被收窄为 number 类型
      } else {
        // foo 在这里是 never
        const check: never = foo
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:

    type Foo = string | number | boolean
    
    1

    然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保

    controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

    TypeScript 断言

    有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

    通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

    类型断言

    类型断言有两种形式:

    // 1.“尖括号” 语法
    let someValue: any = 'this is a string'
    let strLength: number = (<string>someValue).length
    
    // 2.as 语法
    let someValue: any = 'this is a string'
    let strLength: number = (someValue as string).length
    
    1
    2
    3
    4
    5
    6
    7

    非空断言

    在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
    那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景。

    1.忽略 undefined 和 null 类型

    function myFunc(maybeString: string | undefined | null) {
      // Type 'string | null | undefined' is not assignable to type 'string'.
      // Type 'undefined' is not assignable to type 'string'.
      const onlyString: string = maybeString // Error
      const ignoreUndefinedAndNull: string = maybeString! // Ok
    }
    
    1
    2
    3
    4
    5
    6

    2.调用函数时忽略 undefined 类型

    type NumGenerator = () => number
    
    function myFunc(numGenerator: NumGenerator | undefined) {
      // Object is possibly 'undefined'.(2532)
      // Cannot invoke an object which is possibly 'undefined'.(2722)
      const num1 = numGenerator() // Error
      const num2 = numGenerator!() //OK
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    3.确定赋值断言

    在 TypeScript 2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:

    let x: number
    initialize()
    // Variable 'x' is used before being assigned.(2454)
    console.log(2 * x) // Error
    
    function initialize() {
      x = 10
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:

    let x!: number;
    initialize();
    console.log(2 \* x); // Ok
    
    function initialize() {
    x = 10;
    }
    
    1
    2
    3
    4
    5
    6
    7

    通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

    类型守卫

    类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:

    1. in 关键字
    interface Admin {
      name: string
      privileges: string[]
    }
    
    interface Employee {
      name: string
      startDate: Date
    }
    
    type UnknownEmployee = Employee | Admin
    
    function printEmployeeInformation(emp: UnknownEmployee) {
      console.log('Name: ' + emp.name)
      if ('privileges' in emp) {
        console.log('Privileges: ' + emp.privileges)
      }
      if ('startDate' in emp) {
        console.log('Start Date: ' + emp.startDate)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    1. typeof 关键字
    function padLeft(value: string, padding: string | number) {
      if (typeof padding === 'number') {
        return Array(padding + 1).join(' ') + value
      }
      if (typeof padding === 'string') {
        return padding + value
      }
      throw new Error(`Expected string or number, got '${padding}'.`)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,

    "typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

    联合类型和类型别名

    联合类型通常与 null 或 undefined 一起使用:

    const sayHello = (name: string | undefined) => {
      /* ... */
    }
    
    1
    2
    3

    例如,这里 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给 sayHello 函数。

    sayHello('semlinker')
    sayHello(undefined)
    
    1
    2

    通过这个示例,你可以凭直觉知道类型 A 和类型 B 联合后的类型是同时接受 A 和 B 值的类型。此外,对于联合类型来说,你可能会遇到以下的用法:

    let num: 1 | 2 = 1
    type EventNames = 'click' | 'scroll' | 'mousemove'
    
    1
    2

    以上示例中的 1、2 或 'click' 被称为字面量类型,用来约束取值只能是某几个值中的一个。

    # 函数

    TypeScript 函数与 JavaScript 函数的区别

    参数类型和返回类型

    function createUserId(name: string, id: number): string {
      return name + id
    }
    
    1
    2
    3

    函数类型

    let IdGenerator: (chars: string, nums: number) => string
    
    function createUserId(name: string, id: number): string {
      return name + id
    }
    
    IdGenerator = createUserId
    
    1
    2
    3
    4
    5
    6
    7

    可选参数及默认参数

    // 可选参数
    function createUserId(name: string, id: number, age?: number): string {
      return name + id
    }
    
    // 默认参数
    function createUserId(
      name: string = 'semlinker',
      id: number,
      age?: number
    ): string {
      return name + id
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    在声明函数时,可以通过 ? 号来定义可选参数,比如 age?: number 这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。

    剩余参数

    function push(array, ...items) {
      items.forEach(function(item) {
        array.push(item)
      })
    }
    
    let a = []
    push(a, 1, 2, 3)
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 接口 interface

    对象的形状

    interface Person {
      name: string
      age: number
    }
    
    let semlinker: Person = {
      name: 'semlinker',
      age: 33
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    可选 | 只读属性

    interface Person {
      readonly name: string
      age?: number
    }
    
    1
    2
    3
    4

    只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。

    let a: number[] = [1, 2, 3, 4]
    let ro: ReadonlyArray<number> = a
    ro[0] = 12 // error!
    ro.push(5) // error!
    ro.length = 100 // error!
    a = ro // error!
    
    1
    2
    3
    4
    5
    6

    任意属性

    有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。

    interface Person {
      name: string
      age?: number
      [propName: string]: any
    }
    
    const p1 = { name: 'semlinker' }
    const p2 = { name: 'lolo', age: 5 }
    const p3 = { name: 'kakuqo', sex: 1 }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 类

    类的属性与方法
    在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。

    在 TypeScript 中,我们可以通过 Class 关键字来定义一个类:

    class Greeter {
      // 静态属性
      static cname: string = 'Greeter'
      // 成员属性
      greeting: string
    
      // 构造函数 - 执行初始化操作
      constructor(message: string) {
        this.greeting = message
      }
    
      // 静态方法
      static getClassName() {
        return 'Class name is Greeter'
      }
    
      // 成员方法
      greet() {
        return 'Hello, ' + this.greeting
      }
    }
    
    let greeter = new Greeter('world')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    那么成员属性与静态属性,成员方法与静态方法有什么区别呢?这里无需过多解释,我们直接看一下编译生成的 ES5 代码:

    'use strict'
    var Greeter = /** @class */ (function() {
      // 构造函数 - 执行初始化操作
      function Greeter(message) {
        this.greeting = message
      }
      // 静态方法
      Greeter.getClassName = function() {
        return 'Class name is Greeter'
      }
      // 成员方法
      Greeter.prototype.greet = function() {
        return 'Hello, ' + this.greeting
      }
      // 静态属性
      Greeter.cname = 'Greeter'
      return Greeter
    })()
    var greeter = new Greeter('world')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    访问器

    在 TypeScript 中,我们可以通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据。

    let passcode = 'Hello TypeScript'
    
    class Employee {
      private _fullName: string
    
      get fullName(): string {
        return this._fullName
      }
    
      set fullName(newName: string) {
        if (passcode && passcode == 'Hello TypeScript') {
          this._fullName = newName
        } else {
          console.log('Error: Unauthorized update of employee!')
        }
      }
    }
    
    let employee = new Employee()
    employee.fullName = 'Semlinker'
    if (employee.fullName) {
      console.log(employee.fullName)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    抽象类
    使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法:

    abstract class Person {
      constructor(public name: string) {}
    
      abstract say(words: string): void
    }
    
    // Cannot create an instance of an abstract class.(2511)
    const lolo = new Person() // Error
    
    1
    2
    3
    4
    5
    6
    7
    8

    抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。具体如下所示:

    abstract class Person {
      constructor(public name: string) {}
    
      // 抽象方法
      abstract say(words: string): void
    }
    
    class Developer extends Person {
      constructor(name: string) {
        super(name)
      }
    
      say(words: string): void {
        console.log(`${this.name} says ${words}`)
      }
    }
    
    const lolo = new Developer('lolo')
    lolo.say('I love ts!') // lolo says I love ts!
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 泛型

    软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

    在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

    设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。

    泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

    泛型语法

    对于刚接触 TypeScript 泛型的读者来说,首次看到 <T> 语法会感到陌生。其实它没有什么特别,就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。

    参考上面的图片,当我们调用 identity<Number>(1) ,Number 类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。图中 <T> 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。

    其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

    • K(Key):表示对象中的键类型;
    • V(Value):表示对象中的值类型;
    • E(Element):表示元素类型。

    其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:

    function identity<T, U>(value: T, message: U): T {
      console.log(message)
      return value
    }
    
    console.log(identity<Number, string>(68, 'Semlinker'))
    
    1
    2
    3
    4
    5
    6

    除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

    function identity<T, U>(value: T, message: U): T {
      console.log(message)
      return value
    }
    
    console.log(identity(68, 'Semlinker'))
    
    1
    2
    3
    4
    5
    6

    泛型接口

    interface GenericIdentityFn<T> {
      (arg: T): T
    }
    
    1
    2
    3

    泛型类

    class GenericNumber<T> {
      zeroValue: T
      add: (x: T, y: T) => T
    }
    
    let myGenericNumber = new GenericNumber<number>()
    myGenericNumber.zeroValue = 0
    myGenericNumber.add = function(x, y) {
      return x + y
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 装饰器

    装饰器是什么

    它是一个表达式
    该表达式被执行后,返回一个函数
    函数的入参分别为 target、name 和 descriptor
    执行该函数后,可能返回 descriptor 对象,用于配置 target 对象

    装饰器的分类

    类装饰器(Class decorators)
    属性装饰器(Property decorators)
    方法装饰器(Method decorators)
    参数装饰器(Parameter decorators)

    类装饰器
    类装饰器声明:

    declare type ClassDecorator = <TFunction extends Function>(
      target: TFunction
    ) => TFunction | void
    
    1
    2
    3

    类装饰器顾名思义,就是用来装饰类的。它接收一个参数:

    target: TFunction - 被装饰的类

    来个例子

    function Greeter(target: Function): void {
      target.prototype.greet = function(): void {
        console.log('Hello Semlinker!')
      }
    }
    
    @Greeter
    class Greeting {
      constructor() {
        // 内部实现
      }
    }
    
    let myGreeting = new Greeting()
    ;(myGreeting as any).greet() // console output: 'Hello Semlinker!';
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法糖,来使用装饰器。

    友情提示:读者可以直接复制上面的代码,在 TypeScript Playground 中运行查看结果。

    你可能想问,例子中总是输出 Hello Semlinker! ,能自定义输出的问候语么 ?这个问题很好,答案是可以的。

    具体实现如下:

    function Greeter(greeting: string) {
      return function(target: Function) {
        target.prototype.greet = function(): void {
          console.log(greeting)
        }
      }
    }
    
    @Greeter('Hello TS!')
    class Greeting {
      constructor() {
        // 内部实现
      }
    }
    
    let myGreeting = new Greeting()
    ;(myGreeting as any).greet() // console output: 'Hello TS!';
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    属性装饰器

    属性装饰器声明:

    declare type PropertyDecorator = (
      target: Object,
      propertyKey: string | symbol
    ) => void
    
    1
    2
    3
    4

    属性装饰器顾名思义,用来装饰类的属性。它接收两个参数:

    target: Object - 被装饰的类
    propertyKey: string | symbol - 被装饰类的属性名

    趁热打铁,马上来个例子热热身:

    function logProperty(target: any, key: string) {
      delete target[key]
    
      const backingField = '_' + key
    
      Object.defineProperty(target, backingField, {
        writable: true,
        enumerable: true,
        configurable: true
      })
    
      // property getter
      const getter = function(this: any) {
        const currVal = this[backingField]
        console.log(`Get: ${key} => ${currVal}`)
        return currVal
      }
    
      // property setter
      const setter = function(this: any, newVal: any) {
        console.log(`Set: ${key} => ${newVal}`)
        this[backingField] = newVal
      }
    
      // Create new property with getter and setter
      Object.defineProperty(target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
      })
    }
    
    class Person {
      @logProperty
      public name: string
    
      constructor(name: string) {
        this.name = name
      }
    }
    
    const p1 = new Person('semlinker')
    p1.name = 'kakuqo'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    以上代码我们定义了一个 logProperty 函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果:

    
    Set: name => semlinker
    Set: name => kakuqo
    
    1
    2
    3

    方法装饰器
    方法装饰器声明:

    declare type MethodDecorator = <T>(
      target: Object,
      propertyKey: string | symbol,
      descriptor: TypePropertyDescript<T>
    ) => TypedPropertyDescriptor<T> | void
    
    1
    2
    3
    4
    5

    方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:
    target: Object - 被装饰的类
    propertyKey: string | symbol - 方法名
    descriptor: TypePropertyDescript - 属性描述符
    废话不多说,直接上例子:

    function log(
      target: Object,
      propertyKey: string,
      descriptor: PropertyDescriptor
    ) {
      let originalMethod = descriptor.value
      descriptor.value = function(...args: any[]) {
        console.log('wrapped function: before invoking ' + propertyKey)
        let result = originalMethod.apply(this, args)
        console.log('wrapped function: after invoking ' + propertyKey)
        return result
      }
    }
    
    class Task {
      @log
      runTask(arg: any): any {
        console.log('runTask invoked, args: ' + arg)
        return 'finished'
      }
    }
    
    let task = new Task()
    let result = task.runTask('learn ts')
    console.log('result: ' + result)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    复制代码
    以上代码成功运行后,控制台会输出以下结果:

    
    "wrapped function: before invoking runTask"
    "runTask invoked, args: learn ts"
    "wrapped function: after invoking runTask"
    "result: finished"
    
    1
    2
    3
    4
    5

    参数装饰器
    参数装饰器声明:

    declare type ParameterDecorator = (
      target: Object,
      propertyKey: string | symbol,
      parameterIndex: number
    ) => void
    
    1
    2
    3
    4
    5

    参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:

    target: Object - 被装饰的类
    propertyKey: string | symbol - 方法名
    parameterIndex: number - 方法中参数的索引值

    function Log(target: Function, key: string, parameterIndex: number) {
      let functionLogged = key || target.prototype.constructor.name
      console.log(
        `The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`
      )
    }
    
    class Greeter {
      greeting: string
      constructor(@Log phrase: string) {
        this.greeting = phrase
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    以上代码成功运行后,控制台会输出以下结果:

    "The parameter in position 0 at Greeter has been decorated"
    
    
    1
    2

    # 编译

    tsconfig.json 的作用

    用于标识 TypeScript 项目的根路径;
    用于配置 TypeScript 编译器;
    用于指定编译的文件。

    tsconfig.json 重要字段

    files - 设置要编译的文件的名称;
    include - 设置需要进行编译的文件,支持路径模式匹配;
    exclude - 设置无需进行编译的文件,支持路径模式匹配;
    compilerOptions - 设置与编译流程相关的选项。
    15.3 compilerOptions 选项
    compilerOptions 支持很多选项,常见的有 baseUrl、 target、baseUrl、 moduleResolution 和 lib 等。

    compilerOptions 每个选项的详细说明如下:

    {
      "compilerOptions": {
        /* 基本选项 */
        "target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
        "module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
        "lib": [], // 指定要包含在编译中的库文件
        "allowJs": true, // 允许编译 javascript 文件
        "checkJs": true, // 报告 javascript 文件中的错误
        "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
        "declaration": true, // 生成相应的 '.d.ts' 文件
        "sourceMap": true, // 生成相应的 '.map' 文件
        "outFile": "./", // 将输出文件合并为一个文件
        "outDir": "./", // 指定输出目录
        "rootDir": "./", // 用来控制输出目录结构 --outDir.
        "removeComments": true, // 删除编译后的所有的注释
        "noEmit": true, // 不生成输出文件
        "importHelpers": true, // 从 tslib 导入辅助工具函数
        "isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
    
        /* 严格的类型检查选项 */
        "strict": true, // 启用所有严格类型检查选项
        "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
        "strictNullChecks": true, // 启用严格的 null 检查
        "noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
        "alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
    
        /* 额外的检查 */
        "noUnusedLocals": true, // 有未使用的变量时,抛出错误
        "noUnusedParameters": true, // 有未使用的参数时,抛出错误
        "noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
        "noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
    
        /* 模块解析选项 */
        "moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
        "baseUrl": "./", // 用于解析非相对模块名称的基目录
        "paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
        "rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
        "typeRoots": [], // 包含类型声明的文件列表
        "types": [], // 需要包含的类型声明文件名列表
        "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
    
        /* Source Map Options */
        "sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
        "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
        "inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
        "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
    
        /* 其他选项 */
        "experimentalDecorators": true, // 启用装饰器
        "emitDecoratorMetadata": true // 为装饰器提供元数据的支持
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    #TypeScript
    上次更新: 2023/04/05, 09:41:10
    arrayBuffer、blob、file对象

    ← arrayBuffer、blob、file对象

    最近更新
    01
    JavaScript-test
    07-20
    02
    二维码的原理
    07-20
    03
    利用ChatGPT优化代码
    07-20
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式