TypeScript基础

TypeScript简介

简介

TypeScript是以JavaScript为基础构建出来的语言。

TypeScript是JavaScript的一个超集,支持 ECMAScript 6 标准(ES6 教程)。

TypeScript扩展了JavaScript,并添加了类型。

TypeScript可以在任何支持JavaScript的平台中执行。

TypeScript由微软开发的自由和开源的编程语言。

TypeScript设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。

TS不能被JS解析器直接执行。

TypeScript相对于JavaScript增加内容

  • 类型
  • 支持ES新特性
  • 添加ES不具备的新特性
  • 丰富的配置选项
  • 强大的开发工具

TypeScript环境搭建

操作步骤:

  1. 使用npm全局安装typescript。

    npm i -g typescript
    
    # 检查是否安装完成
    tsc -v
    
  2. 新建一个文件:HelloTS.ts。

    console.log("Hello, TS");
    
  3. 使用以下命令转换为JavaScript文件(会生成一个xx.js文件)。

    说明:tsc为TypeScript编译器。

    tsc helloTS.ts
    

TypeScript环境搭建(自动编译)

自动编译整个项目自动编译文件

编译文件时,使用 -w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。

示例:

  tsc xxx.ts -w

说明:如需要关闭监视,按Ctrl+C键即可。

自动编译整个项目

  1. 执行以下命令生成配置文件tsconfig.json。

    tsc --init
    
  2. 修改tsconfig.json配置。

    {
        "outDir": "./dist",
    }
    
  3. 全局监听和自动编译。

    • 方式一:VS Code,单击“终端 > 运行生成任务”,选择“tsc: 监视 - tsconfig.json”。

    • 方式二:执行以下命令即可。

      tsc -w
      

tsconfig.json文件解析

TypeScript环境搭建(webpack)

常规环境搭建

  1. 在项目根目录下,执行以下命令,创建文件package.json。

    npm init -y
    
  2. 执行以下命令,下载构建工具。

    npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader html-webpack-plugin clean-webpack-plugin
    
    # 共安装了7个包:
    # - webpack:构建工具webpack。
    # - webpack-cli:webpack的命令行工具。
    # - webpack-dev-server:webpack的开发服务器。
    # - typescript:ts编译器。
    # - ts-loader:ts加载器,用于在webpack中编译ts文件。
    # - html-webpack-plugin:webpack中html插件,用来自动创建html文件。
    # - clean-webpack-plugin:webpack中的清除插件,每次构建都会先清除目录。
    
  3. 新建文件webpack.config.js,完成TS对应的webpack配置。

    const path = require('path');
    // 引入html插件
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    // 引入clean插件
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    // webpack中的所有的配置信息都应该写在module.exports中
    module.exports = {
        // mode 代表 webpack 运行的模式,可选值有两个 development 和 production
        // 结论:开发时候一定要用 development,因为追求的是打包的速度,而不是体积;
        // 反过来,发布上线的时候一定能要用 production,因为上线追求的是体积小,而不是打包速度快!
        mode: 'development',
        // 指定入口文件
        entry: "./src/index.ts",
        output: {
            // 指定打包文件的目录
            path: path.resolve(__dirname, 'dist'),
            // 打包后文件的文件
            filename: "bundle.js",
    
            // 告诉webpack不使用箭头
            // 默认打包后是一个立即执行的箭头函数,在IE 11中也是无法执行的!
            // 加上下面的配置,可以在webpack打包时,最外层不再是箭头函数
            // webpack新版本已经不想兼容IE了!233
            environment: {
                arrowFunction: false
            }
        },
        // 指定webpack打包时要使用模块
        module: {
            // 指定要加载的规则
            rules: [
                {
                    // test指定的是规则生效的文件
                    test: /\.ts$/,
                    // 要使用的loader
                    // Webpack在加载时是"从后向前"加载!
                    use: {
                        loader: 'ts-loader',
                    },
                    // 要排除的文件
                    exclude: /node-modules/
                }
            ]
        },
    
        // 配置Webpack插件
        plugins: [
            new CleanWebpackPlugin(),
            new HTMLWebpackPlugin({
                // title: "这是一个自定义的title"
                template: "./src/index.html"
            }),
        ],
        // 用来设置引用模块
        resolve: {
            extensions: ['.ts', '.js']
        }
    }
    
    
  4. 执行以下命令生成配置文件tsconfig.json。

    tsc --init
    
  5. 修改文件package.json,增加如下配置(新增build和start选项)。

    {
    	...
    	"scripts": {
    		"test": "echo \"Error: no test specified\" && exit 1",
    		"build": "webpack",
    		"start": "webpack serve --open chrome.exe"
    	},
    	...
    }
    
  6. 项目使用。

    1. 创建src目录。

    2. src目录中新建文件文件index.ts。

    3. src目录中新建并修改文件index.html(VS Code中使用html:5模板生成)。

    4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Document</title>
        </head>
        <body></body>
      </html>
      
      
    5. 在src下创建ts文件,并在并命令行执行以下命令对代码进行编译。

      npm run build
      

      或者执行以下命令来启动开发服务器。

      npm start
      
    6. 打开文件dist/index.html,即可预览运行效果。

新增Babel

除了webpack,开发中还经常需要结合babel来对代码进行转换。

以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将babel引入到项目中。

说明:虽然TS在编译时也支持代码转换,但是只支持简单的代码转换。

对于例如:Promise等ES6特性,TS无法直接转换,这时还要用到babel来做转换。

  1. 执行以下命令安装依赖包。

    npm i -D @babel/core @babel/preset-env babel-loader core-js
    
    # 共安装了4个包:
    # - @babel/core:babel的核心工具
    # - @babel/preset-env:babel的预定义环境
    # - @babel-loader:babel在webpack中的加载器
    # - core-js:core-js用来使老版本的浏览器支持新版ES语法
    
  2. 修改配置文件webpack.config.js。

    ...
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: [
                    {
                        loader: "babel-loader",
                        options:{
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        "targets":{
                                            "chrome": "58",
                                            "ie": "11"
                                        },
                                        "corejs":"3",
                                        "useBuiltIns": "usage"
                                    }
                                ]
                            ]
                        }
                    },
                    {
                        loader: "ts-loader",
                    }
                ],
                exclude: /node_modules/
            }
        ]
    }
    ...
    

新增less

  1. 执行以下命令安装依赖包。

    npm i -D less less-loader css-loader style-loader
    
  2. 修改配置文件webpack.config.js。

    ...
    module: {
        rules: [
            ...
            {
                ...
            },
            {
                test: /\.less$/i,
                use: [
                    // compiles Less to CSS
                    'style-loader',
                    'css-loader',
                    // 引入postcss
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        'postcss-preset-env',
                                        {
                                            browsers: 'last 2 version',
                                        },
                                    ],
                                ],
                            },
                        },
                    },
                    'less-loader',
                ],
            },
        ],
    },
    ...
    
  3. (可选)验证postcss是否生效。

    1. 在src目录下新建style目录。

    2. 在style目录中新建文件index.less。

      body {
        background-color: aqua;
      }
      
    3. 在index.ts中引入样式文件。

      import "./style/index.less";
      ...
      

新增postcss

是一个用 JavaScript 工具和插件转换 CSS 代码的工具。

  1. 执行以下命令安装依赖包。

    npm i -D postcss postcss-loader postcss-preset-env
    
  2. 修改配置文件webpack.config.js。

    ...
    module: {
        rules: [
            ...
            {
                ...
            },
            {
                test: /\.less$/i,
                use: [
                    // compiles Less to CSS
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ],
            },
        ],
    },
    ...
    
  3. (可选)验证less是否生效。

    1. 在src目录下新建style目录。

    2. 在style目录中新建文件index.less。

      body {
        background-color: aqua;
        display: flex; // postcss转换
      }
      
      
    3. 在index.ts中引入样式文件。

      import "./style/index.less";
      ...
      

