編寫高質量JavaScript模塊的4個最佳實踐

使用ES2015模塊,您能夠將應用程序代碼分紅可重用的、封裝的、專一於單一任務的模塊。javascript

這很好,可是如何構造模塊呢?一個模塊應該有多少個函數和類?java

這篇文章介紹了有關如何更好地組織JavaScript模塊的4種最佳實踐。git

1.優先使用命名導出

當我開始使用JavaScript模塊時,我使用默認的語法來導出模塊定義的單個塊,不論是類仍是函數。github

例如,這是一個將模塊 Greeter 導出爲默認值的模塊程序:瀏覽器

// greeter.js
export default class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

隨着時間的推移,我注意到了重構默認導出的類(或函數)的困難。在重命名原始類時,使用者模塊中的類名沒有改變。性能優化

更糟糕的是,編輯器沒有提供有關要導入的類名的自動完成建議。babel

個人結論是,默認的導出並無帶來明顯的好處。而後我轉向了命名導出編輯器

讓咱們將 Greeter 命名爲出口,而後看看好處:函數

// greeter.js
export class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

使用命名導出,編輯器能夠更好地進行重命名:每次更改原始類名時,全部使用者模塊也會更改類名。性能

自動完成功能還會建議導入的類:
JavaScript Named Import Autocomplete
因此,這是個人建議:

"支持命名模塊導出,以受益於重命名重構和代碼自動完成功能。"

注意:使用 React,Lodash 等第三方模塊時,默認導入一般是能夠的。默認的導入名稱是一個不變的常量:React_

2.導入期間不進行繁重的計算工做

模塊級別範圍定義了函數、類、對象和變量。該模塊能夠導出其中一些組件。就這樣。

// Module-level scope

export function myFunction() {
  // myFunction Scope
}

模塊級範圍不該該進行繁重的計算,好比解析JSON、發出HTTP請求、讀取本地存儲等等。

例如,下面的模塊配置解析來自全局變量bigJsonString的配置:

// configuration.js
export const configuration = {
  // Bad
  data: JSON.parse(bigJsonString)
};

這是一個問題,由於bigJsonString的解析是在模塊級範圍內完成的。bigJsonString的解析其實是在導入配置模塊時發生的:

// Bad: 導入模塊時進行解析
import { configuration } from 'configuration';

export function AboutUs() {
  return <p>{configuration.data.siteName}</p>;
}

在更高的級別上,模塊級範圍的做用是定義模塊組件、導入依賴項和導出公共組件:這是依賴項解析過程。它應該與運行時分離:解析JSON、發出請求、處理事件。

讓咱們重構配置模塊來執行延遲解析:

// configuration.js
let parsedData = null;

export const configuration = {
  // Good
  get data() {
    if (parsedData === null) {
      parsedData = JSON.parse(bigJsonString);
    }
    return parsedData;
  }
};

由於data屬性被定義爲一個getter,因此只有在使用者訪問configuration.data時才解析bigJsonString

// Good: 導入模塊時不進行JSON解析
import { configuration } from 'configuration';

export function AboutUs() {
  // 調用時才進行JSON解析
  return <p>{configuration.data.companyDescription}</p>;
}

消費者更清楚何時進行大的操做,使用者可能決定在瀏覽器空閒時執行該操做。或者,使用者可能會導入模塊,可是出於某種緣由不使用它。

這爲更深層的性能優化提供了機會:減小交互時間,最大程度地減小主線程工做。

導入時,模塊不該該執行任何繁重的工做。相反,使用者應該決定什麼時候執行運行時操做。

3.儘量的使用高內聚模塊

內聚性描述了模塊內部各個組件在一塊兒的程度。

高內聚模塊的函數、類或變量是密切相關的。他們專一於單個任務。

formatDate模塊具備很高的內聚性,由於它的功能密切相關,而且側重於日期格式化:

// formatDate.js
const MONTHS = [
  'January', 'February', 'March','April', 'May',
  'June', 'July', 'August', 'September', 'October',
  'November', 'December'
];

function ensureDateInstance(date) {
  if (typeof date === 'string') {
    return new Date(date);
  }
  return date;
}

export function formatDate(date) {
  date = ensureDateInstance(date);
  const monthName = MONTHS[date.getMonth())];
  return `${monthName} ${date.getDate()}, ${date.getFullYear()}`;
}

formatDate()ensureDateInstance()MONTHS彼此密切相關。

刪除MONTHSensureDateInstance()會破壞formatDate():這是高內聚的標誌。

4.避免較長的相對路徑

我發現很難理解一個模塊的路徑包含一個,甚至更多的父文件夾:

import { compareDates } from '../../date/compare';
import { formatDate } from '../../date/format';

// Use compareDates and formatDate

而有一個父選擇器../一般不是問題,擁有2個或更多一般很難掌握。

這就是爲何我建議避免使用父文件夾,而使用絕對路徑:

import { compareDates } from 'utils/date/compare';
import { formatDate } from 'utils/date/format';

// Use compareDates and formatDate

儘管有時寫入絕對路徑的時間更長,可是使用絕對路徑可使導入的模塊的位置清晰明瞭。

爲了減小冗長的絕對路徑,能夠引入新的根目錄。例如,這可使用babel-plugin-module-resolver實現。

使用絕對路徑而不是較長的相對路徑。

5.結論

JavaScript模塊很是適合將您的應用程序邏輯拆分爲多個獨立的小塊。

經過使用命名的導出而不是默認的導出,能夠在導入命名組件時更輕鬆地重命名重構和編輯器自動完成幫助。

使用 import {myFunc} from 'myModule' 的惟一目的就是導入myFunc組件,僅此而已。myModule的模塊級範圍應該只定義包含少許內容的類、函數或變量。

一個組件應該有多少個函數或類,這些組件應該如何與每一個組件相關聯?支持高內聚的模塊:它的組件應該緊密相關並執行一個共同的任務。

包含許多父文件夾../的長相對路徑很難理解。將它們重構爲絕對路徑。

你使用哪些JavaScript模塊最佳作法?


原文:https://dmitripavlutin.com/ja...
做者:Dmitri Pavlutin
譯者:作工程師不作碼農


關注公衆號,第一時間接收最新文章。若是對你有一點點幫助,能夠點喜歡點贊點收藏,還能夠小額打賞做者,以鼓勵做者寫出更多更好的文章。

關注公衆號

相關文章
相關標籤/搜索