定义类
// 加上 import 或者 export,ts就会认为是局部模块; 否则就会认为是全局的。export {}class Pointer {x: number; //ts必须先声明类型,才能赋值。声明的变量会被增加到实例,等同于 public x:numbery: number = 1; //声明后的变量,必须初始化。可以设置默认值,也可以在constructor函数中为其赋值public z: number;// 构造函数中的参数依然可以使用可选参数、剩余运算符、默认参数// 在constructor中的操作都是初始化操作constructor(x: number, y?:number, ...args:number[]){this.x = x;this.y = y as number;this.z = args[0];}}let p1 = new Pointer(50, 100, 1);console.log(p1); //Pointer {x: 50, y: 100, z: 1}//编译为var Pointer = /** @class */ (function () {function Pointer(x, y) {var args = [];for (var _i = 2; _i < arguments.length; _i++) {args[_i - 2] = arguments[_i];}this.y = 1; //声明后的变量,必须初始化。可以设置默认值,也可以在constructor函数中为其赋值this.x = x;this.y = y;this.z = args[0];}return Pointer;}());//简写。如果加上 public 就可省略声明和赋值class Pointer {constructor(public x:number, public y:number){}}let p1 = new Pointer(50, 100);console.log(p1); //Pointer {x: 50, y: 100}
/*** 当我们写一个类的时候,会得到 2 个类型:* 1. 构造函数的类型(即类本身的类型)* 2. 类的实例类型*/namespace a {class Component{static myName:string = '静态名称属性'myName:string = '实例名称属性';}// Component 在这里表示的是实例的类型let c:Component = new Component()console.log(c); //Component { myName: '实例名称属性' }// 构造函数的类型let com = Component; //把构造函数赋值给 com 变量// 先用 typeof 获取 构造函数Component 的类型,再用该类型约束变量 flet f:typeof Component = com;console.log(f); //[Function: Component] { myName: '静态名称属性' }console.log(f === Component); //true}namespace b {function Component (){this.myName = '实例名称属性';}Component.myName = '静态名称属性';}
存取器(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; }());
<a name="Fj7kW"></a># 继承(extends)- 子类继承父类后,子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性- 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑- super 可以调用父类上的方法和属性```typescript// 父类class Animal {constructor(public name:string, public age:number){}}// 子类class Cat extends Animal{constructor(name:string, age:number, public address:string){super(name, age); //Animal.prototype.constructor.call(this, name, age)}}let cat = new Cat('Tom', 8, 'American');console.log(cat); // {name: "Tom", age: 8, address: "American"}
继承的编译
原代码
export {}class Father {}class Child extends Father{}
编译后的代码
"use strict";var __extends = (this && this.__extends) || (function () {var extendStatics = function (Child, Parent) {extendStatics =(Object.setPrototypeOf || ({ __proto__: [] } instanceof Array)&&function (Child, Parent) {Child.__proto__ = Parent;}) ||function (Child, Parent) {for (var p in Parent)// 检测对象是否含有特定的自身属性;if (Object.prototype.hasOwnProperty.call(Parent, p))Child[p] = Parent[p];};return extendStatics(Child, Parent);};return function (Child, Parent) {//继承静态属性extendStatics(Child, Parent);//继承原型链function __() {this.constructor = Child;}Child.prototype =Parent === null? Object.create(Parent): (__.prototype = Parent.prototype, new __());};})();Object.defineProperty(exports, "__esModule", { value: true });var Father = /** @class */ (function () {function Father() {}return Father;}());var Child = /** @class */ (function (_super) {__extends(Child, _super);function Child() {return _super !== null && _super.apply(this, arguments) || this;}return Child;}(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”中访问
<a name="nqtLV"></a>##### 给构造函数添加修饰符- 如果被标识成 protected ,说明不能被new了;- 如果被标识成 private,说明不能被继承了,同时也不能被new```javascriptclass Animal {private constructor(public name:string, public age:number){}}class Cat extends Animal{ //报错:无法扩展类“Animal”。类构造函数标记为私有constructor(name:string, age:number, public address:string){super(name, age); //Animal.call(this, name, age)}}
readonly 只读
- readonly 修饰的变量只能在
构造函数中初始化 - 在 TS 中,const 是
常量标志符,其值不能被重新分配 - ts 的类型系统同样也允许将 interface、type、class 上的属性符标识为 readonly
- readonly 实际上只是在
编译阶段进行代码检查。而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)class Animal {constructor(public readonly name:string){//在constructor中都是初始化操作,所以这里可以随意修改只读属性this.name = name;}changeName(){this.name = name; //报错:无法分配到 "name",因为它是只读属性}}class Cat extends Animal {constructor(name:string){super(name);this.name = name; //报错:无法分配到 "name",因为它是只读属性,//因为已经调用了super,父类已经初始化完成了。}}
静态属性、静态方法
class Father {// 静态属性static fatherName:string = 'fatherName';// 实例属性name:string;constructor(name:string){this.name = name;}// 非静态方法show(){// 此处的 this 指向 类的实例// 非静态方法中 可以访问实例属性,也能访问静态的成员console.log(this.name); //jackconsole.log(Father.fatherName); //fatherName}// 静态方法static showStatic(){// 此处的 this 指向 Father类本身// 静态方法中 可以访问静态成员,但是不能访问非静态的成员。// 类本身默认有个 name 属性,值为 类名console.log(this.name); //Fatherconsole.log(Father.fatherName); //fatherName}}let s1 = new Father('jack');s1.show();Father.showStatic(); //静态成员直接通过类访问,不需要实例化
子类也可以继承父类的静态属性和静态方法
class Animal {static type:string = '哺乳动物';static getType(){return this.type;};}class Cat extends Animal{};console.log(Cat.type); //"哺乳动物"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指向的是子类的实例。
<a name="PBMes"></a>## 作为对象使用<a name="CSUGf"></a>##### 普通方法中使用- `super` 指向**父类的原型对象**。- 通过 `super` 调用父类的方法时,方法内部的`this`指向当前的**子类实例**。```typescriptclass A {constructor(public x:number){this.x = 1;}aa(){console.log('Animal-prototype-aa=>', this.x);}}class B extends A{constructor(x:number){super(x);this.x = 2;}bb(){super.aa(); //等同于 A.prototype.aa.call(this)。// 这里就是将 super 当做一个对象使用。super指向父类的原型对象,即 A.prototype}};let b = new B(100);b.bb(); // A-prototype-aa=> 2// 结果表明,通过 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 指向当前的子类。
<a name="DonpP"></a># 装饰器[https://www.yuque.com/zhuchaoyang/wrif6k/titqbp](https://www.yuque.com/zhuchaoyang/wrif6k/titqbp)<br />配置:`"experimentalDecorators": true, /*让ts支持装饰器*/`- 装饰器是一种特殊的声明,它能够被附加到类声明、方法、属性、参数上,可以修改类的行为- 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器- 装饰器的写法分为普通装饰器和装饰器工厂```typescriptclass Person{say() {console.log('hello')}}// 等同于function Person() {}Object.defineProperty(Person.prototype, 'say', {value: function() { console.log('hello'); },enumerable: false,configurable: true,writable: true});
类装饰器
类装饰器在类声明之前声明,用来监视、修改或替换类定义。
// 接受一个参数 target 是 类本身// 此装饰器功能:给类添加一个name属性和eat方法function addNameEat(target:Function){target.prototype.name = 'jack';target.prototype.eat = function(){console.log(target.name);}}// 类装饰器,等同于 addNameEat(Person)@addNameEatclass Person {name!:string; //这里也要声明一下,但是不用赋值eat!:Function;constructor(){}}let p:Person = new Person();console.log(p.name); //jackp.eat(); //Person
// 使用装饰器工厂function addNameEatFactory(name:string){return function(target:Function){target.prototype.name = name;target.prototype.eat = function(){console.log(target.name);}}}@addNameEatFactory('jack') //等同于 addNameEatFactory('jack')(Person)class Person {name!:string;eat!:Function;constructor(){}}let p:Person = new Person();console.log(p.name); //jackp.eat(); //Person
// 替换类,不过替换的类要与原类结构相同function replaceClass(target:Function){return class {name:string = 'jack';eat(){console.log(target.name);};age!: number; //属性比原类可以多,但是不能少constructor(){}}}@replaceClass //等同于 replaceClass(Person)class Person {name!:string;eat!:Function;constructor(){}}let p:Person = new Person();console.log(p.name); //jackp.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) => {
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
<a name="caqnH"></a>### 装饰方法```typescript// target 如果装饰的是实例方法,target指类的原型对象function noEnumerable(target:any, key:string, descriptor:PropertyDescriptor){descriptor.enumerable = false;}// 重写方法function toNumber(target:any, key:string, descriptor:PropertyDescriptor){let oldValue = descriptor.value;descriptor.value = function(...args:any[]){args = args.map(item => Number.parseFloat(item));return oldValue.apply(this, args);// return oldValue.call(this, ...args);}}class Person {name:string = 'jack';public static age:number = 10;@noEnumerablegetName(){ console.log(this.name); } //实例方法@toNumbersum(...args:any[]){ //实例方法return args.reduce((val:number, item:number) => val+item, 0)}}let p = new Person();// for...in 遍历对象自身的和继承(原型链上)的所有可枚举属性(不含 Symbol 属性)。// getName 用 noEnumerable 修饰器 装饰后,for...in 遍历就查询不到了for (let attr in p) {console.log('attr=', attr); //只剩 name、sum}console.log(p.sum(1,2,3)); //6console.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
<a name="KssBh"></a>## 装饰器执行顺序- 类装饰器总是最后执行,然后后写的类执行器先执行- 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会紧紧挨着方法执行- 方法和该方法的参数,参数执行器先执行,方法执行器后执行- 有多个参数装饰器时,从最后一个参数依次向前执行- **先从外到内进入,然后由内向外执行,**类似React组件的ComponentDidMount 先上后下、先内后外。如下图:```typescriptfunction ClassDecorator1(){console.log('outer-ClassDecorator1');return function(target){console.log('ClassDecorator1');}}function ClassDecorator2(){console.log('outer-ClassDecorator2');return function(target){console.log('ClassDecorator2');}}function PropertyDecorator(name){console.log('outer-PropertyDecorator', name);return function(target, key){console.log('PropertyDecorator', key, name);}}function MethodDecorator(){console.log('outer-MethodDecorator');return function(target, key, descriptor){console.log('MethodDecorator', key);}}function ParameterDecorator(index){console.log('outer-ParameterDecorator', index);return function(target, methodName, paramIndex){console.log('ParameterDecorator', methodName, paramIndex);}}@ClassDecorator1()@ClassDecorator2()class Person {@PropertyDecorator('name')name:string = '';@PropertyDecorator('age')age:number = 10;@MethodDecorator()hello(@ParameterDecorator(0) p1:string, @ParameterDecorator(1) p2:string){};}
输出结果:
outer-PropertyDecorator namePropertyDecorator name nameouter-PropertyDecorator agePropertyDecorator age ageouter-MethodDecoratorouter-ParameterDecorator 0outer-ParameterDecorator 1ParameterDecorator hello 1ParameterDecorator hello 0MethodDecorator helloouter-ClassDecorator1outer-ClassDecorator2ClassDecorator2ClassDecorator1
抽象类(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(); //“汪汪汪”
<a name="ObWza"></a># 重写(override) vs 重载(overload)- 重写:指子类重写继承自父类中的方法- 重载:指为同一个函数提供多个类型定义```typescriptclass Animal {speak(word:string){return `动物叫:${word}`;}}class Cat {// 重写speak(word:string){return `猫叫:${word}`;}}let cat = new Cat();console.log(cat.speak('hello')); //猫叫:hello
let obj:any = {};// 重载,函数attr的参数只能传字符串或者数字function attr(val:string):voidfunction attr(val:number):voidfunction attr(val:any):void{if (typeof val === 'string'){obj.name = val;} else if (typeof val === 'number'){obj.age = val;}}
继承 vs 多态
- 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些具体的特性
- 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
class Animal{speak(word:string):string{return 'Animal: '+word;}}class Cat extends Animal{speak(word:string):string{return 'Cat:'+word;}}class Dog extends Animal{speak(word:string):string{return 'Dog:'+word;}}let cat = new Cat();console.log(cat.speak('hello'));let dog = new Dog();console.log(dog.speak('hello'));
