# TypeScript

# 1. 变量的类型

# 1.1 有哪些类型

强类型语言,JavaScript的超集,

  • boolean
  • number(没有整型和浮点型的区分)
  • string
  • array
  • tuple(元组,数组的一种)
  • enum(枚举类型)
  • any(任意类型)
  • null && undefined (其他类型的子类型)
  • void(常用于定义方法的时候,方法没有返回值)
  • never(其他类型,代表从不会出现的值,never类型只能被never类型所赋值)
let isSleepy: boolean = true;
let num: number = 123;
let str: string = "This is why we play.";

# 1.2 数组

// 两种方法定义数组
let arr1: string[] = ["Duncan", "James", "Allen"];
let arr2: Array<number> = [4, 8, 12, 16];
let arr3: Array<any> = [12, "Range"];

# 1.3 元组

// 元组,数组的一种,指定多种类型
let tp: [string, number, boolean] = ["range", 12, false];

# 1.4 枚举类型

// 枚举类型,主要用于标识状态/固定值
enum Flag {success = 1, error = -1}

let f: Flag = Flag.error;
console.log(f) // -1

enum Color {red, blue, orange = 9, green, purple}

let c1: Color = Color.blue; // 默认值,1
let c2: Color = Color.green // 以上一个值为基准,10

# 2. 函数

# 2.1 函数声明

// 函数声明
function run(): string {
  return 'run';
}

# 2.2 匿名函数

// 匿名函数
let func = function (): number {
  return 2043;
};

# 2.3 定义方法传参

// 定义方法传参
function getInfo(name: string, age?: number): string {
  // age? 表示该参数为可选参数,并且可选参数必须放在参数的末尾。
  if (!age) {
    return `${name} --- age: Secret`;
  } else {
    return `${name} --- ${age}`;
  }
}

let myName: string = 'range';
let myAge: number = 20;
console.log(getInfo(myName, myAge));

# 2.4 默认参数

// 设置默认参数
function getName(name: string = 'range') {
  return name;
}
console.log(getName());

# 2.5 剩余参数

// 剩余参数
// 三点运算符接收形参传过来的值
function sum(a: number, ...numbers: number[]): number {
  let ans = 0;
  numbers.forEach(number => {
    ans += number;
  });
  return ans * a;
}

console.log(sum(2, 3, 4, 2043));

# 2.6 函数重载

  • 函数的重载,相同的方法,不同的参数,实现不同的功能
  • 为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用
  • 为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
function getInfo(name: string): string;
function getInfo(age: number): number;

// 注意这里的 any 并不是重载列表的一部分
function getInfo(str: any): any {
  if (typeof str === 'string') {
    return 'my name is ' + str;
  } else {
    return 'my age is ' + str;
  }
}

console.log(getInfo('range'));

# 3. 类

# 3.1 类的创建

// 类的创建
class Person {
  // 字段
  name: string;

  // 构造函数
  constructor(name: string) {
    this.name = name;
  }

  // 方法
  getName():string {
    return this.name
  }
  setName(name:string):void {
    this.name = name;
  }
}

let p = new Person('range');
console.log(p.getName());
p.setName("sirius");
console.log(p.getName())

# 3.2 类的继承

class Person {
  name: string;

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

  run(): string {
    return `${this.name} is running.`;
  }
}

class Boy extends Person {
  sex: string;

  constructor(name: string) {
    // super表示调用父类的构造函数
    super(name);
    this.sex = 'male';
  }

  boyRun(): string {
    return `A boy named ${this.name} is running.`;
  }
}

let b = new Boy('duncan');
console.log(b.run());
console.log(b.boyRun());

# 3.3 类的修饰符

  • public:公有,类的内外、子类都可以访问
  • protected:保护,类和子类可以访问,外部无法访问
  • private:私有,只有类的内部可以访问
  • 如果属性不加修饰符,则默认公有
class Person {
  protected name: string;

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

  run(): string {
    return `${this.name} is running.`;
  }
}

class Boy extends Person {
  public sex: string;

  constructor(name: string) {
    super(name);
    this.sex = 'male';
  }

  boyRun(): string {
    return `The boy named ${this.name} is running.`;
  }
}

let boy = new Boy('range');
console.log(boy.boyRun());

# 3.4 静态属性和静态方法

// ES5 中的静态属性和静态方法
function Person(name) {
  this.name = name;
}
Person.age = 2043;		// 静态属性
Person.getAge = function () {  // 静态方法
  return this.age;
}

