装饰器接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容后再返回一个方法,供以后调用,同时也失去了对原方法对象的访问。

当我们对某个方法应用了装饰之后,其实就是改变了被装饰方法的入口引用,使其重新指向了装饰器返回的方法的入口点,从而来实现对原函数的扩展、修改等操作

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

babel配置

.babelrc中

  1. "plugins": [
  2. "transform-decorators-legacy"*
  3. ]

ES7的装饰器decorator是依赖于ES5的Object.defineProperty方法

相关知识:Object.defineProperty

Object.defineProperty()在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

语法:

Object.defineProperty(obj, prop, descriptor)

  1. obj:操作的对象
  2. prop:被定义或者修改的属性名称
  3. descriptor:将被定义或修改的属性描述符
  4. 返回值:被传递给函数的对象

属性描述符:descriptor

对象中目前存在的属性描述符有2种:数据描述符和存取描述符

  1. 1、数据描述符:描述属性的值和值是否可被赋值运算符改变<br /> 2、存取描述符:由gettersetter函数对属性的描述<br />** 属性描述符必须是上述两者之一;且不可同时是两者**

属性描述符通用键值(即数据描述符和存取描述符都有的键值):
1、configurable:configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。默认值false,即不可改变
2、enumerable:定义了当前操作的这个属性是否可以for…in和Object.key()中被枚举。设为true时,该属性才能出现在对象的枚举属性中。默认值false,即不可被枚举

数据描述符特有的键值:
1、value:该属性对应的值,可以是任意有效的javascript值(string,number,object,function等等)。默认值undefined
2、writable:当且仅当writable为true时,value才能被赋值运算符改变。默认值false,即不可被改变

  1. let o = {};
  2. o.a = 1;
  3. // 等同于 :
  4. Object.defineProperty(o, "a", {
  5. value: 1,
  6. writable: true,
  7. configurable: true,
  8. enumerable: true
  9. });
  1. // 另一方面,
  2. Object.defineProperty(o, "a", {value: 1});
  3. // 等同于 :
  4. Object.defineProperty(o, "a", {
  5. value: 1,
  6. writable: false,
  7. configurable: false,
  8. enumerable: false
  9. });

存取描述符特有的键值:
1、get:一个给属性提供getter的方法,如果没有getter则为undefined。当访问该属性时get方法会执行,方法执行时没有参数传入,但会传入this对象(由于继承关系,此this不一定是定义改属性的对象)
2、set:一个给属性提供setter的方法,如果没有setter则为undefined。当属性值修改时set方法会执行,该方法将接收唯一参数,即该属性新的参数值

  1. let obj = {}
  2. let num = 30
  3. Object.defineProperty(obj, 'id', {
  4. configurable: true,
  5. enumerable: true,
  6. get: () => num,
  7. set: (newValue) => {
  8. num = newValue
  9. }
  10. })
  11. console.info(obj.id, num) // 30 30
  12. obj.id = 20
  13. console.info(obj.id, num) // 20 20
  14. num = 40
  15. console.info(obj.id, num) // 40 40

装饰器的简单应用

1. 类的装饰

当装饰的对象是类时,我们操作的就是这个类本身,即装饰器函数的第一个参数,就是所要装饰的目标类。

  1. @decorator
  2. class A {}
  3. // 等同于
  4. class A {}
  5. A = decorator(A) || A;

示例:添加一个日志装饰器

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

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

  1. @log('hi')
  2. class MyClass { }
  3. function log(text) {
  4. return function(target) {
  5. target.prototype.logger = () => `${text},${target.name} 被调用`
  6. }
  7. }
  8. const test = new MyClass()
  9. test.logger() // hello,MyClass 被调用

2. 属性或方法的装饰

对于类属性或方法的装饰本质是操作其描述符,可以把此时的装饰器理解成是 Object.defineProperty(obj, prop, descriptor)的语法糖。

  1. class C {
  2. @readonly(false)
  3. method() { console.log('cat') }
  4. }
  5. function readonly(value) {
  6. return function (target, key, descriptor) {
  7. /**
  8. * 此处 target 为 C.prototype;
  9. * key 为 method;
  10. * 原 descriptor 为:{ value: f, enumarable: false, writable: true, configurable: true }
  11. */
  12. descriptor.writable = value
  13. return descriptor
  14. }
  15. }
  16. const c = new C()
  17. c.method = () => console.log('dog')
  18. c.method() // cat

装饰器的复杂应用

一、作用于类的装饰器

当装饰的对象是类时,我们操作的就是这个类本身。
类的装饰器函数的第一个参数,就是所有装饰的目标类
装饰器对类的行为的改变是代码编译时发生的,而不是在运行时。这意味着,装饰器能够在编译阶段运行代 码。也就是说,装饰器本质就是编译时执行的函数
例子:

