TypeScript学习

文章目录

  • TS
    • 1 ts的安装使用
    • 2 基本数据类型
    • 3 引用数据类型(数组和元组)
      • 3.1 数组类型
      • 3.2 字符串类型
      • 3.3 联合类型
      • 3.4 任意类型
      • 3.5 元组类型
    • 4 枚举类型
    • 5 any和void类型
      • 5.1 any类型 任意类型
      • 5.2 void类型 空类型
    • 6 never和object类型
      • 6.2 Never类型 表示的是那些永远不存在的值的类型。(基本上不用)
      • 6.2 Object类型
    • 7 类型断言
      • 7.1 什么是类型断言
    • 8 接口
      • 8.1 什么是接口类型
      • 8.2 可选属性和索引签名
        • 少一个的方式(middleName`?`:string,)
        • 多一个的方式([propName:string]:any)
        • 方式一:类型断言
        • 方式二:使用变量
        • 方式三:使用索引签名 在JS中,JS对象的键都会隐式转化成字符串
      • 8.3 索引签名和只读属性
        • 1.什么是索引签名?
        • 2.什么是只读属性
      • 8.4 函数接口和混合类型接口
        • 8.4.1 函数接口**(就是用接口去约束函数的定义)**
        • 8.4.2 混合类型 接口 :可以约束函数,也可以约束属性
      • 8.5 接口的继承
    • 9 函数
      • 9.1 函数的声明
      • 9.2 函数的重载
    • 10 可选-默认-剩余参数
      • 10.1 可选参数
      • 10.2 默认参数
      • 10.3 剩余参数
    • 11 泛型
      • 11.1 什么是泛型?
      • 11.2 泛型约束
      • 11.3 在泛型约束中使用类型参数
    • 12 类
      • 12.1 类属性修饰符
      • 12.2 类方法修饰符
      • 12.3 类可选属性和参数属性
        • 使用参数属性进行简化
      • 12.4 类存取器
      • 12.5 抽象类
        • 1.什么是抽象类?
        • 2.抽象类和接口区别?
      • 12.6 类和接口
        • 12.6.1 类实现接口
        • 12.6.2 接口“继承”类
      • 12.7 类和泛型
      • 12.8 接口合并现象
    • 13 数字枚举和字符串枚举
      • 13.1 数字枚举
      • 13.2 字符串枚举
      • 13.3 异构枚举(既包含数字又包含字符串)
      • 13.4 枚举成员类型
      • 13.5 联合枚举类型
      • 13.6 运行时枚举
      • 13.7 常量枚举
    • 14 类型推论
    • 15 类型兼容性
      • 15.1 基本兼容性
      • 15.2 函数兼容性
      • 15.3 枚举兼容性
      • 15.4 类兼容性
      • 15.5 泛型兼容性
    • 16 高级类型
      • 16.1 交叉类型
      • 16.2 联合类型 (string | number)
      • 16.3 类型保护(定义类型保护函数/typeof/instanceof实现类型保护)
      • 16.4 null和undefined
      • 16.5 去除 null或 undefined检测
      • 16.6 类型别名
      • 16.6 类型别名和接口的对比
      • 16.7 字面量类型
      • 16.8 可辨识联合(具有共同的可辨识特征)
      • 16.8 索引类型
      • 16.9 映射类型
        • 简化写法
        • Pick映射类型
        • Record映射类型
        • 映射类型推断
      • 16.10 分布式条件类型
        • 16.10.1.条件类型(三目运算)
        • 16.10.2.分布式条件类型
    • 17 模块
      • 17.1 ES6模块(导入导出写法)
      • 17.2 Node模块(导入导出写法)
    • 18 命名空间
    • 19 声明合并
      • 19.1 重名的接口
      • 19.2 重名的命名空间
      • 19.3 命名空间和类合并
      • 19.4 命名空间和函数合并
      • 19.5 命名空间和枚举合并
    • 20 装饰器
      • 20.1 什么是装饰器?
      • 20.2 装饰器基本格式
      • 20.3 如何在TS中使用装饰器?
      • 20.4 类装饰器
      • 20.5 Object.defineProperty()
      • 20.6 方法装饰器
      • 20.7 访问器装饰器
      • 20.8 属性装饰器
      • 20.9 参数装饰器

TS

TypeScript学习_第1张图片

  TypeScript TS
  ts和js之间的关系类似less/sass和css之间的关系

和less/sass一样,它们是对css的扩展,ts也是对js的扩展。
我们写的ts代码,还需要转化成js才能运行。

  JS是弱类型,很多错误,只能在运行时发现,在写代码过程中,是发现不了。
  TS是强类型,它提供了一套静态检测机制,可以帮我们在编译时就发现错误。

    var a = 1;
    a = "hello"
    int a = 1
    stirng str = "hello"

ts的特点:

  • 支持最新的js特性
  • 支持代码的静态检查
  • 支持很多语言如:java,go,c,C++语言的特性

ts不能说是一门新语言,它是对js的扩展,在ts文件中,可以直接写js代码。
ts中提供了类型系统,声明一个变量,都可以指定类型。
ts的提示非常给力

生成tsconfig.json方式:tsc --init

1 ts的安装使用

安装(全局安装)

npm install -g typescript

使用(可以将ts转化复制出一个js文件)

tsc index.ts

2 基本数据类型

// ts中的基本数据类型  和 js中几乎一样

// 数值类型 number
let a:number; // 定义一个名称叫a的变量  这个变量中只能存储数值类型的数据
a = 123;
// a = "hello";  // 报错

// 布尔类型 boolean
let b:boolean;
b = true;
// b = 1; // 报错
console.log(b)

// 字符串类型
let c:string;
c = "hello world";
// c = 123;
c = `a${a}`;
console.log(c)

3 引用数据类型(数组和元组)

3.1 数组类型

// 方式一
// 定义一个数组,数组中只能存储数值类型的数据
let arr1:Array<number>;  // let arr1:number[]
arr1 = [1,2,3];
// arr1 = ["a","b",123];  // 报错
console.log(arr1)

3.2 字符串类型

// 方式二
let arr2:string[];  // let arr2:Array
// arr2 = ["a","hello",123];  // 报错
arr2 = ["hello","world","ok"];

3.3 联合类型

// (number | string) 是联合类型
let arr3:(number | string)[];
arr3 = ["a","b","c",123,456];
console.log(arr3)

3.4 任意类型

//    any表示任意
let arr4:any[];
arr4 = ["a",true,123];
console.log(arr4)

3.5 元组类型

ts中的元组类型其实就是数组类型的扩展
元组用于保存 定长 定数据类型 的数据 说白了,就是更加严格一点的数组

let arr5:[string,number,boolean]; // 表示定义一个名称叫做arr5的元组,这个元组中将要存储3个数据,
// 第1个数据必须是stirng类型,第2个数据必须是number类型,第3个数据必须是boolean类型
arr5 = ["abc",123,true];
// arr5 = ["abc",123,true,"OK"];  // 不OK  定长
// arr5 = [123,"abc",true];   // 不OK  定数据类型
console.log(arr5)

4 枚举类型

  枚举类型 是ts为js扩展出来的新的类型 在原生的js中是没有枚举类型
枚举表示固定的几个取值
TS中的枚举类型底层的本质其实就是数值类型

//如:一年有四季,人的性别
// TS中的枚举类型的取值,默认是从上到下从0开始递增的
// 可以手动指定枚举的取值
enum Gender {  // 这是一个类型  这是一个类型
    Male,  //默认是0
    Femal  //默认是1
}
enum Gender {
    Male,
    Femal=20
}

