Typescript
1.什么是 Typescript(TS)
Typescript 简称 TS
TS 和 JS 之间的关系类似 Less/Sass 和 CSS 之间的关系,就像 Less/Sass 最终会转换成 CSS 一样,编写好的 TS 代码最终也会转换成 JS
2.为什么需要 TS
因为 JS 是弱类型,很多错误只有在运行时才会被发现,而 TS 是强类型,它提供一套静态检测机制,可以帮助我们在编译时就发现错误
3.TS 特点
- 支持最新的 JS 新特性
- 支持代码静态检查
- 支持诸如 C,C++,Java,Go 等后端语言中的特性
4.核心内容
1.基础类型
TS 支持与 JS 几乎相同的数据类型,此外还提供了使用的枚举类型
- number
- string
- null
- undefined
- boolean
2.数组和元祖类型
数组类型
let arr1: Array<number>; // 数组中只能存储数值类型的数据
arr1 = [1, 2, 3];
let arr2: string[]; // 数组中只能存储字符串类型的数据
arr2 = ["a", "b", "c"];
联合类型
let arr3: (number | string)[]; // 数组中可以存储数值类型的数据也可以存储字符串类型的数据
arr3 = [1, "a", 2, "c"];
任意类型
let arr4: any[]; // 数组中可以存储任意类型的数据
arr4 = [1, "v", false];
元祖类型
TS 中的元祖类型其实就是数组类型的扩展
元祖用于保存固定长度固定数据类型的数据
let arr5: [string, number, boolean]; // 元祖中可以存储3个元素,第一个元素必须是字符串类型,第二个元素必须是数字类型,第三个元素必须是布尔类型
arr5 = ["a", 1, true];
3.枚举类型
枚举类型是 TS 为 JS 扩展的一种类型,在原生的 JS 中
枚举用于表示固定的几个取值
enum Gender {
Male,
Femal
}
let val:Gender; // 定义一个叫val的变量,这个变量只能保存Male或者Femal
val = Gender.Male; // 正确
val = Gender.Femal; // 正确
val = 'str' // 错误
val = 666 // 正确
// 可以通过枚举值拿到对应的数字
console.log(Gender.Male) // 0
console.log(Gender.Female) // 1
// 可以通过对应的数据拿到枚举值,原因看底层实现原理
console.log(Gender[0])
console.log(Gender[1])
注意点
- TS 中的枚举底层实现的本质其实就是数值类型,所以赋值一个数值不会报错
- TS 中的枚举类型的取值,默认是从上至下从 0 开始递增,虽然默认是从 0 开始递增,但是可以手动的指定枚举的取值的值
- TS 手动指定前面枚举值的取值,后面枚举值的取值会根据前面的值递增
- TS 手动指定后面枚举的取值,没那么前面枚举值的取值不会受到影响
- TS 手动修改多个枚举值的取值,修改的是什么就是什么
底层实现原理
左边输入:
enum Gender {
Male,
Female
}
右边点 JS 运行
var Gender;
(function (Gender) {
Gender[(Gender["Male"] = 0)] = "Male";
Gender[(Gender["Femal"] = 1)] = "Femal";
})(Gender || (Gender = {}));
=====>
Gender["Male"] = 0;
Gender[0] = "Male";
Gender["Female"] = 1;
Gender[1] = "Female";
4.any 类型
any 表示任意类型,当不清楚某个值的具体类型的时候可以使用 any。
- 一般用于定义一些通用性比较强的变量,或者用于保存从其他框架红获取的不确定类型的值
- 在 TS 中任何数据类型的值都可以给 any 类型
let value: any;
value = 123;
value = "ba";
value = true;
value = [1, 2, 3];
5.void 类型
void 与 any 相反,表示没有任何类型,一般用于函数返回值。
在 TS 中只有 null 和 undefined 可以赋值给 void 类型
function test(): void {
console.log("hello void");
}
test();
let value: void; // 定义了一个不可以保存任意类型数据逇变量,只能保存null和undefined
value = null;
value = undefined;
注意点:null 和 undefined 是所有类型的子类型,所以可以将 null 和 undefined 赋值给任意类型
6.Never 类型
表示的是哪些用不存在的值的类型
一般用于抛出异常或根本不可能有返回值的函数
function demo(): never {
throw new Error("报错了");
}
demo();
function demo2(): never {
while (true) {}
}
demo2();
7.Object 类型
表示一个对象
let obj: object; // 定义了一个只能保存对象的变量
obj = [1, 2];
obj = { name: "ll" };
8.类型断言
8.1 什么是类型断言
TS 中的类型断言和其他编程语言的类型转换很像,可以将一种类型强制转换成另一种类型。类型断言就是告诉编译器, 你不要帮我们检查了, 相信我,我知道自己在干什么。
/*
例如: 我们拿到了一个any类型的变量, 但是我们明确的知道这个变量中保存的是字符串类型
此时我们就可以通过类型断言告诉编器, 这个变量是一个字符串类型
此时我们就可以通过类型断言将any类转换成string类型, 使用字符串类型相关的方法了
*/
let arr:any = 'sss666'
// 方式一
let len = (<string>str).length
// 方式二
let len2 = (str as string).length
注意点:在企业开发中推荐使用 as 来进行类型转换(类型断言),因为方式一有兼容性问题,在使用到了 JSX 的时候兼容性不是很好
9.接口类型
9.1 什么是接口类型
和 number、string、boolean、enum 这些数据类型一样,接口也是一种类型,用来约束使用者
需求:
要求定义一个函数输出一个人完整的姓名, 这个人的姓必须是字符串, 这个人的名也必须是一个字符
- 之前未使用,当参数不符合要求,编译时正常
let obj = {
firstName:'s',
<!-- lastName:'b' -->
lastName:11
}
function say({firstName,lastName}):void{
console.log(`姓名的是:${firstName}_${lastName}`)
}
say(obj)
- 使用 interface,当参数不符合时,在编译的时候就会提示报错
interface FullName{
firstName:string
lastName:string
}
let obj = {
firstName:'s',
lastName:'b',
// lastName:18 // 类型不对,会报错
}
function say({firstName,lastName}):void{
console.log(`姓名的是:${firstName}_${lastName}`)
}
say(obj)
**注意点:**如果使用了接口来限定变量或者形参,再给变量或者形参赋值的时候,赋予的值就必须和接口限定的一模一样才可以,多一个或者少一个都不行
9.2 可选属性和索引签名
在开发中,当变量或形参与接口中的数量不一致
9.2.1 可选属性
1)少一个或者少多个?使用 可选属性
使用 ?
interface FullName {
firstName:string
lastName:string
middleName?:string
}
function say({firstName,lastName,middleName}:FullName):void {
if(middleName){
console.log(`你的姓名:${firstName}_${middleName}_${lastName}`)
}else {
console.log(`你的姓名:${firstName}_${lastName}`)
}
}
say({firstName:'s',middleName:'b',lastName:'a'})
say({firstName:'s',lastName:'a'})
9.2.1 索引签名
1)多一个或者多多个
使用类型断言
使用 as
say({firstName:'s',lastName:'b',middleName:'a',abc:'aaa'} as FullName)
使用变量
let obj = { firstName: "s", lastName: "b", middleName: "a", abc: "aaa" };
say(obj);
使用索引签名
interface 里使用 [propName:类型]:类型
// 索引签名
interface FullName {
firstName:string
lastName:string
middleName?:string
[propName:string]:string
}
function say({firstName,lastName,middleName}:FullName):void {
if(middleName){
console.log(`你的姓名:${firstName}_${middleName}_${lastName}`)
}else {
console.log(`你的姓名:${firstName}_${lastName}`)
}
}
say({firstName:'s',lastName:'b',middleName:'a',abc:'aaa'})
9.2.1 索引签名
什么是索引签名
索引签名用于描述那些通过索引得到的类型,比如 arr[10]或 obj[‘key’]
interface FullName {
[propName: string]: string;
}
let obj: FullName = {
firstName: "s",
lastName: "b",
// middleName: false // 报错,类型有误
// false:'123' // 无论key是什么类型都会自动转换成字符串类型,所以不会报错
};
interface stringArray {
[propName: number]: string;
}
let arr: stringArray = {
0: "a",
1: "b",
2: "c",
};
console.log(arr[0]); // a
console.log(arr["0"]); // a
let arr2: stringArray = ["d", "e", "f"];
console.log(arr2[0]); // d
console.log(arr2["0"]); // d
9.3 只读属性
让对象属性只能在对象刚刚创建的时候修改其值
interface Idp {
readonly ID:number,
fullName:string
}
let yourID:Idp = {
ID:1234,
fullName:'sej'
}
// yourID.ID = 234 // 报错,只读不能修改
TS 内部对只读属性进行了扩展,扩展出来了一个只读数组
let arr: ReadonlyArray<string> = ["a", "b"];
// arr[1] = 'c' // 报错,只读,不能修改
9.4 函数接口和混合类型接口
9.4.1 函数接口
除了通过接口来限定对象,还可以使用接口来限定函数
interface SumInterface {
(a: number, b: number): number;
}
let sum: SumInterface = function (x: number, y: number): number {
return x + y;
};
// let sum:SumInterface = function(x,y) {
// return x + y
// }
let res = sum(10, 22);
9.4.2 混合类型接口
约定的内容中既有对象属性,又有函数
-
要求定义一个函数实现变量累加
以前的写法
let count = 0; // 会污染全局空间
function Demo() {
count++;
console.log(count);
}
Demo();
Demo();
Demo();
解决方法一:闭包
let Demo = (() => {
let count = 0;
return () => {
count++;
console.log(count);
};
})();
Demo(); // 1
Demo(); // 2
Demo(); // 3
解决方法二:混合类型接口
引子:在 JS 中函数的本质是什么?就是一个对象
// JS中
let demo = function () {
demo.count++;
};
demo.count = 0;
demo();
demo();
demo();
interface CountInterface {
():void
count:number
}
/**
* 混合接口中的的条件全都满足
* CounterInterface接口要求数据既要是一个没有参数没有返回值的函数,又要是一个拥有count属性的对象
* fn作为函数的时候符合接口中函数接口的限定 ():void
* fn作为对象的时候符合接口中对象属性的限定 count:number
函数就是一个对象
*/
let getCounter = (function ():CountInterface {
let fn = function(){
fn.count++;
console.log(fn.count);
} as CountInterface // 类型推断
fn.count = 0
return fn;
} )()
getCounter();
getCounter();
getCounter();
9.5 接口的继承
TS 中的接口和 JS 中的类一样是可以继承的
extends
interface LengthInterface {
length: number;
}
interface WidthInterface {
width: number;
}
interface ReacInterface extends LengthInterface, WidthInterface {
height: number;
}
let rect: ReacInterface = {
length: 10,
width: 20,
height: 30,
};
10.函数
TS 中的函数大部分和 JS 相同
10.1 函数的分类
10.1.1 命名函数
// JS中命名函数
function say1(name) {
console.log(name);
}
// TS中的命名函数
function say1(name: string): void {
console.log(name);
}
10.1.2 匿名函数
// JS中的匿名函数
let say2 = function (name) {
console.log(name);
};
// TS中的匿名函数
let say2 = function (name: string): void {
console.log(name);
};
10.1.3 箭头函数
// JS箭头函数
let say3 = (name) => {
console.log(name);
};
// TS箭头函数
let say3 = (name: string): void => {
console.log(name);
};
TS 的函数需要给参数指定类型
10.2 TS 函数完整格式
在 TS 中函数的完整格式应该是由函数的定义和实现两部分组成的
- 写法一
// 定义一个函数
let AddFun: (a: number, b: number) => number;
// 根据定义实现函数
AddFun = function (x: number, y: number): number {
return x + y;
};
let res = AddFun(10, 20);
- 写法二:一步到位写法
let AddFun: (a: number, b: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
// 根据函数的定义自动推到对应的数据类型
let AddFun: (a: number, b: number) => number = function (x, y) {
return x + y;
};
let res = AddFun(20, 20);
10.3 TS 函数声明
type
// 先声明一个函数
type AddFun = (a: number, b: number) => number;
// 再根据声明去实现这个函数
let add: AddFun = function (x: number, y: number): number {
return x + y;
};
let add: AddFun = function (x, y) {
return x + y;
};
let res = add(30, 20);
10.4 TS 函数重载
函数的重载就是同名的函数可以根据不同的参数实现不同的功能
- 之前的写法(同名的函数会报错)
function getArray(x: number): number[] {
let arr = [];
for (let i = 0; i <= x; i++) {
arr.push(i);
}
return arr;
}
function getArray(str: string): string[] {
return str.spilt("");
}
- 重载写法
// 定义函数的重载
function getArray(x:number):number[];
function getArray(str:string):string[]
// 实现函数的重载
function getArray(value:any):any[]{
if(typeof value === 'string'){
return value.split('');
}else{
let arr = [];
for(let i = 0; i <= value; i++){
arr.push(i);
}
return arr;
}
}
let res = getArray("hhxx,ttxs")
10.4 可选参数
10.4.2 ?
function add(x: number, y: number, z?: number): number {
return x + y + (z ? z : 0);
}
let res = add(10, 20);
10.4.2 可选参数可以配置函数重载一起使用,可以让函数重载变得更加强大
function add(x:number,y:number):number;
function add(x:number,y:number,z:number):number;
function add(x:number,y:number,z?:number){
return x + y + (z?z:0)
}
let res = add(10,20,30)
10.4.3 可选参数的位置
可选参数后面只能跟可选参数
function add(x:number,y?:number,z?:number):number {
return x + (y ? y : 0) + (z ? z + 0)
}
let res = add(10) // 可选参数可以是一个或多个(多个的峰值是限制的最多个数)
10.5 默认参数
function add(x: number, y: number = 10): number {
return x + y;
}
let res = add(10);
10.6 剩余参数
function add(x: number, ...ags: number[]) {
console.log(x); // 10
console.log(ags); // [20, 30, 40, 50]
}
add(10, 20, 30, 40, 50);
10.泛型
10.1 什么是泛型?
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性
- 在编写代码的时候我们既要考虑代码的健壮性, 又要考虑代码的灵活性和可重用性
通过 TS 的静态检测能让我们编写的代码变得更加健壮, 但是在变得健壮的同时却丢失了灵活性和可重用性
所以为了解决这个问题 TS 推出了泛型的概念 - 通过泛型不仅可以让我们的代码变得更加健壮, 还能让我们的代码在变得健壮的同时保持灵活性和可重用性
// 需求: 定义一个创建数组的方法, 可以创建出指定长度的数组, 并且可以用任意指定的内容填充这个数组
let getArray = (value: number, items: number = 5): number[] => {
return new Array(items).fill(value);
};
// let arr = getArray(6,3)
// let arr = getArray('abc',3) // 报错,因为指定的类型是number
// 如果可以存放任意类型
let getArray = (value: any, items: number = 5): any[] => {
return new Array(items).fill(value);
};
// let arr = getArray("abc", 3);
let arr = getArray(6, 3);
// console.log(arr);
上面的问题:
let res = arr.map((item) => item.length); // ['abc', 'abc', 'abc'] => [3, 3, 3]
console.log(res);
- 1.编写代码没有提示,因为 TS 的静态检测不知道具体是什么类型
- 2.哪怕代码写错了也不会报错,因为 TS 的静态检测不知道具体是什么类型
// 需求:要有代码提示, 如果写错了要在编译的时候报错
let getArray = <T>(value: T, items: number = 5): T[] => {
return new Array(items).fill(value);
};
// let arr = getArray<string>('abc');
// let arr = getArray<number>(6);
// 注意点: 泛型具体的类型可以不指定
// 如果没有指定, 那么就会根据我们传递的泛型参数自动推导出来
let arr = getArray("abc");
// let arr = getArray(6);
let res = arr.map((item) => item.length);
console.log(res);
在函数名后添加 ,其中 T 用来指代任意输入的类型,在后面的输入 value:T 和输出 T[]中即可使用了。
注意:使用 ts-node 编译的时候,如果你要使用一些 ES6 的新语法,你需要引入 ES6 这个库,或者也可以写 ES2015。
在 tsconfig.json 文件中 在 “bin”:[“es6”,“dom”] lib 用于指定要包含在编译中的库文件
- target 用于指定编译之后的版本目标 version: ‘ES3’ (default), ‘ES5’, ‘ES2015’, ‘ES2016’, ‘ES2017’,‘ES2018’ or ‘ESNEXT’.
- lib 用于指定要包含在编译中的库文件,这个我们在前面的课程中讲过一点,如果你要使用一些 ES6 的新语法,你需要引入 ES6 这个库,或者也可以写 ES2015。
10.1 泛型约束
10.1.1 什么是泛型约束
默认情况下可以指定泛型为任意类型,有些情况下需要指定的类型满足某些条件才能指定,这个时候就可以使用泛型约束
// 需求:要求指定的泛型类型必须有length属性才可以
interface HaveLength {
length:number
}
let getArray = <T extends HaveLength>(value:T): T =>{
console.log(value);
return value
}
let arr = getArray('123')
// let arr2 = getArray(123) // 报错,数字类型没有length属性
10.1.2 在泛型中使用类型参数
一个泛型被另一个泛型约束,就叫做泛型约束中使用类型参数
// 需求:定义一个函数用于根据指定的key获取对象的value
let getProps = (obj: object, key: string): any => {
return obj[key]; // 报错,不知道返回的是什么
};
let obj = {
a: "a",
b: "b",
};
// let res = getProps(obj, "a"); // 上面编译报错,但是还是会返回 a
// let res = getProps(obj, "b"); // 上面编译报错,但是还是会返回 b
let res = getProps(obj, "c"); // 不存在c,但是编译不报错,undefined
// 代码不够健壮,明明没有c这个key但是却没有报错
console.log("res", res);
// 解决方法一(只能让return obj[key]不报错)
interface KeyInterface {
[key: string]: any;
}
let getProps = (obj: KeyInterface, key: string): any => {
return obj[key];
};
// 当输入不存在的key时,编译时也不报错
// 解决方法二:在泛型约束中使用类型参数
// K extends keyof T,K表示传递的key必须是T中存在的key
let getProps = <T, K extends keyof T>(obj: T, key: K): any => {
return obj[key];
};
let obj = {
a: "a",
b: "b",
};
// let res = getProps(obj, "c"); // 编译时就会报错
console.log('res',res);
11.类
TS 中的类和 ES6 中的类’几乎’一样
class Person {
// 注意点:在TS中如果定义了实例属性,name就必须在构造函数中使用,否则就会报错
name: string; // 需要先定义实例属性,才能够使用实例属性
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say(): void {
console.log(`我的名称叫${this.name},我的年龄是${this.age}`);
}
static food: string; // 静态属性
static eat(): void {
console.log(`我正在吃${this.food}`);
}
}
let p = new Person("sss", 12);
p.say();
Person.food = "草莓";
Person.eat();
// 类的继承
class Student extends Person {
book: string;
constructor(name: string, age: number, book: string) {
super(name, age);
this.book = book;
}
say(): void {
console.log(`我是重写之后的say-${this.name}${this.age}${this.book}`);
}
static eat(): void {
console.log(`我是重写之后的eat-${this.food}`);
}
}
let cuteGirl = new Student("xka", 18, "goodstudy");
cuteGirl.say();
Student.food = "冰淇淋";
Student.eat();
11.1 类属性修饰符
-
public 公开的
如果使用 public 来修饰属性,name 表示这个属性是公开的,可以在类的内部使用,也可以在子类中使用,也可以在外部使用
-
private 私有的
如果使用 private 来修饰属性,表示这个属性是私有的,可以在类的内部使用
-
protected 受保护的
如果使用 protected 来修饰属性,表示这个属性是受保护的,可以在类的内部使用
-
readonly 只读的
class Person {
public name:string;
protected age:number;
private gender:string;
readonly id:number;
constructor(name:string,age:number,gender:string,id:number){
this.name = name,
this.age = age,
this.gender = gender,
this.id = id
}
say():void{
console.log(`name=${this.name},age=${this.age}`);
}
}
class Student extends Person{
constructor(name:string,age:number,gender:string,id:number){
super(name,age,gender,id)
}
say():void{
console.log(`name=${this.name}`);
console.log(`age=${this.age}`);
}
}
let p = new Person('sss',12,'male',1)
p.say()
// p.id = 3 // 报错 readonly 只读
// console.log(p.age); // 报错,protected受保护的,外部不可以使用
// console.log(p.gender) // 报错,私有属性只能在类的内部使用
let stu = new Student('zs',66,'female',2)
stu.say()
11.2 类方法修饰符
-
public :
如果使用 public 来修饰方法, 那么表示这个方法是公开的.可以在类的内部使用, 也可以在子类中使用, 也可以在外部使用
-
protected :
如果使用 protected 来修饰方法, 那么表示这个方法是受保护的.可以在类的内部使用, 也可以在子类中使用
-
private
如果使用 private 来修饰方法, 那么表示这个方法是私有的. 可以在类的内部使用
class Person {
name: string;
age: number;
gender: string;
constructor(name: string, age: number, gender: string) {
this.name = name;
this.age = age;
this.gender = gender;
}
public sayName(): void {
console.log(`name=${this.name}`);
}
protected sayAge(): void {
console.log(`age=${this.age}`);
}
private sayGender(): void {
console.log(`gender=${this.gender}`);
}
say(): void {
this.sayName();
this.sayAge();
this.sayGender();
}
}
class Student extends Person {
constructor(name: string, age: number, gender: string) {
super(name, age, gender);
}
say(): void {
this.sayName();
this.sayAge();
// this.sayGender(); // 报错,private私有属性,只能在Person类中使用
}
}
let p = new Person("lnj", 34, "male");
p.say();
p.sayName();
// p.sayAge(); // 爆粗,protected 只能在类和子类中使用,外部不可使用
// p.sayGender(); // 报错,private私有属性,只能在Person类中使用
let stu = new Student("zs", 18, "female");
stu.say();
/*
需求: 有一个基类, 所有的子类都需要继承于这个基类, 但是我们不希望别人能够通过基类来创建对象
* */
class Person {
name:string;
age:number;
gender:string;
protected constructor(name:string, age:number, gender:string){
this.name = name;
this.age = age;
this.gender = gender;
}
say():void{
console.log(`name=${this.name},age=${this.age},gender=${this.gender}`);
}
}
class Student extends Person {
constructor(name: string, age: number, gender: string) {
super(name, age, gender);
}
}
// let p = new Person('lnj', 34, 'male'); // 报错 ,类Person构造函数是protected 仅在类声明中访问
let stu = new Student('zs', 18, 'female');
11.2 类可选属性和参数属性
11.2.1 可选属性
和接口中的可选属性一样,可传可不传的属性
class Person {
// 注意点:在TS中如果定义了实例属性,那么就必须在构造函数中使用,否则就会报错
name:string;
age?:number; // 可选属性
constructor(name:string,age?:number){
this.name = name;
this.age = age;
}
}
let p = new Person('sss')
console.log(p);
11.2.2 参数属性
简化代码,一句话搞定实例属性的接受和定义
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
let p = new Person("s", 18);
console.log(p); // Person {name:'s',age:18}
class Person1 {
constructor(name: string, age: number) {}
}
let p1 = new Person1("s", 18);
console.log(p1); // Person1 {}
class Person2 {
constructor(public name:string,public age:number){
}
}
let p2 = new Person2('s',18)
console.log(p2); // Person2 {name:'s',age:18}
Person2 的效果与 Person 的效果一致
11.3 类存取器
11.3.1 什么是存取器
通过 getter/setter 来截取对对象成员的访问
class Person {
private _age:number = 0;
set age(val:number){
console.log('进入set age方法');
if(val < 0){
throw new Error('人的年龄不能小于0')
}
this._age = val
}
get age():number{
console.log('进入了get age方法');
return this._age
}
}
let p = new Person()
// console.log(p._age); // 报错,private类的内部使用
p.age = 34;
console.log(p.age);
11.4 抽象类
11.4.1 什么是抽象类
抽象类是专门用于定义那些不希望被外界直接创建的类的
抽象类一般用于定义基类
抽象类和接口一样用于约束子类
// Person基类, 所有的子类都可继承于这个基类, 但是不能够通过基类来创建对象
class Person {
name: string;
age: number;
protected constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
constructor(name:string,age:number){
super(name,age)
}
}
// let p = new Person("SS", 33); //类“Person”的构造函数是受保护的,仅可在类声明中访问
let stu = new Student('ss',34)
标准推荐的方式:抽象类 abstract
abstract class Person {
abstract name: string;
abstract say(): void;
eat(): void {
console.log(`${this.name}正在玩游戏`);
}
}
// let p = new Person() // 无法创建抽象类的实例
class Student extends Person {
name: string = "ss";
say(): void {
console.log(`我的名字是${this.name}`);
}
}
let stu = new Student()
stu.say()
stu.eat()
11.5 类和接口
11.5.1 类"实现"接口
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
interface PersonInterface {
name: string;
say(): void;
}
// 只要实现的某一接口,那么必须实现接口中所有的属性和方法
class Person implements PersonInterface {
name: string = "ss";
say(): void {
console.log(`我的名字叫${this.name}`);
}
}
let p = new Person();
p.say();
11.5.2 接口"继承"类
class Person {
name: string = "ss";
age: number = 34;
protected say(): void {
console.log(`name=${this.name},age=${this.age}`);
}
}
let p = new Person();
// p.say() // 报错 属性“say”受保护,只能在类“Person”及其子类中访问
// 注意点:只要一个接口继承了某个类,那么久会继承这个类中所有的属性和方法,但是智慧继承属性和方法的声明,不会继承属性和方法实现
// 注意点:如果接口继承的类中包含了protected的属性和方法,那么久只有这个类的子类才能实现这个接口
interface PersonInterface extends Person {
gender: string;
}
// 类实现接口
// class Student implements PersonInterface // 类“Student”错误实现接口“PersonInterface”。属性“say”受保护,但类型“Student”并不是从“Person”派生的类。
class Student extends Person implements PersonInterface {
gender: string = "male";
name: string = "zsf";
age: number = 11;
say(): void {
console.log(`name=${this.name},age=${this.age}`);
}
}
let stu = new Student();
stu.say();
11.5.3 类和泛型
// 泛型类
class Chache<T> {
arr:T[] = [];
add(value:T):T{
this.arr.push(value)
return value
}
all():T[]{
return this.arr
}
}
let chache = new Chache<number>()
chache.add(1)
chache.add(2)
chache.add(3)
console.log(chache.all());
12.枚举
12.1 数字枚举和字符串枚举
TS 中支持两种枚举,一种是数字枚举,一种是字符串枚举
12.1.1 数字枚举
默认情况下就是数字枚举
enum Gender {
Male,
Female
}
console.log(Gender.Male); // 0
console.log(Gender.Female); // 1
12.1.1.1 数字枚举注意点
数字枚举的取值默认从 0 开始递增
数字枚举的取值可以是字面量,也可以是常量,也可以是计算的结果
const num = 666;
function getNum() {
return 888;
}
enum Gender {
Male = num, // 注意点:如果使用常量给前面的枚举值赋值了,后面的枚举值也需要手动赋值
Female=6
}
enum Gender {
Male = getNum(), // 注意点:如果使用计算结果给前面的枚举值赋值了,后面的枚举值也需要手动赋值
Female = num,
}
12.1.2 枚举反向映射
可以根据枚举值获取到原始值
也可以根据原始值获取枚举值
enum Gender{
Male = 'dmn', // 注意点:如果使用字符串给前面的枚举值赋值了,那么后面的枚举值也必须手动赋值
Female = 'sss'
}
console.log(Gender.Male);
console.log(Gender.Female);
12.2 字符串枚举
enum Gender{
Male = 'dmn', // 注意点:如果使用字符串给前面的枚举值赋值了,那么后面的枚举值也必须手动赋值
Female = 'sss'
}
console.log(Gender.Male);
console.log(Gender.Female);
12.2.1 字符串枚举注意点
注意点:如果使用字符串给前面的枚举值赋值了,那么后面的枚举值也必须手动赋值
注意点:和数字枚举不一样,字符串枚举不能使用常量或者计算结果给枚举值赋值
注意点:虽然字符串枚举不能够使用常量或者计算结果给枚举值赋值,但是可以使用内部的其他枚举值来赋值
12.3 异构枚举
枚举中既包含数字又包含字符串,成为异构枚举
enum Gender {
Male = 6,
Female = 'nv'
}
console.log(Gender.Male);
console.log(Gender.Female);
console.log(Gender[6]);
// console.log(Gender['nv']); // undefined
console.log(Gender); // { '6': 'Male', Male: 6, Female: 'nv' }
注意点:如果是字符串枚举,name 无法通过原始值获取到枚举值
12.4 枚举成员类型和联合类型
12.4.1 枚举成员类型
可以把枚举成员当做类型来使用
enum Gender{
Male,
Female
}
interface TestInterface {
age: Gender.Male
}
class Person implements TestInterface{
age: Gender.Male
// age: Gender.Female // 由于类型不匹配, 所以会报错
// age: 0 // 注意点: 由于数字枚举的本质就是数值, 所以这里写一个数值也不会报错
}
enum Gender {
Male = "www",
Female = "sdsd",
}
interface TestInterface {
age: Gender.Male;
}
class Person implements TestInterface {
age: Gender.Male;
// age: Gender.Female; // 由于类型不匹配, 所以会报错
// age: "www" // 注意点: 如果是字符串枚举,name只能是枚举成员的值,不能是其他的值
}
12.4.2 联合类型
联合类型就是将多种数据类型通过 | 连接起来
可以把枚举类型当做一个联合类型来使用
let value: numner | string; // (number|string) 联合类型
value = 1;
value = "123";
enum Gender {
Male,
Female
}
interface TestInterface {
age:Gender // age: (Gender.Male | Gender.Female)
}
class Person implements TestInterface {
// age:Gender.Female
age:Gender.Male
}
12.5 运行时和常量枚举
12.5.1 运行时枚举
枚举在编译之后是一个真实存储的对象,所以可以在运行时使用
而像接口这种只是用来做约束做静态检查的代码,编译之后是不存在的
12.5.2 常量枚举
普通枚举和常量枚举的区别
- 普通枚举会生成真实存在的对象
- 常量枚举不会生成真实存在的对象,而是利用枚举成员的值直接替换使用到的地方
// TS
enum Gender {
Male,
Female
}
console.log(Gender.Male === 0)
===> 编译后 tsc 文件名.ts ==> 文件名.js
var Gender1;
(function (Gender1) {
Gender1[(Gender1["Male"] = 0)] = "Male";
Gender1[(Gender1["Female"] = 1)] = "Female";
})(Gender1 || (Gender1 = {}));
console.log(Gender1.Male === 0);
// TS
const enum Gender {
Male,
Female
}
console.log(Gender.Male === 0)
===>编译后 tsc 文件名.ts ==> 文件名.js
console.log(0 /* Male */ === 0);
13. TS 类型推论和兼容性
13.1 什么是自动类型推断
不用明确告诉编译器具体是什么类型,编译器就知道是什么类型
13.1.1 根据初始化值自动推断
注意点:如果是先定义再初始化,是无法自动推断的
let value;
value = 123;
value = false;
value = "abc";
如果是定义的同时初始化,那么 TS 就会自动进行类型推断
let value = 123;
value = "abc"; // 报错,不能将类型“string”分配给类型“number”
let arr = [1, "a"]; // let arr:(number | string) = [1, 'a'];
arr = ["a", "b", "c", 1, 3, 5, false]; // 不能将类型“boolean”分配给类型“string | number”
13.1.2 根据上下文类型自动推断
window.onmousedown = (event) => {
console.log(event.target);
};
13.2 类型兼容性
interface TestInterface {
name: string;
}
let p1 = { name: "sss" };
let p2 = { age: 12 };
let p3 = { name: "sss", age: 12 };
let t: TestInterface;
t = p1;
// t = p2 // 报错,缺少属性
t = p3; // 可多不可少
interface TestInterface {
name: string;
children: {
age: number,
};
}
let p1 = { name: "sss", children: { age: 12 } };
let p2 = { name: "www", children: { age: "aaa" } };
let t: TestInterface;
t = p1;
t = p2; // 报错,类型不匹配,会递归检查
13.3 函数兼容性
13.3.1 参数个数
let fn1 = (x: number, y: number) => {};
let fn2 = (x: number) => {};
fn1 = fn2;
fn2 = fn1; // 报错,可少不可多
13.3.2 参数类型
// 参数类型
let fn1 = (x: number) => {};
let fn2 = (y: number) => {};
let fn3 = (x: string) => {};
fn1 = fn2;
fn2 = fn1;
// fn1 = fn3 // 报错,类型必须一模一样
// fn3 = fn1 // 报错,类型必须一模一样
13.3.3 返回值类型
let fn1 = (): number => 123;
let fn2 = (): number => 456;
let fn3 = (): string => "abc";
fn1 = fn2;
fn2 = fn1;
fn1 = fn3; // 报错,类型必须一模一样
fn3 = fn1; // 报错,类型必须一模一样
13.3.4 双向协变
- 参数的双向协变
let fn1 = (x: number | string) => {};
let fn2 = (x: number) => {};
fn1 = fn2;
fn2 = fn1;
- 返回值的双向协变
let fn1 = (x: boolean): number | string => (x ? 123 : "abc");
let fn2 = (x: boolean): number => 456;
fn1 = fn2; // 可以将返回值是具体类型的赋值给联合类型的
// fn2 = fn1 // 报错,不能将返回值是联合类型的赋值给具体类型的
13.3.4 函数重载
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x, y) {
return x + y;
}
function sub(x:number,y:number):number;
function sub(x,y){
return x - y
}
// let fn = add
// fn = sub // 报错,不能将重载少的赋值给重载多的
let fn = sub
fn = add // 可以将重载多的赋值给重载少的
13.4 枚举兼容性
13.4.1 数字枚举与数值兼容
enum Gender {
Male,
Female,
}
let value: Gender;
value = Gender.Male;
value = 0;
13.4.2 数字枚举与数字枚举不兼容
enum Gender {
Male,
Female,
}
enum Animal {
Dog,
Cat
}
let value:Gender;
value = Gender.Male
// value = Animal.Cat // 报错
13.4.3 字符串枚举与字符串枚举不兼容
enum Gender {
Male='QW',
Female='ER',
}
let value:Gender;
value=Gender.Male;
13.4 类兼容性
13.4.1 只比较实例成员,不比较类的构造函数和静态成员
- 比较实例成员
class Person {
name: string;
}
class Animal {
name: string;
age: number;
}
let p: Person;
let a: Animal;
p = a;
// a = p; // 报错,可多不可少
- 不比较构造函数和静态成员
class Person {
name: string;
}
class Animal {
name: string;
static age: number;
constructor(name: string, age: number) {}
}
let p: Person;
let a: Animal;
p = a;
a = p; // 不报错,不比较构造函数和静态成员
13.4.2 类的私有属性和首保护属性会影响兼容性
class Person {
private name: string;
}
class Animal {
private name: string;
}
let p: Person;
let a: Animal;
// p = a; // 报错,不能将类型“Animal”分配给类型“Person”。类型具有私有属性“name”的单独声明
// a = p; // 报错,不能将类型“Person”分配给类型“Animal”。类型具有私有属性“name”的单独声明。
13.5 泛型兼容性
泛型只影响使用的部分,不会影响声明的部分
// 只声明,不使用,不报错
interface TestInterface<T> {}
let t1: TestInterface<number>;
let t2: TestInterface<string>;
t1 = t2;
t2 = t1;
interface TestInterface<T> {
age: T;
}
let t1: TestInterface<number>; // age:number
let t2: TestInterface<string>; // age:string
t1 = t2; // 报错,类型不同
t2 = t1; // 报错,类型不同
14. 高级类型
14.1 交叉和联合类型
14.1.1 交叉类型
格式: type1 & type2 & …
交叉类型是将多个类型合并为一个类型
let mergeFn = <T,U>(x:T,y:U):(T & U)=>{
let res = {} as (T & U)
res = Object.assign(x,y)
return res
}
let res = mergeFn({name:'sss'},{age:11})
console.log(res); // {name:'sss',age:11}
14.1.2 联合类型
格式: type1 | type2 | …
交叉类型是多个类型中的任意一个类型
let value :(string|number)
value = 'abc
value = 123
14.2 类型保护
对于联合类型的变量,在使用时如何确切告诉编译器它是哪一种类型,通过类型断言或者类型保护
let getRandomValue = (): string | number => {
let num = Math.random();
return num >= 0.5 ? "abc" : 123.123;
};
let value = getRandomValue();
if (value.length) { // 报错,不确定value是string还是number类型,当为number,没有length
console.log(value.length); // 报错,不确定value是string还是number类型,当为number,没有length
} else {
console.log(value.toFixed());// 报错,不确定value是string还是number类型,当为string,没有toFixed
}
14.2.1 类型断言
as
// 类型断言
if ((value as string).length) {
console.log((value as string).length);
} else {
console.log((value as number).toFixed);
}
14.2.1.1 缺点
虽然通过类型断言可以确切的告诉编辑器当前的变量是什么类型,但是每一次使用的时候都需要手动的告诉编辑器,这样比较麻烦,冗余
14.2.2 类型保护函数
定义了一个类型保护函数,这个的’返回类型’是一个布尔类型
这个函数的返回值类型时,传入的参数 + is + 具体类型
function isString(value: string | number): value is string {
return typeof value === "string";
}
if (isString(value)) {
console.log(value.length);
} else {
console.log(value.toFixed);
}
14.2.3 typeof
如果使用 typeof 来实现类型保护,那么只能使用 === or !==
如果使用 typeof 来实现类型保护,那么只能保护 number/string/boolean/symbol 类型
if (typeof value === "string") {
console.log(value.length);
}else {
console.log(value.toFixed());
}
14.2.4 instanceof
class Person {
name: string = "ss";
}
class Animal {
age: number = 16;
}
let getRandomObject = (): Person | Animal => {
let num = Math.random();
return num >= 0.5 ? new Person() : new Animal();
};
let obj = getRandomObject();
if (obj instanceof Person) {
console.log(obj.name);
} else {
console.log(obj.age);
}
14.3 null 和 undefined
TS 具有两种特殊的类型,null 和 undefined,它们分别具有值 null 和 undefined
默认情况下我们可以将 null 和 undefined 赋值给任意类型
默认情况下 null 和 undefined 也可以互相赋值
-
注意点:在企业开发中,如果不想把 null 和 undefined 赋值给其他的类型或者不想让 null 和 undefined 互相赋值,可以开启 tsconfig.json 的 strictNullChecks
-
如果开启了 strictNullChecks,还想把 null 和 unfedined 赋值给其他类型,必须在声明的时候使用联合类型
let value: number | null | undefined;
value = null;
value = undefined;
- 对于可选属性和可选参数而言,如果开启了 strcitNullChecks,就是 当前类型+undefined 类型
class Person {
name?: string; // (property) Person.name?: string | undefined
}
function say(age?: number) {}
14.3.1 去除 null 和 undefined 检测
function getLength(value:(string|null|undefined)){
value = 'abc'
return ()=>{
// return value.length // 报错,对象可能为 "null" 或“未定义”
// 方法一
// return (value || '').length
// 方法二
// return (value as string).length
// 方法三 使用 ! 来取出null和undefined,!的含义就是这个变量一定不是null和undefined
return value?.length
}
}
let fn = getLength('ss')
let res = fn()
console.log(res);
14.4 类型别名
14.4.1 什么是类型别名
类型别名就是给一个类型起个新名字,但是它们都代表同一个类型
用type创建类型别名
// 给string类型起了一个别名叫做MyString, 那么将来无论是MyString还是string都表示string
type MyString = string;
let value: MyString;
value = "sss";
// value = 123; // 报错,类型不符
14.4.2 类型别名也可以使用泛型
type MyType<T> = { x: T, y: T };
let value: MyType<number>;
value = { x: 222, y: 111 };
14.4.3 可以在类型别名类型的属性中使用自己
type MyType = {
name: string,
// 一般用于定义一些树状结构或者嵌套结构
children: MyType,
};
let value: MyType = {
name: "one",
children: {
name: "one",
children: {}, // 报错,因为嵌套循环
},
};
解决
type MyType = {
name: string,
// 一般用于定义一些树状结构或者嵌套结构
children?: MyType,
};
let value: MyType = {
name: "one",
children: {
name: "one",
children: {}, // 不报错,因为为可选类型
},
};
14.4.4 接口和类型别名是相互兼容的
type MyType = {
name: string,
};
interface MyInterface {
name: string;
}
let value1: MyType = { name: "ss" };
let value2: MyInterface = { name: "eee" };
value1 = value2;
value2 = value1;
14.4.4.1 接口和类型别名异同
- 1.都可以描述属性或方法
- 2.都允许拓展
- 3.type 可以声明基本类型别名,联合类型,元祖等类型,interface 不能
- 4.type 不会自动合并
// 1.都可以描述属性或方法
type MyType = {
name: string,
say(): void,
};
interface MyInterface {
name: string;
say(): void;
}
// 2.都允许拓展
// 接口拓展 extends
interface MyInterface {
name: string;
say(): void;
}
interface MyInterface2 extends MyInterface {
age: number;
}
let value: MyInterface2 = {
name: "w",
age: 18,
say(): void {},
};
// 类型别名拓展 &
type MyType = {
name: string,
say(): void,
};
type MyType2 = MyType & {
age: number,
};
let value: MyType2 = {
name: "s",
age: 11,
say() {},
};
// 3.type 可以声明基本类型别名,联合类型,元祖等类型,interface 不能
type MyType = boolean;
type MyType1 = string | number;
type MyType2 = [string, number, boolean];
interface MyInterface {
name: string;
}
interface MyInterface {
age: number;
}
let value: MyInterface = {
// 合并
name: "lnj",
age: 18,
};
type MyType = {
name: string,
};
type MyType = {
// 报错,不能重复
age: number,
};
14.4.5 字面量类型
14.4.5.1 什么是字面量
字面量就是源代码中一个固定的值
例如数值字面量:1,2,3…
例如字符串字面量:‘a’,‘abc’,…
14.4.5.2 在 TS 中可以把字面量作为具体的类型来使用
当使用字面量作为具体类型时,该类型的取值就必须是该字面量的值
type MyNum = 1;
let value1: MyNum = 1;
let value2: MyNum = 2; // 报错
14.4.6 可辨识联合
14.4.6.1 什么是可辨识联合
具有共同的可辨识特征
一个类型别名,包含了具有共同的可辨识特征的类型的联合
interface Square {
kind: "square"; // 共同的可辨识特征
size: number;
}
interface Rectangle {
kind: "rectangle"; // 共同的可辨识特征
width: number;
height: number;
}
interface Circle {
kind: "circle"; // 共同的可辨识特征
radius: number;
}
// Shape就是一个可辨识联合
// 因为 它的取值是联合
// 因为 这个联合的每一个取值都有一个共同的可辨识特征
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2; // ** 是ES7中退出的幂运算符
}
}
let value = area({
kind: "square",
size: 12,
});
console.log(value); // 144
14.4.6.2 可辨识联合完整性检查
在企业开发中如果相对可辨识联合的完整性进行检查
方法一:给函数添加返回值 + 开启 strictNullChecks
方法二:添加 default+ never
// 方法一:给函数添加返回值 + 开启strictNullChecks
type Shape = Square | Rectangle | Circle;
function area(s: Shape): number {
// number报错
switch (s.kind) {
case "square":
return s.size * s.size;
// case "rectangle":
// return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2; // ** 是ES7中退出的幂运算符
}
}
type Shape = Square | Rectangle | Circle;
function MyNever(x: never): never {
throw new Error("可辨识联合处理不完整" + x);
}
function area(s: Shape): number {
switch (s.kind) {
case "square":
return s.size * s.size;
// case "rectangle":
// return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2; // ** 是ES7中退出的幂运算符
default:
return MyNever(s); // 报错
}
}
14.5 索引类型
通过[]索引类型访问操作符,能得到某个索引的类型
class Person {
name: string;
age: number;
}
type MyType = Person['name']
应用场景
需求:获取指定对象,部分属性的值,放到数组中返回
let obj = {
name: "sss",
age: 12,
gender: true,
};
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
let arr = [] as T[K][];
keys.forEach((key) => {
arr.push(obj[key]);
});
return arr;
}
let res = getValues(obj, ["name", "age"]);
索引访问操作符注意点
不会返回 null/undefined/never
interface TestInterface {
a:string,
b:number,
c:boolean,
d:symbol,
e:null,
f:undefined,
g:never
}
type MyType = TestInterface[keyof TestInterface]
映射类型
根据旧的类型创建出新的类型,称之为映射类型
14.6.1 通过+/-来指定添加还是删除只读和可选修饰符
interface TestInterface1 {
name: st#### 14.6ring;
age: number;
}
type ReadonlyTestInterface<T> = {
// -readonly [P in keyof T]:T[P]
readonly [P in keyof T]-?:T[P]
}
type MyType = ReadonlyTestInterface<TestInterface1>
// 可以通过+/-来指定添加还是删除只读和可选修饰符
可以通过+/-来指定添加还是删除只读和可选修饰符
14.6.2 Readonly
生成只读属性,TS 提供了 Readonly
// 生成只读属性,TS提供了 Readonly
type MyType2 = Readonly<TestInterface1>;
14.6.3 Partial
生成可选属性,TS 提供了 Partial
// 生成可选属性,TS提供了 Partial
type MyType2 = Partial<TestInterface1>;
14.6.4 同时存在只读和可写
type MyType4 = Partial<Readonly<TestInterface1>>;
14.6.5 Pick 映射类型
可将原有类型中的部分内容映射到新类型中
ps:上面的几种方法是全部映射
interface TestInterface {
name: string;
age: number;
sex: string;
}
type MyType = Pick<TestInterface, "name">;
type MyType1 = Pick<TestInterface, "age" | "sex">;
14.6.6 Record 映射类型
会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
type Animal = "person" | "dog" | "cat";
interface TestInterface {
name: string;
age: number;
}
type MyType = Record<Animal, TestInterface>;
let value: MyType = {
person: {
name: "ss",
age: 11,
},
dog: {
name: "ww",
age: 12,
},
cat: {
name: "jj",
age: 77,
},
};
type Animal = "person";
interface TestInterface {
name: string;
age: number;
}
type TestInterface1 = keyof TestInterface;
type MyType = Record<TestInterface1, Animal>;
let value:MyType = {
name:'person',// 当使用字面量作为具体类型时, 该类型的取值就必须是该字面量的值
age:'person'
}
14.6.7 映射类型推断
由映射类型进行推断
对于 Readonly,Partial 和 Pick 的映射类型,可以对映射之后的类型进行拆包,还原映射之前的类型,这种操作称之为拆包
interface MyInterface {
name:string;
age:number;
}
type MyType<T> = {
+readonly [P in keyof T]:T[P]
}
type test = MyType<MyInterface>
type UnMyType<T> = {
-readonly [P in keyof T]:T[P]
}
type test2 = UnMyType<test>
14.7 分布式条件类型
14.7.1 条件类型(三目运算)
判断前面一个类型是否是 后面一个类型 或者 继承与后面一个类型,如果是就返回第一个结果,如果不是就返回第二个结果
type MyType<T> = T extends string ? string : any
type res = MyType<string>
type res1 = MyType<boolean>
14.7.2 分布式条件类型
被检测类型是一个联合类型的时候,该条件类型就被称之为分布式条件类型
type MyType<T> = T extends any ? T :never;
type res = MyType<string | number | boolean>
14.7.2.1 exclude
从 T 中剔除可以赋值给 U 的类型。Exclude
// 方式一
type MyType<T, U> = T extends U ? never : T;
// T 可能为 string number boolean
// U number
// 当 T 为 string | Boolean不会 extends U,为 number时会,所以为number时啥也没有(never)
type res = MyType<string | number | boolean, number>; // string | boolean
// 方式二
type res1 = Exclude<string | number | boolean, number>; // string | boolean
14.7.2.2 Extract
提取 T 中可以赋值给 U 的类型
type res2 = Extract<string | number | boolean, number>; // number
14.7.2.3 NonNullable 剔除 null 和 undefined
从 T 中剔除 null 和 undefined
type res = NonNullable<string | null | boolean | undefined>; // string|boolean
14.7.2.4 returnType 获取函数返回值类型
type res = ReturnType<() => number>; // number
14.7.2.5 ConstructorParameters
获取一个类的构造函数参数组成的元组类型
class Person {
constructor(name: string, age: number) {}
}
type res = ConstructorParameters<typeof Person>; // [name: string, age: number]
14.7.2.6 Parameters
获得函数的参数类型组成的元祖类型
function say(name: string, age: number, gender: boolean) {}
type res = Parameters<typeof say>; // [name: string, age: number, gender: boolean]
14.7.2.7 infer 关键字
条件类型提供了一个 infer 关键字,可以让我们在条件类型中定义新的类型
// 需求:定义一个类型,如果传入的是数组,就返回数组的元素类型,如果传入的是普通
type MyType<T> = T extends any[] ? T[number] : T;
type res = MyType<string[]>;
type res1 = MyType<number>
// infer 取出数组元素的类型
type MyType1<T> = T extends Array<infer U>?U:T
type res2 = MyType1<string[]>
type res3 = MyType1<number>
14.8 unknown
什么是 unknown 类型
unknown 类型时 TS3.0 中新增的一个顶级类型,被称作安全的 any
14.8.1 什么类型都可以赋值给 unknown 类型
let value: unknown;
value = 123;
value = "ass";
value = false;
14.8.2 如果没有类型断言或基于控制流的类型细化,那么不能将 unknown 类型赋值给其他类型
let value: unknown = 123;
let value1: number;
value1 = value;// 报错,不能将unknown赋值给其他类型
value1 = value as number // 类型断言,不报错
if(typeof value === 'number'){ // 基于控制流的类型细化
value1 = value
}
14.8.3 如果没有类型断言或基于控制流的类型细化,那么不能在 unknown 类型上进行任何操作
let value1:unknown = 123
value1++ // 报错
(value1 as number)++ // 不报错
if(typeof value1 === 'number'){
value1++ // 不报错
}
14.8.4 只能读 unknown 类型进行 相等或不等操作,不能进行其他操作(因为其他操作没有意义)
let value1: unknown = 123;
let value2: unknown = 123;
console.log(value1 === value2);
console.log(value1 !== value2);
console.log(value1 >= value2); // 虽然没有报错, 但是不推荐, 如果想报错提示, 可以打开严格模式
14.8.5 unknown 与其他任何类型组成的交叉类型最后都是其他类型
type MyType = number & unknown; // number
type MyType1 = unknown & string; // string
14.8.6 unknown 除了与 any 以外,与其他任何类型组成的联合类型最后都是 unknown 类型
type MyType1 = unknown | any; // any
type MyType2 = unknown | number; // unknown
type MyType3 = unknown | boolean | string; // unknown
14.8.7 never 类型时 unknown 类型的子类型
type MyType = never extends unknown ? true : false
14.8.8 keyof unknown 等于 never
type MyType = keyof unknown // never
14.8.9 unknown 类型的值不能访问起属性,方法,创建实例
class Person {
name: string = "sss";
say(): void {
console.log(`name = ${this.name}`);
}
}
let p: unknown = new Person();
p.say(); // 报错
console.log(p.name); // 报错
14.8.10 使用映射类型时,如果遍历的是 unknown 类型,name 不会映射任何属性
type MyType<T> = {
[P in keyof T]:any
}
type res = MyType<unknown> // 不会映射任何类型,{}
15.模块和命名空间
15.1 模块
15.1.1 ES6 模块
- 1.1 分开导入导出
export xxx;
import {xxx} from "path";
- 1.2 一次性导入导出
export { xxx, yyy, zzz };
import { xxx, yyy, zzz } from "path";
- 1.3 默认导入导出
export default xxx;
import xxx from "path";
15.1.2 Node 模块
-
1.1 通过 exports.xxx = xxx 导出
通过 const xxx = require(“path”);导入
通过 const {xx, xx} = require(“path”);导入 -
1.2 通过 module.exports.xxx = xxx 导出
通过 const xxx = require(“path”);导入
通过 const {xx, xx} = require(“path”);导入
15.1.3 ES6 的模块和 Node 的模块是不兼容的, 所以 TS 为了兼容两者就推出了
export = xxx;
import xxx = require('path');
15.2 命名空间
15.2.1 什么是命名空间
命名空间可以看做是一个微型模块
当我们想把相关业务代码写在一起,又不想污染全局空间的时候, 我们就可以使用命名空间
本质就是定义一个大对象, 把变量/方法/类/接口…的都放里面
15.2.2 命名空间和模块的区别
在程序内部使用的代码, 可以使用命名空间封装和防止全局污染
在程序内部外部使用的代码, 可以使用模块封装和防止全局污染
总结: 由于模块也能实现相同的功能, 所以大部分情况下用模块即可
- 同一页面访问其他空间的,需要其他空间先 export
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/
export const LettersValidator = (value) =>{ return lettersRegexp.test(value);}
}
-
不同页面
/// 这步操作会把该./56/test.ts 路径下的内容复制一份到需要引用的页面中
/// <reference path="./56/test.ts" />
console.log(Validation.LettersValidator("abc"));
console.log(Validation.LettersValidator(123));
15.3 声明合并
在 TS 当中接口和命名空间是可以重名的。TS 会将多个同名的合并为一个
15.3.1 接口
interface TestInterface {
name: string;
}
interface TestInterface {
age: number;
}
===>
interface TestInterface {
name: string;
age: number;
}
15.3.1.1 同名接口如果属性名相同,那么属性类型必须一致
interface TestInterface {
name: string;
}
interface TestInterface {
name: number; // 报错
}
15.3.1.2 同名接口如果出现同名函数,那么就会成为一个函数的重载
interface TestInterface {
getValue(value: number): number;
}
interface TestInterface {
getValue(value: string): number;
}
let obj: TestInterface = {
getValue(value: any): number {
if (typeof value === "string") {
return value.length;
} else {
return value.toFixed();
}
},
};
15.3.2 命名空间
namespace Validation{
export let name:string = 'lnj';
}
namespace Validation{
export let age:number = 18;
}
console.log(Validation.name);
console.log(Validation.age);
15.3.2.1 同名的命名空间中不能出现同名的变量、方法等
namespace Validation{
export let name:string = 'lnj';//报错
export let say = ()=> "abc";//报错
}
namespace Validation{
export let name:string = 'zs';//报错
export let say = ()=> "abc";//报错
}
15.3.2.1 同名的命名空间中其他命名空间没有通过 export 导出的内容是获取不到的
namespace Validation{
let name:string = 'lnj';
}
namespace Validation{
export let say = ()=> {
console.log(`name = ${name}`);
};
}
Validation.say();
15.3.3 命名空间和类合并
会将命名空间中导出的方法作为一个静态方法合并到类中
class Person {
say(): void {
console.log("sss");
}
}
namespace Person {
export const hi = (): void => {
console.log("hi");
};
}
15.3.4 命名空间和函数合并
注意点:函数必须定义在命名空间前面
function getCounter() {
getCounter.count++;
console.log(getCounter.count);
}
namespace getCounter{
export let count:number = 0;
}
15.3.5 命名空间和枚举合并
注意点:没有先后顺序的要求
enum Gender {
Male,
Female
}
namespace Gender{
export const Yao:number = 666;
}
console.log(Gender);
16.TS 装饰器
16.1 什么是装饰器
Decorator 是 ES7 的一个新语法,目前仍处于提案中
装饰器是一种特殊类型的声明,它能够被附加到类,方法, 访问器,属性或参数上
- 被添加到不同地方的装饰器有不同的名称和特点
- 附加到类上, 类装饰器
- 附加到方法上,方法装饰器
- 附加到访问器上,访问器装饰器
- 附加到属性上,属性装饰器
- 附加到参数上,参数装饰器
16.2 装饰器的基本格式
16.2.0 如何在 TS 中使用装饰器
在 TS 中装饰器也是一项实验性的特性, 所以要使用装饰器需要手动打开相关配置
修改配置文件 experimentalDecorators
16.2.1 普通装饰器
给类绑定一个普通的装饰器,这个装饰器的代码会在定义类之前执行,并且在执行的时候会把这个类传递给装饰器
function test(target) {
console.log("test");
}
@test
class Person {}
16.2.2 装饰器工厂
如果一个函数返回一个回调函数,如果这个函数作为装饰器来使用,那么这个函数就是装饰器工厂
function demo() {
console.log("demo out");
return (target) => {
console.log("demo in");
};
}
@demo()
class Person {}
给 Person 这个类绑定了一个装饰器工厂
在绑定的时候由于在函数后面加上了(),所以会先执行装饰器工厂拿到真正的装饰器
真正的装饰器会在定义类之前执行,所以紧接着又执行力里面
16.2.3 装饰器组合
普通的装饰器可以和装饰器工厂结合起来一起使用
结合起来一起使用的时候,会先从上至下的执行所有的装饰器工厂,拿到所有真正的装饰器
然后再从下至上的执行所有的装饰器
function test(target) {
console.log("test");
}
// 如果一个函数返回一个回调函数, 如果这个函数作为装饰器来使用, 那么这个函数就是装饰器工厂
function demo() {
console.log("demo out");
return (target) => {
console.log("demo in");
};
}
function abc(target) {
console.log("abc");
}
function def() {
console.log("def out");
return (target) => {
console.log("def in");
};
}
// demo out / def out / abc / def in / demo in / test
@test
@demo()
@def()
@abc
class Person {}
16.3 类装饰器
16.4 defineProperty
Object.defineProperty()
可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
16.4.1 定义一个新的属性
let obj = { age: 18 };
Object.defineProperty(obj, "name", {
value: "ss",
});
16.4.2 修改原有属性
let obj = { age: 18 };
Object.defineProperty(obj, "age", {
value: 33,
});
16.4.3 修改属性配置-读写
let obj = { age: 11 };
Object.defineProperty(obj, "age", {
writeable: false, // 不可写
});
obj.age = 33;
console.log(obj.age); // 11
16.4.4 修改属性配置-迭代
let obj = { age: 11, name: "ss" };
Object.defineProperty(obj, "age", {
enumerable: false,
});
for (let key in obj) {
console.log(key); //什么也不打印
}
16.4.5 修改属性配置-配置
let obj = { age: 11, name: "ss" };
Object.defineProperty(obj, "name", {
enumerable: false,
configurable: true, // 为true,下面才可进行配置
});
Object.defineProperty(obj, "name", {
enumerable: true,
configurable: false,
});
for (let key in obj) {
console.log(key);
}
16.5 方法装饰器
- 方法装饰器写在一个方法的声明之前(紧靠着方法声明)
- 方法装饰器可以用来监视,修改或者替换方法定义
- 方法装饰器表达式会在运行时当做函数被调用,传入下列三个参数:
- 对于静态方法而言就是当前的类,对于实例方法而言就是当前的实例
- 被绑定方法的名字
- 被绑定方法的属性描述符
function test(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// descriptor.enumerable = false;
console.log(target); // Person类
console.log(propertyKey); // 方法名sayName
console.log(descriptor);
// {
// value: [Function],
// writable: true,
// enumerable: true,
// configurable: true
// }
}
class Person {
@test
sayName(): void {
console.log("my name is sss");
}
// @test
sayAge(): void {
console.log("my age is 18");
}
// @test
static say(): void {
console.log("say hello world");
}
}
let p = new Person();
for (let key in p) {
console.log(key);
}
替换某个方法
function test(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 通过value修改取值
descriptor.value = (): void => {
console.log("my name is it666");
};
}
class Person {
@test
sayName(): void {
console.log("my name is lnj");
}
}
let p = new Person();
p.sayName(); // my name is it666
16.6 访问装饰器
-
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)
访问器装饰器应用于访问器的 属性描述符并且可以用来监视、修改或替换一个访问器的定义
-
访问器装饰器表达式会在运行时当做函数被调用,传入下列三个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字
- 成员的属性描述符
-
注意
TS 不允许同时修饰一个成员的 get 和 set 访问器
取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上(看 get 和 set 函数谁写在最上面)
function test(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// console.log(target);
// console.log(propertyKey);
// console.log(descriptor);
descriptor.set = (value: string) => {
// 添加myName,取值为传入的
target.myName = value;
};
descriptor.get = (): string => {
return target.myName;
};
}
class Person {
_name: string;
constructor(name: string) {
this._name = name;
}
@test // 装饰器置于get和set的最上面
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
let p = new Person("sss");
p.name = "ww";
console.log(p.name);
console.log(p); // {_name:'sss'} set的修改给了myName,所以_name还是'sss'
16.7 属性装饰器
- 属性装饰器写在一个属性声明之前(紧靠着属性声明)
- 属性装饰器表达式会在运行时当做函数被调用,传入下列 2 个参数
- 对于静态属性来说就是当前的类,对于实例属性来说就是当前实例
- 成员的名字
function test(target: any, proptyName: string) {
console.log(target); // 当前类Person
console.log(proptyName); // age
}
class Person {
@test
static age: number;
name?: string;
}
let p = new Person();
console.log(p);
function test(target: any, proptyName: string) {
console.log(target); // 当前实例
console.log(proptyName); // age
target[proptyName] = 'sss'
}
class Person {
static age: number;
@test
name?: string;
}
let p = new Person();
console.log(p.name); // 'sss'
16.8 参数装饰器
- 参数装饰器写在一个参数声明之前(紧靠着参数声明)
- 参数装饰器表达式会在运行时当作函数被调用,传入的下列三个参数
- 对于静态成员来说是当前类,对于实例成员是当前实例
- 参数所在的方法名称
- 参数在参数列表中的索引
function test(target: any, proptyName: string, index: number) {
console.log(target); // 当前实例
console.log(proptyName); // say
console.log(index); // 1
}
class Person {
say(say: number, @test name: string): void {}
}
16.9 其它
其它
属性装饰器,参数装饰器最常见的应用场景就是配合元数据(reflect-metadata),
在不改变原有结构的同时添加一些额外的信息
但是元数据目前也是在提案中, 也还没有纳入正式的标准
所以对于装饰器而言, 我们只需要了解即可,
因为提案中的所有内容将来都是有可能被修改的
因为提案中的所有内容目前都有兼容性问题
17.TS 混入
17.1 对象混入
Object.assign(需要混入的对象,混入的数据来源)
let obj1 = { name: "sss" };
let obj2 = { age: 22 };
Object.assign(obj1, obj2);
console.log(obj1); // { name: 'sss', age: 22 }
console.log(obj2); //{ age: 22 }
17.2 类混入
- 需求:定义两个类,将两个类的内容混入到一个新的类中
class Dog {
name: string = "ss";
say(): void {
console.log("he he");
}
}
class Cat {
age: number = 11;
run(): void {
console.log("dada");
}
}
// extends 一次只能继承一个类
// class Animal extends Dog,Cat{} //报错
class Animal implements Dog, Cat {
name: string;
age: number;
say: () => void;
run: () => void;
}
function myMixin(target: any, from: any[]) {
from.forEach((fromItem) => {
Object.getOwnPropertyNames(fromItem.prototype).forEach((name) => {
target.prototype[name] = fromItem.prototype[name];
});
});
}
myMixin(Animal, [Dog, Cat]);
let a = new Animal();
console.log(a);
a.say();
a.run();
// console.log(a.name); // undefined
// console.log(a.age); // undefined
方法的实现可以赋值,属性的实现不能赋值
18.声明文件
18.1 什么是声明
- 在企业开发中我们不可避免的需要引用第三方的 JS 的库,但是默认情况下 TS 是不认识我们引入的这些 JS 库的所以在使用这些 JS 库的时候, 我们就要告诉 TS 它是什么, 它怎么用
- 如何告诉 TS 呢?那就是通过声明来告诉
declare 告诉 TS 有$这个符号,具体是什么东西,真正运行还是找的 jQuery.js
declare const $:(selector:string)=>{
width():number;
height():number;
ajax(url:string, config:{}):void;
};
console.log($);
console.log($('.main').width());
console.log($('.main').height());
18.2 声明文件
声明的定义和使用分开写
/** 所有子目录
- 1.对于常用的第三方库, 其实已经有大神帮我们编写好了对应的声明文件
所以在企业开发中, 如果我们需要使用一些第三方JS库的时候我们只需要安装别人写好的声明文件即可 - 2.TS声明文件的规范 @types/xxx
例如: 想要安装jQuery的声明文件, 那么只需要npm install @types/jquery 即可
不完整学习笔记,修改中…