TS(上)

一、TS的基础概念

1.什么是TS

它是JS的一个超集,在原有的语法基础上,添加强类型并切换为基于类的面向对象语言。
TS属于强类型语言,JS属于弱类型语言,强类型语言支持静态语言和动态语言,弱类型语言支持动态语言。

TypeScriptJavaScript
类型检测强类型语言,支持动态语言静态语言弱类型语言,无静态类型选项
面向项目面向解决大型的复杂项目、架构、代码维护复杂场景脚本语言, 脚本化语言,用于面向简单的页面场景
自主检测可以在编译期间自主发现并纠正错误运行时,执行报错
运行流程先被浏览器编译成js语言在浏览器可以直接使用
复杂性支持模块化、接口、泛型-

强类型语言和弱类型语言

强类型语言:强类型语言不允许改变变量的数据类型,除非进行强制类型转换。
弱类型语言:定义与强类型语言相反,一个变量可以被赋予不同数据类型的值。PHP、Ruby、Python。

动态语言和静态语言

静态语言:编译时确定变量的数据类型,运行期间不可以改变其结构,比如C++、Java、C#。
动态语言:运行时才确定数据结构和类型,变量使用之前不需要类型声明,比如:JavaScript、PHP、Ruby、Python。

2.TS基础类型和语法

2.1 boolean、string、number、array、null、undefined

// es
let isEnable = false;
let className = 'zhaowa';
let classNum = 2;
let u = undefined;
let n = null;
let classArr = ['basic', 'execute'];

// TS 
let isEnable: boolean = false;
let className: string = 'zhaowa';
let classNum: number = 2;
let u: undefined = undefined;
let n: null = null;

// 两种方式: 统一方式 & <>方式
let classArr: string[] = ['basic', 'execute'];
let classArr: Array<string> = ['basic', 'execute'];

不能更改它们的变量类型,否则会直接报错。错误示范:

let myName: String = "zhangsan";
myName = true;//Error:Type 'true' is not assignable to type 'String'.

2.2 tuple - 元组

元组:每一个元素有独立的类型。允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let tupleType: [string, boolean] = ['basic', false];

2.3 枚举

枚举主要分为数字类型枚举、字符串类型枚举、异构。
语法:enum + 变量名,意思是定义了一个叫做“变量名”的枚举类型
优点:以为一组数值赋予友好的名字。

  1. 数字枚举
    含义:枚举成员都是number类型,如果没有对枚举对象中的枚举成员赋值,那么会默认从枚举对象中的第一个枚举成员的值是0,并依次递增。
    特点
  2. 反应映射
  3. 枚举成员的值可以不用初始化
// 数字型枚举 - 默认从0开始,依次递增
enum Score {
   BAD,  // 0
   NG,   // 1
   GOOD,  // 2
   PERFECT, // 3
}

let score: Score = Score.BAD;

// 字符串类型枚举
enum Score {
   BAD = 'BAD',
   NG = 'NG',
   GOOD = 'GOOD',
   PERFECT = 'PERFECT',
}

// 反向映射
enum Score {
   BAD,  // 0
   NG,   // 1
   GOOD,  // 2
   PERFECT, // 3
}

let scoreName = Score[0]; // BAD
let scoreVale = Score["BAD"]; // 0

// 异构
enum Enum {
   A,        // 0
   B,        // 1
   C = 'C',
   D = 'D',
   E = 8,
   F,        // 9
}

// 面试题:异构类型每一项的枚举值 => js本质实现(手写一个异构枚举的实现)
let Enum;
(function (Enum) {
   // 正向
   Enum["A"] = 0;
   Enum["B"] = 1;
   Enum["C"] = "C";
   Enum["D"] = "D";
   Enum["E"] = 8;
   Enum["F"] = 9;

   // 逆向
   Enum[0] = "A";
   Enum[1] = "B";
   Enum[8] = "E";
   Enum[9] = "F";
})(Enum || (Enum = {}))

2.4 any、unknow、void、nerver

// any - 绕过所有类型检查 => 类型检测和编译筛查取消
let anyValue: any = 123;

anyValue = "anyValue";
anyValue = false;
let value1: boolean = anyValue;

// unknown -绕过赋值检查 => 禁止更改传递
let unknownValue: unknown;

unknownValue = true;
unknownValue = 123;
unknownValue = "unknownValue";

let value1: unknown = unknownValue; // OK
let value2: any = unknownValue;  // OK
let value3: boolean = unknownValue; // NOK

// void - 声明返回为空
function voidFunction(): void {
    console.log('void');
}

// never - 永不能执行完 or 永远error
function errorGen(msg: string): never {
    throw new Error(msg);
}

function infiniteLoop(): never {
    while(true) {
        // 业务逻辑
    }
}

2.5 any和unknown区别

