裝飾器模式(Decorator模式)理解、使用

簡介

在一些框架(比如 nest.js、midway.js 等)中,經(jīng)??吹皆陬惔a的附近有 @...... 這種代碼,這就是 Decorator

Decorator 可以叫做修飾器,或者是裝飾器

修飾器是一種特殊類型的聲明,它只能夠被附加到類的聲明、方法、屬性或參數(shù)上,可以修改類的行為。但不能用于函數(shù)(因為存在函數(shù)提升)

常見的修飾器有:類修飾器、屬性修飾器、方法修飾器、參數(shù)修飾器

修飾器寫法:普通修飾器(無法傳參)、修飾器工廠(可傳參)

修飾器對類行為的改變,是代碼編譯時發(fā)生的,而不是在運行時。修飾器能在編譯階段運行代碼。也就是說,修飾器本質(zhì)就是編譯時執(zhí)行的函數(shù)

修飾器是一個對類進行處理的函數(shù)。修飾器函數(shù)的第一個參數(shù),就是所要修飾的目標類

修飾器的行為類似如下

@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A

類修飾器

類修飾器在類聲明之前被聲明(緊靠著類聲明)

類修飾器應(yīng)用于類構(gòu)造函數(shù),可以用來監(jiān)視,修改或替換類定義

  • 類修飾器示例
function logClass (target) {
  console.log(target)                                       // [Function: MyTest] target指向修飾的類
  target.nTest = '擴展的靜態(tài)屬性'                     // 擴展靜態(tài)屬性
  target.prototype.nName = '動態(tài)擴展的屬性' // 給原型擴展屬性
  target.prototype.nFun = () => {
    console.log('動態(tài)擴展的方法')
  }
}

@logClass
class MyTest {}

const test = new MyTest()
console.log(test.nTest)  // 擴展的靜態(tài)屬性
console.log(test.nName)  // 動態(tài)擴展的屬性
test.nFun()              // 動態(tài)擴展的方法
  • 修飾器工廠(閉包傳參)
function logClass (params) {
  return function (target) {
    console.log(target)      // [Function: MyTest]
    console.log(params)      // hello
    target.prototype.nName = '動態(tài)擴展的屬性'
    target.prototype.nFun = () => {
      console.log('動態(tài)擴展的方法')
    }
  }
}

@logClass('hello')
class MyTest {}

const test = new MyTest()
console.log(test.nName)  // 動態(tài)擴展的屬性
test.nFun()              // 動態(tài)擴展的方法
  • Mixins 混入例子
// mixins.js  可以返回一個函數(shù)
export function mixins(...list) {
  return function(target) {
    Object.assign( target.prototype, ...list )
  }
}

// main.js
import { mixins } from './mixins.js'
const Foo = {
  foo() {console.log('foo')}
}

@mixins(Foo)  // 當函數(shù)調(diào)用,傳入?yún)?shù)
class MyClass {}

const obj = new MyClass()
obj.foo             // 'foo'
  • 重載構(gòu)造函數(shù)的例子
function logClass (target) {
  return class extends target {
    attr = '重載屬性'

    getData () {
      console.log('重載方法', this.attr)
    }
  }
}

@logClass
class MyTest {
  attr

  constructor () {
    this.attr = '構(gòu)造函數(shù)的屬性'
  }

  getData () {
    console.log(this.attr)
  }
}

const test = new MyTest()
test.getData()      // 重載方法 重載屬性
  • React + Redux 例子
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

// 可改寫成
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

屬性修飾器

屬性裝飾器表達式會在運行時當作函數(shù)被調(diào)用,傳入下列 2 個參數(shù)

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
  2. 修飾的屬性名
function logProperty (params) {
  return function (target, name) {
    console.log(target, name)  // MyTest { getData: [Function] } 'attr'
    target[name] = params
  }
}

class MyTest {
  @logProperty('屬性修飾器的參數(shù)')
  attr

  constructor () {}

  getData () {
    console.log(this.attr) // 屬性修飾器的參數(shù)
  }
}

const test = new MyTest()
test.getData()

方法修飾器

它會被應(yīng)用到方法的屬性描述符上,可以用來監(jiān)聽、修改或者替換方法定義

修飾器會修改屬性的描述對象,然后被修改的描述對象再用來定義屬性

方法修飾器會在運行時傳入下列 3 個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
  2. 修飾的屬性名
  3. 該屬性的描述對象
  • 方法修飾器示例