Rollup构建TS项目

常见类型

语法:

let 变量: 类型;

let 变量: 类型 = 值;

function fn(参数: 类型, 参数: 类型): 类型{
    ...
}

自动类型判断

  • TS拥有自动的类型判断机制。
  • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型。
  • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明。

类型:

序号 类型 例子 描述
1 number 1, -33, 2.5 任意数字。
2 string 'hi', "hi", hi 任意字符串。
3 boolean true、false 布尔值true或false。
4 array [1, 2, 3] 任意JS数组。
6 any * 任意类型。
The any type is useful when you don’t want to write out a long type just to convince TypeScript that a particular line of code is okay.
noImplicitAny * tsconfig.json文件中的配置。
在某些没有类型注释的情况下,当 TypeScript 无法推断变量的类型时,它将退回到 any 类型。这可能导致一些错误被遗漏,例如:
function fn(s) {
// No error?
console.log(s.subtr(3));
}
fn(42);
然而,只要 TypeScript 推断出任何错误,它就会发出一个错误。
function 函数是在 JavaScript 中传递数据的主要方式。TypeScript 允许您指定函数的输入和输出值的类型。
Parameter Type Annotations function greet(name: string) 参数类型注释。
声明函数时,可以在每个参数后面添加类型注释,以声明函数接受的参数类型。参数类型注释位于参数名之后。
Return Type Annotations function getFavoriteNumber(): number 返回类型注释。
还可以添加返回类型注释。返回类型注释出现在参数列表之后。
Anonymous Functions 匿名函数。匿名函数与函数声明稍有不同。当一个函数出现在 TypeScript 可以决定如何调用它的地方时,该函数的参数将自动被赋予类型。
Object let b: { name: string; age?: number };

