美文网首页前端相关
TypeScript 基本概念回顾整理

TypeScript 基本概念回顾整理

作者: DeeJay_Y | 来源:发表于2019-10-11 15:51 被阅读0次

    title: TypeScript 回顾整理
    date: 2019/10/08 15:04:01
    tags:

    • 前端
    • TS
      categories:
    • 前端

    Vue3.0 最近发布了pre-alpha版本,基本都是由TS编写的,借此机会回顾一下TypeScript相关的概念和知识点

    基础用法

    基本数据类型

    JS中的原始基本数据类型:布尔值、数值、字符串、nullundefined 以及 ES6 中的新类型 Symbol

    boolean

    布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean 定义布尔值类型:

    let isDone: boolean = false;
    

    注意,使用构造函数 Boolean 创造的对象不是布尔值

    let createdByNewBoolean: boolean = new Boolean(1);
    
    // Type 'Boolean' is not assignable to type 'boolean'.
    //   'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
    

    事实上 new Boolean() 返回的是一个 Boolean 对象

    let createdByNewBoolean: Boolean = new Boolean(1);
    

    直接调用 Boolean 也可以返回一个 boolean 类型:

    let createdByBoolean: boolean = Boolean(1);
    

    在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数, 不属于基本类型。其他基本类型(除了 null 和 undefined)一样。

    number

    let decLiteral: number = 6;
    let hexLiteral: number = 0xf00d;
    // ES6 中的二进制表示法
    let binaryLiteral: number = 0b1010;
    // ES6 中的八进制表示法
    let octalLiteral: number = 0o744;
    let notANumber: number = NaN;
    let infinityNumber: number = Infinity;
    

    上述的二进制和八进制编译为js之后都会转为十进制。

    string

    let myName: string = 'Tom';
    let myAge: number = 25;
    let info: string = `Hello, i am ${myName} and i am ${myAge} years old`;
    

    对于模板字符串来讲,编译为JS时会改为字符串拼接的形式

    空值void

    js中不存在void的概念,在ts中也只有声明一个函数没有返回值的时候才使用void(注意不是undefined!!!)

    function alertName(): void {
        alert('My name is Tom');
    }
    

    而如果要声明一个void变量的话(无意义的操作),只能赋值为null或者undefined

    let voidVal: void = undefined;
    

    null和undefined

    let u: undefined = undefined;
    let n: null = null;
    

    在TS中,undefined和null是所有类型的子类!这意味着你可以赋值给所有的类型一个null或者undefined

    let num: number = undefined;
    

    但是void类型就不行

    任意值 Any

    任意值(Any)用来表示允许赋值为任意类型

    声明为any类型的变量可以进行下面的操作:

    let myFavoriteNumber: any = 'seven';
    myFavoriteNumber = 7;
    

    any的属性和方法

    在任意值上访问任何属性都是允许的:

    // 编译通过
    let anyThing: any = 'hello';
    console.log(anyThing.myName);
    console.log(anyThing.myName.firstName);
    
    // 编译通过
    let anyThing: any = 'Tom';
    anyThing.setName('Jerry');
    anyThing.setName('Jerry').sayHello();
    anyThing.myName.setFirstName('Cat');
    

    声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

    未声明类型的变量

    变量如果在声明的时候,未指定其类型,那么它会被识别为any类型:

    // 编译通过
    let something;
    something = 'seven';
    something = 7;
    
    something.setName('Tom');
    

    类型推断

    TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

    let myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
    

    但是值得注意的是:

    如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

    联合类型Union Types

    let myFavoriteNumber: string | number;
    myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    

    访问联合类型的属性或者方法

    当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

    function getLength(something: string | number): number {
        return something.length;
    }
    
    // index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
    //   Property 'length' does not exist on type 'number'.
    

    联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型,此后再被赋值为第二种类型之后,访问其不存在的属性或者方法就会报错

    let myFavoriteNumber: string | number;
    myFavoriteNumber = 'seven';
    console.log(myFavoriteNumber.length); // 5
    myFavoriteNumber = 7;
    console.log(myFavoriteNumber.length); // 编译时报错
    
    // index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
    

    接口——对象的类型

    TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。一般首字母大写.

    基本用法

    interface Person {
        name: string;
        age: number;
    }
    
    let tom: Person = {
        name: 'Tom',
        age: 25
    };
    

    此时约束的对象不允许缺少接口的定义的属性也不允许多加属性。

    可选属性

    interface Person {
        name: string;
        age?: number;
    }
    
    let tom: Person = {
        name: 'Tom'
    };
    

    此时age属性可有可无,但是依旧不可以多加属性

    任意属性

    interface Person {
        name: string;
        age?: number;
        [propName: string]: any;
    }
    
    let tom: Person = {
        name: 'Tom',
        gender: 'male'
    };
    

    此时可以多加任意的属性。

    使用[propName: string]定义了任意属性取 string 类型的值。

    需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

    只读属性

    interface Person {
        readonly id: number;
        name: string;
        age?: number;
        [propName: string]: any;
    }
    
    let tom: Person = {
        id: 89757,
        name: 'Tom',
        gender: 'male'
    };
    
    tom.id = 9527;
    
    // index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
    

    只读属性只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:

    数组的类型

    数组定义有几种方法:

    「类型 + 方括号」表示法

    let fibonacci: number[] = [1, 1, 2, 3, 5];
    

    number[]类型的数组中不允许出现第二种类型(如string)的数据,相应的,如果对数组进行操作进行新增时,也不允许加入其他类型的数据。

    let fibonacci: number[] = [1, 1, 2, 3, 5];
    fibonacci.push('8');
    
    // Argument of type '"8"' is not assignable to parameter of type 'number'.
    

    数组泛型

    使用Array<elemType>来表示数组

    let fibonacci: Array<number> = [1, 1, 2, 3, 5];
    

    用接口表示数组(不常用)

    interface NumberArray {
        [index: number]: number;
    }
    let fibonacci: NumberArray = [1, 1, 2, 3, 5];
    

    注意这么写之后,变量其实不是数组类型,如push等方法是不可使用的。 本质上仅仅是个类数组类型。

    类数组

    对于arguments 等类数组, 不能用普通的数组的方式来描述,而应该用接口
    TS中的内置对象中,就使用了这种方法:

    interface IArguments {
        [index: number]: any;
        length: number;
        callee: Function;
    }
    

    上述的IArguments接口即规定了arguments的类型:

    function sum() {
        let args: IArguments = arguments;
    }
    

    函数的类型

    声明式定义

    对于声明式的函数定义,规定输入参数的个数和类型即可以及输出的类型即可。

    function sum(x: number, y: number): number {
        return x + y;
    }
    

    注意: 参数不得少于或者多余函数的规定

    函数表达式

    如果想通过表达式方式定义函数的话,可以直接写为:

    let mySum = function (x: number, y: number): number {
        return x + y;
    };
    

    或者写为箭头函数形式:

    let mySum = (x: number, y: number): number => {
        return x + y;
    };
    

    但是值得注意的是,这块的sum其实是有类型的,只不过没有显示定义而TS帮我们进行了类型推断而已,如果要显式的定义类型,需要写为:

    let mySum: (x: number, y: number) => number = (x: number, y: number): number => {
        return x + y;
    };
    

    注意这边的第一个=>代表着函数的类型,规定了函数的返回值,而第二个=>则是箭头函数

    接口表示函数

    还可以使用接口表示函数:

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

    函数的可选参数

    function buildName(firstName: string, lastName?: string) {
        if (lastName) {
            return firstName + ' ' + lastName;
        } else {
            return firstName;
        }
    }
    

    可选参数必须接在必需参数后面

    函数参数的默认值

    function buildName(firstName: string, lastName: string = 'Cat') {
        return firstName + ' ' + lastName;
    }
    

    函数的...rest参数(剩余参数)

    function push(array: any[], ...items: number[]) {
        items.forEach(function(item) {
            array.push(item);
        });
    }
    push([], 1, 2, 3);
    

    这边的...items代表1,2,3, items即为[1,2,3]为number[]类型

    rest 参数只能是最后一个参数

    函数的重载

    function reverse(x: number): number;
    function reverse(x: string): string;
    function reverse(x: number | string): number | string {
        if (typeof x === 'number') {
            return Number(x.toString().split('').reverse().join(''));
        }
        return x.split('').reverse().join('');
    }
    
    let a: number = reverse(123);
    let b: number = reverse("abc"); // 编译报错  Type 'string' is not assignable to type 'number' .
    

    前2次函数定义代表传入的是number返回的也会是number,传入string返回的也是string,最后的才为函数的实现

    重载时会从最前面的定义开始进行匹配,所以优先把最精确的写在最前

    类型断言(Type Assertion)

    用来手动指定一个值的类型

    断言语法

    1. <类型>值
    2. 值 as 类型

    在tsx中只能使用 值 as 类型的语法

    可以使用类型断言对联合类型进行断言,但是不可断言联合类型之外的类型:

    function getLength(something: string | number): number {
        if ( (something as string).length ) {
            return (<string>something).length;
        }
        return something.toString().length;
    }
    

    对于上例来说,参数为string|number,这里的参数直接可以断言为string,但是不可以断言为string | number之外的类型

    类型断言并不是类型转换

    声明文件

    // TODO
    待补充

    内置对象

    ECMAScript的内置对象

    ECMAScript中一些内置对象如BooleanErrorDateRegExp 等,在TS中可以直接定义:

    let b: Boolean = new Boolean(1);
    let e: Error = new Error('Error occurred');
    let d: Date = new Date();
    let r: RegExp = /[a-z]/;
    

    因为在 TypeScript 核心库的定义文件中定义了这些内置对象。

    DOM 和 BOM 的内置对象

    例如DocumentHTMLElementEventNodeList 等。

    let body: HTMLElement = document.body;
    let allDiv: NodeList = document.querySelectorAll('div');
    document.addEventListener('click', function(e: MouseEvent) {
      // Do something
    });
    

    进阶

    类型别名

    类型别名用来给一个类型起个新名字。

    type Name = string;
    let s: Name = "abc";
    

    字符串字面量类型

    字符串字面量类型用来约束取值只能是某几个字符串中的一个。

    type EventNames = 'click' | 'scroll' | 'mousemove';
    function handleEvent(ele: Element, event: EventNames) {
        // do something
    }
    
    handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
    handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'
    
    // index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
    

    元组

    数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

    let tom: [string, number] = ['Tom', 25];
    

    基本操作不赘述,值得注意的是,元组支持越界,但是越界时新增的元素必须是前面规定的那些类型之一

    let tom: [string, number];
    tom = ['Tom', 25];
    tom.push('male'); // 越界时允许push的类型 本例中为 string | number
    tom.push(true); // 编译报错
    

    枚举

    枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

    枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射

    enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
    
    console.log(Days["Sun"] === 0); // true
    console.log(Days["Mon"] === 1); // true
    console.log(Days["Tue"] === 2); // true
    console.log(Days["Sat"] === 6); // true
    
    console.log(Days[0] === "Sun"); // true
    console.log(Days[1] === "Mon"); // true
    console.log(Days[2] === "Tue"); // true
    console.log(Days[6] === "Sat"); // true
    

    给枚举手动赋值

    枚举可以在初始化的时候手动为其每一项赋值,未被赋值的成员会上一个枚举项递增

    enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
    
    console.log(Days["Sun"] === 7); // true
    console.log(Days["Mon"] === 1); // true
    console.log(Days["Tue"] === 2); // true
    console.log(Days["Sat"] === 6); // true
    

    本例中Tue开始就未赋值,所以会接着上一个枚举项即Mon开始接着递增。

    赋值时允许多个项的值相等,但是会造成覆盖情况!

    对于上述的覆盖情况,举个例子解释一下:

    enum Colors = {red = 1, blue = 1, green};
    

    会被编译为:

    var Colors ;
    (function (Colors ) {
        Colors [Colors ["red"] = 1] = "red";
        Colors [Colors ["blue "] = 1] = "blue ";
        Colors [Colors ["green"] = 2] = "green";
    })(Colors || (Colors = {}));
    

    实际最后的Colors为:

    {
        1: "blue",
        2: "green",
        blue: 1,
        red: 1,
        green: 2
    }
    

    可以看到,对于index的访问方式来说,由于下标相同,之前的1: red已经被覆盖为1: blue,所以要避免枚举值赋值重复。

    枚举的计算所得项

    对于上述的

    enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
    

    即为常数项的枚举,在枚举中也可以使用计算所得项:

    enum Color {Red, Green, Blue = "blue".length};
    

    但是要注意的是:如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错

    enum Color {Red = "red".length, Green, Blue};
    
    // index.ts(1,33): error TS1061: Enum member must have initializer.
    // index.ts(1,40): error TS1061: Enum member must have initializer.
    

    详见中文手册

    常数枚举

    注意和上面的常数项不是一个东西,常数枚举值得是通过const定义的枚举,即:

    使用const enum定义的即为常数枚举,常数枚举会在编译阶段被删除,并且不能包含计算成员。

    const enum Directions {
        Up,
        Down,
        Left,
        Right
    }
    
    let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
    

    编译为JS后代码为:

    var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
    

    外部枚举

    指的是通过declare enum定义的枚举

    declare enum Directions {
        Up,
        Down,
        Left,
        Right
    }
    
    let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
    

    编译为:

    var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
    

    declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

    另外如果搭配const定义,会被编译为常数枚举。

    TS中的类

    public private 和 protected

    TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivateprotected

    constructor 设为private和protected

    防止类被继承(即final class)且不能实例化的话,在TS中要讲其constructor设为private

    class Animal {
        public name;
        private constructor (name) {
            this.name = name;
      }
    }
    class Cat extends Animal {
        constructor (name) {
            super(name);
        }
    }
    
    let a = new Animal('Jack');
    
    // index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.
    // index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.
    

    而要类只不能实例话还可以被继承的话,要使用protected修饰constructor

    class Animal {
        public name;
        protected constructor (name) {
            this.name = name;
      }
    }
    class Cat extends Animal {
        constructor (name) {
            super(name);
        }
    }
    
    let a = new Animal('Jack');
    
    // index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.
    

    此外修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁:

    class Animal {
        // public name: string;
        public constructor (public name) {
            this.name = name;
        }
    }
    

    抽象类

    TS中的抽象类基本和Java一致,不多赘述:

    abstract class Animal {
        public name;
        public constructor(name) {
            this.name = name;
        }
        public abstract sayHi();
    }
    
    let a = new Animal('Jack');
    

    类与接口

    接口还可以对类的一部分行为进行抽象

    interface Alarm {
        alert();
    }
    
    interface Light {
        lightOn();
        lightOff();
    }
    
    class Car implements Alarm, Light {
        alert() {
            console.log('Car alert');
        }
        lightOn() {
            console.log('Car light on');
        }
        lightOff() {
            console.log('Car light off');
        }
    }
    

    补充一点: TS中接口可以继承类

    // TS中接口可以继承类:
    abstract class Point {
        x: number;
        y: number;
        abstract showPoint(): void;
    }
    
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {
        x: 1, y : 1, z: 1,
        showPoint(): void {
          console.log("");
        }
    };
    

    此外接口也可以继承接口

    函数的属性和方法

    函数可以拥有自己的属性和方法:

    interface Counter {
        (start: number): string;
        interval: number;
        reset(): void;
    }
    
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    
    // 这里的counter既是一个函数,也拥有自己的属性和方法
    

    泛型

    泛型的概念不再赘述,来看基本语法:

    function createArray<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray<string>(3, 'x'); // ['x', 'x', 'x']
    

    泛型约束

    在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法

    function getLength<T>(x: T): number() {
        return x.length;
    }
    // error TS2339: Property 'length' does not exist on type 'T'.
    

    上述例子中由于T的类型不明,所以无法访问length属性。

    这时可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量,即泛型约束:

    interface LengthAble {
        length: number;
    }
    
    function getLength<T extends LengthAble>(x: T): number {
        return x.length;
    }
    

    另外泛型之间也可以相互约束,比如在function fn<T extends U, U>(x: T, y: U) {},强制要求了前一个参数的类型继承自后一个类型。

    泛型接口

    泛型也可以应用在接口上:

    interface CreateArrayFunc {
        <T>(length: number, value: T): Array<T>;
    }
    
    let createArray: CreateArrayFunc;
    createArray = function<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray(3, 'x'); // ['x', 'x', 'x']
    

    也可以直接将泛型写到接口上:

    interface CreateArrayFunc<T> {
        (length: number, value: T): Array<T>;
    }
    
    let createArray: CreateArrayFunc<any>;
    createArray = function<T>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray(3, 'x'); // ['x', 'x', 'x']
    

    泛型类

    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; };
    

    泛型参数的默认类型

    TS2.3以后新增了一个泛型参数的默认类型,使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,会采用这个默认类型

    function createArray<T = string>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    

    声明合并

    如果定义了两个相同名字的函数接口命名空间等,那么它们会合并成一个类型

    函数合并

    函数的重载就是函数的合并

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

    接口的合并

    1. 属性合并:
    interface Alarm {
        price: number;
    }
    interface Alarm {
        weight: number;
    }
    

    等价于:

    interface Alarm {
        price: number;
        weight: number;
    }
    

    合并的属性的类型必须是唯一的

    interface Alarm {
        price: number;
    }
    interface Alarm {
        price: number;  // 虽然重复了,但是类型都是 `number`,所以不会报错
        weight: number;
    }
    
    interface Alarm {
        price: number;
    }
    interface Alarm {
        price: string;  // 类型不一致,会报错
        weight: number;
    }
    
    // index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.
    
    1. 方法的合并

    内部方法的合并和函数的合并原则相同

    interface Alarm {
        price: number;
        alert(s: string): string;
    }
    interface Alarm {
        weight: number;
        alert(s: string, n: number): string;
    }
    

    等价于:

    interface Alarm {
        price: number;
        weight: number;
        alert(s: string): string;
        alert(s: string, n: number): string;
    }
    

    相关文章

      网友评论

        本文标题:TypeScript 基本概念回顾整理

        本文链接:https://www.haomeiwen.com/subject/jsffpctx.html