Javascript 里的装饰器目前还处于 stage-2 阶段,借助 TypeScript 或者 Babel,已经有大量的优秀开源项目深度用上它了,比如:VS Code:

理解装饰器有助于帮助我们更好的看到这些优秀项目的源码核心思想。包括AOP,IoC,DI等编程思想:

  • AOP 主要意图是将日志记录,性能统计,安全控制,异常处理等辅助功能逻辑代码从业务逻辑代码中解耦划分出来,将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
  • IoC 即控制反转 (Inversion of Control),是解耦的一种设计理念,IoC 控制反转的设计模式可以大幅度地降低了程序的耦合性。
  • DI 即依赖注入 (Dependency Injection),是 IoC 的一种具体实现。

装饰器在 VS Code 的控制反转设计模式里,其主要作用是实现 DI 依赖注入的功能以及将部分重复的写法进行精简。使用 IoC 前:

使用 IoC 后:

装饰器的存在就是希望实现装饰器模式的设计理念而装饰器的语法是一种便捷的语法糖(写法),通过 @ 来引用,需要编译后才能运行。

Object.getOwnPropertyDescriptor(obj, prop) 方法返回指定对象上一个自有属性对应的属性描述符对象。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

​​​​​​Object.defineProperty(obj, prop, descriptor)方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

实现一个记录函数执行耗时的功能的装饰器 

1. 编写装饰器函数业务逻辑代码

function logTime(target, key, descriptor) {const oldMethed = descriptor.value;const logTime = function (...arg) {let start = +new Date();try {/** 调用函数 */return oldMethed.apply(this, arg); } finally {let end = +new Date()console.log(`耗时: ${end - start}ms`);}}descriptor.value = logTime;return descriptor;
}

2. 语法糖装饰指定属性

@logTime
fn() {// do something ...
}

无论是最新版的 Chrome 浏览器还是 Node.js 都不能直接运行带有 @Decorator 语法糖的代码。我们需要借助 TypeScript 或者 Babel 的能力,将源码编译后才能正常运行。而在 TypeSciprt Playground 上,我们可以直接看到编译后代码:

/*** 1. (this && this.__decorate)  this 是指 window 对象,这一步的含义是避免重复定义 __decorate 函数;*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;return c > 3 && r && Object.defineProperty(target, key, r), r;
};/** 精简后 */
var __decorate = function (decorators, target, key, desc) {var c = arguments.length;/** 备份原来类构造器 (Class.prototype) 的属性描述符 (Descriptor) */var r = desc = Object.getOwnPropertyDescriptor(target, key),var d;for (var i = decorators.length - 1; i >= 0; i--) {/** d 为装饰器业务逻辑函数 */if (d = decorators[i]) {/*** 执行 d,并传入 target 类构造器,key 属性名,r 属性描述符 */r = d(target, key, r) || r;}}/** 用装饰器函数覆盖原来属性描述符 */Object.defineProperty(target, key, r)return r;
};

装饰器工具函数(__decorate)的执行会传入以下 4 个参数:

  1. 装饰器业务逻辑函数

  2. 类的构造器

  3. 类的构造器属性名

  4. 属性描述符(可以为 null)。

给 logTime 添加参数,即实现带参数的装饰器:装饰器工厂函数

logTime 是个高阶函数,可以理解成装饰器工厂函数,其接收参数执行后,返回一个装饰器函数:

function logTime(tag) { return function(target, key, descriptor) {const oldMethed = descriptor.valueconst logTime = function (...arg) {let start = +new Date()try {return oldMethed.apply(this, arg)} finally {let end = +new Date()console.log(`【${tag}】耗时: ${end - start}ms`)}}descriptor.value = logTimereturn descriptor}
}/** 使用 */
@logTime('');
fn() {// do something ...
}

装饰器的种类:类、属性、方法、参数、访问器

装饰器一共有 5 种类型:

        1. 类装饰器

/** * 类装饰器* @params:target 类的构造器* @returns:如果类装饰器返回了一个值,她将会被用来代替原有的类构造器的声明。*/
function classDecorator(target: any) {return // ...
};

因此,类装饰器适合用于继承一个现有类并添加一些属性和方法。例如我们可以添加一个 addToJsonString 方法给所有的类来新增一个 toString 方法:

function addToJSONString(target) {return class extends target {toJSONString() {return JSON.stringify(this);}};
}@addToJSONString
class C {public foo = "foo";public num = 24;
}console.log(new C().toJSONString())
// "{"foo":"foo","num":24}"

        2. 属性装饰器

