TypeScript语法重点

撰写于 2019-02-03 修改于 2019-07-03 分类 TypeScript 标签 TS语法

ts语法


基本类型

  1. 布尔值

    let isDone: boolean = false;
    
  2. 数字

    let decLiteral: number = 5;
    let hexLiteral: number = oxf00d;
    let binaryLiteral: number = 0b1010;
    let octLiberal: number = 0o756;
    
  3. 字符串

    let name: string = "jack";
    //字符串拼接
    let sentences: string = "Hello, my name is" + name + ".\n" + "My age is" + (age + 1) + ".";
    
  4. 数组

    //普通定义
    let list: number[] = [1, 2, 3];
    //泛型定义
    let list: Array<number> = [1, 2, 3];
    
  5. 元组

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

    let x:[string, number];
    //正确初始化
    x = ["hello", 10];
    //错误初始化
    x = [10, "hello"];
    //访问已知索引的元素
    console.log(x[0].substr(1))
    //访问越界元素,会使用联合类型替代(string|number)
    x[3] = "world";
    //"string"和"number"都有toString()函数
    console.log(x[5].toString());
    //不能把该元组的元素赋值为,除string|number之外的类型
    x[6] = true; //error
    
  6. 枚举

    //定义枚举类型,枚举值默认是从0开始的
    enum Color {Red, Green, Blue};
    //定义枚举变量
    let c:Color = Color.Red;
    
    //自定义枚举值
    enum Color {Red = 1,Green, Blue};
    enum Color {Red = 1, Green = 5, Blue = 8};
    
    //使用枚举名
    let colorName: string = Color[5]; //"Green"
    
  7. 任意值

    任意值:any,可以赋给该类型变量任意值,编译阶段不会去检查该类型的值。

    let noSure: any = 4;
    noSure = "maybe a string instead";
    noSure = true;
    

    注意:any的作用同Object类型有相似之处,但是本质不同,any类型的变量,在编译阶段不会去检查类型,也不会检查方法是否存在,例如:

    let notSure: any = 4;
    //以下方法,编译可以通过,编译器不会检查该方法是否存在,isItExists()可能在运行时存在
    notSure.isItExists();
    

    但是,Object是不行的,Object类似于所有类型的父类,所有类型的值都可以赋值给Object类型,但是在编译阶段会检查该值类型和方法调用是否正确,例如:

    let prettySure: Object = 4;
    //错误,Object中没有toFiexd
    prettySure.toFiexd();
    

    any的其他用法:

    let list: any[] = [1, true, "free"];
    list[1] = 100;
    
  8. void、undefined、null、never

  9. object

    object表示非原始类型,也就是除number, string, boolean, sysmbol, null, undefined之外的类型。

  10. 类型断言

    类型断言跟其他语言里面的类型转换很相似,都是进行类型转换,但是ts里面不会进行类型检查,类型检查交给了程序员来处理,明确告诉编译器:相信我,我知道自己在干嘛!,有两种形式的类型断言:

    //1. 尖括号
    let someVaue: any = "This is a string";
    let strLength: number = (<string>someVlue).length;
    //2. as(推荐)
    let strLength: number = (someValue as string).length;
    

变量声明

  1. var声明
//函数外声明
var a = 10;

//函数内声明
function f() {
    var message = "Hello world";
    return message;
}

