TypeScript学习笔记

TypeScript学习笔记


文章目录

一、什么是TypeScript

TypeScipt是JavaScript的超集,它在JavaScript的基础上添加了静态类型定义。它无法被直接执行,需编译成js文件执行。(可以在任何支持js的平台中执行)它支持JavaScript的所有语法。

因为JavaScript的弱类型和没有命名控件 导致代码很难模块化开发

  • TS 引入“类”概念
  • TS 支持类型定义,能在编码期间检测类型错误,为开发人员创建了更高校奥的编码和调试过程
  • TS 引入模块概念,可以把声明、数据、函数和类封装在模块中
  • TS 重构更加容易、便捷

二、如何安装TypeScript

安装TypeScript解释器 需要先安装node.js

// 全局安装typescript
npm install -g typescript
// 安装完成后查询版本
tsc -v

202207251639240

三、正式开始学习

1、基本类型

基础类型:StringNumberBooleannullundefined 以及 ES6SymbolES10BigInt

1-1 字符串类型 string
// 字符类型定义
let str1:string = "HelloWord!"; // 字符串赋值
let str2:string = `TypeScript ${str1}`; // 支持ES6模板语法赋值
console.log(str1);
console.log(str2);

// 如果赋值的值为其他类型 将会报错 Type 'number' is not assignable to type 'string'
// 其他类型等同
let str:string = 123;
1-2 数值类型 number
// 支持NaN、十六进制、十进制、八进制和二进制
let notANumber: number = NaN;//Nan
let num: number = 123;//普通数字
let infinityNumber: number = Infinity;//无穷大
let decimal: number = 6;//十进制
let hex: number = 0xf00d;//十六进制
let binary: number = 0b1010;//二进制
let octal: number = 0o744;//八进制
1-3 布尔类型 boolean
let flag1:boolean = true; // 布尔类型赋值
let flag2:boolean = false; // 布尔类型赋值
let flag3:boolean = Boolean(1); // 布尔类型赋值
let flag4:boolean = Boolean(0); // 布尔类型赋值
let flag5:boolean = new Boolean().valueOf(); // 注意 new Boolean 得到的是布尔对象 无法直接赋值,需要通过valueOf()获取值
console.log(flag1); // flag1 :>>  true
console.log(flag2); // flag2 :>>  false
console.log(flag3); // flag3 :>>  true
console.log(flag4); // flag4 :>>  false
console.log(flag5); // flag5 :>>  false
1-4 空值类型 null、undefined、void
let unde:undefined = undefined;
let nul:null = null;
// void 可以声明 undefined、null还有function
let voidUnde:void = undefined;
let voidNul:void = null;

function funName():void {
    return ""; // 在声明返回类型为void后如果返回带有值将无法通过编译
}
function funName():void {
	return; // 这种情况可以
}

这里有需要注意的点 声明void类型的属性不能被当作子属性赋值给其他属性

但是undefined和null是所有类型的子类型 可以被赋值

let voidUnde:void = undefined;
let str:string = "str";
str = voidUnde; // void声明的属性voidUnde不能当作子属性赋值 报错信息:Type 'void' is not assignable to type 'string'
let unde:undefined = undefined;
let nul:null = null;
str = unde; // 允许赋值
str = nul; // 允许赋值
// 但是实际开发中 undefined和null值 应该尽量避免被使用
// 为了避免编译错误 我们将进行判断值是否存在
str = unde || "||"; // 验证空字符串 它像三元表达式一样 但是它只能验证字符串 
// 在TypeScript3.7版本之后 可以使用 ?? 来替代 || 它可以验证 number、boolean
str = unde ?? "??";
// 也可以使用三元表达式
str = unde ? "true" : "false";

2、任意类型

2-1 any类型

any类型就像原生js一样 不进行任何类型检查 什么类型都可以赋值

let any:any = "str";
any = 123;
any = true;
any = [];
any = {};
2-2 unknown类型
// 和any一样什么类型都可以赋值
let unknown:unknown = "";
unknown = 123;
unknown = true;
unknown = [];
unknown = {};
2-3 any和unknown的区别
  • any可以为其他类型赋值 unknown不行
  • any可以使用任意值的属性和函数 unknown不行
  • unknown 只能给同类型和any赋值
  • unknown 相比较于any更加安全
