定义类

  1. // 加上 import 或者 export,ts就会认为是局部模块; 否则就会认为是全局的。
  2. export {}
  3. class Pointer {
  4. x: number; //ts必须先声明类型,才能赋值。声明的变量会被增加到实例,等同于 public x:number
  5. y: number = 1; //声明后的变量,必须初始化。可以设置默认值,也可以在constructor函数中为其赋值
  6. public z: number;
  7. // 构造函数中的参数依然可以使用可选参数、剩余运算符、默认参数
  8. // 在constructor中的操作都是初始化操作
  9. constructor(x: number, y?:number, ...args:number[]){
  10. this.x = x;
  11. this.y = y as number;
  12. this.z = args[0];
  13. }
  14. }
  15. let p1 = new Pointer(50, 100, 1);
  16. console.log(p1); //Pointer {x: 50, y: 100, z: 1}
  17. //编译为
  18. var Pointer = /** @class */ (function () {
  19. function Pointer(x, y) {
  20. var args = [];
  21. for (var _i = 2; _i < arguments.length; _i++) {
  22. args[_i - 2] = arguments[_i];
  23. }
  24. this.y = 1; //声明后的变量,必须初始化。可以设置默认值,也可以在constructor函数中为其赋值
  25. this.x = x;
  26. this.y = y;
  27. this.z = args[0];
  28. }
  29. return Pointer;
  30. }());
  31. //简写。如果加上 public 就可省略声明和赋值
  32. class Pointer {
  33. constructor(public x:number, public y:number){}
  34. }
  35. let p1 = new Pointer(50, 100);
  36. console.log(p1); //Pointer {x: 50, y: 100}
  1. /**
  2. * 当我们写一个类的时候,会得到 2 个类型:
  3. * 1. 构造函数的类型(即类本身的类型)
  4. * 2. 类的实例类型
  5. */
  6. namespace a {
  7. class Component{
  8. static myName:string = '静态名称属性'
  9. myName:string = '实例名称属性';
  10. }
  11. // Component 在这里表示的是实例的类型
  12. let c:Component = new Component()
  13. console.log(c); //Component { myName: '实例名称属性' }
  14. // 构造函数的类型
  15. let com = Component; //把构造函数赋值给 com 变量
  16. // 先用 typeof 获取 构造函数Component 的类型,再用该类型约束变量 f
  17. let f:typeof Component = com;
  18. console.log(f); //[Function: Component] { myName: '静态名称属性' }
  19. console.log(f === Component); //true
  20. }
  21. namespace b {
  22. function Component (){
  23. this.myName = '实例名称属性';
  24. }
  25. Component.myName = '静态名称属性';
  26. }

存取器(get、set)

  • 在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为
  • 构造函数
    • 主要用于初始化类的成员变量属性
    • 类的对象创建时自动调用执行
    • 没有返回值 ```typescript //属性取值器的好处就是可以访问私有属性,比如下面的myName class User { constructor(private myName:string){ } get name(){ return this.myName; } set name(value){ this.myName = value; } }

let u1 = new User(‘jack’); console.log(u1.name); //jack u1.name = ‘lucy’; console.log(u1.name); //lucy

// 编译为 var User = /* @class / (function () { function User(myName) { this.myName = myName; } Object.defineProperty(User.prototype, “name”, { get: function () { return this.myName; }, set: function (value) { this.myName = value; }, enumerable: false, configurable: true }); return User; }());

  1. <a name="Fj7kW"></a>
  2. # 继承(extends)
  3. - 子类继承父类后,子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  4. - 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
  5. - super 可以调用父类上的方法和属性
  6. ```typescript
  7. // 父类
  8. class Animal {
  9. constructor(public name:string, public age:number){
  10. }
  11. }
  12. // 子类
  13. class Cat extends Animal{
  14. constructor(name:string, age:number, public address:string){
  15. super(name, age); //Animal.prototype.constructor.call(this, name, age)
  16. }
  17. }
  18. let cat = new Cat('Tom', 8, 'American');
  19. console.log(cat); // {name: "Tom", age: 8, address: "American"}

继承的编译

原代码
  1. export {}
  2. class Father {}
  3. class Child extends Father{}

