一. Decorator装饰器

修饰器是ES7加入的新特性,Angular中进行了大量使用,有很多内置的修饰器,后端的同学一般称之为“注解”。修饰器的作用,实际上就是设计模式中常说的装饰者模式的一种实现,早在ES6开始,设计模式原生化就已经是非常明显的趋势了,无论是for..of..Iterator接口的配合内化了迭代者模式Proxy对象实现的代理模式等等,都可以看出Javascript逐渐走向标准化的趋势和决心。

装饰者模式,是指在不必改变原类文件或使用继承的情况下,动态地扩展一个对象的功能,为对象增加额外特性的一种设计模式。考虑到javascript中函数参数为对象时只传递地址这一特性,装饰者模式实际上是非常好复现的,掌握其基本知识对于理解Angular技术栈的原理和执行流程是必不可少的,从结果的角度来看,使用装饰器和直接修改类的定义没有什么区别,但使用装饰器更符合开放封闭原则,且更符合声明式的思想,本文着重分析Typescript中支持的几种不同的装饰器用法。

二. Typescript中的装饰器

2.1 类装饰器

类装饰器,就是用来装饰类的,它只接受一个参数,就是被装饰的类。下面的示例使用@testable修饰器为已定义的类加上一个__testable属性:

//装饰器修改的是类定义的表现,故在javascript中模拟时需要直接将变化添加至原型上
function testable(target: Function):void{
target.prototype.__testable = false;
} //使用类装饰器
@testable
class Person{
constructor(){}
} //测试装饰后的结果
let person = new Person();
console.log(person.__testable);//false

另一方面,我们可以使用工厂函数的方法生成一个可接收附加参数的装饰器,借助高阶函数的思路不难理解,例如Angular中常见的这种形式:

//Angular中的组件定义
@Component({
selector:'hero-detail',
templateUrl:'hero-detail.html',
styleUrls:['style.css']
})
export Class MyComponent{
constructor(){}
} //@Component装饰者类的作用机制可以理解为:
function Component(params:any){
return function(target: Function):void{
target.prototype.metadata = params;
}
}

这样在组件被实例化时,就可以获取到传入的元数据信息。换句话说,Component({...})执行后返回的函数才是真正的类装饰器,Component是一个接受参数然后生成装饰器的函数,也就是装饰器工厂,从元编程的角度来讲,相当于修改了new操作符的行为。

2.2 方法装饰器

方法修饰器声明在一个方法的声明之前,会被应用到方法的属性描述符上,可以用来检视,修改或者替换方法定义。它接收如下三个参数:

  • 1.静态成员时参数是类的构造函数,实例成员时传入类的原型对象。
  • 2.成员名
  • 3.成员属性描述符

下面的装饰器@enumerable将被修饰对象修改为可枚举:

//方法装饰器,返回值会直接赋值给方法的属性描述符。
function enumerable(target: any, propertyKey: string, descriptor:PropertyDescriptor):void{
descriptor.enumerable = true;
} class Person{
constructor(){} @enumerable//使用方法装饰器
sayHi(){
console.log('Hi');
}
} //测试装饰后的结果
let person = new Person();
console.log(person.__testable);//false

更常用的方式依然是利用高阶函数返回一个可被外部控制的装饰器:

function enumerable(value: boolean){
return function (target: any, propertyKey: string, descriptor:PropertyDescriptor):void{
descriptor.enumerable = true;
}
}

2.3 访问器装饰器

访问器,一般指属性的get/set方法,和普通方法装饰器用法一致,需要注意的是typescript中不支持同时装饰一个成员的get访问器和set访问器。

2.4 属性装饰器

属性装饰器表达式运行时接收两个参数:

  • 1.对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
  • 2.成员名

Typescript官方文档给出的示例是这样的:

class Greeter {
@format("Hello, %s") greeting: string; constructor(message: string){
this.greeting = message;
} greet(){
let formatString = getFormat(this, 'greeting');
return formatString.replace('s%',this.greeting);
} }

