美文网首页我爱编程
TS文档的搬运工

TS文档的搬运工

作者: laiyituan | 来源:发表于2018-05-24 15:31 被阅读651次

    你会玩Javascript多级继承吗?
    你能说明白js的作用域吗?
    曾经有个面试题,用console.log写一个方法,要求输入1,输出的是0,结果

    const console = Math
    console.log(1) // 0
    

    实际上,JavaScript里面充斥着大量的彩蛋,消耗大家非常多的时间。
    JavaScript这门语言一直以来为什么会表现得这么诡异呢?一定要看阮老师的这篇文章Javascript诞生记
    我们再看看TS的代码长什么样子
    Validation.ts

    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
    

    LettersOnlyValidator.ts

    import { StringValidator } from "./Validation";
    
    const lettersRegexp = /^[A-Za-z]+$/;
    
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
    

    仔细看你会发现,TS跟我们现在用的es6很像,我们学习TS并没有什么学习成本。
    而TS无论可读性、可维护性、上手的容易程度、写代码的速度,都非常明显地优于JS。


    TS入门

    基础类型

    布尔值、数字、字符串、数组、元组枚举anyvoidNull 和 UndefinedNever类型断言

    let isDone: boolean = false; // 布尔值
    
    // 数字类型,支持十进制和十六进制,二进制和八进制字面量
    let decLiteral: number = 6;
    let hexLiteral: number = 0xf00d;
    
    let sentence: string = `Hello, my name is ${ name }.`; // 字符串类型
    
    // 数组类型 类型+[]
    let list: number[] = [1, 2, 3]; // 可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
    let list: Array<number> = [1, 2, 3];// 使用数组泛型,Array<元素类型>
    // (number | string)[]这是联合类型和数组的结合
    // any[] 任意类型和数组的结合
    
    // 元组类型 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
    let x: [string, number];
    // Initialize it
    x = ['hello', 10]; // OK
    x = [10, 'hello']; // Error
    
    // 枚举类型
    enum Color {Red = 1, Green, Blue}
    let c: Color = Color.Green;
    
    // 任意类型
    let notSure: any = 4;
    notSure = "maybe a string instead";
    notSure = false; // okay, definitely a boolean
    
    // void 表示没有任何类型
    let unusable: void = undefined;// 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null
    
    // null和undefined 类型 Not much else we can assign to these variables!
    let u: undefined = undefined;
    let n: null = null;
    // never 类型 返回never的函数必须存在无法达到的终点
    function error(message: string): never {
        throw new Error(message);
    }
    

    类型断言
    语法
    <类型>值
    值 as 类型(JSX)

    let strLength: number = (<string>someValue).length; //(尖括号)
    let strLength: number = (someValue as string).length; // as语法
    

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

    // error
    function getLength(something: string | number): number {
        return something.length;
    }
    // error
    function getLength(something: string | number): number {
        if (something.length) {
            return something.length;
        } else {
            return something.toString().length;
        }
    }
    // 使用类型断言,将something断言成 string
    function getLength(something: string | number): number {
        if ((<string>something).length) {
            return (<string>something).length;
        } else {
            return something.toString().length;
        }
    }
    // 类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
    // error
    function toBoolean(something: string | number): boolean {
        return <boolean>something;
    }
    

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

    // 类型推论
    let myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;// error TS2322: Type 'number' is not assignable to type 'string'.
    // 等价于
    let myFavoriteNumber: string = 'seven';
    myFavoriteNumber = 7;
    
    // 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
    let myFavoriteNumber;
    myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    

    联合类型
    联合类型使用 | 分隔每个类型

    let myFavoriteNumber: string | number;
    myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    myFavoriteNumber = true; // Type 'boolean' is not assignable to type 'string | number'.
    

    变量声明

    let 变量声明
    const 常量声明
    ts跟es6一样推荐使用 let 和 const,因为他们都是块级作用域;不存在变量提升;
    var 作为一个全局变量,易造成环境的变量污染;

    ”变量提升“,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    

    ”暂时性死区“(temporal dead zone,简称 TDZ),即只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

    var tmp = 123;
    
    if (true) {
      // TDZ开始
      tmp = 'abc'; // ReferenceError
      console.log(tmp); // ReferenceError
    
      let tmp; // TDZ结束
      console.log(tmp); // undefined
    
      tmp = 123;
      console.log(tmp); // 123
    }
    

    结构赋值

    // 数组结构赋值
    let [first, ...rest] = [1, 2, 3, 4];
    console.log(first); // outputs 1
    console.log(rest); // outputs [ 2, 3, 4 ]
    // 展开
    let first = [1, 2];
    let second = [3, 4];
    let bothPlus = [0, ...first, ...second, 5];
    // 对象的结构赋值
    let o = {
        a: "foo",
        b: 12,
        c: "bar"
    };
    let { a, b } = o;
    // 属性重命名
    let { a: newName1, b: newName2 } = o;
    

    结构赋值同样适用于函数声明

    type C = { a: string, b?: number }
    function f({ a, b }: C): void {
        // ...
    }
    

    函数类型
    函数声明

    function sum(x: number, y: number): number {
        return x + y;
    }
    // 输入多余的(或者少于要求的)参数,是不被允许的
    sum(1, 2, 3); // error
    sum(1); // error
    

    函数表达式

    let mySum = function (x: number, y: number): number {
        return x + y;
    };
    // 等价于
    let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
        return x + y;
    };
    // 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。
    // 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
    // 在 ES6 中,=> 叫做箭头函数
    

    可选参数

    function buildName(x: string, y?: string): string {
        if (y) {
            return `${x}${y}`;
        } else {
            return x;
        }
    }
    

    注意,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了
    参数默认值

    function buildName(x: string, y: string = 'Cat') {
        return `${x}${y}`;
    }
    

    剩余参数

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

    重载
    重载允许一个函数接受不同数量或类型的参数时,作出不同的处理;
    比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。

    // 利用联合类型,我们可以这么实现
    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('');
        }
    }
    

    然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

    这时,我们可以使用重载定义多个 reverse 的函数类型

    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('');
        }
    }
    

    我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

    注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

    接口Interfaces

    对行为的抽象,接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀

    interface Person {
        name: string;
        age: number;
    }
    
    let tom: Person = {
        name: 'Tom',
        age: 25
    };
    // 定义的变量比接口少了一些属性是不允许的
    let tom: Person = {
        name: 'Tom'
    };
    // 多一些属性也是不允许的
    let tom: Person = {
        name: 'Tom',
        age: 25,
        gender: 'male'
    };
    

    可见,赋值的时候,变量的形状必须和接口的形状保持一致。
    注意,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以;
    有时我们希望不要完全匹配一个形状,那么可以用可选属性:
    可选属性

    interface Person {
        name: string;
        age?: number;
    }
    // ok
    let tom: Person = {
        name: 'Tom'
    };
    // ok
    let tom: Person = {
        name: 'Tom',
        age: 25
    };
    // oh no 这时仍然不允许添加未定义的属性
    let tom: Person = {
        name: 'Tom',
        age: 25,
        gender: 'male'
    };
    
    • 对可能存在的属性进行预定义
    • 捕获引用了不存在的属性时的错误
      任意属性
      需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
    interface Person {
        name: string;
        age?: number;
        [propName: string]: any;
    }
    // ok
    let tom: Person = {
        name: 'Tom',
        gender: 'male'
    };
    // not ok 
    let tom: Person = {
        name: 'Tom',
        age: 25,
        gender: 'male'
    };
    // 任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 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; // Cannot assign to 'id' because it is a constant or a read-only property.
    

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

    interface Person {
        readonly id: number;
        name: string;
        age?: number;
        [propName: string]: any;
    }
    
    let tom: Person = {
        name: 'Tom',
        gender: 'male'
    };
    // ok
    tom.id = 89757;
    

    TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

    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!
    

    readonly vs const
    最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用readonly。
    函数类型

    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    

    对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配

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

    函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是 false和true)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与 SearchFunc接口中的定义不匹配。

    let mySearch: SearchFunc;
    mySearch = function(src, sub) {
        let result = src.search(sub);
        return result > -1;
    }
    

    可索引的类型
    支持两种索引签名:字符串和数字。
    可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致

    interface NumberArray {
        [index: number]: number;
    }
    let fibonacci: NumberArray = [1, 1, 2, 3, 5];
    // NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number
    interface ReadonlyStringArray {
        readonly [index: number]: string;
    }
    let myArray: ReadonlyStringArray = ["Alice", "Bob"];
    myArray[2] = "Mallory"; // error!
    

    类类型
    继承接口
    一个接口可以继承多个接口,创建出多个接口的合成接口

    interface Shape {
        color: string;
    }
    
    interface PenStroke {
        penWidth: number;
    }
    
    interface Square extends Shape, PenStroke {
        sideLength: number;
    }
    
    let square = <Square>{};
    square.color = "blue";
    square.sideLength = 10;
    square.penWidth = 5.0;
    

    混合类型
    可以同时具有上面提到的多种类型。

    interface Counter {
        (start: number): string;
        interval: number;
        reset(): void;
    }
    
    function getCounter(): Counter {
        let counter = <Counter>function (start: number) { };
        counter.interval = 123;
        counter.reset = function () { };
        return counter;
    }
    
    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;
    

    接口继承类
    参考文档:TS官方文档

    相关文章

      网友评论

        本文标题:TS文档的搬运工

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