编译后的代码
  1. "use strict";
  2. var __extends = (this && this.__extends) || (function () {
  3. var extendStatics = function (Child, Parent) {
  4. extendStatics =
  5. (Object.setPrototypeOf || ({ __proto__: [] } instanceof Array)
  6. &&
  7. function (Child, Parent) {
  8. Child.__proto__ = Parent;
  9. }) ||
  10. function (Child, Parent) {
  11. for (var p in Parent)
  12. // 检测对象是否含有特定的自身属性;
  13. if (Object.prototype.hasOwnProperty.call(Parent, p))
  14. Child[p] = Parent[p];
  15. };
  16. return extendStatics(Child, Parent);
  17. };
  18. return function (Child, Parent) {
  19. //继承静态属性
  20. extendStatics(Child, Parent);
  21. //继承原型链
  22. function __() {
  23. this.constructor = Child;
  24. }
  25. Child.prototype =
  26. Parent === null
  27. ? Object.create(Parent)
  28. : (__.prototype = Parent.prototype, new __());
  29. };
  30. })();
  31. Object.defineProperty(exports, "__esModule", { value: true });
  32. var Father = /** @class */ (function () {
  33. function Father() {
  34. }
  35. return Father;
  36. }());
  37. var Child = /** @class */ (function (_super) {
  38. __extends(Child, _super);
  39. function Child() {
  40. return _super !== null && _super.apply(this, arguments) || this;
  41. }
  42. return Child;
  43. }(Father));

类里面的修饰符

public、protected、private

  • public 公有的。自己、子类、外面都可以访问到
  • protected 受保护的。只有自己、子类可以访问
  • private 私有的。只有自己可以访问 ```typescript class Father { constructor(public name:string, protected age:number, private money:number){ console.log(this.name); //public 自己 可以访问 console.log(this.age); //protected 自己 可以访问 console.log(this.money); //private 自己 可以访问 } }

class Child extends Father { constructor(name:string, age:number, money:number){ super(name, age, money); console.log(this.name); //public 子类可以访问 console.log(this.age); //protected 子类可以访问 // console.log(this.money); //private 子类不可以访问 } }

let s1 = new Child(‘jack’, 11, 100); console.log(s1.name); //jack public 外边也能访问 // console.log(s1.age); //报错:属性“age”受保护,只能在类“father”及其子类中访问 // console.log(s1.money); //报错:属性“money”为私有属性,只能在类“father”中访问

  1. <a name="nqtLV"></a>
  2. ##### 给构造函数添加修饰符
  3. - 如果被标识成 protected ,说明不能被new了;
  4. - 如果被标识成 private,说明不能被继承了,同时也不能被new
  5. ```javascript
  6. class Animal {
  7. private constructor(public name:string, public age:number){
  8. }
  9. }
  10. class Cat extends Animal{ //报错:无法扩展类“Animal”。类构造函数标记为私有
  11. constructor(name:string, age:number, public address:string){
  12. super(name, age); //Animal.call(this, name, age)
  13. }
  14. }

readonly 只读

  • readonly 修饰的变量只能在 构造函数 中初始化
  • 在 TS 中,const 是 常量 标志符,其值不能被重新分配
  • ts 的类型系统同样也允许将 interface、type、class 上的属性符标识为 readonly
  • readonly 实际上只是在 编译 阶段进行代码检查。而 const 则会在 运行时 检查(在支持 const 语法的 JavaScript 运行时环境中)
    1. class Animal {
    2. constructor(public readonly name:string){
    3. //在constructor中都是初始化操作,所以这里可以随意修改只读属性
    4. this.name = name;
    5. }
    6. changeName(){
    7. this.name = name; //报错:无法分配到 "name",因为它是只读属性
    8. }
    9. }
    10. class Cat extends Animal {
    11. constructor(name:string){
    12. super(name);
    13. this.name = name; //报错:无法分配到 "name",因为它是只读属性,
    14. //因为已经调用了super,父类已经初始化完成了。
    15. }
    16. }

静态属性、静态方法

  1. class Father {
  2. // 静态属性
  3. static fatherName:string = 'fatherName';
  4. // 实例属性
  5. name:string;
  6. constructor(name:string){
  7. this.name = name;
  8. }
  9. // 非静态方法
  10. show(){
  11. // 此处的 this 指向 类的实例
  12. // 非静态方法中 可以访问实例属性,也能访问静态的成员
  13. console.log(this.name); //jack
  14. console.log(Father.fatherName); //fatherName
  15. }
  16. // 静态方法
  17. static showStatic(){
  18. // 此处的 this 指向 Father类本身
  19. // 静态方法中 可以访问静态成员,但是不能访问非静态的成员。
  20. // 类本身默认有个 name 属性,值为 类名
  21. console.log(this.name); //Father
  22. console.log(Father.fatherName); //fatherName
  23. }
  24. }
  25. let s1 = new Father('jack');
  26. s1.show();
  27. Father.showStatic(); //静态成员直接通过类访问,不需要实例化