console.log(`Person.age: ${Person.age}`)
console.log(`Person.getAge(): ${Person.getAge()}`)
// TypeScript中的静态属性和静态方法
class Person {
  public name: string;
  // 静态属性
  static age: number = 22;
  static hobby: string;

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

  yield() {
    console.log('HHHHHi!');
  }

  // 静态方法
  static printAge() {
    // console.log(this.name);  // 报错,静态方法中只能调用静态属性
    console.log(this.age);
  }
}

console.log(Person.age)
Person.printAge();

# 3.6 多态

定义:多态是指,父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现。多态属于继承,是继承的一种表现。

// 多态
class Animal {
  public name: string;

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

  eat() {
    // 让子类去实现具体的操作,并且不同子类,操作不同
  }
}

class Cat extends Animal {
  constructor(name:string) {
    super(name);
  }
  eat():string {
    return `The cat ${this.name} is eating fish.`
  }
}

class Dog extends Animal {
  constructor(name:string) {
    super(name);
  }
  eat():string {
    return `The dog ${this.name} is eating bone.`
  }
}

# 3.7 抽象类和抽象方法

  • 抽象类:提供其他类继承的基类,不能直接被实例化。
  • 抽象方法:只能存在于抽象类中,不包含具体的实现,并且必须在派生类中实现。
  • 用于定义标准
abstract class Animal {
  public name: string;

  // 这里有IDE提示:"abstract class constructor can be made protected."
  constructor(name: string) {
    this.name = name;
  }

  abstract eat(): any;
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  eat() {
    return `The dog ${this.name} is eating.`;
  }
}

let d = new Dog("black");
console.log(d.eat());

# 4. 接口

# 4.1 属性类接口

// 属性类接口:对传入的参数进行约束
interface FullName {
  firstName: string;  // 注意分号,不是逗号
  lastName: string;
}

function printName(name: FullName) {
  console.log(`${name.firstName}·${name.lastName}`);
}

// 限制传入的对象
// 如果传入的是字面量,则严格要求,不允许多余的对象属性
// 如果传入的是对象,除了必须包含的属性以外,允许有多余的属性存在
// 以及,接口的约束是两方面的:
// 一方面约束传入的参数
// 另一方面,在传入的参数是对象的时候,允许对象中包含多余的属性,但是并不允许函数显式使用这些多余的属性。
let obj = {
  firstName: 'Tim',
  lastName: 'Duncan',
  pos: 'Power Forward'
};
printName({lastName: 'Town', firstName: 'Evan'});
printName(obj);

# 4.2 可选接口属性

// 接口可选属性
interface FullName {
  firstName: string;
  lastName?: string;
}

function getName(name: FullName) {
  console.log(name);
}

getName({
  firstName: "Tony",
  lastName: "parker"
});

let obj = {
  firstName: "Manu",
  // lastName: "Ginobili",
  pos: "Shooting Guard"
};

getName(obj);
// 举例:原生JS封装AJAX接口
interface Config {
  type: string;
  url: string;
  data?: string;
  dataType: string;
}

function ajax(config: Config) {
  let xhr = new XMLHttpRequest();
  xhr.open(config.type, config.url, true);
  xhr.send(config.data);
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      console.log('Success');
      if (config.dataType === 'json') {
        console.log(JSON.parse(xhr.responseText));;
      } else {
        console.log(xhr.responseText);
      }
    }
  };
}

# 4.3 函数类型接口

// 加密的函数类型接口
interface encrypt {
  (key: string, value: string): string;
}

let md5: encrypt = function (key: string, value: string): string {
  return `000${key}0010${value}1011001`;
};

let sha1: encrypt = function (key, value) {
  return key + value;
};

console.log(md5('key', 'value'));

# 4.4 可索引接口

// 数组的约束
interface UserArr {
  [index: number]: string;
}

let arr: UserArr = ['range', 'ray'];

// 对象的约束
interface UserObj {
  [index: string]: string;
}

let obj:UserObj = {
  name: 'Range',
  pos: 'Point Guard'
}

# 4.5 类的类型接口

interface Animal {
  name: string;

  eat(str: string): void;
}

// 注意这里的 implements
class Dog implements Animal {
  name: string;

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

  eat() {
    console.log(`The dog ${this.name} is eating bone.`);
  }
}

let dog = new Dog('range');
dog.eat();

# 4.6 接口的继承

// 接口的继承
interface Animal {
  eat(): void;
}

interface Person extends Animal {
  work(): void;
}

class Student {
  public name: string;

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

  study() {
    console.log(`The student ${this.name} is doing homework.`);
  }
}

class Boy extends Student implements Person {
  constructor(name: string) {
    super(name);
  }

  eat() {
    console.log(`This boy ${this.name} is eating rice.`);
  }

  work() {
    console.log(`This boy ${this.name} is working.`);
  }
}

let boy = new Boy('Ray');
boy.eat();
boy.work();
boy.study();

# 5. 泛型

# 5.1 基本使用

// 泛型是用来解决类、接口、方法的可复用性,以及对不特定数据类型的支持(类型校验)。
// 希望可以同时支持 string类型 和 number类型
// 传入什么类型,就返回什么类型
// T表示泛型
function getData<T>(value:T):T {
  return value;
}

getData<number>(123);

# 5.2 类的泛型

class MinClass<T> {
  public list: T[] = [];

  add(num: T): void {
    this.list.push(num);
  }

  min(): T {
    let minNum = this.list[0];
    for (let i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i];
      }
    }
    return minNum;
  }
}

let m = new MinClass<number>();
m.add(523);
m.add(619);
m.add(2043);
console.log(m.min());

# 5.3 函数类型接口

interface ConfigFn {
  (value1: string, value2 :string):string;
}

let setData:ConfigFn = function (value1:string, value2:string) :string {
  return value1 + value2;
}

# 5.4 泛型接口

// 第一种定义泛型接口的方法
interface ConfigFn {
  <T>(value: T): T;
}

let getData: ConfigFn = function <T>(value: T): T {
  return value;
};

getData<string>('Ray');

// 第二种定义泛型接口的方法
interface ConfigFn<T> {
  (value: T): T;
}

function getData<T>(value: T): T {
  return value;
}

let myGetData:ConfigFn<string> = getData;

console.log(myGetData('Ray'));

# 5.5 实例

// 1.不使用泛型
class User {
  username: string | undefined;
  password: string | undefined;
}

class MySqlDB {
  // 把类作为参数,来约束传入的参数的数据类型
  add(user: User): boolean {
    console.log(user)
    return true;
  }
}

let user = new User();
user.username = 'Ray';
user.password = '2043';

let db = new MySqlDB();
db.add(user);

// 2.使用泛型
class User {
  username: string | undefined;
  password: string | undefined;
}

class MySqlDB<T> {
  add(user: T): boolean {
    console.log(user);
    return true;
  }
}

let user = new User();
user.username = 'Ray';
user.password = '2043';
let hacker = {
  name: 'Ray',
  age: 24
}

let db = new MySqlDB<User>();
db.add(user);

# 6. 模块

# 6.1 模块的暴露和引入

// 暴露
let dbUrl = 'https://202.43.21.1';

export function getData():void {
}

export function save():void {
}

// 或者
export {getData, save, dbUrl};

// 或者
export default getData;
// 引入
import {getData, save} from "./modules/db";
// 或者
import {getData as get, save} from "./modules/db";
// 或者
import get from './modules/db';

# 6.2 命名空间

// 命名空间
namespace A {
  export class Dog {
    name:string;
    constructor(name:string) {
      this.name = name;
    }
    eat():string {
      return `namespace A, dog ${this.name} is eating.`;
    }
  }
}

namespace B {
  export class Dog {
    name:string;
    constructor(name:string) {
      this.name = name;
    }
    eat():string {
      return `namespace B, dog ${this.name} is eating.`
    }
  }
}

let dogA = new A.Dog('A');
let dogB = new B.Dog('B');
console.log(dogA.eat());
console.log(dogB.eat());

# 7. 装饰器

# 7.1 概念

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法,属性或参数上,可以修改类的行为。
  • 通俗一点,装饰器是一个方法,可以注入到类、方法、属性参数上,从而实现扩展类、属性、方法、参数的功能。
  • "非侵入式的行为修改。"
  • 常见的装饰器类型:
    • 类装饰器
    • 属性装饰器
    • 方法装饰器
    • 参数装饰器
  • 使用场景举例:
    • 某一个函数被很多人用,当我们想要对这个函数执行耗时统计的时候,为了不影响其他人使用,我们自己加上一个耗时统计的装饰器,当不需要计时的时候,删除装饰器即可。

# 7.2 类装饰器

# 7.2.1 普通装饰器

// 类装饰器:普通装饰器(无法传参)
function logClass(params: any) {
  // params指代的就是当前的类,也就是被装饰的类
  console.log(params);
  params.prototype.apiUrl = 'www.art.com';
  params.prototype.run = function () {
    console.log('comes from function run()');
  };
}

@logClass
class HttpClient {
  constructor() {
  }

  getData() {
  }
}

let http: any = new HttpClient();
console.log(http.apiUrl);
http.run();

# 7.2.2 装饰器工厂

// 类装饰器:装饰器工厂(可传参)
function logClass(params: string) {
  // 注意这里的params不再是被装饰的类,而是传给装饰器的参数
  return function (target: any) {
    // 这里面的target才是被装饰的类
    console.log(target);
    console.log(params);
    target.prototype.apiUrl = `apiUrl: ${params}`;
  };
}

@logClass('https://www.art.com')
class HttpClient {
  constructor() {
  }

  getData() {
  }
}

let http: any = new HttpClient();
console.log(http.apiUrl);

# 7.2.3 重载构造函数

// 类装饰器:类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
// 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明

function logClass(target: any) {
  return class extends target {
    // 其实这里不是完全的替换,而是做了一个类的继承
    apiUrl: any = "apiURL in decorator.";
    getData() {
      console.log(`Decorator: ${this.apiUrl}`);
    }
  };
}

@logClass
class HttpClient {
  public apiUrl: string | undefined;

  constructor() {
    this.apiUrl = 'apiURL in constructor.';
  }

  getData() {
    console.log(`Constructor: ${this.apiUrl}`);
  }
}

let http = new HttpClient();
console.log(http.apiUrl)
http.getData();

# 7.3 属性装饰器

// 对于被装饰的类,即使是没有实例化一个实例,装饰器中的代码也会被执行
function logProperty(params: any) {
  return function (target: any, attr: any) {
    console.log(target);
    console.log(attr);
    // target[attr] = params;
    // 但是不能写成 target.attr = params;
    // 而是要写成 target.url = params;
  };
}

// @logClass('Nothing')
class HttpClient {
  @logProperty('Ray')
  public url: any | undefined;

  // 这里的 url 就是一个属性

  constructor() {
  }

  getData() {
    console.log(this.url);
  }
}

let http: any = new HttpClient();
http.getData();

# 7.4 方法装饰器

// 方法装饰器

// 它会被应用到方法的属性描述符上,可以用于监视、修改、替换方法定义
// 方法装饰器会在运行时传入下列3个参数:
// 1.类的构造函数
// 2.成员的名字
// 3.成员的属性描述符: writable, enumerable, configurable ...

function logMethods(params: any) {
  return function (target: any, methodName: any, methodDesc: any) {
    console.log(target);     // 原型对象
    console.log(methodName); // 方法名称
    console.log(methodDesc); // 方法描述
    console.log(methodDesc.value); // 被装饰的方法

    let oMethod = methodDesc.value;
    methodDesc.value = function (...args:any[]) {
      console.log('Method has been changed by decorator. XD')
      oMethod.apply(this, args)
    }

    target.apiUrl = 'apiUrl_apiUrl';
    target.run = function () {
      console.log('Running... QAQ');
    };
  };
}

class HttpClient {
  public url: any | undefined;

  constructor() {
  }

  @logMethods('https://www.art.com')
  getData(str:string) {
    console.log(`${str}, getting data... TAT`);
  }
}

let http: any = new HttpClient();
http.run();
console.log(http.apiUrl);
http.getData('Hey');

# 7.5 参数装饰器

// 方法参数装饰器

// 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:
// 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
// 2.方法的名字
// 3.参数在函数参数列表中的索引。

function logParams(params: any) {
  return function (target: any, methodName: any, paramsIndex: any) {
    console.log(params);
    console.log(target);
    console.log(methodName);
    console.log(paramsIndex);
  };
}

class HttpClient {
  public url: any | undefined;

  constructor() {
  }

  getData(@logParams('decorator_uuid') uuid: any) {
    console.log(`Methods in getData. uuid: ${uuid}`);
  }
}

let http = new HttpClient();
http.getData(2043);

# 10.一些自己的思考

  1. 强类型语言,但仅仅是编程时候,或者说是提供了一种js下强类型的编程范式,但是也可以不遵守
  2. 因为最终ts的代码是被转化为es5的js代码,然后被浏览器执行的,所以非要说的话,其实本质上并没有发生改变
  3. 即使IDE/tsLint可以检查出ts中的语法错误,但仍然是可以执行的,这些都是由于TS基于JS,而JS的本质是弱类型语言以及JS的一些其他的特定,决定了运行时的一些效果。
  4. 重构代码的过程中,至于性能还有待测试,但是避免了在类型上出错,同时这种面向对象的编程思想,也增强了代码的可读性,对于大型项目而言降低了维护成本,虽然重构的时候ESLint搞得人很痛苦。