let a:Gender = Gender.Femal;
console.log(a)  //  Male
console.log(Gender[0]);  // 20

5 any和void类型

5.1 any类型 任意类型

// 当我们不清楚某个数据的类型时,可以使用any
let a:any;
a = 1;
a = "helo";
a = [1,2,3];

5.2 void类型 空类型

表示没有任何类型 一般情况用于函数的返回值。
:void 此函数的返回值是空类型,没有返回值

function test():void{
    // return 123;
}
let res = test();
console.log(res)

let b:void;   // 定义一个不可以保存任意类型的变量
// b = 123;
// b = true;
// b = [1,2,3];
// b如果是void类型,里面可以保存null和und,前提是关闭严格模式
// 可以在tscofig中关闭
b = null;
b = undefined;
console.log(b)

6 never和object类型

6.2 Never类型 表示的是那些永远不存在的值的类型。(基本上不用)

一般用于抛出异常或根本不可能有返回值的函数。

// 基本上不用
function test():never{
    while (true){}
}
// test()

function test2():never {
    throw new Error("报错了...")
}
// test2()

6.2 Object类型

// Object类型   表示一个对象
let obj:Object;   // 在TS中,一切都是对象
// obj = 1;
// obj = true;
// obj = "hello";
obj = {name:"ok",age:110};
console.log(obj)

7 类型断言

7.1 什么是类型断言

TS中的类型断言和基本编程语言中的类型转化很像。
基本编程语言中的类型转化:可以将一种类型强制转换成另外一种类型。
**类型断言:**就是告诉编译器,你不要帮我们检查了,相信我,我知道我在干什么。

// 如:我们拿到了一个any类型的变量,但是我们明确的知道这个变量保存的是字符串类型
// 此时,我们就可以告诉编译器,你不需要帮我们检查了,因为确信它是字符串类型
// 此时,我们就可以通过类型断言将any类型转化成stirng类型

let str:any = "hello";
// 方式一  (str)叫类型断言  ===> 把any类型转成string类型
let len = (<string>str).length;

// 方式二
let len = (str as string).length
console.log(len)  // 5

8 接口

8.1 什么是接口类型

和number,string,boolean,enum这些数据类型一样。
接口也是一种类型, 也是用来约束使用者的。
现在:看到接口,想到约束

// 定义一个接口类型
interface FullName {
    firstName:string,
    lastName:string
}
// 需求: 要求定义一个函数输出一个人完整的姓名, 这个人的姓必须是字符串, 这个人的名也必须是一个字符
let obj = {firstName:"zhang",lastName:"san"};
//:FullName作为接口进行了约束 只能传字符串
function say({firstName,lastName}:FullName) {
    console.log(`我的姓名是:${firstName}_${lastName}`)
}
say(obj);

8.2 可选属性和索引签名

// 定义一个接口  FullName 也是一种数据类型   约束
interface FullName {
    firstName:string,
    lastName:string,
    middleName?:string,//可选属性 ?代表这个参数可有可无
    [propName:string]:any //索引签名 键是string类型 值是any类型
}
// 需求: 如果传递了middleName就输出完整名称, 如果没有传递middleName, 那么就输出firstName和lastName
function say({firstName, lastName, middleName}:FullName) {
    if(middleName){
        console.log(`我的姓名是:${firstName}_${middleName}_${lastName}`)
    }else{
        console.log(`我的姓名是:${firstName}_${lastName}`)
    }
}

如果使用接口来约束变量或形参,那么在给变量或形参赋值时
赋预的值,必须和接口限定的一模一样,多一个或少一个都不行,类型错误也不行

少一个的方式(middleName?:string,)
say({firstName:"wang",lastName:"cai"})
多一个的方式([propName:string]:any)
方式一:类型断言

将里面转化为FullName

say({firstName:"wang",lastName:"cai",middleName:"hello",address:"bj"} as FullName)
方式二:使用变量
 let obj = {firstName:"wang",lastName:"cai",middleName:"hello",address:"bj",abc:"abc"}
say(obj)
方式三:使用索引签名 在JS中,JS对象的键都会隐式转化成字符串
say({firstName:"wang",lastName:"cai",middleName:"hello",address:"bj",abc:true,123:456})

8.3 索引签名和只读属性

1.什么是索引签名?

索引签名用于描述那些“通过索引得到”的类型,比如arr[10]或obj[“key”]
对象键值只能是索引签名中约束好的类型。

interface FullName{
    [PropName:number]:string
}
let obj:FullName={//此时对象键值只能是上面约束好的类型
    23:"wangcai",
    1:"cai",
    // middleName:true  // 报错
}
2.什么是只读属性
interface FullName {
    firstName:string,
    readonly lastName:string  // readonly  表示约束的lastName是只读的,不能修改
}
let myName:FullName = {
    firstName:"w",
    lastName:"c"
}
myName.lastName = "666";//报错  不能修改

8.4 函数接口和混合类型接口

8.4.1 函数接口**(就是用接口去约束函数的定义)**

我们除了可以通过接口来限定对象以外, 我们还可以使用接口来限定函数
接口 现在 先想到约束 接口还可以约束函数

interface SumInterfase{
    // (a:number,b:number) 是约束形参的    number是约束返回值的
    (a:number,b:number):number
}
//let sum= function (x:number,y:number):number { 
let sum:SumInterfase = function (x,y) {//此时等同于上面
    return x+y;
    // return "hello";  // 报错
}
8.4.2 混合类型 接口 :可以约束函数,也可以约束属性

约定的内容中既有对象属性, 又有函数

// 定义混合类型 接口
interface CountInterface {
    ():void  // 约束函数  函数没有形参 没有返回值
    counter:number  // 约束变量  数据
}
let getCounter = (function ():CountInterface {
    let fn = <CountInterface>function () {
        fn.counter++;
        console.log(fn.counter)
    }
    fn.counter = 0;
    return  fn
})();
getCounter()
getCounter()
getCounter()

8.5 接口的继承

接口的继承
TS中的接口和JS中的类一样是可以继承的

interface LengthInterface {
    length:number
}
interface WidthInterface {
    width:number
}
interface RectInterface extends LengthInterface,WidthInterface{
    color:string
}

let rect:RectInterface = {//此时这里必须写上面继承的属性
    length:200,
    width:100,
    color:"red"
}

9 函数

TS中的函数大部分和JS相同

// JS中的函数写法
// 函数声明
function say1(name) {
    console.log(name);
}
// 函数表达式
let say2 = function (name) {
    console.log(name);
}
// 箭头函数
let say3 = (name) => {
    console.log(name);
}

TS中的函数写法

// 函数声明
function say1(name:string) :void{
    console.log(name);
}
// 函数表达式
let say2 = function (name:string):void {
    console.log(name)
}
// 箭头函数
let say3 = (name:string):void=>{
    console.log(name);
}

9.1 函数的声明

TS函数完整格式
在TS中函数的完整格式应该是由函数的定义和实现两个部分组成的。
type在ts中也是关键字,可以定义一个类型。

//完整写法
 //定义一个函数
 //:()=>number  是对 AddFun的约束  约束你AddFun必须是一个函数
 let AddFun:()=>number;
 // AddFun = 123; // 报错了
 AddFun = function ():number {
     return 123;
 }
// 一步到位 后的简化写法
// 如果前面约束了,赋值运算符后面的函数就可以不约束了
let AddFun:(a:number,b:number)=>number = function (x, y) {
    return x+y;
}
let res = AddFun(12,12); 
console.log(res)