然后定义@format装饰器和getFormat函数:

.import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
} function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

与方法装饰器相比,属性装饰器的形参列表中并没有属性描述符,因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,也无法监视属性的初始化方法。TS中的属性描述符单独使用时只能用来监视类中是否声明了某个名字的属性,示例中通过外部功能扩展了其实用性。Angular中最常见的属性修饰器就是Input( )output( )

2.5 参数装饰器

参数装饰器一般用于装饰参数,在类构造函数或方法声明中装饰形参。

它在运行时被当做函数调用,传入下列3个参数:

  • 1.静态成员时接收构造函数,实例成员时接收原型对象。
  • 2.成员名
  • 3.参数在函数参数列表中的索引。

TS中参数装饰器单独使用时只能用来监视一个方法的参数是否被传入,Typescript官方给出的示例如下:

class Greeter {
greeting: string; constructor(message: string) {
this.greeting = message;
} @validate
greet(@required name: string) {//此处使用了参数修饰符
return "Hello " + name + ", " + this.greeting;
}
}

两个装饰器的定义如下:

import "reflect-metadata";
const requiredMetadataKey = Symbol('required'); /*
*@required参数装饰器
*实现的功能就是当函数的参数必须填入时,将相关信息存储到一个外部的数组中,可以看出参数装饰器并*未对参数本身做出什么修改。
*/
function required(target: Object, propertyKey:string | symbol, parameterIndex: number){
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
} /*
*@validate装饰器为方法装饰器
*展示了如何通过操作方法属性描述符中的value属性来实现方法的代理访问。
*/
function validate(target:any, propertyName: string, descriptor:TypedPropertyDescriptor<Function>){
let method = descriptor.value;//方法的属性修饰符的value就是方法的函数表达式
descriptor.value = function(){
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);//在外部存储中查找是否有必填参数
if (requiredParameters){
for(let parameterIndex of requiredParameters){
if(parameterIndex >= arguments.length || arguments[parameterIndex] === undefined){
//传入参数不足或被约束参数为undefined时抛出错误。
throw new Error('Missing required argument');
}
}
}
return method.apply(this, arguments);//如果没有任何错误抛出则继续执行原函数
}
}

在Typescript中,装饰器的运行顺序基本依照参数装饰器,方法装饰器,访问符装饰器,属性装饰器,类装饰器这样的顺序来运行,所以参数装饰器和方法装饰器可以联合使用实现一些额外功能。

三. 用ES5代码模拟装饰器功能

ES5来模拟一下上述的方法装饰器和参数装饰器联合作用的例子,就很容易看出装饰器的作用:

//使用ES5语法模拟装饰器
function Greeter(message){
this.greeting = message;
} Greeter.prototype.greet = function(name){
return "Hello " + name + ", " + this.greeting;
} //外部存储的必要性校验
requiredArray = {}; //参数装饰器
function requireDecorator(FnKey,paramsIndex){
requiredArray[FnKey] = paramsIndex;
} //装饰器函数
function validateDecorator(Fn,FnKey){
let method = Fn;
return function(){
let checkParamIndex = requiredArray[FnKey];
if(checkParamIndex > arguments.length-1 || arguments[checkParamIndex] === undefined){
throw new Error('params invalid');
}
return method.apply(this, arguments);
}
} //运行装饰
requireDecorator('greet',0);
Greeter.prototype.greet = validateDecorator(Greeter.prototype.greet, 'greet'); //测试装饰
let greeter = new Greeter('welcome to join the conference');
console.log(greeter.greet('Tony'));
console.log(greeter.greet());

在node环境中运行一下就可以看到,greet( )方法在未传入参数时会报错提示。

四. 小结

装饰器实际上就是一种更加简洁的代码书写方式,从代码表现来理解,就是使用闭包和高阶函数扩展或者修改了原来的表现,从功能角度来理解,达到了不修改内部实现的前提下动态扩展和修改类定义的目的。

