随着 ES6 和 TypeScript 中类的引入,在某些场景需要在不改变原有类和类属性的基础上扩展些功能,这也是装饰器出现的原因。

装饰器简介

作为一种可以动态增删功能模块的模式(比如 redux 的中间件机制),装饰器同样具有很强的动态灵活性,只需在类或类属性之前加上 @方法名 就完成了相应的类或类方法功能的变化。

不过装饰器模式仍处于第 2 阶段提案中,使用它之前需要使用 babel 模块 transform-decorators-legacy 编译成 ES5 或 ES6。

在 TypeScript 的 lib.es5.d.ts 中,定义了 4 种不同装饰器的接口,其中装饰类以及装饰类方法的接口定义如下所示:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

下面对这两种情况进行解析。

作用于类的装饰器

当装饰的对象是类时,我们操作的就是这个类本身

@log
class MyClass { } function log(target) { // 这个 target 在这里就是 MyClass 这个类
target.prototype.logger = () => `${target.name} 被调用`
} const test = new MyClass()
test.logger() // MyClass 被调用

由于装饰器是表达式,我们也可以在装饰器后面再添加提个参数:

@log('hi')
class MyClass { } function log(text) {
return function(target) {
target.prototype.logger = () => `${text},${target.name} 被调用`
}
} const test = new MyClass()
test.logger() // hello,MyClass 被调用

在使用 redux 中,我们最常使用 react-redux 的写法如下:

@connect(mapStateToProps, mapDispatchToProps)
export default class MyComponent extends React.Component {}

经过上述分析,我们知道了上述写法等价于下面这种写法:

class MyComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

作用于类方法的装饰器

与装饰类不同,对类方法的装饰本质是操作其描述符。可以把此时的装饰器理解成是 Object.defineProperty(obj, prop, descriptor) 的语法糖,看如下代码:

class C {
@readonly(false)
method() { console.log('cat') }
} function readonly(value) {
return function (target, key, descriptor) { // 此处 target 为 C.prototype; key 为 method;
// 原 descriptor 为:{ value: f, enumarable: false, writable: true, configurable: true }
descriptor.writable = value
return descriptor
}
} const c = new C()
c.method = () => console.log('dog') c.method() // cat

可以看到装饰器函数接收的三个参数与 Object.defineProperty 是完全一样的,具体实现可以看 babel 转化后的代码,主要实现如下所示:

var C = (function() {
class C {
method() { console.log('cat') }
} var temp
temp = readonly(false)(C.prototype, 'method',
temp = Object.getOwnPropertyDescriptor(C.prototype, 'method')) || temp // 通过 Object.getOwnPropertyDescriptor 获取到描述符传入到装饰器函数中 if (temp) Object.defineProperty(C.prototype, 'method', temp)
return C
})()

再将再来看看如果有多个装饰器作用于同一个方法上呢?

class C {
@readonly(false)
@log
method() { }
}

经 babel 转化后的代码如下:

desc = [readonly(false), log]
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);

可以清晰地看出,经过 reverse 倒序后,装饰器方法会至里向外执行。

相关链接

javascript-decorators

Javascript 中的装饰器

JS 装饰器(Decorator)场景实战

修饰器

Babel