// type在ts中也是关键字,可以定义一个类型  此时下方写法和上方一样
type AddFun = (a:number,b:number)=>number;
let add:AddFun = function (x,y) {
    return x+y;
}

9.2 函数的重载

函数的重载就是同名的函数可以根据不同的参数实现不同的功能。

//普通例子需求 将输入的字符串转化为数组
function getArray(str:string):string[] {
    return str.split("")//将每一个字符分隔开
}
let res = getArray("hello");
console.log(res)
// 定义函数的重载
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;
    }
}
console.log(getArray(10))
console.log(getArray("hello world"))

10 可选-默认-剩余参数

10.1 可选参数

可选参数(后面必须是可选参数,或者必须在最后) 配合函数的重载
函数的重载: 函数相同,参数的类型不同 或 参数的个数不同

function add(x:number,y:number):number;
function add(x:number,y:number,z:number):number;
function add(x:number,y:number,z?:number):number{
    return x+y+(z?z:0);
}
// let res = add(10,20)
let res = add(10,20,100)
console.log(res)

10.2 默认参数

默认值:不传值的情况下使用。

function add(x:number,y:number=666):number {
    return x+y;
}
// let res = add(10,20)
let res = add(10)
console.log(res)

10.3 剩余参数

第一个参数接收数字,剩余的(后面的)都放入数组

function add(x:number,...args:number[]):void {
    console.log(x)
    console.log(args)//此时args就是剩余参数
}
let res = add(10,20,30,40,50,60)
console.log(res)

11 泛型

11.1 什么是泛型?

  • 在编写代码的时候我们既要考虑代码的健壮性, 又要考虑代码的灵活性和可重用性
    通过TS的静态检测能让我们编写的代码变得更加健壮, 但是在变得健壮的同时却丢失了灵活性和可重用性
    所以为了解决这个问题TS推出了泛型的概念
  • 通过泛型不仅可以让我们的代码变得更加健壮, 还能让我们的代码在变得健壮的同时保持灵活性可重用性
// 不使用泛型
//  尽量不要使用any
let getArray = (value:any,items:number=5):any[]=>{
    return new Array(items).fill(value)
}
// let arr = getArray(6,10);
let arr = getArray("abc",10); // 报错
// arr.map(item=>item.len)  // any的话,TS的强大的提示功能就没有了
console.log(arr)

// 使用泛型
// 泛型是一种不确定的类型  可以自定义名字T A B C   T   K    M
let getArray = <T>(value:T,items:number=5):T[]=>{
    return new Array(items).fill(value)
}
// 使用时,给T传入什么类型,T就是什么类型
// let arr = getArray("abc")//传的string
// arr.map(item=>item.length)
// console.log(arr)

let arr = getArray<number>(8)
// arr.map(item=>item.length)
console.log(arr)

11.2 泛型约束

11.3 在泛型约束中使用类型参数

一个泛型被另一个泛型约束, 就叫做泛型约束中使用类型参数


interface KeyInterface{
    [key:string]:any
}
// T这个泛型没有赋值
// T extends KeyInterface  使用接口去约束泛型
// let getPros = (obj:T,key)=>{

// K extends keyof T  使用一个泛型去约束另一个泛型  keyof   key
let getPros = <T,K extends keyof T>(obj:T,key:K)=>{
    return obj[key]
}
let obj = {
    name:"wc",
    age:100
}
// 在JS中,获取一个对象中不存在的属性时,得到und
// 现在,需求是,如果这个属性不存在,就报错
let res = getPros(obj,"address")  // 报错 address不是obj中的键
// let res = getPros(obj,"name")  // wc
console.log(res)

12 类

TS中的类和ES6中的类’几乎’一样

class Person {
    // 有原生JS不同的是,对于实例属性,需要写出来
    // 实例属性
    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(`我正在吃${Person.food}`)
        // 在静态方法体中,想要调用静态属性,也可以通过this来调用
        console.log(`我正在吃${this.food}`)
    }
}
let p = new Person("wc",100);
p.say();
// 通过类名访问静态属性
Person.food = "meat"
console.log(Person.food);
Person.eat();

子类继承父类的写法

class Student extends Person{
    book:string;  // 实例属性
    constructor(name:string,age:number,book:string) {
        super(name,age);//调用person类的构造方法
        this.book = book;
    }
    // 子类重写父类的方法
    say():void{
        console.log(`我是重写之后的say方法:我的名称叫${this.name},我的年龄是${this.age}--${this.book}`)
    }
    static eat():void{
        console.log(`我是重写之后的eat方法-${this.food}`)
    }
}
let stu = new Student("xq",19,"从0玩转ts")
stu.say();
Student.food = "rice";
Student.eat()

12.1 类属性修饰符

public(公开的) :
如果使用public来修饰属性, 那么表示这个属性是公开的(如果实例属性前面没有写public,默认就是public)
此时的属性可以在类的内部使用, 也可以在子类中使用, 也可以在外部使用。
protected(受保护的) :
如果使用protected来修饰属性, 那么表示这个属性是受保护的
可以在类的内部使用, 也可以在子类中使用
private(私有的) :
如果使用private来修饰属性, 那么表示这个属性是私有的
可以在类的内部使用
readonly(只读的) :
只能读不能改

class Person {
    // 实例属性   如果实例属性前面没有写public,默认就是public
    public name:string;
    protected age:number;//受保护的
    private gender:string;//私有的
    readonly name2:string;//只能读 不能改  
 constructor(name:string,age:number,gender:string,name2:string){
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.name2=name2;
    }
    say():void{
        console.log(`我的名称叫${this.name},我的年龄是${this.age},性别是${this.gender}`)
    }
}
let p = new Person("wc",10,"man","man")
p.say();
p.name;//可以使用
p.age;//报错 提示不能在外部使用
p.name2="xq"//报错 不能改写
class Student extends Person{
    constructor(name:string,age:number,gender:string) {
        super(name,age,gender);
    }
    say():void{
        console.log(`gender=${this.gender}`)
    }
}
let s = new Student("xq",100,"woman")
s.say()

12.2 类方法修饰符

public :
如果使用public来修饰方法, 那么表示这个方法是公开的
可以在类的内部使用, 也可以在子类中使用, 也可以在外部使用
protected :
如果使用protected来修饰方法, 那么表示这个方法是受保护的
可以在类的内部使用, 也可以在子类中使用
private
如果使用private来修饰方法, 那么表示这个方法是私有的
可以在类的内部使用

// 刚才使用了protected修改了构造器
class Person {
    // 实例属性   如果实例属性前面没有写public,默认就是public
    name:string;
    age:number;
    gender:string;
    // 有一个基类, 所有的子类都需要继承于这个基类, 但是我们不希望别人能够通过基类来创建对象 可通过protected实现  这个时候就不能使用new person了
    // protected constructor(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}`)
    }

    public say():void{
        this.sayName();
        this.sayAge();
        this.sayGender();
    }
}
let p = new Person("wc",10,"man")
p.say();
// p.sayAge();//提示错误
// p.sayGender();//提示错误

class Student extends Person{
    constructor(name:string,age:number,gender:string) {
        super(name,age,gender);
    }
    say():void{
        this.sayName();
        this.sayAge();
        // this.sayGender();//提示错误
    }
}
let s = new Student("xq",100,"woman")
s.say()

12.3 类可选属性和参数属性

类可选属性和接口中的可选属性一样,可传可不传的属性。
核心:?

class Person {
    // 实例属性   如果实例属性前面没有写public,默认就是public
    name:string;
    age?:number; // 可选属性
    constructor(name:string,age?:number){
        this.name = name;
        this.age = age;
    }
}
// let p = new Person("wc",100)
let p = new Person("wc")
console.log(p);
使用参数属性进行简化

