枚举、函数、类、装饰器
# 枚举: enum
数字枚举
enum Status {
Uploading,
Success,
Failed = 8
}
console.log(Status.Uploading) // 0
console.log(Status.Success) // 1
console.log(Status.Failed) // 8
enum Status {
Uploading = 3,
Success,
Failed = 8
}
console.log(Status.Uploading) // 3
console.log(Status.Success) // 4
console.log(Status.Failed) // 8
- 如果没有初始化,默认初始化值为0,每项+1
- 如果有初始化,则在初始化值的基础上,每项+1
字符串枚举
- 每个成员必须用字符串文字或其他字符串枚举成员进行常量初始化,否则会报Error:Enum member must have initializer
enum Status {
Uploading = 'Uploading',
Success = 'Success',
Failed = 'Failed'
}
console.log(Status.Uploading)
console.log(Status.Success)
console.log(Status.Failed)
异构枚举
- 不建议使用
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
反向映射
- 一个枚举被编译成一个存储forward(name- > value)和reverse(value- > name)映射的对象
enum Enum {
A
}
const a = Enum.A; // 0
const nameOfA = Enum[a]; // "A"
<!-- var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {})); -->
常数枚举
const enum Directions {
Up,
Down,
Left,
Right
}
const directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
console.log(directions)
<!--会被编译成 var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; -->
# 函数
可以声明具名函数和匿名函数
function fn(x) { // 名字为b的函数
return x;
}
const fn1 = function (x) { // 没有名字的函数声明,直接将函数值赋给变量b1
return x;
}
函数的类型包含参数类型和返回值类型两个部分. 可选参数名必需放在必传的参数后面
- this
返回一个函数:
const fn2: (y: number) => number = function (x: number): number { return x; }
const b14 = {
m1: 1,
m2: function (): () => void { // 返回一个函数
return function (): void {
console.log(this.m1);
};
},
}
b14.m2()(); // 打印:undefined
这里的this是window对象(如果是严格模式的话,this为undefined),因为返回一个函数再执行,相当于在顶层作用域下执行:
function (): void {
console.log(this.m1); // 打印:undefined
};
解决方案: 使用箭头函数解决这个问题,因为箭头函数捕获函数创建时候的this而不是函数调用时的this。
const b14 = {
m1: 1,
m2: function (): () => void { // 返回一个函数
return (): void => {
console.log(this.m1);
};
},
}
b14.m2()(); // 打印:1
// 这里的this就是函数创建时候的this,即b14对象
传递一个函数作为参数: 打开noImplicitThis配置,在使用隐含any类型的this的时候会报错。打开noImplicitThis配置之后,下面这段代码,this的位置会报错:
const b15 = {
m1: 1,
m2: function (x: () => void): void { // 将函数作为一个参数
x();
},
};
const b16 = function (): void {
console.log(this.m1);
}
b15.m2(b16); // 打印:undefined
# 类
- 类可以从基类继承属性和方法。
- public、private、protected是类成员的三个修饰符。分别表示公有成员、私有成员、被保护的成员。
- TypeScript 3.8 带来了私有字段,私有字段以#符号开头
- 每个私有字段名称都唯一地作用于其包含的类。
- 【private修饰符修饰的私有成员】和【前面加上#表示的私有字段】都只能在包含它们的类中被访问。前者能修饰方法和属性,后者只能表示属性不能表示方法。
- protected成员不能被派生类的实例访问
- 构造函数被标记为protected,这意味着这个类不能在包含它的类外面被实例化,但是可以被扩展.
class C11 {
protected m1: number = 123;
}
class C12 extends C11 {
m2(): void {
console.log(this.m1);
}
}
const c12 = new C12();
c12.m2(); // 打印:123
c12.m1; // 报错:属性“m1”受保护,只能在类“C11”及其子类中访问.
class C13 {
m1: number;
protected constructor(x) {
this.m1 = x;
}
}
class C14 extends C13 {
constructor(x) {
super(x);
}
}
const c13 = new C13(1); // 报错:类“C13”的构造函数是受保护的,仅可在类声明中访问
const c14 = new C14(1);
console.log(c14.m1); // 打印:1
# 装饰器 @
- 每个装饰器的表达式从上到下进行评估,然后结果被称为从下到上的函数。
function setName() {
console.log('get setName');
return (target: any) => {
console.log(target, 'setName');
}
}
function setAge() {
console.log('get setAge');
return (target: any) => {
console.log(target, 'setAge');
}
}
@setName()
@setAge()
// 执行结果为
// get setName
// get setAge
// setAge
// setAge
# 类装饰器
function addName(constructor: new() => any) {
constructor.prototype.name = 'liston';
}
@addName
class ClassD {}
interface ClassD {
name: string
}
// 接口添加到 类的原型对象上,注意:interface需要在class 之后添加
const d = new ClassD();
console.log(d, 'ClassD')
console.log(d.name)
# 方法装饰器
在一个方法声明前被声明,装饰器应用到方法的属性描述符上,并且可以用来观察、修改、或者替代一个方法定义。一个方法装饰器不能被用在声明文件中、重载中、或者在任何环境上下文(比如declare类)中 方法装饰器的表达式在运行时作为一个函数调用,包含三个参数:
- target:对于静态成员,是类的构造函数;对于实例成员,是类的原型(prototype)。
- propertyKey:成员的名称。
- descriptor:成员的属性描述符。(注意:属性描述符在你的脚本目标小于ES5的时候会是undefined)
属性描述符:
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
class C26 {
@writable(false)
m1() {
console.log("aaa");
}
}
function writable(x: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
return {
writable: false,
};
};
}
const c26 = new C26();
c26.m1 = () => {
console.log("bbb");
};
// ERROR: decorators.ts:123 Uncaught TypeError: Cannot assign to read only property 'm1' of object '#<C26>'...
# 存取器装饰器
- 存取器装饰器应用在存取器的属性描述符
注意:
TypeScript不允许同时装饰成员的get和set存取器。该成员的所有装饰器必须应用到文档顺序的第一个存取器。这是因为装饰器应用于属性描述符,属性描述符将get和set存取器组合在一起,而不是分开声明。否则会报Error:Decorators cannot be applied to multiple get/set accessors of the same name
# 属性装饰器
- 属性装饰器在一个属性声明前声明。一个属性装饰器不能用在一个声明文件中,或者任何其他的环境上下文(比如一个declare类)中
- 属性装饰器的表达式会作为一个函数在运行时被调用,使用以下两个参数:
1.target: 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型。
2.propertyKey: 成员的名字。
注意:
因为TypeScript中初始化属性装饰器的机制,一个属性描述符不能用作一个属性装饰器的参数。这是因为目前当定义原型的成员的时候,没有机制来描述一个实例属性,并且没有办法来观察或者修改属性的初始化。返回值也被忽视了。因此,一个属性装饰器只能用来观察类中已经明确声明了一个名字的属性
# 参数装饰器(babel 暂不支持)
参数装饰器在参数声明前被声明。参数装饰器应用于类的构造函数声明或方法声明。参数装饰器不能用在声明文件中、重载中、或者在任何其他环境上下文(比如一个declare类)中。
参数装饰器的会作为函数在运行时被调用,带着以下三个参数
- target: 对于静态成员,是类的构造函数;对于实例成员,是类的原型(prototype)。
- propertyKey: 成员的名称。
- parameterIndex: 函数的参数列表中参数的序号索引。
class C29 {
m1 (@decoratorC29 x: number) {
console.log(x);
}
m2 () {
console.log('456');
}
}
function decoratorC29 (
target: any,
propertyKey: string | symbol,
parameterIndex: number
) {
target.m2();
}