对比JS
- js比较简单、易用,但同时容易出现问题,存在
安全隐患
- js后期
维护比较困难
,不适合开发大型项目,面向对象写的比较麻烦(是写在构造函数中还是写在原型中) - 有值类型的概念但是没有变量类型的概念
//变量动态类型
let age = 10;
age = '10';
age = true;
安全问题
:当需要a的类型为数字进行运算时,我们亦可以改变a的类型为字符串,在运算时就会发生错误(字符串拼接),同时这个问题不会报错,因此会产生很大的问题。- 同时函数的参数也没有类型,在
传入参数时没有限制
,后续使用会造成代码冗余或难以维护(第3行写的代码,但是错误出现在第30000行)
- TS – 微软
- 以js为基础构建的语言,js有的ts都有,是js的超集,对js 进行了扩展
引入类型的概念
:声明时初始化的变量类型后期不能更改- 可支持任何js的平台来执行,不能被js解析器直接执行,
需要经过编译
,最终执行还是js - 增加的
新特性
:抽象类、接口、工具、装饰器 - 丰富的配置选项:可被编译成任意类型的es版本,可解决兼容性问题
- 编辑代码时的提示的由来:ts对变量进行了约束,帮助编辑器识别变量的类型
开发环境搭建流程
1.安装Node.js
- 64位:https://nodejs.org/dist/v14.15.1/node-v14.15.1-x64.msl
2.使用npm全局安装typescript
- cpm install -g typescript
3.用ts写node.js(node不是内置对象的一部分,需要引入第三方声明文件)
- npm install @type/node --save-dev
4.使用tsc对ts文件进行编译
- 执行命令:tsc xxx.ts
基础知识
基本类型
类型声明
- 指定TS中变量(参数、形参)的类型
- 当为变量赋值时,TS编译器会
自动检查
值是否符合类型声明,符合则赋值,否则报错
let 变量 : 类型;
function fn(参数: 类型,参数: 类型): 类型 {
...
}
let age: number = 10;
age = 20;
age = 'hello'; //报错
function sum(first: number, second: number): number {
return frist + second;
}
sum( 123 , "456") //“456”报错:类型“string”的参数不能赋给类型“number”的参数。
sum( 123 ) //参数数量不对也会报错
自动类型判断
- 对变量的声明和赋值是同时进行的,会自动判断变量的类型
(即 可省略掉类型声明)
let isShow = false;
- 虽然报错,但是依旧会编译为js文件,后续可配置使其不编译
类型
number、string、boolean、字面量、any、unknown、void、never、object、array、tuple、enum
- 字面量 :
//字面量,类似于限制为常量
let stuId : 10;
stuId = 10;
stuId = 11; //报错
//使用|来链接多个类型(联合类型)
let gender: 'male' | 'female';
gender = 'male' ;
gender = 'female';
- object :对象
let b: {name: string,age?: number};//可指定属性的类型,?表示可选属性
当赋值时不确定有多少个属性时:[propName: string]表示任意类型的属性
let c: {name: string, [propName: string]: any};
c = {name: "猪八戒",age: 18, gender: '男'}
使用箭头函数时设置函数结构的类型声明:
(形参: 类型,形参: 类型,...)=> 返回值类型
let d: (a: number, b: number)=>number;
d = function (n1: number, n2: number): number {
return n1 + n2
}
-
any :任意类型,变量可以任意赋值,相当于对此变量关闭了TS的类型检查
(不建议使用)
- 声明变量时如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any),可以赋值给其他变量,即会影响其他变量的变量检查
- 区别object:只允许赋任意值,但不能在object上调用任意的方法
let notSure;
notSure = 12;
notSure.toFixed();
let isSure: string;
isSure = notSure;
let myObject: object;
myObject.toFixed(); //出现错误
- unknown :表示未知类型的值,区别any:不可直接赋值给其他变量
(不会影响到其他的变量的类型检查)
- 进行变量检查:
//1.
let e: unknown;
e = 12;
let s: string;
if(typeof e === "string") {
s = e;
}
//2.类型断言:告诉解析器变量的实际类型
s = e as string;
s = <string>e;
- void :与any相反,表示空,没有任何类型,函数没有返回值时它的返回值类型为void
function fn(): void {
...
}
//声明一个void类型的变量,赋值只能是undefined或null
let unusable: void = undefined;
- never :函数表达式或箭头函数表达式永远不会返回结果或抛出异常;任何其他类型的值都不能赋值给这个类型的变量
function fn(): never {
throw new Error('报错了!')
}
- Array:合并了
相同类型
的对象(两种方式)
let e: string[];
let f: Array<number>;
- tuple元组:合并了
不同类型
的对象,固定长度的数组(TS新增)
let g: [string, number, string];
g = ['hello', 12, 'bye'];
- enum枚举:罗列所有可能的值(TS新增)
enum Gender {
male = 0,
female = 1
}
let figure : {name: string, gender: Gender};
figure = {
name: '孙悟空',
gender: Gender.male
}
- 异构枚举:混合字符串和数字
- `const`枚举(减少开销):只能使用常量枚举表达式,在编译阶段会被删除,其成员在使用的地方会被内联进来
const enum Directions {
Up,
Down,
Left,
Right
}
- 外部枚举:用来声明已经存在的枚举类型,避免出现编译错误
高级类型
1.交叉类型:多个类型组合在一起得到一个新的类型 A & B
2.联合类型:多个类型中的其中一个 A | B
3.条件类型:
只有当给出具体条件之后才能进行条件推断出类型结果,例如:
当只知道函数f的类型范围,并不能明确指定它的类型时,只有当调用f时,才能知道具体的类型结果
declare function f<T extends boolean>(x: T): T extends true ? string : number;
// Type is 'string | number'
let x = f(Math.random() < 0.5)
// Type is 'number'
let y = f(false)
// Type is 'string'
let z =f(true)
什么是条件类型?
T extends U ? X : Y
类似于三元运算符,当T可以赋值给U时,类型为X,否则类型为Y
interface Foo {
propA: boolean;
propB: boolean;
}
declare function f<T>(x: T): T extends Foo ? string : number;
function foo<U>(x: U) {
// 因为 ”x“ 未知,因此判断条件不足,不能确定条件分支,推迟条件判断直到 ”x“ 明确,
// 推迟过程中,”a“ 的类型为分支条件类型组成的联合类型,
// string | number
let a = f(x);
}
-
分布式条件类型?
条件类型里待检查的类型必须是裸类型- 裸类型:类型参数没有被包装在其他类型里,比如没有被数组、元组、函数、Promise等包裹,简而言之裸类型就是未经过任何其他类型修饰或包装的类型。
// 裸类型参数,没有被任何其他类型包裹,即T
type NakedType<T> = T extends boolean ? "YES" : "NO"
// 类型参数被包裹的在元组内,即[T]
type WrappedType<T> = [T] extends [boolean] ? "YES" : "NO";
- 分布式?
分布式条件类型在实例化时会自动分发成联合类型
如:`T extends U ? X : Y` 使用类型参数 `A | B | C` 实例化 `T` 解析为 `(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`
区分分布式:
// 含有分布式特性的,待检查类型必须为”裸类型“
type Distributed = NakedType<number | boolean> // = NakedType<number> | NakedType<boolean> = "NO" | "YES"
// 不含有分布式特性的,待检查的类型为包装或修饰过的类型
type NotDistributed = WrappedType<number | boolean > // "NO"
应用实例:将T中的undefined和null排除
type NonNullable<T> = T extends null | undefined ? never : T
类(class)
可理解为对象的模型
定义类
class 类名 {
属性名: 类型; //实例属性需要创建实例化 实例化类名.属性名
static 类属性名: 类型; //可直接使用(静态属性) 类名.类属性名
readonly 属性名: 类型; //只读
constructor(参数: 类型) { //构造函数,在对象创建时被调用
this.属性名 = 参数; //实例方法中,this表示当前实例
};
方法名() { //实例方法 实例化类名.方法名
... //在方法中可以使用this调用当前的对象
};
static 方法名() { //类方法。 类名.类方法名
...
};
}
const 实例化类名 = new 类名();
继承
- 类从基类中继承了属性和方法。
- 派生类通常被称作 子类,基类通常被称作 超类。
-
方法重写 :子类中添加了父类中相同的方法,则子类方法会覆盖掉父类的方法
-
super :表示类的父类
Animal作为父类,子类Dog和Cat继承父类的所有属性和方法
class Animal {
name: string;
age: number;
constructor(name: string) {
this.name = name;
this.age = age;
}
sayHello() {
console.log('hi')
}
}
class Dog extends Animal {
run() {
console.log('汪汪汪')
}
sayHello() { //重写sayHello方法
console.log('hello')
}
}
class Cat extends Animal {
console.log('喵喵喵');
super.sayHello(); //super表示类的父类
//如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
constructor(name: string, age: number) {
super(name); //调用用父类的构造函数
this.age = age;
}
属性封装
1.在类中定义的属性可以任意被修改,导致对象中的数据变得不安全
- public:公共的,任意位置访问(修改)
- private:私有的,只能自己访问(修改)
- protected:受保护的,只能自己或派生类访问(修改)
- readonly:只读属性,必须在声明时或构造函数内被初始化
2.构造函数中创建参数时在前面添加一个访问限定符来声明
3.存取器,通过getter/setter来控制对对象成员的访问
class userInfor {
constructor(private _name: string,private _age: number) {
}
//getter,setter属性存取器
get name() {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
const myInfor = new userInfor('孙悟空', 500);
console.log(myInfor)
抽象类
- 不能用来创建对象,是用来被继承的类
- 抽象方法只能定义在抽象类中,没有方法体,派生类必须对抽象方法进行重写
abstract class Animal {
constructor(public name: string){
};
abstract seyHello(): void; //必须在派生类中实现
}
class Monkey extends Animal {
constructor() {
super('Monkey') //在派生类的构造函数中必须调用super()
}
seyHello(): void {
console.log('Monkey is so cute');
}
sayBye(): void {
console.log('bye~')
}
}
let monkey: Animal; //允许创建一个对抽象类型的引用
//monkey = new Animal(); //错误:抽象类不能创建实例
monkey = new Monkey(); //允许对抽象子类进行实例化
monkey.sayBye(); //错误: 方法在声明的抽象类中不存在
接口
- 用来明确类中应该包含哪些属性和方法,同时可以当成类型声明去使用
- 在定义类时限制类的结构,不考虑实际值
- 接口内的所有属性都不能有实际的值,所有的方法都是抽象方法
可选属性(?)
、只读属性(readonly)
interface MyInter {
name: string;
age?: number;
}
const obj: MyInter = {
name: 'sss',
age: 111
}
//在定义类时,可以使用类来实现一个接口,只对实例部分进行类型检查。constructor存在于类的静态部分,不在检查的范围内
class MyClass implements MyInter {
name: string;
constructor(name:string) {
this.name = name;
}
}
额外类型检查
- 当传入函数的参数拼写出现错误时,会经过额外属性检查,如何绕开这个属性检查:
1.接口中定义任意属性[propName: string]: any;
2.将对象字面量赋值给另一个变量
//函数中使用接口
interface MyInter {
name: string;
age?: number;
}
function userInfor(config: MyInter): {name: string; age: number} {
...
}
//该对象字面量存在userInfor中不包含放入属性时,会产生错误
//let myInfor = userInfor({ names: "tom",age: "20"});
//inforOptions不会经历额外的类型检查,编译时不会报错
let inforOptions = { names: "tom",age: "20" };
let myInfor = userInfor(inforOptions)
函数类型接口
只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean { //参数名可变,可以不指定参数类型,ts会进行推断
...
}
type:类型别名,用来定义类型
type vs interface
- 相同点
- 都可以描述一个对象或函数
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void;
}
type User = {
name: string
age: number
}
type SetUser = (name: string, age: number )=>void;
- 都允许拓展(extends)
- interface 可以继承(extend) type, type 也可以继承(&) interface
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
interface Name {
name: string;
}
type User = Name & {
age: number;
}
- 不同点
- type可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
- type可以使用typeof获取实例的类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type divType = typeof div
- interface能够声明合并
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
类型断言
- 手动指定一个值的类型
- 语法:
value as type
//或
<type>value
- 用途:
- 将一个联合类型断言为其中一个类型
//将animal断言为fish
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
//if(typeof animal.swim === 'function') 不确定类型就访问animal.swim编译时会报错
//使用类型断言,将animal断言为Fish类型
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
- 类型断言在编译时不会出错,但是运行时可能会错误
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim(); //将animal断言为Fish,隐藏了animal可能是Cat的情况
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
//编译时不会报错,但是在运行时会报错:TypeError: animal.swim is not a function
swim(tom);//传入的参数是Cat类型的变量,Cat没有swim方法,导致运行错误。
- 将一个父类断言为更加具体的子类
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
// if(typeof Error.code === 'number') 父类Error中没有code属性,直接获取会报错
//1.通过判断是否存在code属性,来判断传入的参数是不是ApiError
if(typeof (error as ApiError).code === 'number'){
return true;
}
//2.通过判断error是否为ApiError的实例,当ApiError不是一个类,而是一个接口时,接口是一个类型,而不是一个真的值,编译时会被删除,无法使用instanceof来判断
if(error instanceof ApiError) {
return true;
}
return false;
}
- 将任何一个类型断言为any
- 当需要在window对象上临时添加一个属性foo时,ts编译会报错,提示window上不存在foo属性,因此当我们非常确定类型时,可以将window临时断言为any类型
//window.foo = 1;
(window as any).foo = 1;
泛型
- 在定义函数、接口或类时,类型不明确时可以使用泛型,在使用的时候再指定类型
function identity <T>(value: T) : T {
return value;
}
console.log(identity<Number>(1)) // 1
- 在函数名后添加了
<T>
,其中T
用来指代任意输入的类型(占位符),在后面的输入value: T
和输出Array<T>
中即可使用了。
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']//不指定泛型,ts类型推断:T为string
createArray<string>(3,'x')//指定泛型
泛型接口
//定义一个泛型接口CreateArrayFunc,引入类型变量T
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
// 将CreateArrayFunc接口作为createArray函数的返回类型
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
泛型除了在接口和函数上使用之外,还可以应用在类中
泛型类
在类名后面,使用<T,…>定义任意多个类型变量
class ownNumber<T> {
value: T;
add: (x: T, y: T) => T;
}
let mynumber = new ownNumber<number>();
mynumber.value = 0;
mynumber.add = function(x, y) { return x + y; };
什么时候使用泛型? 当函数、接口、类要处理多种数据类型时/ 当函数、接口、类在多个地方使用该数据类型时
泛型约束
- 确保属性存在
在函数内部使用泛型变量时,由于不知道它是哪种类型的,所以不能随意的操作它的属性或方法
- 当处理字符串或数组时,通常都会假设
length
属性是可用的
function logging<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// T不一定包含属性length,编译时会报错
对泛型进行约束,只允许这个函数传入包含length属性的变量
interface lengthwise {
length: number;
}
function logging<T extends lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logging(7) // 编译时会报错
logging('dscadc')
2.检查对象上的键是否存在
- keyof操作符:用于获取某种类型的所有键,其返回类型是联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
结合extends约束,限制输入的属性名包含在keyof返回的联合类型中
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// K extends keyof T 确保参数key一定是对象中含有的键
使用:
enum Difficulty {
Easy,
Intermediate,
Hard
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let tsInfo = {
name: "Typescript",
supersetOf: "Javascript",
difficulty: Difficulty.Intermediate
}
let difficulty: Difficulty =
getProperty(tsInfo, 'difficulty'); // OK
let supersetOf: string =
getProperty(tsInfo, 'superset_of'); // Error
通过泛型约束,在编译阶段就可以提前发现错误,提高程序的健壮性和稳定性
泛型工具类型
1.Partial<T>
的作用就是将某个类型里的属性全部变为可选项 ?
2.Record<K extends keyof any, T>
的作用是将 K
中所有的属性的值转化为 T
类型
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" }
};
3.Pick<T, K extends keyof T>
的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
4.Exclude<T, U>
的作用是将某个类型中属于另一个的类型移除掉。
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
5.ReturnType<T>
的作用是用于获取函数 T
的返回类型。
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
装饰器
装饰器模式
在不改变对象自身的基础上,动态增加额外的职责。
优点:把对象核心职责和要装饰的功能分开
举例:一个长相平平的女孩,通过美颜功能,也可以拍出好看的照片。即通过辅助的装饰功能,但真实的长相是不变的,照片却可以拍出多种多样的风格模样。
原理:在不改变原来代码的基础上,通过先保留原来函数,重新改写,在重写的代码中调用原来保留的函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Va79uBb8-1629882115527)(https://km.woa.com/files/photos/pictures/202107/1625719365_7_w388_h562.png)]
图中可以看出,通过一层层的包装,增加了原先对象的功能。
装饰器
装饰器可以被附加到类声明、方法、访问符(getter/setter)、属性和参数上。
使用 @expression 这种形式,expression 求值后为一个函数, **它在运行时被调用,被装饰的声明信息会被做为参数传入。**起到了以声明式方法将信息添加至已有代码的作用
使用:多个装饰器同时作用在一个声明时,可以写一行或者换行写
// 换行写
@test1
@test2
declaration
//写一行
@test1 @test2 ...
declaration
装饰器执行优先级:属性装饰器 -> 访问符装饰器 -> 方法装饰器 -> 参数装饰器 -> 类装饰器。
定义 face.ts 文件:
function thinFace() {
console.log('开启瘦脸')
}
@thinFace
class Girl {
}
编译成 js 代码,在运行时,会直接调用 thinFace 函数。这个装饰器作用在类上,称之为类装饰器。
如果需要附加多个功能,可以组合多个装饰器一起使用:
function thinFace() {
console.log('开启瘦脸')
}
function IncreasingEyes() {
console.log('增大眼睛')
}
@thinFace
@IncreasingEyes
class Girl {
}
多个装饰器组合在一起,在运行时,要注意,调用顺序是 从下至上 依次调用,正好和书写的顺序相反。例子中给出的运行结果是:
'增大眼睛'
'开启瘦脸'
如果要在一个装饰器中给类添加属性,在其他的装饰器中使用,那就要写在最后一个装饰器中,因为最后写的装饰器最先调用。
装饰器工厂
有时需要给装饰器传递一些参数,这要借助于装饰器工厂函数。装饰器工厂函数实际上就是一个高阶函数,在调用后返回一个函数,返回的函数作为装饰器函数。
function thinFace(value: string){
console.log('1-瘦脸工厂方法')
return function(){
console.log(`4-我是瘦脸的装饰器,要瘦脸${value}`)
}
}
function IncreasingEyes(value: string) {
console.log('2-增大眼睛工厂方法')
return function(){
console.log(`3-我是增大眼睛的装饰器,要${value}`)
}
}
@thinFace('50%')
@IncreasingEyes('增大一倍')
class Girl {
}
@ 符号后为调用工厂函数,依次从上到下执行,目的是求得装饰器函数。装饰器函数的运行顺序依然是从下到上依次执行。
运行的结果为:
1-瘦脸工厂方法
2-增大眼睛工厂方法
3-我是增大眼睛的装饰器,要增大一倍
4-我是瘦脸的装饰器,要瘦脸50%
总结一下:
- 写了工厂函数, 从上到下 依次执行,求得装饰器函数。
- 装饰器函数的执行顺序是 从下到上 依次执行。
类装饰器
作用在类声明上的装饰器,可以给我们改变类的机会。在执行装饰器函数时,会把类构造函数传递给装饰器函数。
function classDecorator(value: string){
return function(constructor){
console.log('接收一个构造函数')
}
}
function thinFace(constructor){
constructor.prototype.thinFaceFeature = function() {
console.log('瘦脸功能')
}
}
@thinFace
@classDecorator('类装饰器')
class Girl {}
let g = new Girl();
g.thinFaceFeature(); // '瘦脸功能'
上面的例子中,拿到传递构造函数后,就可以给构造函数原型上增加新的方法,甚至也可以继承别的类。
方法装饰器
作用在类的方法上,有静态方法和原型方法。作用在静态方法上,装饰器函数接收的是类构造函数;作用在原型方法上,装饰器函数接收的是原型对象。
方法装饰器函数有三个参数:
- target —— 当前对象的原型,也就是说,假设 Employee 是对象,那么 target 就是
Employee.prototype
- propertyKey —— 方法的名称
- descriptor —— 方法的属性描述符,即
Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
这里拿作用在原型方法上举例。
function methodDecorator(value: string, Girl){
return function(prototype, key, descriptor){
console.log('接收原型对象,装饰的属性名,属性描述符', Girl.prototype === prototype)
}
}
function thinFace(prototype, key, descriptor){
// 保留原来的方法逻辑
let originalMethod = descriptor.value;
// 改写,增加逻辑,并执行原有逻辑
descriptor.value = function(){
originalMethod.call(this); // 注意修改this的指向
console.log('开启瘦脸模式')
}
}
class Girl {
@thinFace
@methodDecorator('方式装饰器', Girl)
faceValue(){
console.log('我是原本的面目')
}
}
let g = new Girl();
g.faceValue();
从代码中可以看出,装饰器函数接收三个参数,原型对象、方法名、描述对象。
要增强功能,可以先保留原来的函数,改写描述对象的 value 为另一函数。
当使用 g.faceValue() 访问方法时,访问的就是描述对象 value 对应的值。
在改写的函数中增加逻辑,并执行原来保留的原函数。注意原函数要用 call 或 apply 将 this 指向原型对象。
属性装饰器
作用在类中定义的属性上,这些属性不是原型上的属性,而是通过类实例化得到的实例对象上的属性。
装饰器同样会接受两个参数,原型对象,和属性名。而没有属性描述对象,为什么呢?这与TypeScript是如何初始化属性装饰器的有关。 目前没有办法在定义一个原型对象的成员时描述一个实例属性。
function propertyDecorator(value: string, Girl){
return function(prototype, key){
console.log('接收原型对象,装饰的属性名,属性描述符', Girl.prototype === prototype)
}
}
function thinFace(prototype, key){
console.log(prototype, key)
}
class Girl {
@thinFace
@propertyDecorator('属性装饰器', Girl)
public age: number = 18;
}
let g = new Girl();
console.log(g.age); // 18
模块
- 模块内的函数、变量、类等在外部不可见,可使用export导出,外部引用时必须使用import导入
- 包含export或import的文件被当成一个模块,反之,全局可见
导出
- 导出声明
- 变量、函数、类、类型别名、接口都可通过export关键字导出
export interface A {
isAcceptable(s: string): boolean;
}
export const B = /^[0-9]+$/;
export class C implements A {
isAcceptable(s: string) {
return s.length === 5 && B.test(s);
}
}
- 导出语句
- 对导出的部分重命名
class C implements A {
isAcceptable(s: string) {
return s.length === 5 && B.test(s);
}
}
export { C };
export { C as D };
- 重新导出
export class E {
isAcceptable(s: string) {
return s.length === 5 && parseInt(s).toString() === s;
}
}
// 导出原先的验证器但做了重命名
export {C as F} from "./C";
//一个模块中有多个模块,同时导入
export * from "./StringValidator";
导入
- 导入模块中的某个内容
import { C as G } from './C';
let c = new G();
- 将整个模块导入到一个变量
import * as G from './C';
let c = new G.C();
export =
和 import = require()
- 为了支持CommonJS和AMD的exports,TS提供export = 语法
- export = 定义一个模块的导出对象(类、接口、命名空间、函数或枚举)
- 使用export = 导出的模块,必须使用
import module = require("module")
来导入
声明文件
- 声明文件:声明语句放到一个单独的文件(.d.ts为后缀)
- TS会解析项目中所有的*.ts文件,所有的ts文件都可以获得.d.ts的类型定义
全局变量的声明文件:
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含有子属性的)全局对象interface
和type
声明全局类型
npm包的声明文件语法:
export
导出变量export namespace
导出(含有子属性的)对象export default
ES6 默认导出export =
commonjs 导出模块export as namespace
UMD 库声明全局变量
其他声明文件
declare global
扩展全局变量declare module
扩展模块
编译选项
自动编译文件
- 编译文件时,使用-w指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译
tsc xxx.ts -w
自动编译整个项目
- 创建一个tsconfig.json文件,配置后执行tsc命令对整个项目进行编译
- 配置选项:
- include
- 定义希望编译文件所在目录
- 配置选项:
"include": ["src:/**/*","tests/**/*"]
//所有src目录和tests目录下的文件都会被编译(/**任意目录,/*任意文件)
- exclude
- 定义希望被排除在外的目录
"exclude": ["./src/hello/**/*"]
//src下hello目录下的文件不会被编译
- extends
- 定义被继承的配置文件
"extends": "./configs/base"
//当前配置文件中会自动包含config目录下base.json中的所有配置信息
- files
- 指定被编译文件的列表,只有需要编译的文件少时才会用到
"files": [
"index.ts",
"types.ts"
]
//列表中的文件都会被编译
- compilerOptions
- 编译器的选项,包含多个子选项,用来完成对编译的配置
- 项目选项
- target:设置ts代码编译的目标版本
- 可选值:ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017...
- lib:指定代码运行时所包含的库(宿主环境)
- 可选值:ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017...、DOM、WebWorker、ScriptHost...
- module:设置编译后代码使用的模块化系统
- 可选值:CommonJS、UMD、AMD、System、ES2020、ESNext、None
- outDir:编译后文件的所在目录,默认情况下编译后的js文件和ts文件位于相同的目录,设置outDir后可改变编译后文件的位置
- outFile:将代码合并为一个文件(此时module模块标准只能是AMD或System)
- allowJs:是否对js文件进行编译,默认为false
- checkJs:检查js代码是否符合符合规范,默认为false
- removeComments:是否编译注释,默认为true
- noEmit:不生成编译后的文件
- noEmitOnError:当有错误时不生成编译后的文件
- strict:所有的严格检查的总开关
- alwaysStrict:编译后的文件是否使用严格模式,默认false
- noImplicitThis:不允许不明确类型的this
- noImplicitAny:不允许隐式的any类型
- strictNullChecks:严格的检查空值
"compilerOptions": {
"target": "ES6",
"lib": ["ES6","DOM"],
"outDir": "dist",
"outFile": "dist/aa.js",
"module": "CommonJS",
"allowJs": false,
"checkJs": false,
"removeComments": true,
"noEmit": true,
"noEmitOnError": true,
"alwaysStrict": false,
"noImplicitThis": false,
"noImplicitAny":true,
}