// 一句话搞定实例属性的接收和定义

class Person {
    public name:string;
    public age:number;
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
}
//上面等价于下面  下面代码是简写
class Person{
    constructor(public name:string,public age:number) {
    }
}
let p = new Person("wc",100)
console.log(p);

12.4 类存取器

存取器:通过getters/setters来截取对对象成员的访问。

class Person {
    // _age 叫实例属性   private是修饰实例属性的
    // 就意味着,这个_age属性只能在本类内部访问
    // 假如我们就想在外部访问,怎么办?
    // 获取器getters   设置器setters
    private _age:number = 0;
    // 设置器
    set age(val:number){
        console.log('进入了set age方法');
        if(val<0){
            throw new Error('人的年龄不能小于零');
        }
        this._age = val;
    }
    // 获取器
    get age():number{
        // age获取器,内部返回了_age的私有属性
        console.log('进入了get age方法');
        return this._age;//返回内部属性
    }
}
let p = new Person();
// console.log(p.age);  // 当我们访问age时,会调用上面的age获取器
p.age = 666;
console.log(p.age);

12.5 抽象类

1.什么是抽象类?

  当一个类,不想让new(不需要constructor),只想让别人继承,此时,可以声明这个类是抽象类。

  • 抽象类是专门用于定义那些不希望被外界直接创建的类的
  • 抽象类一般用于定义基类
  • 抽象类和接口一样用于约束子类
2.抽象类和接口区别?

接口中只能定义约束, 不能定义具体实现
而抽象类中既可以定义约束, 又可以定义具体实现

// 一个抽象类中可以有属性,属性也可以是抽象属性
abstract class Person {
    abstract name:string;  // 抽象属性  约束
    abstract age:number;  // 抽象属性   约束
    // 也可以定义一些方法实现
    eat():void{  // 非抽象方法
        console.log(`${this.name}正在吃东西`)
    }
}
// let p = new Person();  // 抽象类不能new  主要是为了让别人继承的

// 抽象类中的方法,都需要重写一次
// 子类如果继承了抽象类,要重写抽象类中的 抽象属性
class Student extends Person{
    name:string = "wc";
    age:number = 100;
    say():void{
        console.log(`我的名字是${this.name}`)
    }
}
let p = new Student()
p.say()
p.eat();

12.6 类和接口

12.6.1 类实现接口
interface PersonInterface {
    name:string,
    say():void
}
// 如果一个类实现了某个接口,这个类,需要完成这个接口的中的约束
class Person implements PersonInterface{
    name:string = "wc";
    say(): void {
        console.log(`我的名字的:${this.name}`)
    }
}
let p = new Person();
p.say();//调用方法
12.6.2 接口“继承”类

只要一个接口继承了某个类,那么就会继承这个类中所有的属性和方法
下面的PersonInterface接口,继承了Person类,那么就意味着, PersonInterface接口中
也有 name , age , say三个约束
只会继承声明,不会继承实现(指继承键不继承值)

// 接口 “继承" 类
class Person {
    name:string = "wc";
    age:number = 100;
    protected say():void{
        console.log(`name=${this.name},age=${this.age}`)
    }
}
// let p = new Person();
// p.say();

// 如果接口继承的类中包含了protected的属性或方法时,那就只有这个类的子类才能实现这个接口
interface PersonInterface extends Person{
    gender:string
}
class Student extends Person implements PersonInterface{
    gender:string = "man";
    name:string = "wc";
    age:number = 100;
    say(): void {
        console.log(`name=${this.name}, age=${this.age},gender=${this.gender}`)
    }
}
let stu = new Student();
console.log(stu.gender)
console.log(stu.name)
console.log(stu.age)
stu.say()

12.7 类和泛型

在一个类中使用了泛型就叫做泛型类

// 泛型类
class Chache<T> {
    arr:T[] = [];
    add(value:T):T{//返回值是T number
        this.arr.push(value)
        return value;
    }
    all():T[]{//返回值是一个装T的数组 T是number
        return this.arr;
    }
}
let chache = new Chache<number>();//泛型T是number类型
chache.add(1)
chache.add(2)
chache.add(3)
chache.add(4)
console.log(chache.all())

12.8 接口合并现象

当我们定义了多个同名的接口时, 多个接口的内容会自动合并

interface TestInterface {//接口是约束
    name:string
}
interface TestInterface {//接口是约束
    age:number
}
/* 上面两个同名接口就合并成了 下方这个接口
interface TestInterface {
    name:string
    age:number
}
*/

class Person implements TestInterface{
    name:string = "wc"
    age:number = 110;
}

13 数字枚举和字符串枚举

TS中支持两种枚举, 一种是数字枚举, 一种是字符串枚举。
枚举可以看作一种类型。

13.1 数字枚举

默认情况下就是数字枚举

enum Gender{
    MAN,
    WOMAN
}
console.log(Gender.MAN)  // 0
console.log(Gender.WOMAN)  // 1*/

注意点:

  • 数字枚举的取值默认从0开始递增
  • 数字枚举的取值可以是字面量, 也可以是常量, 也可以是计算的结果
const num = 888;
function getNum() {
    return 123;
}
enum Gender{
    // MAN=6,// 字面量
    // WOMAN

    // MAN=num,  // 注意点:如果使用了常量给前面的枚举值赋值了,后面的枚举值也需要手动赋值
    // WOMAN=999

    MAN = getNum(),// 注意点:如果使用了计算结果给前面的枚举值赋值了,后面的枚举值也需要手动赋值
    WOMAN = 456

}
console.log(Gender.MAN)
console.log(Gender.WOMAN)

枚举反向映射 默认情况下,枚举值的本质还是数字(原始值)
可以根据枚举值获取到原始值
也可以根据原始值获取到枚举值

enum Gender{
    MAN,
    WOMAN
}
console.log(Gender.MAN)  // 0
console.log(Gender[1])  // WOMAN*/

13.2 字符串枚举

enum Gender{
    MAN="www.baidu.com",// 注意点:如果使用了字符串给前面的枚举值赋值了,后面的枚举值也需要手动赋值
    WOMAN="www.taobao.com"
}
console.log(Gender.MAN)
console.log(Gender.WOMAN)

注意点:

  • 如果使用字符串给前面的枚举值赋值了, 那么后面的枚举值也必须手动赋值
  • 和数字枚举不一样, 字符串枚举不能使用常量或者计算结果给枚举值赋值
  • 虽然字符串枚举不能够使用常量或者计算结果给枚举值赋值, 但是它可以使用内部的其它枚举值来赋值
enum Gender{
    // MAN=str,// 报错
    // WOMAN=getNum() // 报错
    MAN = "男",
    WOMAN = "女",
    YAO = MAN//使用内部的其它枚举值来赋值
}

13.3 异构枚举(既包含数字又包含字符串)

enum Gender{
    MAN=110,
    WOMAN="hello world"
}

console.log(Gender.MAN)
console.log(Gender.WOMAN)
console.log(Gender[110])
console.log(Gender["hello world"]) // 注意点:如果是字符串枚举,那么是无法通过原始值获取到枚举值的

13.4 枚举成员类型

我们可以把枚举成员当做类型来使用
注意点:如果是字符串枚举,那么只能使用枚举成员值,就是MAN或WOMAN

