LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

TypeScript基本知识总结版

admin
2024年12月28日 19:55 本文热度 70

一、基本介绍

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

类型是人为添加的一种编程约束和用法提示。 主要就是为了提高代码质量,减少错误

动态类型和静态类型 语法上js属于动态类型,变量的类型是动态的,不具有很强的约束性,这对于提前发现代码错误,非常不利。ts属于静态类型语言,即变量的类型是静态的。

// 变量的类型是动态的。let x = 1;x = 'hello';

静态类型的优缺点:

优点:1.有利于代码静态分析 2.有利于发现错误 3.更好的 IDE 支持,做到语法提示和自动补全。4.有利于代码重构

缺点:1.丧失了动态类型的代码灵活性 2.增加了编程工作量 3.兼容性问题

二、基本用法

类型声明(冒号+类型)

let foo:string;
类型推断(类型声明并不是必需的,如果没有,TypeScript 会自己推断类型)
let foo = 123;

JavaScript 的运行环境(浏览器和 Node.js)不认识 TypeScript 代码。所以,TypeScript 项目要想运行,必须先转为 JavaScript 代码。TypeScript 的类型检查只是编译时的类型检查,而不是运行时的类型检查。一旦代码编译为 JavaScript,运行时就不再检查类型了

三、类型介绍

1、any

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

变量类型一旦设为any,TypeScript 实际上会关闭这个变量的类型检查。

