TypeScript 语法相关知识汇总(一)

学习TypeScript4这一篇就够了_轻松的小希的博客-CSDN博客

最全的TypeScript学习指南_油墨香^_^的博客-CSDN博客

第一章 TypeScript简介

一、TypeScript简介

        在引入编程社区 20 多年后, JavaScript 现在已成为有史以来应用最广泛的跨平台语言之一。 JavaScript最初是一种用于向网页添加微不足道的交互性的小型脚本语言,现已发展成为各种规模的前端和后端应 用程序的首选语言。虽然用 JavaScript 编写的程序的大小、范围和复杂性呈指数级增长,但 JavaScript 语言表达不同代码单元之间关系的能力却没有。结合 JavaScript 相当奇特的运行时语义,语言和程序复 杂性之间的这种不匹配使得 JavaScript 开发成为一项难以大规模管理的任务。
        程序员编写的最常见的错误类型可以描述为类型错误:在预期不同类型的值的地方使用了某种类型的 值。这可能是由于简单的拼写错误、无法理解库的 API 表面、对运行时行为的错误假设或其他错误。 TypeScript 的目标是成为 JavaScript 程序的静态类型检查器——换句话说,是一个在代码运行之前运行 的工具(静态)并确保程序的类型正确(类型检查)。
        TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这 个语言 添加了可选的静态类型和基于类的面向对象编程
        TypeScript 是一种非常受欢迎的 JavaScript 语言扩展。它在现有的 JavaScript 语法之上加入了一层类型层,而这一层即使被删除,也丝毫不会影响运行时的原有表现。许多人认为 TypeScript "只是一个编译",但更好的理解其实是把 TypeScript 看作两个独立的系统:编译器(即处理语法的部分)和语言工具(即处理与编辑器集成的部分)。通过独立看待这两个系统,就可以得到能够解释我们之前所做决策 的两个重要视角。
        在 npm [3] 上, TypeScript 的下载量每年都在翻倍。截止 2021 12 1 日,它的每周下载量超过为 2200 万次。而在去年 12 月,这一数字约为 1200 万次。它仍保持着高速增长的趋势,没有任何放缓的 迹象。
        从 2.0 版本开始, TypeScript 每两月定期发布一个 release 。但是现在放缓了发布的节奏,改为每三个 月发布一次。其中会花一个月编写新 features 并发布 beta 版本,剩下两个月对 beta 版进行测试和 bug 修复,这使得后续的发布更加稳定。

        TypeScript 是由微软开发的一款开源的编程语言TypeScript 是 Javascript 的超集,遵循最新的 ES6、ES5 规范,TypeScript 扩展了 JavaScript 的语法。TypeScript 更像后端 Java、C#这样的面向对象语言,可以让 JavaScript 开发大型企业项目。谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+ 就是基于 Typescript 语法,最新的 Vue 、React 也可以集成 TypeScript。Nodejs 框架中的 Nestjs、midway 中用的就是 TypeScript 语法。

二、JS ,ES ,TS的关系

1、1995年:JavaScript

        当时的网景公司正凭借其Navigator 浏览器成为 Web 时代开启时最著名的第一代互联网公司。
由于网景公司希望能在静态 HTML 页面上添加一些动态效果,于是 Brendan Eich 在两周之内设计出了 JavaScript 语言。
        为什么起名叫JavaScript ?原因是当时 Java 语言非常红火,所以网景公司希望借 Java 的名气来推广,但事 实上 JavaScript 除了语法上有点像 Java ,其他部分基本上没啥关系。

2、1997年:ECMAScript

        因为网景开发了JavaScript ,一年后微软又模仿 JavaScript 开发了 JScript ,为了让 JavaScript 成为全球标准,几个公司联合 ECMA European Computer Manufacturers Association )(欧洲计算机制造商协 会)组织制定了 JavaScript 语言的标准,被称为 ECMAScript 标准。

3、2015年:TypeScript

        TypeScript 是 JavaScript 的超集,即包含 JavaScript 的所有元素,能运行 JavaScript 的代码,并扩展了JavaScript 的语法。相比于 JavaScript ,它还增加了静态类型、类、模块、接口和类型注解方面的功能, 更易于大项目的开发。 TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators ,以帮助建立健壮的组件。
下图显示了 TypeScript ES5 ES2015+ 之间的关系:

 

 个人感觉它俩之间的关系有点类似 C 和 C++ 之间的关系,语法风格更类似 Java、C# 。

三、TypeScript JavaScript 的区别

四、发现问题

        JavaScript 中的每个值都有一组行为,您可以通过运行不同的操作来观察。这听起来很抽象,我们来举一个简单的例子,考虑我们可能对名为 message 的变量运行的一些操作:
// 在 'message' 上访问属性 'toLowerCase',并调用它
message.toLowerCase();
// 调用 'message'
message();
        如果我们分解它,第一行可运行的代码访问一个属性 toLowerCase ,然后调用它。第二个尝试 message 直接调用。 但是假设我们不知道 message 。这很常见 —— 我们无法可靠地说出尝试运行任何这些代码会得到什么结 果。每个操作的行为完全取决于我们最初给 message 的赋值。
  • 可以调用 message 吗?
  • 它有 toLowerCase 这个属性吗?
  • 如果能, toLowerCase 可以调用吗?
  • 如果这两个值都是可调用的,它们返回什么?
        这些问题的答案通常是我们在编写 JavaScript 时牢记在心的东西,我们必须希望所有细节都正确。假设 message 按以下方式定义:
const message = "Hello World!" ;
        正如您可能猜到的,如果我们尝试运行 message.toLowerCase() ,我们只会得到相同的小写字符串。那第二行代码呢?如果您熟悉 JavaScript ,您就会知道这会失败并出现异常:
TypeError: message is not a function
        如果我们能避免这样的错误,那就太好了。当我们运行我们的代码时,我们的 JavaScript 运行时选择做什么的方式是通过确定值的类型 —— 它具有 什么样的行为和功能。这 TypeError 就是暗指的一部分 - 它说字符串 "Hello World!" 不能作为函数调 用。
        对于某些值,例如基本类型 string number ,我们可以在运行时使用 typeof 运算符识别它们的类型。但是对于函数之类的其他东西,没有相应的运行时机制来识别它们的类型。例如,考虑这个函数:
function fn ( x ) {
        return x . flip ();
}
        我们可以通过阅读代码观察到这个函数只有在给定一个具有可调用 flip 属性的对象时才能工作,但是 JavaScript 并没有以我们可以在代码运行时检查的方式来显示这些信息。在纯 JavaScript 中,告诉 fn 定值做什么的 唯一方法 是调用它并查看会发生什么。这种行为使得在运行之前很难预测代码会做什么, 这意味着在编写代码时更难知道代码会做什么。
        这样看来,类型是描述可以传递给 fn 哪些值会崩溃的概念。 JavaScript 只真正提供动态类型 —— 运行代 码看看会发生什么。
        另一种方法是使用静态类型系统在运行之前预测预期的代码。

五、静态类型检查

        回想一下 TypeError 我们之前尝试将 string 作为函数调用的情况。 大多数人不喜欢在运行他们的代码 时出现任何类型的错误 - 这些被认为是错误!当我们编写新代码时,我们会尽量避免引入新的错误。
        理想情况下,我们可以有一个工具来帮助我们在代码运行之前发现这些错误。这就是像 TypeScript 这样 的静态类型检查器所做的。 静态类型系统描述了当我们运行程序时我们的值的形状和行为。像 TypeScript 这样的类型检查器,告诉我们什么时候事情可能会出轨

六、非异常故障

        到目前为止,我们一直在讨论运行时错误——JavaScript 运行时告诉我们它认为某些东西是无意义的情况。出现这些情况是因为 ECMAScript 规范 明确说明了语言在遇到意外情况时应该如何表现。

      规范说尝试调用不可调用的东西应该抛出错误。也许这听起来像是“明显的行为,但您可以想象访问对象上不存在的属性也应该抛出错误。相反,JavaScript 给了我们不同的行为并返回值

undefined
const user = {
        name : " 小千 " ,
        age : 26 ,
};
user . location ; // 返回 undefined
        最终,静态类型系统要求必须调用哪些代码,应该在其系统中标记,即使它是不会立即抛出错误的“ 有效 ”JavaScript 。比如:在 TypeScript 中,以下代码会产生关于 location 未定义的错误:

TypeScript 可以在我们的程序中捕获很多合法的错误。例如:

错别字

未调用的函数

七、TypeScript安装

1、安装Node.js

1、从Node.js官网官网下载node.js安装包

  • node.js是谷歌开发的,基于Chrome V8引擎,可以在浏览器外部执行JavaScript代码

 下载完成后,双击安装,在Custom Setup阶段,注意确保添加系统环境变量的选项(Add to PATH)是选中的否则后续还需要自行配制

2、环境验证

C:\Users\13476>node -v
v18.12.1
 
C:\Users\13476>npm -v
8.19.2
 
C:\Users\13476>node
Welcome to Node.js v18.12.1.
Type ".help" for more information.
>
(To exit, press Ctrl+C again or Ctrl+D or type .exit)
>
C:\Users\13476>

2、安装TypeScript

打开CMD命令行,输入以下代码:这将使用npm全局安装TypeScript。

  • 全局安装:
npm install -g typescript@4.1.2
  • 项目安装
npm install typescript@<version> --save-dev
//或者
npm i typescript -D

 验证TypeScript是否已成功安装。在命令行中运行以下命令

tsc --version

 八、TypeScript运行原理

        TypeScript是一种静态类型的编程语言,它是JavaScript的超集。TypeScript代码在编写时需要先进行类型注解,这样在编译时可以检查类型错误,从而在运行时减少潜在的错误。

TypeScript的执行原理可以简单概括为以下几个步骤:

  1. 编写TypeScript代码并进行类型注解。
  2. 使用TypeScript编译器将TypeScript代码转换为JavaScript代码。编译器会将类型注解去除,并将TypeScript特有的语法转换为JavaScript语法。
  3. 将生成的JavaScript代码执行。

        TypeScript编译器可以将TypeScript代码编译为多个版本的JavaScript,例如ES3、ES5、ES6等。这取决于您的目标浏览器或运行环境支持的JavaScript版本。在编译时,您可以使用编译器的选项来指定目标JavaScript版本和其他编译选项。

        总的来说,TypeScript的执行原理就是将TypeScript代码转换为JavaScript代码,然后将其执行。这样可以在开发过程中提前发现类型错误和其他潜在的问题,并且生成的JavaScript代码可以在任何支持JavaScript的环境中运行。

1、使用TypeScript编译器 tsc

方式一:

  • 创建一个ts文件,文件名后缀为 ts
  • 进入ts文件所在目录的DOS窗口
  • 执行命令: tsc  文件名.ts

方式二:

直接执行命令: tsc

则会按照tsconfig.json文件中指定目录下的ts文件,或者默认全部的ts文件都进行编译

九、TypeScript项目初始化

1、生成tsconfig.json配置文件

打开CMD命令行,输入以下代码:

mkdir typescript-demo
cd typescript-demo
tsc --init

一定先要使用命令生成tsconfig.json配置文件

tsc --init 命令介绍

   tsc --init是TypeScript编译器命令行工具的一个命令,它用于在当前目录下生成一个tsconfig.json文件,用于配置TypeScript编译器的编译选项。

        当您运行tsc --init命令时,TypeScript编译器会检查当前目录下是否存在tsconfig.json文件。如果不存在,则会生成一个默认的tsconfig.json文件,其中包含一些常用的编译选项、例如"target"、"module"和"outDir"等。您可以使用这些选项来指定目标JavaScript版本、模块类型和编译输出目录等。

        在tsconfig.json文件中,您可以配置编译器的各种选项,例如编译目标版本、模块化选项、输出目录、文件包含和排除选项等。这些选项可以根据您的项目需求进行自定义配置,以实现最佳的编译效果和输出结果。

        总的来说,tsc --init命令用于生成一个tsconfig.json文件,它是TypeScript编译器的配置文件,用于指定编译器的各种选项和配置,以实现最佳的编译效果和输出结果。

1.1、配置文件常用参数介绍

tsconfig.ts文件参数详细解释

/*
	tsconfig.json 是ts文件编译器的配置文件,ts编译器可以根据它的信息来对ts文件进行编译
	路径:**:表示任意目录
		   *:表示任意文件

*/

{
	"include" :[ //指定哪些ts文件需要被编译
		"**/*.ts"  //表示当前目录中任意目录下的任意ts文件
	],
	
	"exclude" : [ //不需要被编译的文件目录,默认为node_modules,bower_components,jspm_packages,outDir
	
	],
	
	"extends" : "目标路径下文件名", //定义被继承的配置文件,将外部文件引入
	
	"files" : [  //用于指定具体的编译文件列表,只要需要编译的文件少时才会用到
	
	],
	
	"compilerOptions" : {  //用于指定编译器的具体选项
	
		// 指定ts被编译后的JS版本
		//'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'.
		"target" : "es6",  //可选  es6就是es2015,esnext就是指es的最新版本
		
		
		//指定模块机制
		//'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext'.
		"module" : "es6",
		
		
		//指定项目中需要使用的库,默认为  “es6” 和 "DOM"
		//'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2019.intl', 'es2020.bigint', 'es2020.date', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2020.number', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'es2022.array', 'es2022.error', 'es2022.intl', 'es2022.object', 'es2022.sharedmemory', 'es2022.string', 'es2022.regexp', 'es2023.array', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref', 'decorators', 'decorators.legacy'
		"lib" : [
			
		],
		
		// 指定编译后的文件js文件所存放的目录,一般为"./dist"
		"outDir" : "./dist ",
	
	
		//默认将全局作用域中代码会合并到指定的js文件中
		"outFile" : "./dist/aa.js",
		
		
		//是否对指定编译文件中除ts文件外的js文件进行编译,默认为false
		"allowJs" : false,
		
		
		//是否检查js代码是否符合语法规范,默认是false
		"checkJs": false,
		
		
		//是否移除ts文件中注释内容,若为true则ts中的注释不会编译到js文件中,默认为false
		"removeComments": true,
		
		
		//不生产编译后的js文件,默认为false
		"noEmit": true, 
		
		
		//当有错误时不生成编译后的js文件,默认为false
		"noEmitOnError": true,
		
		
		//设置编译后的js文件是否使用严格模式,默认为false
		"alwaysStrict": true,  


		//不允许使用隐式的any类型
		"noImplicitAny": true,
		
		
		//不允许使用不明确的this,默认为false,可指定类型,比如windows
		"noImplicitThis": true,  
		
		
		//严格检查是否为空值
		"strictNullChecks": true, 
		
		
		//所有严格检查的总开关,为true后,所有严格检查均打开
		"strict": true,
		
		
		
		"types": [],
		/*
		在 TypeScript 编译器中,types 配置选项用于指定项目中需要包含哪些类型声明文件(.d.ts 文件)。
		类型声明文件是一种特殊的文件,用于描述 JavaScript 代码中的类型信息,例如接口、类型别名、枚举、命名空间等。它们通常用于为第三方 JavaScript 库提供类型定义,以便 TypeScript 编译器能够在编译时进行类型检查和类型推断。

		当使用第三方库时,需要将其类型声明文件包含在项目中以便 TypeScript 编译器能够正确地推断类型。types 配置选项就是用于指定需要包含的类型声明文件的列表。

		types 配置选项有以下几种取值:

		string[]:指定一个字符串数组,其中每个字符串都是一个类型声明文件的相对路径或绝对路径。例如:
		
		{
			"compilerOptions": {
				"types": [
					"node",
					"lodash"
				]
			}
		}
		
		上面的配置指定了需要包含 node 和 lodash 两个库的类型声明文件。

		"all":指定包含所有已安装类型声明文件。这是默认值,如果没有显式指定 types,编译器将包含所有已安装类型声明文件。

		"none":指定不包含任何类型声明文件。

		undefined:与不指定 types 选项的效果相同。

		除了 types 配置选项外,还可以使用 typeRoots 配置选项指定类型声明文件的根目录,以及使用 types 和 exclude 配置选项控制哪些类型声明文件会被包含和排除。
		
		*/
	}
}