enum Gender {  // MAN和WOMAN叫枚举值,也叫枚举成员
    MAN = "www.baidu.com",
    WOMAN = "www.taobao.com"
}
interface TestInterface {  // 约束
    // age:string,
    age:Gender.WOMAN, // 把枚举成员当做类型来使用
    // address:Gender.MAN  // 把枚举成员当做类型来使用
}
//在类中
class Person implements TestInterface{
    age:Gender.WOMAN // OK如果是字符串枚举,那么只能使用枚举成员值
    // age:1  // OK 数字枚举就没有上面的限制 也可以直接使用别的数字
}

13.5 联合枚举类型

什么是联合类型?

  • 联合类型就是将多种数据类型通过|连接起来
  • 我们可以把枚举类型当做一个联合类型来使用
let a:(number | string); // (number | string)叫联合类型
a = 123;
a = "hello"
enum Gender {  // MAN和WOMAN叫枚举值,也叫枚举成员
    MAN = "www.baidu.com",
    WOMAN = "www.taobao.com"
}
interface TestInterface {
    age:Gender  // 等同于age:(Gender.MAN | Gender.WOMAN)  // 联合枚举类型
}

13.6 运行时枚举

枚举在编译之后是一个真实存储的对象, 所以可以在运行时使用
而像接口这种只是用来做约束做静态检查的代码, 编译之后是不存在的

13.7 常量枚举

普通枚举和常量枚举的区别:

  • 普通枚举会生成真实存在的对象。
  • 常量枚举不会生成真实存在的对象, 而是利用枚举成员的值直接替换使用到的地方。
    常量枚举转化为js后代码体积小,性能就提高了。
const enum Gender {
    MAN,
    WOMAN
}
console.log(Gender.MAN === 0)//编译为js后 转化为console.log(0/*MAN*/===0)

14 类型推论

1.什么是自动类型推断?
不用明确告诉编译器具体是什么类型, 编译器就知道是什么类型

//1.根据初始化值自动推断
let a:number = 123;//等价于let a = 123;// a的类型,已经被ts编译器推断  是number
a = "hello";//报错 已经是number类型
let arr = [1,"hello"];   //等价于 let arr:(number | string)[] = [1,"hello"]
// 2.根据上下文类型自动推断  event叫事件对象
window.onmousedown = (e)=>{
    // ts编译器已推断出e是事件对象   事件对象中有一个target
    console.log(e.target)
}

15 类型兼容性

什么是类型的兼容性:把一个东西 赋值 给另一个东西 看一下能否赋值。

15.1 基本兼容性

let p1 = {name:"wc"};
let p2 = {age:18};
let p3 = {name:"xq",age:20,address:"bj"}
interface TestInterface {
    name:string
}
let t:TestInterface;//用这个接口去约束t 需要里面有name为string
t = p1;  // OK
// t = p2; // 不OK  name必不可少
t = p3; // OK   对象中的属性可多不可少

15.2 函数兼容性

  • 参数个数的兼容性(可以把少的赋值给多的)
  • 对于参数类型,必须包含(但可以把少的赋值给多的)
  • 返回值类型必须一样
  • 函数双向协变 之 参数的双向协变(可以相互赋值)
  • 函数双向协变 之 返回值双向协变(将返回值是具体类型的赋值给联合类型)
  • 函数重载(可以把重载多的赋值给重载少的)
let fn1 = (x:number,y:number)=>{}
let fn2 = (x:number)=>{}
let fn3 = (x:string)=>{}
let fn4 = (x:number,y:string)=>{}
// 不能把多个赋值给少的
// fn2 = fn1;  // fn1中有两个参数   fn2中只有一个参数  报错
// 可以把少的赋值给多的
fn1 = fn2;
// fn3 = fn1  // 不OK   参数类型不一样   对于参数类型,必须一模一样*/
fn4 = fn1//但可以把参数少的 类型被包含的 赋值给多的

// 函数双向协变 之 参数的双向协变 
let fn1 = (x:(number | string))=>{};
let fn2 = (x:number)=>{};
// fn1 = fn2; // OK
fn2 = fn1;  // OK
// 函数双向协变 之 返回值双向协变
let fn1 = (x:boolean):(number|string)=> x ? 123 :"abc";
let fn2 = (x:boolean):number=>456;
fn1 = fn2;  //  可以的  可以将返回值是具体类型的赋值给联合类型
// fn2 = fn1;  //  不能将联合类型赋值给具体类型

15.3 枚举兼容性

数字枚举与数值兼容
数字枚举与数字枚举不兼容
字符串枚举与字符串不兼容

enum Gender {
    MAN,
    WOMAN
}
let a:Gender;
// a = Gender.MAN  // OK
a = 123;//数字枚举可以用任意数字赋值

// 字符串枚举与字符串不兼容
enum Animal {
    Dog="hello",
    Cat="world"
}

let a:Animal;
a = Animal.Dog;

// a = "hello"; // 字符串枚举与字符串不兼容

15.4 类兼容性

类兼容性只比较实例成员, 不比较类的构造函数和静态成员
类的私有属性(private)和受保护属性(protected)会影响兼容性

// 只比较实例成员, 不比较类的构造函数和静态成员
class Person {
    public name:string;
    public age:number;
    public static age:number;
}

class Animal {
    public name:string;
    constructor(name:string){}
}
let p:Person; // p的类型是Person类型
let a:Animal; // a的类型是Animal类型
// p = a;  // 不OK  
a = p;  // 可多不可少 把多的赋值给少的

15.5 泛型兼容性

// 泛型只影响使用的部分, 不会影响声明的部分
// 兼容的是约束
interface TestInterface<T> {
    // age:T  // age注释了,相当于没有约束了
}

let t1:TestInterface<number>;  // age:number类型
let t2:TestInterface<string>;  // age:string类型
// t1 = t2; // 不OK
t2 = t1;  // 不OK  age注释了,就兼容

16 高级类型

16.1 交叉类型

格式: type1 & type2 & …
交叉类型是将多个类型合并为一个类型

// (T & U)   交叉类型   {name:"wc",age:18}
let margeFn = <T,U>(arg1:T,arg2:U):(T & U)=>{//(T & U)这里的是返回类型约束
    let res = {} as (T & U);  // 类型断言 等同于let res={};
    res = Object.assign(arg1,arg2)//合并对象
    return res;
}
// let res = margeFn({name:"wc"},{age:18});
let res = margeFn({name:"wc"},{age:18});

// 目标:{name:"wc",age:18}
console.log(res)

16.2 联合类型 (string | number)

格式: type1 | type2 | …
联合类型是多个类型中的任意一个类型

let a:(string | number);
a = "123";
a = 123;

16.3 类型保护(定义类型保护函数/typeof/instanceof实现类型保护)

对于联合类型的变量,在使用时如何确切告诉编译器它是哪一种类型
通过类型断言或者类型保护

// 虽然通过类型断言可以确切的告诉编译器当前的变量是什么类型,
// 但是每一次使用的时候都需要手动的告诉编译器, 这样比较麻烦, 冗余代码也比较多
if((a as string).length){//如果是字符串类型
    console.log((a as string).length)
}else{
    console.log((a as number).toFixed())
}
// 定义了一个类型保护函数, 这个函数的'返回类型'是一个布尔类型
// 这个函数的返回值类型是, 传入的参数 + is 具体类型
function isString(value: (string | number)):value is string {//返回值类型
    return typeof value === "string";
}
if(isString(a)) {
    // a是abc
    console.log(a.length)
}else{
    console.log(a.toFixed())
}

除了可以通过定义类型保护函数的方式来告诉编译器使用时联合类型的变量具体是什么类型以外
我们还可以使用typeof来实现类型保护
注意点:

  • 如果使用typeof来实现类型保护, 那么只能使用 === / !==
  • 如果使用typeof来实现类型保护, 那么只能保护 number/string/boolean/symbol类型
  • 还可以通过instanceof来实现类型保护
