【TypeScript】阮一峰TypeScript教程笔记:基本用法、any 类型等、类型系统

2023-10-10 00:20

本文主要是介绍【TypeScript】阮一峰TypeScript教程笔记:基本用法、any 类型等、类型系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

是阮一峰 TypeScript 教程的笔记。笔记目录与教程目录一致。笔记不全,仅作自己学习使用,完整学习请看:阮一峰 TypeScript 教程或TypeScript官方文档。

文章目录

    • 简介
    • 基本用法
    • any 类型,unknown 类型,never 类型
      • any 类型
      • 类型推断问题
      • 污染问题
      • unknown 类型
      • never 类型
    • 类型系统
      • 基本类型
      • 包装对象类型
      • 包装对象类型与字面量类型
      • Object 类型与 object 类型
      • undefined 和 null 的特殊性
      • 值类型
      • 联合类型
      • 交叉类型
      • type 命令
      • typeof 运算符
      • 类型的兼容

简介

TypeScript 可以看成是 JavaScript 的超集(superset),即它继承了后者的全部语法,所有 JavaScript 脚本都可以当作 TypeScript 脚本(但是可能会报错),此外它再增加了一些自己的语法。

TypeScript 对 JavaScript 添加的最主要部分,就是一个独立的类型系统

类型系统

类型是人为添加的一种编程约束和用法提示。 目的:提早发现错误。

举个例子:

function addOne(n: number) {return n + 1;
}

若:addOne("hello");,在TypeScript中会报错,但在JS中不会。

动态类型与静态类型

JS是动态类型语言,TS是静态类型语言。

// 例一
let x = 1;
x = "hello";// 例二
let y = { foo: 1 };
delete y.foo;
y.bar = 2;