子类也可以继承父类的静态属性和静态方法
  1. class Animal {
  2. static type:string = '哺乳动物';
  3. static getType(){
  4. return this.type;
  5. };
  6. }
  7. class Cat extends Animal{};
  8. console.log(Cat.type); //"哺乳动物"
  9. console.log(Cat.getType()); //"哺乳动物"

super

作为函数使用

  • super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super 函数。
  • 作为函数时,super() 只能用在子类的构造函数之中。 ```javascript class A { constructor(){ console.log(new.target.name); } } class B extends Animal{ constructor(){ super(); //等同于 A.prototype.constructor.call(this) } };

let b = new B(); //‘B’ // 结果表明,在super()执行时,它指向的是子类的构造函数,而不是父类的构造函数。 // 也就是说,super()内部的this指向的是子类的实例。

  1. <a name="PBMes"></a>
  2. ## 作为对象使用
  3. <a name="CSUGf"></a>
  4. ##### 普通方法中使用
  5. - `super` 指向**父类的原型对象**。
  6. - 通过 `super` 调用父类的方法时,方法内部的`this`指向当前的**子类实例**。
  7. ```typescript
  8. class A {
  9. constructor(public x:number){
  10. this.x = 1;
  11. }
  12. aa(){
  13. console.log('Animal-prototype-aa=>', this.x);
  14. }
  15. }
  16. class B extends A{
  17. constructor(x:number){
  18. super(x);
  19. this.x = 2;
  20. }
  21. bb(){
  22. super.aa(); //等同于 A.prototype.aa.call(this)。
  23. // 这里就是将 super 当做一个对象使用。super指向父类的原型对象,即 A.prototype
  24. }
  25. };
  26. let b = new B(100);
  27. b.bb(); // A-prototype-aa=> 2
  28. // 结果表明,通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。

静态方法中使用
  • super 指向 父类
  • 通过 super 调用父类的静态方法时,方法内部的 this 指向当前的子类。 ```typescript class A { static aa(){console.log(‘static aa=>’, this === B);} aa(){console.log(‘prototype aa=>’, this); } } class B extends A{ static bb(){ super.aa(); //等同于 A.aa.call(this) // super 指向父类 } };

B.bb(); // static aa=> true // 结果表明,通过 super 调用父类的静态方法时,方法内部的 this 指向当前的子类。

  1. <a name="DonpP"></a>
  2. # 装饰器
  3. [https://www.yuque.com/zhuchaoyang/wrif6k/titqbp](https://www.yuque.com/zhuchaoyang/wrif6k/titqbp)<br />配置:`"experimentalDecorators": true, /*让ts支持装饰器*/`
  4. - 装饰器是一种特殊的声明,它能够被附加到类声明、方法、属性、参数上,可以修改类的行为
  5. - 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
  6. - 装饰器的写法分为普通装饰器和装饰器工厂
  7. ```typescript
  8. class Person{
  9. say() {
  10. console.log('hello')
  11. }
  12. }
  13. // 等同于
  14. function Person() {}
  15. Object.defineProperty(Person.prototype, 'say', {
  16. value: function() { console.log('hello'); },
  17. enumerable: false,
  18. configurable: true,
  19. writable: true
  20. });

类装饰器

类装饰器在类声明之前声明,用来监视、修改或替换类定义。

  1. // 接受一个参数 target 是 类本身
  2. // 此装饰器功能:给类添加一个name属性和eat方法
  3. function addNameEat(target:Function){
  4. target.prototype.name = 'jack';
  5. target.prototype.eat = function(){
  6. console.log(target.name);
  7. }
  8. }
  9. // 类装饰器,等同于 addNameEat(Person)
  10. @addNameEat
  11. class Person {
  12. name!:string; //这里也要声明一下,但是不用赋值
  13. eat!:Function;
  14. constructor(){}
  15. }
  16. let p:Person = new Person();
  17. console.log(p.name); //jack
  18. p.eat(); //Person
  1. // 使用装饰器工厂
  2. function addNameEatFactory(name:string){
  3. return function(target:Function){
  4. target.prototype.name = name;
  5. target.prototype.eat = function(){
  6. console.log(target.name);
  7. }
  8. }
  9. }
  10. @addNameEatFactory('jack') //等同于 addNameEatFactory('jack')(Person)
  11. class Person {
  12. name!:string;
  13. eat!:Function;
  14. constructor(){}
  15. }
  16. let p:Person = new Person();
  17. console.log(p.name); //jack
  18. p.eat(); //Person
  1. // 替换类,不过替换的类要与原类结构相同
  2. function replaceClass(target:Function){
  3. return class {
  4. name:string = 'jack';
  5. eat(){
  6. console.log(target.name);
  7. };
  8. age!: number; //属性比原类可以多,但是不能少
  9. constructor(){}
  10. }
  11. }
  12. @replaceClass //等同于 replaceClass(Person)
  13. class Person {
  14. name!:string;
  15. eat!:Function;
  16. constructor(){}
  17. }
  18. let p:Person = new Person();
  19. console.log(p.name); //jack
  20. p.eat(); //Person