if(typeof a === "string"){
   console.log(a.length)
}else{
   console.log(a.toFixed())
}
//instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
if(obj instanceof Person){//告诉obj是不是person的类型
   console.log(obj.name)
}else{
   console.log(obj.age)
}
/
class Person {
   name:string = "wc";
}
class Animal {
   age:number = 18;
}
let getRandomObject = ():(Person | Animal)=>{
   let num = Math.random();
   return (num>=0.5) ? new Person() : new Animal();
}
let obj = getRandomObject();
// console.log(obj)
// console.log(obj.a)
if(obj instanceof Person){//告诉obj是不是person的类型
   console.log(obj.name)
}else{
   console.log(obj.age)
}

16.4 null和undefined

TypeScript具有两种特殊的类型, null和 undefined,它们分别具有值null和undefined。

默认情况下我们可以将 null和 undefined赋值给任意类型
默认情况下null和 undefined也可以相互赋值
注意点:

  • 在企业开发中, 如果不想把 null和 undefined赋值给其它的类型
  • 或者不想让 null和 undefined相互赋值, 那么我们就可以开启strictNullChecks
// 如果我们开启了strictNullChecks, 还想把null和 undefined赋值给其它的类型
// 那么我们就必须在声明的时候使用联合类型
let a:(number | null | undefined);
a = null;
a = undefined;
a = 120;
// 对于可选属性和可选参数而言, 如果开启了strictNullChecks, 那么默认情况下数据类型就是联合类型
// 就是当前的类型 + undefined类型
class Person{
    name?:string//  string|undefined
}
function say(age?: number) {
}

16.5 去除 null或 undefined检测


function getLength(value:(string | null | undefined)) {
    return ()=>{
        // return value.length;  // 不行  因为value是联合类型

        // return (value || "").length;//是字符串

        // return (value as string).length;//类型断言

        // 可以使用!来去除null和und
        // !的含义就是这个变量一定不是null和und
        return value!.length;
    }
}
let fn = getLength("www.baidu.com")
let res = fn();
console.log(res)

16.6 类型别名

1.什么是类型别名?

类型别名就是给一个类型起个新名字, 但是它们都代表同一个类型

例如: 你的本名叫张三, 你的外号叫小三, 小三就是张三的别名, 张三和小三都表示同一个人

// 给string类型起了一个别名叫做MyString, 那么将来无论是MyString还是string都表示string
type MyString = string;
let a:MyString;
a = "hello";
a = 1;

类型别名也可以使用泛型

type MyType<T> = {x:T,y:T};
let a:MyType<number>;
a = {x:123,y:456};
// a = {x:123,y:456,z:789};  // 不OK
a = {x:666}

可以在类型别名类型的属性中使用自己

type MyType = {
    name:string;
    children?:MyType//可选属性
}
let a:MyType = {
    name:"one",
    children:{
        name:"two",
        children:{
            name:"three"
        }
    }
}

接口和类型别名是相互兼容的

type MyType = {
    name:string;
}
interface MyInterface {
    name:string;
}
let a:MyType = {name:"wc"}
let b:MyInterface = {name:"xq"};
a = b;
b = a;

16.6 类型别名和接口的对比

  1. 都可以描述属性或方法
type MyType = {
    name:string,
    say():void
}
interface MyInterface{
    name:string,
    say():void
}
  1. 都允许拓展
interface MyInterface2 extends MyInterface{
    age:number
}
type MyType2 = MyType & {
    age:number
}
  1. type 可以声明基本类型别名,联合类型,元组等类型, interface不能
type MyType1 = boolean;
type MyType2 = string | number;
type MyType3 = [string,boolean,number];
  1. type不会自动合并 interface会自动合并
interface MyInterface{
    name:string,
}
interface MyInterface{
    age:number,
}
//等同于下方代码
interface MyInterface{
    name:string,
    age:number,
}

16.7 字面量类型

什么是字面量?

字面量就是源代码中一个固定的值

例如数值字面量: 1,2,3,…

例如字符串字面量: ‘a’,‘abc’,…

2.在TS中我们可以把字面量作为具体的类型来使用

当使用字面量作为具体类型时, 该类型的取值就必须是该字面量的值

type MyType = 123; // 定义一个类型别名  类型是一个字面量123
let a:MyType = 123;//此时取值只能是123

type MyType2 = { a:1,b:2 }
let b:MyType2 = { a:1,b:2 }//取值必须一模一样

16.8 可辨识联合(具有共同的可辨识特征)

一个类型别名, 包含了具有共同的可辨识特征的类型的联合。

interface Square {  // 正方形
    kind:"square",
    size:number
}
interface Rectangle {  // 长方形
    kind:"rectangle",
    width:number
    height:number
}
interface Circle {  // 圆形
    kind:"circle",
    radius:number
}
//上方的可辨识特征是kind
/*
Shape就是一个可辨识联合
因为: 它的取值是一个联合
因为: 这个联合的每一个取值都有一个共同的可辨识特征
* */
type Shape = (Square | Rectangle | Circle);
function aera(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
    }
}

可辨识联合的完整性检查

在企业开发中如果相对可辨识联合的完整性进行检查, 那么我们可以使用

  • 方式一: 给函数添加返回值 + 开启strictNullChecks
function aera(s:Shape):number {//加上返回值类型约束
  • 方式二: 添加default + never
function MyNever(x: never):never {
    // while (true){}
    throw new Error("可辨识联合处理不完整"+x)
}
function aera(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;
        default: return MyNever(s)
    }
}

16.8 索引类型

通过[]索引类型访问操作符, 我们就能得到某个索引的类型

// 通过[]索引类型访问操作符, 我们就能得到某个索引的类型
class Person {
    name:string;
    age:number
}
type MyType = Person["name"];
let a:MyType = "hello";
a = 123; // 不OK*/

例子:

// 应用场景
// 需求: 获取指定对象, 部分属性的值, 放到数组中返回
let obj = {
    name:"wc",
    age:10,
    gender:true
}//表示k是来自t的键
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"]);
console.log(res)//wc 10

索引访问操作符注意点
不会返回null/undefined/never类型

interface TestInterface {
    a:string,
    b:number,
    c:boolean,
    d:symbol,
    e:null,
    f:undefined,
    g:never
}
type MyType = TestInterface[keyof TestInterface];
let a:MyType;
a = "hello";
a = 123;
a = true;
a = null;
a = undefined;

16.9 映射类型

根据旧的类型创建出新的类型, 我们称之为映射类型

在映射类型里,新类型以相同的形式去转换旧类型里每个属性。

interface TestInterface1 {
    name:string,
    age:number
}
type ReadonlyTestInterface<T>={
    // [P in keyof T] 作用:遍历出指定类型所有的key,添加到当前对象上
    // [P in keyof T]就相当于是name age   p就是值
    // readonly [P in keyof T]:T[P] //相当于readonly name:string,
    readonly [P in keyof T]?:T[P]//相当于readonly name?:string,
}

type MyType = ReadonlyTestInterface<TestInterface1>

我们可以通过+/-来指定添加还是删除 只读和可选修饰符

interface TestInterface1 {
    name:string,
    age:number
}
type ReadonlyTestInterface<T>={
    // -readonly [P in keyof T]-?:T[P]//相当于[P in keyof T]:T[P]
    +readonly [P in keyof T]+?:T[P]//默认加号
}

type MyType = ReadonlyTestInterface<TestInterface1>*/

