• 【深入理解Typescript】—— 第一章:为什么要使用Typescript


    1.1 开始使用 Typescript

    在安装 Typescript 之前,需要先安装 npm,然后可以使用命令行来安装 Typescript,如下所示:

    npm install typescript@next
    
    • 1

    npm install typescript@next命令中,@next表示安装 TypeScript 的下一个版本,也就是最新的开发版本或预发布版本。

    通常情况下,npm install typescript会安装最新的稳定版本的 TypeScript。但是,如果我们想尝试最新的功能或修复了一些 bug 的预发布版本,可以使用@next标记来安装这个版本。

    预发布版本通常是在正式发布之前进行测试和反馈的版本。它们可能包含一些新的功能、改进或修复了一些问题,但也可能存在一些不稳定性或兼容性问题。因此,使用@next标记安装预发布版本时,需要注意这些潜在的问题,并确保我们的代码和依赖项与该版本兼容。

    现在局部安装的 Typescript 将是最新版本,而且各种IDE也支持它。例如,我们可以使用 VS Code 通过创建 .vscode/setting.json 来使用这个版本,如下所示:

    {
    	"typescript.tsdk":"./node_modules/typescript/lib"
    }
    
    • 1
    • 2
    • 3

    .vscode/settings.json 文件中设置 "typescript.tsdk":"./node_modules/typescript/lib" 的作用是告诉 Visual Studio Code(VS Code) 使用指定的 TypeScript SDK(软件开发工具包) 路径。

    TypeScript SDK 是一组用于开发和构建 TypeScript 项目的工具和库。当你在 VS Code 中使用 TypeScript 时,它需要知道 TypeScript SDK 的位置,以便提供语法检查、代码补全、类型检查等功能。

    通过在 .vscode/settings.json 文件中设置 "typescript.tsdk" 属性,你可以指定 TypeScript SDK 的路径。在这个例子中,"./node_modules/typescript/lib" 表示 TypeScript SDK 的路径是当前项目node_modules 目录下的 typescript/lib 文件夹。

    这样设置后,VS Code 将使用指定路径下的 TypeScript SDK 来提供相关的功能和工具。

    需要注意的是,这个设置是针对特定项目的,因此它会覆盖全局的 TypeScript SDK 设置。这样可以确保每个项目都使用其自己的 TypeScript SDK 版本,而不会受到全局设置的影响。

    1.2 选择TypeScript的理由

    1.2.1 类型是出色的文档形式之一,函数签名是一个定理,函数体是具体的实现。

    • 类型是出色的文档形式之一:

      类型信息可以帮助我们了解变量、函数参数和返回值的预期数据结构。在阅读和理解代码时,类型信息可以作为一种隐式文档,使我们更容易地推断代码的功能和行为。此外,类型信息还可以在编译时提供类型检查,帮助我们捕获潜在的错误。

      例如,考虑以下函数:

      function add(a: number, b: number): number {
      	 return a + b;
      }
      
      • 1
      • 2
      • 3

      通过查看类型信息,我们可以快速地了解这个函数的功能:它接受两个数字参数 ab,并返回它们的和。这种类型信息可以作为一种简洁、易于理解的文档形式。

    • 函数签名是一个定理:

      函数签名 定义了函数的 输入参数类型返回值类型 以及 可能抛出的异常 。它描述了函数的约束和预期行为,就像数学定理一样。函数签名可以帮助我们确保函数的实现和调用遵循预期的类型约束,从而提高代码的可读性和可维护性。

      例如,考虑以下函数签名:

      type MyFunctionType = (param1: string, param2: number) => boolean;
      
      • 1

      这个函数签名定义了一个定理:存在一个函数,它接受一个字符串参数 param1 和一个数字参数 param2,并返回一个 布尔值。这个定理描述了函数的约束和预期行为。

    • 函数是具体的实现:

      函数体是函数签名(定理)的具体实现。它描述了如何根据输入参数计算返回值。函数体需要遵循函数签名的约束,以确保代码的正确性和一致性。

      例如,考虑以下函数实现:

      const myFunction: MyFunctionType = (param1, param2) => {
      	 return param1.length > param2;
      };
      
      • 1
      • 2
      • 3

      这个函数体实现了之前定义的函数签名(定理)。它根据输入参数 param1param2 计算了一个 布尔值 ,即 param1 的长度是否大于 param2

    总之,类型、函数签名和函数体在编程中起着重要作用。类型信息可以作为一种出色的文档形式,函数签名定义了函数的约束和预期行为(定理),而函数体提供了具体的实现。这些概念有助于提高代码的可读性、可维护性和健壮性。

    1.2.2 鸭子类型是一流的语言结构

    鸭子类型(Duck Typing) 是一种动态类型语言的编程概念,它关注对象的行为,而不是对象的实际类型。换句话说,如果一个对象像鸭子一样走路、叫声,那么我们就可以把它当作鸭子来对待,而不关心它是否真的是鸭子。在鸭子类型的语言中,函数和方法的参数不需要声明具体的类型,而是通过对象的属性和方法来判断它是否符合预期的行为。

    鸭子类型的优势在于它提供了很高的灵活性和可扩展性。由于鸭子类型关注对象的行为,而不是类型本身,这使得我们可以编写更通用、更易于重用的代码。这也是为什么鸭子类型被认为是一流的语言结构。

    以下是一个简单的例子,说明了鸭子类型的概念:

    function makeSound(animal) {
      console.log(animal.sound());
    }
    
    const duck = {
      sound: () => "Quack!",
    };
    
    const dog = {
      sound: () => "Woof!",
    };
    
    makeSound(duck); // 输出 "Quack!"
    makeSound(dog); // 输出 "Woof!"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这个例子中,makeSound 函数接受一个 animal 参数,但我们没有声明它的具体类型。相反,我们依赖于 animal 对象是否具有 sound 方法来判断它是否符合预期的行为。这使得我们可以将不同类型的对象传递给 makeSound 函数,只要它们具有 sound 方法即可。

    需要注意的是,鸭子类型主要适用于动态类型语言(如 JavaScriptPython 等)。在静态类型语言(如 TypeScriptJava 等)中,鸭子类型的概念仍然存在,但通常需要通过接口或其他类型抽象机制来实现。例如,在 TypeScript 中,我们可以使用 接口 来实现类似的效果:

    interface Animal {
      sound(): string;
    }
    
    function makeSound(animal: Animal) {
      console.log(animal.sound());
    }
    
    const duck: Animal = {
      sound: () => "Quack!",
    };
    
    const dog: Animal = {
      sound: () => "Woof!",
    };
    
    makeSound(duck); // 输出 "Quack!"
    makeSound(dog); // 输出 "Woof!"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个例子中,我们定义了一个 Animal 接口,它要求实现 sound 方法。makeSound 函数接受一个 Animal 类型的参数,这使得我们可以传递任何实现了 Animal 接口的对象。尽管 TypeScript 是静态类型的,但通过接口,我们仍然可以实现鸭子类型的灵活性。

    1.2.3 类型可以由环境来定义

    Typescript 的主要目标之一是让人们能够安全、轻松地在 Typescript 中使用现有的 Javascript 库。它可以通过声明来做到这一点。Typescript 提供了一个浮动的标准来衡量你希望在声明中投入多少努力;投入的越多,你获得的类型安全和代码智能提示就越多。

    请注意,大多数流行的 Javascript 库的声明定义已经由 DefinedTyped 社区编写过了,因此,在大多数情况下,声明文件已经存在;或者,至少已经拥有了大量经过深思熟虑的可用的 Typescript 声明目标。

    可以把 jQuery 作为一个编写声明文件的简单示例。在默认情况下,Typescript 要求(正如良好的 Javascript 代码所要求的一样)在使用变量(即在某处使用 var )之前先进行声明。

    Typescript 中,直接编写下面代码会报错:

    $(".awesome").show();  // 错误,找不到$
    
    • 1

    $(".awesome").show(); 是使用 jQuery 库中的 选择器 来选取所有具有 awesome 类名的元素,并将它们显示出来。

    具体作用如下:

    • $ 符号是 jQuery 库的入口函数,它用于选取元素或创建 jQuery 对象。
    • $(".awesome") 使用 CSS 选择器 .awesome 来选取所有具有 awesome 类名的元素。这个选择器会匹配文档中所有类名为 awesome 的元素。
    • .show()jQuery 提供的一个方法,用于显示被选元素。它会将选中的元素设置为可见状态,如果元素之前被隐藏了。

    因此,$(".awesome").show(); 的作用是将所有具有 awesome 类名的元素显示出来,使它们在页面上可见。

    TypeScript 中,$(".awesome").show(); 会报错,因为 TypeScript 是一种强类型语言,它需要知道变量和对象的类型。在这个例子中,$ 是一个全局变量,它代表 jQuery 库。TypeScript 编译器不知道 $ 的类型,因此会抛出一个错误。

    要修复这个错误,需要在 TypeScript 代码中声明 $ 的类型。这可以通过添加 declare var \$: any; 这一行代码来实现。这告诉 TypeScript 编译器,$ 是一个任意类型的变量,因此编译器不会对其进行类型检查。如下所示:

    declare var $: any;
    $(".awesome").show();
    
    • 1
    • 2

    这样修改之后,TypeScript 编译器就可以正确地识别 $ 变量,不再报错。但是,使用 any 类型会导致失去类型安全性,因此在实际项目中,建议使用 @types/jquery 这样的类型定义库,而不是直接使用 any 类型。安装 @types/jquery 后,可以直接使用 $ 变量,而不需要 declare var \$: any; 这一行代码。

    如果你愿意,可以基于此来定义结构,并提供更多信息以保护你免收错误的影响,如下所示:

    declare var $: {
    	(selector: string): any;
    }
    $(".awesome").show();  // 正确;
    $(123).show();         // 错误,selector 必须为 string 类型的
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这段代码中,我们声明了 $ 变量的类型为一个对象,这个对象包含一个函数签名,表示这个函数接受一个 string 类型的参数 selector,并返回 any 类型的值。

    declare var $: {
      (selector: string): any;
    };
    
    • 1
    • 2
    • 3

    接下来,我们调用了 $ 函数两次:

    $(".awesome").show();
    $(123).show();
    
    • 1
    • 2

    第一次调用是正确的,因为我们传递了一个字符串参数 ".awesome"。然而,第二次调用是错误的,因为我们传递了一个数字参数 123。根据我们之前声明的类型,$ 函数只接受一个 string 类型的参数。所以,当我们尝试传递一个 number 类型的参数时,TypeScript 编译器会报错,因为这不符合我们声明的类型约束。

    要修复这个错误,我们需要确保传递给 $ 函数的参数是一个字符串。例如,我们可以将数字转换为字符串,如下所示:

    $(String(123)).show();
    
    • 1

    或者,我们可以修改 $ 函数的类型声明,使其接受 number 类型的参数。但是,在实际的 jQuery 用法中,通常不会使用数字作为选择器,因此这种修改可能并不合适。

    1.2.4 函数签名

    函数签名是一种定义函数类型的语法,它描述了函数的输入参数类型、返回值类型以及可能抛出的异常。在 TypeScript 中,函数签名通常用于定义接口、类型别名或对象类型中的函数属性。函数签名可以帮助我们确保函数的实现和调用遵循预期的类型约束,从而提高代码的可读性和可维护性。

    函数签名的基本语法如下:

    (param1: Type1, param2: Type2, ...): ReturnType;
    
    • 1
    • param1param2 等是函数的参数名;
    • Type1Type2 等是函数参数的类型;
    • ReturnType 是函数的返回值类型;

    以下是一些函数签名的用法示例:

    1. 在接口中定义函数签名:

      interface MyInterface {
        myFunction(param1: string, param2: number): boolean;
      }
      
      • 1
      • 2
      • 3

      这个接口 MyInterface 包含一个名为 myFunction 的函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

    2. 在类型别名中定义函数签名:

      type MyFunctionType = (param1: string, param2: number) => boolean;
      
      • 1

      这个类型别名 MyFunctionType 描述了一个函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

    3. 在对象类型中定义函数签名:

      type MyObjectType = {
        myFunction(param1: string, param2: number): boolean;
      };
      
      • 1
      • 2
      • 3

      这个对象类型 MyObjectType 包含一个名为 myFunction 的函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

    4. 定义可选参数和默认参数:

      type MyFunctionType = (param1: string, param2?: number, param3: boolean = true) => boolean;
      
      • 1

      在这个函数签名中,param2 是一个可选参数,它的类型为 number | undefinedparam3 是一个具有默认值 true 的参数,它的类型为 boolean

    5. 定义剩余参数

      type MyFunctionType = (param1: string, ...restParams: number[]) => boolean;
      
      • 1

      在这个函数签名中,...restParams 表示一个剩余参数,它是一个 number 类型的数组。这意味着该函数可以接受一个 string 类型的参数 param1 和任意数量的 number 类型的参数。

    1.2.5 箭头函数

    TypeScript 的箭头函数 (Arrow Function)ES6(ECMAScript 2015) 引入的一种新的函数语法,它使用 => 符号表示。箭头函数具有更简洁的语法,同时它自动绑定了上层作用域的 this 值,这使得在某些场景下编写函数更加方便。箭头函数在 TypeScript 中的使用和在 JavaScript 中的使用基本相同,只是 TypeScript 会对参数和返回值进行类型检查。

    以下是箭头函数的一些特点和用法:

    1. 简洁的语法:

      箭头函数的语法比传统的函数表达式更简洁。例如,一个简单的箭头函数如下所示:

      const add = (a: number, b: number): number => a + b;
      
      • 1

      这个箭头函数接受两个 number 类型的参数 ab,并返回它们的和。等效的传统函数表达式如下所示:

      const add = function (a: number, b: number): number {
      return a + b;
      };
      
      • 1
      • 2
      • 3
    2. 自动绑定 this 值:

      箭头函数会自动捕获它所在上下文的 this 值。这使得在某些场景下编写函数更加方便,例如在类方法中处理事件回调。例如:

      class MyClass {
        value: number;
      
        constructor(value: number) {
          this.value = value;
        }
      
        onClick() {
          // 使用箭头函数自动捕获 `this` 值
          document.addEventListener("click", () => {
            console.log(this.value);
          });
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      在这个例子中,onClick 方法中的箭头函数自动捕获了 this 值,使得我们可以在事件回调中访问 MyClass 实例的 value 属性。如果使用传统的函数表达式,我们需要手动绑定 this 值,例如通过 bind 方法。

      class MyClass {
        value: number;
      
        constructor(value: number) {
          this.value = value;
        }
      
        onClick() {
          // 使用传统的函数表达式,并通过 `bind` 方法手动绑定 `this` 值
          document.addEventListener("click", function () {
            console.log(this.value);
          }.bind(this));
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      在这个例子中,onClick 方法中的传统函数表达式使用了 bind 方法将 this 值绑定到 MyClass 实例。这使得我们可以在事件回调中访问 MyClass 实例的 value 属性。这种方法在 ES6 引入箭头函数之前,是 JavaScript 中常用的处理 this 值的方法。

    3. 没有 arguments 对象:

      箭头函数没有自己的 arguments 对象。它会访问其外部作用域的 arguments 对象。例如:

      function outerFunction(a: number, b: number) {
        const add = (): number => {
          // 访问外部作用域的 `arguments` 对象
          return arguments[0] + arguments[1];
        };
        return add();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      在这个例子中,箭头函数 add 访问了 outerFunctionarguments 对象。请注意,在 TypeScript 中,arguments 对象的类型安全性较差,因此在实际项目中,建议避免使用 arguments 对象,而是使用具名参数或剩余参数 (...rest)

    总之,TypeScript 的箭头函数提供了更简洁的语法和自动绑定 this 值的特性,使得编写函数更加方便。但请注意,箭头函数并不适用于所有场景,例如在定义类方法或需要使用 prototype 的情况下,仍然需要使用传统的函数表达式或方法定义。

  • 相关阅读:
    windows编译xlnt,获取Excel表里的数据
    Kubernetes学习(一)安装minikube
    GPT-5 一年半后发布?对此你有何期待?
    Ps:锁定图层
    scratch大鱼吃小鱼 电子学会图形化编程scratch等级考试二级真题和答案解析2022年6月
    最小二乘(Least Square)与多项式拟合(fitted polynomial)的理解
    Leetcode-每日一题792. 匹配子序列的单词数(分桶)
    深入理解箭头函数和传统函数的区别
    TD集群内存占用过高
    MongoDB,入门看这一篇足矣!
  • 原文地址:https://blog.csdn.net/qq_37388085/article/details/134541460