装饰器与元数据反射(1)方法装饰器
栏目: JavaScript · 发布时间: 7年前
内容简介:让我来深入地了解一下TypeScript对于装饰器模式的实现,以及反射与依赖注入等相关特性。在接下来深入地了解一下每种装饰器:
让我来深入地了解一下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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Master Switch
Tim Wu / Knopf / 2010-11-2 / USD 27.95
In this age of an open Internet, it is easy to forget that every American information industry, beginning with the telephone, has eventually been taken captive by some ruthless monopoly or cartel. Wit......一起来看看 《The Master Switch》 这本书的介绍吧!