let any:any = {name: "张三", anyFunction() {
	console.log("anyFunction");
}};
let unknown:unknown = {name: "李四", unknownFunction() {
	console.log("unknownFunction");
}};

console.log(any.name); // 输出 张三
console.log(unknown.name); // 编译报错 error TS2571: Object is of type 'unknown'

any.anyFunction(); // 调用成功
unknown.unknownFunction(); // 编译报错 error TS2571: Object is of type 'unknown'

let str:string = "";
str = any; // any可以为任意类型赋值
console.log(str); // 输出 { name: '张三', anyFunction: [Function: anyFunction] }

// unknown 不能为除了any个unknown以为的其他类型赋值
str = unknown; // 编译报错 error TS2322: Type 'unknown' is not assignable to type 'string'

any = unknown; // 可以赋值
unknown = unknown; // 可以赋值
unknown = any; // 可以赋值

3、对象、接口类型

3-1 interface定义对象

在TypeScript中我们定义对象需要使用interface关键字来约束对象,可以理解为一个格式模板。

也可以像java中的接口类一样理解 实现类必须实现接口类中定义的属性

// 定义一个对象模板
interface Preson {
	// 属性名: 属性类型
	name: string,
	aeg: number,
	sex: string
}

// 声明对象
let zhangsan:Preson = {
	name: "张三",
	aeg: 18
};
// 这种情况下会编译报错 error TS2741: Property 'sex' is missing in type '{ name: string; aeg: number; }' but required in type 'Preson'
// 缺少了 sex 的属性需要声明

// 正确的声明一个Preson对象
let zhangsan:Preson = {
	name: "张三",
	aeg: 18,
	sex: "男"
};// interface定义的对象模板中定义了什么属性 在声明对象时也就必须要定义该属性 一家人就要整整齐齐才行
3-2 对象的任意属性 [propName: string]
// 定义一个对象模板
interface Preson {
	name: string,
	[propName: string]: any // 定义任意属性
}

// 声明对象
let zhangsan:Preson = {
	name: "张三",
	aeg: 18,
	sex: "男",
	phone: 1234567889011
};
// 定义任意属性后可以自行声明非interface声明的属性 不限制数量 但限制类型
3-3 对象属性修饰符
3-3-1 可选修饰符 " ? "
// 当然也有意外 例如我有一个 非必要属性 那我可以通过 `?` 来声明
interface Preson {
	name: string,
	email?: string // 属性名后面跟 ? 来标识 非必要属性 可选可不选
}
// 声明对象
let zhangsan:Preson = {
	name: "张三"
};
3-3-2 只读修饰符 readonly
interface Preson {
	readonly name: string, // readonly 修饰符修饰的属性 只能在构建对象时赋值
	email?: string // 属性名后面跟 ? 来标识 非必要属性 可选可不选
}
let zhangsan:Preson = {
	name: "张三"
};
zhangsan.name = "王五"; 
// 非构建对象时赋值 编译报错 error TS2540: Cannot assign to 'name' because it is a read-only property
// name属性为只读属性 无法被操作
3-4 对象类型拓展

在我们已定义的对象的基础上定义一个新的对象,使其即拥有原本属性的同时还能拥有自己定义的属性。这样可以避免代码冗余,提高效率。

3-4-1 类型继承 extends

interface声明的对象模板可以通过 extends 关键字进行继承其它定义的对象,可以同时集成多个对象。

interface Role {
    roleName: string
}
interface skills {
	skillName: string
}
interface Preson extends Role{ // 或者多继承 extends Role,skills
	readonly name: string,
	[propName: string]: any
}

// 必须实现所有interface定义的属性
let preson:Preson = {
    name: "张三",
    roleName: "管理员",
    // skillName: "TS",
	aeg: 18
};
3-4-2 类型交叉 &

通过type关键字给一个对象类型起别名时,使用 & 符号来连接多个类型,从而产生一个新类型,新的类型包含所有其它对象类型的属性,即类型交叉。

(啥?你还不知道 type 关键字是啥!那你点这里)

