TypeScript学习笔记

109 阅读8分钟

typescript介绍:

  1. ts是由微软开发的开源的编程语言
  2. ts是js的超集
  3. ts是开发大型应用的基石
  4. ts提供了更加丰富的语法提示
  5. ts在编写阶段就可以检查错误
  • ts 是静态类型,js 是动态类型
  • ts 需要被编译成 js 文件后,才可以直接在浏览器和node中运行的 (使用tsc命令)
    • 所有的ts文件最后都需要被编译成js文件才能执行
    • 1 + undefined = NaN
    • ts中类型是确定的,给变量规定类型之后,其类型不能被改变

typescript中的数据类型

  • 在js中,有原始数据类型和引用数据类型:
    • 原始数据类型:boolean string number null undefined symbol
    • 引用数据类型:object
  • ts中的数据类型:
    • 基础类型: boolean、string、number、null、undefined、symbol、any(新增) never(新增)、void
    • 引用类型被做了细分:
      • 对象: interface(可以描述一个对象的结构)
      • 数组: number[], string[], boolean[], 泛型的写法:Array
      • 函数: 可以规定形参的类型以及返回值的类型
    let test = function(a: number, b: number): number {
        return a + b;
    }
  • 新的语法特性:
  1. as 断言
  2. class,基于原本的class,多了面向对象的三大特性:封装、继承和多态;

布尔、数值、字符串、void的注解(原始数据类型的注解)

  • 布尔值的注解:
    • let isDone: boolean = false;
  • 数字的注解:
    • let num: number = 6
    • let num1: number = 0o744 (可以直接赋值在js中报错的八进制数)
  • 字符串的注解:
    • let str: string = 'Chase'
  • any 的注解:
  1. 如果是普通变量的话,可以是任意的数据类型
  2. 如果是对象的话,any是不能提示原有的属性和方法
    let notSure: any = 4;
    notSure = "maybe a string instead";
    notSure = false;
  1. 未给初始值的变量类型为any
  • Void类型:当一个函数没有返回值时
    function test(): void{
        console.log('');
    }
  • null & undefined的注解:
    • nullundefined是所有类型的子类型(需要修改配置:strictNullChecks,默认为true,需要改为false)
    • 修改了如上的配置项之后,就可以把null以及undefined赋值给任意类型的变量(官方不推荐)
      • let num: number = undefined
      • let bol: boolean = null
    • 官方更推荐使用联合类型:
      • let num: number | undefined | null = undefined;
      • let bol: boolean | undefined | null = null;
  • never的注解:
    • 表示那些永远不存在的值
    function error(message: string): never{
        throw new Error(message);
    }
    
    function fail(){
        return error("Something failed")
    }
    
    function infiniteLoop(): never {
        while(true){}
    }
  • object的注解:
    • 表示非原始类型,可以表示对象、数组、函数
    • declare用于规定函数的形参类型以及返回值类型,并不关心该函数如何实现,所以后面不需要跟上函数的实现细节
    • 对于object的注解用于更好的实现如Object.create的API
    declare function create(o: object | null): void;
    
    create({prop:0}); //OK
    create(null); //OK
    create({}) //OK
    
    create(42);  //Error
    create('string') //Error
  • 在配置ts编译目录的时候,可以修改tsconfig下的outDirrootDir
    • 步骤:
      • $ tsc --init
      • 在生成的tsconfig.json文件下找到outDir以及rootDir
      • outDir: ./dist
      • rootDir: ./src
      • 直接终端输入tsc, 就可以自动查找需要编译的源目录以及将生成的文件传入到dist目录

类型注解和类型推断

  • 类型注解:
    let count: number;
    count = 993;
  • 类型推断(如果ts可以帮忙做类型推断,则尽量不使用类型注解,减少代码量)
    let count = 3; //ts直接推断count的类型为number
    
    //ts不能推断类型的情况
    function test(a, b){ return a+b;}
    test(1,2); //会报错
  • 联合类型
    • 定义:表示一个变量可以为多个类型的时候,就可以使用联合类型
    let a: string | number;
    a = '123';
    a = 123;
  • 注意:
    • 联合类型的共有属性不会报错
    • 在赋值的时候,变量的类型被确定
    let a: string | number;
    a = '123';
    console.log(a.length); //=> 3
    
    a = 10;
    console.log(a.length); //=> error

