TypeScript入门

类型系统

  • 类型安全
    1. 强类型:语言层面限制函数的实参类型必须与形参类型相同;
      在这里插入图片描述

    2. 弱类型: 语言层面不会限制实参的类型;

      function foo (num) {
          console.log(num);
      }
      foo(100);//不会报错 -> 100
      foo('100');//不会报错 -> 100
      
    3. 强类型和弱类型根本不是某一个权威机构的定义:
      强类型有更强的类型约束, 而弱类型中几乎没有什么约束;
      强类型语言中不允许任何的隐式类型转换, 而弱类型语言则允许任意的数据隐式类型转换;
      强类型在代码编译的阶段就会报错, 而不会在允许阶段通过逻辑判断去限制(JavaScript的所有的类型错误都是在运行阶段, 通过逻辑判断手动抛出的);

      console.log('100' - 50);//50
      
  • 类型检查
    1. 静态类型语言: 一个变量声明时它的类型就是明确的, 变量声明后, 它的类型就不允许在修改;

    2. 动态类型语言: 在运行阶段才能够明确一个变量的类型, 而且变量的类型随时可以改变; 动态语言类型中的变量是没有类型的, 变量中存放的值是有类型的;

      var foo = 100;
      foo = bar;//javaScript 是动态类型语言
      

    在这里插入图片描述

  • JavaScript弱类型的部分问题:
    1. 某些类型异常需要等到运行时才能发现;

      const obj = {};
      //如果代码过大, 不去运行这行代码, 代码就不会有错误, 这个我们的代码安全带来了隐患;
      obj.foo();//会报错
      
    2. 类型不明确, 有可能造成代码功能错误;

      function sun (a, b) {
          return a + b;
      }
      console.log(sum(100, 100));//200
      //类型不明确, 代码的功能出现错误
      console.loe(sum(100, '100'));//100100
      
    3. 使我们对对象的索引器用法产生错误;

      const obj = {};
      obj[true] = 100;
      //这里的使用对象的索引器(obj['true'])是错误的
      console.log(obj['true']);//100
      
  • 强类型的优势
    1. 错误更早的暴露: 可以在编码阶段提前去消灭一大波可能会出现的类型异常;

    2. 代码更智能, 编码更准确:

      function render(element) {
         element.className = 'container',//这里智能提示不起作用(开发工具定义不到element的类型), 只能通过记忆书写代码, 很可能出现错误代码
      }
      
    3. 重构更牢靠: 删除某个对象的成员, 或者修改某个成员的名称 更加的牢靠;

      const util = {//如果代码中大量使用该对象, 当我们要修改aaa属性名的时候, 修改了之后要运行代码才能显示出来; 如果是强类型语言, 修改完以后, 在编译的时候就会爆出错误, 我们就可以找到所有用到aaa的地方加以修改;
          aaa: {} => {
              console.log('util func');
          }
      };
      
    4. 减少不必要的类型判断

      function sun (a, b) {
          //如果是强类型, 这个判断就不需要写
          if ('number' !=== typeof a || 'number' !=== typeof b) {
              throw new TypeError('arguments must be a number');
          }
          retrun a + b;
      }
      