属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数

  • 属性装饰器用来装饰属性
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是属性的名称
  • 方法装饰器用来装饰方法

    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是方法的名称
    • 第三个参数是方法描述符

      装饰属性

      ```typescript // target 如果装饰的是实例属性,target指类的原型对象 function upperCase(target:any, key:string){ // console.log(target, key); let val = target[key];

    Object.defineProperty(target, key, { get: () => val.toUpperCase(), set: (newVal:string) => {

    1. val = newVal;

    }, enumerable: true, configurable: true, }) }

// target 如果装饰的是静态属性,target指类本身 function double(num:number){ return function(target:any, key:string){ // console.log(target, key); let val = target[key]; Object.defineProperty(target, key, { get(){ return num * val; } }) } }

class Person { @upperCase name:string = ‘jack’; //实例属性 @double(2) public static age:number = 10; //静态属性 }

let p = new Person(); console.log(p.name); // “JACK” console.log(Person.age); //20

  1. <a name="caqnH"></a>
  2. ### 装饰方法
  3. ```typescript
  4. // target 如果装饰的是实例方法,target指类的原型对象
  5. function noEnumerable(target:any, key:string, descriptor:PropertyDescriptor){
  6. descriptor.enumerable = false;
  7. }
  8. // 重写方法
  9. function toNumber(target:any, key:string, descriptor:PropertyDescriptor){
  10. let oldValue = descriptor.value;
  11. descriptor.value = function(...args:any[]){
  12. args = args.map(item => Number.parseFloat(item));
  13. return oldValue.apply(this, args);
  14. // return oldValue.call(this, ...args);
  15. }
  16. }
  17. class Person {
  18. name:string = 'jack';
  19. public static age:number = 10;
  20. @noEnumerable
  21. getName(){ console.log(this.name); } //实例方法
  22. @toNumber
  23. sum(...args:any[]){ //实例方法
  24. return args.reduce((val:number, item:number) => val+item, 0)
  25. }
  26. }
  27. let p = new Person();
  28. // for...in 遍历对象自身的和继承(原型链上)的所有可枚举属性(不含 Symbol 属性)。
  29. // getName 用 noEnumerable 修饰器 装饰后,for...in 遍历就查询不到了
  30. for (let attr in p) {
  31. console.log('attr=', attr); //只剩 name、sum
  32. }
  33. console.log(p.sum(1,2,3)); //6
  34. console.log(p.sum('1', '2', '3')); //装饰前:"0123"; toNumber装饰后:6

参数装饰器