/** * 属性装饰器* @params target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链*         propertyKey: 属性的名称* @returns 返回的结果将被忽略。*/
function propertyDecorator(target: any, propertyKey: string) {}

利用属性装饰器,可以实现当属性改变时触发指定函数的监听功能 :

function observable(fnName) {  // 装饰器工厂函数return function (target: any, key: string): any {  // 装饰器let prev = target[key];Object.defineProperty(target, key, {set(next) {target[fnName](prev, next);prev = next;}})}
}class Store {@observable('onCountChange')count = -1;onCountChange(prev, next) {console.log('>>> count has changed!')console.log('>>> prev: ', prev)console.log('>>> next: ', next)}
}const store = new Store();
store.count = 10
// >>> count has changed!
// >>> prev: ",  undefined
// >>> next: ",  -1
// >>> count has changed!"
// >>> prev: ",  -1
// >>> next: ",  10

        3. 方法装饰器

/** * 访问器装饰器* @params target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链*         propertyKey: 属性的名称*         descriptor: 属性的描述器 - { value writable enumerable configurable}* @returns 如果返回了值,它会被用于替代属性的描述器。*/
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {return // ...
};

利用方法装饰器可以实现与 Before / After 钩子 相关的场景功能。

        4. 访问器装饰器

/** * 访问器装饰器* @params target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链*         propertyKey: 属性的名称*         descriptor: 属性的描述器 - { get, set, enumerable, configurable }* @returns 如果返回了值,它会被用于替代属性的描述器。*/
function accessorDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {return // ...
};

利用访问器装饰器,可以将某个属性在赋值或访问的时候做一层代理,比如额外相加一个值:

function addExtraNumber(num) {return function (target, propertyKey, descriptor) {const original = descriptor.set;descriptor.set = function (value) {const newObj = {}Object.keys(value).forEach(key => {newObj[key] = value[key] + num})return original.call(this, newObj)}}
}class C {private _point = { x: 0, y: 0 }@addExtraNumber(2)set point(value: { x: number, y: number }) {this._point = value;}get point() {return this._point;}
}const c = new C();
c.point = { x: 1, y: 1 };console.log(c.point)
/** {"x": 3,"y": 3}
*/

        5. 参数装饰器

/** * 参数装饰器* @params target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链*         methedKey: 方法的名称,注意!是方法的名称,而不是参数的名称*         parameterIndex: 参数在方法中所处的位置的下标* @returns 返回的结果将被忽略。*/
function parameterDecorator(target: any, methedKey: string, parameterIndex: number) {}

一般都被用于记录可被其它装饰器所使用的信息,比如,参数,方法名等:

function Log(target, methedKey, parameterIndex) {console.log(`方法名称 ${methedKey}`);console.log(`参数顺序 ${parameterIndex}`);
}class Student {watch(@Log bookName) {console.log(`看了一本书:${bookName} `)}
}// "方法名称 watch"
// "参数顺序 0"

多个装饰器的执行顺序

同种装饰器组合,其顺序会像剥洋葱一样,先从外到内进入,然后由内向外执行。和 Koa 的中间件顺序类似。