Flow: JavaScript 的类型检查器

  • 代码通过类型注解的方式来标注变量或者参数的数据类型, Flow通过类型注解来检查代码中变量或者参数是否存在类型使用异常, 从而实现在开发阶段对类型的检查;

    function sum (a: number, b: number) {//:number 为类型注解
        return a + b;
    }
    
    • 在生产环境中: 可以通过Babel/Flow提供的模块去除类型注解, 所以不会对生产环境造成影响;

    • 不要求所有的变脸或参数都加注解, 不加的为any类型;

  • Flow具有类型推断的特征: 可以根据代码中的使用情况, 去推断出变量的类型;

  • Flow的快速上手
    • 安装: 这里通过yarn 安装

      1. yarn init --yes(npm init -y): Flow是以一个npm 模块工作的, 所以在项目中要初始化 package.json
      2. yarn add flow-bin --dev(npm i flow-bin --dev): 作为项目的依赖模块安装;
    • 使用

      1. 在文件的开头使用注释

        @flow的标记, 这样Flow才会检查这个文件

      2. 在文件中使用类型注解

        function sum (a: number, b: number) {//:number 为类型注解
            return a + b;
        }
        
      3. 运行yarn flow init(npm 在这之前需要在package.json的scripts中添加"flow": "flow"语句, 然后再执行 npm run flow init): 成功后文件目录下就会多出一个.flowconfig的文件(这是flow的配置文件);

      4. 运行yarn flow(npm run flow): 执行flow 命令, 对文件进行检查;

      5. 运行yarn flow stop(npm run flow stop): 关闭flow命令;

  • Flow编译
    1. flow-remove-types: 官方提供的模块, 移除Js代码中的类型注解

      • 安装:

        //npm环境: npm i flow-remove-types --dev
        yarn add flow-remove-types
        
      • 运行

        //npm环境: 需先在package.json -> scripts中添加: "flowRemove": "flow-remove-types src/ -d dist/"
        //npm环境: npm run flowRemove
        //src 为生产环境下代码文件夹 dist 为输出代码文件夹
        yarn flow-remove-types src -d dist
        
    2. Babel: 使用Babel移除Js代码中的类型注解

      • 安装

        //babel/core: babel的核心模块; babel/cli:babel的cli工具, 使我们直接在命令行中使用babel命令完成编译; babel/preset-flow: 包含了转换flow类型注解的一个插件;
        yarn add @babel/core @babel/cli @babel/preset-flow --dev
        //npm环境: npm i @babel/core @babel/cli @babel/preset-flow --dev
        
      • 在src同级目录下添加 .babelrc 文件, 增加配置

        {
            "presets": ["@babel/preset-flow"]
        }
        //npm环境: 一样的操作
        
      • 使用

        //src 为生产环境下代码文件夹 dist 为输出代码文件夹
        yarn babel src -d dist
        //npm环境: 在package.json -> scripts中添加 "babel": "babel src/ -d dist/"
        //运行: npm run babel
        
  • Flow 插件 - Flow Language Supper (Flow 官方提供)

    异常不显示在终端, 开发工具中直接显示;

    https://flow.org/en/docs/editors/ : 官网给出的插件支持情况;

  • 类型注解:
    • 函数参数可以类型注解

      function  square(n: number) {
         return n * n;
      }
      
    • 定义变量可以类型注解

      let num: number = 100;
      num = 'string';//重新赋值为其他类型会报语法错误;
      
    • 函数返回值可以类型注解

      function foo (): number {//此函数只能number类型的数据
          return 100;   
      }
      //没有返回值的函数,其实返回的是undefined, 所以没有返回值的函数, 函数类型为void
      function noReturn (): void {
          
      }
      
  • Flow 类型
    • 原始类型

      const a: string = 'string';
      const b: number = Infinity // NaN // 100
      const c: boolean = false // true
      const d: null = null;
      const e: void = undefined;// Flow中undefined类型定义为void
      const f: symbol = Symbol();
      
    • 数组类型

      //array<number>: 表示泛型, 表示定的数组的成员只能为number类型
      const arr1: Array<number> = [1, 2, 3];
      //数组的成员只能为number类型
      const arr2: number[] = [4, 5, 6];
      //数组的成员是混合型, 但是数组的 length 只能等于2, 第一个必须为number, 第二个必须为字符串 -> 这种固定长度的数组 叫 元祖, 一般在函数中要返回多个返回值的时候,会用到元祖
      const arr3: [number, string] = [6, 'lc'];
      
    • 对象类型

      //obj1必须为对象类型, obj1内部必须要有foo、bar两个成员, foo的值必须为String类型, bar的值必须为number类型
      const obj1: {foo: string, bar: number} = {foo: 'string', bar: 100, csa: 100};
      //obj2中的foo是可有可无的
      const obj2: {foo?: string, bar: number} = {bar: 100};
      //obj3可以添加任意多的成员, 但是成员的键的类型和值的类型都必须为string类型
      const obj3: {[string]: string} = {};
      obj3.key1 = 'value1';
      obj3.key2 = 100;//这里100报错, 不允许数组类型的值
      
    • 函数类型

      函数类型除了参数和函数返回值可以类型注解, 但参数为函数的时候也可以类型注解;

      //用箭头函数的形式表示函数为参数的时候的类型注解
      function csa (callback: (string, number) => void) {
         callback('string', 100);
      }
      csa(function (str, n) {
         //str => string: tr只能为 string 类型
         //n => number: n 只能为 number类型
         //此函数不能有返回值
      });
      
    • 特殊类型

      //字面量类型: 限制变量必须为某个特定的值
      const foo: 'foo' = 'foo';// foo变量的值只能为foo, 为其他值就会报错;
      // 联合类型(或类型): returnMsg 的值可以是error、success、warning 三个值中的任意一个, 不能为其他值;
      const returnMsg: 'error' | 'success' | 'warning' = 'success';
      //typeVariate 的值可以为 number, 可以为 string
      const typeVariate: number | string = 'string' // 100
      //Flow 可以用type关键字声明一个 联合类型
      type StringOrNumber = string | number;
      const definition: StringOrNumber = 'uniteType'
      //Maber 类型: 有可能,在基础的类型之上扩展了 null 和 undefined 两个类型, 相当于 基础类型 | null | undefined
      //const gender: number = null;//此时gender不能为空
      const genderGood: ?number = null;//现在genderGood 可以为空, 也可以为undefined
      
    • mixed 和 any 类型

      1. mixed 类型 和 any 类型都可以表示任意类型;
      2. mixed 类型是强类型, any 类型是弱类型
      3. any 类型存在的意义: 兼容以前的老代码;
      //Mixed 类型: 可以用来接收任意类型的值
      //强类型: 必须要明确后才能调用方法
      function passMixed (value: mixed) {
         //value * value;//这里语法报错
         //value.slice(0, -1);//这里语法报错
         if('number' === typeof value) {
            value * value
         }
         if('string' === typeof value) {
            value.slice(0, -1);
         }
      }
      passMixed('string');
      passMixed(100);
      //any 类型: 可以用来接收任意类型的值
      //弱类型: 语法上既可以调用 string 的方法, 也可以调用 number 的方法
      function passAny(value: any) {
         value * value; 
         value.slice(0, -1);
      }
      passAny('string');
      passAny(100);
      
    • https://flow.org/en/docs/types: 官方文档给出的所有类型的描述文档

    • https://www.saltycrane.com/cheat-sheets/flow-type/latest/: 第三方类型手册

  • Flow 运行环境 API

    因为 JavaScript 不是独立工作的, 必须要运行到某个特定的运行环境中, 代码必然会使用到这些环境所提供的API 或者一些对象, 这些API 或者对象一样会有类型限制;

    //document.getElementById 要求传入的参数必须是一个字符串, 不能为数字, element 变量就必须为 html 或者 null 格式
    const element: HTMLElement | null = document.getElementById('app');
    
    • 运行环境API 声明函数的链接

      https://github.com/facebook/flow/blob/master/lib/core.js: JavaScript 自身标准库中的成员;

      https://github.com/facebook/flow/blob/master/lib/dom.js

      https://github.com/facebook/flow/blob/master/lib/bom.js

      https://github.com/facebook/flow/blob/master/lib/cssom.js

      https://github.com/facebook/flow/blob/master/lib/node.js