参数配置

{
  "include": [ //要解析的ts文件地址,为任意目录下的任意ts文件
    "**/*.ts",
  ],

  "exclude": [ //排除dist的文件不进行编译,因为dist中是js文件
    "./dist",
  ],

  "compilerOptions": {
    
    "target": "ES6",                                  /* 将ts转换成es6 */
    "lib": ["ES6","DOM"],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    
    "module": "ES6",                                /* Specify what module code is generated. */
                               
    "types": ["cypress","node"],                                 /*一定要指定 cypress,否则会找不到模块*/    
    
    "outDir": "./dist",                                   /* Specify an output folder for all emitted files. */
    
    "strict": true,                                      /* Enable all strict type-checking options. */
    
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

2、开发工具配置

开发工具选用: Visual Studio Code,已安装汉化插件

开发工具版本: VSCodeSetup-x64-1.51.1.exe

开发环境版本: node-v14.15.0-x64.msi

开发工具使用:

 

相当于打开监视所有的ts文件,自动编译

对所有ts文件进行监视,自动编译:tsc  -w

对指定ts文件进行监视,自动编译:tsc   文件名.ts   -w

按住快捷键 CTRL + SHIFT + ~ 调出当前项目的终端,我们需要在终端中输入命令,来执行 js目录 中的已经编译好的代码。 

3、运行ts文件

方式一:

切换到编译后js文件所在的文件夹,使用node *.js 命令运行文件

方式二:

ts-node 是一个 TypeScript 运行时库,它允许您直接在 Node.js 环境中运行 TypeScript 代码,而无需提前进行编译。下面是 ts-node 库的使用方法:

1、安装 ts-node 库:在终端中运行以下命令来全局安装 ts-node

npm install --save-dev ts-node

2、运行 TypeScript 文件:在终端中使用 ts-node 命令运行 TypeScript 文件。例如:

npx ts-node yourfile.ts

十、显式类型

       到现在为止,我们还没有告诉 typescript person 或者 date 是什么类型。当我们编辑代码时会告诉 TypeScript person 是一个 string ,那 date 应该是一个 Date 对象。
        有了这个,TypeScript 可以告诉我们其他 greet 可能被错误调用的情况。例如修改一下 hello.ts 码:
function greet ( person : string , date : Date ) {
        console . log ( `Hello ${ person }, today is ${ date . toDateString () }!` );
}

嗯? TypeScript 在我们的第二个参数上报告了一个错误,这是为什么呢?
也许令人惊讶的是, Date() JavaScript 中调用会返回一个 string 。可以使用 new Date() 满足我们 的期望 , 快速修复错误:
function greet ( person : string , date : Date ) {
        console . log ( `Hello ${ person }, today is ${ date . toDateString () }!` );
}
greet ( " 小锋 " , new Date ());

 成功的编译输出了 hello.js

十一、隐式类型

        请记住,我们并不总是必须给变量编写明确的类型注释。在许多情况下,TypeScript 可以为我们自动推 断(或 找出 )类型,即使我们忽略定义这些类型。比如:
        这里没有给 msg 指定具体的类型, typescript 会根据函数的实参来自动推断类型。这是一个特性,当类型系统最终会推断出相同的类型时,最好不要添加类型注释。

十二、擦除类型

让我们来仔细看看,当我们 tsc 编译上面的代码会输出的什么样的 JavaScript ,观察 hello.js
这里要注意两点:
1. 我们的 person date 参数不再有类型注释。
2. 我们的 模板字符串 ” - 使用反引号( ` 字符)的字符串 - 被转换为带有连接 ( + ) 的纯字符串。
稍后会详细介绍第二点,现在让我们专注于第一点。类型注释不是 JavaScript 的一部分(或者说
ECMAScript 是落后的),因此实际上没有任何浏览器,或其他运行时可以不加修改地运行
TypeScript 。这就是 TypeScript 首先需要编译器的原因 —— 它需要某种方式来剥离或转换任何特定于TypeScript 的代码,以便我们可以运行它。大多数 TypeScript 特定的代码都被删除了。
请记住:类型注释永远不会改变程序的运行时行为

 十三、降级编译

与上面的另一个区别是我们的模板字符串是从:
`Hello ${ person }, today is ${ date . toDateString () }!`

到:

"Hello " + person + ", today is " + date . toDateString () + "!" ;
为什么会这样?
        模板字符串是 ECMAScript 版本的一个特性,称为 ECMAScript 2015 (又名 ECMAScript 6 ES2015 、ES6 等)。 TypeScript 能够将代码从较新版本的 ECMAScript 重写为旧版本,例如 ECMAScript 3 ECMAScript 5 (又名 ES3 ES5 )。这种从更新或 更高 版本的 ECMAScript ,向下移动到旧版本或 版本的过程有时称为降级。
        默认情况下,TypeScript ES3 为目标,这是一个非常旧的 ECMAScript 版本。通过使用 target 项,我们可以选择更新一些的内容。运行 -- target es2015 TypeScript 以针对 ECMAScript 2015 进行更 改,这意味着代码应该能够在支持 ECMAScript 2015 的任何地方运行。
所以运行 tsc -- target es2015 hello.ts 会给我们以下输出:
        虽然默认目标是 ES3 ,但当前绝大多数浏览器都支持 ES2015 。因此,大多数开发人员可以安全地 ES2015 或更高版本指定为目标,除非考虑与某些旧浏览器的兼容性。

十四、严格模式

        不同的用户使用 TypeScript 在类型检查器中,希望检查的严格程度不同。有些人正在寻找更宽松的验证体验,它可以帮助仅验证其程序的某些部分,并且仍然拥有不错的工具。这是 TypeScript 的默认体验, 其中类型是可选的,推理采用最宽松的类型,并且不检查潜在的 null / undefine 值,就像 tsc 面对错 误时如何编译生成 JS 文件一样。如果你要迁移现有的 JavaScript ,这可能是理想的第一步。
        相比之下,许多用户更喜欢让 TypeScript 尽可能多地立即验证,这就是该语言也提供严格性设置的原因。这些严格性设置将静态类型检查,从开关(无论您的代码是否被检查)转变为更接近于拨号的东 西。你把这个拨盘调得越远, TypeScript 就会为你检查越多。这可能需要一些额外的工作,但总的来 说,从长远来看,它是物有所值的,并且可以实现更彻底的检查和更准确的工具。如果可能,新的代码 库应该始终打开这些严格性检查。
        TypeScript 有几个可以打开或关闭的类型检查严格标志,除非另有说明,否则我们所有的示例都将在启用所有这些标志的情况下编写。在命令行里设置 strict ,或在 tsconfig.json 中配置 "strict": true 将它打开

 

以上两个案例错误是因为我们配置了 -- strict true 。同时我们可以单独选择配置它们。我们应该知道的最典型的两个是 noImplicitAny strictNullChecks

1、noImplicitAny

        回想一下,在某些地方,TypeScript 不会尝试为我们推断类型,而是退回到最宽松的类型: any 。这并 不是可能发生的最糟糕的事情 —— 毕竟, any 无论如何,都能退回到普通的 JavaScript 体验。
        但是,使用 any 通常首先会破坏使用 TypeScript 的目的。你的程序类型越多,你获得的验证和工具就越 多,这意味着你在编写代码时会遇到更少的错误。打开该 noImplicitAny 标志将对类型隐式推断为,当 任何变量发出错误时都应用 any 类型。

2、strictNullChecks

        默认情况下,值为 null undefined 可分配给任何其他类型。这可以使编写一些代码更容易,但忘记处理 null 并且 undefined 是你代码无数错误的元凶 - 有些人认为这是一个 十亿美元的错误 !该 strictNullChecks 标志,使得操作 null undefined 更加明确,它使我们不用担心是否忘记处理 null undefined

第二章 TypeScript数据类型

        在 TypeScript 中,有基本数据类型和引用数据类型两种不同的数据类型。 是在JavaScript的基础上进行拓展

一、基本数据类型分类

在 TypeScript 中,基本数据类型包括:

  • number:表示数字类型,包括整数和浮点数。
  • string:表示字符串类型,用单引号或双引号括起来。
  • boolean:表示布尔类型,只有 true 和 false 两个值。
  • null:表示空值,只有一个值 null。
  • undefined:表示未定义的值,只有一个值 undefined。
  • symbol:表示唯一的、不可变的值。

二、引用数据类型分类

引用数据类型包括:

  • 数组(Array):数组是一组相同类型的元素的集合
  • 元组(Tuple):元组是一组已知长度和类型的数组
  • 对象(Object):表示一组键值对,键是字符串类型,值可以是任意类型。
  • 函数(Function):表示一个可执行的代码块,可以接受参数并返回值。
  • 类(Class):表示一种面向对象的编程范式,可以创建对象和继承。
  • 接口(Interface):表示一个抽象的数据结构,定义了对象的属性和方法。

        需要注意的是,在 TypeScript 中,基本数据类型和引用数据类型的使用方式略有不同,需要根据具体的情况选择使用哪种类型。

params: object 表示一个对象类型,它是 TypeScript 中的基本类型之一。

object 类型表示一个普通的 JavaScript 对象,它可以包含多个键值对(属性和对应的值)。这个类型不限制对象的具体结构,可以包含任意类型的属性和任意数量的属性。

以下是一个示例,展示了使用 object 类型的参数:

function processObject(params: object) {
  // 处理参数对象的逻辑
  console.log(params);
}

// 使用 object 类型作为参数
processObject({ foo: 'bar', baz: 42 });  // 输出: { foo: 'bar', baz: 42 }

const myParams = { name: 'John', age: 25 };
processObject(myParams);  // 输出: { name: 'John', age: 25 }

在上述示例中,processObject 函数接受一个 params 参数,其类型为 object。这意味着该参数可以接收任何类型的对象作为输入。

在函数内部,我们可以使用 params 对象进行进一步的处理和操作。在示例中,我们简单地将参数对象打印到控制台。

需要注意的是,由于 object 类型并没有指定对象的具体结构,因此在使用 params 对象的属性时,需要注意确保属性存在或进行相应的类型检查,以避免运行时错误。

三、特殊数据类型分类

  • 枚举(Enum):枚举是一种数据类型,它允许为一组数值赋予友好的名称
    • 基本数据类型,是 TypeScript 中的一种特殊类型,用于定义一组有名字的常量集合。它们在运行时被表示为 JavaScript 对象。
  • any:表示任意数据类型,可以赋值给指定数据类型的变量
    • 可为基本数据类型或引用数据类型,它可以被用来表示任何类型的值,包括原始类型和对象类型。它可以被视为一种“万能类型”,因为它可以接受任何类型的值。使用 any 类型可能会导致类型安全性的下降,因为 TypeScript 编译器不会对 any 类型进行类型检查。
  • unknown:表示任意数据类型,不可以赋值给指定数据类型的变量
    • 可为基本数据类型或引用数据类型,它类似于 any 类型,但与 any 类型不同,对 unknown 类型的值进行操作时,必须先进行类型检查或类型断言。使用 unknown 类型可以提高代码的类型安全性。
  • void:void 表示没有返回值的函数
    • 基本数据类型,函数没有返回值,或者说函数返回的值是 undefined。它也可以被用作变量的类型,表示该变量没有任何值
  • never:never 表示那些永远不会出现的值。例如,一个抛出异常或者无限循环的函数的返回类型为 never
    • 基本数据类型,表示从不会出现的值。它通常作为函数的返回类型,表示该函数抛出异常或永远不会返回任何值。never 类型也可以用于类型推断,例如在泛型中使用
  • 直接使用字面值作为数据类型
    • 基本数据类型

四、可变性分析

        基本数据类型全都是不可变的,引用数据类型全都是可变的,特殊数据类型有的是可变的,有的是不可变的

在 TypeScript 中,基本数据类型是不可变的,而引用数据类型是可变的。

基本数据类型在赋值时会创建一个新的值,修改一个基本数据类型的值只能通过重新赋值来实现,原来的值不会被修改。例如:

let x: number = 10;
x = 20; // x 的值被重新赋值为 20,原来的值 10 没有被修改

而引用数据类型则是指向对象内存地址的引用,修改对象的属性或方法会改变对象本身的值,因此引用数据类型是可变的。例如:

let arr: number[] = [1, 2, 3];
arr.push(4); // arr 被修改为 [1, 2, 3, 4]

需要注意的是,对于引用数据类型的赋值和传递参数,只是将引用拷贝了一份,指向的对象内存地址是相同的,因此修改被拷贝的引用变量也会影响原来的对象。例如:

let obj1 = { a: 1 };
let obj2 = obj1; // obj2 和 obj1 指向同一个对象内存地址

obj2.a = 2; // 修改 obj2 的属性 a 也会影响 obj1
console.log(obj1.a); // 输出 2

五、基本数据类型

        类型名称 String , Number , Boolean (以大写字母开头)是合法的,但指的是一些很少出现在 代码中的特殊内置类型。对于类型,始终使用 string , number , boolean

1、数字类型(基元类型 number)

        number 表示数字值,如 42 JavaScript 没有一个特殊的整数运行时值,所以没有等价于 int 或 float 类型, 一切都只是 number

整数型:

let num: number = 123;
console.log(num);

 浮点型:

let num: number = 3.1415926;
console.log(num);

2、字符串类型(基元类型 string)

string 表示字符串值,如 "Hello, world"
let str: string = "Hello,TypeScript";
console.log(str);

3、布尔类型(基元类型 boolean)

boolean 只有两个值 true false
let flag: boolean = true;
console.log(flag);

4、null类型

let n: null = null;

5、undefined类型

let u: undefined = undefined;

六、引用数据类型

1、数组类型

1、数组中的元素类型一般保持一致

2、数组的长度可变

//表示字符串数组,数组里面全是字符串

let e : string[];

//表示数值数组,数组里面元素全是数值

let f : number[];

let g : Array<number>;

//表示数组里面可存任意类型数据

let h : Array<any>;

第一种定义数组的方式:以数字类型数组为例

let arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
console.log(arr);

第二种定义数组的方式:以数字类型数组为例

let arr: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
console.log(arr);

2、元组类型

1、元组属于数组的一种,元组中的元素可以不必全部保持类型一致

2、元祖的长度固定不变

3、语法:【元素数据类型,......】

        元素数量及数据类型必须和声明一致

let user: [number, string];
let userId = 10086;
let userName = "Nick";
let randomBoolean = true;

user = [userId, userName];      // 正确
user = [userId, randomBoolean]; // 错误

3、函数

        函数是在 JavaScript 中传递数据的主要方式。 TypeScript 允许您指定函数的输入和输出值的类型。
  • 参数类型注释
        声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。参数类型注释位于参数名称之后:
// 参数类型定义
function greet ( name : string) {
        console . log ( "Hello, " + name . toUpperCase () + "!!" );
}

当参数具有类型注释时,将检查该函数的参数:

// 如果执行,将是一个运行时错误!
greet ( 42 );

即使您的参数上没有类型注释,TypeScript 仍会检查您是否传递了正确数量的参数

  • 返回类型注释 
你还可以添加返回类型注释。返回类型注释出现在参数列表之后:
function getFavoriteNumber (): number {
        return 26 ;
}
        与变量类型注释非常相似,通常不需要返回类型注释,因为 TypeScript 会根据其 return 语句推断函数 的返回类型。上面例子中的类型注释不会改变任何东西。某些代码库会出于文档目的明确指定返回类 型,以防止意外更改或仅出于个人偏好。
  • 匿名函数
        匿名函数与函数声明有点不同。当一个函数出现在 TypeScript 可以确定它将如何被调用的地方时,该函数的参数会自动指定类型。
下面是一个例子:
// 这里没有类型注释,但是 TypeScript 可以发现错误
const names = [ "Alice" , "Bob" , "Eve" ];
// 函数上下文类型
names . forEach ( function ( s ) {
        console . log ( s . toUppercase ());
});
// 上下文类型也适用于箭头函数
names . forEach (( s ) => {
        console . log ( s . toUppercase ());
})
        即使参数 s 没有类型注释, TypeScript 也会使用 forEach 函数的类型,以及数组的推断类型来确定 s 类型。
        这个过程称为上下文类型,因为函数发生在其中的上下文通知它应该具有什么类型。 与推理规则类似,你不需要明确了解这是如何发生的,但了解它的机制确实可以帮助你注意何时不需要
类型注释。稍后,我们将看到更多关于值出现的上下文如何影响其类型的示例。

4、对象类型

        除了 string number boolean 类型(又称基元类型)外,你将遇到的最常见的类型是对象类型。 这指的是任何带有属性的 JavaScript 值,几乎是所有属性!要定义对象类型,我们只需列出其属性及其 类型。
例如,这是一个接受点状对象的函数:
对象类型作为函数参数时,必须和声明的一致
// 参数的类型注释是对象类型
function printCoord ( pt :  { x : number; y : number }) {
        console . log ( " 坐标的 x 值为: " + pt . x );
        console . log ( " 坐标的 y 值为: " + pt . y );
}
printCoord ({ x : 3 , y : 7 });
在函数外声明一个对象,传入的情况可不一致

function printCoord(pt: { x: number; y: number }) {

    console.log("坐标的x值为: " + pt.x);

    console.log("坐标的y值为: " + pt.y);

}

let obj = { x: 3, y: 7 , z: 8}

printCoord(obj);

        在这里,我们使用具有两个属性的类型注释参数 - x y - 这两个属性都是 number 类型。你可以以使用 , ; 来分隔属性,最后一个分隔符是可选的。
        每个属性的类型部分也是可选的。如果你不指定类型,则将假定为 any

4.1、可选属性 

        对象类型还可以指定其部分或全部属性是可选的。为此,请在属性名称后添加一个 ?
function printName ( obj : { first : string; last ? : string }) {
// ...
}
// 两种传递参数都可以
printName ({ first : "Felix" });
printName ({ first : "Felix" , last : "Lu" });
        在 JavaScript 中,如果访问一个不存在的属性,将获得值 undefined 而不是运行时错误。因此,当你读取可选属性时,必须使用它之前用 undefined 进行检查。
function printName ( obj : { first : string; last ? : string }) {
        // 错误 - 'obj.last' 可能不存在 !
        console . log ( obj . last . toUpperCase ());
        if ( obj . last !== undefined ) {
                // 这样可以
                console . log ( obj . last . toUpperCase ());
        }
        // 使用现代 JavaScript 语法的安全替代方案:
        console . log ( obj . last ?. toUpperCase ());
}

4.2、索引签名

1、索引签名(Index Signature)是TypeScript中的一种语法,用于定义对象类型中的索引类型。

2、可理解为将对象看成字符串,对象的属性像是字符串中的索引,属性的值就是索引对应的值

        在对象类型中,通常使用点符号(.)来访问属性,如 obj.property。然而,有时我们需要使用变量或动态的字符串作为属性名来访问对象的属性。这就是索引签名的作用所在。

索引签名允许我们定义一个接受特定类型的键,并返回对应类型值的索引类型。它的语法如下:

{[indexName: KeyType]: valueType} 

其中:

  • indexName:是一个占位符,你可以使用任何有效的标识符来代替它。这个标识符表示对象中的属性名,它的类型为字符串。
  • keyType 表示索引的键的类型。它可以是字符串类型 (string) 或数字类型 (number)。
  • valueType 表示索引的值的类型。

        这样的类型声明允许你在对象中拥有任意数量的属性,并且属性名必须是字符串类型,而属性值也必须是字符串类型。

        索引签名允许我们按照索引的键的类型来访问和操作对象的属性。当我们使用索引访问对象属性时,TypeScript 会根据索引签名的定义来推断和验证类型。

以下是一个使用字符串索引签名的示例:

let obj: {[key: string]: number} = {
  "a": 1,
  "b": 2,
  "c": 3
};

console.log(obj["a"]); // 输出: 1
console.log(obj["b"]); // 输出: 2
console.log(obj["c"]); // 输出: 3

        在上面的例子中,obj 是一个对象,它的属性名是字符串类型,属性值是数字类型。我们可以使用字符串索引来访问对象的属性。

        总而言之,索引签名允许我们以动态方式访问对象的属性,使我们能够定义具有任意数量和类型的属性的对象类型。

5、接口

一个接口声明是另一种方式来命名对象类型:
interface Point {
    x: number;
    y: number;
}
function printCoord(pt: Point) {
    console.log("坐标x的值是: " + pt.x);
    console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 });
        就像我们在上面使用类型别名时一样,该示例就像我们使用了匿名对象类型一样工作。TypeScript 只关 心我们传递给的值的结构 printCoord —— 它只关心它是否具有预期的属性。只关心类型的结构和功 能,是我们将 TypeScript 称为结构类型类型系统的原因。

七、特殊数据类型

1、枚举类型

枚举类型的介绍:

        随着计算机的不断普及,程序不仅只用于数值计算,还更广泛地用于处理非数值的数据。枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数 TypeScript 功能不同,这不是JavaScript 的类型级别的添加,而是添加到语言和运行时的内容。因此,你确定你确实需要枚举在做些事情,否则请不要使用。

        例如:性别、月份、星期几、颜色、单位名、学历、职业等,都不是数值数据。

        在其它程序设计语言中,一般用一个数值来代表某一状态,这种处理方法不直观,易读性差。

        如果能在程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。

        也就是说,事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。

1.1、枚举类型的定义:

enum 枚举名 {

        标识符[= 整型常数/字符串],

        标识符[= 整型常数/字符串],

        ...

        标识符[= 整型常数/字符串],

 };

枚举类型的示例:

enum Flag {
    success,
    error,
    overtime
};

let s: Flag = Flag.overtime;
console.log(s);//2

代码解读:如果标识符没有赋值,它的值就是下标。

enum Flag {
    success = 200,
    error = 404,
    overtime = 500
};

let s: Flag = Flag.overtime;
console.log(s);//500

代码解读:如果标识符已经赋值,它的值就是被赋的值。

enum Flag {
    success,
    error = 100,
    overtime
};

let s: Flag = Flag.overtime;
console.log(s);//101

代码解读:如果标识符没有赋值,它的值就是下标,如果从中间突然指定了一个值,那么它之后的值都会从当前值开始重新计算。

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

let d: Direction = Direction.Up;
console.log(d);//UP
enum Gender {
    Male = 0,
    Female = 1
}

let j : {name: string,gender: Gender};

j = {
    name: 'jzq',
    gender: Gender.Male
}

console.log(j.gender === Gender.Male);

2、any类型

1、TypeScript 中的 any 类型表示任意数据类型。一个变量设置类型为any,相当于该变量关闭了TS的类型检测

2、如果不设置数据类型,且声明时不初始化赋值,则默认为any类型

3、当一个值的类型是 any 时,可以访问它的任何属性,将它分配给任何类型的值,或者几乎任何其他语法上的东西都合法的:

enum Flag {
    success,
    error,
    overtime
};

let flag: any = true;//布尔型
let num: any = 123;//数字型
let str: any = 'Hello,TypeScript';//字符型
let arr: any = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];//数组型
let tuple: any = [10086, 'Nick'];//元组型
let e: any = Flag.success;//枚举型
let n: any = null;//null型
let u: any = undefined;//undefined型

3、unknown类型

表示任意的数据类型,与any的区别在于unknown类型的变量不能赋值给指定数据类型的变量,而any可以

let s : any;
s = 10;
s = "hello";
s = true;

let c : unknown;
c = 10;
c = "hello";
c = true;

let str : string;
//d的类型是any,可以赋值给任意数据
str = s;

//虽然c的值是字符串,但是其数据类型为unknown,不能赋值给string类型
c = 'abc';
// str = c;

//解决办法1
if(typeof c === "string"){
    str = c;
}

//解决办法2
//数据类型转换
str = c as string;

str = <string> c;

4、void类型

1、TypeScript 中的 void 类型表示空,没有任何类型,一般用于定义方法的时候方法没有返回值。

2、一般如果没有return语句,则默认为void,void包括undefined,不包括null

function success(): void {
    console.log('执行成功了,我不需要返回值');
}

4、never类型

        TypeScript 中的 never 类型是任何类型的子类型,(实际上,其他类型不能直接分配给 never 类型,因为 never 是一个底部类型(bottom type),它表示不可达的代码路径或无法返回的函数)也可以赋值给任何类型,但是没有类型是 never 的子类型或可以赋值给 never 类型, 即使 any 类型也不可以赋值给never。这意味着声明 never 类型的变量只能被 never 类型所赋值。

        never类型也表示返回为空,比void还强烈,连void也不返回,一般用于抛出异常

1、never 类型表示永远不会发生的类型。它表示一个不可达的代码路径或一个无法返回的函数。当一个函数抛出异常或永远不会返回时,它的返回类型可以被推断为 never

2、在 TypeScript 中,'never' 类型表示永远不会发生的类型。它用于表示不可达的代码路径或无法返回的函数。在类型系统中,'never' 是所有类型的子类型,因此它可以分配给其他类型,但其他类型不能分配给 'never'。

例如,考虑下面的代码片段:

function throwError(): never {
  throw new Error('An error occurred');
}

let result: never;
result = throwError(); // 正确,throwError 返回类型为 never,可以赋值给 never 类型的变量

let str: string = 'Hello';
result = str; // 错误,不能将类型 'string' 分配给类型 'never'

        在这个例子中,我们有一个函数 throwError,它的返回类型被标注为 'never',因为该函数永远不会正常返回,而是抛出一个错误。我们可以将 throwError 的返回值赋值给一个类型为 'never' 的变量 result,因为 throwError 的返回值类型与 result 的类型兼容。

        然而,当我们尝试将一个具体的类型(例如 'string')赋值给类型为 'never' 的变量 result 时,TypeScript 编译器会报错,因为 'string' 类型不是 'never' 类型的子类型。

        因此,要解决这个错误,你需要确保将具体的类型分配给适当的类型,或者重新考虑代码逻辑以避免将 'string' 类型分配给 'never' 类型的变量。

3、永远不会返回的函数:

function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // 无限循环
  }
}

5、直接使用字面值进行类型声明

相当于常量,该变量的值只能是这个字面值,不能为其他值

//直接使用字面值进行类型声明

let a : 10;
a = 10;
//只能为10,不能为其他值
a = 11;

let b1 : "male" | "female";
b1 = "male";
b1 = "female";
b1 = "hello";

6、文字类型

除了一般类型 string number ,我们可以在类型位置引用特定的字符串和数字。
一种方法是考虑 JavaScript 如何以不同的方式声明变量。 var let 两者都允许更改变量中保存的内容, const 不允许,这反映在 TypeScript 如何为文字创建类型上。
就其本身而言,文字类型并不是很有价值:
let x : "hello" = "hello" ;
// 正确
x = "hello" ;
// 错误
x = "howdy" ;

拥有一个只能有一个值的变量并没有多大用处!但是通过将文字组合成联合,你可以表达一个更有用的概念 —— 例如,只接受一组特定已知值的函数:
function printText ( s : string, alignment : "left" | "right" | "center" ) {
        // ...
}
printText ( "Hello, world" , "left" );
printText ( "G'day, mate" , "centre" );

数字文字类型的工作方式相同:
function compare ( a : string, b : string): - 1 | 0 | 1 {
        return a === b ? 0 : a > b ? 1 : - 1 ;
}
当然,你可以将这些与非文字类型结合使用:
interface Options {
        width : number;
}
function configure ( x : Options | "auto" ) {
        // ...
}
configure ({ width : 100 });
configure ( "auto" );
configure ( "automatic" );
        还有一种文字类型:布尔文字。只有两种布尔文字类型,它们是类型 true false 。类型 boolean 身实际上只是联合类型 union 的别名 true | false

6.1、文字推理

        当你使用对象初始化变量时,TypeScript 假定该对象的属性稍后可能会更改值。例如,如果你写了这样的代码
const obj = { counter : 0 };
if ( someCondition ) {
        obj . counter = 1 ;
}
TypeScript 不假定先前具有的字段值 0 ,后又分配 1 是错误的。另一种说法是 obj.counter 必须有
number 属性, 而非是 0 ,因为类型用于确定读取和写入行为。
这同样适用于字符串:
function handleRequest ( url : string, method : 'GET' | 'POST' | 'GUESS' ) {
        // ...
}
const req = { url : 'https://example.com' , method : 'GET' };
handleRequest ( req . url , req . method );
在上面的例子 req.method 中推断是 string ,不是 "GET" 。因为代码可以在创建 req 和调用之间进行评估, TypeScript 认为这段代码有错误。
有两种方法可以解决这个问题。
1. 可以通过在任一位置添加类型断言来更改推理
// 方案 1:
const req = { url : "https://example.com" , method : "GET" as "GET" };
// 方案 2
handleRequest ( req . url , req . method as "GET" );
方案 1 表示 我打算 req.method 始终拥有文字类型 "GET" ,从而防止之后可能分配 "GUESS" 给该字段。
方案 2 的意思是 我知道其他原因 req.method 具有 "GET"
2. 可以使用 as const 将整个对象转换为类型文字
const req = { url : "https://example.com" , method : "GET" } as const ;
handleRequest ( req . url , req . method );
as const 后缀就像 const 定义,确保所有属性分配的文本类型,而不是一个更一般的 string
number

第三章 数据类型声明

        在 TypeScript 中,类型声明是指为一个变量、常量、参数等指定其类型。类型声明中所声明的类型,是该变量的值应该拥有的类型,而不是该变量名所对应的类型。

 例如,以下代码中的类型声明 number 表示变量 x 应该存储一个数字类型的值:

let x: number = 10;

        这意味着,变量名 x 本身并没有类型,它只是一个标识符,用于引用存储在该变量中的值。而类型声明 number 是告诉 TypeScript 编译器,在该变量中应该存储一个数字类型的值。

        因此,在 TypeScript 中,类型声明是为了让编译器知道变量应该存储哪种类型的值,以便在编译时检查代码是否符合类型规则。这样可以减少类型错误的发生,提高代码的可靠性。

        可以理解为对象的键是一个变量名,但是它与普通变量名的不同之处在于,它是用于访问对象属性的字符串或符号类型的名称,而不是用于存储变量值的标识符。在 JavaScript/TypeScript 中,变量名和对象的键都是标识符,但是它们所代表的含义不同。因此,虽然对象的键可以理解为一种变量名,但是它与普通变量名有很大的区别。对象的键必须是字符串类型或符号类型。

一、变量的值数据类型声明

1、基本格式

变量格式一:

let 变量名: 变量类型 = 初始化值;

如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测,以首次赋值的类型为准 

变量格式二:

let 变量名: 变量类型 ;

变量名 = 变量值; 

如果变量的声明和赋值是分开进行的,则须在声明时指定类型,否则为any,为隐式声明为any

二、函数/方法的数据类型声明

1、函数声明类型

function  函数名(形参1:数据类型,形参2:数据类型 ...):返回值数据类型  {

        函数体;

}

2、匿名函数类型

const  变量名 = function(形参1:数据类型,形参2:数据类型 ...):返回值数据类型  {

        函数体;

}

3、箭头函数类型

(形参:形参类型,......)=>返回值类型

  • 函数结构必须和声明类型一致
let d : (a:number,b:number) => number;

d = function(n1,n2): number{
    return n1 + n2;
}

console.log(d(1,2));

        在 TypeScript 中,箭头函数 (selectedOptionText: string) => void 和冒号函数 (selectedOptionText: string): void 都可以用来定义函数类型。它们的含义是相同的,指定了函数的参数类型和返回类型。

        无论使用箭头函数 (selectedOptionText: string) => void 还是冒号函数 (selectedOptionText: string): void,它们都可以在接口中定义回调函数的类型,具体选择哪种写法取决于个人喜好和团队的编码风格。

三、组合数据类型格式

1、针对单数据类型 或 组合

TypeScript 中支持一个变量可以赋予多种不同的变量类型,多个变量类型使用 | 分隔。

let num: number | null | undefined;

num = 3;
console.log(num);

num = null;
console.log(num);

num = undefined;
console.log(num);

特殊情况:

        如果联合的每个成员都有效,TypeScript 将只允许你使用联合做一些事情。例如,如果你有联合类型string | number ,则不能只使用一种类型的操作,比如 string
function printId ( id : number | string) {
        console . log ( id . toUpperCase ());
}

        解决方案是用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。 当 TypeScript 可以根据代码 结构为值推断出更具体的类型时,就会发生缩小。
例如, TypeScript 知道只有一个 string 值才会有一个 typeof "string"
function printId ( id : number | string) {
        if ( typeof id === "string" ) {
                // 在此分支中,id 的类型为 “string”
                console . log ( id . toUpperCase ());
        } else {
                // 此处,id 的类型为 “number”
                console . log ( id );
        }
}

 另一个例子是使用如下函数 Array.isArray

function welcomePeople ( x : string[] | string) {
        if ( Array . isArray ( x )) {
                // 此处: 'x' 的类型是 'string[]'
                console . log ( "Hello, " + x . join ( " and " ));
        } else {
                // 此处: 'x' 的类型是 'string'
                console . log ( "Welcome lone traveler " + x );
        }
}
        请注意,在 else 分支中,我们不需要做任何特别的事情 —— 如果 x 不是 string[] ,那么它一定是 string
        有时你会有一个 union ,所有成员都有一些共同点。例如,数组和字符串都有一个 slice 方法。如果联 合中的每个成员都有一个共同的属性,则可以使用该属性而不会缩小范围:
// 返回类型推断为 number[] | string
function getFirstThree ( x : number[] | string) {
        return x . slice ( 0 , 3 );
}

2、针对普通object类型组合

语法:

1、{属性名:属性值类型,属性名:属性值类型,...} 

  •  变量格式必须和声明格式一致

2、{属性名:属性值类型,属性名?:属性值类型,...} 

  • ?表示可选,加?的属性名及其对应的值可省略

3、{属性名:属性值类型,【属性名:属性名类型】:any,...} 

  • 部分属性名及其值须和声明格式一致,其余均可选
  • 在这段代码中,myType 类型使用了索引签名 [propName:string] : any,表示这个类型可以拥有任意数量和名称的属性,这些属性的名称是字符串类型,值的类型是 any

    当你使用 myType 类型来声明一个变量 obj1 时,你必须提供一个名为 name 的字符串类型属性,因为在类型声明中,属性名 name 被硬编码成了必须存在的属性。但是,对于其他任意属性,你可以任意添加、删除或修改它们的名称和值,因为类型声明中使用的索引签名允许你这样做。

    因此,当你创建 obj1 对象时,你可以添加 ageid 或任何其他属性,它们的类型可以是任何类型,因为类型声明中使用的索引签名允许你这样做。这样设计的目的是让这个类型变得更加灵活,适用于需要存储任意数量和名称属性的场景。

4、{属性名:属性值类型,......}  &  {属性名:属性值类型,......}

  • 对象数据类型 与 连接
let a : {name : string,age : number}
//必须和声明数据格式一致
a = {name: "jzq",age: 27}

let b : {name : string,age? : number}
//age属性为可选
b = {name: "jzq"}

let c : {name : string,[propName:string] : string}
//name属性必须一致,后面的属性可选,但必须保证前面的string类型和后面的相匹配
c = {name: "jzq",jzq:'sa'}

//& 逻辑与连接
let k : {name: string} & {age: number};
k = {name:'jzq',age: 27}

四、type关键字声明类型

语法:

type  数据类型别名  =  数据类型格式;

type myType = 1 | 2 | 3 | 4;
let l : myType;
let m : myType;
l = 1;
// m = 10;  不行

1、type关键字的特点

1.1、type关键字不可重复声明相同的类型别名

1.2、type关键字可重复声明同一类型的不同别名

type UserInputSanitizedString = string;
type a = string;
function sanitizeInput(str: a): UserInputSanitizedString {
return str.slice(0, 2)
}
// 创建经过 sanitize 的输入
let userInput = sanitizeInput('hello');
// 但仍可以使用字符串重新分配值
userInput = "new input";

2、type关键字和interface关键字的区别

在 TypeScript 中,interfacetype 都用于定义类型,但它们有一些区别和不同的用途。

1、interface 是 TypeScript 中用于定义对象类型、类的契约或合同的关键字。它可以描述对象的结构、属性、方法和类的实现细节。可以通过接口来定义对象的形状,并使代码更具可读性和可维护性。

2、type 是TypeScript 中用于定义类型别名的关键字。它允许给现有类型起一个新的名字。使用 type 可以创建复杂的类型,包括联合类型、交叉类型和元组类型等。类型别名还可以使用泛型和其他类型操作符来定义更加灵活的类型。

尽管 interfacetype 在某些方面有重叠的功能,但它们之间也有一些区别:

2.1、声明方法不同

  • 可以合并:在同一范围内,可以多次声明同名的 interface,它们会自动合并成一个。而 type 不支持合并,如果多次声明同名的 type,会报错。

示例:

// interface合并
interface Person {
  name: string;
}

interface Person {
  age: number;
}

const person: Person = {
  name: "John",
  age: 25,
};

// type不能合并
type Person = {
  name: string;
};

type Person = {
  age: number;
}; // 报错

2.2、扩展方式不同

  • 继承/实现的能力:interface 可以通过 extends 关键字来继承其他接口,实现接口的复用。而 type 不支持继承,type可通过交叉点来实现扩展。

示例:

// interface继承
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

const square: Square = {
  color: "red",
  sideLength: 10,
};

// type不支持继承
type Shape = {
  color: string;
};

type Square = Shape & {
  sideLength: number;
}; // 使用交叉类型实现类似继承的效果

const square: Square = {
  color: "red",
  sideLength: 10,
};

2.3、实现方式不同

  • 实现类/抽象类:interface 可以用于描述类的契约,即类需要遵守接口定义的属性和方法。而 type 不支持直接描述类的契约,它更适用于描述对象类型。

示例:

interface Animal {
  name: string;
  speak(): void;
}

class Dog implements Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  speak() {
    console.log("Woof!");
  }
}

// type不支持类的实现
type Animal = {
  name: string;
  speak(): void;
};

class Dog implements Animal { // 报错,type不能实现类的契约
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  speak() {
    console.log("Woof!");
  }

3、type关键字和interface关键字的使用条件

当涉及到选择使用 interface 还是 type 时,可以考虑以下几个因素:

  • 如果要描述一个对象的形状或类的契约,优先选择 interfaceinterface 更符合面向对象的思维方式,可以清晰地定义对象的结构和方法。

示例:

interface Person {
  name: string;
  age: number;
  greet(): void;
}
  • 如果需要创建复杂的类型,例如联合类型、交叉类型或使用泛型操作符,或者需要定义类型别名以提高代码可读性,优先选择 type

示例:

type UnionType = string | number;
type IntersectionType = { name: string } & { age: number };
type Pair<T> = [T, T];
  • 如果需要进行类型的扩展或合并,或者在同一范围内需要多次声明同名的类型,使用 interface

示例:

interface Shape {
  color: string;
}

interface Shape {
  size: number;
}

const shape: Shape = {
  color: "red",
  size: 10,
}

        需要注意的是,在大多数情况下,interfacetype 是可以互换使用的,并且在实际项目中可以根据个人和团队的偏好选择使用哪个。它们的目标是为了提供类型的抽象和复用,使代码更具可读性和可维护性。

五、类型断言

1、普通类型断言

语法:

        变量名  或  变量值   as   数据类型; //顺序不能变

        <数据类型>   变量名; //顺序不能变

有时,你会获得有关 TypeScript 不知道的值类型的信息。
        例如,如果你正在使用 document.getElementById TypeScript 只知道这将返回某种类型的
HTMLElement ,但你可能知道你的页面将始终具有 HTMLCanvasElement 给定 ID 的值 。
在这种情况下,你可以使用类型断言来指定更具体的类型:
const myCanvas = document . getElementById ( "main_canvas" ) as HTMLCanvasElement;
与类型注释一样,类型断言由编译器删除,不会影响代码的运行时行为。
还可以使用尖括号语法(除非代码在 .tsx 文件中),它是等效的:
const myCanvas = <HTMLCanvasElement> document.getElementById("main_canvas");
提醒:因为类型断言在编译时被移除,所以没有与类型断言相关联的运行时检查。 null 如果类型
断言错误,则不会出现异常。
TypeScript 只允许类型断言转换为更具体或不太具体的类型版本。此规则可防止 不可能 的强制,例如:
const x = "hello" as number;

将类型 string 转换为类型 number 可能是错误的,因为两种类型都没有充分重叠。如果这是有意的,请先将表达式转换为 any unknown ,然后是所需的类型:
const x = ( "hello" as unknown) as number;

案例:

let a = {
    kind : 'square' as 'square',
    sideLength: 2,
    cc: 3
}

本来kind的类型为string,断言后类型为 “square”字面值类型

2、非空断言运算符( ! 后缀)

TypeScript 也有一种特殊的语法 null undefined ,可以在不进行任何显式检查的情况下,从类型中移除和移除类型。 ! 在任何表达式之后写入实际上是一种类型断言,即该值不是 null or undefined
function liveDangerously(x?: number | null) {
    // 正确
    console.log(x!.toFixed());
    //错误
    console.log(x.toFixed());
}
        就像其他类型断言一样,这不会更改代码的运行时行为,因此仅 ! 当你知道该值不能是 null undefined 时使用才是重要的。

六、可选运算符(?后缀)

语法:

变量名?:数据类型

表示该变量可选,可以传入,也可以不传入

第四章、类型保护

        类型保护(Type Guards)是一种在 TypeScript 中用于缩小变量类型范围的技术。它允许你在特定的条件下,对变量的类型进行判断并缩小类型范围,以便在后续代码块中使用更具体的类型信息。

        通过类型保护,你可以告诉 TypeScript 编译器某个变量在特定条件下具有特定的类型,从而获得更准确的类型推断和代码提示。

一、类型缩小

        在TypeScript(TS)中,类型缩小(Type Narrowing)是指通过条件判断或类型断言等方式,将一个类型限制为更具体的子类型或取出类型中的特定属性的过程。

        类型缩小可以用于在代码中对不同类型的值进行处理,并在不同的上下文中使用它们。这样可以增加代码的可读性和类型安全性,因为在缩小类型之后,TypeScript 编译器能够根据特定类型的属性和方法提供更准确的代码提示和错误检查。

假设我们有一个名为 padLeft 的函数:
function padLeft(padding: number | string, input: string) {
    console.log(padding + 1)
}

        在 padding + 1 处我们遇到错误。 TypeScript 警告我们,运算符 + 不能应用于类型 string |
number number ,这是对的。换句话说,我们没有明确检查 padding 是否为 number ,也没有处理 它是 string 的情况,所以我们这样做:
function padLeft(padding: number | string, input: string):void {
    if (typeof padding === 'number'){
        console.log(padding + 1)
    }
    console.log(padding + input)  
}
        虽然看起来不多,但实际上有很多东西在这里。就像TypeScript 使用静态类型分析运行时的值一样,它在 JavaScript 的运行时控制流构造上叠加了类型分析,如 if/else 、条件三元组、循环、真实性检查等,这 些都会影响到这些类型。
        在我们的if 检查中, TypeScript 看到 typeof padding==="number" ,并将其理解为一种特殊形式的代 码,称为类型保护。 TypeScript 遵循我们的程序可能采取的执行路径,以分析一个值在特定位置的最具 体的可能类型。它查看这些特殊的检查(称为类型防护)和赋值,将类型细化为比声明的更具体的类型 的过程被称为缩小。在许多编辑器中,我们可以观察这些类型的变化,我们甚至会在我们的例子中这样 做。
TypeScript 可以理解几种不同的缩小结构。

二、typeof类型缩小

typeof 类型缩小是一种常见的类型缩小技术,在 TypeScript 中用于根据变量的值来缩小变量的类型范围。它基于 JavaScript 中的 typeof 运算符,用于确定给定值的类型。

当使用 typeof 运算符应用于一个变量或表达式时,TypeScript 编译器会根据运算符的结果将变量的类型缩小为特定的类型。下面是一些常见的 typeof 类型缩小用法和示例:

1、typeof variable === "type"

用于将变量的类型缩小为基本类型

  • "string"
  • "number"
  • "boolean"
  • "object"
  • "function"
  • "symbol"
  • "undefined"
function processValue(value: string | number) {
  if (typeof value === "string") {
    // 在这个块中,value 的类型被缩小为 string
    console.log(value.toUpperCase());
  } else {
    // 在这个块中,value 的类型被缩小为 number
    console.log(value.toFixed(2));
  }
}

2、typeof variable !== "type"

用于将变量的类型缩小为与指定类型不匹配的情况。

function logValue(value: string | number) {
  if (typeof value !== "string") {
    // 在这个块中,value 的类型被缩小为 number
    console.log("Invalid value:", value);
  } else {
    // 在这个块中,value 的类型被缩小为 string
    console.log("Valid value:", value);
  }
}

3、typeof variable === "function"

用于检查变量是否为函数类型。

function executeCallback(callback: (() => void) | undefined) {
  if (typeof callback === "function") {
    // 在这个块中,callback 的类型被缩小为函数类型
    callback();
  } else {
    console.log("No callback provided.");
  }
}

        需要注意的是,typeof 类型缩小只能用于缩小到基本类型或 "function" 类型,无法用于缩小到自定义类型。对于自定义类型,可以使用其他类型缩小技术,如类型保护和类型断言。

        另外,typeof 类型缩小是在编译时进行的静态类型检查,不会在运行时影响变量的值。因此,它只能在编译阶段确定变量的类型,并不能处理动态类型的情况。

三、真值类型缩小

"真值缩小"(Truthy Narrowing)是 TypeScript 中一种类型缩小的概念,它基于 JavaScript 中的"真值"(Truthy)和"假值"(Falsy)的概念来缩小变量的类型范围。

注意:js里,空字符串;null;undefined;0;NaN 转成布尔类型,都是false 。

        在 TypeScript 中,"真值"指的是在布尔上下文中被视为真的值,而"假值"指的是在布尔上下文中被视为假的值。在进行真值缩小时,TypeScript 编译器会根据条件表达式的真值性来缩小变量的类型范围。

以下是一些常见的真值缩小场景和示例:

1、条件表达式为真(Truthy)时的类型缩小:

function processValue(value: string | null) {
  if (value) {
    // 在这个块中,value 的类型被缩小为 string
    console.log(value.toUpperCase());
  } else {
    // 在这个块中,value 的类型被缩小为 null
    console.log("Value is null.");
  }
}

        在上述示例中,如果 value 的值为非空字符串,则条件表达式为真,编译器会将变量 value 的类型缩小为 string 类型。

2、条件表达式为假(Falsy)时的类型缩小:

function processNumber(value: number | undefined) {
  if (!value) {
    // 在这个块中,value 的类型被缩小为 undefined
    console.log("No value provided.");
  } else {
    // 在这个块中,value 的类型被缩小为 number
    console.log("Value:", value);
  }
}

        在上述示例中,如果 value 的值为 undefined0NaN 或空字符串等 JavaScript 中被视为假的值,条件表达式为假,编译器会将变量 value 的类型缩小为 undefined 类型。

        通过真值缩小,我们可以根据变量的真值性来缩小类型范围,从而编写更安全、更具有可读性的代码。需要注意的是,真值缩小仅适用于布尔上下文中的条件表达式,而不适用于其他类型的条件判断。

四、等值缩小

        "等值缩小"(Equality Narrowing)是 TypeScript 中一种类型缩小的概念,它基于变量的等值比较来缩小变量的类型范围。

        在进行等值缩小时,TypeScript 编译器会根据变量与特定值的等值比较结果来缩小变量的类型范围。等值缩小适用于以下比较操作符:

  • ===(严格等于)
  • !==(严格不等于)
  • ==(相等)
  • !=(不相等)

以下是一些常见的等值缩小场景和示例:

1、使用严格等于(===)进行等值缩小:

function processValue(value: string | number):void {
    if (typeof value === "string") {
        // 在这个块中,value 的类型被缩小为 string
        console.log(value.toUpperCase());
    } else {
        // 在这个块中,value 的类型被缩小为 number
        console.log(value.toFixed(2));
    }
}
function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // 在这个块中,x和y的类型被缩小为string
        x.toUpperCase();
        y.toLowerCase();
    } else {
        console.log(x);
        console.log(y);
    }
}

example(1,false)

2、使用严格不等于(!==)进行等值缩小:

function processNumber(value: number | undefined) {
  if (value !== undefined) {
    // 在这个块中,value 的类型被缩小为 number
    console.log("Value:", value);
  } else {
    // 在这个块中,value 的类型被缩小为 undefined
    console.log("No value provided.");
  }
}

        在上述示例中,如果 value 严格不等于 undefined,则编译器会将变量 value 的类型缩小为 number 类型。

        通过等值缩小,我们可以根据变量与特定值的等值比较结果来缩小类型范围,从而编写更安全、更具有可读性的代码。需要注意的是,等值缩小仅适用于特定的等值比较操作符,而不适用于其他类型的条件判断。

五、in操作符缩小

        在 TypeScript 中,in 操作符可以用于进行类型缩小(type narrowing)。它可以用于判断一个联合类型中的变量是否属于某个特定的类型,并在条件分支中缩小变量的类型范围。

        当使用 in 操作符对一个联合类型进行判断时,TypeScript 可以根据判断结果自动缩小变量的类型。

下面是一个简单的示例:

type Circle = {
    kind: 'circle';
    radius: number;
};

type Square = {
    kind: 'square';
    sideLength: number;
};

type Shape = Circle | Square;

function getArea(shape: Shape): number {
    if ('radius' in shape) {
        // 在这个条件分支中,shape 被缩小为 Circle 类型
        return shape.radius * 2;
    }
    if ('sideLength' in shape) {
        // 在这个条件分支中,shape 被缩小为 Square 类型
        return shape.sideLength * 2;
    }
    // 这里的 shape 类型为 Shape,没有具体的属性可以使用
    throw new Error('Unknown shape');
}

let result = getArea({
    kind: 'circle',
    radius: 12
})

console.log(result)
另外,可选属性还将存在于缩小的两侧,例如,人类可以游泳和飞行(使用正确的设备),因此应该出现在 in 检查的两侧:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
    if ("swim" in animal) {
        // animal: Fish | Human
        animal.swim();
    } else {
        // animal: Bird | Human
        animal.fly
    }
}

move({swim : () => {
    console.log("游泳")
}})

六、instanceof操作符缩小

        在 TypeScript 中,instanceof 操作符也可以用于进行类型缩小(type narrowing)。它用于判断一个对象是否是某个类的实例,并在条件分支中缩小对象的类型范围。

        当使用 instanceof 操作符对一个对象进行判断时,TypeScript 可以根据判断结果自动缩小对象的类型。

下面是一个简单的示例:

//一个类中只能有一个构造函数,还并不是默认继承于Object,得手动继承
class Animal {
    name: string;
    constructor(name: string) {

        this.name = name;
    }

}

class Dog extends Animal {

    // num : number;

    // constructor(num: number,name: string){
    //     super(name);
    //     this.num = num;
    // }

    //默认有此构造函数
    constructor(name: string) {
        super(name);
    }

    bark(): void {
        console.log('Woof!');
    }
}

class Cat extends Animal {
    meow(): void {
        console.log('Meow!');
    }
}

function makeSound(animal: Animal) {
    if (animal instanceof Dog) {
        // 在这个条件分支中,animal 被缩小为 Dog 类型
        animal.bark();
    }
    if (animal instanceof Cat) {
        // 在这个条件分支中,animal 被缩小为 Cat 类型
        animal.meow();
    }
}

//实例子类,若子类中没有显示指定构造方法,则默认有个构造方法,使用super调用父类的构造方法
let dog: Dog = new Dog("小黑");
makeSound(dog)

        在上面的示例中,我们使用 instanceof 操作符检查 animal 是否是 DogCat 类的实例。如果是 Dog 的实例,则在相应的条件分支中,animal 被自动缩小为 Dog 类型,从而可以调用 bark() 方法。同样,如果是 Cat 的实例,animal 被自动缩小为 Cat 类型,可以调用 meow() 方法。

instanceof 操作符提供了一种方便且类型安全的方式来判断对象的类型,并在条件分支中进行相应的处理。它可以帮助开发者在编写 TypeScript 代码时进行更精确的类型推断和类型检查。

function logValue(x: Date | string) {
    if (x instanceof Date) {
        //在这个条件分支中:X被缩小为Date类型
        console.log(x.toUTCString());
    } else {
        //在这个条件分支中:X被缩小为string类型
        console.log(x.toUpperCase());
    }
}

logValue(new Date()) // Fri, 12 May 2023 05:58:37 GMT
logValue('hello ts') // HELLO TS

七、分配缩小

        在TypeScript中,分配缩小(assignment narrowing)是一种类型缩小的技术,用于根据变量的赋值操作来缩小其类型范围。当我们在一个条件分支中给变量赋值时,TypeScript可以根据赋值操作的类型来自动缩小变量的类型。

下面是一个简单的示例:

function printLength(value: string | string[]) {
    if (typeof value === 'string') {
        // 在这个条件分支中,value 被缩小为 string 类型
        console.log(value.length);
    } else {
        // 在这个条件分支中,value 被缩小为 string[] 类型
        console.log(value.length);
    }
}

printLength('jzq'); //3
printLength(['1','2']) //2

        在上面的例子中,我们有一个参数 value,它可以是一个字符串或字符串数组。当我们在条件分支中使用 typeof 操作符将 value 的类型与 'string' 进行比较时,TypeScript 可以自动缩小 value 的类型为 string,因此我们可以安全地访问 value.length 属性。类似地,如果 value 的类型不是 string,它会被自动缩小为 string[] 类型。

        这种分配缩小的技术使得我们能够在不需要手动进行类型断言或类型转换的情况下,根据变量的赋值操作来获取更精确的类型信息。它提高了代码的可读性和类型安全性,并减少了手动处理类型的需要。

//在重新赋值时进行类型缩小
// let x: string | number
let x = Math.random() < 0.5 ? 10 : "hello world!";

x = 1;

// let x: number
console.log(x);

x = "goodbye!";

// let x: string
console.log(x);

八、控制流分析

        在TypeScript中,控制流分析(Control Flow Analysis)是一种静态分析技术,用于推断程序在不同代码路径上的变量类型。这种基于可达性的代码分析被称为控制流分析,TypeScript使用这种流分析来缩小类型,因为它遇到了类型守卫和赋值。当一个变量被分析时,控制流可以一次又一次地分裂和重新合并,该变量可以被观察到在每个点上有不同的类型。

变量每次一赋值后,就相当于占了一种类型

        控制流分析通过分析程序的条件分支、循环和异常处理等代码结构,来确定变量在不同代码路径上的类型范围。它可以根据条件判断的结果、循环迭代的次数以及异常处理的情况,推断出变量的类型缩小范围。

例如,考虑以下代码片段:

function example(value: string | number) {
  if (typeof value === 'string') {
    // 在这个条件分支中,value 被缩小为 string 类型
    console.log(value.toUpperCase());
  } else {
    // 在这个条件分支中,value 被缩小为 number 类型
    console.log(value.toFixed(2));
  }
}

        在这个例子中,当我们在条件分支中使用 typeof 进行类型检查时,TypeScript会根据条件分支的结果将变量 value 缩小为 string 类型或 number 类型。这是通过控制流分析来推断出的。

        控制流分析可以在类型推断和类型检查中发挥重要作用。它可以帮助开发者编写更精确的代码,避免不必要的类型错误,并提供更好的代码提示和自动补全。它是TypeScript的一项强大功能,使得静态类型检查可以更好地理解程序的控制流程,从而提供更精确的类型推断和类型检查。

function example() {
    let x: string | number | boolean;
    x = Math.random() < 0.5;
    // let x: boolean
    console.log(x);
    if (Math.random() < 0.5) {
        x = "hello";
        // let x: string
        console.log(x);
    } else {
        x = 100;
        // let x: number
        console.log(x);
    }
    // let x: string | number
    return x;
}

let x = example()
x = 'hello'
x = 100
x = true // error

        在给定的代码示例中,函数 example() 定义了一个变量 x,它的类型是 string | number | boolean,即可以是字符串、数字或布尔值。

        在代码的执行过程中,首先给变量 x 赋值 Math.random() < 0.5。由于 Math.random() 返回的是一个随机的浮点数,当判断条件 Math.random() < 0.5 为真时,赋值给 x 的结果是布尔值 true,所以此时 x 的类型被推断为 boolean

        然后,在条件语句 if (Math.random() < 0.5) 中,进一步根据条件的结果分别给 x 赋值。如果条件为真,即执行了 x = "hello",这时 x 的类型被推断为 string。而如果条件为假,执行了 x = 100,则 x 的类型被推断为 number

        在代码的最后,函数返回了变量 x。由于在前面的代码分支中,x 的类型被缩小为 stringnumber,因此返回的类型是 string | number,表示可以是字符串或数字。

        总结起来,通过控制流分析,根据赋值操作的类型,TypeScript 可以在不同代码路径上推断出变量 x 的类型缩小范围。在给定的代码示例中,最终返回的变量 x 的类型被缩小为 string | number,表示它可以是字符串或数字。

九、类型谓词

        在TypeScript中,类型谓词(Type Predicate)是一种用于缩小变量类型的特殊语法。它允许开发者在条件表达式中使用自定义断言函数来确定变量的类型。

类型谓词的语法形式为   parameterName is Type

        其中 parameterName 是函数参数的名称,Type 是要断言的类型。在函数中使用类型谓词后,TypeScript会将该函数参数的类型缩小为指定的类型。

下面是一个简单的示例:

function isNumber(value: unknown): value is number {
    return typeof value === 'number';
}

console.log(isNumber('2'))

function processValue(value: unknown) {
    if (isNumber(value)) {
        // 在这个条件分支中,value 被缩小为 number 类型
        console.log(value.toFixed(2));
    } else {
        console.log('Invalid value');
    }
}

        在上面的例子中,我们定义了一个类型谓词函数 isNumber,它接收一个未知类型的值并返回一个布尔值,指示该值是否为数字类型。在 processValue 函数中,我们使用 isNumber 函数进行类型判断。当 isNumber(value) 返回 true 时,在条件分支中,TypeScript会将 value 的类型缩小为 number,使我们可以安全地调用 toFixed() 方法。

        类型谓词函数为我们提供了一种自定义的类型判断机制,允许我们根据自己的逻辑来确定变量的类型范围。它可以在条件判断中使用,并配合控制流分析,提供更精确的类型推断和类型检查。类型谓词函数常用于处理复杂的类型判断逻辑,提高代码的可读性和类型安全性。

type Fish = {
    name: string
    swim: () => void
}

type Bird = {
    name: string
    fly: () => void
}

function isFish(pet: Fish | Bird): pet is Fish {
    //类型断言只能写在左边
    return (<Fish>pet).swim !== undefined
}

function getSmallPet(): Fish | Bird {

    let fish: Fish = {
        name: 'gold fish',
        swim: () => {
            console.log('fish')
        }
    }

    let bird: Bird = {
        name: 'sparrow',
        fly: () => {
            console.log('bird')
        }
    }
    return true ? bird : fish
}

// 这里 pet 的 swim 和 fly 都可以访问了
let pet = getSmallPet()
if (isFish(pet)) {
    pet.swim()
} else {
    pet.fly()
}

        上述代码片段定义了两个类型 FishBird,分别表示鱼和鸟的属性和方法。然后,代码中定义了一个 isFish 函数,用于判断传入的参数是否是鱼类型。函数使用类型谓词 pet is Fish 来缩小参数的类型范围,只有在参数具有 swim 方法时才返回 true

        接下来,代码定义了一个 getSmallPet 函数,根据条件返回鱼或鸟的实例。在主代码块中,首先调用 getSmallPet 函数获取宠物,然后使用条件语句和类型谓词 isFish 来判断宠物的类型。如果宠物是鱼类型,则可以调用 swim 方法;如果宠物是鸟类型,则可以调用 fly 方法。

        这段代码的目的是展示如何使用类型谓词和条件判断来根据类型的不同执行相应的操作。通过使用类型谓词,我们可以在条件判断中缩小变量的类型范围,从而确保只调用特定类型所具有的方法,避免类型错误。

        在这个例子中,通过 isFish 函数和条件判断,我们可以在宠物是鱼类型时调用 swim 方法,在宠物是鸟类型时调用 fly 方法,从而保证了类型的安全性和代码的正确性。

1、类型谓词的作用

        类型谓词的作用是在 TypeScript 中用于缩小变量的类型范围。通过自定义断言函数并使用类型谓词,我们可以在条件判断中根据特定的类型属性或方法的存在与否,确定变量的更具体的类型。

具体来说,类型谓词的作用包括:

  1. 类型缩小:通过类型谓词,可以在条件分支中将变量的类型缩小为更具体的类型。这有助于在不同的代码路径上进行更精确的类型推断和类型检查。

  2. 类型保护:类型谓词提供了一种类型保护机制,使得我们可以根据自定义的断言函数来确定变量的类型。通过类型保护,可以避免不必要的类型错误,并提供更准确的类型信息。

  3. 智能提示:当变量的类型被缩小为更具体的类型时,编辑器可以根据这些信息提供更准确的代码提示和自动补全,帮助开发者编写正确的代码。

  4. 可读性和维护性:使用类型谓词可以使代码更加清晰和易读,因为它明确地表达了我们对变量类型的判断和假设,使代码逻辑更易理解和维护。

        总之,类型谓词在 TypeScript 中的作用是通过自定义断言函数来缩小变量的类型范围,提供类型保护、智能提示以及改善代码的可读性和维护性。它是一种强大的工具,可以增强类型系统的表现力和可靠性。

2、类型谓词的实现步骤

要实现类型谓词,需要遵循以下步骤:

  1. 定义一个函数,该函数的参数类型为待判断的变量类型,返回类型为布尔值。这个函数就是类型谓词函数。

  2. 在类型谓词函数中,使用自定义的逻辑来判断传入的参数是否满足特定的条件,从而确定变量的更具体的类型。

  3. 在调用类型谓词函数的地方,通过调用语法 parameterName is Type,将变量的类型缩小为指定的类型。

下面是一个示例:

interface Fish {
  name: string;
  swim: () => void;
}

interface Bird {
  name: string;
  fly: () => void;
}

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

        在上面的示例中,我们定义了一个 isFish 函数作为类型谓词函数。它接收一个参数 pet,类型为 Fish | Bird,并返回一个布尔值。

        在 isFish 函数的实现中,我们使用 (pet as Fish).swim !== undefined 来判断传入的参数 pet 是否为鱼类型。如果 pet 对象具有 swim 方法,那么它被认为是鱼类型,并返回 true

        在调用 isFish 函数的地方,使用 if (isFish(pet)) 来进行条件判断。如果 isFish 返回 true,则 TypeScript 缩小了变量 pet 的类型为 Fish,允许我们在条件分支中安全地调用 swim 方法。

        需要注意的是,在类型谓词中使用类型断言 (pet as Fish) 是为了告诉 TypeScript 编译器将变量 pet 视为 Fish 类型进行类型判断。这样 TypeScript 就能够正确地推断和缩小变量的类型。

        通过实现自定义的类型谓词函数,我们可以根据特定的条件确定变量的更具体的类型,从而提供更准确的类型推断和类型检查。

3、类型谓词短语的作用

pet is Fish 是类型谓词的语法,它的作用是告诉 TypeScript 编译器在条件判断中缩小变量的类型范围。虽然在没有使用类型谓词的情况下也可以进行类型判断,但是类型谓词提供了以下几个优势:

  1. 类型推断:使用类型谓词可以让 TypeScript 编译器更准确地推断变量的类型。在使用类型谓词后,编译器能够将变量的类型缩小为指定的类型,并在相应的代码分支中提供更准确的类型信息。

  2. 智能提示:类型谓词的存在可以使编辑器提供更准确的代码提示和自动补全。当变量的类型被缩小为更具体的类型时,编辑器可以根据这些信息提供相关类型的方法和属性,帮助开发者编写正确的代码。

  3. 类型保护:使用类型谓词可以提供类型保护机制,避免不必要的类型错误。在使用类型谓词进行类型判断后,编译器会在相应的代码块中将变量的类型缩小为指定的类型,从而确保在该代码块中只能使用该类型所具有的方法和属性。

        综上所述,使用 pet is Fish 类型谓词可以让 TypeScript 编译器对变量的类型进行更精确的推断和类型检查,并提供更准确的智能提示。它能够增强代码的可读性和类型安全性,帮助开发者避免潜在的类型错误。虽然在某些简单的情况下可能不会有明显的差异,但对于复杂的类型判断和类型缩小,使用类型谓词是一种推荐的做法

十、受歧视的 unions

        到目前为止,我们所看的大多数例子都是围绕着用简单的类型(如 string boolean number )来缩小单个变量。虽然这很常见,但在 JavaScript 中,大多数时候我们要处理的是稍微复杂的结构。 为了激发灵感,让我们想象一下,我们正试图对圆形和方形等形状进行编码。圆记录了它们的半径,方 记录了它们的边长。我们将使用一个叫做 kind 的字段来告诉我们正在处理的是哪种状。这里是定义 Shape 的第一个尝试。
interface Shape {
    kind: "circle" | "square";
    radius?: number;
    sideLength?: number;
}
        注意,我们使用的是字符串字面类型的联合。 "circle " "square " 分别告诉我们应该把这个形状 当作一个圆形还是方形。通过使用 "circle" | "square " 而不是 string ,我们可以避免拼写错误的问题。
function handleShape(shape: Shape) {
    // oops!
    if (shape.kind === "rect") {
        // ...
    }
}

        我们可以编写一个 getArea 函数,根据它处理的是圆形还是方形来应用正确的逻辑。我们首先尝试处理圆形。
function getArea(shape: Shape) {
    return Math.PI * shape.radius ** 2;
}

        在 strictNullChecks 下,这给了我们一个错误 —— 这是很恰当的,因为 radius 可能没有被定义。 但是如果我们对 kind 属性进行适当的检查呢?
function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius ** 2;
    }
}

        嗯,TypeScript 仍然不知道该怎么做。我们遇到了一个问题,即我们对我们的值比类型检查器知道的更多。我们可以尝试使用一个非空的断言 ( radius 后面的那个叹号 ! ) 来说明 radius 肯定存在。
function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius! ** 2;
    }
}
        但这感觉并不理想。我们不得不用那些非空的断言对类型检查器声明一个叹号( ! ),以说服它相信shape.radius 是被定义的,但是如果我们开始移动代码,这些断言就容易出错。此外,在
strictNullChecks 之外,我们也可以意外地访问这些字段(因为在读取这些字段时,可选属性被认为总是存在的)。
        Shape 的这种编码的问题是,类型检查器没有办法根据种类属性知道 radius sideLength 是否存 在。我们需要把我们知道的东西传达给类型检查器。考虑到这一点,让我们再来定义一下 Shape
interface Circle {
    kind: "circle";
    radius: number;
}
interface Square {
    kind: "square";
    sideLength: number;
}
type Shape = Circle | Square;

        在这里,我们正确地将 Shape 分成了两种类型,为 kind 属性设置了不同的值,但是 radius sideLength 在它们各自的类型中被声明为必需的属性。
让我们看看当我们试图访问 Shape 的半径时会发生什么。

        就像我们对 Shape 的第一个定义一样,这仍然是一个错误。当半径是可选的时候,我们得到了一个错误(仅在 strictNullChecks 中),因为 TypeScript 无法判断该属性是否存在。现在 Shape 是一个联 合体, TypeScript 告诉我们 shape 可能是一个 Square ,而 Square 并没有定义半径 radius 。 这两种 解释都是正确的,但只有我们对 Shape 的新编码仍然在 strictNullChecks 之外导致错误。
但是,如果我们再次尝试检查 kind 属性呢?
function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        // shape: Circle
        return Math.PI * shape.radius ** 2;
    }
}
        这就摆脱了错误! union 中的每个类型都包含一个与字面类型相同的属性时, TypeScript 认为这是一个有区别的 union ,并且可以缩小 union 的成员。
        
        在这种情况下, kind 就是那个共同属性(这就是 Shape 的判别属性)。检查 kind 属性是否为 "circle" ,就可以剔除 Shape 中所有没有 "circle" 类型属性的类型。这就把 Shape 的范围缩小到
Circle 这个类型。
        同样的检查方法也适用于 switch 语句。现在我们可以试着编写完整的 getArea ,而不需要任何讨厌的叹号 非空的断言。
interface Circle {
    kind: "circle";
    radius: number;
}
interface Square {
    kind: "square";
    sideLength: number;
}
type Shape = Circle | Square;

function getArea(shape: Shape) {
    switch (shape.kind) {
        // shape: Circle
        case "circle":
            return Math.PI * shape.radius ** 2;
        // shape: Square
        case "square":
            return shape.sideLength ** 2;
    }
}

let a = {
    kind : 'square' as 'square',
    sideLength: 2,
    cc: 3
}

console.log(getArea(a)) //输出4
        这里最重要的是 Shape 的编码。向 TypeScript 传达正确的信息是至关重要的,这个信息就是 Circle和 Square 实际上是具有特定种类字段的两个独立类型。这样做让我们写出类型安全的 TypeScript 代码, 看起来与我们本来要写的 JavaScript 没有区别。从那里,类型系统能够做 " 正确 " 的事情,并找出我们 switch 语句的每个分支中的类型。

十一、never类型与穷尽性检查

        穷尽性检查(exhaustiveness check)是指在编程语言中,确保在进行条件分支判断时,所有可能的情况都已经被处理到,以避免遗漏或意外的情况。

        在 TypeScript 中,穷尽性检查通常与联合类型和类型判断结合使用。当一个变量的类型为联合类型时,使用 switch 语句进行条件分支判断时,TypeScript 编译器会进行穷尽性检查,以确保所有可能的情况都被处理到。

以下是一个示例来说明穷尽性检查的概念:

type Shape = 'circle' | 'square' | 'triangle';

function getShapeArea(shape: Shape, radius?: number, sideLength?: number) {
    switch (shape) {
        case 'circle':
            if (radius) {
                return Math.PI * radius * radius;
            }
            break;
        case 'square':
            if (sideLength) {
                return sideLength * sideLength;
            }
            break;
        case 'triangle':
            if (sideLength) {
                return (Math.sqrt(3) / 4) * sideLength * sideLength;
            }
            break;
        default:
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck
    }

    // 当 shape 的类型为 never 时,这个分支会被当作穷尽性检查
}

        在上面的代码中,Shape 是一个联合类型,表示可能的形状。getShapeArea 函数接受一个 shape 参数来指定形状,以及可选的 radiussideLength 参数用于计算面积。

        在 switch 语句中,根据 shape 的值,我们检查相应的情况并计算面积。如果所有可能的情况都被处理到,那么编译器不会报错。但是,如果遗漏了某个情况,例如忘记了处理 'triangle',TypeScript 编译器会发出一个错误,指出存在穷尽性检查的问题。

        在上面的代码中,我们使用了 const exhaustiveCheck: never = shape; 这一行作为穷尽性检查。当 shape 的类型为 never 时,也就是没有任何一个情况匹配到 shape,这个分支会被执行。此时,我们可以确保所有可能的情况都已经被处理到。

通过穷尽性检查,我们可以在编译时捕获到可能的错误,并确保代码的健壮性和可靠性。

例如:

第五章、函数

        函数是任何应用程序的基本构件,无论它们是本地函数,从另一个模块导入,还是一个类上的方法。它 们也是值,就像其他值一样, TypeScript 有很多方法来描述如何调用函数。

一、函数定义

        函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。

        通常情况下,TypeScript 代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。

二、函数签名

1、函数声明类型

function 函数名(参数列表): 返回值类型 {
    函数体 ...
    [return 返回值;]
}

2、函数表达式(匿名函数)类型

const 函数名 = function (参数列表): 返回值类型 {
    函数体 ...
    [return 返回值;]
};

3、箭头函数类型

(形参:形参类型,......)=>返回值类型

  • 函数结构必须和声明类型一致

三、函数类型表达式(箭头函数类型)

描述一个函数的最简单方法是用一个函数类型表达式。这些类型在语法上类似于箭头函数。
function greeter(fn: (a: string) => void) {
    fn("Hello, World");
}
function printToConsole(s: string) {
    console.log(s);
}
greeter(printToConsole);
        语法 (a: string) => void 意味着 " 有一个参数的函数,名为 a ,类型为字符串,没有返回值 " 。就像 函数声明一样,如果没有指定参数类型,它就隐含为 any 类型。 当然,我们可以用一个类型别名来命名一个函数类型。
type GreetFunction = (a: string) => void;

function greeter(fn: GreetFunction) {
    // ...
}

四、调用签名

调用签名表示该对象可以像函数一样被调用

 1、普通函数调用签名

        在 TypeScript 中,调用签名(Call Signature)是用来描述函数类型的一部分。它定义了函数的参数类型和返回类型。

调用签名的语法如下:

(parameter: type, parameter: type, ...) => return-type

        其中,(parameter: type, parameter: type, ...) 表示函数的参数列表和对应的类型,=> 表示函数的返回类型。

以下是一个简单的示例,展示了调用签名的使用:

type AddFunc = (a: number, b: number) => number;

let add: AddFunc = (a, b) => {
  return a + b;
};

console.log(add(3, 5)); // 输出: 8

        在上述示例中,我们定义了一个类型 AddFunc,它是一个函数类型,包含了一个调用签名。这个调用签名表示函数接受两个参数 ab,都是数字类型,返回一个数字类型。

        然后,我们创建了一个变量 add,并将一个满足 AddFunc 类型的函数赋值给它。该函数接受两个参数并返回它们的和。

        最后,我们调用 add(3, 5),输出结果为 8。

        调用签名在 TypeScript 中用于描述函数类型,它可以用来定义函数类型的变量、参数类型和返回类型,以及在接口(interface)或类(class)中定义函数成员的类型。它提供了静态类型检查和类型推断的能力,有助于提高代码的可读性和可靠性。

2、普通对象中的函数调用签名

案例一:

        调用签名表示对象可以像函数一样被调用,意味着我们可以使用括号运算符 () 来调用该对象,就像调用函数一样。

        在 TypeScript 中,函数是一种特殊的对象类型,具有调用能力。通过使用调用签名,我们可以将类似函数的行为应用于其他对象类型,使其可以被像函数一样调用。

        当我们在一个对象类型中定义了一个调用签名,它就具备了可调用的特性。这意味着我们可以使用对象名后面加上括号来调用该对象,并向其传递相应的参数。

下面是一个简单的示例来说明这个概念:

type MyCallableObject = {
  (param: number): string;
};

let myObject: MyCallableObject = (param: number) => {
  return `Received parameter: ${param}`;
};

console.log(myObject(10)); // 输出: Received parameter: 10

        在这个例子中,我们定义了一个类型别名 MyCallableObject,它具有一个调用签名 (param: number) => string。这个调用签名表示该对象可以像函数一样被调用,并接受一个 number 类型的参数,返回一个 string 类型的结果。

        然后,我们声明了一个变量 myObject,类型为 MyCallableObject。我们给 myObject 赋值一个函数,该函数接受一个参数,并返回一个字符串。

        最后,我们调用 myObject(10),输出结果为 "Received parameter: 10"。这里的调用方式和调用函数非常相似。

        通过使用调用签名,我们可以定义具有函数调用能力的对象类型,使其可以像函数一样被调用,从而增加了灵活性和可重用性。

案例二:
type DescribableFunction = {
    description: string;
    //调用签名
    (someArg: number): boolean;
};

function doSomething(fn: DescribableFunction) {
    console.log(fn.description + " returned " + fn(6));
}

/**
 * 以下三种方式都可以
 */

//函数声明
// function myFunction(someArg: number): boolean {
//     return someArg > 0;
// };

//函数表达式(匿名函数)
// const myFunction = function(someArg:number):boolean {
//     return someArg >0;
// }

//箭头函数
const myFunction = (someArg:number):boolean => {
    return someArg >0;
}

myFunction.description = "This is my function";

doSomething(myFunction); //This is my function returned true

  声明引用数据类型 最好使用const声明

        在这个例子中,我们首先定义了 DescribableFunction 类型别名,表示具有特定属性和调用签名的对象类型。

        然后,我们定义了一个名为 doSomething 的函数,它接受一个参数 fn,类型为 DescribableFunction。在函数内部,我们通过访问 fn.description 属性和调用 fn(6) 函数来打印一些信息。

        接下来,我们声明了一个变量 myFunction,类型为 DescribableFunction。我们给 myFunction 赋值一个函数,该函数接受一个参数,并返回一个布尔值。我们还为 myFunction 添加了一个 description 属性。

        最后,我们调用了 doSomething(myFunction),将 myFunction 作为参数传递给 doSomething 函数。doSomething 函数会打印 myFunction.description 的值以及调用 myFunction(6) 的结果。

这样定义的对象类型允许我们将对象作为函数进行调用,并使用对象的属性。

语法解释

  • 对象类型中函数的声明语法: 在 TypeScript 中,使用 type 关键字可以创建类型别名(type alias)。类型别名用于给一个类型起一个新的名称,以方便在代码中重复使用。在这个例子中,DescribableFunction 是一个类型别名,用于表示特定的函数类型。函数类型可以包含参数列表和返回类型,因此函数类型的声明中需要指定参数和返回类型。语法上,函数类型的声明中的参数和返回类型之间使用箭头 => 分隔。这种函数类型的声明语法允许我们定义具有特定参数和返回类型的函数类型。

  • fn(6) 中的 fn 不是对象,为什么可以调用? 在这个例子中,fn 实际上是一个函数,而不是一个对象。在 JavaScript 和 TypeScript 中,函数也是一种特殊的对象。因为函数是对象,所以它们可以具有属性。在代码中,通过 fn(6) 可以调用函数 fn,并将 6 作为参数传递给它。这是 JavaScript/TypeScript 中调用函数的语法。

  • myFunction 是普通函数,为什么能有 .属性? 在 JavaScript 中,函数也是对象,因此可以给函数对象添加属性。JavaScript 中的对象是一种键值对的集合,可以动态地向对象中添加属性。在这个例子中,通过 myFunction.description = 'balabala...',我们给函数 myFunction添加了一个名为 description 的属性,并赋予了一个字符串值。这是 JavaScript 允许的动态属性赋值的语法。

五、构造签名

        在TypeScript(TS)中,构造签名是用于描述类的构造函数的类型签名。它定义了类实例化时构造函数的参数类型和返回类型。

构造签名使用特殊的名称"new"来表示构造函数,并且与类名相似。它的语法如下:

new (args: T): ClassName;

        在上述语法中,"args"表示构造函数的参数列表,"T"表示参数的类型,"ClassName"表示类的名称。构造函数可以接受任意数量的参数,并且参数的类型可以是基本类型、自定义类型或其他类型。

下面是一个示例,展示了如何在TS中使用构造签名:

class Person {

    name: string

    age : number

    constructor(name: string, age: number) {
        // 构造函数逻辑
        this.name = name;
        this.age = age;
    }
}

//方法一:
const person: Person = new Person('jzq',27);

console.log(person.name);

//方法二:
type PersonConstructor = new (name: string, age: number) => Person;

const createPerson: PersonConstructor = Person;

const person1 = new createPerson("John", 25);

console.log(person1.name);

        在上述示例中,我们定义了一个名为PersonConstructor的类型,它是一个构造签名。然后,我们将Person类赋值给createPerson变量,这样createPerson就可以用于实例化Person类的对象了。最后,我们使用createPerson来创建一个名为person的实例。

        通过使用构造签名,我们可以在TypeScript中对类的构造函数进行类型检查和静态分析,从而提供更好的代码提示和类型安全性。

1、案例一

//定义一个类 Ctor
class Ctor {
    //实例变量
    s: string
    //构造方法
    constructor(s: string) {
        this.s = s
    }
}

//声明一个对象对象,里面有函数调用签名
type SomeConstructor = {
    new(s: string): Ctor
}

/**
 * 
 * @param ctor 为SomeConstructor
 * @returns 返回值类型为 Ctor
 */
function fn(ctor: SomeConstructor) {
    return new ctor("hello")
}

const f = fn(Ctor)
console.log(f.s)

2、案例二

//表示 ClockConstructor类型覆盖ClockInterface
interface ClockConstructor {
    new(hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
    tick(): void;
}

function createClock(
    ctor: ClockConstructor,
    hour: number,
    minute: number
): ClockInterface {
    return new ctor(hour, minute);
}

//表示 ClockInterface类型覆盖DigitalClock
class DigitalClock implements ClockInterface {
    tick() {
        console.log("beep beep");
    }
}

//表示 ClockInterface类型覆盖AnalogClock
class AnalogClock implements ClockInterface {
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
digital.tick()

let analog = createClock(AnalogClock, 7, 32);
analog.tick()

这段代码展示了如何使用接口和工厂函数来创建不同类型的时钟对象。

首先,定义了两个接口:ClockConstructorClockInterface

ClockConstructor 接口描述了一个构造函数的签名,它接受两个参数 hourminute,并返回一个符合 ClockInterface 接口的对象。这意味着任何一个实现了 ClockInterface 接口的类可以作为构造函数传递给 ClockConstructor

ClockInterface 接口定义了一个 tick 方法,它表示时钟的滴答声或者操作。

接下来,定义了一个 createClock 函数,它接受三个参数:ctorhourminutector 参数是一个符合 ClockConstructor 接口的构造函数,hourminute 是用于初始化时钟对象的参数。在函数内部,通过调用传入的构造函数 ctor 来创建一个时钟对象,并将其返回。

然后,定义了两个类 DigitalClockAnalogClock,它们都实现了 ClockInterface 接口,并实现了 tick 方法。

最后,通过调用 createClock 函数,我们创建了一个 DigitalClock 对象和一个 AnalogClock 对象,并分别赋值给 digitalanalog 变量。然后,调用 tick 方法来触发时钟对象的操作。

六、泛型函数

在写一个函数时,输入的类型与输出的无关,或者两个输入的类型以某种方式相关,这是常见的。

function firstElement(arr: any[]) {
    return arr[0];
}

const arrayList = [1,"s",true]

console.log(firstElement(arrayList));

        这个函数完成了它的工作,但不幸的是它的返回类型是:any,如果该函数返回数组元素的类型会更好。在TS中,当我们想描述两个值之间的对应关系时,会使用泛型。我们通过在函数签名中声明一个类型参数来做到这一点:

function firstElement<T>(arr: T[]): T | undefined {
    return arr[0];
}

const arrayList:string[] = ["a","b","c"];

console.log(firstElement<string>(arrayList)) //a

通过给这个函数添加一个类型参数T,并在两个地方使用它,我们已经在函数的输入(数组)和输出(返回值)之间建立了一个联系,当我们调用它时:一个更具体的类型就出来了:

// s 是 'string' 类型
const s = firstElement(["a", "b", "c"]);
console.log(s) //a

// n 是 'number' 类型
const n = firstElement([1, 2, 3]);
console.log(n) //1

// u 是 undefined 类型
const u = firstElement([]);
console.log(u) //undefined

1、类型推断

请注意:在这个例子中,我们没必要指定类型,类型是由TS自动推断出来的。

我们也可以使用多个类型参数,例如:一个独立版本的map

/**
 * 
 * @param arr 类型为Input[]
 * @param func 类型为(arg: Input) => Output,函数类型表达式
 * @returns 返回值类型为Output[]
 */
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
    return arr.map(func);
}

// 参数'n'是'字符串'类型。
// 'parsed'是'number[]'类型。
const parsed = map<string, number>(["1", "2", "3"], (n): number => Number.parseInt(n) * 2);
console.log(parsed) //[ 2, 4, 6 ]

如在调用map函数时,不指定类型参数,TS也会自动推断类型为 string和number

2、泛型约束(extends)

        在 TypeScript 中,泛型约束(Generic Constraints)用于限制泛型参数可以接受的类型范围。通过泛型约束,我们可以指定泛型参数必须具备某些特定的属性、方法或遵循特定的接口。

        使用泛型约束的语法是在泛型参数后面使用 extends 关键字加上约束条件,指定允许传递给泛型的类型。例如:

function functionName<T extends ConstraintType>(arg: T): void {
  // 函数体
}

下面是一些关于泛型约束的详细说明:

  1. 约束类型(Constraint Type): 约束类型是指通过 extends 关键字指定的约束条件。它可以是一个具体的类型,也可以是一个接口或抽象类。

  2. 泛型参数(Generic Parameter): 泛型参数是函数或类中定义的用于表示参数类型或返回值类型的占位符。它们在尖括号 < > 内声明,并可以在函数参数、返回值或类成员中使用。

  3. 类型范围限制: 泛型约束可以限制泛型参数只能接受满足约束条件的类型。这意味着传递给泛型的实际类型必须具备约束类型所要求的属性、方法或遵循接口的规范。

  4. 访问约束类型的属性和方法: 在泛型约束中,可以访问泛型参数的属性和方法,前提是该属性或方法属于约束类型。这样可以在函数体内安全地使用泛型参数的属性和方法,而无需进行额外的类型断言。

下面是一个示例,演示如何使用泛型约束:

interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
    console.log(arg.length);
}

logLength('Hello'); // 输出: 5,因为字符串类型具有 length 属性
logLength([1, 2, 3]); // 输出: 3,因为数组类型具有 length 属性
logLength(10); // 编译错误,因为数字类型没有 length 属性

        在上面的示例中,我们定义了一个名为 Lengthwise 的接口,它具有一个 length 属性,其类型为数字。然后,我们使用泛型约束 <T extends Lengthwise> 来限制 logLength 函数的泛型参数 T 必须满足 Lengthwise 接口的要求,即具有 length 属性且属性类型为 number

        通过使用泛型约束,我们确保了在函数体内访问 arglength 属性是安全的,并且只有满足约束类型的实际类型才能传递给该函数。

        总结起来,泛型约束提供了一种方式来限制泛型参数的类型范围,从而增加代码的类型安全性和灵活性。它可以确保泛型参数满足特定的条件,并在使用泛型时提供更准确的类型推断和类型检查。

        我们已经写了一些通用函数,可以对任何类型的值进行操作。有时我们想把两个值联系起来,但只能对某个值的子集进行操作。在这种情况下,我们可以使用一个约束条件来限制一个类型参数可以接受的类型。

        写一个函数,返回两个值中较长的值。要做到这一点,我们需要一个长度属性,是一个数字类型。我们通过写一个扩展子句将类型参数限制在这个类型上。

/**
 * 类型参数T必须有length属性,且length属性值的类型必须为number
 * @param a 
 * @param b 
 * @returns 
 */

type myType = { length: number };

function longest<T extends myType>(a: T, b: T): T {
    if (a.length >= b.length) {
        return a;
    }
    return b;
}

// longerArray 的类型是 'number[]'
const longerArray = longest<number[]>([1, 2], [1, 2, 3]);
console.log(longerArray); //[ 1, 2, 3 ]

// longerString 是 'alice'|'bob' 的类型。
const longerString = longest<string>("alice", "bob");
console.log(longerString) //alice

// 错误! 数字没有'长度'属性
// const notOK = longest(10, 100);

        在这个例子中,有一些有趣的事情需要注意。我们允许TS推断longest的返回类型,返回类型推断也适用于通用函数。

        因为我们将T类型参数约束为{length:number},所以我们被允许访问a和b参数的length属性。如果没有类型约束,我们就不能访问这些属性,因为这些值可能就没有length属性。

longestArray和longerString的类型是根据参数推断出来的。记住,泛型就是把两个或多个具有相同类型的值联系起来。

最后,正如我们所希望的,对longest(10,100)的调用被拒绝了,因为number类型就没有length属性

3、使用受限值

这里有一个使用通用约束条件时的常见错误。

function minimumLength<Type extends { length: number }>(
    obj: Type,
    minimum: number
): Type {
    if (obj.length >= minimum) {
        return obj
    } else {
        return { length: minimum }
    }
}

        遇到了报错,可能是因为 TypeScript 编译器无法推断出正确的返回类型。在这种情况下,你可以明确指定返回类型为 Type,如下所示:

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum } as Type;
  }
}

        在上面的代码中,我使用了类型断言 as Type 来告诉编译器返回的对象类型是泛型参数 Type。这样就能够解决编译器报错的问题。

        请注意,使用类型断言时需要确保返回的对象类型与泛型参数 Type 是兼容的。如果返回的对象类型与 Type 不兼容,可能会导致运行时错误或类型不匹配。

4、指定类型参数

TS通常可以推断出通用调用中的预期类型参数,但并非总是如此

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
    return arr1.concat(arr2);
}

const arr = combine([1, 2, 3], ["hello"]);

通常情况下,用不匹配的数组调用这个函数是一个错误:

这个时候就需要手动指定类型参数

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
    return arr1.concat(arr2);
}

const arr = combine<number | string>([1, 2, 3], ["hello"]);
console.log(arr); //[ 1, 2, 3, 'hello' ]

 5、编写优秀通用函数的准则

编写泛型函数很有趣,而且很容易被类型参数所迷惑,有太多的类型参数火灾不需要的地方使用约束,会使推理不太成功。

5.1、类型参数下推

下面是两种看似相同的函数写法

function firstElement1<Type>(arr: Type[]) {
    return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
    return arr[0];
}

// a: number (推荐)
const a = firstElement1([1, 2, 3]);
// b: any (不推荐)
const b = firstElement2([1, 2, 3]);

乍一看,这些可能是相同的,当时firstElement1是更好的

 因为TS必须使用约束类型来解析判断arr[0]的类型,而不是在运行期间“等待”解析该元素。

5.2、使用更少的类型参数

下面是另一队类似的函数。

//按照一个类型参数声明
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
    return arr.filter(func);
}
//调用
const numbers1:number[] = filter1<number>([1,2,3,4,5,6,7],(arg) => {return arg > 2})
console.log(numbers1) //[ 3, 4, 5, 6, 7 ]

//按照两个类型参数声明
function filter2<Type, Func extends (arg: Type) => boolean>(
    arr: Type[],
    func: Func
): Type[] {
    return arr.filter(func);
}
//调用
const numbers2:number[] = filter2<number,(arg: number) => boolean>([1,2,3,4,5,6,7],(arg) => {return arg <4})
console.log(numbers2) //[ 1, 2, 3 ]

        我们已经创建了一个类型参数Func,它并不涉及两个类型,这是个坏习惯,因为它意味着想要指定类型的调用者必须无缘无故的手动指定一个额外的类型参数。Func除了是函数更难阅读和推理外,没有什么用处。

总是尽可能少的使用类型参数

5.3、类型参数应出现两次,只出现一次的情况不需要泛型

有时我们会忘记,一个函数可能不需要通用的

//没有必要使用泛型
function greet<T extends string>(s: T) {
    console.log("Hello, " + s);
}

greet("world");

我们完全可以写一个更简单的版本

function greet(s: string) {
    console.log("Hello, " + s);
}

greet("world");

注意:类型参数是用来关联多个值的类型的。如果一个类型参数在函数签名中只使用一次,那么没必要使用泛型

六、可选参数

 可选参数:为了解决在函数传参的时候,某些参数可以不用传递,我们就需要可选参数了。

function getInfo(name: string, age?: number): string {
    return `${name} --- ${age}`;
}

console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误

注意:可选参数必须配置到参数的最后面。

        JS中的函数经常需要一个可变数量的参数。例如:number的toFixed方法需要一个可选的数字计数。

function f(n: number) {
    console.log(n.toFixed()); // 0 个参数
    console.log(n.toFixed(3)); // 1 个参数
}

我们可以在TS中使用 ? 标记来表示该参数可选:

function fn(x?:number){
    //函数体。。。
    console.log(x)
}
fn(); //正确
fn(10); //正确

虽然参数被指定为number类型,但是x参数实际上具有 number | undefined类型,因为JS中未指定的参数会得到undefined的值

注意:当一个参数是可选的,调用者总是可以传递undefined参数,因为这是模拟一个“丢失”的 参数

function fn(x?:number ){
    //函数体。。。
    console.log(x)
}
//以下调用都是正确的
fn();
fn(10);
fn(undefined)

6.1、回调中的可选参数

一旦你了解了可选参数和函数类型表达式,在编写调用回调的函数时就容易犯以下错误:

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
    for (let i = 0; i < arr.length; i++) {
        callback(arr[i], i); //实参传入
    }
}

我们在写index?作为一个可选参数时,通常是想让这些调用都合法的

myForEach([1, 2, 3], (a) => console.log(a)); //定义形参,实参数量比形参数量多
myForEach([1, 2, 3], (a, i) => console.log(a, i)); //定义形参,实参数量等于形参数量

这只是将传入的实参简单进行输出,如果实参传入数量不够,或者未传入,则会有undefined替代,也不会报错,但是如果针对传入的实参调用其方法,这时候如果不传入,或者传入undefined就有问题了

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
    for (let i = 0; i < arr.length; i++) {
        //例如,这里修改不传入索引i,但是还有调用i的方法
        callback(arr[i]); //实参传入
    }
}

调用i的方法:

myForEach([1, 2, 3], (a, i) => {
    console.log(i.toFixed()); 
    //要么修改如下:
    // console.log(i?.toFixed()); 
})

 在JS中,如果你调用一个实参数量少于形参数量的函数,额外的参数会被简单的忽略,TS的行为也是如此。因此,当为回调写一个函数类型是,永远不要写一个可选参数。

七、必选参数

        必选参数:在调用函数的时候,必须要传入的参数,参数列表里边的参数默认就是必选参数,只要在声明的时候写了参数,在传递的时候,就必须传入参数,而且,实参与形参的数量与类型要一致。

function getInfo(name: string, age: number): string {
    return `${name} --- ${age}`;
}

console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 错误
console.log(getInfo(28)); // 错误

八、默认参数

        默认参数:为了解决在函数传参的时候,某些参数可以不用传递,但是我们又需要该参数的值,这时候我们就需要给这个参数设定一个默认值也叫初始化值,就得用到默认参数了。

function getInfo(name: string, age: number = 20): string {
    return `${name} --- ${age}`;
}

console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误

注意:可选参数不能够进行初始化值的设定。 

九、函数重载

调用重载函数时,传入的参数必须包含在函数重载签名的类型中,比如:函数重载签名中形参的个数有2个和4个,则调用重载函数时,传入的参数要么是2个要么是4个,不能是其他值。

1、概述

        一些JS函数可以在不同的参数数量和类型中被调用,例如,你可能会写一个函数来产生一个Date,它需要一个时间戳(一个参数)或者一个 月/日/年 规格(三个参数)

        在TS中,我们可以通过编写重载签名来制定一个可以以不同方式调用的函数。要做到这一点,要写一些数量的函数重载签名(通常两个或者更多),然后是函数的主体:

//函数重载签名
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;

//函数实现签名
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
    if (d !== undefined && y !== undefined) {
        return new Date(y, mOrTimestamp, d);
    } else {
        return new Date(mOrTimestamp);
    }
}

const d1 = makeDate(12345678);
console.log(d1) //1970-01-01T03:25:45.678Z

const d2 = makeDate(5, 5, 5);
console.log(d2) //1905-06-04T16:00:00.000Z

/**
 * 编译报错:没有需要 2个参数的重载,但存在需要 1个或 3个参数的重载
 * 因为函数重载签名中没有是2个参数的
 */
// const d3 = makeDate(1,5);

        在这个例子中,我们写了两个重载:一个接受一个参数,另一个接受三个参数。这前两个签名被称为重载签名。

        然后,我们写了一个具有兼容签名的函数实现。即使我们写了一个必选参数之后又两个可选参数,但因为其是重载函数,也不也能以两个参数来调用

1、函数重载签名

1、函数重载声明指的是多个具有相同名称但参数类型和返回值类型不同的函数声明,没有函数执行体

2、在函数重载声明中,可以定义任意数量的函数签名,用于描述不同的参数类型和返回值类型组合

2、函数实现签名

1、重载函数签名是函数重载声明的一部分,它用于描述函数的不同参数类型和返回值类型组合。

2、重载函数签名的作用是为函数的不同调用提供类型约束,以便在调用函数时能够进行更精确的类型检查。

3、重载函数签名:就是把声明中出现的参数都写出来,如果可选,就使用可选参数,一个变量名可以使用多种类型用组合类型

函数重载声明可以分开实现,如下所示:

//重载函数声明
function funcName(arg1: Type1, arg2: Type2): ReturnType1;
function funcName(arg1: Type1, arg2: Type2, arg3: Type3): ReturnType2;
function funcName(arg1: Type1, arg2: Type2, arg3: Type3, arg4: Type4): ReturnType3;
// ...

//重载函数签名
function funcName(arg1: Type1, arg2: Type2, arg3?: Type3, arg4?: Type4): any {
  // 具体实现代码
}

        这里定义了多个函数重载声明,每个函数重载声明都描述了不同的参数类型和返回值类型组合。在最后一个函数重载声明中,参数 arg3arg4 都被定义成了可选参数,实现代码也被定义在最后一个函数重载声明中。

        重载签名和实现签名通常是一个常见的混乱来源。通常我们会这样写代码,却不明白为什么会出现错误:

//重载签名
function fn(x: string): void;
//实现签名
function fn() {
    // ...
}
// 期望能够以零参数调用
fn();

//修改为:
//重载签名
function fn(x: string): void;
//实现签名
function fn(x: string) {
    // ...
}
fn("123");

        实现签名也必须与重载签名兼容。例如,这些函数有错误,因为实现签名没有以正确的方式匹配重载:

function fn(x: boolean): void;
// 参数类型不正确
function fn(x: string): void;
function fn(x: boolean) { }


//应该修改为:
function fn(x: boolean): void;
function fn(x: string): void;
function fn(x: boolean | string) { }

 返回值类型也得兼容

function fn(x: string): string;
// 返回类型不正确
function fn(x: number): boolean;
function fn(x: string | number) {
    return "oops";
}

应修改为:

function fn(x: string): string;
// 返回类型不正确
function fn(x: number): boolean;
function fn(x: string | number):boolean | string {
    return "oops";
}

3、编写好的重载的原则

        每调用一次实现签名,就会对应查找一次重载签名,且传入的参数类型及返回值类型必须和众多重载签名中的一个相同。

        和泛型一样,在使用函数重载时,有一些准则应该遵循,遵循这些原则将使你的函数更容易调用,更容易理解,更容易实现。

让我们考虑一个返回字符串或数组长度的函数:

function len(s: string): number;
function len(arr: any[]): number;

function len(x: any) {
    return x.length;
}

这个函数是好的,我们可以 string 或者 any[] 类型来调用,但是不能使用  string | any[] 类型来调用,因为TS只能将一个函数调用解析为一个重载

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); //编译报错

因为两个重载都有相同的参数数量和相同的返回值类型,我们可以改写一个非重载版本的函数:

//不使用重载
function len(x: string | any[]): number {
    return x.length;
}

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); //ok

在可能的情况下,总是倾向于使用联合类型的参数而不是重载参数 

4、函数内的this声明

TS会通过代码流分析来推断函数中的this应该是什么,比如下面的例子:

const user = {
    id: 123,
    admin: false,
    becomeAdmin: function () {
        this.admin = true;
    },
};

console.log(user.admin); //false
user.becomeAdmin();
console.log(user.admin); //true

        TS理解函数user.becomeAdmin有一个对应的this,它指向外部对象user。这个对于很多情况来说已经足够了,但是有很多情况下你需要更多的控制this代表什么对象。JS规范规定,不能有一个叫this的参数,所以TS使用了这个语法空间,让你在函数中声明this的类型。

1、this作为函数参数时,必须手动调用call方法传入上下文对象,且该函数不能是箭头函数,因为箭头函数本身没有绑定this对象,可以是函数声明或是函数表达式

2、只要是this作为函数参数,都必须手动传入this上下文对象

interface User {
    admin: boolean
}
interface DB {
    filterUsers(filter: (this: User) => boolean): boolean[];

    aa:number;
}

const db: DB = {
    filterUsers: function(filter: (this: User) => boolean): boolean[]  {
        const user1: User = {
            admin: true
        }
        const user2: User = {
            admin: false
        }

        console.log(this.aa)
        
        //编译报错:未传入this
        //filter()

        //需要手动给它传入this
        return [filter.call(user1), filter.call(user2)]
    },

    aa:100
}

//此处不能使用箭头函数,因为箭头函数没有绑定this,也没有原型
// const admins = db.filterUsers(function (this:User) {
//     return this.admin;
// })

//这种写法也可以
const fn = function(this:User):boolean{
    return this.admin;
}

const admins = db.filterUsers(fn);

console.log(admins) //[ true, false ]

代码解释:

        在这段 TypeScript 代码中,this 关键字的用法是为了指定函数内部的上下文对象。具体来说,在 db.filterUsers 方法中,this 关键字被用来指向调用该方法的对象,也就是 db 对象本身。

让我们逐步分析这段代码:

  1. 首先,我们定义了一个接口 User,它表示一个用户对象,具有一个布尔类型的 admin 属性。

  2. 接下来,我们定义了一个接口 DB,它表示一个数据库对象,具有一个名为 filterUsers 的方法。该方法接受一个函数类型的参数 filter,该函数以 User 对象为上下文(即 this),并返回一个布尔值。方法 filterUsers 的返回类型是 boolean[],表示返回一个布尔l数组。还有一个属性aa,值为number类型

  3. 然后,我们创建了一个名为 db 的对象,它符合接口 DB 的定义。在 db 对象中,我们实现了 filterUsers 方法,并在方法的实现中创建了两个 User 对象 user1user2,然后将它们的属性admin作为数组返回。

  4. 最后,我们调用 db.filterUsers 方法,并传入一个匿名函数作为参数。这个匿名函数被定义为 (this: User) => boolean,表示该函数期望以 User 对象为上下文。在该函数内部,我们使用 this.admin 来访问调用上下文对象的 admin 属性,并返回它。

        总结来说,通过在函数类型参数中使用 (this: User) => boolean 的语法,我们明确指定了该函数的上下文对象应该是一个 User 类型的对象。在调用 db.filterUsers 时,实际上就是将 db 对象作为上下文传递给了该函数。

        通过使用 this 关键字和函数类型参数中的 (this: User) 语法,我们可以在 TypeScript 中明确指定函数的上下文对象,并在函数内部使用该上下文对象的属性和方法。这种用法可以帮助我们更加灵活地操作对象的属性和行为

        在 TypeScript 中,通过使用 (this: User) => boolean 的函数类型定义来约束函数的上下文对象,确保函数内部的 this 指向的是一个 User 类型的对象。

        当我们调用 db.filterUsers 方法时,该方法会将 db 对象作为上下文传递给传入的函数。换句话说,函数内部的 this 指向的是 db 对象。然而,由于函数类型定义中指定了 (this: User),TypeScript 会在编译时进行类型检查,确保函数的上下文对象符合 User 类型。

        因此,在实际运行时,即使 db 对象会自动作为上下文传递给函数,但是由于this被指定为User,所以需要手动传入,而不是 db 对象。

        在 db.filterUsers 的实现中,我们定义了一个匿名函数 (filter: (this: User) => boolean) => {} 作为 filterUsers 方法的实现。在这个匿名函数中,我们调用了 filter(),但没有传递上下文对象给它。

        由于函数类型 (this: User) => boolean 中指定了该函数需要以 User 对象为上下文,我们必须在调用 filter() 时提供相应的上下文对象,否则 TypeScript 编译器会报错。

        所以,具体到这段代码中的 db.filterUsers 方法中的匿名函数 (this: User) => booleanthis 指向的是 User 对象

5、案例一

function toBoolean(value: string): boolean;
function toBoolean(value: number): boolean;
function toBoolean(value: string | number): boolean {
  if (typeof value === 'string') {
    return value.toLowerCase() === 'true';
  } else {
    return value !== 0;
  }
}

console.log(toBoolean('true')); // 输出 true
console.log(toBoolean(1));      // 输出 true
console.log(toBoolean(0));      // 输出 false

        在这个示例中,我们定义了两个函数签名:一个参数为字符串,返回值为布尔值;另一个参数为数字,返回值为布尔值。然后我们在最后一个函数签名中实现了具体的转换逻辑,根据传入的参数类型进行相应的类型转换并返回布尔值。

        这样,在调用 toBoolean 函数时,TypeScript 可以根据参数类型的精确匹配来选择相应的函数签名进行调用,从而实现更准确的类型检查。

十、和函数有关的额其他类型

        有一些额外的和函数有关的其他类型,他们在处理函数类型时经常出现。像所有的类型一样,你可以在任何地方使用它们,但这些类型在函数的上下文中特别相关。

1、void

void表示没有返回值的函数的返回值。当一个函数没有任何返回语句,或者没有从这些返回语句中返回任何明确的值时,它都是推断出来的类型。

// 推断出的返回类型是void
function noop() {
    return;
}

2、object

        特殊类型object指的是任何不是基元的值(string,number,boolean,null,undefined,

bigint,symbol)。这与空对象类型{ } 不同,也与全局类型Object不同。

3、unknown

        unknown类型代表任何值,这与any类型很类似,但更安全,因为对未知 unknown值做任何事情都是不合法的。

function f1(a: any) {
    a.b(); // 正确
}

function f2(a: unknown) {
    a.b();
}

        这在描述函数类型时很有用,因为你可以描述接受任何值的函数,而不需要在函数体内有any值,反之,也可以描述一个返回未知类型的值的函数:

function safeParse(s: string): unknown {
    return JSON.parse(s);
}
let a = "10";

const obj = safeParse(a);
console.log(obj)

4、never

当函数执行抛出异常时,表示永远不会返回一个值:

function fail() : never {
    throw new Error("出错了");
}

fail();

5、Function        

        全局性的Function类型描述了诸如bind,call,apply和其他存在于JS中所有函数值的属性,它还有一个特殊的属性,即Function类型的值总是可以被调用。

function doSomething(f: Function): void {
    f(1, 2, 3);
}

console.log(typeof doSomething)

十一、参数展开运算符

1、形参折叠(Rest Parameters)

        除了使用可选参数或者重在来制作可以接受各种固定参数数量的函数之外,还可以使用休止(剩余)参数来定义接受无限制数量的参数的函数。

注意:剩余参数必须配置到参数的最后面。并使用 ... 语法

        剩余参数:在参数的类型确定而参数个数不确定的情况时,我们需要用到剩余参数,它使用 ... 将接收到的参数传到一个指定类型的数组中。

function multiply(n: number, ...m: number[]) {
    return m.map((x) => n * x);
}

// 'a' 获得的值 [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);
console.log(a) //[ 10, 20, 30, 40 ]
//求和算法
function sum(...result: number[]): number {
    let sum = 0;
    for (let i = 0; i < result.length; i++) {
        sum += result[i];
    }
    return sum;
}

console.log(sum(1, 2, 3, 4, 5, 6)); //21
function sum(init: number, ...result: number[]): number {
    let sum = init;
    for (let i = 0; i < result.length; i++) {
        sum += result[i];
    }
    return sum;
}

console.log(sum(100, 1, 2, 3, 4, 5, 6)); //121

2、实参展开(spread Arguments)

        反之,我们可以使用spread语法从数组中提供可变数量的参数。例如,数组的push方法需要任意数量的参数。

const array1:number[] = [1,2,3];
const array2:number[] = [7,8,9];

let result: number = array1.push(...array2);
console.log(result); //6,输入添加元素后当前数组中的元素个数
console.log(array1); //[ 1, 2, 3, 7, 8, 9 ]

另外,在类型推断中,number[ ] 只是表示一个数组中有0个或多个number类型的元素,并不会限定元素个数,如下:

// 推断的类型是 number[] -- "一个有零个或多个数字的数组"。
// 不专指两个数字
const args = [8, 5];
//但是Math.atan2()方法只需要两个参数,所以会报错
const angle = Math.atan2(...args);

修改如下:

// 推断的类型是 number[] -- "一个有零个或多个数字的数组"。
// as const 声明上下文 context 将数组中的元素个数限定为2个
const args = [8, 5] as const;
const angle = Math.atan2(...args);

十二、参数解构

可以使用参数重构来方便的将作为参数提供的对象,解压到函数主体的一个或多个局部变量中。

function sum({ a, b, c }) {
    console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 }); //22

对象的类型注解在解构的语法之后:

function sum({ a, b, c }: { a: number, b: number, c: number }): void {
    console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 }); //22

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值