会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些元数据
在IOC容器里大放异彩,Nest.js中也大量用到了参数装饰器。

  • 第一个参数对于静态成员是类的构造函数本身,对于实例成员是类的原型对象
  • 第二个参数是 方法的名称
  • 第三个参数是 参数在函数列表中的索引 ```typescript function addAge(target:any, methodName:string, paramIndex:number){ console.log(target); //Person { login: [Function (anonymous)] } console.log(methodName); //“login” console.log(paramIndex); //1 target.age = 10; }

class Person { age:number; login(username:string, @addAge password:string){ console.log(username, password, this.age); } } let p = new Person(); p.login(‘1’, ‘2’); //1 2 10

  1. <a name="KssBh"></a>
  2. ## 装饰器执行顺序
  3. - 类装饰器总是最后执行,然后后写的类执行器先执行
  4. - 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会紧紧挨着方法执行
  5. - 方法和该方法的参数,参数执行器先执行,方法执行器后执行
  6. - 有多个参数装饰器时,从最后一个参数依次向前执行
  7. - **先从外到内进入,然后由内向外执行,**类似React组件的ComponentDidMount 先上后下、先内后外。如下图:
  8. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/139415/1610559602546-56154d9f-ddb1-4f4b-9bcb-fc936cca6b04.png#height=354&id=ap7iZ&margin=%5Bobject%20Object%5D&name=image.png&originHeight=354&originWidth=538&originalType=binary&ratio=1&size=60968&status=done&style=stroke&width=538)
  9. ```typescript
  10. function ClassDecorator1(){
  11. console.log('outer-ClassDecorator1');
  12. return function(target){
  13. console.log('ClassDecorator1');
  14. }
  15. }
  16. function ClassDecorator2(){
  17. console.log('outer-ClassDecorator2');
  18. return function(target){
  19. console.log('ClassDecorator2');
  20. }
  21. }
  22. function PropertyDecorator(name){
  23. console.log('outer-PropertyDecorator', name);
  24. return function(target, key){
  25. console.log('PropertyDecorator', key, name);
  26. }
  27. }
  28. function MethodDecorator(){
  29. console.log('outer-MethodDecorator');
  30. return function(target, key, descriptor){
  31. console.log('MethodDecorator', key);
  32. }
  33. }
  34. function ParameterDecorator(index){
  35. console.log('outer-ParameterDecorator', index);
  36. return function(target, methodName, paramIndex){
  37. console.log('ParameterDecorator', methodName, paramIndex);
  38. }
  39. }
  40. @ClassDecorator1()
  41. @ClassDecorator2()
  42. class Person {
  43. @PropertyDecorator('name')
  44. name:string = '';
  45. @PropertyDecorator('age')
  46. age:number = 10;
  47. @MethodDecorator()
  48. hello(@ParameterDecorator(0) p1:string, @ParameterDecorator(1) p2:string){};
  49. }

输出结果:

  1. outer-PropertyDecorator name
  2. PropertyDecorator name name
  3. outer-PropertyDecorator age
  4. PropertyDecorator age age
  5. outer-MethodDecorator
  6. outer-ParameterDecorator 0
  7. outer-ParameterDecorator 1
  8. ParameterDecorator hello 1
  9. ParameterDecorator hello 0
  10. MethodDecorator hello
  11. outer-ClassDecorator1
  12. outer-ClassDecorator2
  13. ClassDecorator2
  14. ClassDecorator1

抽象类(abstract)

  • 抽象描述一种抽象的概念,就像是我定义一种规范,你必须要按照我的规范来,一个都不能少。
  • 抽象类无法被实例化,只能被继承。
  • 抽象属性、抽象方法只能出现在抽象类中。
  • 抽象属性、抽象方法不包含具体实现,必须全部在子类中实现。 ```typescript //抽象类 abstract class Animal { name:string = ‘哺乳动物’; abstract age:number; //抽象属性 abstract speak():void; //抽象方法,不能有方法体
    }

class Cat extends Animal{ age = 1; speak():void{ console.log(‘喵喵喵’); } } class Dog extends Animal{ age = 2; speak():void{ console.log(‘汪汪汪’); } }

// let animal = new Animal(); //报错:无法创建抽象类的实例 let cat = new Cat(); console.log(cat); //Cat {name: “哺乳动物”, age: 1} cat.speak(); //“喵喵喵” let dog = new Dog(); dog.speak(); //“汪汪汪”

  1. <a name="ObWza"></a>
  2. # 重写(override) vs 重载(overload)
  3. - 重写:指子类重写继承自父类中的方法
  4. - 重载:指为同一个函数提供多个类型定义
  5. ```typescript
  6. class Animal {
  7. speak(word:string){
  8. return `动物叫:${word}`;
  9. }
  10. }
  11. class Cat {
  12. // 重写
  13. speak(word:string){
  14. return `猫叫:${word}`;
  15. }
  16. }
  17. let cat = new Cat();
  18. console.log(cat.speak('hello')); //猫叫:hello
  1. let obj:any = {};
  2. // 重载,函数attr的参数只能传字符串或者数字
  3. function attr(val:string):void
  4. function attr(val:number):void
  5. function attr(val:any):void{
  6. if (typeof val === 'string'){
  7. obj.name = val;
  8. } else if (typeof val === 'number'){
  9. obj.age = val;
  10. }
  11. }

继承 vs 多态

  • 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些具体的特性
  • 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
    1. class Animal{
    2. speak(word:string):string{
    3. return 'Animal: '+word;
    4. }
    5. }
    6. class Cat extends Animal{
    7. speak(word:string):string{
    8. return 'Cat:'+word;
    9. }
    10. }
    11. class Dog extends Animal{
    12. speak(word:string):string{
    13. return 'Dog:'+word;
    14. }
    15. }
    16. let cat = new Cat();
    17. console.log(cat.speak('hello'));
    18. let dog = new Dog();
    19. console.log(dog.speak('hello'));