若在JS中,上面的代码就是正确的:约束性很弱,不利于提前发现代码错误。(无法提前知道某个属性在不在,或某个变量的数据类型
在TS中,上述代码会报错。TS为JS引入了静态类型特征

基本用法

类型声明

为JS变量加上类型声明。写法:标识符后“冒号+类型”。

变量:

let foo:string;

函数:

function toString(n: number): string {return String(n);
}

报错:

  • 变量的值与声明类型不一致
  • 变量未赋值就使用(在JS中,为赋值的变量返回undefined但不会报错)

类型推断

类型声明并不是必需的。若无,TS会自己推断类型。若变量赋值后更改为其他类型的值,跟推断的类型不一致,TS会报错。

let foo = 123;
foo = "hello"; // 报错

编译

JS的运行环境(浏览器和Node.js)不认识TS。因此,TS想要运行,要先转为JS,这个过程即编译

关于编译:编译时,会将类型声明和类型相关的代码全部删除,只留下能运行的 JavaScript 代码,并且不会改变 JavaScript 的运行结果。

关于类型检查:只是编译时的类型检查,而不是运行时的类型检查。

值与类型

“类型”是针对“值”的,可以视为是“值”的一个属性。每一个值在TS中都是有类型的。如,3是一个值,他的类型是number。

TypeScript 代码只涉及类型,不涉及值。所有跟“值”相关的处理,都由 JavaScript 完成。
这一点务必牢记。TypeScript 项目里面,其实存在两种代码,一种是底层的“值代码”,另一种是上层的“类型代码”。前者使用 JavaScript 语法,后者使用 TypeScript 的类型语法。

在TS的编译过程,其实就是把“类型代码”全部拿掉,只保留“值代码”。

TypeScript Playground

https://www.typescriptlang.org/play

any 类型,unknown 类型,never 类型

any 类型

any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。

let x: any;x = 1; // 正确
x = "foo"; // 正确
x = true; // 正确
let x: any = "hello";x(1); // 不报错
x.foo = 100; // 不报错

上面示例中,变量x的值是一个字符串,但是把它当作函数调用,或者当作对象读取任意属性,TypeScript 编译时都不报错。原因就是x的类型是any,TypeScript 不对其进行类型检查。

因此,尽量避免使用any类型,否则就失去了使用 TypeScript 的意义。

实际开发中,any类型主要适用以下两个场合:

  1. 出于特殊原因,需要关闭某些变量的类型检查,就可以把该变量的类型设为any。
  2. 为了适配以前老的 JavaScript 项目,让代码快速迁移到 TypeScript,可以把变量类型设为any。有些年代很久的大型 JavaScript 项目,尤其是别人的代码,很难为每一行适配正确的类型,这时你为那些类型复杂的变量加上any,TypeScript 编译时就不会报错。

类型推断问题

对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是any

使用letvar命令声明变量,但不赋值也不指定类型,是不会报错的。

var x; // 不报错
let y; // 不报错

建议使用let和var声明变量时,如果不赋值,就一定要显式声明类型,否则可能存在安全隐患。 如下面代码:用let声明后不赋值,则x类型推断为any。下面代码不会报错。

let x;x = 123;
x = { foo: "hello" };

污染问题

any类型除了关闭类型检查,还有一个很大的问题,就是它会 “污染” 其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。

let x: any = "hello";
let y: number;y = x; // 不报错y * 123; // 不报错
y.toFixed(); // 不报错

上面示例中,变量x的类型是any,实际的值是一个字符串。变量y的类型是number,表示这是一个数值变量,但是它被赋值为x,这时并不会报错。然后,变量y继续进行各种数值运算,TypeScript 也检查不出错误,问题就这样留到运行时才会暴露。

污染其他具有正确类型的变量,把错误留到运行时,这就是不宜使用any类型的另一个主要原因。

unknown 类型

为了解决any类型“污染”其他变量的问题,TypeScript 3.0 引入了unknown类型。它与any含义相同,表示类型不确定,可能是任意类型,但是有一些限制。

与any的相同之处:所有类型的值都可以分配给unknown类型。
与any的不同之处:不能直接使用。

  • 不能直接赋值给其他类型的变量(除了any类型和unknown类型)
  • 不能直接调用unknown类型变量的方法和属性
  • unknown类型变量能进行的运算有限:比较运算(运算符=====!=!==||&&?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错

使用unknown的方法:“类型缩小”。即缩小unknown变量的类型范围,确保不会出错。

如:

let a: unknown = 1;if (typeof a === "number") {let r = a + 10; // 正确
}
let s: unknown = "hello";if (typeof s === "string") {s.length; // 正确
}

unknown可以看作是更安全的any。凡是需要设为any类型的地方,通常都应该优先考虑设为unknown类型。

never 类型

TS引入“空类型”的概念,即该类型为空,不包含任何值。由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。

let x: never;

上面示例中,变量x的类型是never,就不可能赋给它任何值,否则都会报错。

使用场景:

  • 在一些类型运算之中,保证类型运算的完整性
  • 不可能返回值的函数

如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never类型。如:

function fn(x: string | number) {if (typeof x === "string") {// ...} else if (typeof x === "number") {// ...} else {x; // never 类型}
}

可以赋值给任意其他类型。

function f(): never {throw new Error("Error");
}let v1: number = f(); // 不报错
let v2: string = f(); // 不报错
let v3: boolean = f(); // 不报错

类型系统

基本类型

boolean、string、number、bigint、symbol、object、undefined、null。

undefined:表示未定义。
null:表示空(此处没有值)。

包装对象类型

booleanstringnumber有对应的包装对象类型:Boolean()String()Number()

在调用方法时,字符串会自动转为包装对象。(原本原始类型的值没有方法,对象才有)

"hello".charAt(1); // 'e'

当作构造函数使用时,才会返回包装对象。

const s = new String("hello");
typeof s; // 'object'
s.charAt(1); // 'e'

包装对象类型与字面量类型

每一个原始类型的值都有包装对象和字面量两种情况。

"hello"; // 字面量
new String("hello"); // 包装对象

大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象:

const s1: String = "hello"; // 正确
const s2: String = new String("hello"); // 正确const s3: string = "hello"; // 正确
const s4: string = new String("hello"); // 报错

建议只使用小写类型,不使用大写类型。

因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。而且,TypeScript 把很多内置方法的参数,定义成小写类型,使用大写类型会报错。如:

const n1: number = 1;
const n2: Number = 1;Math.abs(n1); // 1
Math.abs(n2); // 报错

内置的Math.abs的参数类型是小写的number,若传入大写的Number类型就会报错。

Object 类型与 object 类型

Object(大写)

所有可以转成对象的值,都是Object类型。除了undefinednull这两个值不能转为对象,其他任何值都可以赋值给Object类型。

空对象{}Object类型的简写形式。

object(小写)

不包含原始类型值,只包含对象、数组和函数。

let obj: object;obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
obj = true; // 报错
obj = "hi"; // 报错
obj = 1; // 报错

建议总是使用小写类型object。大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。

undefined 和 null 的特殊性

任何其他类型的变量都可以赋值为undefined或null。

let age: number = 24;age = null; // 正确
age = undefined; // 正确

这样做的原因是:以便跟 JavaScript 的行为保持一致。

有时候,这不是开发者想要的行为,也不利于发挥类型系统的优势。

const obj: object = undefined;
obj.toString(); // 编译不报错,运行就报错

为了避免这种情况,TypeScript 提供了一个编译选项strictNullChecks。打开它以后,undefinednull只能赋值给自身,或者any类型和unknown类型的变量。

值类型

单个值也是一种类型,称为“值类型”。值类型不能赋为其他值。

let x: "hello";x = "hello"; // 正确
x = "world"; // 报错

注意,const命令声明的变量,如果赋值为对象,并不会推断为值类型。const变量赋值为对象时,属性值是可以改变的。

联合类型

联合类型(union types)指的是多个类型组成的一个新类型,使用符号|表示。联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。

let x: string | number;x = 123; // 正确
x = "abc"; // 正确

联合类型可以与值类型相结合,表示一个变量的值有若干种可能。

let setting: true | false;let gender: "male" | "female";let rainbowColor: "赤" | "橙" | "黄" | "绿" | "青" | "蓝" | "紫";

前面提到,打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。

let name: string | null;name = "John";
name = null;

如果一个变量有多种类型,读取该变量时,往往需要进行“类型缩小”(type narrowing),区分该值到底属于哪一种类型,然后再进一步处理。

直接调用会报错:

function printId(id: number | string) {console.log(id.toUpperCase()); // 报错
}

类型缩小:

function printId(id: number | string) {if (typeof id === "string") {console.log(id.toUpperCase());} else {console.log(id);}
}

“类型缩小”是 TypeScript处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种“类型放大”(type widening),处理时就需要“类型缩小”(type narrowing)。

交叉类型

交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号&表示。任何一个类型必须同时属于A和B,才属于交叉类型A&B,即交叉类型同时满足A和B的特征。

主要用途是表示对象的合成。 变量obj同时具有属性foo和属性bar。

let obj: { foo: string } & { bar: string };obj = {foo: "hello",bar: "world",
};

常常用来为对象类型添加新属性。 类型B是一个交叉类型,用来在A的基础上增加了属性bar。

type A = { foo: number };
type B = A & { bar: number };

type 命令

type命令用来定义一个类型的别名。

type命令为number类型定义了一个别名Age。这样就能像使用number一样,使用Age作为类型。

type Age = number;
let age: Age = 55;

别名的作用域是块级作用域。

if (true) {type T = number;let v: T = 5;
} else {type T = string;let v: T = "hello";
}

别名支持使用表达式,也允许嵌套。

示例中,别名Greeting使用了模板字符串,读取另一个别名World。

type World = "world";
type Greeting = `hello ${World}`;

typeof 运算符

JS中typeof运算符返回的都是字符串:

typeof undefined; // "undefined"
typeof true; // "boolean"
typeof 1337; // "number"
typeof "foo"; // "string"
typeof {}; // "object"
typeof parseInt; // "function"
typeof Symbol(); // "symbol"
typeof 127n; // "bigint"

TS中返回的是TS类型:

const a = { x: 0 };type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number

同一段代码可能存在两种typeof运算符,一种用在值相关的 JavaScript 代码部分,另一种用在类型相关的 TypeScript 代码部分。如:

第一个是类型运算,第二个是值运算。

let a = 1;
let b: typeof a;if (typeof a === "number") {b = a;
}

JavaScript 的 typeof 遵守 JavaScript 规则,TypeScript 的 typeof 遵守 TypeScript 规则。它们的一个重要区别在于,编译后,前者会保留,后者会被全部删除。上例的代码编译结果如下。

let a = 1;
let b;
if (typeof a === "number") {b = a;
}

TypeScript 规定,typeof 的参数只能是标识符,不能是需要运算的表达式。typeof命令的参数不能是类型。

类型的兼容

如果类型A的值可以赋值给类型B,那么类型A就称为类型B的子类型(subtype)

如:类型number就是类型number|string的子类型。

type T = number | string;let a: number = 1;
let b: T = a;

凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行。

let a: "hi" = "hi";
let b: string = "hello";b = a; // 正确
a = b; // 报错

这篇关于【TypeScript】阮一峰TypeScript教程笔记:基本用法、any 类型等、类型系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/176619

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python中logging模块用法示例总结

《Python中logging模块用法示例总结》在Python中logging模块是一个强大的日志记录工具,它允许用户将程序运行期间产生的日志信息输出到控制台或者写入到文件中,:本文主要介绍Pyt... 目录前言一. 基本使用1. 五种日志等级2.  设置报告等级3. 自定义格式4. C语言风格的格式化方法

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

Python ORM神器之SQLAlchemy基本使用完全指南

《PythonORM神器之SQLAlchemy基本使用完全指南》SQLAlchemy是Python主流ORM框架,通过对象化方式简化数据库操作,支持多数据库,提供引擎、会话、模型等核心组件,实现事务... 目录一、什么是SQLAlchemy?二、安装SQLAlchemy三、核心概念1. Engine(引擎)

基于C#实现PDF转图片的详细教程

《基于C#实现PDF转图片的详细教程》在数字化办公场景中,PDF文件的可视化处理需求日益增长,本文将围绕Spire.PDFfor.NET这一工具,详解如何通过C#将PDF转换为JPG、PNG等主流图片... 目录引言一、组件部署二、快速入门:PDF 转图片的核心 C# 代码三、分辨率设置 - 清晰度的决定因

Java中HashMap的用法详细介绍

《Java中HashMap的用法详细介绍》JavaHashMap是一种高效的数据结构,用于存储键值对,它是基于哈希表实现的,提供快速的插入、删除和查找操作,:本文主要介绍Java中HashMap... 目录一.HashMap1.基本概念2.底层数据结构:3.HashCode和equals方法为什么重写Has

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手