JS 装饰器解析的更多相关文章

  1. JS 装饰器,一篇就够

    更多文章,请在Github blog查看 在 ES6 中增加了对类对象的相关定义和操作(比如 class 和 extends ),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并 ...

  2. JS装饰器模式

    装饰器模式:在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法),保护原有功能的完整性需要条件:原对象,新内容(属性/方法)个人理解:重新实现一下,原对象的方法,在方法内容,先执行原对象的 ...

  3. Python3 装饰器解析

    第6章 函数 6.1 函数的定义和调用 6.2 参数传递 6.3 函数返回值 6.4 变量作用域 6.5 匿名函数(lambda) 6.6 递归函数 6.7 迭代器 6.8 生成器 6.9 装饰器 6 ...

  4. 初学Python——装饰器

    一.什么是装饰器 当我们做好一个产品之后,需要对它进行不断地维护,对某些函数增加一些功能.这个时候如果去修改源代码将是非常不合适的.(原因:1.原则上已经写好的函数尽量不去修改它,因为一旦修改可能会导 ...

  5. Python自动化开发 - 装饰器

    本节内容 一.装饰器导引 1.函数对象特性 2.扩展业务功能需求 3.各种解决方案 二.装饰器解析 1.装饰器基本概念 2.无参装饰器解析 一.装饰器导引 1.函数对象特性 #### 第一波 #### ...

  6. python系列4之装饰器

    目录 递归算法解析 冒泡排序解析 装饰器解析 一. 递归 1. 递归的定义 递归(Recursion),又成为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法.递归一词还较长用于描述以 ...

  7. Fluent_Python_Part3函数即对象,07-closure-decoration,闭包与装饰器

    第7章 函数装饰器和闭包 装饰器用于在源码中"标记"函数,动态地增强函数的行为. 了解装饰器前提是理解闭包. 闭包除了在装饰器中有用以外,还是回调式编程和函数式编程风格的基础. 1 ...

  8. [Python进阶]002.装饰器(1)

    装饰器(1) 介绍 HelloWorld 需求 使用函数式编程 加入装饰器 解析 介绍 Python的装饰器叫Decorator,就是对一个模块做装饰. 作用: 为已存在的对象添加额外功能. 与Jav ...

  9. python 装饰器(四):装饰器基础(三)叠放装饰器,参数化装饰器

    叠放装饰器 示例 7-19 演示了叠放装饰器的方式:@lru_cache 应用到 @clock 装饰fibonacci 得到的结果上.在示例 7-21 中,模块中最后一个函数应用了两个 @htmliz ...

随机推荐

  1. 接口-以PHP为例

    <?php //使用程序模拟现实情况 //使用规范(方法/属性) interface iUSB { public function start(); public function stop() ...

  2. PHP安全、Sql防注入安全汇总

    利用Mysqli和PDO 产生原因 主要就是一些数据没有经过严格的验证,然后直接拼接 SQL 去查询.导致漏洞产生,比如: $id = $_GET['id']; $sql = "SELECT ...

  3. Asp.Net Core Identity+EFCore + Mysql踩坑记录

    搭建基础框架准备试试传说中的Identity,本以为很顺利,结果一路踩了N多坑 遂就把过程记录下来.方便自己以后查看,也希望能帮到遇到同样问题的朋友. 1.首先,引入Identity需要的类库,还有M ...

  4. spring-boot-starter家族成员简介

    应用程序starters 以下应用程序starters是Spring Boot在org.springframework.boot组下提供的: springboot使用指南https://docs.sp ...

  5. 【BZOJ3529】【SDOI2014】 数表

    Time Limit: 10 Sec Memory Limit: 512 MB Description ​ 有一张\(n×m\)的数表,其第i行第j列(\(,1 \le i \leq n,1 \le ...

  6. Spring注解简介

    提供了基于注解(Annotation-based)的配置,我们可以通过注解的方式来完成注入依赖. 1. 使用注解方式配置 我们需要修改spring配置文件的头信息,修改部分红色标注,如下: <c ...

  7. Hi3531 SDK v2.0.8.0 安装

    1.Hi3531 SDK包位置 在"Hi3531_V100R001***/01.software/board"目录下,您可以看到一个 Hi3531_SDK_Vx.x.x.x.tgz ...

  8. linux之x86裁剪移植---grub 识别文件系统

    grub主要作用是找到内核(kernel)与部分核心模块的镜像(initrd,主要是sata硬盘驱动之类的模块),把它们导入内存中运行.kernel与initrd放在文件系统中,因此grub必须有识别 ...

  9. VBR与CBR的区别是什么?

    VBR是动态码率.CBR是静态码率. VBR(Variable Bitrate)动态比特率.也就是没有固定的比特率,压缩软件在压缩时根据音频数据即时确定使用什么比特率,这是以质量为前提兼顾文件大小的方 ...

  10. Trouble HDU - 4334

    Hassan is in trouble. His mathematics teacher has given him a very difficult problem called 5-sum. P ...