TypeScript: JavaScript的超集(扩展集)

TypeScript是在JavaScript的基础上增加了一套强大的类型系统以及对ES6+的支持,最终会被编译为原始的JavaScript;
TypeScript的文件以 .ts结尾;

  • TS优缺点
    • 优点:

      1. 任何一种JavaScript运行环境都支持;

      2. 功能更为强大, 生态也更健全、更完善;

      3. TypeScript属于 渐进式的

    • 缺点:

      1. 语言本身多了很多概念;
      2. 项目初期, TypeScript会增加一些成本: 项目初期, 会编写很多的类型声明;
  • TS快速上手
    • 安装

      1. yarn init --yes(npm init -y): TypeScript是以一个npm 模块工作的, 所以在项目中要初始化 package.json
      2. yarn add typescript --dev(npm i typescript --dev): 作为项目的依赖模块安装;
      3. 安装成功过后, 会在node_modules -> .bin 目录下多一个 tsc(TypeScriptCompile -> 作用: 编译TypeScript代码) 命令
    • 编译: yarn tsc 文件名;

      1. 检查代码的类型使用异常;
      2. 移除类型注解等一些扩展的语法;
      3. 转换 ECMAScript 的新特性;
  • TS配置文件

    tsc: tsc命令不仅仅可以去编译指定 ts 文件, 还可以去编译整个项目(工程), 在编译整个项目的时候需要给项目创建 TypeScript 配置文件;

    1. 初始化 TypeScript 配置文件

      yarn tsc --init
      
    2. 配置文件(tsconfig.json): 说明

      1. target: 设置编译后的JavaSC 所采用的 ECMAScript 标准
      2. module: 设置输出的代码会采用什么方式进行模块化;
      3. outDir: 设置编译结果输出到的文件夹;
      4. rootDir: 设置源代码所在的文件夹;
      5. sourceMap: 设置是否开启源代码映射, 一般为true;
      6. strict: 设置是否开启所有的严格模式(TS严格模式下需要类型注解);
      7. strictNullChecks: 设置变量不能为空;
      8. lib: 设置代码所引用的标准库;
  • TS标准库

    定义: 内置对象所对应的声明; 我们在代码中使用内置对象就必须引用相对应的标准库, 否则就会报错;

  • TS作用域问题

    解决变量重复定义的问题(原因: 变量是定义在全局作用域上的, TS去编译项目的时候就会出现这个错误): 在实际开发中一般不会遇到, 因为每个文件都会以模块的形式去工作;

    1. 利用立即执行函数:

      (function () {
          const a:number = 123;//在这里变量的作用域是函数的作用域
      })();
      
    2. 使用 export {}: 这样文件就可以作为一个模块, 作用域为模块作用域;

  • TS数据类型
    1. 原始类型

      在严格模式下, string、number、boolean 三种类型不允许为 null, 在默认模式下允许为 null;

      const a: string = 'foobar';
      const b: number = 100; //NaN //Infinity
      const c: boolean = true; // false
      const d: void = undefined;
      const e: null = null;
      const f: undefined = undefined;
      const g: symbol = Symbol();
      
    2. Objcect 类型: 并不单指普通的对象类型, 泛指所有的非原始类型(对象、数组、函数);
      const foo: object = function () {} // [] // {}
      //TS 中这样定义对象类型, 不能多于类型注解中的长度;
      const obj: {foo: string, bar: number} = { foo: 'string', bar: 123 };
      //专业的定义对象类型 需要用接口;
      
    3. 数组类型:
      //表示数组的成员全为数字
      const arr1: Array<number> = [1, 2];
      //number[] -> 表示数组的成员全为数字
      const arr2: number[] = [3, 4, 5];
      //利用元祖的形式 Array<number>, 明确数组的长度和每个元素的类型
      const arr3:[number, string] = [1, 'lc'];
      //数组类型的使用
      let sum = (...args: number[]) => {
         return args.reduce((prev, current) => prev + current, 0);
      }
      //当调用sum函数的时候, 传入字符串就会报错;
      sum(1, 3, 5, 7/* , 'lc' */);
      
    4. 元祖类型

      定义: 明确每个元素类型以及元素数量的数组

      const tuple: [number, string] = [18, 'lc'];
      const [age, name] = tuple;
      //元祖类型的使用: entries 返回的都是元祖类型
      Object.entries({
         foo: 123, 
         bar: 456
      });
      
    5. 枚举类型
      • 弊端:
        在开发中需要用某几个数值去代表某种状态, 我们一般用数字去表示, 但是时间久了以后, 可能会不清楚数字所代表所代表的类型; 这种情况下就需要用枚举来表示状态;

      • 特点

        1. 可以给一组数值分别取上有意义的名字;
        2. 一个枚举中只会出现几个固定的值, 不会出现超出范围的可能性;
      • JavaScript 中的枚举: 使用对象模拟 枚举

        const PostStatus = {
           Draft: 0,
           Unpublished: 1,
           Published: 2
        };
        

        在代码中可以用对象的属性来表示某种状态

        const articlePost = {
           title: 'hello TS',
           content: 'TS is a type superset of JavaScript',
           status: PostStatus.Draft //这里就用枚举类型定义的属性
        };
        
      • TS 中有专门的枚举类型: 使用一个 enum 去声明一个枚举, enum 后面跟枚举名称, 名称后跟一个对象, 对象内就是具体的枚举值

        • 特点:

          1. TS枚举对象中使用的是 等号(=) 赋值, 而不是使用对象中的 冒号(😃, 使用方法和对象的使用方法一样;

          2. TS枚举对象中可以不用 等号的方式去指定, 不指定的情况, 枚举对象中的值就会默认从 0 开始累加, 如果给枚举对象中第一个值去指定了值, 后面的值就会在第一个的基础上去累加

            enum PostStatus1 {
               Draft /* = 0 */ /* = 6 */,//这里可以不用指定值, 默认从0 开始累加, 如果指定了值, 后面的值就会从指定的值开始累加
               Unpublished /* = 1 */,
               Published/*  = 2 */
            };
            
          3. TS 枚举的值除了可以是数字以外, 还可以是字符串, 就是字符串枚举, 字符串枚举不能像数字一样去自增长, 所以定义字符串枚举时需要给每个成员指定相对应的值

            enum PostStatus2 {
               Draft = 'aaa',
               Unpublished = 'bbb',
               Published = 'ccc'
            };
            
          4. TS 枚举类型会入侵到我们运行时的代码(会影响我们编译后的结果): TS中大部门的类型, 经过编译转换后基本上都会被移除掉, 但是枚举类型不会, 枚举类型会被编译为双向的键值对对象;

            /* PostStatus1 通过编译后的代码: 
            var PostStatus1;
            (function (PostStatus1) {
                PostStatus1[PostStatus1["Draft"] = 0] = "Draft"; 
                PostStatus1[PostStatus1["Unpublished"] = 1] = "Unpublished"; 
                PostStatus1[PostStatus1["Published"] = 2] = "Published"; 
            })(PostStatus1 || (PostStatus1 = {})); */
            
          5. 如果确定代码中不会通过索引器的方式去访问枚举, 我们就使用常量枚举;

            //常量枚举: 就是在 enum 的前面加上 const
            const enum PostStatus3 {
               Draft,
               Unpublished,
               Published
            };
            

            编译后的PostStatus3 的枚举都会被移除掉, 被应用的地方都会被替换为相对应的值, 枚举的名称会注释的状态标注;

      1. 函数类型

        函数的类型约束: 对函数的输入、输出做类型限制;

        1. 通过函数声明定义的函数

          function func1 (a: number, b: number): string {
              return 'func1';
          };
          
          • 调用时参数的个数必须和声明的参数个数一样: 实参和形参个数必须相同,如果需要某个参数是可选的:

            1. 用可选参数: 在类型注解的冒号(😃 前添加问号(?);

            2. 用ES6 参数默认值, 有参数默认值的参数可有可无;

            3. 可选参数或者默认参数, 都必须放在参数列表的最后(原因: 参数会按照位置进行传递, 如果可选参数在必传参数前面, 必传参数不能正常拿到所对应的值);

              //默认参数 //可选参数
              function func2 (a: number = 10, b?: number): string {
                  return 'func2';
              }
              
          • 如果要接收任意个数的参数, 用ES6 的 rest 操作符

            function func3(...rest: number[]): string {
                return 'func3';
            }
            
        2. 函数表达式

          函数表达式最终是放到一个变量中的, 接收这个函数的变量也需要类型注解, 如果该函数作为参数传递时, 可以用箭头函数的形式进行类型注解 -> (a: number, b: number) =>string;

          const func4 = function (a: number, b: number): string {
              return 'func4';
          }
          
    6. 任意类型

      任意类型是弱类型且不安全;

      function stingify (value: any) {
          return JSON.stingify(value);
      }
      stringify('string');//value 可以是字符串类型
      stringify(100); //value 可以是数字类型
      
  • TS隐式类型推断

    定义: 在TS中, 如果我们没有用类型注解去明确标明变量的类型, TS 就会通过这个变量的使用情况, 去推断这个变量的类型;

    let age = 16;//age没有类型注解, 被隐式类型推断为 number 类型;
    /* age = 'string'; */ //这里age重新赋值为 string 类型, 语法上回报错;
    

    TS如果无法推断变量的类型, TS就会把变量做为 any 类型;

    let foo; //foo 会被隐式推断为 any 类型;
    foo = 100; 
    foo = 'string';
    

    建议书写代码时, 为每个变量添加明确的类型注解, 便于更直观的去理解代码;

  • TS类型断言

    在某些特定的环境下, TS无法推断变量的具体类型, 而我们作为开发者, 可以明确知道变量的类型, 开发者就可以为这个变量进行类型断言;

    //假定 nums 来自一个明确的接口
    const nums = [110, 120, 119, 112];
    

cosnt res = nums.find(i => i > 0);//这种情况下, TS无法明确 res 的类型: TS推断出 res 的类型为 -> number | undefined;


上面这种情况, 作为开发者, 就知道 res 一定会返回 number 类型的数据, 所以我们就可为 res 做类型断言;

类型断言有两种方式:

1. 使用 as 关键字:

   ```typescript
   let num1 = res as number;
   ```

2. 使用尖括号(<>): 在被断言的变量前面使用: 这种方式会与 JSX 中的标签产生语法冲突, 建议使用第一种;

   ```typescript
   let num2 = <number>res
   ```

- #### TS接口

- 定义: 用来约定一个对象的结构, 去使用接口就必须遵守接口的全部约定;在TS中, 接口用来约定对象中有哪些成员, 并且约定成员所对应的数据类型;

  ```typescript
  function printPost (Article) {//参数 Article 要求必须要有 title、content 属性, 但是这个要求是隐式的; 这里可以定义一个 Article 接口, 来表现出这个要求;
      console.log(Article.title);
      console.log(Article.content);
  }
  ```

- 声明接口:

  使用 interface 关键字来声明接口, 后面跟上接口名称, 名称后面是一个对象, 对象里面的键值用分号(;)分割

  ```
  interface Article {
  	title: string;
  	content: string;
  };
  ```

  接口中的成员分类:

  1. 普通成员

     ```typescript
     interface DefaultMember {
     	title: string;//title为普通成员
     	content: string;//content 为普通成员
     };
     //接口的使用
     let defaultMember: DefaultMember = {
         title: 'Hello TS',
         content: 'A JavaScript superset'
     };
     ```

  2. 可选成员: 在成员后添加问号(?) -> 表示成员可有可无

     ```typescript
     interface ChoosableMember {
         title: string;
         subTitle?: string;//subTitle 为可选成员
     };
     //接口的使用
     let choosableMember: ChoosableMember = {
         title: 'Hello Ts'// subTitle 可有可无
     };
     ```

  3. 只读成员: 在成员添加 readonly, 只读成员在初始化后就不能修改;

     ```typescript
     interface ReadonlyMember {
         title: string;
         readonly summary: string
     };
     //接口的使用
     let readonlyMember: ReadonlyMember = {
         title: 'Hello Ts',
         summary: 'A JavaScript Superset'
     };
     /* readonlyMember.summary = 'csa'; */ //这里语法报错; 
     ```

  4. 动态成员: 可以向接口中动态添加成员

     ```typescript
     interface Cache {
         [key: string]: string;//Cache 接口内的成员必须是 string 类型的键值;
     };
     //接口的使用
     const cache: Cache = {};
     cache.foo = 'value1';
     cache.bar = 'value2';
     ```


- #### TS 类

- 作用: 描述一类具体事物(方法)的抽象特征;

- 特征: 类的下面还可以细分很多子类: 子类一定会满足父类的所有特征, 然后再多出一些额外的特征;

- 使用: 我们不能去直接使用类, 而是去使用类的一些具体事物(方法);

  1. 在TS中, 需要明确 在类型中去声明类中所拥有的一些属性;

  2. 类中的属性必须要初始化: 可以用等号(=)的方式去赋值一个初始值, 也可以在构造函数 constructor 中动态赋值, 二者必须选其一;

  3. 在类的方法中, 可以使用 this.属性名 访问类中定义的属性;

     ```typescript
     class Person {
         name: string /* = 'lc' */; //name属性 通过等号直接初始化赋值;
         age: number;
         constructor (name: string, age: number) {
             this.name = name;//name属性 在构造函数 constructor 中动态赋值
             this.age = age;
         }
         sayHi (msg: string): void {
             //通过 this.属性名 访问类中定义的属性
             consle.log(`I am ${this.name}, ${this.age}`);
         }
     };
     ```

     

- ###### TS类的访问修饰符

  类中的每一个成员都可以用访问修饰符去修饰;

  1. public 修饰符: 表示被修饰的对象为共有的, 在TS中默认的访问修饰符为public;

  2. private 修饰符: 表示被修饰的对象为私用的, 私有的: 只能在类的内部去访问;

  3. protected 修饰符: 表示被修饰的对象为受保护的, 受保护的: 只能在此类中去访问该对象;

     ```typescript
     class Person {
         public name: string;//public 修饰符: 默认修饰符;
         private age: number;//private 修饰符: 私有的;
         protected gender: boolean;//protected 修饰符: 受保护的;
         
         constructor (name: string, age: number) {
             this.name = name; 
             this.age = age;
             this.gender = true;
         }
         
         sayHi (msg: string): void {
             console.log(`I an ${this.name}, ${msg}`);
             console.log(this.age);//age是私有属性, 只能在类中使用;
         }
     };
     const Tom = new Person('tom', 22);
     console.log(Tom.name);//tom
     console.log(Tom.age);//报错: 私有属性只能在内部使用;
     console.log(Tom.gender);//报错: 受保护的属性只能在此类中使用;
     ```

  4. private 修饰符 和 protected 修饰符的区别: 被 protected 修饰的对象是可以被继承的;

     ```typescript
     //这里的person 为上一个代码段的 Person
     class Student extends Person {
         constructor (name: string, age: number) {
             super(name, age);
             console.log(gender);//true //受保护的属性在此类中使用;
         }
     };
     ```

  5.  构造函数 constructor 的修饰符默认也是 public, 如果构造函数 constructor 被设置为私有的 private , 那么构造函数 constructor 就不能在外面用 new 实例化; 也不能被继承; 这种情况我们只能在类中创建一个静态方法, 让这个静态方法来实现实例化;

     ```typescript
     //这里的person 为上一个代码段的 Person
     class Student1 extends Person {
         private constructor (name: sting, age: num) {//现在的构造函数不能在外面 new 实例化
             super(name, age);
             console.log(this.gender);//true //这里是可以访问的
         }
         //创建一个静态方法来实现实例化
         static create (name: string, age: number) {
             return new Student1(name, age);
         }
     };
     const Jack = new Student1('jack', 19);//报错 //构造函数 constructor 是私有的 private;
     const Jack1 = Student1.create('jack', 19);//通过类中的静态方法实现实例化
     ```

- ###### 类的只读属性

  对于类的属性成员, 除了可以用访问修饰符来修饰, 还可以用 readonly 关键词设置类的属性成员为只读;

  如果类的属性成员已经被访问修饰符修饰, readonly 关键词只能跟在访问修饰符的后面;

  被 readonly 声明的属性, 等号初始化 和 构造函数 constructor 动态赋值只能选其一, 赋值后, 该属性就不允许被修改;

  ```typescript
  class newPerson {
      protected readonly height: number;//readonly 必须跟在protected 后面
      constructor (height: number) {
         this.height = height;//动态赋值 和 初始化赋值 只能选其一;
      }
  };
  ```

- ###### TS类与TS接口

  我们用接口来约束多个类的共同方法的类型, 不做方法的实现;

  ```typescript
  class MyPerson {
      eat (food: string): void {
          console.log(`优雅的进餐: ${food}`);
      }
      run (distance: number) {
          console.log(`直立行走: ${distance}`);
      }
  };
  class Animal {
       eat (food: string): void {
          console.log(`咕噜咕噜的吃: ${food}`);
      }
      run (distance: number) {
          console.log(`爬行: ${distance}`);
      }
  };
  //MyPerson 类和 Animal 类, 有相同的特性(方法), 我们可以用接口来对这两个特性(方法)做约束;
  interface EatAndRun {
      eat (food: string): void;
      run (distance: number): void;
  };
  //类用 implements 来实现接口的约束;
  //上面的代码可以修改为
  class MyPerson1 implements EatAndRun {
      eat (food: string): void {
        console.log(`优雅的进餐: ${food}`);
     }
     run (distance: number): void {
        console.log(`自立行走: ${distance}`);
     }
  };
  ```

- ###### 抽象类

  - 定义: 可以用来约束子类中必须要有某一个成员; 和接口的不同 -> 抽象类可以包含方法的具体实现, 接口不能包含方法的实现;
  - 语法: 声明抽象类: 在class 前面添加 abstract 关键词
  - 特征:
    1. 只能被继承, 不能用 new 实例化;
    2. 必须要用子类去继承抽象类;

  ```typescript
  abstract class NewAnimal {
  	eat (food: string) {
  		console.log(`咕噜咕噜的吃: ${food}`);
  	}
  	//在抽象类中还可以去定义一些抽象方法: 抽象方法需要用 abstract 关键词修饰, 抽象方法没有方法体;
  	abstract run (distance: number): void;
  };
  class Dog extends NewAnimal {
  	//继承了抽象类, 必须实现抽象类中的抽象方法, 不然后报错;
  	run (distance): void {
  		console.log(`四脚爬行: ${distance}`);
  	}
  };
  //抽象类的使用
  const d = new Dog();
  d.eat('大骨头');
  d.run(100);
  ```

- #### TS 泛型

- 定义: 在定义函数、接口、类的时候没有去指定具体的类型, 等到我们具体去使用时才指定类型的这样的特征;

  ```typescript
  function createNumberArray (length: number, value: number): number[] {
      const arr = Array(length).fill(value);
      //由于Array 默认创建的是 any 类型的数组; 这里的 Array 是一个泛型类;
      //这里就需要用泛型参数去传递一个类型去指定 Array(泛型类) 的数据类型;
      const arr1 = Array<number>(length).fill(value);
      return arr1;
  }
  const ResNumberArr = createNumberArray(3, 100);
  console.log(ResNumberArr);//[100, 100, 100]
  //这里的 createNumberArray 只能创建一个 number 类型的数组, 如果要创建一个 string 类型的数组, createNumberArray 做不到;
  ```

- 泛型使用: 在函数名后面去使用一对尖括号(<>), 尖括号内定义泛型参数(一般泛型参数用 T 作为名称), 函数中不明确类型的参数的类型注解就用 T 代替;

  ```typescript
  function createArray<T> (length: number, value: T): T[] {
      const returnArr = Array<T>(length).fill(value);
      return returnArr;
  }
  const stringArr = createArray<string>(3, 'foo');
  console.log(stringArr);//['foo', 'foo', 'foo']
const numberArr = createArray<number>(3, 100);
  console.log(numberArray);//[100, 100, 100]
  ```
  

- #### TS类型声明

在实际项目开发中, 我们会用到第三方的 npm 模块, 而这些模块并不一定是通过 TS 编写的, 所有有些模块所提供的成员就不会有强类型的体验;

```typescript
import { camelCase } from 'lodash';
//使用 camelCase 函数时没有类型注解; 这里可以使用 declare 关键词进行类型声明;
  • 语法: declare + 函数声明(function) + 要声明的函数 + 参数列表 + 函数的类型注解;
declare function camelCase (input: string): string;
//类型声明后 camelCase 就有类型限制了;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值