【Angular专题】 (3)装饰器decorator,一块语法糖的更多相关文章

  1. Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析

    装饰器 / Decorator 目录 关于闭包 装饰器的本质 语法糖 装饰器传入参数 1 关于闭包 / About Closure 装饰器其本质是一个闭包函数,为此首先理解闭包的含义. 闭包(Clos ...

  2. python 语法之 装饰器decorator

    装饰器 decorator 或者称为包装器,是对函数的一种包装. 它能使函数的功能得到扩充,而同时不用修改函数本身的代码. 它能够增加函数执行前.执行后的行为,而不需对调用函数的代码做任何改变. 下面 ...

  3. python语法32[装饰器decorator](转)

    一 装饰器decorator decorator设计模式允许动态地对现有的对象或函数包装以至于修改现有的职责和行为,简单地讲用来动态地扩展现有的功能.其实也就是其他语言中的AOP的概念,将对象或函数的 ...

  4. python 装饰器(decorator)

    装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...

  5. Python装饰器--decorator

    装饰器 装饰器实质是一个函数,其作用就是在不改动其它函数代码的情况下,增加一些功能.如果我们需要打印函数调用前后日志,可以这么做 def log(func): print('%s is running ...

  6. python函数编程-装饰器decorator

    函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...

  7. Python_高阶函数、装饰器(decorator)

    一.变量: Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来. 对变量赋值x = y是把变量 ...

  8. Python学习——装饰器/decorator/语法糖

    装饰器 定义:本质是函数,为其他函数添加附加的功能. 原则:1.不能修改原函数的源代码 2.不能修改被原函数的调用方式 重点理解: 1.函数即“变量” 2.高阶函数:返回值中包含函数名 3.嵌套函数 ...

  9. es6 装饰器decorator的使用 +webpack4.0配置

    decorator 装饰器 许多面向对象都有decorator(装饰器)函数,比如python中也可以用decorator函数来强化代码,decorator相当于一个高阶函数,接收一个函数,返回一个被 ...

随机推荐

  1. mac上安装iterm2的一些步骤记录

    1.首先到item官网上下载item   下载地址 http://iterm2.com/ 2.把iitem2设置为默认终端: 3.设置快速打开关闭的hotkey 我们这里设置为command + T键 ...

  2. zipkin

    转:https://blog.csdn.net/liaokailin/article/details/52077620 zipkin为分布式链路调用监控系统,聚合各业务系统调用延迟数据,达到链路调用监 ...

  3. 百度网盘免VIP全速下载!

    不知道大家在用百度网盘下载文件时会不会遇到这样一个问题: 过分! 太过分了! 100M的宽带你就给我限速到20KB/s... 当然 解决办法有很多 1.充钱(这辈子都不可能的) ······ 百度上有 ...

  4. Python练手例子(13)

    73.反向输出一个链表. #python3.7 if __name__ == '__main__': ptr = [] for i in range(5): num = int(input('Plea ...

  5. js深度复制三种方法

    1.用递归的方式进行深度复制 2.用JSON.stringify加上JSON.parse()进行深度复制 3.用jquery中自带的方法$.extend()进行深度复制 具体实现代码可百度自行查询

  6. 《Android插件化开发指南》面世

    本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...

  7. 255.Spring Boot+Spring Security:使用md5加密

    说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)h ...

  8. TensorFlow.org教程笔记(一)Tensorflow初上手

    本文同时也发布在自建博客地址. 本文翻译自www.tensorflow.org的英文教程. 本文档介绍了TensorFlow编程环境,并向您展示了如何使用Tensorflow解决鸢尾花分类问题. 先决 ...

  9. [Swift]LeetCode501. 二叉搜索树中的众数 | Find Mode in Binary Search Tree

    Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred ...

  10. [Swift]LeetCode1028. 从先序遍历还原二叉树 | Recover a Tree From Preorder Traversal

    We run a preorder depth first search on the root of a binary tree. At each node in this traversal, w ...