学习TypeScript4这一篇就够了_轻松的小希的博客-优快云博客
最全的TypeScript学习指南_油墨香^_^的博客-优快云博客
第一章 TypeScript简介
一、TypeScript简介
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
2、1997年:ECMAScript

3、2015年:TypeScript
个人感觉它俩之间的关系有点类似 C 和 C++ 之间的关系,语法风格更类似 Java、C# 。
三、TypeScript 与 JavaScript 的区别
四、发现问题
// 在 'message' 上访问属性 'toLowerCase',并调用它
message.toLowerCase();
// 调用 'message'
message();
- 可以调用 message 吗?
- 它有 toLowerCase 这个属性吗?
- 如果能, toLowerCase 可以调用吗?
- 如果这两个值都是可调用的,它们返回什么?
const message = "Hello World!" ;
TypeError: message is not a function
function fn ( x ) {return x . flip ();}
五、静态类型检查
六、非异常故障
规范说尝试调用不可调用的东西应该抛出错误。也许这听起来像是“明显的行为”,但您可以想象访问对象上不存在的属性也应该抛出错误。相反,JavaScript 给了我们不同的行为并返回值
const user = {name : " 小千 " ,age : 26 ,};user . location ; // 返回 undefined
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的执行原理可以简单概括为以下几个步骤:
- 编写TypeScript代码并进行类型注解。
- 使用TypeScript编译器将TypeScript代码转换为JavaScript代码。编译器会将类型注解去除,并将TypeScript特有的语法转换为JavaScript语法。
- 将生成的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.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
十、显式类型
function greet ( person : string , date : Date ) {console . log ( `Hello ${ person }, today is ${ date . toDateString () }!` );}
function greet ( person : string , date : Date ) {console . log ( `Hello ${ person }, today is ${ date . toDateString () }!` );}greet ( " 小锋 " , new Date ());

成功的编译输出了 hello.js 。
十一、隐式类型

十二、擦除类型

请记住:类型注释永远不会改变程序的运行时行为
十三、降级编译
`Hello ${ person }, today is ${ date . toDateString () }!`
到:
"Hello " + person + ", today is " + date . toDateString () + "!" ;

十四、严格模式

1、noImplicitAny

2、strictNullChecks

第二章 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、函数
- 参数类型注释
// 参数类型定义function greet ( name : string) {console . log ( "Hello, " + name . toUpperCase () + "!!" );}
当参数具有类型注释时,将检查该函数的参数:
// 如果执行,将是一个运行时错误!greet ( 42 );
即使您的参数上没有类型注释,TypeScript 仍会检查您是否传递了正确数量的参数
- 返回类型注释
function getFavoriteNumber (): number {return 26 ;}
- 匿名函数
// 这里没有类型注释,但是 TypeScript 可以发现错误const names = [ "Alice" , "Bob" , "Eve" ];// 函数上下文类型names . forEach ( function ( s ) {console . log ( s . toUppercase ());});// 上下文类型也适用于箭头函数names . forEach (( s ) => {console . log ( s . toUppercase ());})

4、对象类型
// 参数的类型注释是对象类型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);
4.1、可选属性
function printName ( obj : { first : string; last ? : string }) {// ...}// 两种传递参数都可以printName ({ first : "Felix" });printName ({ first : "Felix" , last : "Lu" });
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 });
七、特殊数据类型
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、文字类型
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" );