//闭包声明
function f() {
    var a = 10;
    return function() {
        var b = a + 1;
        return b;
    }        
}
var g = f();
g();    //return 11
  1. var变量作用域

    如果你用过c/c++,java等其他高级语言,你会发现ts/js中的变量的作用域很奇怪,这是应为ts/js中以前是没有块级作用域的概念,例如:

    function f(isInit: boolean) {
        if (isInit) {
            var x = 10;
        }
        return x;
    }
    
    f(true);    //return 10
    f(false);    //undefined
    

    你会发现,虽然 var x = 10 定义在 if 语句块中,但是块外依旧可以访问,这是因为var声明的变量只有两种作用域:1.全局作用域 2.函数作用域。这种方式虽然有方便的地方,但是也会引发很多问题:

    function sumMatrix(matrix: num[][]) {
        var sum = 0;
        for (var i = 0; i < matrix.length; i++) {
            var currentRow = matrix[i];
            for (var i = 0; i < currentRow.length; i++ {
                sum += currentRow[i];
            }
        }
        return sum;
    }
    

    如果以上逻辑运行在C/C++平台上,没有一点问题,因为内层 for 循环的i会屏蔽掉外层 fori,但是在ts/js中,这就会引发重大问题,在函数内无论声明多少次 i ,都是引用的同一个变量。

    还有一个诡异的问题:

    for (var i = 0; i < 10; i++) {
        //setTimeout延迟若干秒后,执行函数
        setTimeout(function() {console.log(i);}, 100 * i};
    }
    

    运行以上函数会输出:10 10 10 10 10 10 10 10 10 10 而不是预想中的:0 1 2 3 4 5 6 7 8 9

    这是因为:循环10次,引用的都是同一个i,循环10次后,i 为10,所有的函数使用的都是同一个i,所以输出的都是10!

    如何处理呢?只需每次循环的时候,为每次i创建一个备份,单独创建一个函数环境:

    for (var i = 0; i < 10; i++) {
        function(i) {
            setTimeout(function() {console.log(i);}, 100 * i};
        }(i);
    }
    
  2. let 声明

    正是因为 var带来了很多不方便的地方,所以let就出现了,它弥补了var块级作用域的缺失。使用方法,同var相同

    function f(input: boolean) {
        let a = 100;
        if (input) {
            let b = a + 1;
            return b;            
        }
        //错误,b没有定义,因为b是let声明的,只在 if 语句中可见
        return b;
    }
    

    var声明的变量,无论在何处声明,都可以,但是let不行,let声明的变量必须要在使用前声明

    //var声明的变量,这样写没有问题
    a = a + 1;
    var a = 10;
    
    //但是let声明的变量就不行, 会报错: b is not defined
    b = b + 1;
    let b = 10;
    
  3. 重定义

    var 声明的变量,无论声明多少次,都不会报错,都是引用的同一个变量:

    //以下正常运行
    function f(x) {
        var x;
        var x = 10;
        if (true) {
            var x;
        }
    }
    

    let声明的变量不允许出现这种情况,相同的作用域中,不能出现重定义。

    //错误!
    function f(x) {
        let x = 10;
    }
    
    //错误!
    function f() {
        let x = 10;
        var x = 10;
    }
    

    但是在不同的作用域中,let声明的变量,允许出现相同定义,内层定义会屏蔽外层定义。

    function g(isInit: boolean) {
        var x = 10;
        if (isInit) {
            let x = 20;
            return x;
        }
        return x;
    }
    
    g(true);    //return 20;
    g(false);    //return 10;
    

    总之,let的使用方式,跟C/C++,Java等其他高级语言默认变量声明,是相似的。

  4. const

    就是常量,被第一次赋值以后不允许再赋值。跟let有相同的作用域规则。

    注意:只是变量不允许第二次赋值,但是里面包含的成员变量,是不受限制的。当然也可以将成员变量设置为只读的。

    const kitty = {
        name: "Aurora",
        numLives: 9,
    };
    
    //kitty不允许再进行赋值,再次进行赋值操作会报错。
    kitty = {
        name = "Danielle",
        numLives: 12,
    };
    
    //但是可以修改kitty.name 和 kitty.numLives的值。
    kitty.name = "Danielle";
    kitty.numLives = 12;
    
  5. 解构

    解构赋值是ts/js的一种表达式,可以将数组中的值或者对象的属性解包为不同的变量

  6. 数组解构

    //基本的数组解构
    var foo = ["one", "two", "three"];
    var [one, two, tree] = foo;
    console.log(one); //"one"
    console.log(two); //"two"
    console.log(three);    //"three"
    
    //声明与赋值分开
    var a, b;
    [a, b] = [1, 2];
    console.log(a);//1
    console.log(b);//2
    
    //默认值
    var a, b;
    [a=5, b=7] = [1];
    console.log(a);//1
    console.log(b);//7
    
    //交换值
    var a = 1;
    var b = 3;
    [a, b] = [b, a];
    console.log(a); //3
    console.log(b);    //1
    
    //函数返回值解构    
    function f() {
        return [1, 2];
    }
    var a,b;
    [a, b] = f();
    console.log(a); //1
    console.log(b);    //2
    
    //忽略一些值
    function f() {
        return [1, 2, 3];
    }
    var [a, , b] = f();
    console.log(a); //1
    console.log(b);    //3
    
    //把一个数组的剩余部分赋值给一个变量
    var [a, ...b] = [1, 2, 3];
    console.log(a); //1
    console.log(b);    //[2, 3]
    
    //剩余部分的变量赋值(即...b),不允许后面跟一个空逗号,所以下面的使用是错误的
    var [a, ...b,] = [1, 2, 3];
    
    //从一个正则表达式匹配项里面解包(操作都一样)
    function parseProtocol(url) { 
      var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
      if (!parsedURL) {
        return false;
      }
      console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]
    
      var [, protocol, fullhost, fullpath] = parsedURL;
      return protocol;
    }
    
    console.log(parseProtocol('https://developer.mozilla.org/en-US/Web/JavaScript')); // "https"
    
  7. 对象解构

    //基础赋值
    var o = {p:42, q:true};
    var {p, q} = o;
    console.log(p);    //42
    console.log(q);    //true
    
    //不声明,直接赋值
    var a, b;
    //注:当在没有声明的情况下使用对象字面量解构,则必选要加上()。因为如果不加(),左边会被认为是一个语句块,并不会被认定为一个对象字面量。
    //相当于:var {a, b} = {a:1, b:2}
    ({a, b} = {a:1, b:2});
    
    //赋值给一个新的变量名
    var o = {p:42, q:true};
    var {p: foo, q: bar} = 0;
    console.log(foo);    //42
    console.log(bar);    //true
    
    //默认值
    var {a = 10, b = 5} = {a:3};
    console.log(a); // 3
    console.log(b); // 5
    
    //新的变量名,给出默认值
    var {a: aa = 10, b: bb = 5} = {a: 3};
    console.log(aa); //3
    console.log(bb); //5
    
    //设置函数参数的默认值
    function drawES2015Chart({size = 'big', cords = {x: 0, y: 0}, radius = 25} = {}) {
      console.log(size, cords, radius);
      // do some chart drawing
    }
    
    drawES2015Chart({
      cords: {x: 18, y: 30},
      radius: 30
    });
    
    //嵌套形式对象的解构
    var metadata = {
        title: 'Scratchpad',
        translations: [
           {
            locale: 'de',
            localization_tags: [],
            last_edit: '2014-04-14T08:43:37',
            url: '/de/docs/Tools/Scratchpad',
            title: 'JavaScript-Umgebung'
           }
        ],
        url: '/en-US/docs/Tools/Scratchpad'
    };
    
    var {title: englishTitle, translations: [{title: localeTitle}]} = metadata;
    
    console.log(englishTitle); // "Scratchpad"
    console.log(localeTitle);  // "JavaScript-Umgebung"
    
    //for解构
    var people = [
      {
        name: 'Mike Smith',
        family: {
          mother: 'Jane Smith',
          father: 'Harry Smith',
          sister: 'Samantha Smith'
        },
        age: 35
      },
      {
        name: 'Tom Jones',
        family: {
          mother: 'Norah Jones',
          father: 'Richard Jones',
          brother: 'Howard Jones'
        },
        age: 25
      }
    ];
    
    for (var {name: n, family: {father: f}} of people) {
      console.log('Name: ' + n + ', Father: ' + f);
    }
    // "Name: Mike Smith, Father: Harry Smith"
    // "Name: Tom Jones, Father: Richard Jones"
    
    //解包对象字段,传递给函数参数
    function userId({id}) {
      return id;
    }
    
    function whois({displayName, fullName: {firstName: name}}) {
      console.log(displayName + ' is ' + name);
    }
    
    var user = { 
      id: 42, 
      displayName: 'jdoe',
      fullName: { 
          firstName: 'John',
          lastName: 'Doe'
      }
    };
    
    console.log('userId: ' + userId(user)); // "userId: 42"
    whois(user); // "jdoe is John"
    
    //计算出对象属性名字,然后解构
    let key = 'z';
    let {[key]: foo} = {z: 'bar'};
    console.log(foo); // "bar"
    
    //对象解构的剩余部分(剩余部分,必须是可枚举的)
    let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
    a; // 10 
    b; // 20 
    rest; // { c: 30, d: 40 }
    
    //非法js标识符作为属性名
    //通过提供有效的替代标识符,可以将解构与非法js标识符的属性名一起使用。
    const foo = { 'fizz-buzz': true };
    const { 'fizz-buzz': fizzBuzz } = foo;
    console.log(fizzBuzz); // "true"
    
  8. 展开

    展开操作符与解构相反,讲一个数组展开为另一个数组,或者将一个对象展开为另外一个对象:

    //数组
    let first = [1, 2];
    let second = [3, 4];
    let bothPlus = [0, ...first, ..second, 5]; //[0, 1, 2, 3, 4, 5],对first,second进行浅拷贝
    
    //对象:
    //对象的展开比数组的展开要复杂的多。 像数组展开一样,它是从左至右进行处理,但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。
    let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
    let search = { ...defaults, food: "rich" }; //{ food: "rich", price: "$$", ambiance: "noisy" }
    
    //展开的对象,里面的属性必须是可枚举属性,才可以被展开,方法会被丢失掉
    class C {
      p = 12;
      m() {
      }
    }
    let c = new C();
    let clone = { ...c };
    clone.p; // ok
    clone.m(); // error!
    