any类型:表示任意类型,即可以赋值给任何类型的变量。使用any类型后,该变量可以进行任何操作而不会触发类型检查,相当于关闭了类型检查器的所有限制。

unknown类型:表示未知类型,即不确定具体类型的变量。与any不同,使用unknown类型后,该变量在没有进行类型检查或类型断言之前,不能被赋值给其他变量或进行任何操作。

使用方式:

  1. any类型的使用:在需要灵活处理类型的场景下,可以使用any类型。例如,当从第三方库中获取数据时,由于其类型无法确定,可以将其声明为any类型以避免类型错误。
  2. unknown类型的使用:当我们需要对类型进行安全的操作时,可以使用unknown类型。通常会配合类型判断(type guard)或类型断言来使用。类型判断可以帮助我们在确定类型之后,对变量进行类型转换或操作。
  3. 注意:使用any类型会绕过类型检查器的所有限制,容易引入类型错误和调试困难。为了保持代码的类型安全性,建议尽可能避免使用any类型,而是使用更精确的类型或通过类型断言将unknown类型转换为其他确定类型。

3.对象的三种状态:object/Object/{}

object - 非原始类型

用来描述后天创建类型的类型描述

interface ObjectConstructor {
    create(o: object | null): any;
}

const proto = {};

Object.create(proto);
Object.create(null);
Object.create(undefined); // Error

Object - 原型上的属性,一个集合

// Object.prototype 上的属性
interface Object {
    constructor: Function;
    toString(): string;
    toLocaleString(): string;
    valueOf(): Object;
}

// 定义了Object类属性
interface ObjectConstructor {
    new(value: any): Object;
    readonly prototype: Object;
}

{} - 定义空属性对象

// {} - 定义空属性对象
const obj = {};

obj.prop = "props"; // NOK
obj.toString(); // OK

二、接口 - interface

什么是接口?对行为模块的抽象,具体的行为是有类来实现

// 描述对象内容
interface Class {
    name: string;
    time: number;
}

let zhaowa: Class = {
    name: 'typescript',
    time: 2
}

// 只读
// readonly 的声明不可改,它是一个只读属性
interface Class {
    readonly name: string;
    time: number;
}

// 面试题 - JS的引用类型操作不同 
// 经常和const对比,const 创建的引用类型是可以改变的,readonly类型的声明是不可被改变的(让一个对象或者数组真正只读)
let arr: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = arr;

ro[0] = 12;         // 赋值 - Error
ro.push(5);          // 增加 - Error
ro.length = 10;         // 长度改写 - Error
arr = ro;         // 覆盖 - Error

// ==> 追问:如何拓展interface接口的可描述程度
// 任意可添加属性
interface Class {
    readonly name: string;
    time: number;
    [propName: string]: any;
}

const c1 = { name: "JS" };
const c2 = { name: "browser", time: 1 };
const c3 = { name: "ts", level: 1 };

三、交叉类型 - &

3.1 合并

把一个复杂的东西拆成较简单的小块去做独立的抽象说明。
优点:更加的灵活。

// 合并
interface A {
    inner: D;
}
interface B {
    inner: E;
}
interface C {
    inner: F;
}

interface D {
    d: boolean;
}
interface E {
    e: string;
}
interface F {
    f: number;
}

type ABC = A & B & C;

let abc: ABC = {
    inner: {
        d: false,
        e: 'className',
        f: 5
    }
};

3.2 合并冲突

interface A {
    c: string;
    d: string;
}
interface B {
    c: number;
    e: string;
}

// 声明一个类型,是A和B的合并
type AB = A & B;

let ab: AB;
c - never (永远达不到)

合并的关系是 “且”,如果两个类型不一致 则相交的结果是never。所以c的类型既是string又是number,不存在这样的类型。

四、断言

类型的声明和转换(和编译器做了一个告知交流)

4.1 尖括号形式

语法:<type>+value,尖括号中填写具体的类型。

//anyValue是any类型,在使用时候将其断言为string类型
let anyValue: any = 'hi zhaowa';
let anyLength: number = (<string>anyValue).length;

<string>:执行到这里的时候阶段性的类型是字符串,虽然声明的时候是any类型,但是执行到这个阶段的时候是string

4.2 as声明

语法:value as type,as后跟具体类型。

let anyValue: any = 'hi zhaowa';
let anyLength: number = (anyValue as string).length;

4.3 非空判断 - 只确定不是空

(不能保证执行到这里是字符串,但是能保证执行到这里不是空)
含义:非空断言用!表示,它用来断定某变量一定不是 null 和 undefined。

type ClassTime = () => number;

const start = (ClassTime: ClassTime | undefined) {
    // 业务逻辑
    // if (额外判断逻辑) {
        let time = classTime!(); // 具体类型待定,但是非空确认
    // }
}