// 方法修飾器
function logFunction (params) {
  return function (target, methodName, desc) {
    console.log(target, methodName, desc)
    /*
    MyTest { getData: [Function] }
    'getData'
    {
      value: [Function], // 值
      writable: true, // 可讀
      enumerable: true, // 可枚舉
      configurable: true // 可設(shè)置
      }
    */

    target.nName = '動態(tài)擴展的屬性'
    target.nFun = () => {
      console.log('動態(tài)擴展的方法')
    }

    // 將接收到的參數(shù)改為 string 類型
    const oMethod = desc.value
    desc.value = function (...args) {
      args = args.map((v) => {
        return String(v)
      })
      return oMethod.apply(this, args)
    }
  }
}

class MyTest {
  @logFunction('方法修飾器的參數(shù)')
  getData (...args) {
    console.log(args)
  }
}

const test = new MyTest()
console.log(test.nName)              // 動態(tài)擴展的屬性
test.nFun()                                    // 動態(tài)擴展的方法
test.getData(123, '234', () => {})  // [ '123', '234', 'function () { }' ]
  • 輸出日志的例子
class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

// @log修飾器的作用就是在執(zhí)行原始的操作之前,執(zhí)行一次console.log,從而達到輸出日志的目的
function log(target, name, descriptor) {
  const oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);
  • 修飾器有注釋的作用,看上去一目了然
@Component({
  tag: 'my-component',
  styleUrl: 'my-component.scss'
})
export class MyComponent {
  @Prop() first: string;  // props
  @Prop() last: string;   // props
  @State() isVisible: boolean = true; // props

  render() {
    return (
      <p>Hello, my name is {this.first} {this.last}</p>
    );
  }
}

參數(shù)修飾器

參數(shù)修飾器表達式會在運行時當作函數(shù)被調(diào)用,可以使用參數(shù)修飾器為類的原型增加一些元素數(shù)據(jù),傳入下列 3 個參數(shù)

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
  2. 參數(shù)的名字
  3. 參數(shù)在函數(shù)參數(shù)列表中的索引
function logParams (params) {
  return function (target, methodName, paramsIndex) {
    // MyTest { getData: [Function] } 'getData' 0
    console.log(target, methodName, paramsIndex)
    target.param = params
  }
}

class MyTest {
  getData (@logParams('參數(shù)修飾符的參數(shù)') id) {
    console.log(id)
  }
}

const test = new MyTest()
test.getData(123)
console.log(test.param) // 參數(shù)修飾符的參數(shù)

執(zhí)行順序

屬性修飾器 > 參數(shù)修飾器 > 方法修飾器 > 類修飾器

function log (params) {
  return function () {
    console.log(params)
  }
}

@log('類修飾器')
class MyTest {
  @log('屬性修飾器')
  id

  @log('方法修飾器')
  getData (@log('參數(shù)修飾器') id) {
    this.id = id
  }
}

new MyTest()
  • 同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內(nèi)進入,然后由內(nèi)向外執(zhí)行
function dec(id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
}

class Example {
  @dec(1)
  @dec(2)
  method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

不能用于函數(shù)

修飾器只能用于類和類的方法,不能用于函數(shù),因為存在函數(shù)提升。

// 意圖是執(zhí)行后counter等于 1,但是實際上結(jié)果是counter等于 0
var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

上面的代碼,函數(shù)提升后相當于

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目的: iOS APP在下一個版本會用swift開發(fā),在多人開發(fā)中,即使有官網(wǎng)的規(guī)范模板.但每個人的代碼風格和規(guī)...
    nick5683閱讀 2,023評論 0 1
  • 目的: iOS APP在下一個版本會用swift開發(fā),在多人開發(fā)中,即使有官網(wǎng)的規(guī)范模板.但每個人的代碼風格和規(guī)...
    技術(shù)進階在路上閱讀 13,439評論 2 15
  • 隨著轉(zhuǎn)譯變得司空見慣,我們會經(jīng)常在一些實際代碼或教程中遇到新的語言特性。這些特性中,裝飾器絕對是讓人第一次碰到時會...
    shallynon閱讀 7,867評論 0 2
  • 類是一個重要的C#編程概念,它在一個單元內(nèi)定義了表示和行為。類提供了面向?qū)ο缶幊毯兔嫦蚪M件編程所需的語言支持,是創(chuàng)...
    CarlDonitz閱讀 988評論 0 2
  • 裝飾器 概念 裝飾器是一種特殊類型的聲明,他能夠被附加到類聲明,方法,屬性或者參數(shù)上??梢孕薷念惖男袨椤3R姷难b飾...
    TouchMe丶閱讀 663評論 0 0

友情鏈接更多精彩內(nèi)容