接口

  1. 介绍

    ts的接口定义,还是跟其他语言的接口,有点区别的。ts里,接口的就是定义契约(约束)。

    //基本用法
    interface LabelValue {
        label: string;
    }
    
    function printLabel(labelObj: LabelValue) {
        console.log(labelObj.lable);
    }
    
    let myObj = {size: 10, label: "Size 10 Object"};
    printLable(myObj);
    

    注:上面的代码运行起来没有问题,但是会有一个奇怪的问题就是:如果不定义 let myObj = {size: 10, label: "Size 10 Object"},直接使用 printLable({size: 10, label: "Size 10 Object"}),会报错:Argument of type '{ size: number; lable: string; }' is not assignable to parameter of type 'LabelValue'.Object literal may only specify known properties, and 'size' does not exist in type 'LabelValue'。大致的意思是:对象字面量只能指定已知属性。(暂时不知道什么意思)

  2. 可选属性

    有些情况下接口里面的属性,只是在某些条件下存在,有的时候不存在,可以使用可选属性。

    //可选属性
    interface SquareCongfig {
        color?: string;
        width?: number;
    }
    
    function createSquare(config: SquareCongfig): { color: string, area: number } {
        let newSquare = { color: "white", area: 100 };
        if (config.color) {
            newSquare.color = config.color;
        }
    
        if (config.width) {
            newSquare.area = config.width * config.width;
        }
        //也可用展开
        //let newSquare = { color: "white", area: 100, ...config };
        return newSquare;
    }
    
    let mySquare = createSquare({ color: "black" })
    console.log(mySquare.color);
    console.log(mySquare.area);
    
  3. 只读属性

    顾名思义,对象中属性的值不能被修改,用 readonly 修饰属性即可

    interface Point {
        readonly x: number;
        readonly y: number;
    }
    
    let point: Point = {x: 10, y: 20};
    //error: Cannot assign to 'x' because it is a constant or a read-only property.
    point.x = 5
    

    数组也可以只读,只需使用ReadonlyArray<T>定义数组即可,使用方法跟 Array<T> 一样,但是去掉了所有可修改值得方法。

    let a: number[] = [1, 2, 3, 4];
    let ro: ReadonlyArray<number> = a;
    //error
    ro.push(5);
    //error
    ro[0] = 12;
    //error
    ro.length = 10;
    //error
    a = ro;
    //可以强转
    a = ro as number[];
    

    注:变量用 const, 属性用 readonly

  4. 函数类型

    //函数类型
    interface SearchFunc {
        (source: string, subString: string): number;
    }
    //使用
    let mySearch: SearchFunc = function (source: string, subString: string) {
        let result = source.search(subString);
        return result;
    }
    //不加参数类型,依旧可以根据接口判断出来
    let otherSearch: SearchFunc = function (src, sub) {
        return src.search(sub);
    }
    
  5. 可索引类型

    索引签名表示:当时用number去索引 对象或者数组时,会返回 string 类型的返回值。

    //可索引类型
    interface StringArray {
        [index: number]: string;
    }
    
    let myArray: StringArray = ["Bob", "Fred"]
    

    支持两种类型的索引:stringnumber。可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。这是因为:当时用number来索引时,js会将它转换为string然后再去索引对象,也就是用100去索引和用“100”去索引,两者是等效的,因此,两者要保持一致。

  6. 类类型

    //接口描述了公共部分,而不是公有和私有部分。
    interface ClockInterface {
        currentTime: Date;
        setTime(d: Date);
    }
    
    class Clock implements ClockInterface {
        currentTime: Date;
        setTime(d: Date) {
            this.currentTime = d;
        }
        constructor(h: number, m: number) {}
    }
    
  7. 类静态部分与实例部分(不知所云,先不看了)

  8. 继承接口

    //一个接口可以继承很多接口
    interface Shape {
        color: string;
        (): number;
    }
    
    interface PenStroke {
        penWidth: number;
    }
    
    interface Square extends Shape, PenStroke {
        sideLength: number;
    }
    
    let square = <Square> {};
    square.color = "blue";
    square.sideLength = 10;
    square.penWidth = 5.0;
    
  9. 混合接口类型

    //一个对象可以同时作为函数和对象使用,并带有额外的属性
    
    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的类跟 java 的类很相似。

  1. 继承

    //继承
    class Animal {
        name: string;
        constructor(theName: string) {
            this.name = theName;
        }
        move(distanceInMeters: number = 0) {
            console.log(this.name + " moved " + distanceInMeters + "m.");
        }
    }
    
    class Snake extends Animal {
        constructor(name: string) {
            super(name);
        }
        move(distanceInMeters = 5) {
            console.log("Slithering...");
            super.move(distanceInMeters);
        }
    }
    
    class Horse extends Animal {
        constructor(name: string) {
            super(name);
        }
        move(distanceInMeters = 45) {
            console.log("Golloping...");
            super.move(distanceInMeters);
        }
    }
    let sam = new Snake("Sammy the Python");
    let tom: Animal = new Horse("Tommy the Palomino");
    
    sam.move();
    tom.move(34);
    
  2. 权限:public, private, protected

  3. readonly: 修饰的属性为只读。

  4. 参数属性

    参数属性算是一个语法糖,在构造函数的参数部分,定义并初始化一个成员。

    class Animal {
        //相当于定义了: priavte name: string
        constructor(private name: string) {}
        move(distanceInMeters: number) {
            console.log(this.name + " moved " + distanceInMeters + "m.");
        }
    }
    
  5. 存取器:getters/setters

    let password = "secret passcode";
    class Employee {
        private _fullName: string;
        get fullName(): string {
            return this._fullName;
        }
    
        set fullName(newName: string) {
            if (password && password == "secret passcode") {
                this._fullName = newName;
            } else {
                console.log("Error: Can't update name!");
            }
        }
    }
    let employee = new Employee();
    employee.fullName = "Bob Smith";
    if (employee.fullName) {
        alert(employee.fullName);
    }
    
  6. 静态属性: static

  7. 抽象类: abstract

  8. 构造函数:

    上面例子中,我们使用 let employee = new Employee(),创建一个Employee类型的实例,这个Employee其实是一个构造函数,这个构造函数包含了类所有的静态属性。

    class Greeter {
        static standardGreeting = "Hello, there";
        greeting: string;
        greet() {
            if (this.greeting) {
                return "Hello, " + this.greeting;
            }
            else {
                return Greeter.standardGreeting;
            }
        }
    }
    
    let greeter1: Greeter;
    greeter1 = new Greeter();
    console.log(greeter1.greet());
    
    let greeterMaker: typeof Greeter = Greeter;
    greeterMaker.standardGreeting = "Hey there!";
    
    let greeter2: Greeter = new greeterMaker();
    console.log(greeter2.greet());
    
  9. 类当接口使用

    class Point {
        x: number;
        y: number;
    }
    
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {x: 1, y: 2, z: 3};
    