// 面试题 在ts里可以这样去写吗,非空处理成功了吗
const tsClass: number | undefined = undefined;
const zhaowa: number = tsClass!;
console.log(zhaowa);
// 不行,因为这里使用断言无效。
// 在严格模式下会转义成
const tsClass = undefined;
const zhaowa = tsClass;
console.log(zhaowa); // undefined
//不建议在赋值的时候断言,赋值的时候可以使用逻辑功能做非空限制

4.4 断言的最高境界: 肯定断言

肯定化保证赋值

let score: number;
startClass();
console.log('' + score); // 使用前赋值

function startClass() {
    score = 5;
}

// let score!: number; - 提前告知

五、类型守卫

语法规范范围内,额外的确认
多态 - 多种状态(单个变量有多种类型)

  1. in 定义属性场景下内容的确认
    通过类型守卫实现了额外的规范化的流,不能交叉类型合并可以通过导流
interface Teacher {
    name: string;
    courses: string[];
}
interface Student {
    name: string;
    startTime: Date;
}

type Class = Teacher | Student;

function startCourse(cls: Class) {
    // 进入教室的有可能是老师 有可能是学生 
    if ('courses' in cls) {
        console.log("Courses:" + cls.courses);
    }
    if ('startTime' in cls) {
        console.log("startTime:" + cls.startTime);
    }
}
  1. typeof / instanceof - 类型分类场景下的身份确认
    通过类型判断:string进来是学生,(返回为good,perfect),number进来是老师,(返回为 1-5分)
// typeof 判断
function class(name: string, score: string | number) {
    if (typeof score === "number") {
        return "teacher:" + name + ":" + score;
    }
    if (typeof score === "string") {
        return "student:" + name + ":" + score;
    }
}
// instanceof 判断
const getName = (cls: Class) => {
    if(cls instanceof Teacher) {
        return cls.courses;
    }
    if(cls instanceof Student) {
        return cls.startTime;
    }
}
  1. 自定义类型
// cls是teacher或student中的一种,当前这个函数是用来判断它是不是老师
const isTeacher = function (cls: Teacher | Student): cls is Teacher {
    return 'courses' in cls; 
}

const getName = (cls: Teacher | Student) => {
    if(isTeacher(cls)) {
        return cls.courses;
    }
}
// 类型别名 & 联合类型

六、TS进阶

6.1 函数重载

6.1.1 什么是函数重载

指在一个作用域中,可以定义多个同名函数,但它们的参数类型或参数个数不同。在调用这个同名函数时,编译器会根据不同的参数类型或参数个数来确定具体调用哪个函数。

class Course {
        start(name: number, score: number): number;
        start(name: string, score: string): string;
        start(name: string, score: number): string;
        start(name: number, score: string): string;
        start(name: Combinable, score: Combinable) {
            if (typeof name === 'string' || typeof score === 'string') {
                return 'student:' + name + ':' + score; 
            }
        }
    }
// Combinable:多种类型的重载
// 对实际数据的类型做匹配
    const course = new Course();
    course.start('yunyin', 5);
6.1.2 为什么需要函数重载

提高代码的可读性和可维护性。为同一个功能定义多个重载函数,可以使函数名具有更强的表达力,让代码更加直观和易懂。
提供更灵活的参数选择,使函数使用更加方便。

6.2 泛型

6.2.1 泛型有什么用?

当我们定义一个变量不确定类型的时候有两种解决方式:
1、使用any
缺点:无法获取函数返回值类型,也失去了ts类型保护的优势
2、使用泛型
泛型指的是在定义函数/接口/类型时,不预先指定具体的类型,而是在使用的时候指定类线限制的一种特性

让模块可以支持多种类型数据 - 让类型声明和值一样,可以被赋值和传递

  function startClass <T, U>(name: T, score: U) : T {
        return name + score;
    }

    console.log(startClass<String, Number>("yunyin", 5));

    // T、U、K - 键值、V - 值、E - 节点、元素

6.3 装饰器

装饰器:一个设计模式的概念,不局限于语言,在任何的实例上去嵌套一个通用的能力。

一个壳:给函数、 类、 类的属性或方法,套了一个通用的壳。

// 配置:tsc --target ES5 --experimentalDecorators
    // "experimentalDecorators": true

    // 类装饰器
    function Zhaowa(target: Function): void {
        target.prototype.startClass = function(): void {
            // 通用功能
        }
    }

    // 属性装饰器
    function propsWrapper(target: Object, key: string) {
        // 属性的统一操作
        Object.defineProperty(target, key, {
        })
    }

    // 方法装饰器 - target: Object, propertyKey: string, descriptor: TypePropertyDescript

    @Zhaowa
    class Course {
        constructor() {
            // 业务逻辑
        }

        @propsWrapper
        public name: string;

        @methodDec
        
    }


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值