1、simple class decorator

in decorator.js

  1. // 类的装饰器
  2. export const classDecorator = (target) => {
  3. // 此处的target为类本身
  4. target.a = true // 给类添加一个静态属性
  5. }

in index.js

  1. @classDecorator
  2. export class ClassA {
  3. constructor() {
  4. this.a = 1
  5. }
  6. a = 2
  7. }
  8. console.info('ClassA.a: ', ClassA.a) // true

2、class decorator with params 传参的类装饰器

in decorator.js

  1. // 传参的类的装饰器
  2. export const classDecoratorWithParams = (params = true) => (target) => {
  3. target.a = params
  4. }

in index.js

  1. @classDecoratorWithParams(false)
  2. export class ClassB {
  3. constructor() {
  4. this.a = 1
  5. }
  6. fun = () => {
  7. console.info('fun中ClassB.a: ', this.a, ClassB.a) // 1, false
  8. }
  9. }
  10. console.info('ClassB.a: ', ClassB.a) // false
  11. const classB = new ClassB()
  12. console.info('new ClassB().a: ', classB.a) // 1
  13. classB.fun()

3、class decorator add prototype 给修饰类添加实例属性

in decorator.js

  1. // 类的装饰器(给类添加实例属性)
  2. export const classDecoratorAddPrototype = prototypeList => (target) => {
  3. target.prototype = { ...target.prototype, ...prototypeList }
  4. target.prototype.logger = () => console.info(`${target.name} 被调用`) // target.name即获得类的名
  5. }

in index.js

  1. @classDecoratorAddPrototype({ fn() { console.info('fnfnfn') } }) // 此处不能使用箭头函数?
  2. export class ClassC {
  3. constructor() {
  4. this.a = 1
  5. }
  6. }
  7. // console.info('ClassC.fn: ', ClassC.fn()) // 报错,fn不在ClassC的静态属性上
  8. const classC = new ClassC()
  9. classC.fn()
  10. classC.logger()

例子github:https://github.com/zzsscc/decorators
在redux中我们经常使用react-redux的connect装饰器即为作用于类的装饰器

  1. connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
  2. export default class MyComponent extends React.Component {}
  3. 相当于
  4. class MyComponent extends React.Component {}
  5. export default connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(MyComponent)

二、作用于类方法的装饰器

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

1、class function decorator

in decorator.js

  1. // 方法的装饰器
  2. export const funDecorator = (params = { readonly: true }) => (target, prototypeKey, descriptor) => {
  3. /*
  4. 此处target为类的原型对象,即方法Class.prototype
  5. ps:装饰器的本意是要装饰类的实例,但此时实例还未生成,所以只能装饰类的原型
  6. */
  7. /*
  8. prototypeKey为要装饰的方法(属性名)
  9. */
  10. /*
  11. descriptor为要修饰的方法(属性名)的描述符,即(默认值为):
  12. {
  13. value: specifiedFunction,
  14. enumerable: false,
  15. configurable: true,
  16. writable: true
  17. }
  18. */
  19. // 实现一个传参的readonly,修改描述符的writable
  20. descriptor.writable = !params.readonly
  21. // 返回这个新的描述符
  22. return descriptor
  23. }
  24. /*
  25. 调用funDecorator(Class.prototype, prototypeKey, descriptor)
  26. 相当于
  27. Object.defineProperty(Class.prototype, prototypeKey, descriptor)
  28. */

in index.js

  1. export class ClassD {
  2. constructor() {
  3. this.a = 1
  4. }
  5. @funDecorator()
  6. fun = (tag) => {
  7. this.a = 2
  8. console.info(`this.a ${tag}`, this.a)
  9. }
  10. }
  11. const classD = new ClassD()
  12. classD.fun('first')
  13. // 报错,无法改变classD.fun,因为他的描述符descriptor.writable已经被装饰器修改为false
  14. try {
  15. classD.fun = (tag) => {
  16. console.info(`this.a changed ${tag}`)
  17. }
  18. classD.fun('sec')
  19. } catch (err) {
  20. throw new Error(err)
  21. }

2、fun enhance(front/end) decorator

in decorator.js

  1. // 方法的装饰器(在方法执行的前后添加操作:如show/hide loading)
  2. export const funEnhanceDecorator = (params = {}) => (target, prototypeKey, descriptor) => {
  3. // 默认需要showLoading
  4. const { showLoading = true } = params
  5. const oldValue = descriptor.value
  6. descriptor.value = async function A(...args) {
  7. try {
  8. showLoading && console.info('加载中')
  9. const result = await oldValue.apply(this, args)
  10. console.info('hide')
  11. return result
  12. } catch (err) {
  13. console.info('hide')
  14. console.error(err)
  15. return null
  16. }
  17. };
  18. return descriptor
  19. }

