装饰器与元数据反射(1)方法装饰器
栏目: JavaScript · 发布时间: 5年前
内容简介:让我来深入地了解一下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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员面试笔试宝典
何昊、叶向阳、窦浩 / 2012-10 / 59.80元
《程序员面试笔试宝典》除了对传统的计算机相关知识(C/C++、数据结构与算法、操作系统、计算机网络与通信、软件工程、数据库、智力题、英语面试等)进行介绍外,还根据当前计算机技术的发展潮流,对面试笔试中常见的海量数据处理进行了详细的分析。同时,为了更具说服力,《程序员面试笔试宝典》特邀多位IT名企面试官现身说法,对面试过程中求职者存在的问题进行了深度剖析,同时《程序员面试笔试宝典》引入了一批来自于名......一起来看看 《程序员面试笔试宝典》 这本书的介绍吧!