曾经一直在嵌入式数通领域混迹着,之后移民到新西兰发现这些都没有了用武之地。两个月前决定开始学习前端,看了很多新的知识点。当然JS 是不得不学的一个语言,今天就讲讲Decorators的运用。在这篇文字里,我将一点点的抽丝剥茧来阐述Decorators,以至于一点点的把它的美展现出来。
如果你有过Python 的深厚背景的话,这个语法和思想应该是一见如故的感觉。学起来相对会简单很多。但是对于我这种只有过C背景的菜鸟来说 相对有点难度。不过要感谢 w3Schools。很多的语言的网站开发的语言概述 我基本上都是从上面看的。查了一下 Decorators 应该是 Typescript 里面的概念。
我一开始直接学习的 是 RN,所以对 typescript 和 Angular2 并没有太大的感觉 。但是当我读了不少代码的时候 我慢慢的发现很多的库文件都开始用typescript来编写了。还是那么的优美 我感觉是时候要来学习他们了。上周五 我非常好奇的学习了这个在ES7里面的新功能 Decorators. 我发现我有点迷恋上了它。它性感迷人的以至于我们不得不想方设法的把它运用到个个地方. 因为没有发现好的文章去写它,所以我决定把我学习的历程写出来,希望对想学习的朋友们有所帮助。
首先:Decorators 到底是一个什么东西?
下图是我从网上查到的 Decorators pattern 的一些信息:
是不是有点抽象? 那我们来个具体的模型 :
WeaponAccessory 在这里就是一个 decorator. 他们给BaseWeapon 增加了额外的功. 如果还没有看明白 不用紧张 我第一遍也没看明白。 多看几遍就好了。这里我们先给Decorators下一个定义: Decorators are functions that give additional super-powers to different function。从这个定义中我们获知: 1. Decorators 是功能 2. 这个功能是增强了额外的超能量 给被修饰的功能。想想也是 Decorators的中文意思 就是 修饰,装修的意思。装修一个东西 就是让这个东西更好看 更美丽 更实用。是吧?
一句话: 就把它认为是一个object的一次外包装。
1. 实际的 object 是被 wrapper 封装住的
2. 如果你想访问一个object ,那你一定要先访问 它的 封装wrapper
Decorators 让我们可以注释和更改类以及类的属性 再设计阶段
请注意: Decorators 仅仅只是函数
其次:我们到底怎么运用它呢?
Decorators 是在ES7里面提出的一个功能点 在这里我们将使用Babel 和 Typescript 来实现的编译器来实现他们 (在本文中 我将用 Typescript )
1. 怎么设置?
在你的 tsconfig.json 这个文件里面, 你应该把 experimentalDecorators 和 emitDecoratorMetadata 设置为 true.
{"compilerOptions":{
"module":"commonjs",
"experimentalDecorators":true,
"emitDecoratorMetadata":true,
"target":"es2015"
},
"exclude":[
"node_modules"]
}
2. 闭嘴,观察
为了让事情简单话,我一直遵循这一个学习方法那就是:少说多做. 我会不管三七二十一先不停的写代码,关注输出,然后再去思考
function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling "${propertyKey}" with`, arguments,target);
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
console.log(`Function is executed`);
return value + "; This is awesome";
};
return descriptor;
}
class JSMeetup {
speaker = "Ruban";
//@leDecorator
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1} ${arg2}`);
return `${arg1} ${arg2}`;
}
}
const meetup = new JSMeetup();
console.log(meetup.welcome("World", "Hello"));
一旦你运行了上面的代码 他们的输出会是:
Arguments Received are World Hello
World Hello
现在把代码里面的// 去掉,再运行它结果会是:
Calling "welcome" with { '0': 'World', '1': 'Hello' } JSMeetup {}
Arguments Received are Hello World
Function is executed Hello World;
This is awesome
是不是觉得很神奇?你只是用Decorator 注释了一个函数,结果就截然不同了
这到底为什么呢? 让我们一起揭开他们的面纱吧。
Decorators的类型:
1.方法修饰( Method Decorator )
2. 属性修饰(Property Decorators )
3. 类修饰(Class Decorator)
4. 参数修饰(Parameter Decorator )
好了 不要急, 让我们一个个的来揭示其中的奥妙吧。(为了方便下面标题只写英文)
Method Decorators
Method Decorators是第一个我们将 要 一起学习的修饰类型. 我们将会学习到它的一些本质。通过使用Method Decorators 我们将会学会很好的掌控输入函数和输出函数.
修饰签名(Decorator Signature)
MethodDecorator = <T>(target: Object, key: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | Void;
前件:
当我们学习写第一个Decorators,让我们一起学习一下基本知识:.
理解Decorators的参数
1. target -> 被修饰的对象
2. key -> 被修饰方法的名字
3 descriptor -> 给定的属性的标识符. 你可以看到这个属性标识符通过调用 这个函数: functionObject.getOwnPropertyDescriptor()
Property Descriptors:
看下面的输出结果 可以更好的有一个直观的概念:
着这里我们定义 一个object ,命名为o,其包含属性: foo , bar 和 foobar. 然后我们打印每一个属性的 属性描述标识 用 Object.getOwnPropertyDescriptor(). 在这里让我们快速过一下value , enumerable , configurable 和 writable 的意思.
value – > 实际的数值 或者函数 被这个object 属性拥有的
enumerable -> 是否这么属性可以被枚举。可能在便利的时候会被用到
configurable – >这个属性是否可以被配置
writable -> 这个属性是否可以被写入
正如我们所发现,所有的属性和方法都有他们自己独有的描述。Decorators会更改在这个描述去添加额外的功能。当我们知道了怎么更改和定义这些属性的时候,我们就可以用Decorators 去做一些改变了
实例( Method Decorator)
在下面的例子里,我们将去更改输入和输出的方法:
function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling "${propertyKey}" with`, arguments,target);
// Executing the original function interchanging the arguments
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
//returning a modified value
return value + "; This is awesome";
};
return descriptor;
}
class JSMeetup {
speaker = "Ruban";
//@leDecorator
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1}, ${arg2}`);
return `${arg1} ${arg2}`;
}
}
const meetup = new JSMeetup();
console.log(meetup.welcome("World", "Hello"));
如果我们run 上面的代码(Decorators被注释掉的情况下), 输出如下:
Arguments Received are World, Hello
World Hello
但是当我们使用Decorators的时候(去掉//):
Arguments Received are Hello, World
Hello World; This is awesome
到底发生了什么?
在这个修饰函数中,这个描述标识 包含了与它相关的特定的属性(前面我们讲过). 首先,我们把原始函数保存到一个变量中 (var oldValue = descriptor.value; ) . 然后,我们更改这个描述标识中的值,然后把它返回. 正如我们所见的更改函数,我们执行了原始函数但是更改了参数,同时返回了更改结果.
是不是很神奇?
Decorators 包装了我们的方法. 通过使用Decorators, 我们基本上有能力掌控输入哈数和输出函数. 我们是不是很有魔法? 哈哈
需要深思的几点:
1. Decorators 被调用是Class 被声明的时候,而不是被实例化的时候。要是后者的话 那就是虚函数了 哈哈 C++的
2. 函数Decorators 会返回一个值
3. 正如上面的例子,最好是先保存存在的标识值,然后返回一个新的。原因是当多Decorators的时候 其他的Decorators 有可能无法访问原始的东西
4. 不要使用箭头语法当设置标识里面的变量的时候
我们能用Decorators 做点什么呢?
1. 做Log
2. 格式化
3. 权限检测
4. 屏蔽复写的函数
5. 打时间戳
6. 流量限制
哈哈 当然 可能还有很多其他的用法。我们可以继续追加。。。。。
在我们结束之前 我们再过一遍主题思想:
Decorator Factory
我们可以通过使用 decorator factories 去定制化的实现我们想要的功能 :
function leDecoratorFactory(randomData: string) {
return (target, propertyKey: string, descriptor: PropertyDescriptor): any => {
var oldValue = descriptor.value;
descriptor.value = function () {
console.log(`Calling "${propertyKey}" with`, arguments, target);
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
console.log(`Function is executed`);
return value + "; This is awesome; " + randomData;
}
return descriptor;
}
}
class JSMeetup {
speaker = "Ruban";
@leDecoratorFactory("Extra Data")
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1} ${arg2}`);
return `${arg1} ${arg2}`;
}
}
const meetup = new JSMeetup();
console.log(meetup.welcome("World", "Hello"));
上面的代码的输出如下:(仔细看看 和前一个例子有什么微小的差别,找到了吗? cheer!!!!!!!!!!!!)
Arguments Received are Hello World
Hello World; This is awesome; Extra Data
总结?
Decorator Factory 可以接受用户的参数并且返回一个修饰函数.
我们可以不用再去创建相同的修饰函数了
我们可以通过一个点去访问多个不同的Decorators
重点:
Decorators 是从上到下的评估
Decorators 是从下到上的执行
Property Decorators
Property decorators 和上面讲的方法修饰很相似. 我们可以通过她去从新定义 getters,setters, 还有其他的属性类似枚举,配置等
修饰签名:
PropertyDecorator = (target: Object, key: string) => void;
前提条件:
为了更好的礼节和写属性修饰,我们需要深入了解 Object.defineProperty
思考点:
1. 没有返回值
2. defineProperty的使用
用途:
1. 更改数据
2. 验证合法性
3. 格式化
Class Decorators
Class Decorators 基本上是更改类的构造函数. 通过使用类修饰,我们可以更改或者添加新的属性,方法 到类里面去
修饰签名:
ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction;
例子:
function AwesomeMeetup<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements extra {
speaker: string = "Ragularuban";
extra = "Tadah!";
}
}
//@AwesomeMeetup
class JSMeetup {
public speaker = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.speaker;
}
}
interface extra {
extra: string;
}
const meetup = new JSMeetup() as JSMeetup & extra;
console.log(meetup.greet());
console.log(meetup.extra);
如果不开启修饰类的话 结果如下:
Hi, I’m Ruban
undefined
开启修饰类的结果如下:
Hi, I’m Ragularuban
Tadah!
由此可见类修饰接受一个参数,就是它自己. 我们可以通过它更改这个类 .
思考点:
当原始函数的构造函数被调用的时候 这个扩展类就会被调用
场景:
记录
任何你想更改的东西
Parameter Decorator:
到目前为止,我们可以认为参数修饰是被用作改变函数的参数。但是 还是有点小不同的
它可以用来标记需要受注意的参数. 我们标记这些参数,然后用函数修饰去做动作.
修饰签名:
ParameterDecorator = (target: Object, propertyKey: string, parameterIndex: number) => void;
例子:
function logParameter(target: any, key: string, index: number) {
var metadataKey = `myMetaData`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
function logMethod(target, key: string, descriptor: any): any {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
var metadataKey = `myMetaData`;
var indices = target[metadataKey];
console.log('indices', indices);
for (var i = 0; i < args.length; i++) {
if (indices.indexOf(i) !== -1) {
console.log("Found a marked parameter at index" + i);
args[i] = "Abrakadabra";
}
}
var result = originalMethod.apply(this, args);
return result;
}
return descriptor;
}
class JSMeetup {
//@logMethod
public saySomething(something: string, @logParameter somethingElse: string): string {
return something + " : " + somethingElse;
}
}
let meetup = new JSMeetup();
console.log(meetup.saySomething("something", "Something Else"));
没有修饰的结果:
something : Something Else
修饰后的结果:
something : Abrakadabra
发生了什么?
Parameter Decorator 有三个参数(target, key,index). 在参数修饰中 , 我们基本上标记出参数再一个数组中 (myMetaData ).
然后,我们使用函数修饰来读取这些标识的参数(in myMetaData),最后更改这些参数
思考点:
没有返回值
通常不不能单独使用
使用:
依赖注入
标记参数然后进行判断合法性
好了 今天就到此结束了。
最后给几个不错的开源项目 拜读一下大师怎么具体使用的 :
TypeDI – Dependency Injection based on Typescript and Decorators
TypeORM – ORM based on Typescript and Decorators
由于是第一次写东西. 写的不好的地方 请多多指教。 希望对大家有帮助
Fred
奥克兰