function dec(id){console.log('装饰器初始化', id);return function (target, property, descriptor) { // 装饰器函数console.log('装饰器执行', id);}
}class Example {@dec(1)@dec(2)method(){}
}/** dec(1), dec(2) 初始化立即执行, dec 返回的装饰器函数倒序执行 */
/** 编译后的代码 */
var __decorate = function (decorators, target, key, desc) {var c = arguments.length,r = desc = Object.getOwnPropertyDescriptor(target, key),d;for (var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = d(target, key, r) || r;Object.defineProperty(target, key, r)return r;
};function dec(id) {console.log('装饰器初始化', id);return function (target, property, descriptor) {console.log('装饰器执行', id);};
}
class Example {method() { }
}
__decorate([dec(1),dec(2)
], Example.prototype, "method", null);

不同类型装饰器顺序,实例成员最高,内部成员里面的装饰器则按定义顺序执行,类装饰器最低:

  1. 实例成员:(参数 > 方法) / 访问器 / 属性 装饰器 (按顺序)

  2. 静态成员:(参数 > 方法) / 访问器 / 属性 装饰器 (按顺序)

  3. 构造器:参数装饰器

  4. 类装饰器

比如:

function decorator(key: string): any {return function () {console.log("执行: ", key);};
}@decorator("8. 类")
class C {@decorator("4. 静态属性")static prop?: number;@decorator("5. 静态方法")static method(@decorator("6. 静态方法的参数") foo) {}constructor(@decorator("7. 构造器的参数") foo) {}@decorator("2. 实例方法")method(@decorator("1. 实例方法的参数") foo) {}@decorator("3. 实例属性")prop?: number;
}// "执行: ",  "1. 实例方法的参数"
// "执行: ",  "2. 实例方法"
// "执行: ",  "3. 实例属性"
// "执行: ",  "4. 静态属性"
// "执行: ",  "6. 静态方法的参数"
// "执行: ",  "5. 静态方法"
// "执行: ",  "7. 构造器的参数"
// "执行: ",  "8. 类"

总结

利用装饰器可以在不修改原有代码情况下,对功能进行扩展。同时,也可以把“辅助性功能逻辑”从“业务逻辑”中分离,解耦出来。而且装饰类和被装饰类可以独立发展,不会相互耦合。装饰模式是 Class 继承的一个替代模式,可以理解成组合。但是,若滥用装饰器,导致装饰器层次过多,不仅会增加调试追溯 bug 的成本,也会增加理解代码本身逻辑的难度。

因此,装饰器的功能逻辑代码应该是辅助性的,这样才符合 AOP 面向切面编程的思想,此外,装饰器语法标准化的过程还需要很长时间。

理解 JS 装饰器:@Decorator相关推荐

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

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

  2. 理解TS装饰器Decorator

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

  3. JS 装饰器(Decorator)场景实战

    本文不会大篇幅介绍装饰器(Decorator)的概念和基础用法,核心介绍我们团队如何将装饰器应用于实际开发,和一些高级用法的实现. 装饰器简介 Decorator 是 ES7 的一个新语法,正如其&q ...

  4. python lock_python lock一步步教你理解Python装饰器

    请仔细看我们的decorator实例.我们定义了一个接受单个参数some_func的名为outer的函数.在outer内部我们定义了一个名为inner的嵌套函数.inner函数打印一个字符串然后调用s ...

  5. python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

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

  6. python装饰器由浅入深_由浅入深理解Python装饰器

    前提知识: 1.Python里函数也是一种对象: def shout(word="yes"): return word.capitalize()+"!" pri ...

  7. python装饰器理解_如何理解Python装饰器?

    首先,本垃圾文档工程师又来了.开始日常的水文写作.起因是看到这个问题如何理解Python装饰器?,正好不久前给人讲过这些,本垃圾于是又开始新的一轮辣鸡文章写作行为了. 预备知识 首先要理解装饰器,首先 ...

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

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

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

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

最新文章

  1. 【Pygame小游戏】剧情流推荐:什么样的游戏才能获得大家的喜欢呢?(魔鬼恋人、霸总娇妻版)
  2. 从头开始学JavaScript (七)——函数
  3. ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your
  4. WhatFont——Google Chrome字体识别扩展
  5. windows下IDEA全面红色,但是能编译,不能智能提示
  6. linux fuse 阻塞,FUSE原理总结
  7. stat在python中_stat模块接口
  8. 面试准备每日系列:计算机底层之并发编程(三)JVM-垃圾回收
  9. CentOS下编译安装LAMP开发环境
  10. 在你的 Android App 中使用 Flutter | Google开发者大会
  11. 实用工具系列 - Xshell安装下载与使用
  12. 跳转页面 bscroll 无法无法从顶部滚动
  13. 简述数字信号处理的内容和理论
  14. iOS 实现UIButton加小红点
  15. 服务器如何选择固态硬盘,为什么绝大数服务器还使用机械硬盘,而不选固态硬盘呢?...
  16. 康耐视VisionPro
  17. 鱼眼摄像头 实时动、静目标的检测,跟踪,分类
  18. SMOTE-类不平衡问题
  19. 文献解读|苍术属植物叶绿体基因组变异及系统发育关系
  20. 竞争性传输函数:compet

热门文章

  1. struts2 拦截器Interceptor中取得request、response
  2. 统计学生平均成绩。使用静态成员函数。如果不将average函数定义为静态成员函数行不行?程序能否通过编译?需要作什么修改?为什么要用静态成员函数?请分析其理由。
  3. 包你大开眼界 看一看真正标准化机房 多图
  4. 苏州大学历年真题及推荐习题总结
  5. Termius Mac 跨平台SSH客户端 v7.28.0
  6. 计算机教案检查结果评语,教学设计与反思互评语
  7. saltstack数据系统——pillar
  8. 利用 shell 脚本进行android 马甲包制作
  9. 第六周感想2013.4.3
  10. 用C#做一个简单纸牌游戏的程序