TypeScript Js

2022-12-19 约 6715 字 阅读时长14 分钟

TypeScript

初始

TypeScript 的介绍

TypeScript是一种由微软开发的开源、跨平台的编程语言。它是JavaScript的超集,最终会被编译为JavaScript代码

2012年10月,微软发布了首个公开版本的TypeScript,2013年6月19日,在经历了一个预览版之后微软正式发布了正式版TypeScript

TypeScript的作者是安德斯·海尔斯伯格,C#的首席架构师。它是开源和跨平台的编程语言

TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以运行在TypeScript环境中

TypeScript是为大型应用的开发而设计,并且可以编译为JavaScript

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6+ 的支持**,它由 Microsoft 开发,代码开源于 GitHub 上

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统对 ES6+ 的支持,它由 Microsoft 开发,代码开源于 GitHub (opens new window)

总结:TypeScript 在社区的流行度越来越高,它非常适用于一些大型项目,也非常适用于一些基础库,极大地帮助我们提升了开发效率和体验

特点

TypeScript 主要有 3 大特点:

  • 始于JavaScript,归于JavaScript

    TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的JavaScript 引擎中。

  • 强大的类型系统

    *类型系统**允许 JavaScript 开发者在开发 JavaScript 应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。

  • 先进的 JavaScript

    TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件

安装

  1. 全局安装

    bash
    1npm install -g typescript
  2. 安装完成后,在控制台运行如下命令,检查安装是否成功

    bash
    1tsc -V

hello TS

hello TS

  1. 编写01_hello.ts文件

    typescript
    1(()=>{
    2    // string 表示参数str为string类型
    3    function sayHello(str:string){
    4        console.log("Hello "+str)
    5    }
    6    let str='TypeScript'
    7    sayHello(str)
    8})()
  2. 打开控制台,编译ts文件;编译成功后同级目录下会多出一个.js文件

    bash
    1tsc 01_hello.ts
  3. 新建index.html,引入编译后的 js 脚本

    html
     1<!DOCTYPE html>
     2<html lang="en">
     3<head>
     4    <meta charset="UTF-8">
     5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
     6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     7    <script src="./01_hello.ts" ></script>
     8    <title>Document</title>
     9</head>
    10<body>
    11</body>
    12</html>
  4. 打开浏览器,可以看到控制台输出了:Hello TypeScript

总结

  • 如果 ts 文件中不包含 ts 特有语法,则 html 引入浏览器可直接执行
  • 如果 ts 文件中包含 ts 特有语法,浏览器执行报错
  • ts 编译为js代码后,let 修饰的变量会变为 var 修饰