由于生成只读属性和可选属性比较常用, 所以TS内部已经给我们提供了现成的实现

简化写法
// Readonly / Partial
interface TestInterface1 {
    name:string,
    age:number
}
type MyType1 = Readonly<TestInterface1>;//只读
type MyType2 = Partial<TestInterface1>;//可选
type MyType3 = Partial<Readonly<TestInterface1>>;//只读可选
Pick映射类型

将原有类型中的部分内容映射到新类型中

interface TestInterface {
    name:string,
    age:number
}
type MyType = Pick<TestInterface,"name">//映射过去name
Record映射类型

他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型

type Animal = 'person' | 'dog' | 'cat';
interface TestInterface {
    name:string,
    age:number
}
type MyType = Record<Animal,TestInterface>

let a:MyType = {
    person:{
        name:"wc",
        age:10
    },
    dog:{
        name:"wc",
        age:10
    },
    cat:{
        name:"wc",
        age:10
    }
}

映射类型推断

对于Readonly, Partial和 Pick的映射类型, 我们可以对映射之后的类型进行拆包,

还原映射之前的类型, 这种操作我们称之为拆包。

interface TestInterface {
    name:string,
    age:number
}
type MyType<T> = {
    +readonly [P in keyof T]:T[P]//添加只读
}

type test = MyType<TestInterface>;

type UnMyType<T> = {
    -readonly [P in keyof T]:T[P]//删除只读  这就是拆包
}
type test2 = UnMyType<test>

16.10 分布式条件类型

16.10.1.条件类型(三目运算)

判断前面一个类型是否是后面一个类型或者继承于后面一个类型

如果是就返回第一个结果, 如果不是就返回第二个结果

语法: T extends U ? X : Y;

type MyType<T> = T extends string ? string : any;
type res = MyType<boolean>;
16.10.2.分布式条件类型

被检测类型是一个联合类型的时候, 该条件类型就被称之为分布式条件类型

type MyType<T> = T extends any ? T : never;
type res = MyType<string | number | boolean>;
// 从T中剔除可以赋值给U的类型
type MyType<T,U> = T extends U ? never : T;
type res = MyType<string | number | boolean,number>//string boolean
// Exclude关键字了可以达到上面的效果
type res = Exclude<string | number | boolean,number>//string boolean
// 提取T中可以赋值给U的类型  Extract 提取
type res = Extract<string | number | boolean,number | boolean>//number boolean
// NonNullable 表示剔除 null和und
type res = NonNullable<string | null | boolean | undefined>//string boolean
// ReturnType  提取函数的返回值类型
type res = ReturnType<((()=>number))>//number
//InstanceType 获取构造函数类型的实例类型
class Person {
    constructor(name:string,age:number) {
    }
}
type res = InstanceType<typeof Person>;  // person

unknown类型是TS3.0中新增的一个顶级类型, 被称作安全的any(纯粹的any就和js一样了)

//1.任何类型都可以赋值给unknown类型
let a:unknown;
a = 1;
a = "hello";
a = true;
//2.如果没有类型断言或基于控制流的类型细化, 那么不能将unknown类型赋值给其它类型
let a:unknown = 123;
let b:number;
//b = a; // 不能将unknown类型赋值给其它类型
b = a as number;  // 使用类型断言后,可以把unknown类型赋值给其它类型
if(typeof a === 'number'){//类型保护 确定值类型
    b = a;
}
//3.如果没有类型断言或基于控制流的类型细化, 那么不能在unknown类型上进行任何操作
let a:unknown = 123;
//a++;//直接这样不行 下面可以进行操作
(a as number)++;
if(typeof a === 'number'){
    a++
}
//4.只能对unknown类型进行 相等或不等操作, 不能进行其它操作(因为其他操作没有意义)
let a:unknown = 123;
let b:unknown = 123;
console.log(a == b)
console.log(a != b)
console.log(a > b)//报错
console.log(a <= b)//报错
//5.unknown与其它任何类型组成的交叉类型最后都是其它类型
type MyType = number & unknown;//number
type MyType2 = string & unknown;//string
//6.unknown除了与any以外, 与其它任何类型组成的联合类型最后都是unknown类型
type MyType1 = number | unknown;
type MyType2 = string | number | boolean | unknown;
//7.never类型是unknown类型的子类型
type MyType = never extends unknown ? true : false;
//8.keyof unknown等于never
type MyType = keyof unknown;
//9.unknown类型的值不能访问其属性,方法,创建实例
class Person {
    name:string = "wc";
    say():void{
        console.log(`name=${this.name}`)
    }
}
// let p = new Person();  // p的类型是Person类型
let p:unknown = new Person();  // p的类型是unknown类型
p.say();//报错 不能访问
console.log(p.name)//报错 不能访问
// 10.使用映射类型时, 如果遍历的是unknown类型, 那么不会映射任何属性
type MyType<T> = {
    [P in keyof T]:any
}
type res = MyType<unknown>;

17 模块

TS中的模块几乎和ES6和Node中的模块一致

模块之间相互隔离。

17.1 ES6模块(导入导出写法)

//1 分开导入导出
export xxx;
import {xxx} from "path";

//2 一次性导入导出
export {xxx, yyy, zzz};
import {xxx, yyy, zzz} from "path";

//3 默认导入导出
export default xxx;
import xxx from "path";

17.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");导入

ES6的模块和Node的模块是不兼容的, 所以TS为了兼容两者就推出了

export = xxx;//导出
import xxx = require('path');//导入

18 命名空间

1.什么是命名空间?

命名空间可以看做是一个微型模块,

当我们先把相关的业务代码写在一起, 又不想污染全局空间的时候, 我们就可以使用命名空间

本质就是定义一个大对象, 把变量/方法/类/接口…的都放里面

2.命名空间和模块区别

在程序内部使用的代码, 可以使用命名空间封装和防止全局污染

在程序内部外部使用的代码, 可以使用模块封装和防止全局污染

总结: 由于模块也能实现相同的功能, 所以大部分情况下用模块即可

namespace Space1{
    export let a = 1;
    let b = 2;
}

namespace Space2{
    let a = 3;
    let b = 4;
}

console.log(Space1.a)//想要使用 需要导出

19 声明合并

在ts当中接口和命名空间是可以重名的, ts会将多个同名的合并为一个。

19.1 重名的接口

同名的接口重名就会合并内部属性。

同名接口如果属性名相同, 那么属性类型必须一致。

同名接口如果出现同名函数, 那么就会成为一个函数的重载。

19.2 重名的命名空间

同名的命名空间中会合并内部属性。

同名的命名空间中不能出现同名的变量,方法等。

同名的命名空间中其它命名空间没有通过export导出的内容是获取不到的。

namespace Validation{
    export let name:string = 'wc';
}
namespace Validation{
    export let say = ()=> {
        console.log(`name = ${name}`);
    };
}
Validation.say();//没有导出 name为空

除了同名的接口和命名空间可以合并以外
命名空间还可以和同名的类/函数/枚举合并

19.3 命名空间和类合并

注意点: 类必须定义在命名空间的前面
会将命名空间中导出的方法作为一个静态方法合并到类中

class Person {
    say():void{
        console.log('hello world');
    }
}
namespace Person{
    export const hi = ():void=>{
        console.log('hi');
    }
}
console.dir(Person);
Person.hi()//静态方法可以这样使用

19.4 命名空间和函数合并

注意点:函数必须定义在命名空间的前面

function getCounter() {
    getCounter.count++;
    console.log(getCounter.count);
}
namespace getCounter{
    export let count:number = 0;
}
getCounter();