数组的注解

  1. 使用number[]来注解:
  • let list: number[] = [1,2,3]; 没有长度限制, 只能存储数字
  • 让数组既可以存数字又可以存字符: let list: (number|string)[] = [1,'2',3,'4']
  1. 使用泛型注解:
  • let list: Array<number> = [1,2,3]
  1. 使用interface来表示数组
    interface List {
        [index: number]: number | string; //可以添加无限数量的元素,数组长度不受限
    }
    
    let list: List = [1,2,3,4,5,'6']

类数组情况:

    interface Args {
        [index: number]: any; //键和值可以为任意属性
        length: number;
        callee: Function;
    }
    
    function test(){
        let args: Args = arguments;
    }

函数的注解

包括:1. 函数声明的注解方式; 2. 函数表达式的注解方式

    //函数声明
    function test(a, b){
        return a + b;
    }
    //函数表达式
    let test1 = function(a, b){
        return a + b;
    }
    
    //函数声明的注解
    function test(a: number, b: number){
        return a + b;
    }
    
    //函数声明的注解
    function test(a: number, b: number): number{
        return 1;
        //console.log(1) //函数返回值为void
        //throw new Error() //函数返回值为never
    }
    
    //函数表达式的注解,箭头后面的number代表的是返回值的类型而不是箭头函数的执行体
    let test1: (a: number, b: number) => number = function(a,b){
        return a + b;
    }    
    let test2: (a: number, b: number) => {} = function(a,b){//返回值为对象
        { a: 1 };
    } 
    

可选参数和默认参数

  • 可选参数:
    function buildName(firstName: string, lastName?: string){
        //lastName可以为null, undefined
        return firstName + " " + lastName;
    }
  • 默认参数
    function buildName(firstName: string, lastName: string = 'Chase'){}
    
    //可以简写,赋值字符串后会进行自动的类型推断
    functionm buildName(firstName: string, lastName = 'Chase'){}
  • 剩余参数(类似于ES6中的rest)
function t(fName: string, lName = 'Chase', ...restPara: string[]){}
  • 解构赋值
function test(
{first, second}: {first:number, second: number} = {fist:1, second: 2}){
    return first + second
}

function test({first: first = 2}:{first:number}){
//键名和键值相同就简写:function test({first:2}: {first: number})
    
}

this指向相关

  • 箭头函数能保存函数创建时的this值,而不是调用时的值
  • 可以通过箭头函数来绑定this指向
  • 在ts配置项中noImplicitThis -> false,可以设置this是否为严格模式,严格undefined,非严格指向window
  • 官网例子:
    interface Card {
        suit: string;
        card: number;
    }
    interface Deck {
        suits: string[];
        cards: number[];
        createCardPicker(this: Deck): () => Card; 
        //类型是(),也就是函数,返回值是Card类型的变量
    }
    
    let deck: Deck = {
        suits: ["hearts", "spades", "clubs", "diamonds"],
        cards: Array(52);
        createCardPicker: function (this: Deck){//这里指明this类型
            return ()=>{//这里把function改成箭头函数就可以正常保存之前的this值
                let pickedCard = Math.floor(Math.random()*52);
                let pickedSuit = Math.floor(pickedCard / 13);
                
                return {
                            suit: this.suits[pickedSuit],
                            card: pickedCard
                            }
            
            }
        }
    }
    
    let cardPicker = deck.createCardPicker();
    let pickedCard = cardPicker();

ts中函数的重载

单纯为了使得相关的表达更加清楚。(表义更加清楚)

    function reverse(x: string): string;
    function reverse(x: number): number;
    function reverse(x: string | number) {
        if(typeof x === 'string'){
            return x.split('').reverse.join('');
        }
        if(typeof x === 'number'){
            return Number(x.toString().split('').reverse().join(''))
        }
    }

  • 类中的所有属性都需要注解
    class Greeter {
        greeting: string;
        constructor(message: string){
            this.greeting = message;
        }
        greet(){
            return 'Hello, ' + this.greeting;
        }
    }
    
    let greeter = new Greeter('world');