vscode 中自动编译

  1. 使用命令生成配置文件tsconfig.json

    bash
    1tsc --init
  2. 修改配置文件中以下内容

    json
    1{
    2  "compilerOptions": {
    3	 "outDir": "./js",                  /* 编译后的js文件保存目录 */
    4      "strict": false,                /* 不启用严格模式 */
    5}
  3. 然后在菜单栏 终端-->运行任务-->tsc:监视 运行监视任务即可

  4. 此时编写 ts 文件会自动编译,并保存在当前项目的 js 目录下

类型注解

类型注解是 TypeScript 语法,是一种轻量级的为函数或变量添加约束的方式;如下:

typescript
1(()=>{
2    function sayHello(str:string){
3        console.log("Hello "+str)
4    }
5    let str='TypeScript'
6    sayHello(str)
7})()

sayHello 函数入参,使用了类型注解,此时如果调用时传入函数参数不是 string 类型编译器就会报错

并且进行编译时就会抛错:

bash
16:14 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

总结:TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解

接口和类的演示

接口

接口是一种约束,TypeScript中也可以使用类型注解对接口进行实时的代码分析,如下:

typescript
 1(()=>{
 2    interface Person{
 3        firstName:string
 4        lastName:string
 5    }
 6    function getName(person:Person){
 7        return person.firstName + person.lastName
 8    }
 9    const p1={
10        firstName:'唐',
11        lastName:'每天八杯水'
12    }
13    // 如果这里传入参数p1的结构不满足接口约定,则会抛错
14    console.log(getName(p1))
15})()

这样使用,在 TS 中编写代码时会有提示检查

typescript
 1(()=>{
 2    class User{
 3        firstName:string
 4        lastName:string
 5        fullName:string
 6
 7        //定义构造函数
 8        constructor(firstName:string,lastName:string){
 9            this.firstName=firstName
10            this.lastName=lastName
11            this.fullName=firstName+'_'+lastName
12        }
13    }
14
15    function getName(person:User){
16        return person.fullName
17    }
18
19    const u1=new User('唐','每天八杯水')
20
21    console.log(getName(u1))
22
23})()

总结:查看编译后的js文件,可以看到看到 TypeScript 里的类只是一个语法糖,本质上还是 JavaScript 函数的实现

常用语法

数据类型

TypeScript 支持与 JavaScript 几乎相同的数据类型

布尔值

最基本的数据类型就是简单的 true/false 值,在JavaScript 和 TypeScript 里叫做 boolean

typescript
1let isDone: boolean = false;
2isDone = true;
3// isDone = 2 // error

数字

和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。

typescript
1let a1: number = 10 // 十进制
2let a2: number = 0b1010  // 二进制
3let a3: number = 0o12 // 八进制
4let a4: number = 0xa // 十六进制

字符串

和 JavaScript 一样,可以使用双引号(")或单引号(')表示字符串

typescript
1let name:string = 'tom'
2name = 'jack'
3// name = 12 // error
4let age:number = 12
5const info = `My name is ${name}, I am ${age} years old!`

undefined 和 null

TypeScript 里,undefinednull 两者各自有自己的类型分别叫做 undefinednull;默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 nullundefined 赋值给 number 类型的变量

typescript
1let num1: number = 1
2num1 = undefined  // 取消严格模式检测
3let u: undefined = undefined
4let n: null = null

数组

TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[]

typescript
1// 方式一
2let list1: number[] = [1, 2, 3]
3// 方式二
4let list2: Array<number> = [1, 2, 3]

元组 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

typescript
1let t1: [string, number]
2t1 = ['hello', 10] // OK
3t1 = [10, 'hello'] // Error

枚举

enum 类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字

默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数值

枚举类型提供的一个便利是你可以由枚举的值得到它的名字

typescript
 1enum Color {
 2  Red = 1,
 3  Green,
 4  Blue
 5}
 6
 7// 枚举数值默认从0开始依次递增
 8// 根据特定的名称得到对应的枚举数值
 9let myColor: Color = Color.Green  // 2
10let colorName: string = Color[2]  // Green

any

any 类型,类型检查器不进行检查而是直接让它们通过编译阶段的检查

typescript
1let notSure: any = 4
2notSure = 'maybe a string'
3notSure = false // 也可以是个 boolean

void

表示没有任何类型

typescript
 1// 表示没有任何类型, 一般用来说明函数的返回值不能是undefined和null之外的值
 2function fn(): void {
 3  console.log('fn()')
 4  // return undefined
 5  // return null
 6  // return 1 // error
 7}
 8
 9// 无实际作用,因为你只能赋值 undefined 和 null
10let unusable: void = undefined

never

一个从来不会有返回值的函数(如:如果函数内含有 while(true) {}

一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') }foo 的返回类型是 never

never 类型变量只能赋值另外一个 never

typescript
1let bar: never = (() => {
2    throw new Error('Throw my hands in the air like I just dont care');
3})();
4
5
6let b: never = (() => {
7    while (true) { }
8})()

当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never类型

object

object 表示非原始类型,也就是除 numberstringboolean之外的类型

typescript
1function fn2(obj:object):object {
2  console.log('fn2()', obj)
3  return {}
4  // return undefined
5  // return null
6}
7console.log(fn2(new String('abc')))
8// console.log(fn2('abc') // error
9console.log(fn2(String))

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种 ,使用|来创建联合类型

typescript
 1/*
 2  语法:number | string | boolean
 3*/
 4
 5// 定义一个一个函数得到一个数字或字符串值的字符串形式值
 6function toString2(x: number | string) : string {
 7  return x.toString()
 8}
 9
10
11// 返回长度
12function getLength(x: number | string) {
13
14  // return x.length // error 因为可能为number类型
15    
16  return x.toString().length
17}

类型断言

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”

它没有运行时的影响,只是在编译阶段起作用

typescript
 1/* 
 2类型断言(Type Assertion): 可以用来手动指定一个值的类型
 3语法:
 4    方式一: <类型>值
 5    方式二: 值 as 类型  tsx中只能用这种方式
 6*/
 7function getLength(x: number | string) {
 8    // 类型断言,表示确定为string类型
 9    let size = (x as string).length
10    if (typeof x === 'string') {
11        // typeof 会被编译器推断出,这里 x 为 string 类型
12        return x.length
13    } else {
14        return x.toString().length
15    }
16}
17console.log(getLength('abcd'), getLength(1234))

类型推断

类型推断: TS会在没有明确的指定类型的时候推测出一个类型

有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型

typescript
1/* 定义变量时赋值了, 推断为对应的类型 */
2let b9 = 123 // number
3// b9 = 'abc' // error
4
5/* 定义变量时没有赋值, 推断为any类型 */
6let b10  // any类型
7b10 = 123
8b10 = 'abc'

接口

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)

  • 接口是一种规范、是一种规则
  • 接口里面可以定义方法和属性,对于属性来说可以是非必须属性和只读属性
  • 除了描述带有属性的普通对象外,接口也可以描述函数类型

初探

typescript
 1interface IPerson1{
 2    // readonly 表示只读属性,初始化对象后不能再次赋值
 3    readonly id:number,
 4    name:string,
 5    // ? 表示改属性非必须属性
 6    sex?:string
 7}
 8
 9// sex为非必需属性,可以不填
10const p1:IPerson1={
11    id:1,
12    name:'唐'
13    // age: 25  // err not assignable to type
14}
15// p1.id=2   // err read-only property

函数类型

typescript
1// 接口可以描述函数类型(参数的类型与返回的类型)
2interface SearchFunc {
3    // 定义函数的参数和返回值
4    (source: string, subString: string): boolean
5}
6
7const mySearch: SearchFunc = function (source, subString): boolean {
8    return source.indexOf(subString) != -1
9}

类类型

typescript
 1// 定义接口 IPerson1 ,里面规定了一个 fly 行为
 2interface IPerson1 {
 3    fly()
 4}
 5
 6// 定义接口 IPerson2 ,里面规定了一个 swim 行为
 7interface IPerson2 {
 8    swim()
 9}
10
11// 定义接口 IPerson3,继承了 IPerson1 和 IPerson2 接口
12interface IPerson3 extends IPerson1, IPerson2 {
13}
14
15// 定义类实现接口 , 并实现里面的方法
16class Person1 implements IPerson1 {
17    fly() {
18        console.log('飞1')
19    }
20}
21
22// 可以实现多个接口
23class Person2 implements IPerson1, IPerson2 {
24    fly() {
25        console.log('飞2')
26    }
27    swim() {
28        console.log('游泳2')
29    }
30}
31
32// 实现接口 IPerson3,因为接口 IPerson3 继承了两个接口,因此该类也需要实现两个方法
33class Person3 implements IPerson3 {
34    fly() {
35        console.log('飞2')
36    }
37    swim() {
38        console.log('游泳2')
39    }
40}

类定义基础示例

类是为了可以更好地面向对象编程

typescript
 1// 定义类,关键字 class
 2class Person{
 3    // 定义类属性 name age
 4    name:string
 5    age:number
 6    // 构造方法,传入参数 name age,指定默认值
 7    constructor(name:string='张三',age:number=18){
 8        this.name=name
 9        this.age=age
10    } 
11    //定义类方法
12    saySomeThings(str:string){
13        console.log(`${this.name}说:${str}`)
14    }
15}
16const p:Person=new Person("李四");
17p.saySomeThings('hello')   // 李四说:hello

继承和多态

  • 继承关键字 extends

  • 如果 B 继承了 A,那么 A 被称为父类/基类,B 被称为子类/派生类

  • 子类实例化时,必须调用super方法;子类可以通过 super 关键字调用父类的属性和方法

  • 多态指的是子类实例可以用父类类型进行引用

示例:

typescript
 1class Student extends Person {
 2    teacher: string
 3    // 构造方法,传入参数 name age,指定默认值
 4    constructor(name: string = '张三', age: number = 18, teacher: string) {
 5        // super 调用父类方法
 6        super(name, age)
 7        this.teacher = teacher
 8    }
 9}
10// 多态的体现,这里父类引用,子类实例
11const stu1:Person = new Student("李四",18,"李老师")

修饰符

类属性和方法的修饰符,默认为public

  • private:被修饰的属性或方法不能在类定义以外的任何地方访问(包括子类的 super 调用)
  • protected:可以在子类中访问,不可以在外部访问
  • public:可以在任何地方访问
  • readonly:被修饰的属性,只能在构造方法或定义时进行赋值操作

注意:如果在构造函数的参数上使用修饰符,那么会默认存在该属性

typescript
1class TuGou{
2    constructor(public name:string){
3        // 这里因为构造方法参数存在修饰符,所以this可以调用到
4        this.name=name
5    }
6}

存取器(封装)

TypeScript 支持通过 getters/setters 来截取对对象成员的访问

示例:

typescript
 1class It民工{
 2    private firstName:string
 3    private lastName:string
 4    // 该类会多一个fullName 属性
 5    set fullName(value:string){
 6        const  =value.split('-')
 7        this.firstName=names[0]
 8        this.lastName=names[1]
 9    }
10    get fullName () {
11         return this.firstName + '-' + this.lastName
12    }
13}
14const t1:It民工=new It民工()
15t1.fullName

静态属性

使用 static 关键字修饰属性或方法,然后可以通过类名直接调用而不必实例化

typescript
1class Teacher{
2    static kinds:string='老师'
3    static say(str:string){
4        console.log(`我是${str}老师`)
5    }
6}
7console.log(Teacher.kinds) // 老师
8Teacher.say('王')  // 我是王老师

抽象类

  • 使用 abstract 关键字定义抽象类
  • 抽象类做为其它派生类的基类使用, 它们不能被实例化
  • 不同于接口,抽象类可以包含成员的实现细节

示例:

typescript
 1/* 
 2  抽象类 不能创建实例对象, 只有实现类才能创建实例
 3  可以包含未实现的抽象方法
 4*/
 5
 6abstract class Animal {
 7	// 抽象方法,子类必须实现该方法
 8    abstract cry()
 9	// 非抽象方法,子类可以直接调用
10    run() {
11        console.log('run()')
12    }
13}
14
15class Dog extends Animal {
16    cry() {
17        console.log(' Dog cry()')
18    }
19}
20
21const dog = new Dog()
22dog.cry()
23dog.run()

函数

函数是 JavaScript 应用程序的基础,它帮助你实现抽象层,模拟类,信息隐藏和模块

在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方

TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用

基本示例

typescript
1// 命名函数
2function add(x, y) {
3  return x + y
4}
5
6// 匿名函数
7let myAdd = function(x, y) { 
8  return x + y;
9}

函数类型

示例

typescript
1function add(x: number, y: number): number {
2  return x + y
3}
4
5let myAdd = function(x: number, y: number): number { 
6  return x + y
7}

可选参数和默认参数

TypeScript 里的每个函数参数都是必须的,传递给一个函数的参数个数必须与函数期望的参数个数一致

JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined。 在TypeScript 里我们可以在参数名旁使用 ? 实现可选参数的功能

typescript
 1// firstName 指定默认参数A,调用时可不传;lastName是可选参数,可不传
 2function buildName(firstName: string='A', lastName?: string): string {
 3  if (lastName) {
 4    return firstName + '-' + lastName
 5  } else {
 6    return firstName
 7  }
 8}
 9
10console.log(buildName('C', 'D'))
11console.log(buildName('C'))
12console.log(buildName())

剩余参数

在 TypeScript 里,可以把所有参数收集到一个变量里:剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组。 默认参数关键字:...

剩余参数必须在参数列表末尾

示例

typescript
1// args 为默认茶树
2function info(x: string, ...args: string[]) {
3  console.log(x, args)
4}
5info('abc', 'c', 'b', 'a')

函数重载

函数重载: 函数名相同, 而形参不同的多个函数

在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法

示例

typescript
 1// 函数重载声明(表示add函数只支持入参同时为number或string)
 2function add(a:number,b:number):number
 3function add(a:string,b:string):string
 4// 函数定义
 5function add(a:number|string,b:number|string):number|string{
 6    if(typeof a==='string' && typeof b==='string'){
 7        return a+b
 8    }else if(typeof a==='number' && typeof b==='number'){
 9        return a+b
10    }
11}

泛型

基础示例

指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性

示例:

typescript
 1// 根据指定的数量 count 和数据 value , 创建一个包含 count 个 value 的数组
 2// T 为泛型标识符,在调用该函数时传入具体类型
 3// 也可以将类型指定为any,这样后续会使用数组中元素时没有提示
 4function creatArr<T>(value:T,count:number):T[]{
 5    const arr:T[]=[]
 6    for(let i=0;i<count;i++){
 7        arr.push(value)
 8    }
 9    return arr
10}
11// 泛型为Number,该函数返回Number数组
12creatArr<Number>(123,10)

泛型接口

typescript
 1interface IbaseCRUD <T> {
 2  data: T[]
 3  add: (t: T) => void
 4  getById: (id: number) => T
 5}
 6
 7class User {
 8  id?: number; //id主键自增
 9  name: string; //姓名
10  age: number; //年龄
11
12  constructor (name, age) {
13    this.name = name
14    this.age = age
15  }
16}
17
18class UserCRUD implements IbaseCRUD <User> {
19  data: User[] = []
20  
21  add(user: User): void {
22    user = {...user, id: Date.now()}
23    this.data.push(user)
24    console.log('保存user', user.id)
25  }
26
27  getById(id: number): User {
28    return this.data.find(item => item.id===id)
29  }
30}
31
32
33const userCRUD = new UserCRUD()
34userCRUD.add(new User('tom', 12))
35userCRUD.add(new User('tom2', 13))
36console.log(userCRUD.data)

泛型类

typescript
 1class GenericNumber<T> {
 2  zeroValue: T
 3  add: (x: T, y: T) => T
 4}
 5
 6let myGenericNumber = new GenericNumber<number>()
 7myGenericNumber.zeroValue = 0
 8myGenericNumber.add = function(x, y) {
 9  return x + y 
10}
11
12let myGenericString = new GenericNumber<string>()
13myGenericString.zeroValue = 'abc'
14myGenericString.add = function(x, y) { 
15  return x + y
16}
17
18console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
19console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))

