添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

1. typescript是什么 #

  • Typescript是由微软开发的一款开源的编程语言
  • Typescript是Javascript的超集,遵循最新的ES5/ES6规范。TypeScript扩展了Javascript语法
  • TypeScript更像后端Java、C#这样的面向对象语言可以让JS开发大型企业应用
  • 越来越多的项目是基于TS的,比如VSCode、Angular6、Vue3、React16
  • TS提供的类型系统可以帮助我们在写代码的时候提供更丰富的语法提示
  • 在创建前的编译阶段经过类型系统的检查,就可以避免很多线上的错误
  • 2. TypeScript安装和编译 #

    2.1 安装 #

    cnpm i typescript -g
    
    tsc helloworld.ts
    

    2.2 Vscode+TypeScript #

    2.2.1 生成配置文件 #

    tsc --init
      "compilerOptions": {
        /* Basic Options */
        "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. 指定ECMAScript的目标版本*/
        "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. 指定模块代码的生成方式*/
        // "lib": [],                             /* Specify library files to be included in the compilation. 指定编译的时候用来包含的编译文件*/
        // "allowJs": true,                       /* Allow javascript files to be compiled. 允许编译JS文件*/
        // "checkJs": true,                       /* Report errors in .js files. 在JS中包括错误*/
        // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. 指定JSX代码的生成方式 是保留还是react-native或者react*/
        // "declaration": true,                   /* Generates corresponding '.d.ts' file.生成相应的类型声明文件 */
        // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. 为每个类型声明文件生成相应的sourcemap*/
        // "sourceMap": true,                     /* Generates corresponding '.map' file. 生成对应的map文件 */
        // "outFile": "./",                       /* Concatenate and emit output to single file. 合并并且把编译后的内容输出 到一个文件里*/
        // "outDir": "./",                        /* Redirect output structure to the directory.按原始结构输出到目标目录 */
        // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. 指定输入文件的根目录,用--outDir来控制输出的目录结构*/
        // "composite": true,                     /* Enable project compilation 启用项目编译*/
        // "removeComments": true,                /* Do not emit comments to output. 移除注释*/
        // "noEmit": true,                        /* Do not emit outputs. 不要输出*/
        // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
        // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. 当目标是ES5或ES3的时候提供对for-of、扩展运算符和解构赋值中对于迭代器的完整支持*/
        // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule').r把每一个文件转译成一个单独的模块 */
        /* Strict Type-Checking Options */
        //"strict": true,                           /* Enable all strict type-checking options. 启用完全的严格类型检查 */
        // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. 不能使用隐式的any类型*/
        // "strictNullChecks": true,              /* Enable strict null checks. 启用严格的NULL检查*/
        // "strictFunctionTypes": true,           /* Enable strict checking of function types. 启用严格的函数类型检查*/
        // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions.启用函数上严格的bind call 和apply方法 */
        // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. 启用类上初始化属性检查*/
        // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type.在默认的any中调用 this表达式报错 */
        // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. 在严格模式下解析并且向每个源文件中发射use strict*/
        /* Additional Checks */
        // "noUnusedLocals": true,                /* Report errors on unused locals. 有未使用到的本地变量时报错 */
        // "noUnusedParameters": true,            /* Report errors on unused parameters. 有未使用到的参数时报错*/
        // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. 当不是所有的代码路径都有返回值的时候报错*/
        // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. 在switch表达式中没有替代的case会报错 */
        /* Module Resolution Options */
        // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). 指定模块的解析策略 node classic*/
        // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. 在解析非绝对路径模块名的时候的基准路径*/
        // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. 一些路径的集合*/
        // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. 根目录的列表,在运行时用来合并内容*/
        // "typeRoots": [],                       /* List of folders to include type definitions from. 用来包含类型声明的文件夹列表*/
        // "types": [],                           /* Type declaration files to be included in compilation.在编译的时候被包含的类型声明 */
        // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking.当没有默认导出的时候允许默认导入,这个在代码执行的时候没有作用,只是在类型检查的时候生效 */
        //"esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.*/
        // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks.不要symlinks解析的真正路径 */
        /* Source Map Options */
        // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. 指定ts文件位置*/
        // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. 指定 map文件存放的位置 */
        // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. 源文件和sourcemap 文件在同一文件中,而不是把map文件放在一个单独的文件里*/
        // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. 源文件和sourcemap 文件在同一文件中*/
        /* Experimental Options */
        // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. 启动装饰器*/
        // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
    

    2.2.2 执行编译 #

    2.2.3 vscode运行 #

  • Terminal->Run Task-> tsc:build 编译
  • Terminal->Run Task-> tsc:watch 编译并监听
  • 2.2.4 npm scripts #

  • npm run 实际上是调用本地的 Shell 来执行对应的 script value,所以理论上能兼容所有 bash 命令
  • Shell 在类 Unix 系统上是 /bin/sh,在 Windows 上是 cmd.exe
  • 2.2.5 npm scripts 的 PATH #

  • npm run 会预置 PATH,对应包下的 node_modules/.bin 目录
  • 3. 数据类型 #

    3.1 布尔类型(boolean) #

    let married: boolean=false;
    

    3.2 数字类型(number) #

    let age: number=10;
    

    3.3 字符串类型(string) #

    let firstname: string='zfpx';
    

    3.4 数组类型(array) #

    let arr2: number[]=[4,5,6];
    let arr3: Array<number>=[7,8,9];
    

    3.5 元组类型(tuple) #

  • 在 TypeScript 的基础类型中,元组( Tuple )表示一个已知数量类型的数组
  • let zhufeng:[string,number] = ['zhufeng',5];
    zhufeng[0].length;
    zhufeng[1].toFixed(2);
    let myColors = [Colors.Red, Colors.Yellow, Colors.Blue];
    
    const enum Color {Red, Yellow, Blue = "blue".length};
    

    3.7 任意类型(any) #

  • any就是可以赋值给任意类型
  • 第三方库没有提供类型文件时可以使用any
  • 类型转换遇到困难时
  • 数据结构太复杂难以定义
  • let root:any=document.getElementById('root');
    root.style.color='red';
    
    let root:(HTMLElement|null)=document.getElementById('root');
    root!.style.color='red';//非空断言操作符
    

    3.8 null 和 undefined #

  • null 和 undefined 是其它类型的子类型,可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 null 或 undefined
  • strictNullChecks 参数用于新的严格空检查模式,在严格空检查模式下, null 和 undefined 值都不属于任何一个类型,它们只能赋值给自己这种类型或者 any
  • let x: number;
    x = 1;
    x = undefined;    
    x = null;   
    let y: number | null | undefined;
    y = 1;
    y = undefined;   
    y = null;   
    

    3.9 void 类型 #

  • void 表示没有任何类型
  • 当一个函数没有返回值时,TS 会认为它的返回值是 void 类型。
  • function greeting(name:string):void {
        console.log('hello',name);
        //当我们声明一个变量类型是 void 的时候,它的非严格模式(strictNullChecks:false)下仅可以被赋值为 null 和 undefined
        //严格模式(strictNullChecks:true)下只能返回undefined
        //return null;
        //return undefined;
    

    3.10 never类型 #

    never是其它类型(null undefined)的子类型,代表不会出现的值

    3.10.1 #

  • 作为不会返回( return )的函数的返回值类型
  • // 返回never的函数 必须存在 无法达到( unreachable ) 的终点
    function error(message: string): never {
        throw new Error(message);
    let result1 = error('hello');
    // 由类型推论得到返回值为 never
    function fail() {
        return error("Something failed");
    let result = fail();
    // 返回never的函数 必须存在 无法达到( unreachable ) 的终点
    function infiniteLoop(): never {
        while (true) {}
    

    3.10.2 strictNullChecks #

  • 在 TS 中, null 和 undefined 是任何类型的有效值,所以无法正确地检测它们是否被错误地使用。于是 TS 引入了 --strictNullChecks 这一种检查模式
  • 由于引入了 --strictNullChecks ,在这一模式下,null 和 undefined 能被检测到。所以 TS 需要一种新的底部类型( bottom type )。所以就引入了 never。
  • // Compiled with --strictNullChecks
    function fn(x: number | string) {
      if (typeof x === 'number') {
        // x: number 类型
      } else if (typeof x === 'string') {
        // x: string 类型
      } else {
        // x: never 类型
        // --strictNullChecks 模式下,这里的代码将不会被执行,x 无法被观察
    

    3.10.3 never 和 void 的区别 #

  • void 可以被赋值为 null 和 undefined的类型。 never 则是一个不包含值的类型。
  • 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。
  • 3.11 Symbol #

  • 我们在使用 Symbol 的时候,必须添加 es6 的编译辅助库
  • Symbol 是在ES2015之后成为新的原始类型,它通过 Symbol 构造函数创建
  • Symbol 的值是唯一不变的
  • const sym1 = Symbol('key');
    const sym2 = Symbol('key');
    Symbol('key') === Symbol('key') // false
    

    3.12 BigInt #

  • 使用 BigInt 可以安全地存储和操作大整数
  • 我们在使用 BigInt 的时候,必须添加 ESNext 的编译辅助库
  • 要使用1n需要 "target": "ESNext"
  • number和 BigInt类型不一样,不兼容
  • const max = Number.MAX_SAFE_INTEGER;// 2**53-1
    console.log(max + 1 === max + 2);
    
    const max = BigInt(Number.MAX_SAFE_INTEGER);
    console.log(max + 1n === max + 2n);
    
    let foo: number;
    let bar: bigint;
    foo =bar;
    bar = foo;
    

    3.13 类型推论 #

  • 是指编程语言中能够自动推导出值的类型的能力,它是一些强静态类型语言中出现的特性
  • 定义时未赋值就会推论成any类型
  • 如果定义的时候就赋值就能利用到类型推论
  • let username2;
    username2 = 10;
    username2 = 'zhufeng';
    username2 = null;
    

    3.14 包装对象(Wrapper Object) #

  • JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
  • 所有的原始数据类型都没有属性(property)
  • 原始数据类型
    • undefined
    • Symbol
    let name = 'zhufeng';
    console.log(name.toUpperCase());
    console.log((new String('zhufeng')).toUpperCase());
    
  • 当调用基本数据类型方法的时候,JavaScript 会在原始数据类型和对象类型之间做一个迅速的强制性切换
  • let isOK: boolean = true; // 编译通过
    let isOK: boolean = Boolean(1) // 编译通过
    let isOK: boolean = new Boolean(1); // 编译失败   期望的 isOK 是一个原始数据类型
    

    3.15 联合类型 #

  • 联合类型(Union Types)表示取值可以为多种类型中的一种
  • 未赋值时联合类型上只能访问两个类型共有的属性和方法
  • let name: string | number;
    console.log(name.toString());
    name = 3;
    console.log(name.toFixed(2));
    name = 'zhufeng';
    console.log(name.length);
    export {};
    

    3.16 类型断言 #

  • 类型断言可以将一个联合类型的变量,指定为一个更加具体的类型
  • 不能将联合类型断言为不存在的类型
  • let name: string | number;
    console.log((name as string).length);
    console.log((name as number).toFixed(2));
    console.log((name as boolean));
    
    interface Person {
        name: string;
        age: number;
    const person = 'zhufeng' as any as Person; // ok
    

    3.17 字面量类型和类型字面量 #

  • 字面量类型的要和实际的值的字面量一一对应,如果不一致就会报错
  • 类型字面量和对象字面量的语法很相似
  • const up:'Up'= 'Up';
    const down: "Down" = "Down";
    const left: "Left" = "Left";
    const right: "Right" = "Right";
    type Direction = 'Up' | 'Down' | 'Left' | 'Right';
    function move(direction: Direction) {}
    move("Up");
    
    type Person = {
      name:string,
      age:number
    

    3.18 字符串字面量 vs 联合类型 #

  • 字符串字面量类型用来约束取值只能是某几个字符串中的一个, 联合类型(Union Types)表示取值可以为多种类型中的一种
  • 字符串字面量 限定了使用该字面量的地方仅接受特定的值,联合类型 对于值并没有限定,仅仅限定值的类型需要保持一致
  • 4. 函数 #

    4.1 函数的定义 #

  • 可以指定参数的类型和返回值的类型
  • function hello(name:string):void {
        console.log('hello',name);
    hello('zfpx');
    

    4.2 函数表达式 #

  • 定义函数类型
  • type GetUsernameFunction = (x:string,y:string)=>string;
    let getUsername:GetUsernameFunction = function(firstName,lastName){
      return firstName + lastName;
    

    4.3 没有返回值 #

    let hello2 = function (name:string):void {
        console.log('hello2',name);
        return undefined;
    hello2('zhufeng');
    

    4.4 可选参数 #

    在TS中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数

    function print(name:string,age?:number):void {
        console.log(name,age);
    print('zfpx');
    

    4.5 默认参数 #

    function ajax(url:string,method:string='GET') {
        console.log(url,method);
    ajax('/users');
    

    4.6 剩余参数 #

    function sum(...numbers:number[]) {
        return numbers.reduce((val,item)=>val+=item,0);
    console.log(sum(1,2,3));
    

    4.7 函数重载 #

  • 在Java中的重载,指的是两个或者两个以上的同名函数,参数不一样
  • 在TypeScript中,表现为给同一个函数提供多个函数类型定义
  • let obj: any={};
    function attr(val: string): void;
    function attr(val: number): void;
    function attr(val:any):void {
        if (typeof val === 'string') {
            obj.name=val;
        } else {
            obj.age=val;
    attr('zfpx');
    attr(9);
    attr(true);
    console.log(obj);
    

    5. 类 #

    5.1 如何定义类 #

  • "strictPropertyInitialization": true / 启用类属性初始化的严格检查/
  • name!:string
  • class Person{
        name:string;
        getName():void{
            console.log(this.name);
    let p1 = new Person();
    p1.name = 'zhufeng';
    p1.getName();
     * 当我们写一个类的时候,会得到2个类型
     * 1. 构造函数类型的函数类型
     * 2. 类的实例类型
    class Component {
        static myName: string = '静态名称属性';
        myName: string = '实例名称属性';
    let com = Component;
    //Component类名本身表示的是实例的类型
    //ts 一个类型 一个叫值 
    //冒号后面的是类型
    //放在=后面的是值
    let c: Component = new Component();
    let f: typeof Component = com;
    

    5.2 存取器 #

  • 在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为
  • 主要用于初始化类的成员变量属性
  • 类的对象创建时自动调用执行
  • 没有返回值
  • class User {
        myname:string;
        constructor(myname: string) {
            this.myname = myname;
        get name() {
            return this.myname;
        set name(value) {
            this.myname = value;
    let user = new User('zhufeng');
    user.name = 'jiagou'; 
    console.log(user.name); 
    
    "use strict";
    var User = /** @class */ (function () {
        function User(myname) {
            this.myname = myname;
        Object.defineProperty(User.prototype, "name", {
            get: function () {
                return this.myname;
            set: function (value) {
                this.myname = value;
            enumerable: true,
            configurable: true
        return User;
    }());
    var user = new User('zhufeng');
    user.name = 'jiagou';
    console.log(user.name);
    

    5.3 参数属性 #

    class User {
        constructor(public myname: string) {}
        get name() {
            return this.myname;
        set name(value) {
            this.myname = value;
    let user = new User('zhufeng');
    console.log(user.name); 
    user.name = 'jiagou'; 
    console.log(user.name);
    

    5.4 readonly #

  • readonly修饰的变量只能在构造函数中初始化
  • 在 TypeScript 中,const 是常量标志符,其值不能被重新分配
  • TypeScript 的类型系统同样也允许将 interface、type、 class 上的属性标识为 readonly
  • readonly 实际上只是在编译阶段进行代码检查。而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)
  • class Animal {
        public readonly name: string
        constructor(name:string) {
            this.name = name;
        changeName(name:string){
            this.name = name;
    let a = new Animal('zhufeng');
    a.changeName('jiagou');
    

    5.5 继承 #

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  • 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
  • super可以调用父类上的方法和属性
  • class Person {
        name: string;//定义实例的属性,默认省略public修饰符
        age: number;
        constructor(name:string,age:number) {//构造函数
            this.name=name;
            this.age=age;
        getName():string {
            return this.name;
        setName(name:string): void{
            this.name=name;
    class Student extends Person{
        no: number;
        constructor(name:string,age:number,no:number) {
            super(name,age);
            this.no=no;
        getNo():number {
            return this.no;
    let s1=new Student('zfpx',10,1);
    console.log(s1);
    

    5.6 类里面的修饰符 #

    class Father {
        public name: string;  //类里面 子类 其它任何地方外边都可以访问
        protected age: number; //类里面 子类 都可以访问,其它任何地方不能访问
        private money: number; //类里面可以访问, 子类和其它任何地方都不可以访问
        constructor(name:string,age:number,money:number) {//构造函数
            this.name=name;
            this.age=age;
            this.money=money;
        getName():string {
            return this.name;
        setName(name:string): void{
            this.name=name;
    class Child extends Father{
        constructor(name:string,age:number,money:number) {
            super(name,age,money);
        desc() {
            console.log(`${this.name} ${this.age} ${this.money}`);
    let child = new Child('zfpx',10,1000);
    console.log(child.name);
    console.log(child.age);
    console.log(child.money);
    

    5.7 静态属性 静态方法 #

    class Father {
        static className='Father';
        static getClassName() {
            return Father.className;
        public name: string;
        constructor(name:string) {//构造函数
            this.name=name;
    console.log(Father.className);
    console.log(Father.getClassName());
    

    5.8 装饰器 #

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
  • 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
  • 装饰器的写法分为普通装饰器和装饰器工厂
  • class Person{
        say() {
            console.log('hello')
    function Person() {}
    Object.defineProperty(Person.prototype, 'say', {
        value: function() { console.log('hello'); },
        enumerable: false,
        configurable: true,
        writable: true
    

    5.8.1 类装饰器 #

  • 类装饰器在类声明之前声明,用来监视、修改或替换类定义
  • namespace a {
        //当装饰器作为修饰类的时候,会把构造器传递进去
        function addNameEat(constructor: Function) {
          constructor.prototype.name = "zhufeng";
          constructor.prototype.eat = function () {
            console.log("eat");
        @addNameEat
        class Person {
          name!: string;
          eat!: Function;
          constructor() {}
        let p: Person = new Person();
        console.log(p.name);
        p.eat();
    namespace b {
        //还可以使用装饰器工厂
        function addNameEatFactory(name:string) {
        return function (constructor: Function) {
            constructor.prototype.name = name;
            constructor.prototype.eat = function () {
            console.log("eat");
        @addNameEatFactory('zhufeng')
        class Person {
          name!: string;
          eat!: Function;
          constructor() {}}
        let p: Person = new Person();
        console.log(p.name);
        p.eat();
    namespace c {
        //还可以替换类,不过替换的类要与原类结构相同
        function enhancer(constructor: Function) {
        return class {
            name: string = "jiagou";
            eat() {
            console.log("吃饭饭");
        @enhancer
        class Person {
          name!: string;
          eat!: Function;
          constructor() {}}
        let p: Person = new Person();
        console.log(p.name);
        p.eat();
    

    5.8.2 属性装饰器 #

  • 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数
  • 属性装饰器用来装饰属性
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是属性的名称
    • 方法装饰器用来装饰方法
      • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
      • 第二个参数是方法的名称
      • 第三个参数是方法描述符
      namespace d {
          //修饰实例属性
          function upperCase(target: any, propertyKey: string) {
              let value = target[propertyKey];
              const getter = function () {
                  return value;
              // 用来替换的setter
              const setter = function (newVal: string) {
                  value = newVal.toUpperCase()
              // 替换属性,先删除原先的属性,再重新定义属性
              if (delete target[propertyKey]) {
                  Object.defineProperty(target, propertyKey, {
                      get: getter,
                      set: setter,
                      enumerable: true,
                      configurable: true
          //修饰实例方法
          function noEnumerable(target: any, property: string, descriptor: PropertyDescriptor) {
              console.log('target.getName', target.getName);
              console.log('target.getAge', target.getAge);
              descriptor.enumerable = true;
          //重写方法
          function toNumber(target: any, methodName: string, descriptor: PropertyDescriptor) {
              let oldMethod = descriptor.value;
              descriptor.value = function (...args: any[]) {
                  args = args.map(item => parseFloat(item));
                  return oldMethod.apply(this, args);
          class Person {
              @upperCase
              name: string = 'zhufeng'
              public static age: number = 10
              constructor() { }
              @noEnumerable
              getName() {
                  console.log(this.name);
              @toNumber
              sum(...args: any[]) {
                  return args.reduce((accu: number, item: number) => accu + item, 0);
          let p: Person = new Person();
          for (let attr in p) {
              console.log('attr=', attr);
          p.name = 'jiagou';
          p.getName();
          console.log(p.sum("1", "2", "3"));
      

      5.8.3 参数装饰器 #

    • 会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据
      • 第1个参数对于静态成员是类的构造函数,对于实例成员是类的原型对象
      • 第2个参数的名称
      • 第3个参数在函数列表中的索引
      namespace d {
          interface Person {
              age: number;
          function addAge(target: any, methodName: string, paramsIndex: number) {
              console.log(target);
              console.log(methodName);
              console.log(paramsIndex);
              target.age = 10;
          class Person {
              login(username: string, @addAge password: string) {
                  console.log(this.age, username, password);
          let p = new Person();
          p.login('zhufeng', '123456')
      

      5.8.4 装饰器执行顺序 #

    • 有多个参数装饰器时:从最后一个参数依次向前执行
    • 方法和方法参数中参数装饰器先执行。
    • 类装饰器总是最后执行
    • 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
    • 类比React组件的componentDidMount 先上后下、先内后外
    • namespace e {
          function Class1Decorator() {
              return function (target: any) {
                  console.log("类1装饰器");
          function Class2Decorator() {
              return function (target: any) {
                  console.log("类2装饰器");
          function MethodDecorator() {
              return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
                  console.log("方法装饰器");
          function Param1Decorator() {
              return function (target: any, methodName: string, paramIndex: number) {
                  console.log("参数1装饰器");
          function Param2Decorator() {
              return function (target: any, methodName: string, paramIndex: number) {
                  console.log("参数2装饰器");
          function PropertyDecorator(name: string) {
              return function (target: any, propertyName: string) {
                  console.log(name + "属性装饰器");
          @Class1Decorator()
          @Class2Decorator()
          class Person {
              @PropertyDecorator('name')
              name: string = 'zhufeng';
              @PropertyDecorator('age')
              age: number = 10;
              @MethodDecorator()
              greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
      name属性装饰器
      age属性装饰器
      参数2装饰器
      参数1装饰器
      方法装饰器
      类2装饰器
      类1装饰器
      

      5.9 抽象类 #

    • 抽象描述一种抽象的概念,无法被实例化,只能被继承
    • 无法创建抽象类的实例
    • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
    • abstract class Animal {
          name!:string;
          abstract speak():void;
      class Cat extends Animal{
          speak(){
              console.log('喵喵喵');
      let animal = new Animal();//Cannot create an instance of an abstract class
      animal.speak();
      let cat = new Cat();
      cat.speak();
      let cat = new Cat();
      console.log(cat.speak('hello'));
      //--------------------------------------------
      function double(val:number):number
      function double(val:string):string
      function double(val:any):any{
        if(typeof val == 'number'){
          return val *2;
        return val + val;
      let r = double(1);
      console.log(r);
      

      5.12 继承 vs 多态 #

    • 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
    • 多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
    • class Animal{
          speak(word:string):string{
              return 'Animal: '+word;
      class Cat extends Animal{
          speak(word:string):string{
              return 'Cat:'+word;
      class Dog extends Animal{
          speak(word:string):string{
              return 'Dog:'+word;
      let cat = new Cat();
      console.log(cat.speak('hello'));
      let dog = new Dog();
      console.log(dog.speak('hello'));
      

      6. 接口 #

    • 接口一方面可以在面向对象编程中表示为行为的抽象,另外可以用来描述对象的形状
    • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
    • 一个类可以继承另一个类并实现多个接口
    • 接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念
    • 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类的可以有多个子类,但只能有一个父类
    • 6.1 接口 #

    • interface中可以用分号或者逗号分割每一项,也可以什么都不加
    • 6.1.1 对象的形状 #

      //接口可以用来描述`对象的形状`,少属性或者多属性都会报错
      interface Speakable{
          speak():void;
          name?:string;//?表示可选属性
      let speakman:Speakable = {
          speak(){},//少属性会报错
          name,
          age//多属性也会报错
      

      6.1.2 行为的抽象 #

      //接口可以在面向对象编程中表示为行为的抽象
      interface Speakable{
          speak():void;
      interface Eatable{
          eat():void
      //一个类可以实现多个接口
      class Person implements Speakable,Eatable{
          speak(){
              console.log('Person说话');
          eat(){}
      class TangDuck implements Speakable{
          speak(){
              console.log('TangDuck说话');
          eat(){}
      

      6.1.3 任意属性 #

      //无法预先知道有哪些新的属性的时候,可以使用 `[propName:string]:any`,propName名字是任意的
      interface Person {
        readonly id: number;
        name: string;
        [propName: string]: any;
      let p1 = {
        id:1,
        name:'zhufeng',
        age:10
      

      6.2 接口的继承 #

    • 一个接口可以继承自另外一个接口
    • interface Speakable {
          speak(): void
      interface SpeakChinese extends Speakable {
          speakChinese(): void
      class Person implements SpeakChinese {
          speak() {
              console.log('Person')
          speakChinese() {
              console.log('speakChinese')
      

      6.3 readonly #

    • 用 readonly 定义只读属性可以避免由于多人协作或者项目较为复杂等因素造成对象的值被重写
    • interface Person{
        readonly id:number;
        name:string
      let tom:Person = {
        id :1,
        name:'zhufeng'
      tom.id = 1;
      

      6.4 函数类型接口 #

    • 对方法传入的参数和返回值进行约束
    • interface discount{
        (price:number):number
      let cost:discount = function(price:number):number{
         return price * .8;
      

      6.5 可索引接口 #

    • 对数组和对象进行约束
    • userInterface 表示index的类型是 number,那么值的类型必须是 string
    • UserInterface2 表示:index 的类型是 string,那么值的类型必须是 string
    • interface UserInterface {
        [index:number]:string
      let arr:UserInterface = ['zfpx1','zfpx2'];
      console.log(arr);
      interface UserInterface2 {
        [index:string]:string
      let obj:UserInterface2 = {name:'zhufeng'};
      

      6.6 类接口 #

    • 对类的约束
    • interface Speakable {
          name: string;
          speak(words: string): void
      class Dog implements Speakable {
          name!: string;
          speak(words:string) {
              console.log(words);
      let dog = new Dog();
      dog.speak('汪汪汪');
      

      6.7 构造函数的类型 #

    • 在 TypeScript 中,我们可以用 interface 来描述类
    • 同时也可以使用interface里特殊的new()关键字来描述类的构造函数类型
    • class Animal{
        constructor(public name:string){
      //不加new是修饰函数的,加new是修饰类的
      interface WithNameClass{
        new(name:string):Animal
      function createAnimal(clazz:WithNameClass,name:string){
         return new clazz(name);
      let a = createAnimal(Animal,'zhufeng');
      console.log(a.name);
      

      6.8 抽象类 vs 接口 #

    • 不同类之间公有的属性或方法,可以抽象成一个接口(Interfaces)
    • 而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
    • 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化
    • 一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
    • 抽象类也可以实现接口
    • abstract class Animal{
          name:string;
          constructor(name:string){
            this.name = name;
          abstract speak():void;
      interface Flying{
            fly():void
      class Duck extends Animal implements Flying{
            speak(){
                console.log('汪汪汪');
            fly(){
                console.log('我会飞');
      let duck = new Duck('zhufeng');
      duck.speak();
      duck.fly();
      

      7. 泛型 #

    • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
    • 泛型T作用域只限于函数内部使用
    • 7.1 泛型函数 #

    • 首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
    • function createArray(length: number, value: any): Array<any> {
        let result: any = [];
        for (let i = 0; i < length; i++) {
          result[i] = value;
        return result;
      let result = createArray(3,'x');
      console.log(result);
      

      使用了泛型

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

      7.2 类数组 #

    • 类数组(Array-like Object)不是数组类型,比如 arguments
    • function sum() {
          let args: IArguments = arguments;
          for (let i = 0; i < args.length; i++) {
              console.log(args[i]);
      sum(1, 2, 3);
      let root = document.getElementById('root');
      let children: HTMLCollection = (root as HTMLElement).children;
      children.length;
      let nodeList: NodeList = (root as HTMLElement).childNodes;
      nodeList.length;
      

      7.3 泛型类 #

      7.3.1 泛型类 #

      class MyArray<T>{
          private list:T[]=[];
          add(value:T) {
              this.list.push(value);
          getMax():T {
              let result=this.list[0];
              for (let i=0;i<this.list.length;i++){
                  if (this.list[i]>result) {
                      result=this.list[i];
              return result;
      let arr=new MyArray();
      arr.add(1); arr.add(2); arr.add(3);
      let ret = arr.getMax();
      console.log(ret);
      

      7.3.2 泛型与 new #

      function factory<T>(type: {new():T}): T {
        return new type(); // This expression is not constructable.
      

      7.5 泛型接口 #

    • 泛型接口可以用来约束函数
    • interface Calculate{
        <T>(a:T,b:T):T
      let add:Calculate = function<T>(a:T,b:T){
        return a;
      add<number>(1,2);
      

      7.6 多个类型参数 #

    • 泛型可以有多个
    • function swap<A,B>(tuple:[A,B]):[B,A]{
        return [tuple[1],tuple[0]];
      let swapped = swap<string,number>(['a',1]);
      console.log(swapped);
      console.log(swapped[0].toFixed(2));
      console.log(swapped[1].length);
      

      7.7 默认泛型类型 #

      function createArray3<T=number>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
          result[i] = value;
        return result;
      let result2 = createArray3(3,'x');
      console.log(result2);
      

      7.8 泛型约束 #

    • 在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性或方法。
    • function logger<T>(val: T) {
          console.log(val.length); //直接访问会报错
      //可以让泛型继承一个接口
      interface LengthWise {
          length: number
      //可以让泛型继承一个接口
      function logger2<T extends LengthWise>(val: T) {
          console.log(val.length)
      logger2('zhufeng');
      logger2(1);
      

      7.9 泛型接口 #

    • 定义接口的时候也可以指定泛型
    • interface Cart<T>{
        list:T[]
      let cart:Cart<{name:string,price:number}> = {
        list:[{name:'zhufeng',price:10}]
      console.log(cart.list[0].name,cart.list[0].price);
      

      7.10 compose #

      compose

      import compose from ".";
      /* zero functions */
      console.log(compose()<string>("zhufeng"));
      /* one functions */
      interface F{
          (a:string):string
      let f: F = (a:string):string=>a+'f';
      console.log(compose<F>(f)("zhufeng"));
      /* two functions */
      type A = string;
      type R = string;
      type T = string[];
      let f1 = (a: A): R => a + "f1";
      let f2 = (...a: T): A => a + "f2";
      console.log(compose<A,T,R>(f1,f2)("zhufeng"));
      

      7.11 泛型类型别名 #

    • 泛型类型别名可以表达更复杂的类型
    • type Cart<T> = {list:T[]} | T[];
      let c1:Cart<string> = {list:['1']};
      let c2:Cart<number> = [1];
      

      7.12 泛型接口 vs 泛型类型别名 #

    • 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
    • 类型别名不能被 extends和 implements,这时我们应该尽量使用接口代替类型别名
    • 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适
    • 8.结构类型系统 #

      8.1 接口的兼容性 #

    • 如果传入的变量和声明的类型不匹配,TS就会进行兼容性检查
    • 原理是Duck-Check,就是说只要目标类型中声明的属性变量在源类型中都存在就是兼容的
    • interface Animal {
          name: string;
          age: number;
      interface Person {
          name: string;
          age: number;
          gender: number
      // 要判断目标类型`Person`是否能够兼容输入的源类型`Animal`
      function getName(animal: Animal): string {
          return animal.name;
      let p = {
          name: 'zhufeng',
          age: 10,
          gender: 0
      getName(p);
      //只有在传参的时候两个变量之间才会进行兼容性的比较,赋值的时候并不会比较,会直接报错
      let a: Animal = {
          name: 'zhufeng',
          age: 10,
          gender: 0
      

      8.2 基本类型的兼容性 #

      //基本数据类型也有兼容性判断
      let num : string|number;
      let str:string='zhufeng';
      num = str;
      //只要有toString()方法就可以赋给字符串变量
      let num2 : {
        toString():string
      let str2:string='jiagou';
      num2 = str2;
      

      8.3 类的兼容性 #

    • 在TS中是结构类型系统,只会对比结构而不在意类型
    • class Animal{
          name:string
      class Bird extends Animal{
         swing:number
      let a:Animal;
      a = new Bird();
      let b:Bird;
      //并不是父类兼容子类,子类不兼容父类
      b = new Animal();
      class Animal{
        name:string
      //如果父类和子类结构一样,也可以的
      class Bird extends Animal{}
      let a:Animal;
      a = new Bird();
      let b:Bird;
      b = new Animal();
      
      //甚至没有关系的两个类的实例也是可以的
      class Animal{
        name:string
      class Bird{
        name:string
      let a:Animal ;
      a = new Bird();
      let b:Bird;
      b = new Animal();
      

      8.4 函数的兼容性 #

    • 比较函数的时候是要先比较函数的参数,再比较函数的返回值
    • 8.4.1 比较参数 #

      type sumFunc = (a:number,b:number)=>number;
      let sum:sumFunc;
      function f1(a:number,b:number):number{
        return a+b;
      sum = f1;
      //可以省略一个参数
      function f2(a:number):number{
         return a;
      sum = f2;
      //可以省略二个参数
      function f3():number{
          return 0;
      sum = f3;
       //多一个参数可不行
      function f4(a:number,b:number,c:number){
          return a+b+c;
      sum = f4;
      

      8.4.2 比较返回值 #

      type GetPerson = ()=>{name:string,age:number};
      let getPerson:GetPerson;
      //返回值一样可以
      function g1(){
          return {name:'zhufeng',age:10};
      getPerson = g1;
      //返回值多一个属性也可以
      function g2(){
          return {name:'zhufeng',age:10,gender:'male'};
      getPerson = g2;
      //返回值少一个属性可不行
      function g3(){
          return {name:'zhufeng'};
      getPerson = g3;
      //因为有可能要调用返回值上的方法
      getPerson().age.toFixed();
      

      8.5 函数的协变与逆变 #

    • 协变(Covariant):只在同一个方向;
    • 逆变(Contravariant):只在相反的方向;
    • 双向协变(Bivariant):包括同一个方向和不同方向;
    • 不变(Invariant):如果类型不完全相同,则它们是不兼容的。

    • A ≼ B 意味着 A 是 B 的子类型。

    • A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。
    • x : A 意味着 x 的类型为 A

    • 返回值类型是协变的,而参数类型是逆变的

    • 返回值类型可以传子类,参数可以传父类
    • 参数逆变父类 返回值协变子类 搀你父,返鞋子
    • class Animal{}
      class Dog extends Animal{
          public name:string = 'Dog'
      class BlackDog extends Dog {
          public age: number = 10
      class WhiteDog extends Dog {
          public home: string = '北京'
      let animal: Animal;
      let blackDog: BlackDog;
      let whiteDog: WhiteDog;
      type Callback = (dog: Dog)=>Dog;
      function exec(callback:Callback):void{
          callback(whiteDog);
      //不行  callback(redDog);
      type ChildToChild = (blackDog: BlackDog) => BlackDog;
      const childToChild: ChildToChild = (blackDog: BlackDog): BlackDog => blackDog
      exec(childToChild);
      //也不行,理由同上
      type ChildToParent = (blackDog: BlackDog) => Animal;
      const childToParent: ChildToParent = (blackDog: BlackDog): Animal => animal
      exec(childToParent);
      //不行 因为有可能调用返回的Dog的方法
      type ParentToParent = (animal: Animal) => Animal;
      const parentToParent: ParentToParent = (animal: Animal): Animal => animal
      exec(parentToParent);
      //可以,所有的狗都是动物,返回的不管什么狗都是狗
      type ParentToChild = (animal: Animal) => BlackDog;
      const parentToChild: ParentToChild = (animal: Animal): BlackDog => blackDog
      exec(parentToChild);
      //(Animal → Greyhound) ≼ (Dog → Dog)
      //返回值类型很容易理解:黑狗是狗的子类。但参数类型则是相反的:动物是狗的父类!
      
      // string | number|boolean 是 string | number的父类型
      // string是string|number的子类型
      type Callback2 = (a: string | number) => string | number;
      function exec2(callback: Callback2):void{
          callback('');
      type ParentToChild2 = (a: string | number | boolean) => string;
      const parentToChild2: ParentToChild2 = (a: string | number | boolean): string => ''
      exec2(parentToChild2);
      type Callback3 = (a: string | number) => string | number;
      function exec3(callback: Callback2): void {
          callback('');
      type ParentToParent3 = (a: string) => string;
      const parentToParent3: ParentToParent3 = (a: string): string => ''
      exec3(parentToChild3);
      
    • 在 TypeScript 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes--strict 标记来修复这个问题
    • 8.6 泛型的兼容性 #

    • 泛型在判断兼容性的时候会先判断具体的类型,然后再进行兼容性判断
    • //接口内容为空没用到泛型的时候是可以的
      //1.接口内容为空没用到泛型的时候是可以的
      interface Empty<T>{}
      let x!:Empty<string>;
      let y!:Empty<number>;
      x = y;
      //2.接口内容不为空的时候不可以
      interface NotEmpty<T>{
        data:T
      let x1!:NotEmpty<string>;
      let y1!:NotEmpty<number>;
      x1 = y1;
      //实现原理如下,称判断具体的类型再判断兼容性
      interface NotEmptyString{
          data:string
      interface NotEmptyNumber{
          data:number
      let xx2!:NotEmptyString;
      let yy2!:NotEmptyNumber;
      xx2 = yy2;
      

      8.7 枚举的兼容性 #

    • 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容
    • 不同枚举类型之间是不兼容的
    • //数字可以赋给枚举
      enum Colors {Red,Yellow}
      let c:Colors;
      c = Colors.Red;
      c = 1;
      c = '1';
      //枚举值可以赋给数字
      let n:number;
      n = 1;
      n = Colors.Red;
      

      9.类型保护 #

    • 类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型
    • 类型保护就是能够通过关键字判断出分支中的类型
    • 9.1 typeof 类型保护 #

      function double(input: string | number | boolean) {
          if (typeof input === 'string') {
              return input + input;
          } else {
              if (typeof input === 'number') {
                  return input * 2;
              } else {
                  return !input;
      

      9.2 instanceof类型保护 #

      class Animal {
          name!: string;
      class Bird extends Animal {
          swing!: number
      function getName(animal: Animal) {
          if (animal instanceof Bird) {
              console.log(animal.swing);
          } else {
              console.log(animal.name);
      

      9.3 null保护 #

    • 如果开启了strictNullChecks选项,那么对于可能为null的变量不能调用它上面的方法和属性
    • function getFirstLetter(s: string | null) {
          //第一种方式是加上null判断
          if (s == null) {
              return '';
          //第二种处理是增加一个或的处理
          s = s || '';
          return s.charAt(0);
      //它并不能处理一些复杂的判断,需要加非空断言操作符
      function getFirstLetter2(s: string | null) {
          function log() {
              console.log(s!.trim());
          s = s || '';
          log();
          return s.charAt(0);
      

      9.4 链判断运算符 #

    • 链判断运算符是一种先检查属性是否存在,再尝试访问该属性的运算符,其符号为 ?.
    • 如果运算符左侧的操作数 ?. 计算为 undefined 或 null,则表达式求值为 undefined 。否则,正常触发目标属性访问,方法或函数调用。
    • a?.b; //如果a是null/undefined,那么返回undefined,否则返回a.b的值.
      a == null ? undefined : a.b;
      a?.[x]; //如果a是null/undefined,那么返回undefined,否则返回a[x]的值
      a == null ? undefined : a[x];
      a?.b(); // 如果a是null/undefined,那么返回undefined
      a == null ? undefined : a.b(); //如果a.b不函数的话抛类型错误异常,否则计算a.b()的结果
      a?.(); //如果a是null/undefined,那么返回undefined
      a == null ? undefined : a(); //如果A不是函数会抛出类型错误
      //否则 调用a这个函数
      

      链判断运算符 还处于 stage1 阶段,TS 也暂时不支持

      9.5 可辨识的联合类型 #

    • 就是利用联合类型中的共有字段进行类型保护的一种技巧
    • 相同字段的不同取值就是可辨识
    • interface WarningButton{
        class:'warning',
        text1:'修改'
      interface DangerButton{
        class:'danger',
        text2:'删除'
      type Button = WarningButton|DangerButton;
      function getButton(button:Button){
       if(button.class=='warning'){
        console.log(button.text1);
       if(button.class=='danger'){
        console.log(button.text2);
      

      类型字面量+可辨识联合类型

      interface User {
          username: string
      type Action = {
          type:'add',
          payload:User
      } | {
          type: 'delete'
          payload: number
      const UserReducer = (action: Action) => {
        switch (action.type) {
          case "add":
            let user: User = action.payload;
            break;
          case "delete":
            let id: number = action.payload;
            break;
          default:
            break;
      

      9.6 in操作符 #

    • in 运算符可以被用于参数类型的判断
    • interface Bird {
          swing: number;
      interface Dog {
          leg: number;
      function getNumber(x: Bird | Dog) {
          if ("swing" in x) {
            return x.swing;
          return x.leg;
      

      9.7 自定义的类型保护 #

    • TypeScript 里的类型保护本质上就是一些表达式,它们会在运行时检查类型信息,以确保在某个作用域里的类型是符合预期的
    • type is Type1Class就是类型谓词
    • 谓词为 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名
    • 每当使用一些变量调用isType1时,如果原始类型兼容,TypeScript会将该变量缩小到该特定类型
    • function isType1(type: Type1Class | Type2Class): type is Type1Class {
          return (<Type1Class>type).func1 !== undefined;
      
      interface Bird {
        swing: number;
      interface Dog {
        leg: number;
      //没有相同字段可以定义一个类型保护函数
      function isBird(x:Bird|Dog): x is Bird{
        return (<Bird>x).swing == 2;
        //return (x as Bird).swing == 2;
      function getAnimal(x: Bird | Dog) {
        if (isBird(x)) {
          return x.swing;
        return x.leg;
      

      9.8 unknown #

    • TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的安全类型
    • unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查
    • 9.8.1 any 类型 #

    • 在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的 顶级类型 (也被称作 全局超级类型)。
    • TypeScript允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查
    • let value: any;
      value = true;             // OK
      value = 42;               // OK
      value = "Hello World";    // OK
      value = [];               // OK
      value = {};               // OK
      value = Math.random;      // OK
      value = null;             // OK
      value = undefined;        // OK
      let value: any;
      value.foo.bar;  // OK
      value.trim();   // OK
      value();        // OK
      new value();    // OK
      

      9.8.2 unknown 类型 #

    • 就像所有类型都可以被归为 any,所有类型也都可以被归为 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)

    • 任何类型都可以赋值给unknown类型

      let value: unknown; value = true; // OK value = 42; // OK value = "Hello World"; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK
    • unknown类型只能被赋值给any类型和unknown类型本身
    • let value: unknown;
      let value1: unknown = value;   // OK
      let value2: any = value;       // OK
      let value3: boolean = value;   // Error
      let value4: number = value;    // Error
      let value5: string = value;    // Error
      let value6: object = value;    // Error
      let value7: any[] = value;     // Error
      let value8: Function = value;  // Error
      

      9.8.3 缩小 unknown 类型范围 #

    • 如果没有类型断言或类型细化时,不能在unknown上面进行任何操作
    • typeof
    • instanceof
    • 自定义类型保护函数
    • 可以对 unknown 类型使用类型断言
    • const value: unknown = "Hello World";
      const someString: string = value as string;
      

      9.8.4 联合类型中的 unknown 类型 #

    • 在联合类型中,unknown 类型会吸收任何类型。这就意味着如果任一组成类型是 unknown,联合类型也会相当于 unknown:
      type UnionType1 = unknown | null;       // unknown
      type UnionType2 = unknown | undefined;  // unknown
      type UnionType3 = unknown | string;     // unknown
      type UnionType4 = unknown | number[];   // unknown
      

      9.8.5 交叉类型中的 unknown 类型 #

    • 在交叉类型中,任何类型都可以吸收 unknown 类型。这意味着将任何类型与 unknown 相交不会改变结果类型
    • type IntersectionType1 = unknown & null;       // null
      type IntersectionType2 = unknown & undefined;  // undefined
      type IntersectionType3 = unknown & string;     // string
      type IntersectionType4 = unknown & number[];   // number[]
      type IntersectionType5 = unknown & any;        // any
      

      9.8.6 never是unknown的子类型 #

      type isNever = never extends unknown ? true : false;
      

      9.8.7 keyof unknown 等于never #

      type key = keyof unknown;
      

      9.8.8 只能对unknown进行等或不等操作,不能进行其它操作 #

      un1===un2;
      un1!==un2;
      un1 += un2;
      

      9.8.9 不能做任何操作 #

    • 不能访问属性
    • 不能作为函数调用
    • 不能当作类的构造函数不能创建实例
    • un.name
      un();
      new un();
      

      9.8.10 映射属性 #

    • 如果映射类型遍历的时候是unknown,不会映射属性
      type getType<T> = {
        [P in keyof T]:number
      type t = getType<unknown>;
      

      10. 类型变换 #

      10.1 类型推断 #

    • TypeScript 能根据一些简单的规则推断变量的类型
    • 10.1.1 从右向左 #

    • 变量的类型可以由定义推断
    • 这是一个从右向左流动类型的示例
    • let foo = 1; // foo 是 'number'
      let bar = 'zhufeng'; // bar 是 'string'
      //foo = bar; // Error: 不能将 'string' 赋值给 `number`
      

      10.1.2 底部流出 #

    • 返回类型能被 return 语句推断
    • function add(a: number, b: number) {
          return a + b;
      let c = add(1,2);
      

      10.1.3 从左向右 #

    • 函数参数类型/返回值类型也能通过赋值来推断
    • type Sum = (a: number, b: number) => number;
      let sum: Sum = (a, b) => {
          a='zhufeng';
          return a + b;
      

      10.1.4 结构化 #

    • 推断规则也适用于结构化的存在(对象字面量)
    • const person = {
          name: 'zhufeng',
          age: 11
      let name =person.name;
      let age =person.age;
      age = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
      

      10.1.5 解构 #

    • 推断规则也适用于解构
    • const person = {
          name: 'zhufeng',
          age: 11
      let { name,age } = person;
      age = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
      //数组也一样
      const numbers = [1, 2, 3];
      numbers[0] = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
      

      10.1.5 DefaultProps #

      interface DefaultProps{ name?:string; age?:number; let defaultProps: DefaultProps = { name:'zhufeng', age:10 let props = { ...defaultProps, home:'北京' type Props = typeof props;

      10.1.6 小心使用返回值 #

    • 尽管 TypeScript 一般情况下能推断函数的返回值,但是它可能并不是你想要的
    • function addOne(a:any) {
          return a + 1;
      function sum(a: number, b: number) {
          return a + addOne(b);
      type Ret = ReturnType<typeof sum>;
      

      10.1 交叉类型 #

    • 交叉类型(Intersection Types)是将多个类型合并为一个类型
    • 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
    • //TypeScript 交叉类型是将多个类型合并为一个类型
      //这让我们可以把现有的多种类型叠加到一起成为一种类型
      //它包含了所需的所有类型的特性
      export {}
      //接口的交叉
      interface Bird {
          name: string,
          fly(): void
      interface Person {
          name: string,
          talk(): void
      type BirdPerson = Bird & Person;
      let p: BirdPerson = { name: 'zhufeng', fly() { }, talk() { } };
      p.fly;
      p.name
      p.talk;
      
      interface X {
          a: string;
          b: string;
      interface Y {
          a: number;
          c: string
      type XY = X & Y;
      type YX = Y & X;
      //c = string & number
      //let p1: XY={a:'',b:'',c:''};
      

      联合类型的交叉类型

      type Ta = string | number;
      type Tb = number | boolean;
      type Tc = Ta & Tb;
      

      mixin混入模式可以让你从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能

      interface AnyObject {
          [prop: string]: any;
      function mixin<T extends AnyObject, U extends AnyObject>(one: T,two: U): T & U {
          const result = <T & U>{};
          for (let key in one) {
              (<T>result)[key] = one[key];
          for (let key in two) {
              (<U>result)[key] = two[key];
          return result;
      const x = mixin({ name: "zhufeng" }, { age: 11 });
      console.log(x.name, x.age);
      

      10.2 typeof #

    • 可以获取一个变量的类型
    • //先定义类型,再定义变量
      type People = {
          name:string,
          age:number,
          gender:string
      let p1:People = {
          name:'zhufeng',
          age:10,
          gender:'male'
      
      //先定义变量,再定义类型
      let p1 = {
          name:'zhufeng',
          age:10,
          gender:'male'
      type People = typeof p1;
      function getName(p:People):string{
          return p.name;
      getName(p1);
      

      10.3 索引访问操作符 #

    • 可以通过[]获取一个类型的子类型
    • interface Person{
          name:string;
          age:number;
          job:{
              name:string
          interests:{name:string,level:number}[]
      let FrontEndJob:Person['job'] = {
          name:'前端工程师'
      let interestLevel:Person['interests'][0]['level'] = 2;
      

      10.4 keyof #

    • 索引类型查询操作符
    • interface Person{
        
  •