in index.js

  1. export class ClassE {
  2. constructor() {
  3. this.result = {}
  4. }
  5. afun = (params) => {
  6. return new Promise((resolve, reject) => {
  7. setTimeout(() => {
  8. resolve(params.id)
  9. }, 2000)
  10. })
  11. }
  12. @funEnhanceDecorator()
  13. async fun(params = {}) { // 不能使用箭头函数?
  14. const result = await this.afun(params)
  15. console.info(result)
  16. }
  17. }
  18. const classE = new ClassE()
  19. classE.fun({ id: 100 })

3、test decorators sequence多个装饰器的包装顺序

in decorator.js

  1. // time => 计数and计时
  2. const labels = {};
  3. // Exported for mocking in tests
  4. export const defaultConsole = {
  5. time: console.time ? console.time.bind(console) : (label) => {
  6. labels[label] = new Date();
  7. },
  8. timeEnd: console.timeEnd ? console.timeEnd.bind(console) : (label) => {
  9. const timeNow = new Date();
  10. const timeTaken = timeNow - labels[label];
  11. delete labels[label];
  12. console.info(`${label}: ${timeTaken}ms`);
  13. }
  14. };
  15. let count = 0;
  16. export const time = (params = { prefix: null, console: defaultConsole }) => (target, prototypeKey, descriptor) => {
  17. const fn = descriptor.value
  18. let { prefix } = params
  19. const { console } = params
  20. if (prefix === null) {
  21. prefix = `${target.constructor.name}.${prototypeKey}`
  22. }
  23. if (typeof fn !== 'function') {
  24. throw new SyntaxError(`@time can only be used on functions, not: ${fn}`)
  25. }
  26. return {
  27. ...descriptor,
  28. async value(...args) {
  29. const label = `${prefix}-${count}`
  30. count += 1
  31. console.time(label)
  32. try {
  33. return await fn.apply(this, args)
  34. } finally {
  35. console.timeEnd(label)
  36. }
  37. }
  38. }
  39. }
  40. // deprecate => 标记废弃
  41. const DEFAULT_MSG = 'This function will be removed in future versions.'
  42. export const deprecate = (params = { options: {} }) => (target, prototypeKey, descriptor) => {
  43. if (typeof descriptor.value !== 'function') {
  44. throw new SyntaxError('Only functions can be marked as deprecated')
  45. }
  46. const methodSignature = `${target.constructor.name}#${prototypeKey}`
  47. let { msg = DEFAULT_MSG } = params
  48. const { options } = params
  49. if (options.url) {
  50. msg += `\n\n See ${options.url} for more details.\n\n`;
  51. }
  52. return {
  53. ...descriptor,
  54. value(...args) {
  55. console.warn(`DEPRECATION ${methodSignature}: ${msg}`)
  56. return descriptor.value.apply(this, args)
  57. }
  58. }
  59. }
  60. // test sequence 测试顺序
  61. export const testSequence1 = (params = {}) => (target, prototypeKey, descriptor) => {
  62. const oldValue = descriptor.value
  63. return {
  64. ...descriptor,
  65. value(...args) {
  66. console.log('test1')
  67. oldValue.apply(this, args)
  68. }
  69. }
  70. }
  71. export const testSequence2 = (params = {}) => (target, prototypeKey, descriptor) => {
  72. const oldValue = descriptor.value
  73. return {
  74. ...descriptor,
  75. value(...args) {
  76. console.log('test2')
  77. oldValue.apply(this, args)
  78. }
  79. }
  80. }

in index.js

  1. export class ClassF {
  2. constructor() {
  3. this.result = {}
  4. }
  5. @time()
  6. @deprecate({ options: { url: 'https://github.com/zzsscc' } })
  7. @testSequence1()
  8. @testSequence2()
  9. fun() {
  10. return new Promise((resolve, reject) => {
  11. setTimeout(() => {
  12. resolve(this.result)
  13. }, 3000)
  14. })
  15. }
  16. }
  17. const classf = new ClassF()
  18. classf.fun()
  19. classf.fun()

三、core-decorators.js

提供了一些常用的装饰器方法
code view更有助于你理解装饰器
跳转github查看

—————————————————————————————————————————————————
原文链接:https://www.jianshu.com/p/c3cbea4f86c6
原文链接:https://segmentfault.com/a/1190000014495089
扩展学习1:点击跳转
扩展学习2:https://www.sitepoint.com/javascript-decorators-what-they-are/
扩展学习3:https://es6.ruanyifeng.com/#docs/decorator#core-decorators-js