一. 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;}@validategreet(@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高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

    Python高级编程--装饰器Decorator超详细讲解(上篇) 送你小心心记得关注我哦!! 进入正文 全文摘要 装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向 ...

  2. python装饰器-理解Python装饰器(Decorator)

    理解Python装饰器(Decorator) Python装饰器看起来类似Java中的注解,然鹅和注解并不相同,不过同样能够实现面向切面编程. 想要理解Python中的装饰器,不得不先理解闭包(clo ...

  3. 如何使用python装饰器_Python学习之如何使用装饰器 @decorator

    建议大家在看这篇文章之前还是点击上面这篇文章看一下,先了解一下什么是装饰器之后再看. 猿人学python之我的学习笔记分享:如何使用装饰器 @decorator 上面代码中使用装饰器的方法看起来有点复 ...

  4. 装饰器Decorator(函数的装饰)

    一.LEGB函数作用域的优先级和解析 函数是function的一个对象,被调用完后内部变量就会被回收,被引用的除外(例如return的变量) 1.   local :函数内部作用域 2.  enclo ...

  5. python描述器descriptor_python装饰器decorator、描述器descriptor

    背景 image.png 装饰器decorator 返回值为另一函数的函数,通常使用 @wrapper语法. 装饰器的常见例子包括 装饰器语法只是一种语法快捷方式,以下两个函数定义在语义上完全等价: ...

  6. 理解TS装饰器Decorator

    理解TS装饰器Decorator 介绍 类装饰器 方法装饰器 介绍 详细的关于ts中装饰器的使用和语法可以参考 ts官网中decorator这一章节,或者阮一峰的ts教程的decorator章节这里这 ...

  7. python装饰器(Decorator)【修改】

    python装饰器(Decorator) python装饰器(Decorator) python装饰器(decorator)官方定义 [https://docs.python.org/zh-cn/3/ ...

  8. [react] 装饰器(Decorator)在React中有什么应用?

    [react] 装饰器(Decorator)在React中有什么应用? connect.withRouter,等类似的高阶组件都可以用装饰器来使用 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放 ...

  9. [转载] Python学习笔记——用装饰器decorator和Memoization记忆化提高效率,原理讲清楚了

    参考链接: 在Python中使用装饰器进行记忆 Python学习笔记--用装饰器decorator和Memoization记忆化提高效率 装饰器Memoization记忆化运用`functools`中 ...

  10. py函数式编程(高阶函数map/reduce/filter/sorted、闭包函数/返回函数、匿名函数lamber、@装饰器decorator、偏函数functool.partial())

    #py函数式编程.py #高阶函数map/reduce/filter/sorted.闭包函数/返回函数.匿名函数lamber.@装饰器decorator.偏函数functool.partial()# ...

最新文章

  1. 云原生时代消息中间件的演进路线
  2. QIIME 2教程. 10数据导出ExportingData(2021.2)
  3. 如何利用循环代替递归以防止栈溢出(译)
  4. php rmdir 返回值,php通过rmdir删除目录的简单用法
  5. java中的数组、队列、堆栈
  6. Java笔记-spring-rabbitmq中使用@RabbitListener消费(手动确认,获header数据)
  7. 苹果Mac Studio 3月18日发售 但法国一用户已提前收货
  8. 已有项目要不要迁移到Addressable系统?
  9. PayPal网站付款标准版(for PHP)
  10. MyBatis 自定义插件
  11. win10系统下mysql5.7初始化失败怎么办?
  12. java个人所得税计算器
  13. 推荐系统--基于图的推荐算法
  14. 用U盘制作PE启动盘安装Windows10系统
  15. 阿里云服务器配置Tomcat
  16. 计算机图形学:Mesh
  17. 王艳 201771010127《面向对象程序设计(Java)》第四周学习总结
  18. AndroidStudio选择及裁剪图片
  19. Js(三)将es6语法转换成es5语法
  20. 百度地图使用、调起本机App地图

热门文章

  1. 安卓开发使用ttf文字_Android中正确使用字体图标(iconfont)的方法
  2. selinux= 为 disabled_Selinux安全加固
  3. python的scapy_Python Scapy vs dp
  4. unity text不能刷新_unity自走棋制作(二)-UI制作
  5. day8 Java学习(面向对象.多态接口)
  6. Web框架之Tornado
  7. keep-alive使用笔记
  8. Repository(资源库)模式
  9. String.Format数字格式化输出 {0:N2} {0:D2} {0:C2}
  10. mysql select call_MySQL的SQL语句 - 数据操作语句(1)- CALL 语句