let x:any = 'hello';
x(1// 不报错x.foo = 100; // 不报错

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

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

污染问题:

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

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

x值是字符串,类型为any,但是y被赋值为x并不会报错,y继续运算。TypeScript 也检查不出错误,问题就这样留到运行时才会暴露。污染其他具有正确类型的变量。

2、unknow

为了解决any类型“污染”其他变量的问题,TypeScript 3.0 引入了unknow类型。它与any含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像any那样自由,可以视为严格版的any

unknownany的相似之处,在于所有类型的值都可以分配给unknown类型。

unknown类型跟any类型的不同之处在于,它不能直接使用。主要有以下几个限制。

1.不能直接赋值给其他类型的变量(除了any类型和unknown类型)

let v:unknown = 123;
let v1:boolean = v; // 报错let v2:number = v; // 报错
2.不能直接调用unknown类型变量的方法和属性。
let v1:unknown = { foo: 123 };v1.foo  // 报错let v2:unknown = 'hello';v2.trim() // 报错let v3:unknown = (n = 0) => n + 1;v3() // 报错

3.unknown类型变量能够进行的运算是有限的,只能进行比较运算(运算符=====!=!==||&&?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错。

怎么才能使用unknown类型变量呢? “类型缩小” 将一个不确定的类型缩小为更明确的类型

let a:unknown = 1;
if (typeof a === 'number') {  let r = a + 10// 正确  }

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

3、never

为了保持与集合论的对应关系, 引入“空类型”的概念,即该类型为空,不包含任何值。由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。

never类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性,不可能返回值的函数,返回值的类型就可以写成never

never类型的一个重要特点是,可以赋值给任意其他类型。为什么never类型可以赋值给任意其他类型呢?这也跟集合论有关,空集是任何集合的子集。任何类型都包含了never类型,TypeScript 把这种情况称为“底层类型”(bottom type)

TypeScript 有两个“顶层类型” (anyunknown),但是“底层类型” 只有never唯一一个。

4、基本类型

JavaScript语言(注意,不是 TypeScript)将值分成8种类型。

  • boolean
  • string
  • number
  • bigint
  • symbol
  • object(对象、函数、数组)
  • undefined
  • null

TypeScript继承了JavaScript 的类型设计,以上8种类型可以看作 TypeScript 的基本类型。

首字母大写的NumberStringBoolean等在 JavaScript 语言中都是内置对象,而不是类型名称。

undefined 和 null 既可以作为值,也可以作为类型

bigint 与 number 类型不兼容。

如果没有声明类型的变量,被赋值为undefinednull,它们的类型会被推断为any

如果希望避免这种情况,则需要打开编译选项strictNullChecks

// 打开编译设置 strictNullCheckslet a = undefined;   // undefinedconst b = undefined; // undefinedlet c = null;        // nullconst d = null;      // null

包装对象

boolean、string、number、bigint、symbol 上面这五种原始类型的值,都有对应的包装对象(wrapper object)。所谓“包装对象”,指的是这些值在需要时,会自动产生的对象。

'hello'.charAt(1// 'e'

上面示例中,字符串hello执行了charAt()方法。但是,在 JavaScript 语言中,只有对象才有方法,原始类型的值本身没有方法。这行代码之所以可以运行,就是因为在调用方法时,字符串会自动转为包装对象,charAt()方法其实是定义在包装对象上。

五种包装对象之中,symbol 类型和 bigint 类型无法直接获取它们的包装对象(即Symbol()BigInt()不能作为构造函数使用),但是剩下三种可以。

  • Boolean()
  • String()
  • Number()

以上三个构造函数,执行后可以直接获取某个原始类型值的包装对象。

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

注意,String()只有当作构造函数使用时(即带有new命令调用),才会返回包装对象。如果当作普通函数使用(不带有new命令),返回就是一个普通字符串。其他两个构造函数Number()Boolean()也是如此。

包装类型和字面量类型

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

'hello' // 字面量new String('hello'// 包装对象

为了区分这两种情况,TypeScript 对五种原始类型分别提供了大写和小写两种类型。

  • Boolean 和 boolean
  • String 和 string
  • Number 和 number
  • BigInt 和 bigint
  • Symbol 和 symbol

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

const n1:Number = 1; // 正确const n2:Number = new Number(1); // 正确
const n3:number = 1; // 正确const n4:number = new Number(1); // 报错

上面示例中,Number类型可以赋值为数字的字面量,也可以赋值为包装对象。但是,number类型只能赋值为字面量,赋值为包装对象就会报错。

建议只使用小写类型,不使用大写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。

很多内置方法的参数,定义成小写类型,使用大写类型会报错。

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

Symbol()BigInt()这两个函数不能当作构造函数使用,所以没有办法直接获得 symbol 类型和 bigint 类型的包装对象,因此SymbolBigInt这两个类型虽然存在,但是完全没有使用的理由。

Object类型和object类型

TypeScript的对象类型也有大写Object和小写object两种。

大写的Object类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值。除了undefinednull这两个值不能转为对象,其他任何值都可以赋值给Object类型。

let obj:Object;obj = true;obj = 'hi';obj = 1;obj = { foo123 };obj = [12];obj = (a:number) => a + 1;
obj = undefined// 报错obj = null; // 报错
空对象{}是Object类型的简写形式,所以使用Object时常常用空对象代替。
let obj:{};
小写的object类型代表JavaScript里面的狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值。
let obj:object;obj = { foo123 };obj = [12];obj = (a:number) => a + 1;obj = true// 报错obj = 'hi'; // 报错obj = 1; // 报错

undefined 和 null 的特殊性

undefinednull既是值,又是类型。以便跟 JavaScript 的行为保持一致,TypeScript 就允许了任何类型的变量都可以赋值为这两个值。

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

为了避免这种情况,及早发现错误,TypeScript提供了一个编译选项strictNullChecks。只要打开这个选项,undefinednull就不能赋值给其他类型的变量(除了any类型和unknown类型)。

5、值类型

TypeScript 规定,单个值也是一种类型,称为“值类型”。

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

TypeScript 推断类型时,遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。

因为const命令声明的变量,一旦声明就不能改变,相当于常量。值类型就意味着不能赋为其他值。

// x 的类型是 "https"const x = 'https';// y 的类型是 stringconst y:string = 'https';

注意,const命令声明的变量,如果赋值为对象,并不会推断为值类型。

值类型可能会出现一些很奇怪的报错。

let x:5 = 5;let y:number = 4 + 1;x = y; // 报错y = x; // 正确

等号左侧的类型是数值5,等号右侧4+1的类型,TypeScript推测为number。由于5number的子类型,number5的父类型,父类型不能赋值给子类型,所以报错了。反过来是可以的,子类型可以赋值给父类型。

6、联合类型和交叉类型

联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。
let x:string|number;x = 123// 正确x = 'abc'; // 正确

交叉类型A&B表示,任何一个类型必须同时属于AB,才属于交叉类型A&B,即交叉类型同时满足AB的特征。

交叉类型的主要用途是表示对象的合成。

let obj: { foo: string } & { bar: string };
obj = {  foo: 'hello'   bar: 'world'   };
交叉类型常常用来为对象类型添加新属性。类型B是一个交叉类型,用来在A的基础上增加了属性bar。
type A = { foonumber };type B = A & { barnumber };

7、type命令 typeof运算符

type命令

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

别名不允许重名。同一个别名Color声明了两次,就报错了

type Color = 'red';type Color = 'blue'// 报错
别名支持使用表达式,也可以在定义一个别名时,使用另一个别名,即别名允许嵌套。
type World = "world";type Greeting = `hello ${World}`;

type命令属于类型相关的代码,编译成 JavaScript 的时候,会被全部删除。

typeof运算符

JavaScript 语言中,typeof 运算符是一个一元运算符,返回一个字符串,代表操作数的类型。

typeof undefined; // "undefined"typeof true; // "boolean"typeof 1337// "number"typeof "foo"// "string"typeof {}; // "object"typeof parseInt; // "function"typeof Symbol(); // "symbol"typeof 127n // "bigint"
TypeScript将typeof运算符移植到了类型运算,它的操作数依然是一个值,但是返回的不是字符串,而是该值的 TypeScript 类型。
const a = { x0 };type T0 = typeof a;   // { x: number }type T1 = typeof a.x; // number
这种用法的typeof返回的是 TypeScript 类型,所以只能用在类型运算之中(即跟类型相关的代码之中),不能用在值运算。
let a = 1;let b:typeof a;if (typeof a === 'number') {  b = a;}

上面示例中,用到了两个typeof,第一个是类型运算,第二个是值运算。它们是不一样的,不要混淆。

JavaScript 的 typeof 遵守 JavaScript 规则,TypeScript 的 typeof 遵守 TypeScript 规则。它们的一个重要区别在于,编译后,前者会保留,后者会被全部删除。上面示例中,编译后只保留了原始代码的第二个typeof,删除了第一个 typeof。

typeof 的参数只能是标识符,不能是需要运算的表达式。

type T = typeof Date()// 报错

上面示例会报错,原因是 typeof 的参数不能是一个值的运算式,而Date()需要运算才知道结果。

typeof命令的参数不能是类型。

type Age = number;type MyAge = typeof Age// 报错
TypeScript 的类型存在兼容关系,某些类型可以兼容其他类型。
type T = number|string;let a:number = 1;let b:T = a;

上面示例中,变量ab的类型是不一样的,但是变量a赋值给变量b并不会报错。这时,我们就认为,b的类型兼容a的类型。

TypeScript 为这种情况定义了一个专门术语。如果类型A的值可以赋值给类型B,那么类型A就称为类型B的子类型(subtype)。在上例中,类型number就是类型number|string的子类型。

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

8、数组

TypeScript 数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。

数组的类型有两种写法。第一种写法是在数组成员的类型后面,加上一对方括号。

let arr:number[] = [123];
如果数组成员的类型比较复杂,可以写在圆括号里面。
let arr:(number|string)[];
数组类型的第二种写法是使用 TypeScript 内置的 Array 接口。
let arr:Array<number> = [123];
数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。这种规定的隐藏含义就是,数组的成员是可以动态变化的。
let arr:number[] = [123];arr[3] = 4;arr.length = 2;arr // [1, 2]

上面示例中,数组增加成员或减少成员,都是可以的。

9、元组

元组(tuple)是 TypeScript 特有的数据类型,JavaScript 没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。

数组的成员类型写在方括号外面(number[]),元组的成员类型是写在方括号里面([number]

let a:[number] = [1];

使用元组时,必须明确给出类型声明(上例的[number]),不能省略,否则 TypeScript 会把一个值自动推断为数组。

元组成员的类型可以添加问号后缀(?),表示该成员是可选的。

let a:[numbernumber?] = [1];
注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
type myTuple = [  number,  number,  number?,  string?];
由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。
let x:[stringstring] = ['a''b'];x[2] = 'c'// 报错
但是,使用扩展运算符(...),可以表示不限成员数量的元组。
type NamedNums = [  string,  ...number[]];const a:NamedNums = ['A'12];const b:NamedNums = ['B'123];

10、函数

函数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。

function hello( txt:string ):void {  console.log('hello ' + txt);}

上面示例中,函数hello()在声明时,需要给出参数txt的类型(string),以及返回值的类型(void),后者写在参数列表的圆括号后面。void类型表示没有返回值

如果不指定参数类型(比如上例不写txt的类型),TypeScript 就会推断参数类型,如果缺乏足够信息,就会推断该参数的类型为any

返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。

如果变量被赋值为一个函数,变量的类型有两种写法。

// 写法一const hello = function (txt:string) {  console.log('hello ' + txt);}// 写法二const hello:  (txt:string) => voidfunction (txt) {  console.log('hello ' + txt);};

上面示例中,变量hello被赋值为一个函数,它的类型有两种写法。写法一是通过等号右边的函数类型,推断出变量hello的类型;写法二则是使用箭头函数的形式,为变量hello指定类型,参数的类型写在箭头左侧,返回值的类型写在箭头右侧。

写法二有两个地方需要注意。

首先,函数的参数要放在圆括号里面,不放会报错。

其次,类型里面的参数名(本例是txt)是必须的。有的语言的函数类型可以不写参数名(比如 C 语言),但是 TypeScript 不行。如果写成(string) => void,TypeScript 会理解成函数有一个名叫 string 的参数,并且这个string参数的类型是any

type MyFunc = (string, number) => number;// (string: any, number: any) => number

11、泛型

有些时候,函数返回值的类型与参数类型是相关的。

function getFirst(arr) {  return arr[0];}
上面示例中,函数getFirst()总是返回参数数组的第一个成员。参数数组是什么类型,返回值就是什么类型
function f(arr:any[]):any {  return arr[0];}
上面的类型声明,就反映不出参数与返回值之间的类型关系。为了解决这个问题,TypeScript 就引入了“泛型”(generics)。泛型的特点就是带有“类型参数”(type parameter)。
function getFirst<T>(arr:T[]):T {  return arr[0];}

上面示例中,函数getFirst()的函数名后面尖括号的部分<T>,就是类型参数,参数要放在一对尖括号(<>)里面。本例只有一个类型参数T,可以将其理解为类型声明需要的变量,需要在调用时传入具体的参数类型。

上例的函数getFirst()的参数类型是T[],返回值类型是T,就清楚地表示了两者之间的关系。比如,输入的参数类型是number[],那么 T 的值就是number,因此返回值类型也是number

函数调用时,需要提供类型参数

getFirst<number>([1, 2, 3])
//不过为了方便,函数调用时,往往省略不写类型参数的值,让 TypeScript 自己推断。getFirst([1, 2, 3])
有些复杂的使用场景,TypeScript可能推断不出类型参数的值,这时就必须显式给出了。
function comb<T>(arr1:T[], arr2:T[]):T[] {  return arr1.concat(arr2);}
// 两个参数arr1、arr2和返回值都是同一个类型。如果不给出类型参数的值,下面的调用会报错。comb([1, 2], ['a''b']) // 报错// 但是,如果类型参数是一个联合类型,就不会报错。comb<number|string>([1, 2], ['a''b']) // 正确

类型参数的名字,可以随便取,但是必须为合法的标识符。习惯上,类型参数的第一个字符往往采用大写字母。一般会使用T(type 的第一个字母)作为类型参数的名字。如果有多个类型参数,则使用 T 后面的 U、V 等字母命名,各个参数之间使用逗号(“,”)分隔。

总之,泛型可以理解成一段类型逻辑,需要类型参数来表达。有了类型参数以后,可以在输入类型与输出类型之间,建立一一对应关系

泛型主要用在四个场合:函数、接口、类和别名。

12、interface

 interface是对象的模板,可以看作是一种类型约定,中文译为“接口”。
interface Person {  firstNamestring;  lastNamestring;  agenumber;}

上面示例中,定义了一个接口Person,它指定一个对象模板,拥有三个属性firstNamelastNameage。任何实现这个接口的对象,都必须部署这三个属性,并且必须符合规定的类型。

interface 可以表示对象的各种语法,它的成员有5种形式。

  • 对象属性
  • 对象的属性索引
  • 对象方法
  • 函数
  • 构造函数

(1)对象属性

interface Point {  x: number;  y: number;}//如果属性是可选的,就在属性名后面加一个问号。interface Foo {  x?: string;}// 如果属性是只读的,需要加上readonly修饰符。interface A {  readonly a: string;}
(2)对象的属性索引
interface A {  [prop: string]: number;}

上面示例中,[prop: string]就是属性的字符串索引,表示属性名只要是字符串,都符合类型要求。

一个接口中,最多只能定义一个字符串索引。字符串索引会约束该类型中所有名字为字符串的属性。

interface MyObj {  [propstring]: number;  aboolean;      // 编译错误}
属性的数值索引,其实是指定数组的类型。
interface A {  [prop: number]: string;}const obj:A = ['a''b''c'];
如果一个 interface 同时定义了字符串索引和数值索引,那么数值索引必须服从于字符串索引。
interface A {  [prop: string]: number;  [prop: number]: string; // 报错}interface B {  [prop: string]: number;  [prop: number]: number; // 正确}

上面示例中,数值索引的属性值类型与字符串索引不一致,就会报错。数值索引必须兼容字符串索引的类型声明。

(3)对象的方法

// 写法一interface A {  f(x: boolean): string;}// 写法二interface B {  f: (x: boolean) => string;}// 写法三interface C {  f: { (x: boolean): string };}
(4)函数
interface Add {  (x:numbery:number): number;}const myAdd:Add = (x,y) => x + y;
(5)构造函数
interface ErrorConstructor {  new (message?: string): Error;}
interface 可以使用extends关键字,继承其他 interface。
interface Shape {  namestring;}interface Circle extends Shape {  radiusnumber;}
多个同名接口会合并成一个接口。
interface Box {  height: number;  width: number;}interface Box {  length: number;}//上面示例中,两个Box接口会合并成一个接口,同时有heightwidth和length三个属性。

interface与type的异同

很多对象类型即可以用 interface 表示,也可以用 type 表示。而且,两者往往可以换用,几乎所有的 interface 命令都可以改写为 type 命令。

interface 与 type 的区别有下面几点。

(1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。

(2)interface可以继承其他类型,type不支持继承。

继承的主要作用是添加属性,type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。

type Animal = {  namestring}type Bear = Animal & {  honeyboolean}

继承时,type 和 interface 是可以换用的。interface 可以继承 type。

type Foo = { xnumber; };interface Bar extends Foo {  ynumber;}
interface Foo {  xnumber;  }  type Bar = Foo & { ynumber; };
(3)同名interface会自动合并,同名type则会报错。也就是说,TypeScript不允许使用type多次定义同一个类型。
type A = { foo:number }; // 报错type A = { bar:number }; // 报错//type两次定义了类型A,导致两行都会报错。
interface A { foo:number };interface A { bar:number };const obj:A = {  foo: 1,  bar: 1  };  //interface把类型A的两个定义合并在一起。

这表明,inteface 是开放的,可以添加属性,type 是封闭的,不能添加属性,只能定义新的 type。

(4)interface不能包含属性映射(mapping),type可以

interface Point {  xnumber;  ynumber;}// 正确type PointCopy1 = {  [Key in keyof Point]: Point[Key];};// 报错interface PointCopy2 {  [Key in keyof Point]: Point[Key];};

(5)this关键字只能用于interface

(6)type 可以扩展原始数据类型,interface 不行。

(7)interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以。

type A = { /* ... */ };type B = { /* ... */ };type AorB = A | B;type AorBwithName = AorB & {  namestring};

上面示例中,类型AorB是一个联合类型,AorBwithName则是为AorB添加一个属性。这两种运算,interface都没法表达。

综上所述,如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用。

四、总结

上述部分主要总结了typescript的基本用法,涵盖了大部分使用场景。


阅读原文:原文链接


该文章在 2024/12/30 16:02:57 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved