乐趣区

关于typescript:TypeScript丨进阶2

Typescript 长处之一:加强的 oo,能够用更多设计模式,IoC,AOP…

class

js 中,应用函数和基于原型的继承来创立可重用的组件
es6 中,JavaScript 程序员将可能应用基于类的面向对象的形式
ts 中,容许开发者当初应用 class 更多的个性【public,private,protected…】,而不须要等到下个 JavaScript 版本

在构造函数里拜访 this 的属性之前,咱们 肯定要调用 super()。这个是 TypeScript 强制执行的一条重要规定。

js 中,生成实例对象的传统办法是通过构造函数
function Dog(name) {this.name = name;}
Dog.prototype.voice = () => {console.log("wang wang wang");
};
const dog = new Dog("yello dog");
dog.voice();
// 输入:wang wang wang
ts 中,咱们能够应用更多的个性,更好的 oo 编程

一个例子形容所有个性【public / private / protected / readonly / static / 存取器[get/set] / 继承 / 重写】

/*
  基类 / 超类
 */
class Animal {
  // 构造函数初始化赋值的两种写法

  // 1.
  // private what: string;
  // constructor(what: string) {
  //   this.what = what;
  // }

  // 2.
  constructor(private what: string) {}

  public eat() {console.log("i am an animal,i eat");
  }
}

/*
  派生类 / 子类
  ts 规定子类必须要在构造函数写 super()
 */
class Dog extends Animal {
  public name: string;
  protected color: string;
  readonly legs: number;
  constructor(name: string = "", color: string ="") {super("dog");
    this.name = name;
    this.color = color;
    this.legs = 4;
  }
  public voice() {console.log("wang wang wang...");
  }
}

const dog = new Dog();

/*
  继承 Dog
 */
class YellowDog extends Dog {
  // 动态变量被所有的对象所共享,在内存中只有一个正本,它当且仅当在类首次加载时会被初始化
  static yellowDogNum: number = 0;

  public firstName: string = "";
  public lastName: string = "";

  // 存取器
  get fullName(): string {return this.firstName + "." + this.lastName;}

  set fullName(fullName: string) {if (fullName === "dog.king") {console.log("名字不能为 dog.king");
    }
  }

  constructor(name: string, color: string) {super(name, color);

    YellowDog.yellowDogNum += 1;

    this.color = "yellow yellow yellow";
    // 在子类中能够拜访父类的 protected 成员

    // this.what = "";
    // 在子类中不能够拜访父类的 private 成员
    // error:Property 'what' is private and only accessible within class 'Animal'.

    console.log(this.getDogNums());
  }
  private getDogNums() {return ` 当初一共有 ${YellowDog.yellowDogNum}只黄狗 `;
  }
  // 重写 父类 eat 办法
  public eat() {console.log("i am YelloDog,i eat");
    // 调用父类办法
    console.log("parent-eat:");
    super.eat();}
}

const yellowDog = new YellowDog("yellow dog", "yellow");
// 当初一共有 1 只黄狗
console.log(yellowDog);

yellowDog.legs = 5;
// 不能批改 read-only 属性
// error:Cannot assign to 'legs' because it is a read-only property.

console.log(yellowDog.what);
// 实例对象不能够拜访 private 成员
// error:Property 'what' is private and only accessible within class 'Animal'.

console.log(yellowDog.color);
// 实例对象不能够拜访 protected 成员
// error:Property 'what' is private and only accessible within class 'Animal'.

yellowDog.eat();
// 输入:i am YelloDog,i eat

yellowDog.firstName = "dog";
yellowDog.lastName = "queen";
console.log(yellowDog.fullName);
// 输入:dog.queen

yellowDog.fullName = "dog.king";
// 输入:名字不能为 dog.king

const yellowDog2 = new YellowDog("yellow dog2", "yellow2");
// 输入:当初一共有 2 只黄狗
const yellowDog3 = new YellowDog("yellow dog3", "yellow3");
// 输入:当初一共有 3 只黄狗
抽象类

抽象类中的形象办法不蕴含具体实现并且必须在派生类中实现

abstract class Person {constructor(public name: string) {}
  printName(): void {console.log("Person name:" + this.name);
  }
  // 必须在派生类中实现
  abstract working(): void;}

class Workman extends Person {constructor() {super("工人");
  }

  working(): void {console.log("working 8 hours");
  }

  rest(): void {console.log("rest...");
  }
}

// 容许创立一个对形象类型的援用
let workman: Person;


workman = new Person();
// 谬误:不能创立一个抽象类的实例

// 容许对一个形象子类进行实例化和赋值
workman = new Workman();
workman.printName();
workman.working();

workman.rest();
// 谬误:办法在申明的抽象类中不存在
把类当做接口应用

因为类能够创立出类型,所以你可能在容许应用接口的中央应用类,
所以同名的 类和接口 也会被合并

class User {
  id?: string;
  age?: number;
  sleep?: () => void;
  run() {return "run";}
}
interface User {
  name: string;
  hobby: string;
  play: () => string[];
}
const user: User = {
  name: "zhangsan",
  hobby: "working",
  play() {return ["game", "ball"];
  },
  run() {return "running";}
};

装璜器 (Decorators)

在一些场景下咱们须要额定的个性来反对标注或批改类及其成员。
装璜器为咱们在类的申明及成员上通过元编程语法增加标注提供了一种形式。
Javascript 里的装璜器目前处在征集阶段,但在 TypeScript 里已做为一项实验性个性予以反对。

装璜器是一种非凡类型的申明,它可能被附加到类、办法、拜访符、属性、参数上。
装璜器应用 @expression这种模式,esxpression求值后必须为一个函数,它会在运行时被调用,被装璜的申明信息做为参数传入。

装璜器要传入参数的话必须再包一层函数,而后 return 装璜器函数

先理解 Typescript 内置接口 PropertyDescriptor【属性描述符】
interface PropertyDescriptor {
  /* 
    configurable 键值为 true 时,可能从新定义属性,可能删除属性
    默认:false
  */
  configurable?: boolean;
  /*
    是否可枚举
    for..in
    Object.keys
    JSON.stringify
    Object.assign()
    默认 false
   */
  enumerable?: boolean;
  // 属性的值
  value?: any;
  /*
   当且仅当该属性的 writable 键值为 true 时,属性的值,能力被赋值运算符扭转。默认 false
   */
  writable?: boolean;

  get?(): any;
  set?(v: any): void;
}
类装璜器
interface Test {fn: () => void;
}
// 附加到 类 只能有一个参数,target,是一个构造函数
const Inject = (fn: () => void = () => {}) => (target: any) => {
  // 动态成员赋值形式
  // target.fn = fn;

  target.prototype.fn = fn;
};

@Inject(() => {console.log("my name is fn");
})
class Test {}
const test = new Test();
console.log(test.fn());
// 输入:my name is fn
办法装璜器
// 装璜器有参数
const makeFriends = (key: string) => {
  return (
    target: any,
    propertyKey: string,
    // 属性形容
    descriptor: PropertyDescriptor
  ) => {
    const origin = descriptor.value;
    descriptor.value = function (...args: any[]) {(this as any)[key] = true;
      origin.apply(this, args);
      (this as any)[key] = false;
    };
  };
};

interface Test {fn: () => void;
}

class Test {
  private _goOut: boolean;
  role: string;
  constructor() {
    this._goOut = false;
    this.role = "parent";
  }

  get goOut() {return this._goOut;}
  set goOut(val: boolean) {
    this._goOut = val;
    console.log("current-goOut :>>", this._goOut);
  }

  @makeFriends("goOut")
  sayHello(name: string, age: number) {console.log("currthis.role :>>", this.role);
    console.log(`hi,my name is ${name},i am ${age} years old`);
  }
}
const test = new Test();

test.sayHello("zhangsan", 10);

控制台输入

current-goOut :>>  true
currthis.role :>>  parent 
hi,my name is zhangsan,i am 10 years old 
current-goOut :>>  false

当有多个装璜器的时候。
装璜器会由上至下顺次对装璜器表达式求值。
求值的后果会被当作函数,由下至上顺次调用。

class Xx{
  @f
  @g
  xxx
}

等价于 

f(g(xxx))  
拜访符 / 属性 / 参数 润饰器
// appendStr.ts
/**
 * 拜访符
 * @desc 追加字符串
 */
const appendStr = (apdStr: string = "") => (
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) => {
  const origin = descriptor.set;
  descriptor.set = function (val: string = "") {origin!.call(this, val + apdStr);
  };
};

// initProp.ts
/**
 * 属性
 * @desc 初始化值
 */
const initProp = (val: string = "") => (target: any, propertyKey: string) => {target[propertyKey] = val;
};

// log.ts
/**
 * 参数
 */
const log = (target: any, propertyKey: string, parameterIndex: number) => {console.log("参数地位", parameterIndex);
};

// index.ts
class Test {
  private _a: string;

  @initProp("hello")
  public b?: string;

  constructor() {this._a = "";}

  get a() {return this._a;}
  @appendStr("haha")
  set a(val: string) {this._a = val;}

  hello(@log c: string) {}}

// 实例
const test = new Test();

// 拜访符测试
test.a = "xixi";
console.log(test.a);
// 输入:xixihaha

// 属性测试
console.log(test.b);
// 输入:hello

AOP(Aspect Oriented Programming)

次要实现的目标是针对业务处理过程中的切面进行提取,所面对的是处理过程中某个步骤或阶段,以取得逻辑过程中各局部之间低耦合性的隔离成果。

AOP 是对 OOP 的一个横向的补充,次要作用是把一些业务无关的性能抽离,例如日志打印、统计数据、安全控制、异样解决等。这些性能都与外围业务无关,但又随处可见。将其抽离进去用动静插入的形式嵌入到各业务逻辑中。让业务模块变得比拟洁净、不受净化,同时性能点可能失去很好的复用,给模块解耦。

前置告诉、后置告诉、盘绕告诉

如:

before(前置告诉)
场景:
提交表单时,咱们想要先通过一系列的验证,而后再执行表单提交
零碎统计用户点击某个按钮的次数

around(盘绕告诉)
场景:提交表单的时候,点击提交按钮。我想要在提交前把按钮禁用掉,而后提交胜利后【ajax 申请后】把按钮启用。

盘绕告诉例子

ThorttleButton.ts

/**
 * @desc 按钮节流开关,场景:用在异步提交表单的时候,避免用户反复提交
 * 1 开 0 关
 * @param prop
 */
const ThorttleButton = (prop: string) => (
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
): any => {
  const origin = descriptor.value;
  descriptor.value = async function(...args: any[]) {(this as any)[prop] = false;
    await origin.apply(this, args);
    (this as any)[prop] = true;
  };
};
export default ThorttleButton;

调用

<!-- ... -->
<button :disabled="postBtn"> 提交 </button>
<!-- ... -->

import ThorttleButton from '@/utils/decorate/ThorttleButton';
// ...
postBtn: boolean = true;
// ...

@ThorttleButton('postBtn')
async postForm() {await this.$request.post('/postData');
}

// ...

IoC(Inversion of Control)

  • 管制反转思维
  • 依赖倒置准则(Dependence Inversion Principle)
  • 依赖注入(Dependency Injection)是具体实现
  • 高层次的模块不应该依赖于低层次的模块
  • 形象不应该依赖于具体实现,具体实现应该依赖于形象
  • 面向接口编程
  • 能够解耦具备依赖关系的对象,升高开发保护老本

假如咱们要实现一个 App,App 有几个模块
停车场模块,统计停车场共有几辆车
用户性能,显示停车场的所有用户

于是咱们开始编写模块

car.ts

class Car {constructor() {}
  getNum() {
    const num = 1000;
    console.log("初始化显示所有车辆数:", num);
    return num;
  }
}
export default Car;

user.ts

class User {constructor() {}
  getAll() {const users = Array.apply(Array, Array(1000)).map((_, i) => ({
      id: i + 1,
      name: "张三" + i
    }));
    console.log("初始化显示所有用户:", users);
    return users;
  }
}
export default User;

