TypeScript-学习之路

对比JS

  1. js比较简单、易用,但同时容易出现问题,存在安全隐患
  2. js后期维护比较困难,不适合开发大型项目,面向对象写的比较麻烦(是写在构造函数中还是写在原型中)
  3. 有值类型的概念但是没有变量类型的概念
//变量动态类型
let age = 10;
age = '10';
age = true;
  • 安全问题:当需要a的类型为数字进行运算时,我们亦可以改变a的类型为字符串,在运算时就会发生错误(字符串拼接),同时这个问题不会报错,因此会产生很大的问题。
  • 同时函数的参数也没有类型,在传入参数时没有限制,后续使用会造成代码冗余或难以维护(第3行写的代码,但是错误出现在第30000行)
  1. 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 类名();
继承
  • 类从基类中继承了属性和方法。
  • 派生类通常被称作 子类,基类通常被称作 超类。
  1. 方法重写 :子类中添加了父类中相同的方法,则子类方法会覆盖掉父类的方法

  2. 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
  • 相同点
  1. 都可以描述一个对象或函数
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;
  1. 都允许拓展(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; 
}
  • 不同点
  1. type可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
  1. type可以使用typeof获取实例的类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type divType = typeof div
  1. interface能够声明合并
interface User {
  name: string
  age: number
}
interface User {
  sex: string
}
/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

类型断言

  • 手动指定一个值的类型
  • 语法:
value as type
//或
<type>value
  • 用途:
  1. 将一个联合类型断言为其中一个类型
//将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方法,导致运行错误。
  1. 将一个父类断言为更加具体的子类
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;
}
  1. 将任何一个类型断言为any
    • 当需要在window对象上临时添加一个属性foo时,ts编译会报错,提示window上不存在foo属性,因此当我们非常确定类型时,可以将window临时断言为any类型
//window.foo = 1;
(window as any).foo = 1;

泛型

  1. 在定义函数、接口或类时,类型不明确时可以使用泛型,在使用的时候再指定类型
function identity <T>(value: T) : T {
  return value;
}
console.log(identity<Number>(1)) // 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; };

什么时候使用泛型? 当函数、接口、类要处理多种数据类型时/ 当函数、接口、类在多个地方使用该数据类型时

泛型约束

  1. 确保属性存在
    在函数内部使用泛型变量时,由于不知道它是哪种类型的,所以不能随意的操作它的属性或方法
  • 当处理字符串或数组时,通常都会假设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%

总结一下:

  1. 写了工厂函数, 从上到下 依次执行,求得装饰器函数。
  2. 装饰器函数的执行顺序是 从下到上 依次执行。

类装饰器

作用在类声明上的装饰器,可以给我们改变类的机会。在执行装饰器函数时,会把类构造函数传递给装饰器函数。

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 对应的值。
在改写的函数中增加逻辑,并执行原来保留的原函数。注意原函数要用 callapplythis 指向原型对象。

属性装饰器

作用在类中定义的属性上,这些属性不是原型上的属性,而是通过类实例化得到的实例对象上的属性。
装饰器同样会接受两个参数,原型对象,和属性名。而没有属性描述对象,为什么呢?这与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的文件被当成一个模块,反之,全局可见

导出

  1. 导出声明
    • 变量、函数、类、类型别名、接口都可通过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);
    }
}
  1. 导出语句
    • 对导出的部分重命名
class C implements A {
    isAcceptable(s: string) {
        return s.length === 5 && B.test(s);
    }
}
export { C };
export { C as D };
  1. 重新导出
export class E {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
    }
}
// 导出原先的验证器但做了重命名
export {C as F} from "./C";
//一个模块中有多个模块,同时导入
export * from "./StringValidator"; 

导入

  1. 导入模块中的某个内容
import { C as G } from './C';
let c = new G();
  1. 将整个模块导入到一个变量
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 声明(含有子属性的)全局对象
  • interfacetype 声明全局类型

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,
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值