JavaScript 的裝飾器:它們是什麼及如何使用

請訪問個人獨立博客地址:https://imsense.site/2017/06/js-decorator/javascript

裝飾器的流行應該感謝在Angular 2+中使用,在Angular中,裝飾器因TypeScript能使用。可是在JavaScript中,還處於提議階段。本文將介紹裝飾器是什麼,及裝飾器如何讓代碼更加簡潔和容易理解。java

什麼是裝飾器

裝飾器是用一個代碼包裝另外一個代碼的簡單方式。react

這個概念與以前所聽過的函數複合和高階組件類似。git

這已經用於不少狀況,就是簡單的將一個函數包裝成另外一個函數:es6

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

上個例子產生新函數wrapped,此函數與doSomething作一樣事情,可是他們不一樣在於在包裝函數以前和以後輸出一些語句。github

doSomething('Graham');
// Hello, Graham
wrapped('Graham');
// Starting
// Hello, Graham
// Finished

如何使用JavaScript裝飾器

JavaScript中裝飾器使用特殊的語法,使用@做爲標識符,且放置在被裝飾代碼以前。npm

注意:如今裝飾器還處於提議階段,意味着還有能夠變化之處數組

能夠放置許多裝飾器在一樣代碼以前,而後解釋器會按照順序相應執行瀏覽器

@log()
@immutable()
class Example {
  @time('demo')
  doSomething() {

  }
}

上例中定義了一個類,採用了三個裝飾器:兩個用於類自己,一個用於類的屬性:babel

  • @log能記錄全部全部訪問類
  • @immutable讓類不可變-也許新實例調用了Object.freeze
  • @time會記錄一個方法從執行到輸出一個獨特標籤

如今,雖然如今瀏覽器或Node還沒支持。可是若是使用Babel,能使用 transform-decorators-legacy插件使用裝飾器。

插件中使用legacy是由於Babel 5支持處理裝飾器,可是它也許會跟最終的標準有區別,因此才使用legacy這個詞。

爲何使用裝飾器

函數複合在JavaScript已經成爲可能,可是它至關困難或不可能用於另外一個代碼(如類或類屬性)。

裝飾器提議能夠用於類或屬性,將來JavaScript版本可能會增長用於其餘地方。

裝飾器也考慮到採用較爲簡潔的語法。

不一樣類型的裝飾器

如今,裝飾器只支持類和類屬性,這包含屬性、方法、get函數和set函數

裝飾器只會在程序第一次運行時執行一次,裝飾的代碼會被返回的值代替

類屬性裝飾器

屬性裝飾器適用於類的單獨成員-不管是屬性、方法、get函數或set函數。
裝飾器函數調用三個參數:

  • target-被修飾的類
  • name-類成員的名字
  • descriptor-成員描述符。對象會將這個參數傳給Object.defineProperty

@readonly是經典的例子:

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

上例會將成員描述符中的writable設爲false

接着用於類中屬性:

class Example {
  a() {}
  @readonly
  b() {}
}

const e = new Example();
e.a = 1;
e.b = 2;
// TypeError: Cannot assign to read only property 'b' of object '#<Example>'

可是咱們能夠作的更好,能夠用別的形式代替裝飾函數。例如,記錄全部的輸入和輸出:

function log(target, name, descriptor) {
  const original = descriptor.value;
  if (typeof original === 'function') {
    descriptor.value = function(...args) {
      console.log(`Arguments: ${args}`);
      try {
        const result = original.apply(this, args);
        console.log(`Result: ${result}`);
        return result;
      } catch (e) {
        console.log(`Error: ${e}`);
        throw e;
      }
    }
  }
  return descriptor;
}

注意咱們使用了擴展運算符,會自動將全部參數轉爲數組。

class Example {
    @log
    sum(a, b) {
        return a + b;
    }
}

const e = new Example();
e.sum(1, 2);
// Arguments: 1,2
// Result: 3

可讓裝飾器獲取一些參數,例如重寫log裝飾器以下:

function log(name) {
  return function decorator(t, n, descriptor) {
    const original = descriptor.value;
    if (typeof original === 'function') {
      descriptor.value = function(...args) {
        console.log(`Arguments for ${name}: ${args}`);
        try {
          const result = original.apply(this, args);
          console.log(`Result from ${name}: ${result}`);
          return result;
        } catch (e) {
          console.log(`Error from ${name}: ${e}`);
          throw e;
        }
      }
    }
    return descriptor;
  };
}

這與以前的log裝飾器相同,只是利用了外部函數的name參數。

class Example {
  @log('some tag')
  sum(a, b) {
    return a + b;
  }
}

const e = new Example();
e.sum(1, 2);
// Arguments for some tag: 1,2
// Result from some tag: 3

類裝飾器

類裝飾器用於整個類,裝飾器函數的參數爲被裝飾的構造器函數。

注意只用於構造器函數,而不適用於類的每一個實例。這就意味着若是想控制實例,就必須返回一個包裝版本的構造器函數。

一般,類裝飾器沒什麼用處,由於你所須要作的,一樣能夠用一個簡單函數來處理。你所作的只須要在結束時返回一個新的構造函數來代替類的構造函數。

回到咱們記錄那個例子,編寫一個記錄構造函數參數:

function log(Class) {
  return (...args) => {
    console.log(args);
    return new Class(...args);
  };
}

這裏接收一個類做爲參數,返回新函數做爲構造器。此函數打印出參數,返回這些參數構造的實例。

例如:

@log
class Example {
  constructor(name, age) {
  }
}

const e = new Example('Graham', 34);
// [ 'Graham', 34 ]
console.log(e);
// Example {}

構造Example類時會輸出提供的參數,構造值e也確實是Example的實例。

傳遞參數到類裝飾器與類成員同樣。

function log(name) {
  return function decorator(Class) {
    return (...args) => {
      console.log(`Arguments for ${name}: args`);
      return new Class(...args);
    };
  }
}

@log('Demo')
class Example {
  constructor(name, age) {}
}

const e = new Example('Graham', 34);
// Arguments for Demo: args
console.log(e);
// Example {}

真實例子

Core decorators

Core decorators是一個庫,提供了幾個常見的修飾器,經過它能夠更好地理解修飾器。

想理解此庫,也能夠去看看阮老師的關於此庫的介紹

React

React普遍運用了高階組件,這讓React組件成爲一個函數,而且能包含另外一個組件。
使用裝飾器是不錯的替代法,例如,Redux庫有一個connect函數,用於鏈接React組件和React store。

一般,是這麼使用的:

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

然而,可使用裝飾器代替:

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

參考資料

JavaScript Decorators: What They Are and When to Use Them
阮老師ES6入門-修飾器

相關文章
相關標籤/搜索