[(啥?你还不知道 type 关键字是啥!那你点这里)](# 10-2 类型别名)

type newPrson = Preson & Role & skills // Preson & Role & skills 不知道的往上面看
let newA:newPrson = {
	name: "", roleName: "", skillName: ""
}
3-4-3 泛型对象

泛型:使用尖括号<>来声明类型参数 (可以有多个)来表示暂时未知的类型,在实际声明变量时传入相应的类型 (或者由TS自动推论) 来替换相应出现该类型参数的地方,从而将抽象的、未知的类型替换为具体的、已知的类型。一个类型参数指代一种类型,例如<T,K,U,…>分别指代一种暂时未知的类型。将泛型用于定义对象类型,便得到了泛型对象。

interface Preson<T> {
	name: T
}
let person: Preson<string> = {
    name: "张三"
}

4、数组类型

4-1 类型[]定义
let arr1: number[] = [2020, 10, 8];
let arr2: string[] = ["2020", "加油", "!"];
let arr3: any[] = [2020, "加油", true];
// 就类似于定义普通类型一样跟一个[]
console.log(arr1,arr2,arr3);
4-2 Array<>泛型数组定义
let arr1: Array<number> = [2020, 10, 8];
let arr2: Array<string> = ["2020", "加油", "!"];
let arr3: Array<any> = [2020, "加油", true];
// 同上
console.log(arr1,arr2,arr3);
4-3 多维度数组定义
let arr1: string[][] = [["2020", "加油", "!"],["2020", "加油", "!"]];
let arr2: string[][][] = [[["2020", "加油", "!"],["2020", "加油", "!"]],[["2020", "加油", "!"],["2020", "加油", "!"]]];
// 就以此类推
// 泛型定义多维数组
let arr1: Array<Array<string>> = [["2020", "加油", "!"],["2020", "加油", "!"]];
let arr2: Array<Array<Array<string>>> = [[["2020", "加油", "!"],["2020", "加油", "!"]],[["2020", "加油", "!"],["2020", "加油", "!"]]];
4-4 ReadonlyArray 只读数组

只读数组是TS提供的一种特殊数组,它的所有元素不能被修改

  • 不能使用push,pop等会改变自身元素的函数
  • 可以使用slice、map、indexOf等不改变自身元素的函数
  • 定义时需要规定数组类型,它是一个泛型
  • 可以被同类型数组(含普通数组) 重新赋值,但是普通数组不能被只读数组赋值
// 定义一个readonly数组
let arr:ReadonlyArray<string> = ["2022", "TypeScript", "学习中..."];

// 例:使用push函数 增加一个新元素
arr.push("新的元素"); // 编译报错 error TS2339: Property 'push' does not exist on type 'readonly string[]'

// 例:使用不改变自身的函数 并返回一个新函数
let newArr = arr.map(item => item);

// 可以被普通类型 和 同类型赋值
arr = newArr; 
let arrs:ReadonlyArray<string> = ["2022", "加油"];
arr = arrs;

// newArr 是普通数组
newArr = arrs; // 只读数组不能为普通数组赋值 
// 编译报错 error TS4104: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'
4-5 Tuple Types 元组

元组(Tuple)是一个默认长度固定的数组。与普通数组最大的不同在元组中的元素类型可以是不同的,且数量固定

// 元组类型
// 定义时 规定每个元素的类型 规定几个定义几个 超出报错
let arr: [string, number] = ["Fankyt", 2022]
// 为元组增加越界元素 后续push新元素到元组中 会自动判断类型是否匹配 string|number
// 元组的类型判定改为 联合类型 满足联合类型即可
arr.push("加油")
console.log(arr)

5、函数类型

5-1 定义函数参数的类型

使用函数时传递参数也可以限制参数的类型,用法与声明变量一致

也可以使用接口规定参数类型

function info(name:string, age:number) {
    console.log(name, age)
}
info("Fankyt", 22); // 传递的参数类型必须与参数列表一致

// 接口规定参数类型 ============================================================================
interface Preson{
    name: string,
    age: number
    sex?: number
}
function info(user:Preson) {
    console.log(user)
}
info({
    name: "Fankyt",age: 22,sex: 1
});
5-2 函数的可选参数与默认值

有时函数参数列表中的参数并非必须时,可以使用可选修饰符修饰,可以赋予参数默认值(可选参数不能赋予默认值)

function info(name:string, age?:number) {
    console.log(name, age)
}
info("Fankyt");

function info(name:string = "Fankyt", age:number = 22) {
    console.log(name, age)
}
5-3 函数返回类型

函数返回值的类型类型也可以进行规定只要在函数的()后跟 :string 就可以规定函数必须且只能返回string类型

function info(user:Preson):Preson {
    return user;
}
let user = info({
    name: "Fankyt",age: 22,sex: 1
});
console.log("user:>>>", user)

6、联合类型、类型断言

6-1联合类型修饰符 " | "
// 一般我们存储性别 可能存储 0 1 也可能存储 true false 也可能直接存储 男 女
// 那我们请求到的数据我们也不能确认是什么类型 那我们就可以使用联合类型 参数类型必须符合其中一项
let sex:number | string | boolean = "男";
let sex:number | string | boolean = 1;
let sex:number | string | boolean = true;

// 在函数中也可以使用
function info(sex: string | boolean) {
    console.log(sex)
}
6-2 类型断言 " as "
interface A {
       run: string
}
 
interface B {
       build: string
}
 
const fn = (type: A | B): string => {
       return (type as A).run
}
// 可以使用类型断言来推断他传入的是A接口的值
// 类型断言只能保证不会编译错误 属性不存在 它实际的值还是undefined

7、 内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

7-1 ECMAScript 的内置对象
// Boolean、Number、string、RegExp、Date、Error
let b: Boolean = new Boolean(1)
console.log(b)
let n: Number = new Number(true)
console.log(n)
let s: String = new String('哔哩哔哩关注小满zs')
console.log(s)
let d: Date = new Date()
console.log(d)
let r: RegExp = /^1/
console.log(r)
let e: Error = new Error("error!")
console.log(e)
7-2 DOM 和 BOM 的内置对象

DocumentHTMLElementEventNodeList

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
    
});
//dom元素的映射表
interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "applet": HTMLAppletElement;
    "area": HTMLAreaElement;
    "article": HTMLElement;
    "aside": HTMLElement;
    "audio": HTMLAudioElement;
    "b": HTMLElement;
    "base": HTMLBaseElement;
    "bdi": HTMLElement;
    "bdo": HTMLElement;
    "blockquote": HTMLQuoteElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "canvas": HTMLCanvasElement;
    "caption": HTMLTableCaptionElement;
    "cite": HTMLElement;
    "code": HTMLElement;
    "col": HTMLTableColElement;
    "colgroup": HTMLTableColElement;
    "data": HTMLDataElement;
    "datalist": HTMLDataListElement;
    "dd": HTMLElement;
    "del": HTMLModElement;
    "details": HTMLDetailsElement;
    "dfn": HTMLElement;
    "dialog": HTMLDialogElement;
    "dir": HTMLDirectoryElement;
    "div": HTMLDivElement;
    "dl": HTMLDListElement;
    "dt": HTMLElement;
    "em": HTMLElement;
    "embed": HTMLEmbedElement;
    "fieldset": HTMLFieldSetElement;
    "figcaption": HTMLElement;
    "figure": HTMLElement;
    "font": HTMLFontElement;
    "footer": HTMLElement;
    "form": HTMLFormElement;
    "frame": HTMLFrameElement;
    "frameset": HTMLFrameSetElement;
    "h1": HTMLHeadingElement;
    "h2": HTMLHeadingElement;
    "h3": HTMLHeadingElement;
    "h4": HTMLHeadingElement;
    "h5": HTMLHeadingElement;
    "h6": HTMLHeadingElement;
    "head": HTMLHeadElement;
    "header": HTMLElement;
    "hgroup": HTMLElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "i": HTMLElement;
    "iframe": HTMLIFrameElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "ins": HTMLModElement;
    "kbd": HTMLElement;
    "label": HTMLLabelElement;
    "legend": HTMLLegendElement;
    "li": HTMLLIElement;
    "link": HTMLLinkElement;
    "main": HTMLElement;
    "map": HTMLMapElement;
    "mark": HTMLElement;
    "marquee": HTMLMarqueeElement;
    "menu": HTMLMenuElement;
    "meta": HTMLMetaElement;
    "meter": HTMLMeterElement;
    "nav": HTMLElement;
    "noscript": HTMLElement;
    "object": HTMLObjectElement;
    "ol": HTMLOListElement;
    "optgroup": HTMLOptGroupElement;
    "option": HTMLOptionElement;
    "output": HTMLOutputElement;
    "p": HTMLParagraphElement;
    "param": HTMLParamElement;
    "picture": HTMLPictureElement;
    "pre": HTMLPreElement;
    "progress": HTMLProgressElement;
    "q": HTMLQuoteElement;
    "rp": HTMLElement;
    "rt": HTMLElement;
    "ruby": HTMLElement;
    "s": HTMLElement;
    "samp": HTMLElement;
    "script": HTMLScriptElement;
    "section": HTMLElement;
    "select": HTMLSelectElement;
    "slot": HTMLSlotElement;
    "small": HTMLElement;
    "source": HTMLSourceElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "sub": HTMLElement;
    "summary": HTMLElement;
    "sup": HTMLElement;
    "table": HTMLTableElement;
    "tbody": HTMLTableSectionElement;
    "td": HTMLTableDataCellElement;
    "template": HTMLTemplateElement;
    "textarea": HTMLTextAreaElement;
    "tfoot": HTMLTableSectionElement;
    "th": HTMLTableHeaderCellElement;
    "thead": HTMLTableSectionElement;
    "time": HTMLTimeElement;
    "title": HTMLTitleElement;
    "tr": HTMLTableRowElement;
    "track": HTMLTrackElement;
    "u": HTMLElement;
    "ul": HTMLUListElement;
    "var": HTMLElement;
    "video": HTMLVideoElement;
    "wbr": HTMLElement;
}

