目录
一、TypeScript 简介
- TypeScript 由微软开发,是基于JavaScript 的一个扩展语言。
- TypeScript 包含了JavaScript 的所有内容,即:TypeScript 是JavaScript 的超集。
- TypeScript 增加了:静态类型检查、接口、泛型等很多现代开发特性,因此更适合大型项目的开发。
- TypeScript 需要编译为 JavaScript ,然后交给浏览器或其他 JavaScript 运行环境执行。
二、为何需要TypeScript
1、今非昔比的 JavaScript(了解)
- ·JavaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入一些简单的逻辑,而且代码量很少。
- 随着时间的推移,JavaScript 变得越来越流行,如今的JavaScript 已经可以全栈编程了。
- 现如今的 JavaScript 应用场景比当年丰富的多。代码量也比当年大很多,随便一个JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行!
- 然而 JavaScript 当年“出生简陋”没考虑到如今的应用场景和代码量,逐渐的就出现了很多困扰。
2、JavaScript 中的困扰
1、不清不楚的数据类型
let welcome = 'hello';
welcome(); // 此行报错:Uncaught TypeError: welcome is not a function 。
// 有时候接收到的一个函数,但是太多代码,并不记得自己定义了这个变量,编辑器并没有校验,就会导致这个错误。
2、有漏洞的逻辑
const str = Date.now() % 2 ? '奇数' : '偶数';
if (str !== '奇数') {
alert('这不是奇数');
} else if (str === '偶数') {
// 逻辑漏洞 但是没有校验提示
alert('这不是奇数');
}
3、访问不存在的属性
const obj = { width: 100, height: 200 };
const area = obj.width * obj.height; // 访问不存在的对象,但是也没有检验提示
4、低级的拼写错误
const str = 'hellow,world'
// 拼写错误也没有校验提示
const UpperStr = str.toUperCase() // 此行报错 Uncaught TypeError: str.toUperCase is not a function
console.log(UpperStr);
3、静态类型检查
- 在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常的出现的几率,此种检查叫『静态类型检查』,TypeScript 和核心就是『静态类型检查』,简言之就是把运行时的错误前置。
- 同样的功能,TypeScript 的代码量要大于 JavaScript,但由于 TypeScript 的代码结构更加清晰,在后期代码的维护中 TypeScript 却远胜于 JavaScript。
ts 是静态类型检查。无需运行就可以给你校验基本错误。
三、编译TypeScript
浏览器不能直接运行 TypeScript 代码,需要编译为 JavaScript 再交由浏览器解析器执行。
1、命令行编译(了解即可)
第一步:创建 demo.ts 文件。例如:
const person = {
name: 'Angindem',
age: 32
};
console.log(`my name is ${person.name} and I'm ${person.age} years old.`);
第二步:全局安装 TypeScript
npm i -g typescript
第三步:使用命令编译 .ts 文件
tsc demo.ts
2、自动化编译
第一步:创建 TypeScript 编译控制文件
tsc --init
- 工程中会生成一个 tsconfig.json 配置文件,其中包含着很多编译时的配置
- 观察发现,默认编译的 JS 版本是 ES7 ,我们可以手动调整为其他版本。
第二步:监视目录中的 .ts 文件变化
tsc --watch
第三步:加上指定属性进行小优化,当编译出错时不生产 .js 文件
tsc --noEmitOnError --watch
(不推荐命令行加上属性优化)
所有的属性开启应该在 tsconfig.json 中进行开启。然后我们执行第二步就行了。
如果使用了脚手架,如 Vue 或 React 。我们可以发现我们不用以上编译了。而是基于 webpack 或 vite 进行编译运行。随后我们使用 npm 或 yarn 或 pnpm 执行相对应的 script 常量名即可。
四、类型声明(这里可以算是真正开始TypeScript)
使用 “ :” 来对 变量 或 函数形参,进行 类型声明。
let a: string;
let b: number;
let c: boolean;
a = 'hello world'
b = -123
c = true
function count(x: number, y: number): number /*函数右括号后边进行函数返回值类型声明*/
{
return x + y
}
// count(1) // 警告:应有 2 个参数,但获得了 1 个
// count(1, 2,3) // 警告:应有 2 个参数,但获得了 3 个
// count(100,'200') // 警告:类型“string”的参数不能赋给类型“number”的参数
// let result = count(1, 2);
// console.log(result);
在 “ :” 后也可以写 常量值类型 ,不过实际开发中用的不多。
let a: string;
// 声明指定存储的 常量值
let b: 'hello';
a = 'asd'
// b = 'fas'
// 只能存储 hello
b = 'hello'
console.log(b);
五、类型推断
let d = -99; // 等价于 let d: number = -99;
let e = 'hello'; // 等价于 let e: string = 'hello';
六、类型总览
1、JavaScript 中的数据类型
① string
② number
③ boolean
④ null
⑤ undefined
⑥ bigint
⑦ symbol
⑧ object
备注:其中 object 包含:Array、Date、Error 等......
2、TypeScript 中的数据类型
1、上述所有 JavaScript 类型
2、六个新类型
① any
② unknow
③ never
④ void
⑤ tuple
⑥ enum
3、两个用户自定义类型的方式
① type
② interface
注意点:
在 JavaScript 中的这些内置构造函数:Number、它们用于创建对应String 、Boolean,的包装对象,在日常开发时很少使用,在 Typescript 中也是同理,所以在 Typescript 中进行类型声明时,通常都是用小写的 number、string、boolean
let str1: string // TS 官方推荐的写法
str1 = 'hello world'
/*
不能将类型"String" 赋给类型 "string"。
“string” 是基元,但 “String” 是包装器对象。如可能,首选使用基元类型。
*/
// str1 = new String('hello world');
let str2: String
str2 = 'hello world'
str2 = new String('hello world')
①、原始类型 VS 包装对象
- 原始类型:如 number、string、boolean ,在JavaScript 中是简单数据类型,它们在内存占用空间少,处理速度快。
- 包装对象:如 Number 对象、string对象、Boolean 对象,是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象。
②.自动装箱:JavaScript 在必要时会自动将原始类型包装成对象,以便调用方法或访问属性。
// 原始类型字符串
let str= 'hello';
//当访问str.length时,Javascript引擎做了以下工作:
let size = (function () {
// 1.自动装箱:创建一个临时的string对象包装原始字符串
let tempStringObject =new String(str);
//2.访问string对象的length属性
let lengthValue = tempStringObject.length;
// 3.销毁临时对象,返回长度值
//(Javascript引擎自动处理对象销毁,开发者无感知)
return lengthValue,
})();
console.log(size);//输出:
七、常用类型
1、any
any 的含义是:任意类型,一旦将变量类型限制为 any,那就意味着放弃了对该变量的类型检查。
let a: any; // 显示类型指定
a = 1;
a = "1";
a = true;
a = [1, 2, 3];
let b; // 隐式类型转换
b = 1;
b = "1";
b = true;
b = [1, 2, 3];
注意点:any 类型的变量,可以赋值给任意类型的变量。
2、unknow
unknown 的含义是:未知类型。
//设置a的类型为unknown
let a: unknown
//以下对a的赋值,均正常
a = 100
a = false
a='你好'
//设置x的数据类型为 string
let x: string
x = a //警告:不能将类型“unknown”分配给类型“string
unknown 可以理解为一个类型安全的any,适用于:不确定数据的具体类型
//设置a的类型为unknown
let a: unknown
//以下对a的赋值,均正常
a = 100
a = false
a='你好'
//设置x的数据类型为 string
let x: string
// x = a//警告:不能将类型“unknown”分配给类型“string
// 第一种
if (typeof a === 'string') {
x = a
}
// 第二种( as 断言: 断定 a 是 string类型 【有点类似 强制类型转换 】)
x = a as string
// 第三种( <类型> 断言: 断定 a 是 string类型 【有点类似 强制类型转换 】)
x = <string>a
读取 any 类型数据的任何属性都不会报错,而 unknown 正好与之相反。
let str1: string
str1 = "hello world"
str1.toUpperCase() // 无警告
let str2: any
str2 = "hello world"
str2.toUpperCase() // 无警告
let str3: unknown
str3 = "hello world"
str3.toUpperCase() // 警告:“str3”的类型为 " 未知 "
3、never
never 的含义是:任何值都不是,简言之就是不能有值,undefined、null、0 都不行!
①、几乎不用never去直接限制变量,因为没有意义,例如:
let a: never;
// 以下都会警告
// a = 9
// a = '9'
// a = true
// a = [1, 2, 3]
② never 一般是 TypeScript 主动推断出来的
3、never 也可以用户限制函数的返回值
function demo(): number{
return 100;
}
// 只有 两种情况 never 才不会爆红:1. 函数死循环 、2. 函数抛出异常
// 死循环
function demo2(): never{
demo2();
}
// 抛出错误
function demo3(): never{
throw new Error('error')
}
4、void
① void 通常用于函数返回值声明,含义:【函数不返回任何值,调用者也不应依赖其返回值进行任何操作】
function logMessage(msg: string): void{
console.log(msg);
// return undefined; // void 能接受的是 undefined
}
logMessage("Hello World!");
注意:编码者没有编写 return 去指定函数的返回值,所以 1ogMessage 函数是没有显式返回值的,但会有一个隐式返回值,就是 undefined ;即:虽然函数返回类型为 void ,但也是可以接受 undefined 的,简单记:undefined 是void 可以接受的一种“空”。
② 以下写法均符合规范
// 无警告
function logMessage1(msg: string): void{
console.log(msg);
}
// 无警告
function logMessage2(msg: string): void{
console.log(msg);
return;
}
// 无警告
function logMessage3(msg: string): void{
console.log(msg);
return undefined;
}
// function demo1(): void{
// console.log("hello world");
// }
// let result = demo1();
// if (result) { // 警告: void 不应该有操作
// }
function demo2(): undefined{
console.log("hello world");
}
let res = demo2();
if (res) { // undefined :JavaScript 允许 undefined 赋值给任何变量
}
理解 void 与 undefined
- void 是一个广泛的概念,用来表达“空”,而 undefined 则是这种“空”的具体实现之一。
- 因此可以说 undefined 是 void 能接受的“空”状态的一种具体形式。
- 换句话说:void 包含 undefined ,但 void 表达的语义超越了单纯的 undefined ,它是一种意图上的约定,而不仅仅是特定值的限制。
总结:若函数返回类型为 void ,那么:
- 从语法上讲:函数是可以返回 undefined 的,至于显示返回,还是隐式返回,这无所谓!
- 从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!即使返回了 undefined 值。
5、object
关于 object 与 Object , 直接说结论:实际开发中用的相对较少,因为范围太大了。
object(小写)
object (小写) 的含义是:所有非原始类型,可存储:对象、函数、数组等,由于限制的范围比较宽泛,在实际开发中使用的相对较少。
let a: object // a 能存储的类型是 【非原始类型】 a = {} a = { name: 'why' } a = [1, 3, 5, 7, 9] a = function () { } a = new String('why') class Person { } a = new Person() // 以下代码,是将 【原始类型】 赋给a,有警告 a = 1 // 警告: 不能将类型“number”分配给类型“object”。 a = true // 警告:不能将类型“boolean”分配给类型“object”。 a = 'why' // 警告:不能将类型“string”分配给类型“object”。 a = null // 警告:不能将类型“null”分配给类型“object”。 a = undefined // 警告:不能将类型“undefined”分配给类型“object”。
Object (大写)
官方描述:所有可以调用 Object方法 的类型。
简单记忆:除了 undefined 和 null 的任何值。
由于限制的范围实在太大了!所以实际开发中使用频率极低。let b: Object // b 能存储的类型是 可以调用到 Object方法 的类型 class Person { } b = new Person() b = {} b = { name: 'why' } b = [1, 3, 5, 7, 9] b = function () { } b = new String('why') b = 1 b = true b = 'why' b = null // (调用不到 Object方法 ) 警告:不能将类型“null”分配给类型“object”。 b = undefined // (调用不到 Object方法 ) 警告:不能将类型“undefined”分配给类型“object”。
声明对象类型
①、实际开发总,限制一般对象,通常使用以下形式
// 限制 person1 对象必须有 name 属性,age 为 可选属性
let person1: { name: string, age?: number };
// 含义同上,也能用分号做分隔
let person2: { name: string; age?: number };
// 含义同上,也能用换行做分隔
let person3: {
name: string
age?: number
};
// 如下赋值均可以
person1 = { name: 'Tom' };
person2 = { name: 'Tom', age: 18 };
person3 = { age: 18, name: 'Tom' };
// 如下赋值不合法,因为 person3 的类型限制中,没有对 gender 属性的声明
person3 = { name: 'Tom', age: 18, gender: 'male' };
②、索引签名: 允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性,(具有动态属性的对象)。
let person3: {
name: string
age?: number
[key: string]: any // key 可以是任意字符串,存储的类型是 any
};
person3 = { age: 18, name: 'Tom' };
person3 = { name: 'Tom', age: 18, gender: 'male' };
声明函数类型
// let count: (参数1: <类型>, 参数1: <类型>) => <返回值类型> {}
let count: (asdasdads: number, dasafsfasb: number) => number; // 做好声明
// 后续会自动校验
// count = function (x, y) {
// return x + y + '222'
// }
// count = function (x, y) {
// return x + y
// }
count = (x, y) => {
return x + y
}
备注:
- TypeScript 中的 =>在函数类型声明时表示函数类型,描述其参数类型和返回类型
- JavaScript 中的 =>是一种定义函数的语法,是具体的的数实现。
- 函数类型声明还可以使用:接口、自定义类型等方式,下文中会详细讲解。
声明数组类型
let arr: string[];
// arr = ["a", "b", "c",123];
// 泛型 <> 指定 数组类型
let arr2 : Array<number>;
arr2 = [1,2,123];
备注:上述代码中的 Array<string>属于泛型,下文会详细讲解。
6、tuple
元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同,元组用于精确描述一组值的类型,?表示可选的元素。
// let a: tuple // 警告:找不到名称“tuple”。 它不是一个关键字
// [string, number] 就是元组,且 存储元素位置的类型 应 对应声明的一致
// let arr1: [string, number] = [2, '1'] // 警告:类型的参数不能赋值不对应的声明类型。
// let arr1: [string, number] = ['1', 1]
// 元组 number 类型可以包含可选元素
// let arr2: [string, number?];
// arr2 = ['1'];
// arr2 = ['1', 1];
// ...string[] 表示剩余参数,后面可以有任意多个 string 类型
let arr3: [boolean, number, ...string[]] = [true, 1, 'asddsadas', 'asdadsasdads'];
7、enum
枚举(enum) 可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护。
如下代码的功能是:根据调用 walk 时传入的不同参数,执行不同的逻辑,存在的问题是调用 walk 时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的up、down、left、right 是连续且相关的一组值,那此时就特别适合使用 枚举(enum )。
const walk = (str: string): void => {
if(str === 'up'){
console.log('向【上】走');
}else if(str === 'down'){
console.log('向【下】走');
}else if(str === 'left'){
console.log('向【左】走');
}else if(str === 'right'){
console.log('向【右】走');
}else{
console.log('未知方向');
}
}
walk('up');
walk('down');
walk('left');
walk('right');
数字枚举
数字枚举一种最常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,在下面代码的打印中,不难发现:可以通过值来获取对应的枚举成员名称。
enum Direction{
// 默认从0开始
Up = 5,
Down = 12,
Left, // 以顺序最后的值开始递增 这里就是 13
Right // 这里就是 14
}
function walk(str: Direction) {
console.log(str);
if (str === Direction.Up) {
console.log("向【上】走");
}else if (str === Direction.Down) {
console.log("向【下】走");
}else if (str === Direction.Left) {
console.log("向【左】走");
}else if (str === Direction.Right) {
console.log("向【右】走");
}else {
console.log("无效的参数");
}
}
walk(Direction.Left);
字符串枚举
枚举成员的值是字符串。注意:这里就没有反向映射
enum Direction{
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right'
}
function walk(str: Direction) {
console.log(str);
if (str === Direction.Up) {
console.log("向【上】走");
}else if (str === Direction.Down) {
console.log("向【下】走");
}else if (str === Direction.Left) {
console.log("向【左】走");
}else if (str === Direction.Right) {
console.log("向【右】走");
}else {
console.log("无效的参数");
}
}
常量枚举
官方描述:常量枚举是一种特殊枚举类型,它使用 const 关键字定义,在编译时会内联,避免生成一些额外的代码。
何为编译时内联?
所谓“内联”其实就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象。这可以减少生成的 JavaScript 代码量,并提高运行时性能。
使用普通枚举的TypeScript效果如下:
常量枚举后,效果如下:
8、type
type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行类型复用和扩展。
基本用法:
类型别名使用 type 关键字定义,type 后跟类型名称,例如下面代码中 Num 是类型别名。
type Num = number;
function printNum(num: Num) :void{
console.log(num);
}
printNum(200);
联合类型:
联合类型是一种高级类型,它表示一个值可以是几种不同类型之一。(简单理解:或 概念)
type Status = number | string;
type gender = 'male' | 'female';
function printStatus(status: Status) :void{
console.log(status);
}
function printGender(gender: gender) :void{
console.log(gender);
}
printStatus(200);
printStatus('string');
printGender('male');
printGender('female');
交叉类型
交叉类型(Intersection Types):允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交又类型通常用于对象类型。(简单理解:“ 并且and ” 概念)
type Area = {
hight: number,
width: number
}
type Address = {
num: number // 楼号
cell: number // 单元号
room: string // 房间号
}
type House = Address & Area
const house: House = {
hight: 10,
width: 20,
num: 3,
cell: 4,
room: 'B-324'
}
type Demo = number & string
// Demo 无法成为值,它是 never 类型
let x: Demo;
9、特殊情况
情况1:在函数定义时,限制函数返回值为 void ,那么函数的返回值就必须是空 或 undefined
// 注意书写习惯,当函数返回值为 空 的时候,加上 void 限制
function demo(): void{
return;
return undefined;
// 以下代码均不合法
// return 100;
// return true;
// return "hello world";
// return null;
}
情况2(特殊):
使用类型声明限制函数返回值为 void 时,Typescript 并不会严格要求函数返回空。
type LogFunc = () => void; // 函数声明,只是声明,并没有变量
// 所以这里报错了
// LogFunc = function () { }
// 有了变量后声明是哪个函数表达式
const f1: LogFunc = function () {
// 特殊情况出现了:校验无效,允许返回
return 999
}
为什么会这样?
是为了确保如下代码成立,我们知道Array.prototype.push的返回一个数字,而Array.prototype.forEach 方法期望其回调的返回类型是 void
const src = [1, 2, 3] const dst = [0] // forEach 里面的匿名箭头函数 最后返回值是 dst.push(el) src.forEach((el) => dst.push(el)) src.map((item) => { return item + 1 }) src.find((item) => { return item > 1 })
10、复习类相关知识
本小节是复习类相关知识,如果有相关基础可以跳过。这里的类知识跟 Java 的类知识基本一致。
class类
class Person{
// 类属性
private name: string
// 类构造器
constructor(name: string) {
this.name = name
}
// 类方法
public speak(){
console.log(`Hi, I am ${this.name}`)
}
public getName(){
return this.name
}
}
const p1 = new Person('Angindem')
p1.speak();
继承类
class Person{
// 类属性
private name: string
// 类构造器
constructor(name: string) {
this.name = name
}
// 类方法
public speak(){
console.log(`Hi, I am ${this.name}`)
}
public getName(){
return this.name
}
}
// const p1 = new Person('Angindem')
// p1.speak();
// 继承 Person 类
class Student extends Person{
private age: number
// 继承父类的构造器
constructor(name: string, age: number) {
super(name);
this.age = age
}
public study(){
console.log(`I am studying`)
}
// override 重写符号,对这个方法进行了覆盖重写
override speak(){
console.log(`I am ${this.getName()} and I am ${this.age} years old`)
}
}
const s1 = new Student('Angindem', 18)
s1.study()
s1.speak()
11、属性修饰符
修饰符 | 含义 | 具体规则 |
public | 公开的 | 可以被:类内部、子类、类外部访问。 |
protected | 受保护的 | 可以被:类内部、子类访问。 |
private | 私有的 | 可以被:类内部访问。 |
readonly | 只读属性 | 属性无法修改 |
注意点:Script中 class 类内部没有指定属性修饰符的时候默认是 public
简写构造器
class Person{
// 简写构造器,构造同时声明属性
constructor(public name:string,public age:number){}
}
const p1 = new Person('张三', 18);
console.log(p1.name);
12、抽象类
概述:抽象类是无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法。
通过以下场景,理解抽象类:
我们定义一个抽象类 Package ,表示所有包裏的基本结构,任何包裏都有重量属性 weight ,包裏都需要计算运费。但不同类型的包裹(如:标准速度、特快专递)都有不同的运费计算方式,因此用于计算运费的 calculate 方法是一个抽象方法,必须由具体的子类来实现。
// 定义抽象类
abstract class Package {
// 构造方法
constructor(public weight:number) {
}
// 抽象方法
abstract calculateCost(): number
// 具体方法
printPackageInfo() {
console.log(`包裹的重量为 ${this.weight} kg,运费为 ${this.calculateCost()} 元`);
}
}
class StandardPackage extends Package {
constructor(
weight: number,
public unitPrice: number
) {
super(weight);
}
// 实现抽象方法
calculateCost(): number {
return this.weight * 0.5 * this.unitPrice;
}
}
const s1 = new StandardPackage(5, 4);
s1.printPackageInfo();
总结:何时使用抽象类?
定义通用接口:为一组相关的类定义通用的行为(方法或属性)时。
提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现。
确保关键实现:强制派生类实现一些关键行为。
共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
13、接口
interface 是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定义格式,不能包含任何实现!
定义类结构
interface PersonInterface {
name: string;
speak(n:number): void;
}
class Person implements PersonInterface {
constructor(public name:string){}
speak(n: number): void {
for(let i = 0; i < n; i++){
console.log(`${this.name} says hello!`);
}
}
}
const p = new Person('Angindem');
p.speak(5);
定义对象结构
interface CountInterface {
(a: number, b: number): number;
}
// 接口限制函数参数和返回值类型
const count: CountInterface = (a: number, b: number): number => {
return a + b;
};
定义函数结构
interface CountInterface {
(a: number, b: number): number;
}
// 接口限制函数参数和返回值类型
const count: CountInterface = (a: number, b: number): number => {
return a + b;
};
接口之间的继承
interface继承另一个interface,从而实现代码的复用
interface PersonInterface {
name: string;
age: number;
}
interface StudentInterface extends PersonInterface {
grade: string;
}
const stu:StudentInterface = {name: '张三', age: 18, grade: 'A'};
接口自动合并(可重复定义)
interface PersonInterface {
name: string;
age: number;
}
interface PersonInterface {
gender: string;
}
const p: PersonInterface = {
name: '张三',
age: 18,
gender: '男'
}
总结:何时使用接口?
- 定义对象的格式:描述数据模型、API 响应格式、配置对象!..等等,是开发中用的最多的场景。
- 类的契约:规定一个类需要实现哪些属性和方法。
- 自动合并:一般用于扩展第三方库的类型,这种特性在大型项目中可能会用到。
14、一些相似概念的区别
14.1 interface 与 type 区别
相同点:interface 和 type都可以用于定义对象结构,两者在许多场景中是可以互换的。
不同点:
- interface:更专注于定义对象和类的结构,支持继承、合并。
- type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并。
- interface 和 type 都可以定义对象结构
- interface 可以继承、合并
- type 的交叉类型
14.2 interface 与 抽象类的区别
相同点:都用于定义一个类的格式(应该遵循的契约)
不相同:
- 接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口。
- 抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类。
- 一个类 可以 实现多个接口
八、泛型
泛型允许我们在定义函数、类或接口时,使用类型参数来表未指定的类型,这些参数在具体使用时才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
举例:如下代码中<T>就是泛型,(不一定非叫 T),设置泛型后即可在函数中使用 T来表示该类型:
泛型函数
// 泛型函数
function logData<T>(data: T): T{
console.log(data);
return data
}
logData<string>('hello world');
logData<number>(123);
泛型可以有多个
function logData<T, U>(data1: T, data2: U): T | U {
Date.now() % 2 ? console.log(data1) : console.log(data2);
return Date.now() % 2 ? data1 : data2;
}
logData<string,boolean>('hello world',true);
logData<number,boolean>(123,true);
泛型接口
interface PersonInterface<T>{
name: string;
age: number;
extraInfo: T;
}
type Job = {
jobTitle: string;
salary: number;
}
let p : PersonInterface<Job> = {
name: 'Jane',
age: 25,
extraInfo: {
jobTitle: 'Software Developer',
salary: 50000
}
}
泛型类
class Person<T>{
constructor(
public name: string,
public age: number,
public extraInfo: T
) { }
speak() {
console.log(`${this.name} is ${this.age} years old`)
console.log(this.extraInfo);
}
}
type Job = { job: string };
const p1 = new Person<string>('Mike', 20, 'I am a string');
p1.speak();
const p2 = new Person<Job>('Mike', 20, { job: 'Software Engineer' });
p2.speak();
九、类型声明文件(.d.ts)
类型声明文件是 TypeScript 中的一种特殊文件,通常以.d.ts 作为扩展名。它的主要作用是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示。
一般项目中都会在 types 文件夹中。
假设有demo.js
export function add(a, b) {
return a + b;
}
export function mul(a, b) {
return a * b;
}
然后我们 ts 引入
原因:引入的 JS 默认为 any ,爆出警告,所以我们需要类型声明文件
// demo.d.ts
// declare : 声明
declare function add(a: number, b: number): number;
declare function mul(a: number, b: number): number;
export {
add,
mul
};