2023-06-13
TypeScript
00
请注意,本文编写于 305 天前,最后修改于 212 天前,其中某些信息可能已经过时。

目录

常见面试题
TypeScript介绍
设计理念
安装使用
编辑器实时编译
webstorm
vscode配置
为什么要使用TypeScript
TypeScript 的缺点
TypeScrit 特性
TypeScript中的原始数据类型
boolean number string
包装对象
null 和 undefined
空值 void
any
unknow
never类型
类型进阶
联合类型
对象的类型
数组的类型
数组的三种表示法
类数组
函数的类型
元组
参考资料

本文对TS背景进行简单的介绍,用精简的语言和案例阐述了TS的常用类型,是TS入门学习很好的一篇文章。本文主要参考 《ts入门教程》 一书和 《TS常见类型》一文并加入了一些自己的思考和实践及整理了一些比较基础的面试题。

常见面试题

  • TS有哪些基本类型
  • voidundefined的区别
  • unknowany类型的区别
  • never类型有了解吗?TS最底层的类型是什么?

带着问题开始本篇文章的阅读吧

TypeScript介绍

  • TypeScript 是由微软开发的一款开源的编程语言。
  • TypeScriptJavascript 的超级,遵循最新的 ES6+ 规范。TypeScript 扩展了 JavaScript的语法。
  • TypeScript 更像后端 JavaC#这样的面向对象语言可以让 JS 开发大型企业项目。
  • 谷歌也在大力支持 Typescript 的推广,谷歌的 Angular2.x+就是基于 Typescript 语法。
  • 最新的 VueReact 也可以集成 TypeScript

设计理念

有过一定TS经验的同学可以了解一下TS的设计理念,这有助于我们更好的学习和理解TypeScript。

官方TS设计理念介绍

安装使用

  • npm install -g typescript

  • tsc --init 生成配置文件 tsconfig.json

  • tsc helloworld.ts 生成 helloworld.js

  • ts在线编译

  • 运行和断点调试 要安装 ts-node

    • npm install ts-node
    • 运行TS npx ts-node helloword.ts
    • 调试只能在编辑器里调试,跟JS调试一样,
    • (VSCode点击右上角的 运行和调试 --> JavaScript调试终端

编辑器实时编译

webstorm

  • 打开Preferences > Languages & Frameworks > TypeScript配置如下
  • image.png
  • 点击弹窗右下角 OK ,此时会生成 helloWorld.js, 选中它分屏
  • image.png
  • 预览效果

vscode配置

点击菜单 任务-运行任务 点击 tsc:监视-tsconfig.json 然后就可以自动生成代码了

image.png

为什么要使用TypeScript

  1. TypeScript 增加了代码的可读性和可维护性
    • 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
    • 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
    • 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等
  2. TypeScript 非常包容
    • TypeScriptJavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
    • 即使不显式的定义类型,也能够自动做出类型推论
    • 可以定义从简单到复杂的几乎一切类型
    • 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
    • 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
  3. TypeScript 拥有活跃的社区
    • 大部分第三方库都有提供给 TypeScript 的类型定义文件
    • Google 开发的 Angular2 就是使用 TypeScript 编写的
    • TypeScript 拥抱了 ES6 规范,也支持部分 ESNext 草案的规范

TypeScript 的缺点

任何事物都是有两面性的,我认为 TypeScript 的弊端在于:

  1. 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念
  2. 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本
  3. 集成到构建流程需要一些工作量
  4. 可能和一些库结合的不是很完美

TypeScrit 特性

  • 静态类型(编译时还是运行时进行类型检查)
  • 弱类型 (是否允许隐式类型转换)
  • IDE代码补全和接口提示等功能
    • 一些第三方库原生支持了 TypeScript,在使用时就能获得代码补全了,比如 Vue 3.0
    • 如果三方库原生不支持 TypeScript,以通过安装社区维护的类型声明库 比如npm install --save-dev @types/react
  • 完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
  • 可以编译为 JavaScript
  • 配置丰富,类型检查的严格程度由你决定
  • TypeScript与标准同步发展

TypeScript中的原始数据类型

  • 与JS中的原始类型相对应booleannumberstringnullundefinedSymbolBigInt
  • 包装类型也与JS对应(包装类型不是原始类型)
  • void
  • any
  • unknow
  • never

boolean number string

  • 即使不显式的定义类型,也能够自动做出类型推论
  • 声明未赋值等价于 自动推断为any类型
ts
// 不显式的定义类型,也能够自动做出类型推论 let age = 1 // let age: number age = 'zhangsan' // 报错 不能将类型“string”分配给类型“number”。ts(2322) // 声明未赋值 自动推断为any类型 let person; // let person: any

补充知识点:类型推论

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

例如 let age = 1 会自动推断为 age: number

包装对象

我们知道在 JS 中存在包装对象的概念。

当你使用原始类型(字符串、数字、boolean值)调用属性或方法是,JS会创建一个临时的包装对象,使你能够访问课操作原始数据类型的属性和方法。这个过程称为自动包装。

TS中也存在包装对象

  • 原始类型兼容对应的对象包装类型, 对象类型不兼容对应的原始类型。 (子类可以赋值给父类,反之不行,原始类型是父类)
ts
var str = "Hello"; var strObject = new String(str); // 创建一个字符串包装对象 console.log(strObject.length); // 访问字符串的长度属性 console.log(str.length); // 访问字符串的长度属性 // 在TS中也存在包装对象 // 报错 Type 'Boolean' is not assignable to type 'boolean'. let createdByNewBoolean: boolean = new Boolean(1);

null 和 undefined

  • void的区别是 undefinednull 是所有类型的子类型
    • 也就是说 undefined 类型的变量,可以赋值给其他类型的变量(未指定strictNullChecks时)
ts
// 这样不会报错 let num: number = undefined; let u: void; let num2: number = u; // 不能将类型“void”分配给类型“number”。ts(2322)

注意: tsconfig.json指定了"strictNullChecks":true , nullundefined 只能赋值给 anyunknown 和它们各自的类型, undefined可以赋值给void类型。

空值 void

  • JS中void是一个运算符,表示忽略表达式的返回值, 或者或返回值是undefined
  • JS函数总是返回一些东西,没有返回值即返回undefined
  • TS中可以用void表示没有任何返回值的函数, 它是undfined的父类型
  • 未定义返回值自动推断为 void 类型
  • 可以将undefinednull 赋值给void类型(未指定strictNullChecks时)
ts
// 未定义返回值自动推断为 void 类型 function alertName() { alert('My name is Tom'); } var showAlert = alertName(); // var showAlert: void // null 和 undefined 可以赋值给 void let unusable: void = undefined;

那么TS既然有了 undefined 为什么还需要 void 呢?区别是什么?

区别在于 作为返回类型的void可以用不同的类型替换,以允许高级回调模式

ts
function doSomething (callback: () => void) { let c = callback(); // 此时c为void类型 // ... } // 返回number类型 function aNumberCallback (): number { return 2; } doSomething(aNumberCallback);

any

TS新手超级喜欢这个类型

  • 任意值(Any)用来表示允许赋值为任意类型。
  • 它允许访问任意属性和调用任意方法 因此any成了系统的顶级类型
ts
// 但如果是 any 类型,则允许被赋值为任意类型。 let myFavoriteNumber: any = 'seven'; myFavoriteNumber = 7; // 在任意值上访问任何属性都是允许的: console.log(myFavoriteNumber.logName); // 也允许调用任何方法: myFavoriteNumber.split(''); // 声明未赋值 推断为any let any1; // any1: any

unknow

  • any一样都是TypeScript的顶层类型,所有类型都可以分配给unknow

  • any不同的是

    • 不能对unknow类型执行任意操作,必须先缩小到具体类型才能使用其方法和属性
    • unknow类型只能被赋值给any类型和unknow类型本身
  • 缩小未知类型的范围的三种方法

    • 类型断言
    • typeof
    • is关键字
ts
let unknownVal: unknown = 'str'; unknownVal = true; // 所以所有类型都可以分配给unknown // unknow类型只能赋值给any类型和unknow类型 let anyVal: any; anyVal = unknownVal let num: number = 12; num = unknownVal; // 不能将类型“unknown”分配给类型“number”。ts(2322) /* 缩小未知类型的范围的三种方法 */ // 类型断言 function toFixed(value: unknown) { return (value as number).toFixed(2); } // typeof 类型保护 function toFixed2(value: unknown) { if(typeof value === 'number') { return value.toFixed(2); } } // is 自定义类型保护 const isNumber = (val: unknown): val is number => typeof val === 'number'; function toFixed3(value: unknown) { if (isNumber(value)) { return value.toFixed(2); } }

never类型

  • never类型表示的是那些永不存在的值的类型,这意味着,定义了一个never类型,只能被never类型的值所赋值
  • neverundefinednull的子类型, 是最底层的类型

什么情况下会出现never类型

  • 一个从来不会有返回值的函数
    • let ne = (() => { while(true) {} })();
  • 一个总是会抛出错误的函数
    • let ne2 = (() => { throw new Error("异常"); })();

类型进阶

  • 联合类型
  • 对象的类型
  • 数组的类型
  • 函数的类型
  • 元组类型

联合类型

  • 联合类型(Union Types)表示取值可以为多种类型中的一种
  • 只能访问此联合类型的所有类型里共有的属性或方法
ts
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; myFavoriteNumber = 7; function getLength(something: string | number): string { console.log(something.length) // 不可以 number类型没有length属性 console.log(something.toFixed(2)) // 不可以, string类型没有toFixed方法 return something.toString(); }

对象的类型

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

  • 何为接口?它是对行为的抽象。
  • 接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。
  • 接口定义后 实例多一些属性或少一些属性都是不允许的
  • 属性修饰 ? 可选属性 readonly 只读属性
  • 任意属性 [propName: string]: any;
    • 一旦定义了任意属性,那么确定属性和可选属性都必须是它的类型的子集
ts
interface Person { name: string; age: number; } let tom: Person = { // 报错 缺少age属性 name: 'Tom' }; let tom2: Person = { // 报错 不需要 gender 属性 name: 'Tom', age: 25, gender: 'male' }; interface Person2 { readonly name: string; age?: number; // [propName: string]: string; // 报错,任意类型必须包含可选类型和确定类型 [propName: string]: string | number | undefined; } let jack: Person2 = { name: 'zs' } jack.name = 'ls' // 报错 jack.age = 12; // ok jack.sex = 1 // ok // 只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候 interface Person3 { readonly id?: number; name: string; age?: number; [propName: string]: any; } let tom3: Person3 = { name: 'Tom', gender: 'male' }; tom3.id = 89757; // 报错

数组的类型

数组的三种表示法

  • 「类型 + 方括号」表示法 let fibonacci: number[] = [1, 1, 2, 3, 5];
  • 泛型表示法 let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • 接口表示法
ts
interface NumberArray { [index: number]: number; } let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

ts
function sum() { let args: number[] = arguments; // 报错 } // Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more. function sum() { let args: IArguments = arguments; } // IArguments 是TS内置对象 // 实际 IArguments 定义如下 interface IArguments { [index: number]: any; length: number; callee: Function; } // 用 any 表示数组中允许出现任意类型 let list: any[] = ['xcatliu', 25, { website: 'xcatliu.com' }];

函数的类型

  • 可以约束函数参数类型、参数个数及返回值类型
  • 函数声明语句的TS修饰
  • 函数定义语句的TS修饰
  • 使用interface修饰函数
  • 对函数参数设置默认值, 同样会进行类型推断
  • 可选修饰符:
    • 仅最后一个参数可以用?
    • 设置了默认值就不能使用可选修饰符
  • readoly修饰参数 只能在构造函数中使用
  • 剩余参数, 准许JS语法 只能在最后一个参数定义
  • 函数重载 (完整的函数重载将在下一篇进阶课中讲解)
ts
// 函数声明语句 function sum(x: number, y: number): number { return x + y; } sum(1); // 报错,不能少参数 sum(1, 2); // ok // 函数定义表达式 let mySum = function (x: number, y: number): number { return x + y; }; // 等同于如下代码 let mySum: (x: number, y: number) => number = function (x: number, y: number): number { return x + y; }; // 使用interface修饰函数 interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { return source.search(subString) !== -1; } // 函数重载 function reverse(x: number): number; // 重载签名 function reverse(x: string): string; // 重载签名 // 实现签名 function reverse(x: number | string): number | string | void { if (typeof x === 'number') { return Number(x.toString().split('').reverse().join('')); } else if (typeof x === 'string') { return x.split('').reverse().join(''); } }

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象

  • 访问已知索引的元素会得到正确的类型
  • 添加越界的元素时,他的类型会被限制为元组中每个类型的联合类型
ts
// 元组的定义 let tom4: [string, number] = ['Tom', 25]; let tom5: [string, number]; // tom5 = [25, 'Tom'] // 报错 tom5 = ['Tom', 25] tom5[0] = 'Tom'; // 可以只赋值其中一项: // 当赋值或访问一个已知索引的元素时,会得到正确的类型: tom4[0].slice(1); tom4[1].toFixed(2); // 当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型: tom4.push('male'); // ok tom4.push(true); // 报错

参考资料

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!