泛型约束

typescript
 1interface Lengthwise {
 2  length: number;
 3}
 4
 5// 指定泛型约束
 6function fn2 <T extends Lengthwise>(x: T): void {
 7  console.log(x.length)
 8}
 9
10fn2('abc')
11// fn2(123) // error  number没有length属性

其他

声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能

什么是声明语句

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $jQuery 了。

但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西

typescript
 1/* 
 2当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
 3声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
 4  declare var jQuery: (selector: string) => any;
 5声明文件: 把声明语句放到一个单独的文件(jQuery.d.ts)中, ts会自动解析到项目中所有声明文件
 6下载声明文件: npm install @types/jquery --save-dev
 7*/
 8
 9jQuery('#foo');
10// ERROR: Cannot find name 'jQuery'.

这时,我们需要使用 declare var 来定义它的类型

typescript
1declare var jQuery: (selector: string) => any;
2jQuery('#foo');

declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:

typescript
1jQuery('#foo');

一般声明文件都会单独写成一个 xxx.d.ts 文件

创建 01_jQuery.d.ts, 将声明语句定义其中, TS编译器会扫描并加载项目中所有的TS声明文件

typescript
1declare var jQuery: (selector: string) => any;

很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx, 可以在 https://www.npmjs.com/package/package 进行搜索

有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)

示例:

  1. 安装jquery依赖

    bash
    1npm install jquery
  2. 安装jquery声明文件

    bash
    1npm install @types/jquery

内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。

  1. ECMAScript 的内置对象

Boolean Number String Date RegExp Error

typescript
1/* 1. ECMAScript 的内置对象 */
2let b: Boolean = new Boolean(1)
3let n: Number = new Number(true)
4let s: String = new String('abc')
5let d: Date = new Date()
6let r: RegExp = /^1/
7let e: Error = new Error('error message')
8b = true
9// let bb: boolean = new Boolean(2)  // error
  1. BOM 和 DOM 的内置对象

Window Document HTMLElement DocumentFragment Event NodeList

typescript
1const div: HTMLElement = document.getElementById('test')
2const divs: NodeList = document.querySelectorAll('div')
3document.addEventListener('click', (event: MouseEvent) => {
4  console.dir(event.target)
5})
6const fragment: DocumentFragment = document.createDocumentFragment()
使用滚轮缩放
按住拖动