函数

  1. 定义
//定义
function add(x: number, y: number) {
    return x + y;
}
//或者
let add = function(x: number, y: number): number {return x + y;}
//完整定义(参数名字无所谓,重在函数的签名;没有返回值,也要写void)
let add: (x: number, y: number) => number = function(x: number, y: number): number {return x + y;};
  1. 可选参数

    在js中,可以传递任意数量的参数给函数,但是ts不行,传递的函数参数数量,必须要符合函数的定义。

    //函数类型为:(firstName: string, lastName?: string) => string
    function buildName(firstName: string, lastName?: string) {
        if (lastName)
            return firstName + " " + lastName;
        else
            return firstName;
    }
    
    let result1 = buildName("Bob");  // works correctly now
    let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
    let result3 = buildName("Bob", "Adams");  // ah, just right
    
  2. 参数默认值:同上

  3. 剩余参数

    function buildName(firstName: string, ...restOfName: string[]) {
      return firstName + " " + restOfName.join(" ");
    }
    
    let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
    //函数类型
    let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
    

    高级类型

  4. 交叉类型: typeA & typeB

    function extend<T, U>(first: T, second: U): T & U {
        let result = <T & U>{};
        for (let id in first) {
            (<any>result)[id] = (<any>first)[id];
        }
    
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                (<any>result)[id] = (<any>second)[id];
            }
        }
        return result;
    }
    
    class PersonOne {
        constructor(public name: string) {}
    }
    
    interface Loggable {
        log(): void;
    }
    
    class ConsoleLogger implements Loggable {
        log() {}
    }
    
    let jim = extend(new PersonOne("Jim"), new ConsoleLogger());
    var n = jim.name;
    jim.log();
    
  5. 联合类型: typeA | typeB

    限制数据类型只能是 typeA 或者 typeB

    function padLeft(value: string, padding: string | number) {
        // ...
    }
    //Error!只能传入string或者number
    let indentedString = padLeft("Hello world", true);
    

    还有一种情况,当一个变量的类型是 typeA | typeB,那么该变量包含了A和B共有的成员。

    interface Bird {
        fly():
        layEggs();
    }
    interface Fish {
        swim():
        layEggs();
    }
    
    function getSmallPet(): Fish | Bird {
        //...
    }
    
    let pet = getSmallPet();
    pet.layEggs();//Okay!
    pet.swim(); //Error!
    

    怎么让以上代码没有错误呢,使用类型断言:

    if ((<Fish>pet).swim) {
        (<Fish>pet).swim();
    } else {
        (<Bird>pet).fly();
    }
    

    可迭代性

当一个对象实现了Symbol.iterator属性时,我们认为他是可迭代性的。Array,Map,Set,String,Int32Array,Uint32Array都是实现了各自的Symbol.iterator。

  1. for…of 值的遍历 (注意对象不能使用!!)

    let someArray = [1, "string", false];
    
    for (let entry of someArray) {
        console.log(entry);
    }
    
  2. for…in 键的遍历

    for (let index in someArray) {
        console.log(index); //
    }
    
Site by ZHJ using Hexo & Random

Hide