1、TS初见
1.1、为什么要有 TypeScript
TypeScript是JavaScript的超集,因为它扩展了JavaScript,有JavaScript没有的东西。硬要以父子类关系来说的话,TypeScript是JavaScript子类,继承的基础上去扩展。
`TypeScript的初衷就是为了做类型检查,提早发现错误,所以「类型」是其最核心的特性。当然它只是给出你代码可能不会按预期执行的警告,比如你未按照声明的类型传参,你的代码还是可以运行的。这一点与强类型语言还是有本质的区别,强类型语言会直接导致编译不通过,因为TypeScript只是转译。跟JavaScript不同,TypeScript文件后缀使用 .ts扩展名。浏览器是不识别 .ts 文件,所以使用时必须提前把 TS 代码转换成 JavaScript 代码。这个转换过程被称为转译,编译和转译的微小差别在于:
编译是把源码转变成另一种语言
转译是把源码转变另一个相同抽象层级的语言
1.2、前提准备
- 安装 node环境下安装 npm install -g typescript
- 手动编译 tsc **.ts
- vscode自动编译 tsc --init 在tsconfig.js中配置,在终端打开tsc监视即可
- ts语法提示错误,不影响js的编译,因为js是弱类型语言(如类型注解不通过)
// 类型注解 一种轻量级的为函数或变量添加约束
(() => {
function showMsg(str:String) {
return str + '123214'
}
let str = "woshi"
console.log(showMsg(str));
})()
// 接口演示 定义接口
(() => {
interface Iperson{
fristName:string,
lastName:string
}
// 输出对象
function showFul`在这里插入代码片`lName(person:Iperson) {
return person.fristName + person.lastName
}
// 定义对象
const person = {
fristName: 'dongfnag',
lastName: 'yuechu'
}
// 打印
console.log(showFullName(person))
})()
1.3、基础类型
1、布尔 数字 字符串
let a:boolean = true
let b:number = 1010
let c:string = '字符串'
//字符串可与数字拼接 TS中变量一开始是什么类型就只能用这个类型,不允许混用
2、undefined & null
let und:undefined = undefined
let nul:null = null
// 给其他类型赋予
let num:number = null
let num:number = undefined
// undefined和null 可以作为其他类型的子类型,可以将undef和null赋予其他类型(需关闭ts严格模式)
3、 数组和元组
数组定义方式1
let arr:number[] = [10,20,30]
数组定义方式2(泛型写法)
let arr:Array<number> = [1,2,3]
元组Tuple
let arr:[string,boolean] = ['字符串', true]
4、Enum枚举 (ts补充)void
使用枚举类型可以为一组赋值赋予友好的名字
void没有返回值的类型,打印undefined
5、any & unknown
unknown
与any
的最大区别是:
unknown是 top type(任何类型都是它的 subtype) , 而 any
既是 top type
, 又是 bottom type
(它是任何类型的 subtype
) , 这导致 any
基本上就是放弃了任何类型检查。因为any
既是top type
, 又是 bottom type
,所以任何类型的值可以赋值给any
,同时any
类型的值也可以赋值给任何类型。但unknown
只是 top type
,任何类型的值都可以赋值给它,但它只能赋值给unknown
和any
,因为只有它俩是top type
。
// any`会跳过类型检查器对值的检查,任何值都可以赋值给`any`类型,它通常被称为`top type
let notSure: any = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK
// unknown与any一样,所有类型都可以分配给unknown:
let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK
6、object & never
never
类型表示的是那些永不存在的值的类型。
never
与其他类型的联合后,是没有never
的
值会永不存在的两种情况:
-
如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
-
函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
-
never
类型同null
和undefined
一样,也是任何类型的子类型,也可以赋值给任何类型: -
但是没有类型是
never
的子类型或可以赋值给never
类型(除了never
本身之外),即使any
也不可以赋值给never
:
object
function getobj(obj:object):object {
console.log(obj)
return {
name: 'kakaxi'
}
}
console.log(getobj({name: '213'}));
1.4、类型(联合 断言 交叉 推断)
1.1、联合类型
联合类型表示取值可以为多种类型中的一种,使用 |
分隔每个类型。当任意属性使用联合类型且属性中存在可选属性时,需要联合undefined
类型,否则编译报错,原因显而易见,因为可选属性可有可无
let a: string | number
a = 'ok',
a = 11
1.2、类型断言
类型断言好比其它语言里的类型转换,类型转换通常发生在你比TS
更了解某个值的详细信息的时候。(相当于告诉编译器,我知道我是什么类型,知道在干什莫,相信我!按我说的处理)
// 类型断言两种方法<> he as
// 例:<>断定变量str为字符串类型
(<string>str)
// as方法
(str as string)
1.3、类型推断
如果没有指定明确的类型,那么TS会按照类型推论的规则推断出一个类型,如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any
类型而完全不被类型检查
1.4、交叉类型
交叉类型取的多个类型的并集,但是如果key
相同但是类型不同,则该key
为never
类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用&
定义交叉类型。
interface A {
name: string,
age: number
}
interface B {
name: string,
gender: string
}
let a: A & B = { // OK
name: "兔兔",
age: 18,
gender: "男"
};
1.5、接口
接口是对象状态(属性)和行为(方法)的抽象(描述)
(接口是一种类型 规范 规则 约束 能力)
可选属性readonly 只读属性?
interface Props {
readonly name: string;
age: number;
money?: number;
}
对于数组,TS
还有ReadonlyArray<T>
类型,此类型将数组的所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
readonly
声明的只读数组类型与ReadonlyArray
声明的只读数组类型,二者等价
接口的继承
接口继承接口使用关键字extends ,继承的本质是复制,抽出共同的代码,所以子接口拥有父接口的类型定义
interface Shape {
name: string
}
interface Square extends Shape {
age: number
}
let square: Square = {name: '雷', age: 18}
接口可以多继承,大部分语言是不支持多继承的,原因是会引发混乱
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VB9MHWY-1631874347575)(image-20210907172322403.png)]
接口定义函数
采用函数表达式接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。
interface SearchFunc {
(source: string, subString:string): boolean
}
// 左侧进行限制 限制参数为两个且必须为字符串类型,返回值必须为布尔型
let mySearch: SearchFunc = function(source: string, subString:string) {
let result = source.search(subString);
return result >-1;
}
1.6、函数
1.1、 函数声明 & 函数表达式
// 声明
function sum(x:number, y:number) {
return x + y
}
// 表达式
let mysum:(x:number, y:number) => number = function(x;number, y:number):number {
return x + y
}
// 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。切忌与ES6的箭头函数混淆了。
1.2、函数中的this
声明
ts会通过代码流分析出this是属于什么类型,,当然我们也可以明确的指定函数中的this是什么类型,this类型变量必须放在参数的首位
interface Obj {
fn: (this: Obj, name: string) => void;
}
let obj: Obj = {
fn(name: string) {}
}
let rab: Obj ={
fn(name: string) {}
}
obj.fn("兔兔"); // OK
obj.fn.call(rab, "兔兔"); // OK
obj.fn.call(window, "兔兔"); // Error: this 应该为 Obj 类型
1.3、参数
可选参数
参数后加问号(?)注:可选参数后面不允许再出现必需参数
参数默认值
在函数定义时,参数内直接赋值
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
剩余参数
同js,用 … 表示
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
1.4、重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
重载
的概念在学JAVA
(JAVA中的重载
)的时候接触到的,JS
是没有这个概念的,TS
的重载感觉更应该称之为函数签名重载
。因为最后函数实现的内部还是依赖判断类型来处理,前面的函数定义只是为了精确表达输入类型对应的输出类型。
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('');
}
}
1.7、 类
修饰符
- public 属性修饰符默认为
public
公共的,即类的属性、方法可以在外部访问 - private 与
public
相对,私有修饰符,即类的属性、方法不可以在外部访问 - protected 修饰符与
private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问。注意,这里是派生类中,而不是实例、子类实例。 - readonly 只读属性修饰符 构造函数中可以对只读属性进行修改 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lktmu4F5-1631874347577)(image-20210910114715199.png)]
- 发生继承关系后类与类的叫法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcjLUAry-1631874347578)(image-20210909200925647.png)]
参数属性
我们也可以在类的内部方法上对参数使用public、private、protected
修饰符,它的作用是可以更方便地让我们在一个地方定义并初始化一个成员。
class Animal {
constructor(public name: string, private age: number, protected sex: string) {}
}
接口实现类
TypeScript
也能够用接口来明确的强制一个类去符合某种契约。类去实现接口,这里使用关键字implements
// 一个类可以实现多个接口
interface Age {
age: number;
}
interface Title{
title: string;
}
class title implements Title, Age{
title: string = '兔兔';
age: number = 18;
}
抽象类
抽象类做为其它派生类的基类使用, 不允许被实例化。 不同于接口,抽象类可以包含成员的实现细节。
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含abstract
关键字并且可以包含访问修饰符。
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // OK:允许创建一个对抽象类型的引用
department = new Department(); // Error: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // OK:允许对一个抽象子类进行实例化和赋值
department.printName(); // OK
department.printMeeting(); // OK
department.generateReports(); // Error: 方法在声明的抽象类中不存在
1.8、 泛型<>
erateReports(): void {
console.log(‘Generating accounting reports…’);
}
}
let department: Department; // OK:允许创建一个对抽象类型的引用
department = new Department(); // Error: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // OK:允许对一个抽象子类进行实例化和赋值
department.printName(); // OK
department.printMeeting(); // OK
department.generateReports(); // Error: 方法在声明的抽象类中不存在
## 1.8、 泛型<>
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。