让我来深切地相识一下TypeScript关于装潢器形式的完成,以及反射与依靠注入等相干特征。
在Typescript
的源代码中,能够看到装潢器能用来润饰class
,property
,method
,parameter
:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
接下来深切地相识一下每种装潢器:
要领装潢器
首先来依据上面的标识,完成一个名为log
的要领装潢器。运用装潢器的要领很简单:在装潢器名前加@
字符,写在想要装潢的要领上,相似写解释的体式格局:
class C {
@log
foo(n: number) {
return n * 2;
}
}
装潢器实际上是一个函数,入参为所装潢的要领,返回值为装潢后的要领。在运用之前须要提早完成这个装潢器函数,以下:
function log(target: Function, key: string, descriptor: any) {
// target === C.prototype
// key === "foo"
// descriptor === Object.getOwnPropertyDescriptor(C.prototype, "foo")
// 保留对原要领的援用,防止重写
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// 将“foo”函数的参数列表转化为字符串
var a = args.map(a => JSON.stringify(a)).join();
// 挪用 foo() 并猎取它的返回值
var result = originalMethod.apply(this, args);
// 将返回的效果转成字符串
var r = JSON.stringify(result);
// 打印日记
console.log(`Call: ${key}(${a}) => ${r}`);
// 返回挪用 foo 的效果
return result;
}
// 返回已编辑的描述符
return descriptor;
}
该装潢器函数包括三个参数:
-
target
:所要润饰的要领。 -
key
:被润饰要领的名字。 -
descriptor
:属性描述符,假如为给定能够经由过程挪用Object.getOwnPropertyDescriptor()
来猎取。
我们视察到,类C
中运用的装潢器函数log
并没有显式的参数通报,难免猎奇它所须要的参数是怎样通报的?以及该函数是怎样被挪用的?
TypeScript终究照样会被编译为JavaScript实行,为了搞清上面的题目,我们来看一下TypeScript编译器将类C
的定义终究天生的JavaScript代码:
var C = (function () {
function C() {
}
C.prototype.foo = function (n) {
return n * 2;
};
Object.defineProperty(C.prototype, "foo",
__decorate([
log
], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo")));
return C;
})();
而为增加装潢器所天生的JavaScript代码以下:
var C = (function () {
function C() {
}
C.prototype.foo = function (n) {
return n * 2;
};
return C;
})();
对照二者发明运用装潢的差别,只是在类定义中,多了以下代码:
Object.defineProperty(
__decorate(
[log], // 装潢器
C.prototype, // target:C的原型
"foo", // key:装潢器润饰的要领名
Object.getOwnPropertyDescriptor(C.prototype, "foo") // descriptor
);
);
经由过程查询MDN文档,能够知悉defineProperty
的作用:
Object.defineProperty()
要领可直接在一个对象上定义一个新的属性,或许修正对象上一个已有的属性,然后返回这个对象。
TypeScript编译器经由过程defineProperty
要领重写了所润饰的要领foo
,新要领的完成是由函数__decorate
返回的,那末题目来了:__decorate
函数在哪声明的呢?
掘地三尺不难找到,来一同把玩一下:
var __decorate = this.__decorate || function (decorators, target, key, desc) {
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
return Reflect.decorate(decorators, target, key, desc);
}
switch (arguments.length) {
case 2:
return decorators.reduceRight(function(o, d) {
return (d && d(o)) || o;
}, target);
case 3:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key)), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key, o)) || o;
}, desc);
}
};
第一行运用了或操作符(||
),以确保假如函数__decorate
已被建立,他将不会被重写。
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
第二行是一个前提语句,运用了JavaScript的一个新特征:元数据反射。这个主题后续再睁开报告,下面我们先聚焦视察下该新特征的兼容计划:
switch (arguments.length) {
case 2:
return decorators.reduceRight(function(o, d) {
return (d && d(o)) || o;
}, target);
case 3:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key)), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key, o)) || o;
}, desc);
}
此处__decorate
函数接收了4个参数,所以case 4
将被实行。平心而论这块代码有点生涩,没紧要掰开揉碎了看。
reduceRight
要领接收一个函数作为累加器和数组的每一个值(从右到左)将其削减为单个值。
为了轻易明白,上面的代码重写以下:
[log].reduceRight(function(log, desc) {
if(log) {
return log(C.prototype, "foo", desc);
}
else {
return desc;
}
}, Object.getOwnPropertyDescriptor(C.prototype, "foo"));
能够看到当这段代码实行的时刻,装潢器函数log
被挪用,而且参数C.prototype
,"foo"
,previousValue
也被传入,云云之前的题目如今能够解答了。
经由装潢过的foo
要领,它依旧根据本来的体式格局实行,只是分外实行了附件的装潢器函数log
的功用。
const c = new C();
const r = c.foo(23); // "Call: foo(23) => 46"
console.log(r); // 46