App.ts

import Car from "./car";
import User from "./user";

class App {
  car: Car;
  user: User;
  advertising: Advertising;
  constructor() {this.car = new Car();
    this.user = new User();

    this.init();}

  init() {this.car.getNum();
    this.user.getAll();}
}

export default App;

index.ts

import App from "./App";

new App();

上线稳固后,产品经理筹备新增广告【advertising】性能了
于是咱们新增了

advertising.ts【新增】

class Advertising {constructor() {}
  getAll() {const users = Array.apply(Array, Array(3)).map((_, i) => ({
      id: i + 1,
      name: "轮播广告" + i
    }));
    console.log("初始化显示所有广告:", users);
    return users;
  }
}
export default Advertising;

App.ts【批改】

import Car from "./car";
import User from "./user";
// 新增
import Advertising from "./advertising";

class App {
  car: Car;
  user: User;
  advertising: Advertising;
  constructor() {this.car = new Car();
    this.user = new User();
    // 新增
    this.advertising = new Advertising();

    this.init();}

  init() {this.car.getNum();
    this.user.getAll();
    // 新增
    this.advertising.getAll();}
}

export default App;

剖析:
从 App 来看,会迭代更新,模块会越来越多
从 IoC 来看,this.car 和 this.user …,都还是对“具体实现”的依赖。违反了 IoC 思维的准则,须要进一步形象 App 模块。
从编码上来看,这种不完全符合“开闭准则”的形式很容易产生额定的 bug。

咱们来革新一下

革新 App.ts

import {TModule} from "./modules.type";

interface App {[prop: string]: any;
}

class App {static modules: TModule[] = [];
  constructor() {App.modules.forEach((module) => {module.init(this);
    });
  }
  // 依赖注入
  static inject(module: TModule) {App.modules.push(module);
  }
}

export default App;

新增了 modules.type.ts 文件,用来申明束缚须要注入的 module

import Car from "./car";
//import User from "./user";
//import Advertising from "./advertising";

export type TModule = Car

革新 car.ts

import App from "./App";
class Car {constructor() {}
  init(app: App){app.car = this;}
  getNum() {
    const num = 1000;
    console.log("初始化显示所有车辆数:", num);
    return num;
  }
}
export default Car;

在 index.ts 里

import App from "./App";
import Car from "./car";

App.inject(new Car());

const app = new App();

(app.car as Car).getNum();
// 初始化显示所有车辆数:1000
还没完,咱们要更灵便,更优雅

咱们实现一个类装璜器,用于实例化所依赖的低层模块,并将其注入到容器(App)内

新增 decrotate.inject.ts

import {TModule} from "./modules.type";

const Inject = (modules: TModule[]) => (
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) => {modules.forEach((module: TModule) => {module.init(target);
  });
};
export default Inject;

革新 car.ts

import App from "./App";
class Car {constructor() {}
  init(app: App){
    // app.car = this;

    // 要用 prototype 赋值,因为装璜器执行的程序在 App 实例化之前
    app.prototype.car = this;
  }
  getNum() {
    const num = 1000;
    console.log("初始化显示所有车辆数:", num);
    return num;
  }
}
export default Car;

革新 App.ts

import Inject from "./decrotate.inject";
import Car from "./car";

@Inject([new Car()])
class App {constructor() {}}

export default App;
总结

这其实就是 IoC 思维中对“面向接口编程,而不要面向实现编程”,App 不关怀模块具体实现了什么,只有满足对 接口 init 的约定就能够了。
App 模块此时应该称之为“容器”比拟适合了,跟业务曾经没有任何关系了,它仅仅只是提供了一些办法来辅助治理注入的依赖和管制模块如何执行。
管制反转(Inversion of Control)是一种思维,依赖注入(Dependency Injection)则是这一思维的一种具体实现,这里的 App 则是辅助依赖治理的一个容器。

TypeScript 领有的个性能够让咱们对 oo 体验更好,集体认为对于 Java/C# 的后端小伙伴来说,Node + TypeScript + typescript-ioc,是一件很美妙的事

退出移动版