继承

    //父类,也叫基类
    class Animal {
        move(distanceInMeters: number = 0){//number初始值为0
            console.log(`Animal moved ${distanceInMeters}m.`)
        }
    }
    
    //子类,也叫父类
    class Dog extends Animal {
        bark() {
            console.log('Woof! Woof!');
        }
    }
    
    const dog = new Dog();
    dog.bark();
    dog.move(10);
  • 注意:
    • tsconfig文件中的strictPropertyInitialization用来要求变量必须初始化
    • 类的注解方式就是给类中所有的变量注解类型

类的修饰符:

面向对象(OOP)的三大特性: 封装,继承,多态

  • 封装: PrivatePublicProtected是类成员的修饰符
    • public: 公有成员
      • 自身可以直接调用
      • 子类可以直接调用
      • 实例可以直接调用
    class Animal {
        public name: string;
        public constructor(theName: string){this.name = theName;}
        public move(distanceInMeters: number){
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
  • private: 私有成员变量
    • 自身可以直接调用
    • 子类不可以直接调用
    • 实例不可以直接调用
  • protected: 受保护的成员变量
    • 自身可以调用
    • 子类可以调用
    • 实例不可以调用
  • readOnly修饰符
    • 规定属性是只读的,不可被修改,只能在声明或构造函数里使用,跟在public后面
    • 不能修饰成员方法
    class Animal {
        public readonly name: string;
    }
  • 参数属性
    constructor(private name: string){}//常用这种写法
    //等价于下面的代码
    
    private name: string;
    constructor(name: string){this.name = name}
    

存取器

  • 本质就是给一个类实现一个getsetget是取值函数, set是存值函数
  • 初始化存取器之后,给私有成员变量赋值就可以直接instance.variable = 123; 类会隐式地自动调用相关的get和set函数,而不需要显式的调用instance.getName()instance.setName()
  • getset应该成对出现,不应该只定义其中一个
    class Employee{
        private _fullName: string = 'initial Name';
        get fullName(): string{
            return this._fullName;
        }
        set fullName(newName: string){
            this._fullName = newName;
        }
        //两个函数名是一样的,不一样的是前面的set和get
    }

静态属性和抽象类

  • 静态属性: static,可以直接class.staticVariable进行调用
  • 抽象类:
    • 能够提供其他类的基类
    • 父类身上有可以复用的方法的时候,可以使用抽象类
    • 注意1: 无法创建实例,无法使用new关键字
    • 注意2: 抽象方法一定要被实现
    • 注意3: 抽象方法必须在派生类里实现,而不是直接在抽象类里实现
    abstract class Animal {
        abstract makeSound(): void;
        move(): void {console.log('moving...')}
    }
    
    new Animal(); //抽象类不能使用new关键字,会报错
    
    class Snake extends Animal{
        makeSound(){
            //必须要实现这个方法
            console.log('ssssssssss');
        }
    }
    

TS高级技巧

  1. 在定义一个类的时候,也就相当于定义了一个数据类型,在进行数据注解的时候,可以将相关变量的类型注解设置为这个类
  2. 定义一个类的时候,也就相当于定义了一个构造函数
    class Greeter{}
    
    let greeter1: Greeter = new Greeter();
    
    let greeterMaker: typeof Greeter = Greeter; //类型注解为构造函数
    //greeterMaker相当于一个构造函数
    
    let greeter2: Greeter = new greeterMaker();
  1. 接口可以继承类
    class Point{
        x: number;
        y: number;
    }
    
    interface Point3d extends Point{
        z: number;
    }
    
    let point3d: Point3d = {x:1, y:2, z:3}
  • 补充 1: 可以通过设置tsconfig里的target属性来决定编译出来的js版本(es5,es6等) 2: 在注解类的时候,只需要注解其中变量的的数据类型即可

接口 interface

  • 定义:对“对象”的形状进行描述
    //原来给object加注解:
    let person: {
        num: String,
        toString: ()=>{}
    } = {
        num: '123',
        toString: function(){}
    }
    
    //使用interface
    //首先定义一个interface
    interface Person {
        name: string,
        age: number
    }
    //然后用刚刚定义好的interface来给对象加注解
    let person1: Person = {
        name:'chase',
        age:23
        
    }
  • 注意
    • 正常情况下,多添加属性或少添加属性都会报错
    • 如果不确定是否一定需要某个属性,可以在该属性后加一个问号(?)
    interface Person{
        name: string;
        age?: number; //这样在后面初始化Person实例时,没有初始化age也不会报错
    }
  • 如果需要添加某一属性:
    interface Person {
        name: string;
        age?: number;
        
        [propName: string]: any; 
        //使用这行代码可以在初始化Person时添加任意数量的新属性
        //任意属性的类型的值一定是any
    }
  • 如果想要某个属性为只读属性,不允许被外部修改,可以使用readOnly关键字
    interface Person {
        name: string;
        readOnly id: number;
    }

对类的一部分行为的抽象

  • 接口初探
    interface LabelledValue {
        label: string;
    }
    
    function printLabel(labelledObj: LabelledValue){
        console.log(labelledObj.label);
    }
    
    let myObj = {size:10, label: "Size 10 Object"}
    printLabel(myObj);
  • 使用接口对函数进行注解
    //先定义一个接口
    interface SearchFunc {
        (source: string, subString: string): boolean;
        //形参类型                            返回值类型
    }
    
    //后面的函数注解就可以直接使用先前定义好的接口
    let mySearch: SearchFunc;
    mySearch = function(source: string, subString: string){
        let result = source.search(subString);
        return result > -1;
    }

还可以直接使用type关键字来注解函数:

    type SearchFunc = (source: string, subString: string) => boolean;
    let mySearch: SearchFunc = function (source: string, subString: string): boolean{
        let result = source.search(subString);
        return result > -1;
    }

接口和可索引的类型接口

    interface NumberArray {
        //左边代表索引,索引的类型为number时,可以描述一个数组 
        //右边代表索引对应的值
        [index: number]: number
    }
    
    let obj: NumberArray = {
        0: 1,
        1: 2,
        2: 3
    }
    
    let arr: NumberArray = [1, 2, 3, 4, 5]
  • 注意1:当使用number来索引时,JavaScript会将它转换为string然后再去索引对象
    class Animal {name: string}
    class Dog extends Animal {breed: string}
    interface animalInterface {
        [x: string]: Animal;
        [x: number]: Dog;
        //number会继承string???
    }
  • 注意2: 在interface中索引签名的优先级最高,可以给索引签名设置readonly,设置readonly的属性在之后初始化出来的实例都是不能修改的
    interface NumberDictionary {
        [index: string]: number; //索引签名,优先级最高
        length: number;
        name: string; //报错,与索引签名要求的不一致
    }
    
    interface Person {
        name: string;
        age: number;  //报错,理由同上,
        //修改 [propName: string]: string | number就不会报错了
        
        [propName: string]: string;
    }

类类型接口: 对类的一部分行为的抽象

    interface ClockInterface {
        currentTime: Date; //接口中只是定义了属性的类型
        setTime(d: Date): void; //描述一个方法,参数在左边,返回值在右边
    }
    
    //类需要能实现接口
    class Clock implements ClockInterface {
        currentTime = new Date(); //类中需要定义接口中属性的实现方式
        
        setTime(){
            //需要实现接口中声明的方法
        }
    }

拓展例子,定义一个接口用来报警,定义一个Door类,然后定义Door类的派生类SecurityDoor

    interface Alarm {
        alert(): void;
    }
    
    interface Light{
        color: string;
        lightOn(): void;
    }
    
    class Door {}
    
    class SecurityDoors extends Door implements Alarm {
        alert(){
            console.log('ViWu~ ViWu~')
        }
    }
    
    class Car implements Alarm, Light{//可以实现多个接口
        alert(){
                console.log('didiididididididi')
            }
            
         color = 'red';
         lightOn(){
            console.log('lightOn')
         }
    }