8、Class 类 与 抽象类

8-1 Class类

class这个概念以及用法都很像JavaClass,这也是es6为了更接近传统语言的写法,让对象的结构更加清晰,更接近面向对象的语法。

// 定义一个class
class Car {
    // 定义属性 这里有实例属性与静态属性
    // 属性修饰符 public private protected
    // public 允许所有实例访问
    // private 仅允许自身访问
    // protected 仅允许自身及其子类访问
    carName: string
    public weightThe: number
    private lengthThe: number
    protected high: number
    // 定义静态属性
    static factory: string = "MadeInChina"

    // 定义构造函数 构造函数只能有一个 不能重载
    constructor(carName: string, weightThe: number, lengthThe: number, high: number) {
        this.carName = carName;
        this.weightThe = weightThe;
        this.lengthThe = lengthThe;
        this.high = high;
    }

    // 定义实例函数
    getCarParmams(): Car {
        return this;
    }

    // 定义静态函数
    static getFactory(): string {
        return Car.factory;
    }
}

let car = new Car("笨池",12,42,12);
console.log(car.getCarParmams())
console.log(Car.getFactory())
8-2 抽象类

抽象类使用 abstract 关键字修饰 它就像一个模板,有点类似 [interface](# 3-1 interface定义对象) 接口一样。但是抽象类中定义的抽象函数必须在继承时进行重写

abstract class Preson {
    name: string

    constructor(name: string) {
        this.name = name;
    }

    getName(): string {
        return this.name;
    }
}

// 直接new一个抽象类 将会编译报错 error TS2511: Cannot create an instance of an abstract class.
let preson = new Preson();


// 需要被继承使用
class Man extends Preson {
    constructor(name:string) {
        super(name);
    }
}
let man = new Man();
man.getName()

// 如果定义了抽象函数就必须被重写
abstract class Preson {
    abstract sayFankytNB(): void
}

// 如果没有重写 会编译报错  
// error TS2515: Non-abstract class 'Man' does not implement inherited abstract member 'sayFankytNB' from class 'Preson'
// 非抽象类 Man 没有实现继承的抽象函数 sayFankytNB 继承自 Preson
class Man extends Preson {
    sayFankytNB(): void {
        console.log("Fankyt牛逼")
    }
}

let man = new Man();
man.sayFankytNB()
8-3 类实现接口
interface 次元 {
    次元名称: string
}

interface 动漫 {
    动漫名称: string
}

class 动漫角色 implements 次元, 动漫 {
    动漫角色名称: string
    // 实现接口 必须显示实现其元素 不懂看上面接口的内容
    动漫名称: string
    次元名称: string

    constructor(动漫角色名称: string, 动漫名称: string, 次元名称: string) {
        this.动漫角色名称 = 动漫角色名称
        this.动漫名称 = 动漫名称
        this.次元名称 = 次元名称
    }
}

let 路飞 = new 动漫角色("路飞", "海贼王", "二次元")
console.log(路飞)
// 输出 动漫角色 { '动漫角色名称': '路飞', '动漫名称': '海贼王', '次元名称': '二次元' }

9、枚举类型

// 枚举类型 ts增加了枚举类 通过enum 关键字定义
enum Names {
    // key - value
    ZhangSan = 1,
    LiSi = 1,
    WangWu = 2
    // 没设置 value 将会使用number 递增
    // 可以 手动设置 value 值
    // 手动设置 value类型
}
// 通过value去获取key 只能为number类型时可以使用
// 如果同时有多个value相同时 ts 无法推断 将返回undefined
console.log(Names[0]);
console.log(Names.LiSi);
console.log(Names.WangWu);

10、类型推论 | 类型别名

10-1 类型推论是个啥?

在定义变量的时候没有指定变量的类型,将会通过赋予的值类型推断变量的类型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YtjPwl1g-1658738812471)(C:\Users\Fankyt\Home\笔记\typescript\img\image-20220718095713950.png)]

10-2 类型别名

故名思意 给类型定义一个别名,有时候觉得写string类型好长 不想写就起个比较短的别名。

// 给单个类型取别名
type s = string;
let str:s = "字符串";

联合类型别名

type sn = string | number
let strnb: sn = "str";
strnb = 666;

自变量类型别名

// 这个有点像枚举
type enu = "男" | "女"
let sex:enu = "男"; // 必须为类型中的一个值
console.log(sex);

混合使用

type hh = string | 666 | boolean
let hhl:hh = 666;
hhl = false;
hhl = "混合了"

11、never类型

TypeScript中 用never来标识响应为空(不存在/必定必会有结果) 并且抛出异常(只要记住 never修饰 必须抛出一个异常就对了)

简单来说never需要响应一个异常类(这里的响应不是指 returnthrow

// never 修饰函数 必须响应一个异常
function sayHello(): never {
    throw new Error("手动抛出异常");
    // return "正常返回"; // return 返回参数编译报错 Type 'string' is not assignable to type 'never'
}

sayHello();

12、命名空间

为了避免全局变量污染,TypeScript提供了namespace用来防止污染问题。

想要在其他文件使用命名空间 通过 import {xx} from "url";

12-1 定义一个命名空间
// 命名空间中的属性 需要通过export导出才可以被使用
namespace A{
    export let str:string = "str";
}
console.log(A.str);
12-2 重名命名空间

如果出现重名命名空间,将会和interface一样进行合并,但是需要注意重名命名空间中出现重名变量(合并重名命名空间不允许存在重名变量)

// 命名空间
namespace A{
    export let b:string = "bstr";
}
// 重名命名空间中 重名属性报错 Cannot redeclare block-scoped variable 'b'
namespace A{
    // export const b:number = 123;
    export let name:string = "Fankyt";
}

console.log(A.b);
console.log(A.name);
12-3嵌套命名空间
namespace A{
    export let a:string = "a";
    // 嵌套命名空间 也需要export 才能使用
    export namespace B{
        export let b:string = "b";
    }
}
console.log(A.B.b);

13、Mixins混入

13-1 对象混入

将多个对象进行混合 构建一个新的对象

// mixins
let obj_1 = {
    name: "Fankyt"
}
let obj_2 = {
    age: 22
}
// 对象混入
let new_obj = Object.assign(obj_1, obj_2);
console.log(new_obj);
13-2 类混入

为对象引入附加的方法或属性,从而达到修改对象结构的目的,即为 mixin.

ts很骚 class 可以被 impl 实现 从而实现多类混入

这里ts通过类混 可以实现类似aop的方案(代理模式) 案例:

// 类混入 通过aop来理解使用
class Start_log {
    getStartTime() {
        console.log("启动时间");
        console.log(new Date());
    }
}

class End_log {
    getEndTime() {
        console.log("结束时间");
        console.log(new Date());
    }
}

//进行混入
class Preson {
    // 定义自己的事情
    name: string;
    age: number;
    money: number = 100;

    constructor(name: string, age: number, money: number) {
        this.name = name;
        this.age = age;
        this.money = money;
    }

    shopping() {
        console.log(`购买商品消费:${this.money}`)
    }
}

class shoppingProxy implements Preson, Start_log, End_log {
    age: number;
    money: number;
    name: string;

    constructor(name: string, age: number, money: number) {
        this.name = name;
        this.age = age;
        this.money = 200;
    }

    buy() {
        this.getStartTime();
        setTimeout(()=>{
            console.log(this.name)
            this.shopping();
            this.getEndTime();
        },2000);
    }

    getEndTime(): void {
    }

    getStartTime(): void {
    }

    shopping(): void {
    }

}

// 混入函数
function mixins(tar: any, arr: Array<any>) {
    arr.forEach(item => {
        Object.getOwnPropertyNames(item.prototype).forEach(nameItem => {
            // 即将目标函数重构实现 constructor不会被重构
            tar.prototype[nameItem] = item.prototype[nameItem];
        })
    })
}

mixins(shoppingProxy, [Preson, Start_log, End_log]);

let shopping = new shoppingProxy("FANKYT", 22,200);
shopping.buy();

14、装饰器

14-1 什么是装饰器?

ES2015 进入 Class 之后,当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也就是装饰器被提出的一个很重要的原因。但是 ECMAScript 的装饰器提案到现在还没有定案,所以来学习一下TypeScript中的装饰器。不过,在 TS 中,装饰器仍然是一项实验性特性,未来可能有所改变,所以如果要使用装饰器,需要在 tsconfig.json 的编译配置中开启experimentalDecorators,将它设为 true

装饰器(decorator)的主要作用是给一个已有的方法或类扩展一些行为,而是去直接修改本身

不能用在声明文件中(.d.ts),也不能用在任何外部上下文中

14-2 开启 [tsconfig.json 配置](# 四、TypeScript配置)
{
	"compilerOptions":{
		"experimentalDecorators": true
	}
}
14-3 类装饰器
  • 定义类的装饰器函数时,必须且只能接收一个参数,类的构造函数将作为实参传给装饰器
  • 类装饰器虽然是紧跟着类的声明,但实际上类装饰器是作用于类的构造函数的
  • 类装饰器在运行时被当作函数执行,并且会把类的构造函数作为装饰器的唯一参数
  • 类装饰器可用于监视,修改或者替换类的定义
// 定义装饰器
const cDecorator: ClassDecorator = (target: Function) => {
    // target 就是使用了装饰器的类的构造函数
    target.prototype.hello = () => {
        console.log("hello")
    }
}
@cDecorator
class App {}
const app = new App();
(app as any).hello();
14-4 函数装饰器
  • 方法装饰器必须紧跟着方法的声明,且声明在方法之前
  • 方法装饰器也不能用在重载函数中
  • 方法装饰器在运行时也是会被当做函数运行,但与类装饰器不同的是:方法装饰器在运行时会接收3个参数:
    • a).target:对于静态成员来说是类的构造函数,对于实例成员则是类的原型对象 xxx.prototype
    • b).propertyKey(string):成员的名字(一般是方法名)
    • c).descriptor(PropertyDescriptor):成员的属性描述(如果代码输出目标版本低于ES5,则属性描述是undefined)
  • 方法装饰器的定义一般都会定义为装饰器工厂的形式(可理解为函数柯理化的形式)
  • 方法装饰器可在不改变方法内部代码的情况下为方法增加额外的功能
// 定义函数装饰器
const mDecorator: MethodDecorator = (target, propertyKey, descriptor) => {
    // target 函数所属类
    // propertyKey 函数名
    // descriptor 函数描述
    console.log("函数装饰器");
    console.log(target,propertyKey,descriptor);
}
class App {
    @mDecorator
    get(name:string) {}
}
14-5 属性装饰器
  • 属性装饰器的声明必须紧跟着属性的声明
  • 属性装饰器不能声明在声明文件(.d.ts)或者任何外部上下文中
  • 属性装饰器在运行时会被作为函数调用
  • 属性装饰器接收2个参数:
    • 1.target:对于静态成员是类的构造函数,对于实例成员是类的原型对象
    • 2.propertyKey(string):成员的名字(属性名)
  • 属性装饰器没有第三个参数,也就是说属性描述不会作为参数传给属性装饰器
// 定义属性装饰器
const propDecorator: PropertyDecorator = (target, propertyKey) => {
    console.log("属性装饰器")
    console.log(target,propertyKey)
}
class App {
    @propDecorator
    name:string = "Fankyt";
}
14-6 参数装饰器
  • 参数装饰器必须紧挨着参数
  • 参数装饰器不能声明在声明文件(.d.ts)或任何外部上下文中
  • 参数装饰器在运行时会被作为函数调用
  • 参数装饰器接收3个参数:
    • target:对于静态成员是类的构造函数,对于实例成员是类的原型对象
    • propertyKey(string):成员名称(参数名)
    • index(number):参数在函数参数列表中的索引
  • 参数装饰器用来并且只能用来监视一个方法的参数是否被传入、
// 定义参数装饰器
const parmDecorator:ParameterDecorator = (target, propertyKey, parameterIndex) => {
    // target 对象
    // propertyKey 参数名称
    // parameterIndex 参数位置索引
    console.log("参数装饰器")
    console.log(target,propertyKey,parameterIndex)
}
class App {
    get( @parmDecorator name:string) {}
}
完整示例代码
// 装饰器
// 定义类装饰器
const cDecorator: ClassDecorator = (target: Function) => {
    console.log("类装饰器")
    // target 就是使用了装饰器的类的构造函数
    target.prototype.hello = () => {
        target.prototype.name = "Fankyt";
        console.log("hello==>>>"+target.prototype.name)
    }
}
// 定义函数装饰器
const mDecorator: MethodDecorator = (target, propertyKey, descriptor) => {
    // target 函数所属类
    // propertyKey 函数名
    // descriptor 函数描述
    console.log("函数装饰器");
    console.log(target,propertyKey,descriptor);
}
// 定义属性装饰器
const propDecorator: PropertyDecorator = (target, propertyKey) => {
    console.log("属性装饰器")
    console.log(target,propertyKey)
}
// 定义参数装饰器
const parmDecorator:ParameterDecorator = (target, propertyKey, parameterIndex) => {
    // target 对象
    // propertyKey 参数名称
    // parameterIndex 参数位置索引
    console.log("参数装饰器")
    console.log(target,propertyKey,parameterIndex)
}
@cDecorator
class App {
    @propDecorator
    name:string = "Fankyt";
    @mDecorator
    get(@parmDecorator name:string) {

    }
}
const app = new App();
(app as any).hello();

// 执行顺序:属性装饰器-》参数装饰器-》函数装饰器-》类装饰器

四、TypeScript配置

在TypeScript工程中 可以在tsconfig.json配置对其进行配置

// 生成tsconfig.json文件
tsc --init

配置文件详解(百度一堆)


"compilerOptions": {
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
  "diagnostics": true, // 打印诊断信息 
  "target": "ES5", // 目标语言的版本
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JS,JSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释 
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}
 
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
   "src/**/*"
],
// 指定一个排除列表(include的反向操作)
 "exclude": [
   "demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
 "files": [
   "demo.ts"
]

介绍几个常用的

1.include
指定编译文件默认是编译当前目录下所有的ts文件

2.exclude
指定排除的文件

3.target
指定编译js 的版本例如es5 es6

4.allowJS
是否允许编译js文件

5.removeComments
是否在编译过程中删除文件中的注释

6.rootDir
编译文件的目录

7.outDir
输出的目录

8.sourceMap
代码源文件

9.strict
严格模式

10.module
默认common.js 可选es6模式 amd umd 等
搬砖地址:https://blog.csdn.net/qq1195566313/article/details/122525099
示不报错)
“noFallthroughCasesInSwitch”: true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
“noImplicitReturns”: true, //每个分支都会有返回值
“esModuleInterop”: true, // 允许export=导出,由import from 导入
“allowUmdGlobalAccess”: true, // 允许在模块中全局变量的方式访问umd模块
“moduleResolution”: “node”, // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
“baseUrl”: “./”, // 解析非相对模块的基地址,默认是当前目录
“paths”: { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
“jquery”: [“node_modules/jquery/dist/jquery.min.js”]
},
“rootDirs”: [“src”,“out”], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
“listEmittedFiles”: true, // 打印输出文件
“listFiles”: true// 打印编译的文件(包括引用的声明文件)
}

// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
“include”: [
“src/**/*”
],
// 指定一个排除列表(include的反向操作)
“exclude”: [
“demo.ts”
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
“files”: [
“demo.ts”
]


> 介绍几个常用的
>
>1.include
>指定编译文件默认是编译当前目录下所有的ts文件
>
>2.exclude
>指定排除的文件
>
>3.target
>指定编译js 的版本例如es5  es6
>
>4.allowJS
>是否允许编译js文件
>
>5.removeComments
>是否在编译过程中删除文件中的注释
>
>6.rootDir
>编译文件的目录
>
>7.outDir
>输出的目录
>
>8.sourceMap
>代码源文件
>
>9.strict
>严格模式
>
>10.module
>默认common.js  可选es6模式 amd  umd 等
>搬砖地址:https://blog.csdn.net/qq1195566313/article/details/122525099
  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值