一. 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. python-邮件提醒功能

      当scrapy爬取完成以后会发送详细信息到邮箱 1.首先编写邮件发送模块 #!usr/bin/env python # -*- coding:utf-8 -*- """ ...

  2. js拼接字符串后swiper不能动的解决方案

    swiper的配置一定要放在拼接字符串之后,紧随其后,如果放在其他的位置,swiper是不识别HTML的.

  3. 初学spring boot 一

    建立maven项目,在prom.xml中导入依赖 <parent> <groupId>org.springframework.boot</groupId> < ...

  4. Android简单计时器

    本文利用ContextMenu(上下文菜单),Chronometer实现简单计数器. Main.xml: <?xml version="1.0" encoding=" ...

  5. 接口调试之Postman 使用方法详解

    一.Postman背景介绍 用户在开发或者调试网络程序或者是网页B/S模式的程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具.今天给大家介 ...

  6. div里包含img底部多出3px的解决办法

    如果将一个img放在div里面,你会发现在img下面无端端的就多出3px的空白出来.padding.margin.border都设为0,无效!那么怎么解决这个问题呢? 问题图: 解决后的效果: 这个B ...

  7. SUSE12Sp3-MongoDB安装

    1.解压 sudo mkdir /usr/local/mongodb # 创建mongodb目录 将mongodb-linux-x86_64-suse12-4.0.6.tgz复制到/usr/local ...

  8. numpy.random 常用函数详解之排列乱序篇(Permutations)

    1.numpy.random.shuffle(x) 参数:填入数组或列表. 返回值:无. 函数功能描述:对填入的数组或列表进行乱序处理,shape保持不变. 2.numpy.random.permut ...

  9. [Swift]LeetCode363. 矩形区域不超过 K 的最大数值和 | Max Sum of Rectangle No Larger Than K

    Given a non-empty 2D matrix matrix and an integer k, find the max sum of a rectangle in the matrix s ...

  10. [Swift]LeetCode830. 较大分组的位置 | Positions of Large Groups

    In a string S of lowercase letters, these letters form consecutive groups of the same character. For ...