6.1、文字推理
const obj = { counter : 0 };if ( someCondition ) {obj . counter = 1 ;}
function handleRequest ( url : string, method : 'GET' | 'POST' | 'GUESS' ) {// ...}const req = { url : 'https://example.com' , method : 'GET' };handleRequest ( req . url , req . method );

// 方案 1:const req = { url : "https://example.com" , method : "GET" as "GET" };// 方案 2handleRequest ( req . url , req . method as "GET" );
const req = { url : "https://example.com" , method : "GET" } as const ;handleRequest ( req . url , req . method );
第三章 数据类型声明
在 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);
特殊情况:
function printId ( id : number | string) {console . log ( id . toUpperCase ());}
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 );}}
// 返回类型推断为 number[] | stringfunction 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
对象时,你可以添加age
、id
或任何其他属性,它们的类型可以是任何类型,因为类型声明中使用的索引签名允许你这样做。这样设计的目的是让这个类型变得更加灵活,适用于需要存储任意数量和名称属性的场景。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 中,
interface
和type
都用于定义类型,但它们有一些区别和不同的用途。
1、interface
是 TypeScript 中用于定义对象类型、类的契约或合同的关键字。它可以描述对象的结构、属性、方法和类的实现细节。可以通过接口来定义对象的形状,并使代码更具可读性和可维护性。
2、type
是TypeScript 中用于定义类型别名的关键字。它允许给现有类型起一个新的名字。使用type
可以创建复杂的类型,包括联合类型、交叉类型和元组类型等。类型别名还可以使用泛型和其他类型操作符来定义更加灵活的类型。尽管
interface
和type
在某些方面有重叠的功能,但它们之间也有一些区别:
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
时,可以考虑以下几个因素:
- 如果要描述一个对象的形状或类的契约,优先选择
interface
。interface
更符合面向对象的思维方式,可以清晰地定义对象的结构和方法。
示例:
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,
}
需要注意的是,在大多数情况下,interface
和 type
是可以互换使用的,并且在实际项目中可以根据个人和团队的偏好选择使用哪个。它们的目标是为了提供类型的抽象和复用,使代码更具可读性和可维护性。
五、类型断言
1、普通类型断言
语法:
变量名 或 变量值 as 数据类型; //顺序不能变
<数据类型> 变量名; //顺序不能变
const myCanvas = document . getElementById ( "main_canvas" ) as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement> document.getElementById("main_canvas");
const x = "hello" as number;
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());
}
六、可选运算符(?后缀)
语法:
变量名?:数据类型
表示该变量可选,可以传入,也可以不传入
第四章、类型保护
类型保护(Type Guards)是一种在 TypeScript 中用于缩小变量类型范围的技术。它允许你在特定的条件下,对变量的类型进行判断并缩小类型范围,以便在后续代码块中使用更具体的类型信息。
通过类型保护,你可以告诉 TypeScript 编译器某个变量在特定条件下具有特定的类型,从而获得更准确的类型推断和代码提示。
一、类型缩小
在TypeScript(TS)中,类型缩小(Type Narrowing)是指通过条件判断或类型断言等方式,将一个类型限制为更具体的子类型或取出类型中的特定属性的过程。
类型缩小可以用于在代码中对不同类型的值进行处理,并在不同的上下文中使用它们。这样可以增加代码的可读性和类型安全性,因为在缩小类型之后,TypeScript 编译器能够根据特定类型的属性和方法提供更准确的代码提示和错误检查。
function padLeft(padding: number | string, input: string) {
console.log(padding + 1)
}
function padLeft(padding: number | string, input: string):void {
if (typeof padding === 'number'){
console.log(padding + 1)
}
console.log(padding + input)
}
二、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
的值为 undefined
或 0
或 NaN
或空字符串等 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)
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
是否是 Dog
或 Cat
类的实例。如果是 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
的类型被缩小为 string
或 number
,因此返回的类型是 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()
}
上述代码片段定义了两个类型 Fish
和 Bird
,分别表示鱼和鸟的属性和方法。然后,代码中定义了一个 isFish
函数,用于判断传入的参数是否是鱼类型。函数使用类型谓词 pet is Fish
来缩小参数的类型范围,只有在参数具有 swim
方法时才返回 true
。
接下来,代码定义了一个 getSmallPet
函数,根据条件返回鱼或鸟的实例。在主代码块中,首先调用 getSmallPet
函数获取宠物,然后使用条件语句和类型谓词 isFish
来判断宠物的类型。如果宠物是鱼类型,则可以调用 swim
方法;如果宠物是鸟类型,则可以调用 fly
方法。
这段代码的目的是展示如何使用类型谓词和条件判断来根据类型的不同执行相应的操作。通过使用类型谓词,我们可以在条件判断中缩小变量的类型范围,从而确保只调用特定类型所具有的方法,避免类型错误。
在这个例子中,通过 isFish
函数和条件判断,我们可以在宠物是鱼类型时调用 swim
方法,在宠物是鸟类型时调用 fly
方法,从而保证了类型的安全性和代码的正确性。
1、类型谓词的作用
类型谓词的作用是在 TypeScript 中用于缩小变量的类型范围。通过自定义断言函数并使用类型谓词,我们可以在条件判断中根据特定的类型属性或方法的存在与否,确定变量的更具体的类型。
具体来说,类型谓词的作用包括:
-
类型缩小:通过类型谓词,可以在条件分支中将变量的类型缩小为更具体的类型。这有助于在不同的代码路径上进行更精确的类型推断和类型检查。
-
类型保护:类型谓词提供了一种类型保护机制,使得我们可以根据自定义的断言函数来确定变量的类型。通过类型保护,可以避免不必要的类型错误,并提供更准确的类型信息。
-
智能提示:当变量的类型被缩小为更具体的类型时,编辑器可以根据这些信息提供更准确的代码提示和自动补全,帮助开发者编写正确的代码。
-
可读性和维护性:使用类型谓词可以使代码更加清晰和易读,因为它明确地表达了我们对变量类型的判断和假设,使代码逻辑更易理解和维护。
总之,类型谓词在 TypeScript 中的作用是通过自定义断言函数来缩小变量的类型范围,提供类型保护、智能提示以及改善代码的可读性和维护性。它是一种强大的工具,可以增强类型系统的表现力和可靠性。
2、类型谓词的实现步骤
要实现类型谓词,需要遵循以下步骤:
-
定义一个函数,该函数的参数类型为待判断的变量类型,返回类型为布尔值。这个函数就是类型谓词函数。
-
在类型谓词函数中,使用自定义的逻辑来判断传入的参数是否满足特定的条件,从而确定变量的更具体的类型。
-
在调用类型谓词函数的地方,通过调用语法
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 编译器在条件判断中缩小变量的类型范围。虽然在没有使用类型谓词的情况下也可以进行类型判断,但是类型谓词提供了以下几个优势:
-
类型推断:使用类型谓词可以让 TypeScript 编译器更准确地推断变量的类型。在使用类型谓词后,编译器能够将变量的类型缩小为指定的类型,并在相应的代码分支中提供更准确的类型信息。
-
智能提示:类型谓词的存在可以使编辑器提供更准确的代码提示和自动补全。当变量的类型被缩小为更具体的类型时,编辑器可以根据这些信息提供相关类型的方法和属性,帮助开发者编写正确的代码。
-
类型保护:使用类型谓词可以提供类型保护机制,避免不必要的类型错误。在使用类型谓词进行类型判断后,编译器会在相应的代码块中将变量的类型缩小为指定的类型,从而确保在该代码块中只能使用该类型所具有的方法和属性。
综上所述,使用 pet is Fish
类型谓词可以让 TypeScript 编译器对变量的类型进行更精确的推断和类型检查,并提供更准确的智能提示。它能够增强代码的可读性和类型安全性,帮助开发者避免潜在的类型错误。虽然在某些简单的情况下可能不会有明显的差异,但对于复杂的类型判断和类型缩小,使用类型谓词是一种推荐的做法
十、受歧视的 unions
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
function handleShape(shape: Shape) {
// oops!
if (shape.kind === "rect") {
// ...
}
}
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
}
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
}
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius! ** 2;
}
}
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === "circle") {
// shape: Circle
return Math.PI * shape.radius ** 2;
}
}
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
十一、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
参数来指定形状,以及可选的 radius
和 sideLength
参数用于计算面积。
在 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);
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
,它是一个函数类型,包含了一个调用签名。这个调用签名表示函数接受两个参数 a
和 b
,都是数字类型,返回一个数字类型。
然后,我们创建了一个变量 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()
这段代码展示了如何使用接口和工厂函数来创建不同类型的时钟对象。
首先,定义了两个接口:ClockConstructor
和 ClockInterface
。
ClockConstructor
接口描述了一个构造函数的签名,它接受两个参数 hour
和 minute
,并返回一个符合 ClockInterface
接口的对象。这意味着任何一个实现了 ClockInterface
接口的类可以作为构造函数传递给 ClockConstructor
。
ClockInterface
接口定义了一个 tick
方法,它表示时钟的滴答声或者操作。
接下来,定义了一个 createClock
函数,它接受三个参数:ctor
、hour
和 minute
。ctor
参数是一个符合 ClockConstructor
接口的构造函数,hour
和 minute
是用于初始化时钟对象的参数。在函数内部,通过调用传入的构造函数 ctor
来创建一个时钟对象,并将其返回。
然后,定义了两个类 DigitalClock
和 AnalogClock
,它们都实现了 ClockInterface
接口,并实现了 tick
方法。
最后,通过调用 createClock
函数,我们创建了一个 DigitalClock
对象和一个 AnalogClock
对象,并分别赋值给 digital
和 analog
变量。然后,调用 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 {
// 函数体
}
下面是一些关于泛型约束的详细说明:
-
约束类型(Constraint Type): 约束类型是指通过
extends
关键字指定的约束条件。它可以是一个具体的类型,也可以是一个接口或抽象类。 -
泛型参数(Generic Parameter): 泛型参数是函数或类中定义的用于表示参数类型或返回值类型的占位符。它们在尖括号
< >
内声明,并可以在函数参数、返回值或类成员中使用。 -
类型范围限制: 泛型约束可以限制泛型参数只能接受满足约束条件的类型。这意味着传递给泛型的实际类型必须具备约束类型所要求的属性、方法或遵循接口的规范。
-
访问约束类型的属性和方法: 在泛型约束中,可以访问泛型参数的属性和方法,前提是该属性或方法属于约束类型。这样可以在函数体内安全地使用泛型参数的属性和方法,而无需进行额外的类型断言。
下面是一个示例,演示如何使用泛型约束:
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
。
通过使用泛型约束,我们确保了在函数体内访问 arg
的 length
属性是安全的,并且只有满足约束类型的实际类型才能传递给该函数。
总结起来,泛型约束提供了一种方式来限制泛型参数的类型范围,从而增加代码的类型安全性和灵活性。它可以确保泛型参数满足特定的条件,并在使用泛型时提供更准确的类型推断和类型检查。
我们已经写了一些通用函数,可以对任何类型的值进行操作。有时我们想把两个值联系起来,但只能对某个值的子集进行操作。在这种情况下,我们可以使用一个约束条件来限制一个类型参数可以接受的类型。
写一个函数,返回两个值中较长的值。要做到这一点,我们需要一个长度属性,是一个数字类型。我们通过写一个扩展子句将类型参数限制在这个类型上。
/**
* 类型参数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 {
// 具体实现代码
}
这里定义了多个函数重载声明,每个函数重载声明都描述了不同的参数类型和返回值类型组合。在最后一个函数重载声明中,参数 arg3
和 arg4
都被定义成了可选参数,实现代码也被定义在最后一个函数重载声明中。
重载签名和实现签名通常是一个常见的混乱来源。通常我们会这样写代码,却不明白为什么会出现错误:
//重载签名
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
对象本身。
让我们逐步分析这段代码:
-
首先,我们定义了一个接口
User
,它表示一个用户对象,具有一个布尔类型的admin
属性。 -
接下来,我们定义了一个接口
DB
,它表示一个数据库对象,具有一个名为filterUsers
的方法。该方法接受一个函数类型的参数filter
,该函数以User
对象为上下文(即this
),并返回一个布尔值。方法filterUsers
的返回类型是boolean[]
,表示返回一个布尔l数组。还有一个属性aa,值为number类型 -
然后,我们创建了一个名为
db
的对象,它符合接口DB
的定义。在db
对象中,我们实现了filterUsers
方法,并在方法的实现中创建了两个User
对象user1
和user2
,然后将它们的属性admin作为数组返回。 -
最后,我们调用
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) => boolean
,this
指向的是 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