b = { name: "张三" };
b = { name: "张三", age: 25 };
任意的JS对象。
Union Types let phone: number | string = "027-88888888"; 组合类型的第一种方法是联合类型。联合类型是由两个或多个其他类型组成的类型,表示可能是其中任何一种类型的值。我们将这些类型中的每一种称为工会成员。
联合类型,多种类型:可以用`
The type alias type Point = { x: number; y: number; };
let a: Point;
类型别名。
Interface interface Point { x: number; y: number; };
let a: Point;
接口声明 是命名对象类型的另一种方式。
类型别名和接口之间的区别
类型别名和接口非常相似,在大多数情况下你可以在它们之间自由选择。 几乎所有的 interface 功能都可以在 type 中使用,关键区别在于不能重新开放类型以添加新的属性,而接口始终是可扩展的。
Type Assertions 类型断言。
Literal Types `function printText(s: string, alignment: "left" "right"
Literal Inference 字面推理。
null and undefined JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.
strictNullChecks off 关闭 strictNullChecks 后,仍然可以正常访问可能为 null 或未定义的值,并且可以将 null 和未定义的值赋给任何类型的属性。
strictNullChecks on
Non-null Assertion Operator Postfix非空断言运算符(后缀!
TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn’t null or undefined.
enum enum
5 字面量 其本身 限制变量的值就是该字面量的值。 直接使用字面量进行类型声明,也可以使用联合类型。
7 unknown * 类型安全的any,TypeScript 3.0中引入的unknown类型也被认为是top type,但它更安全。与any一样,所有类型都可以分配给unknown。
8 void 空值(undefined) 没有值(或undefined),主要用于函数的返回值使用。
9 never 没有值 不能是任何值,主要用于函数的返回值使用。
12 tuple [4, 5] 元素,TS新增类型,固定长度数组。
14 symbol 自ECMAScript 2015起,symbol成为了一种新的原生类型,就像number和string一样。 symbol类型的值是通过Symbol构造函数创建的。 可以传递参做为唯一标识 只支持string和number类型的参数。

类型举例:

  1. 数字类型 number:双精度 64 位浮点值。它可以用来表示整数和分数。

    // 示例1
    let binaryLiteral: number = 0b1010; // 二进制
    let octalLiteral: number = 0o744; // 八进制
    let decLiteral: number = 6; // 十进制
    let hexLiteral: number = 0xf00d; // 十六进
    
    // 示例2
    let notANumber: number = NaN;
    let num: number = 123;
    let infinityNumber: number = Infinity; // 无穷大
    let decimal: number = 6; // 十进制
    let hex: number = 0xf00d; // 十六进制
    let binary: number = 0b0101;
    
  2. 字符串类型 string:一个字符系列,使用单引号'或双引号"来表示字符串类型。反引号```来定义多行文本和内嵌表达式。

    let name: string = "Runoob";
    let years: number = 5;
    let words: string = `您好,今年是 ${name} 发布 ${years + 1} 周年`;
    
  3. 布尔类型 boolean:表示逻辑值:true 和 false。

    let flag: boolean = true;
    
    let flag2: boolean = Boolean(1); // 使用构造函数 Boolean 创造的对象不是布尔值
    
  4. 数组类型 :声明变量为数组。

    let arr1: number[] = [1, 2]; // 数字类型的数组
    let arr2: string[] = ["1", "2"]; // 字符串类型的数组
    arr2 = ["a", "b", "c"];
    let arr3: boolean[] = [true, true, false]; // 布尔类型的数组
    let arr4: any[] = [1, "2", true]; // 任意类型的数组
    
    // ----
    // 泛型方式定义数组
    let arr_1: Array<number> = [1, 2]; // 使用数组泛型,数字类型的数组
    let arr_2: Array<string> = ["a", "b", "c"]; // 使用数组泛型,字符串类型的数组
    let arr_3: Array<boolean> = [true, true, false]; // 使用数组泛型,布尔类型的数组
    let arr_4: Array<any> = [1, "2", true]; // 使用数组泛型,任意类型的数组
    
    console.log(arr1, arr2, arr3, arr4);
    
    // ----
    console.log(arr_1, arr_2, arr_3, arr_4);
    
    // ----
    // 多维数组的场景
    let arr1_: number[][] = [
      [1, 2, 3],
      [4, 5, 6],
    ];
    
    let arr_1_: Array<Array<number | string>> = [
      [1, 2, 3],
      [4, 5, "6"],
    ];
    
    console.log(arr1_);
    console.log(arr_1_);
    
    // ---
    // arguments数组
    function arrFn1(...arg: number[]): void {
      console.log(arguments);
    }
    
    arrFn1(2, 3, 4, 5);
    
    function arrFn2(...arg: number[]): void {
      console.log(arguments);
      // ts内置对象IArguments 定义
      let arrTemp: IArguments = arguments;
    }
    
    arrFn2(2, 3, 4, 5);
    
    // 其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
    // interface IArguments {
    //   [index: number]: any;
    //   length: number;
    //   callee: Function;
    // }
    
    // ----
    // 通过接口定义数组
    interface ArrNum {
      [index: number]: string;
    }
    let arrInter = ["2", "3", "4", "5"];
    console.log(arrInter);
    
    
  5. 字面量 :可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围。

    // 直接使用字面量进行类型声明
    let color: 'red' | 'blue' | 'black';
    let num: 1 | 2 | 3 | 4 | 5;
    let sex: 'male' | 'female';
    
    color = "ss"; // 报错,不能将类型 "ss" 分配给类型 "red" | "blue" | "black" 
    
    // 也可以使用联合类型
    let c: boolean | string;
    c = true;
    c = "Hello TS";
    
  6. (不推荐)任意类型 any: 声明为any 变量可以赋予任意类型的值。

    // any表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测
    // 显式声明
    let d: any = 4;
    d = 'hello';
    d = true;
    
    // 隐式声明,声明变量不指定类型,则TS解析器会自动判断变量的类型为any
    let c = 4;
    c = 'hello';
    c = true;
    
  7. unknown:类型安全的any(未知类型的值)。

    // unkown实际上就是一个类型安全的any
    // unkown类型的变量,不能直接赋值给其他变量
    let c: unknown;
    c = "HelloTS";
    c = 10;
    
    let e: string;
    
    // e = c; // 报错:不能将类型unkown分配给类型string
    // 方式一
    if (typeof c === "string") {
      e = c;
    }
    
    // 方式二,类型断言,可以用告诉解析器变量的实际类型
    /*
     * 语法:
     *  变量as类型
     *  <类型>变量
     */
    e = c as string;
    e = <string>c;
    
    
    // unknown 可以定义任何类型的值
    let value: unknown;
    
    value = true;            // OK
    value = 42;              // OK
    value = "Hello World";   // OK
    value = [];              // OK
    value = {};              // OK
    value = null;            // OK
    value = undefined;       // OK
    value = Symbol("type");  // OK
    
    // ----
    let namesUnknow: unknown = "123";
    // 这样写会报错unknow类型不能作为子类型只能作为父类型 any可以作为父类型和子类型
    // unknown类型不能赋值给其他类型
    // let namesUnknow2: string = namesUnknow;
    
    // 这样就没问题 any类型是可以的
    let namesAny: any     = "123";
    let namesAny2: string = namesAny;
    
    // unknown可赋值对象只有unknown和any
    let bbb: unknown = "123";
    let aaa: any     = "456";
    
    aaa = bbb;
    
    
  8. 函数返回值的类型 void:用于标识方法返回值的类型,表示该方法没有返回值。

    // JavaScript没有空值(void)的概念
    // TypeScript中,可以用void表示没有任何返回值的函数
    // void类型的用法,主要用在我们不希望调用者关系函数返回值的情况下,比如通常的异步回调函数
    function hello(): void {
      alert("Hello Runoob");
    }
    
    // void也可以定义undefined和null类型
    let u: void = undefined;
    // let n: void = null;
    
    function sum(a: number, b: number): number {
      return a + b;
    }
    let result = sum(2, 3); // 得到result的返回值类型也就是number类型
    
    // 函数的返回类型为number或者string
    function fnTest(): number | string {
      return "Hello TS";
    }
    
    // ----
    // 使用参数默认值
    const fn2 = function (name: string, age: number = 30): string {
      console.log(name + "-" + age);
      return name + "-" + age;
    };
    fn2("张三", 25);
    fn2("李四");
    
    // ----
    // 使用可选式操作符
    const fn3 = function (name: string, age?: number): string {
      console.log(name + "-" + age);
      return name + "-" + age;
    };
    fn3("张三_?", 25);
    fn3("李四_?"); // age未传值,默认值为undefined
    
    // ----
    // 使用interface约束接口和返回值
    interface User {
      name: string;
      age: number;
    }
    const fn4 = function (user: User): User {
      console.log(user);
      return user;
    };
    fn4({ name: "张三_inter", age: 28 });
    
    // ----
    // 函数重载,待验证
    // 重载是方法名字相同,而参数不同,返回类型可以相同,也可以不同
    //  如果参数类型不同,则操作函数参数类型应该设置为any
    //  参数数量不同,可以将不同的参数设置为可选
    function fnReload(param: number): void {
      console.log(param);
    }
    function fnReload(param: string): void {
      console.log(param);
    }
    function fnReload(param: boolean): void {
      console.log(param);
    }
    function fnReload(param: string, param2: number): void {
      console.log(param);
    }
    function fnReload(param: any, param2?: any): void {
      console.log(param);
    }
    
    fnReload(12);
    fnReload("Hello TS", 12);
    
    
  9. never never:never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环),示例代码如下:

    // ---
    // 声明中的never类型
    // let a: string | number;
    
    // ----
    // 函数举例
    // 返回值为 never 的函数可以是抛出异常的情况
    function error(message: string): never {
      throw new Error(message);
    }
    
    // 返回值为 never 的函数可以是无法被执行到的终止点的情况
    function loop(): never {
      while (true) {}
    }
    
    // ----
    // 函数举例
    let x: never;
    let y: number;
    
    // 运行错误,数字类型不能转为 never 类型
    // x = 123;
    
    // 运行正确,never 类型可以赋值给 never类型
    x = (() => {
      throw new Error("exception");
    })();
    
    // 运行正确,never 类型可以赋值给 数字类型
    y = (() => {
      throw new Error("exception");
    })();
    

    经典示例:

    1. 初始代码

      // 经典举例
      //  定义了一个interface A和interface B之后,在实际使用时候,针对其他类型配置为了never类型
      //  如果再继续定义了一个interface C,这样在代码检查时候,即会出现报错提示
      interface A {
        type: "保安";
      }
      interface B {
        type: "草莓";
      }
      
      type All = A | B;
      function fn(val: All) {
        switch (val.type) {
          case "保安":
            break;
          case "草莓":
            break;
          default:
            const check: never = val;
            break;
        }
      }
      
      
    2. 如果需要添加新的代码interface C。

      // 经典举例
      //  定义了一个interface A和interface B之后,在实际使用时候,针对其他类型配置为了never类型
      //  如果再继续定义了一个interface C,这样在代码检查时候,即会出现报错提示
      interface A {
        type: "保安";
      }
      interface B {
        type: "草莓";
      }
      
      interface C {
        type: "菜菜";
      }
      
      type All = A | B | C;
      function fn(val: All) {
        switch (val.type) {
          case "保安":
            break;
          case "草莓":
            break;
          default: // 报错,已声明“check”,但从未读取其值
            const check: never = val;
            break;
        }
      }
      
      
    3. 需要修改代码如下所示。

      // 经典举例
      //  定义了一个interface A和interface B之后,在实际使用时候,针对其他类型配置为了never类型
      //  如果再继续定义了一个interface C,这样在代码检查时候,即会出现报错提示
      interface A {
        type: "保安";
      }
      interface B {
        type: "草莓";
      }
      
      interface C {
        type: "菜菜";
      }
      
      type All = A | B | C;
      function fn(val: All) {
        switch (val.type) {
          case "保安":
            break;
          case "草莓":
            break;
          case "菜菜":
            break;
          default: // 报错,已声明“check”,但从未读取其值
            const check: never = val;
            break;
        }
      }
      
      
  10. Object类型。

  • 参考:TypeScript: Documentation - Object Types
    // {}用来指定对象中可以包含哪些属性
    // 语法:{ 属性名1: 属性值1, 属性值2: 属性值2 }
    // 在属性名后加上?,表示属性是可选的
    let b: { name: string; age?: number };
    
    b = { name: "张三" };
    b = { name: "张三", age: 25 };
    
    // 定义对象结构
    // [propName: string]: any,表示任意类型的属性
    let c: { name: string; [propName: string]: any };
    c = { name: "八戒", age: 18, gender: "男" };
    
    // [propName: string]: any,表示number类型的属性
    let c2: { name2: number; [propName: string]: number };
    c2 = { name2: 22, age: 18, gender: 2 };
    
    // 定义函数结构
    // 设置函数结构的类型声明
    //  语法:(形参: 类型, 形参: 类型, ...) => 返回值
    let dFn: (a: number, b: number) => number;
    // 等价于
    dFn = function (n1: number, n2: number): number {
      return 10;
    };
    
    
  1. 元祖(tuple) ,数组的变种:元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。

    let x: [string, number];
    x = ["Runoob", 1]; // 运行正常
    // x = [1, 'Runoob']; // 报错
    console.log(x[0]); // 输出 Runoob
    
    // ----
    let arr: [string, number] = ["张三", 23];
    console.log(arr[0].length);
    
    let arr2: [string, number] = ["张三", 23];
    arr.push("12345", 23456);
    
    let excel: [string, string, number][] = [["Title", "Name", 1]];
    
    
  2. 枚举 enum:枚举类型用于定义数值集合。

    1. 数字枚举。

      // ----
      // 数字枚举
      // 红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色0 绿色为1 蓝色为2
      enum Color1 {
        Red,
        Green,
        Blue,
      }
      console.log(Color1.Red); // 0
      console.log(Color1.Green); // 1
      console.log(Color1.Blue); // 2
      
      // ----
      // 增长枚举
          // 定义初始的枚举的值
      enum Color2 {
            Red = 1,
            Green,
            Blue,
          }
          console.log(Color2.Red); // 1
          console.log(Color2.Green); // 2
          console.log(Color2.Blue); // 3
      
      // 自定义枚举
      // 自定义枚举的值
      enum Color3 {
        Red = 1,
        Green = 2,
        Blue = 4,
      }
      console.log(Color3.Red); // 1
      console.log(Color3.Green); // 2
      console.log(Color3.Blue); // 4
      
    2. 字符串枚举

      enum Color {
        Red = "red",
        Green = "green",
        Blue = "blue",
      }
      console.log(Color.Red); // red
      console.log(Color.Green); // green
      console.log(Color.Blue); // blue
          
      
    3. 异构枚举(字符串和数字混合使用)

      enum Color {
        Red = "red",
        Green = 2,
        Blue = "blue",
      }
      console.log(Color.Red); // red
      console.log(Color.Green); // 2
      console.log(Color.Blue); // blue
      
      enum Type {
        Yes = 1,
        No = "no",
      }
      console.log(Type.Yes); // 1
      console.log(Type.No); // no
      
      
    4. 接口枚举

      enum Type {
        Yes = 1,
        No = "no",
      }
      console.log(Type.Yes); // 1
      console.log(Type.No); // no
      
      interface A {
        red: Type.Yes;
      }
      
      let obj: A = {
        red: Type.Yes,
      };
      
      
    5. 常量枚举

      // const 声明的枚举会被编译成常量
      // 普通声明的枚举编译完后是个对象
      
      const enum Type {
        Yes = 1,
        No = "no",
      }
      console.log(Type.Yes); // 1
      console.log(Type.No); // no
      
      
    6. 反向映射

      // 反向映射,只支持数字类型,不支持例如字符串类型
      // 它包含了正向映射( name -> value)和反向映射( value -> name)
      enum Types {
        Success = 456,
      }
      
      let success: number = Types.Success;
      let key = Types[success];
      
      console.log(`value: ${success}`, `key: ${key}`); // value: 456 key: Success
      
      
    7. 其他举例

      enum Gender {
        Male,
        Female,
      }
      
      let i: { name: string; gender: Gender };
      i = {
        name: "孙悟空",
        gender: Gender.Male,
      };
      
      console.log(i.gender === Gender.Male);
      
      
  3. null null:表示对象缺失。
    在 JavaScript 中 null 表示 "什么都没有"。
    null是一个只有一个值的特殊类型。表示一个空对象引用。
    用 typeof 检测 null 返回是 object。

  4. undefined undefined:用于初始化变量为一个未定义的值。

    let u: undefined = undefined; // 定义undefined
    let n: null = null; // 定义null
    
    // void与undefined和null最大的区别
    // - undefined和null是所有类型的子类型,也就是说undefined类型的变量,可以赋值给string类型的变量
    console.log("Hello TS");
    
    let test: void = undefined;
    let num: string = "1";
    
    // num = test; // 报错,不能将类型void分配给undefined
    
    // 这样是没问题的
    let test2: null = null;
    let num2: string = "1";
    // num2 = test2; // 严格模式报错,不能将类型void分配给undefined
    
    
  5. 类型别名。

    type myType = 1 | 2 | 3 | 4 | 6 | 9;
    let k: myType;
    let l: myType;
    
  6. |,联合类型,多种类型:可以用 | 来支持多种类型。

    let phone: number | string = "027-88888888";
    
    let fn = function (type: number | boolean): boolean {
      console.log(!!type);
      return !!type;
    };
    
    fn(1); // true
    fn(false); // false
    
    
    // ----
    let x: number | string = "aa";
    x = 2;
    x = true; // 报错,不能将类型 boolean 分配给类型 string | number
    
    // 启用 --strictNullChecks
    let x: number | null | undefined;
    x = 1; // 运行正确
    x = undefined; // 运行正确
    x = null; // 运行正确
    x = "Hello"; // 报错,不能将类型 string 分配给类型 number
    
  7. &,交叉类型,多种类型的集合,联合对象将具有所联合类型的所有成员 ,表示同时。

    // ----
    // 示例1
    let j: { name: string } & { age: number };
    j = { name: "孙悟空", age: 100 };
    
    // ----
    // 示例2
    interface People {
      name: string;
      age: number;
    }
    
    interface Man {
      gender: string;
    }
    
    const zhangSan = (man: People & Man): void => {
      console.log(man);
    };
    
    zhangSan({
      name: "张三",
      age: 18,
      gender: "男",
    });
    
    
  8. 类型断言

    // 方式二,类型断言,可以用告诉解析器变量的实际类型
    /*
     * 语法:
     *  变量as类型
     *  <类型>变量
     */
    // e = c as string;
    // e = <string>c;
    
    // ----
    let fn1 = function (num: number | string): void {
      console.log((num as string).length);
    };
    
    fn1("Hello TS"); // 8
    // 不能滥用类型断言
    fn1(12345); // undefined
    
    // ----
    interface A {
      run: string;
    }
    interface B {
      build: string;
    }
    
    let fn2 = (type: A | B): void => {
      console.log((type as A).run);
      console.log((type as B).build);
    };
    
    fn2({
      build: "Build",
    });
    
    fn2({
      run: "Run",
    });
    
    // ----
    // 临时断言成任何类型
    (window as any).abc = 123;
    
    // ----
    // 断言实际未生效的场景
    const fn3 = (type: any): boolean => {
      return type as boolean;
    };
    
    fn3(1); // 实际返回的还是1,而非boolean类型的数据
    
    • 用途1:将一个联合类型断言为其中一个类型。
      当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法。

      interface Cat {
          name: string;
          run(): void;
      }
      interface Fish {
          name: string;
          swim(): void;
      }
      
      function getName(animal: Cat | Fish) {
          return animal.name;
      }
      

      而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如:

      interface Cat {
          name: string;
          run(): void;
      }
      interface Fish {
          name: string;
          swim(): void;
      }
      
      function isFish(animal: Cat | Fish) {
          if (typeof animal.swim === 'function') {
              return true;
          }
          return false;
      }
      
      // index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
      //   Property 'swim' does not exist on type 'Cat'.
      

      此时可以使用类型断言,将 animal 断言成 Fish

      interface Cat {
          name: string;
          run(): void;
      }
      interface Fish {
          name: string;
          swim(): void;
      }
      
      function isFish(animal: Cat | Fish) {
          if (typeof (animal as Fish).swim === 'function') {
              return true;
          }
          return false;
      }
      

      这样就可以解决访问 animal.swim 时报错的问题了。

    • 用途2:将一个父类断言为更加具体的子类。

      interface ApiError extends Error {
          code: number;
      }
      interface HttpError extends Error {
          statusCode: number;
      }
      
      function isApiError(error: Error) {
          if (typeof (error as ApiError).code === 'number') {
              return true;
          }
          return false;
      }
      
    • 用途3:将任何一个类型断言为any

    • 用途4:将 any 断言为一个具体的类型。

  9. 类型推论

    let str = "张三"; // 自动推算出str的类型是string类型
    // str = 123; // 报错
    
    let a; // 自动推算出a的类型是any类型
    a = [];
    a = {};
    a = true;
    
  10. 类型别名

    // ----
    type s1 = string | number;
    let str: s1 = "张三";
    let num: s1 = 15;
    
    // ----
    // 函数类型别名
    type cb = () => string;
    const fn: cb = () => "张三";
    
    // 值的类型别名
    type T = "Off" | "On" | false | 5;
    let str2:T = "On";
    console.log(str2);
    
    
  11. symbol类型

    // ----
    // symbol,内存地址指针位置不同,所以是唯一的
    let s1: symbol = Symbol("二蛋");
    let s2: symbol = Symbol("二蛋");
    console.log(s1 == s2); // false
    console.log(s1 === s2); // false
    
    let num1: symbol = Symbol(12);
    console.log(s1); // Symbol(二蛋)
    console.log(num1); // Symbol(12)
    
    // ----
    // 用作对象属性的key值
    let sym: symbol = Symbol();
    let obj = {
      [sym]: "value",
    };
    console.log(obj[sym]); // value
    
    

    通过symbol类型定义的属性,在遍历时候,是不会被渲染出来的。

    const symbol1 = Symbol("666");
    const symbol2 = Symbol("777");
    const obj1 = {
      [symbol1]: "张三",
      [symbol2]: "二蛋",
      age: 19,
      gender: "女",
    };
    // 1 for in 遍历
    for (const key in obj1) {
      // 注意在console看key,是不是没有遍历到symbol1和symbol2
      console.log(key); // age, gender
    }
    // 2 Object.keys 遍历
    Object.keys(obj1);
    console.log(Object.keys(obj1)); //  ['age', 'gender']
    // 3 getOwnPropertyNames
    console.log(Object.getOwnPropertyNames(obj1)); //  ['age', 'gender']
    // 4 JSON.stringfy
    console.log(JSON.stringify(obj1)); // {"age":19,"gender":"女"}
    
    // ----
    // 1、拿到具体的symbol属性,对象中有几个就会拿到几个
    Object.getOwnPropertySymbols(obj1);
    console.log(Object.getOwnPropertySymbols(obj1)); // [Symbol(666), Symbol(777)]
    // 2、ES6的Reflect拿到对象的所有属性
    Reflect.ownKeys(obj1);
    console.log(Reflect.ownKeys(obj1)); // ['age', 'gender', Symbol(666), Symbol(777)]
    
    

    Symbol.iterator迭代器和生成器for of

    var arr = [1, 2, 3, 4];
    let iterator: Iterator<number> = arr[Symbol.iterator]();
    
    console.log(iterator.next()); // { value: 1, done: false }
    console.log(iterator.next()); // { value: 2, done: false }
    console.log(iterator.next()); // { value: 3, done: false }
    console.log(iterator.next()); // { value: 4, done: false }
    console.log(iterator.next()); // { value: undefined, done: true }
    
    

    测试用例(Symbol.iterator迭代器方式):

    type mapKeys = string | number;
    let arr: Array<number> = [4, 5, 6];
    
    let set: Set<number> = new Set([1, 2, 3]);
    
    let map: Map<mapKeys, mapKeys> = new Map();
    map.set("1", "王爷");
    map.set("2", "皇上");
    
    // 注意:不支持对象类型
    function gen(erg: any) {
      let iter: Iterator<any> = erg[Symbol.iterator]();
      let next: any = { done: false };
      while (!next.done) {
        next = iter.next();
        if (!next.done) {
          console.log(next);
        }
      }
    }
    
    gen(arr);
    // {value: 4, done: false}
    // {value: 5, done: false}
    // {value: 6, done: false}
    
    gen(set);
    // {value: 1, done: false}
    // {value: 2, done: false}
    // {value: 3, done: false}
    
    gen(map);
    // {value: Array(2), done: false}
    // {value: Array(2), done: false}
    
    

    测试用例(生成器for of方式,推荐):

    type mapKeys = string | number;
    let arr: Array<number> = [4, 5, 6];
    
    let set: Set<number> = new Set([1, 2, 3]);
    
    let map: Map<mapKeys, mapKeys> = new Map();
    map.set("1", "王爷");
    map.set("2", "皇上");
    
    // for...of...的方式
    for (const item of arr) {
      console.log(item);
    }
    // 4
    // 5
    // 6
    
    for (const item of set) {
      console.log(item);
    }
    // 1
    // 2
    // 3
    
    for (const item of map) {
      console.log(item);
    }
    // ['1', '王爷']
    // ['2', '皇上']
    
    // ----
    // for...in...的方式
    // 循环出来的是数组的索引值
    
    

内置对象

ECMAScript的内置对象

let b: Boolean = new Boolean(1);
console.log(b); // Boolean {true}

let n: Number = new Number(true);
console.log(n); // Number {1}

let s: String = new String("哔哩哔哩关注小满zs");
console.log(s); // String {'哔哩哔哩关注小满zs'}

let d: Date = new Date();
console.log(d); // Sun May 08 2022 15:02:34 GMT+0800 (中国标准时间)

let r: RegExp = /^1/;
console.log(r); // /^1/

let e: Error = new Error("报错了!");
console.log(e); // Error: 报错了!

定义Promise

function promiseFn(): Promise<number> {
  return new Promise<number>((resolve, reject) => {
    resolve(1);
  });
}

promiseFn().then((res) => {
  console.log(res);
});

OOP(面向对象)

  • 参考:[JasonkayZK/typescript-learn at 4-OOP](JasonkayZK/typescript-learn at 4-OOP)

类的介绍和构造函数

  1. 新建文件夹OOPTS。

  2. 配置自动编译整个项目。参见[自动编译整个项目](# 自动编译整个项目)。

  3. 新建文件oop.ts。

    class Person {
      /**
       * 直接定义的属性是实例属性,需要通过对象的实例去访问
       *      const person = new Person();
       *      person.getName();
       *
       * 使用static开头的属性或者方法为静态属性(类属性)/静态方法(类方法),可以直接通过类去调用
       *    Person.feeling
       *
       * readonly开头的属性表示一个只读的属性,无法修改
       */
    
      /**
       * TS可以在属性前添加属性的修饰符
       *
       * public,公有属性,修饰的属性可以在任意位置访问(和修改),默认就是public属性
       * private,私有属性,私有属性只能在类内部进行访问(和修改)
       * 	通过在类中添加方法使得私有属性可以被外部访问
       * protected,受保护的属性,只能在当前类和当前类的子类中进行访问(和修改)
       */
      /**
       * getter方法用来读取属性
       * setter方法用来设置属性
       * 	它们被称为属性的存取器
       */
      private _name: string;
      private _age: number;
      public static feeling: string = "I'm feeling good!";
      public readonly gender: string = "Male or Female";
      public static readonly ageRange: string = "0 to 200 years!";
    
      // 构造器
      // 在TS中只能有一个构造器方法!
      constructor(name: string, age: number) {
        // 在实例方法中,this就表示当前的实例
        // 在构造函数中当前对象就是当前新建的那个对象
        // 可以通过this向新建的对象中添加属性
        this._name = name;
        this._age = age;
      }
    
      // 在方法中可以通过this来表示当前调用方法的对象
      public setName(name: string) {
        this._name = name;
      }
    
      public setAge(age: number) {
        if (age >= 0) {
          this._age = age;
        }
      }
    
      public getName() {
        return this._name;
      }
    
      public getAge() {
        return this._age;
      }
    
      public sayHelloWorld() {
        console.log("Hello person.");
      }
    
      public static sayHello() {
        console.log("Hello person.");
      }
    }
    
    const person = new Person("张三", 25);
    
    // 实例属性/实例方法举例
    console.log(person.getName());
    console.log(person.getAge());
    
    person.sayHelloWorld();
    
    // 类属性/类方法举例
    console.log(Person.feeling);
    console.log(Person.ageRange);
    Person.feeling = "xx";
    // Person.ageRange = "xx"; // 报错,只读属性
    
    Person.sayHello();
    
    
  4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

    <!DOCTYPE html>
    <html lang="zh">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>类</title>
      </head>
      <body>
        <script src="./dist/oop.js"></script>
      </body>
    </html>
    
    

TS属性的封装方式二

修改文件oop.ts。

class Person {
  /**
   * 直接定义的属性是实例属性,需要通过对象的实例去访问
   *      const person = new Person();
   *      person.getName();
   *
   * 使用static开头的属性或者方法为静态属性(类属性)/静态方法(类方法),可以直接通过类去调用
   *    Person.feeling
   *
   * readonly开头的属性表示一个只读的属性,无法修改
   */

  /**
   * TS可以在属性前添加属性的修饰符
   *
   * public,公有属性,修饰的属性可以在任意位置访问(和修改),默认就是public属性
   * private,私有属性,私有属性只能在类内部进行访问(和修改)
   */
  /**
   * getter方法用来读取属性
   * setter方法用来设置属性
   * 	它们被称为属性的存取器
   */
  private _name: string;
  private _age: number;
  public static feeling: string = "I'm feeling good!";
  public readonly gender: string = "Male or Female";
  public static readonly ageRange: string = "0 to 200 years!";

  // 构造器
  // 在TS中只能有一个构造器方法!
  constructor(name: string, age: number) {
    // 在实例方法中,this就表示当前的实例
    // 在构造函数中当前对象就是当前新建的那个对象
    // 可以通过this向新建的对象中添加属性
    this._name = name;
    this._age = age;
  }

  // 在方法中可以通过this来表示当前调用方法的对象
  set name(name: string) {
    this._name = name;
  }

  set age(age: number) {
    if (age >= 0) {
      this._age = age;
    }
  }

  get name() {
    return this._name;
  }

  get age() {
    return this._age;
  }

  sayHelloWorld() {
    console.log("Hello person.");
  }

  static sayHello() {
    console.log("Hello person.");
  }
}

const person = new Person("张三", 25);

// 实例属性/实例方法举例
console.log(person.name);
console.log(person.age);

person.sayHelloWorld();

// 类属性/类方法举例
console.log(Person.feeling);
console.log(Person.ageRange);
Person.feeling = "xx";
// Person.ageRange = "xx"; // 报错,只读属性

Person.sayHello();

类的继承

  1. 新建文件夹OOPTS。

  2. 配置自动编译整个项目。参见[自动编译整个项目](# 自动编译整个项目)。

  3. 新建文件oop.ts。

    (function () {
      // 定义一个Animal类
      class Animal {
        private _name: string;
        private _age: number;
    
        // 构造器
        // 在TS中只能有一个构造器方法!
        constructor(name: string, age: number) {
          this._name = name;
          this._age = age;
        }
    
        public setName(name: string) {
          this._name = name;
        }
    
        public setAge(age: number) {
          if (age >= 0) {
            this._age = age;
          }
        }
    
        public getName() {
          return this._name;
        }
    
        public getAge() {
          return this._age;
        }
    
        public sayHello() {
          console.log("动物在叫。。。");
        }
      }
    
      /**
       * Dog extends Animal
       *    - 此时,Animal被称为父类,Dog被称为子类
       *    - 使用继承后,子类会继承父类所有的方法和属性
       *    - 通过继承可以将多类中公有的代码写在一个父类中
       *        这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
       *        如果希望在子类中添加一些父类中没有的属性或者方法,在子类中直接添加即可
       *    - 如果在子类中添加了和父类相同的方法,则子类方法会覆盖父类的方法
       *        这种子类覆盖父类方法的形式,我们称为方法重写
       */
      // 定义一个表示狗的类
      // 使Dog类继承Animal类
      class Dog extends Animal {
        super() {}
    
        run() {
          console.log(`${this.getName()}在跑`);
        }
        public sayHello() {
          console.log("汪汪汪。。。");
        }
      }
    
      // 定义一个表示猫的类
      // 使Cat类继承Animal类
      class Cat extends Animal {
        gender: string;
    
        constructor(name: string, age: number, gender: string) {
          // 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
          super(name, age); // 调用父类的构造函数
          this.gender = gender;
        }
    
        public sayHello() {
          // 在类的方法中super就表示当前类的父类
          super.sayHello();
          console.log("喵喵喵。。。");
        }
      }
    
      // 测试验证
      const dog = new Dog("旺财", 5);
      const cat = new Cat("咪咪", 5, "Female");
      console.log(dog);
      dog.run();
      dog.sayHello();
      cat.sayHello();
    })();
    
    
  4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

    <!DOCTYPE html>
    <html lang="zh">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>类</title>
      </head>
      <body>
        <script src="./dist/oop.js"></script>
      </body>
    </html>
    
    

抽象类

  1. 新建文件夹OOPTS。

  2. 配置自动编译整个项目。参见[自动编译整个项目](# 自动编译整个项目)。

  3. 新建文件oop.ts。

    (function () {
      /**
       * 以abstract开头的类是抽象类
       *      抽象类和其他类区别不大,只是不能用来创建对象
       *      抽象类就是专门用来被继承的类
       *
       *      抽象类中可以添加抽象方法
       */
      // 定义一个Animal类
      abstract class Animal {
        private _name: string;
        private _age: number;
    
        // 构造器
        // 在TS中只能有一个构造器方法!
        constructor(name: string, age: number) {
          this._name = name;
          this._age = age;
        }
    
        abstract sayHello(): void;
      }
    
      /**
       * Dog extends Animal
       *    - 此时,Animal被称为父类,Dog被称为子类
       *    - 使用继承后,子类会继承父类所有的方法和属性
       *    - 通过继承可以将多类中公有的代码写在一个父类中
       *        这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
       *        如果希望在子类中添加一些父类中没有的属性或者方法,在子类中直接添加即可
       *    - 如果在子类中添加了和父类相同的方法,则子类方法会覆盖父类的方法
       *        这种子类覆盖父类方法的形式,我们称为方法重写
       */
      // 定义一个表示狗的类
      // 使Dog类继承Animal类
      class Dog extends Animal {
        super() {}
        public sayHello() {
          console.log("汪汪汪。。。");
        }
      }
    
      // 定义一个表示猫的类
      // 使Cat类继承Animal类
      class Cat extends Animal {
        gender: string;
    
        constructor(name: string, age: number, gender: string) {
          // 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
          super(name, age); // 调用父类的构造函数
          this.gender = gender;
        }
    
        public sayHello() {
          // 在类的方法中super就表示当前类的父类
          console.log("喵喵喵。。。");
        }
      }
    
      // 测试验证
      const dog = new Dog("旺财", 5);
      const cat = new Cat("咪咪", 5, "Female");
      console.log(dog);
      dog.sayHello();
      cat.sayHello();
    })();
    
    

    示例2:

    abstract class A {
      name: string;
      constructor(name: string) {
        this.name = name;
      }
    
      setName(name: string) {
        this.name = name;
      }
      abstract getName(): string;
    }
    
    class B extends A {
      constructor(name: string) {
        super(name);
      }
    
      getName(): string {
        return this.name;
      }
    }
    
    let b = new B("张三");
    console.log(b.getName()); // 张三
    b.setName("李四");
    console.log(b.getName()); // 李四
    
    
  4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

    <!DOCTYPE html>
    <html lang="zh">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>类</title>
      </head>
      <body>
        <script src="./dist/oop.js"></script>
      </body>
    </html>
    
    

接口

接口的作用类似于抽象类,不同点在于:接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。

接口主要负责定义一个类的结构,接口可以去限制一个对象的接口:对象只有包含接口中定义的所有属性和方法时才能匹配接口。

同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  1. 新建文件夹OOPTS。

  2. 配置自动编译整个项目。参见[自动编译整个项目](# 自动编译整个项目)。

  3. 新建文件oop.ts。

    (function () {
      /**
       * 接口可以在定义类的时候去限制类的结构,用来定义一个类中应该包含哪些属性和方法
       */
      interface myInter {
        name: string;
        age?: number; // ?表示可选属性
        // [propName: string]: any; // 表示还可以额外添加任意类型的属性
        // [propName: string]: string | number; // 表示还可以额外添加联合类型的属性
      }
    
      interface myInter {
        gender: string;
      }
    
      // 两个接口名称一样,会进行合并
      const obj: myInter = {
        name: "张三",
        age: 25,
        gender: "Male",
      };
    
      // -----
      // 也可以组合使用
      interface A {
        name: string;
      }
    
      interface B extends A {
        age: number;
      }
    
      let p: B = {
        name: "张三",
        age: 18,
      };
    
      // ----
      /**
       * 接口可以在定义类的时候去限制类的结构,用来定义一个类中应该包含哪些属性和方法
       *  接口中的所有属性都不能有实际的值
       *  接口只定义对象的结构,而不考虑实际值
       *      在接口中所有的方法都是抽象方法
       */
      interface myInterface {
        name: string;
        age: number;
    
        sayHello(): void;
      }
    
      /**
       * 定义类时,可以使类去实现一个接口
       *    实现接口就是使得类满足接口的要求
       */
      class MyClass implements myInterface {
        name: string;
        age: number;
    
        constructor(name: string, age: number) {
          this.name = name;
          this.age = age;
        }
    
        sayHello(): void {
          console.log("大家好~~");
        }
      }
    
      const myClass = new MyClass("张三", 25);
      myClass.sayHello();
    })();
    
    

    示例2:

    interface Person {
      run(type: boolean): boolean;
    }
    
    interface H {
      set(): void;
    }
    
    class A {
      private param: string;
      constructor(param: string) {
        this.param = param;
      }
    }
    
    class Man extends A implements Man, H {
      constructor(param: string) {
        super(param);
      }
    
      run(type: boolean): boolean {
        return type;
      }
    
      set(): void {
        throw new Error("Method not implemented.");
      }
    }
    
    const man = new Man("张三");
    man.run(true);
    man.set();
    
    
  4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

    <!DOCTYPE html>
    <html lang="zh">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>类</title>
      </head>
      <body>
        <script src="./dist/oop.js"></script>
      </body>
    </html>
    
    

泛型

泛型,在定义的时候,可以不用明确使用的类型,而在具体使用的时候明确需要的类型即可。

  1. 新建文件夹OOPTS。

  2. 配置自动编译整个项目。参见[自动编译整个项目](# 自动编译整个项目)。

  3. 新建文件oop.ts。

    /**
     * 在定义函数或者类时候,如果遇到类型不明确就可以使用泛型
     */
    function fn<T>(a: T): T {
      return a;
    }
    // 调用
    // 可以直接调用具有泛型的函数
    let result1 = fn(10);
    // 或者指定泛型进行调用
    let result2 = fn<string>("HelloTS");
    
    /**
     * 可以指定多个泛型
     */
    function fn2<T, K>(a: T, b: K): T {
      console.log(b);
      return a;
    }
    fn2<string, number>("HelloTS", 25);
    fn2<number, string>(25, "HelloTS");
    
    

    泛型约束

    interface Len {
      length: number;
    }
    function getLength<T extends Len>(args: T): number {
      return args.length;
    }
    getLength("Hello TS");
    // getLength(1); // 报错
    // getLength(false); // 报错
    
    // ----
    /**
     * 结合接口使用
     */
    interface MyInter {
      len: number;
    }
    
    // T extends MyInter表示泛型T必须是MyInter实现类(子类)
    function fn3<T extends MyInter>(a: T): number {
      return a.len;
    }
    

    使用key of约束泛型对象

    // ----
    // TS泛型和泛型约束
    //  首先定义了T类型并使用extends关键字继承object类型的子类型
    //  然后使用keyof操作符获取T类型的所有键
    //  它的返回类型是联合类型
    //  最后用extends关键字约束K类型必须为keyof T联合类型的子类型
    function prop<T, K extends keyof T>(obj: T, key: K) {
      return obj[key];
    }
    let o = {
      a: 1,
      b: 2,
      c: 3,
    };
    prop(o, "a");
    // prop(o, "d"); // 报错,类型“"d"”的参数不能赋给类型“"a" | "b" | "c"”的参数
    

    泛型类

    class MyClass<T> {
      name: T;
      constructor(name: T) {
        this.name = name;
      }
    }
    const mc = new MyClass<string>("孙悟空");
    
    // ----
    // 示例2
    class Sub<T> {
      attr: T[] = [];
      add(a: T): T[] {
        return [a];
      }
    }
    
    let s1 = new Sub<number>();
    s1.attr = [1, 2, 3, 4, 5, 6, 6];
    s1.add(123);
    
    let str = new Sub<string>();
    str.attr = ["1", "2", "3", "4", "5", "6", "6"];
    str.add("123");
    
    console.log(s1, str); // attr: (7) [1, 2, 3, 4, 5, 6, 6], attr: (7) ['1', '2', '3', '4', '5', '6', '6']
    
    
  4. 新建并修改文件index.html(VS Code中使用html:5模板生成)。

    <!DOCTYPE html>
    <html lang="zh">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>类</title>
      </head>
      <body>
        <script src="./dist/oop.js"></script>
      </body>
    </html>
    
    

namespace命名空间

我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace避免这个问题出现。

  • 内部模块,主要用于组织代码,避免命名冲突。
  • 命名空间内的类默认私有
  • 通过 export 暴露
  • 通过 namespace 关键字定义

TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。

操作步骤

  1. 环境搭建(省略)。

  2. 新建文件index.ts文件。
    命名空间

    // 如果不用export 导出是无法读取其值的
    namespace A {
      export const a: number = 123;
    }
    
    console.log(A.a);
    
    // ----
    // 示例2
    namespace a {
      export const Time: number = 1000;
      export const fn = <T>(arg: T): T => {
        return arg;
      };
      fn(Time);
    }
    
    namespace b {
      export const Time: number = 1001;
      export const fn = <T>(arg: T): T => {
        return arg;
      };
      fn(Time);
    }
    
    console.log(a.Time); // 1000
    console.log(b.Time); // 1001
    
    

    嵌套命名空间

    // 如果不用export 导出是无法读取其值的
    namespace A {
      export namespace B {
        export const a: number = 123;
      }
    }
    
    console.log(A.B.a);
    
    

    抽离命名空间

    1. 新建文件index2.ts。

      export namespace B {
        export const aa = { name: "张三" };
      }
      
      
    2. 修改文件index.ts内容。

      import { B } from "./index2";
      
      namespace A {
        export const a: number = 123;
      }
      
      console.log(A.a);
      console.log(B.aa); // {name: '张三'}
      
      

    简化命名空间

    // 如果不用export 导出是无法读取其值的
    namespace A {
      export namespace B {
        export const a: number = 123;
      }
    }
    
    console.log(A.B.a);
    
    // 简化命名空间
    import X = A.B;
    console.log(X.a);
    
    

    合并命名空间

    // 重名的命名空间会合并
    namespace A {
      export const b: number = 213;
    }
    
    namespace A {
      export const c: number = 456;
    }
    
    console.log(A.b);
    console.log(A.c);
    
    

三斜线指令

声明文件declare

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

declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
/// <reference /> 三斜线指令

例如我们有一个express和axios。

Mixin混入

TypeScript混入Mixins其实vue也有mixins这个东西 你可以把他看作为合并。

对象混入

可以使用ES6的Object.assign合并多个对象。

此时 people 会被推断成一个交差类型 Name & Age & sex;

interface Name {
  name: string;
}

interface Age {
  age: number;
}

interface Gender {
  gender: string;
}

let person1: Name = {
  name: "张三",
};
let person2: Age = {
  age: 24,
};
let person3: Gender = {
  gender: "男",
};

let person = Object.assign(person1, person2, person3);

console.log(person); // {name: '张三', age: 24, gender: '男'}

TS编写发布订阅模式

  • on:订阅/监听

  • emit:发布/注册

  • once:只执行一次

  • off:解除绑定

interface EventFace {
  on: (name: string, callback: Function) => void;
  emit: (name: string, ...arg: Array<any>) => void;
  off: (name: string, fn: Function) => void;
  once: (name: string, fn: Function) => void;
}

interface List {
  [key: string]: Array<Function>;
}

class Dispatch implements EventFace {
  list: List;
  constructor() {
    this.list = {};
  }
  on(name: string, callback: Function) {
    const callbackList: Array<Function> = this.list[name] || [];
    callbackList.push(callback);
    this.list[name] = callbackList;
  }
  emit(name: string, ...args: Array<any>) {
    let evnetName = this.list[name];
    if (evnetName) {
      evnetName.forEach((fn) => {
        fn.apply(this, args);
      });
    } else {
      console.error("该事件未监听");
    }
  }
  off(name: string, fn: Function) {
    let evnetName = this.list[name];
    if (evnetName && fn) {
      let index = evnetName.findIndex((fns) => fns === fn);
      evnetName.splice(index, 1);
    } else {
      console.error("该事件未监听");
    }
  }
  once(name: string, fn: Function) {
    let decor = (...args: Array<any>) => {
      fn.apply(this, args);
      this.off(name, decor);
    };
    this.on(name, decor);
  }
}
const o = new Dispatch();

o.on("abc", (...arg: Array<any>) => {
  console.log(arg, 1);
});

o.once("abc", (...arg: Array<any>) => {
  console.log(arg, "once");
});
// let fn = (...arg: Array<any>) => {
//     console.log(arg, 2);
// }
// o.on('abc', fn)
// o.on('ddd', (aaaa: string) => {
//     console.log(aaaa);
// })
//o.off('abc', fn)

o.emit("abc", 1, true, "张三");

o.emit("abc", 2, true, "张三");

// o.emit('ddd', 'addddddddd')

Proxy和Reflect

type Person = {
  name: string;
  age: number;
  text: string;
};

const proxy = (object: any, key: any) => {
  return new Proxy(object, {
    get(target, prop, receiver) {
      console.log("====>>get", prop);
      return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
      console.log("====>>set");
      console.log(prop);
      return Reflect.set(target, prop, value, receiver);
    },
  });
};

const logAccess = <T>(obj: T, key: keyof T): T => {
  return proxy(obj, key);
};

let man: Person = logAccess(
  {
    name: "张三",
    age: 18,
    text: "Hello TS",
  },
  "name"
);
man.name = "李四";

let man2 = logAccess(
  {
    name: "张三",
    id: "001",
  },
  "id"
);

Partial&Pick

Partial

  • 源码

    /**
     * Make all properties in T optional
      将T中的所有属性设置为可选
     */
    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    
  • 使用前

    type Person = {
        name:string,
        age:number
    }
     
    type p = Partial<Person>
    
  • 使用后

    type p = {
        name?: string | undefined;
        age?: number | undefined;
    }
    

解析

  1. keyof我们讲过很多遍了 将一个接口对象的全部属性取出来变成联合类型
  2. in 我们可以理解成for in P 就是key 遍历 keyof T 就是联合类型的每一项
  3. ?这个操作就是将每一个属性变成可选项
  4. T[P] 索引访问操作符,与 JavaScript 种访问属性值的操作类似

Pick

  • 源码

    /**
     * From T, pick a set of properties whose keys are in the union K
     * 从类型定义T的属性中,选取指定一组属性,返回一个新的类型定义
     */
    type Pick<T, K extends keyof T> = {
        [P in K]: T[P];
    };
    
  • 示例

    type Person = {
      name: string;
      age: number;
      text: string;
      address: string;
    };
    
    type Ex = "text" | "age";
    
    type A = Pick<Person, Ex>;
    
    /**
     * 得到的新的类型如下所示
    type A = {
      age: number;
      text: string;
    };
    */
    

Readonly&Record

Readonly

  • 源码

    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    };
    
  • 示例

    type Person = {
      name: string;
      age: number;
      text: string;
      address: string;
    };
    
    // 新的类型,将原类型的没个属性都变成只读
    type A = Readonly<Person>;
    
    

解析

  1. keyof我们讲过很多遍了 将一个接口对象的全部属性取出来变成联合类型
  2. in 我们可以理解成for in P 就是key 遍历 keyof T 就是联合类型的每一项
  3. Readonly 这个操作就是将每一个属性变成只读
  4. T[P] 索引访问操作符,与 JavaScript 种访问属性值的操作类似

Record

做到了约束对象的key同时约束了value。

  • 源码

    type Record<K extends keyof any, T> = {
        [P in K]: T;
    };
    
  • 示例

    type Person = {
      name: string;
      age: number;
      text: string;
      address: string;
    };
    
    type K = "A" | "B" | "C";
    type R = Record<K, Person>;
    
    let obj:R = {
        "A": {
            name: "张三",
            age: 18,
            text: "吃饭",
            address: "xx路"
        },
        B: {
            name: "",
            age: 0,
            text: "",
            address: ""
        },
        C: {
            name: "",
            age: 0,
            text: "",
            address: ""
        }
    }
    

解析

  1. keyof any 返回 string number symbol 的联合类型
  2. in 我们可以理解成for in P 就是key 遍历 keyof any 就是string number symbol类型的每一项
  3. extends来约束我们的类型
  4. T 直接返回类型

infer

infer是TypeScript 新增到的关键字,充当占位符。

我们来实现一个条件类型推断的例子。

定义一个类型,如果是数组类型,就返回数组元素的类型,否则就传入什么类型,就返回什么类型。

// 定义一个类型,如果是数组类型,就返回数组元素的类型,否则,传入什么类型就返回什么类型
type TYPE<T> = T extends Array<any> ? T[number] : T;

type A = TYPE<(string | number)[]>;
type B = TYPE<boolean>;

// 使用infer优化上述代码
type TYPE2<T> = T extends Array<infer U> ? U : T;

type A2 = TYPE2<(string | number)[]>;
type B2 = TYPE2<boolean>;

// 将元祖类型转换为联合类型
type TupleToUni<T> = T extends Array<infer E> ? E : never;

type TTuple = [string, number];

type ToUnion = TupleToUni<TTuple>; // string | number

参考

工具参考


日志

  • 2021年02月06日 17:35:06 初始版本
  • 2022年12月23日21:28:47 参考TypeScript官网内容补充优化全文

posted on 2021-02-05 10:16  zyjhandsome  阅读(124)  评论(0编辑  收藏  举报