一. 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. 初学angular项目中遇到的一些问题

    1.当angular渲染完成后操作DOM树方法 //当数据渲染完毕 ngApp.directive('repeatFinish', function () {            return {  ...

  2. ES8 async/await语法

    Async/await的主要益处是可以避免回调地狱(callback hell)问题 Chromium JavaScript引擎 从v5.5开始支持async/await功能,Chromium Jav ...

  3. CVE-2018-20129:DedeCMS V5.7 SP2前台文件上传漏洞

    一.漏洞摘要 漏洞名称: DedeCMS V5.7 SP2前台文件上传漏洞上报日期: 2018-12-11漏洞发现者: 陈灿华产品首页: http://www.dedecms.com/软件链接: ht ...

  4. SDKmanager的位置

    最近学习Android Studio 因为配置的问题,需要查找SDKmanager的位置 一下是查找方法: 查找到啦~

  5. 约瑟夫环问题 --链表 C语言

    总共有m个人在圆桌上,依次报名,数到第n个数的人退出圆桌,下一个由退出人下一个开始继续报名,循环直到最后一个停止将编号输出 #include <stdio.h>#include <s ...

  6. 两层fragment嵌套时出现空白,(收藏别人的)

    完美解决 两层Fragment,内层空白 转载:http://blog.csdn.net/bingospunky/article/details/51352400 目录(?)[+] 前言 两层Frag ...

  7. C语言面试题分类->回调

    本文主要讲解如果实现回调,特别是在封装接口的时候,回调显得特别重要,我们首先假设有两个程序员在写代码,A程序员写底层驱动接口,B程序员写上层应用程序,然而此时底层驱动接口A有一个数据d需要传输给B,此 ...

  8. 微信小程序开发资料

      微信开放平台:主要面向App开发者.通常是拥有成熟的应用程序后,通过开放平台将内容分享到朋友圈或发送给某个微信好友/群.例如QQ音乐分享,美图秀秀修改过的照片直接发朋友圈或聊天. 微信公众平台:强 ...

  9. 高级Java面试总结3

    1,java堆,分新生代老年代,新生代有Eden,from surviver,to surviver三个空间,堆被所有线程共.eden内存不足时,发生一次minor GC,会把from survivo ...

  10. 动态规划----最长递增子序列问题(LIS)

    题目: 输出最长递增子序列的长度,如输入 4 2 3 1 5 6,输出 4 (因为 2 3 5 6组成了最长递增子序列). 暴力破解法:这种方法很简单,两层for循环搞定,时间复杂度是O(N2). 动 ...