19.5 命名空间和枚举合并

注意点: 没有先后顺序的要求

enum Gender {
    Man,
    Woman
}
namespace Gender{
    export const Yao:number = 666;
}
console.log(Gender);//Man,Woman,Yao

20 装饰器

20.1 什么是装饰器?

Decorator 是 ES7 的一个新语法,目前仍处于提案中,
装饰器是一种特殊类型的声明,它能够被附加到类,方法, 访问器,属性或参数上
被添加到不同地方的装饰器有不同的名称和特点

  • 附加到类上, 类装饰器
  • 附加到方法上,方法装饰器
  • 附加到访问器上,访问器装饰器
  • 附加到属性上,属性装饰器
  • 附加到参数上,参数装饰器

20.2 装饰器基本格式

  1. 普通装饰器

  2. 装饰器工厂

  3. 装饰器组合

20.3 如何在TS中使用装饰器?

在TS中装饰器也是一项实验性的特性, 所以要使用装饰器需要手动打开相关配置

修改配置文件 experimentalDecorators

function test(target){// taget就会接收到Person这个类
    console.log("test")
}
// test是一个普通的装饰器,把它绑定到了一个类上
// 这个装饰器的代码,会在定义类之前执行,并且会把类传递给装饰器
//@test//写法  把它绑定到了person类上面
//class person{}

// 如果一个函数返回一个回调函数,如果这个函数作为装饰器来使用,那么这个函数就是装饰器工厂
function demo() {
    console.log("demo out")
    return (target)=>{
        console.log("demo in")
    }
}
// 给Person这个类绑定了一个装饰器工厂
// 在绑定的时候,由于函数后面直接加了上了(),所以会先执行装饰器工厂,得到真正的装饰器
// 真正的装饰器会在定义类之前执行,所以还会执行里面的小函数
//@demo()//多加上一个()意味着 装饰器是return后面的
//class person{   }
// 普通的装饰器可以和装饰器工厂一起使用,结合起来一起使用时,会先从上到下
// 执行所有的装饰器工厂,拿到所有真正的装饰器
// 最后再从下致上执行所有的装饰器
// @def()
//  =====> demo out   /    def out   /    def in   /   demo in    /    test

@test
@demo()
@def()
class Person {

}
先执行装饰器工厂拿到所有装饰器,=>原有的装饰器和拿到的装饰器按从下往上顺序依次执行。

20.4 类装饰器

1.类装饰器

  • 类装饰器在类声明之前绑定(紧靠着类声明)。
  • 类装饰器可以用来监视,修改或替换类定义
  • 在执行类装饰器函数的时候, 会把绑定的类作为其唯一的参数传递给装饰器
  • 如果类装饰器返回一个新的类,它会新的类来替换原有类的定义

2.装饰器和装饰器工厂区别
时候可以传递自定义参数

function test(target) {
    console.log(target)
    target.prototype.personName = "wc";//prototype 属性允许您向对象添加属性和方法
    target.prototype.say = ():void =>{
        console.log(`my name is${target.prototype.personName}`)
    }
}

@test
class Person {

}
let p = new Person();
interface Person{
    say():void;
}
console.log(p.say())
///新类替换旧类
function test(target) {
    return class NewPerson {
        name:string = "xq";
        age:number = 100
    }
}


@test
class Person {

}

let p = new Person();
console.log(p)

20.5 Object.defineProperty()

可以直接在一个对象上定义一个新属性,

或者修改一个对象的现有属性,并返回此对象(添加重复的值不同就可以)。

// 定义一个新的属性
let obj = {age:18}
Object.defineProperty(obj,"name",{//给obj新增一个name属性
    value:"wc"
})
console.log(obj)

// 修改属性配置-读写
let obj = {age:18}
Object.defineProperty(obj,"age",{
    writable:false // 设置不能修改 不能写
})
console.log(obj.age)
obj.age = 100;
console.log(obj.age)

// 修改属性配置-迭代
let obj = {age:18,name:"wc"}
for(let key in obj){
    console.log(key+"---"+obj[key])//输出age name
}
Object.defineProperty(obj,"name",{
    enumerable:false//表示不能迭代    name不能参与下面的迭代
})
for(let key in obj){
    console.log(key+"---"+obj[key])//只会输出一个age
}

// 修改属性配置-配置某个属性是否可以删除
let obj = {age:18,name:"wc"}

Object.defineProperty(obj,"name",{
    configurable:false//表示无法删除
})
console.log(obj.name)
delete obj.name;//删除操作 没有执行
console.log(obj.name)

20.6 方法装饰器

方法装饰器写在在一个方法的声明之前(紧靠着方法声明)。
方法装饰器可以用来监视,修改或者替换方法定义。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • (参数一)对于静态方法而言就是当前的类, 对于实力方法而言就是当前的实例
  • (参数二)被绑定方法的名字。
  • (参数三)被绑定方法的属性描述符。
function test(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // console.log(target)
    // console.log(propertyKey)  // 被绑定方法的名字。
    // console.log(descriptor)  // 属性的描述符
    descriptor.enumerable = false;  // 不能遍历
}

class Person {
    @test
    sayName():void{
        console.log("my name is xxx")
    }
    @test
    sayAge():void{
        console.log("my age is 18")
    }
    // 静态访问是属于类的  Person.say
    // @test
    static say():void{
        console.log("say hello world")
    }
}
let p = new Person();
//对象是属性的无序集合  所以可以使用for in
for (let key in p){  // 静态访问是属于类的 不属于对象   遍历的时候就拿不到say()
    console.log(key)
}

20.7 访问器装饰器

  访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。
  访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。
访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
  • 成员的属性描述符。

注意:

TypeScript不允许同时装饰一个成员的get和set访问器。

取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上

function test(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.get = ():string=>{
        return "hello"
    }
}

class Person{
    private _name:string;
    constructor(name:string){
        this._name = name;
    }
    // 访问器
    @test
    get name():string{
        return this._name;
    }
    // 设置器
    set name(value:string){
        this._name = value;
    }
}

let p = new Person("wc")
// console.log(p._name)  // 不OK
console.log(p.name)
p.name = "xq";
console.log(p.name)

20.8 属性装饰器

属性装饰器写在一个属性声明之前(紧靠着属性声明)
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

  • 对于静态属性来说就是当前的类, 对于实例属性来说就是当前实例
  • 成员的名字。
function test(target: any, propertyKey: string) {
    // console.log(target)
    // console.log(propertyKey)

    target[propertyKey] = "xwc"
}

class Person {
    // name:string = "wc"

    @test
    name:string
}

let p = new Person();
console.log(p.name)

20.9 参数装饰器

参数装饰器写在一个参数声明之前(紧靠着参数声明)。
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • 对于静态成员来说是当前的类,对于实例成员是当前实例。
  • 参数所在的方法名称。
  • 参数在参数列表中的索引。
其它:
   属性装饰器,参数装饰器最常见的应用场景就是配合元数据(reflect-metadata),
   在不改变原有结构的同时添加一些额外的信息
   但是元数据目前也是在提案中, 也还没有纳入正式的标准
   所以对于装饰器而言, 我们只需要了解即可,
   因为提案中的所有内容将来都是有可能被修改的
   因为提案中的所有内容目前都有兼容性问题
function test(target: Object, propertyName: string, index: number) {
    // console.log(target)
    // console.log(propertyName)  //say
    console.log(index)
}

class Person {
    say(age:number, @test name:string):void{

    }
}

和官方文档相比并不全,推荐官方补充:传送门

你可能感兴趣的:(TS,JS,知识总结,typescript,javascript,前端)