- 原文地址:Why I've stopped exporting defaults from my JavaScript modules
- 原文做者:Nicholas C. Zakas
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Hopsken
- 校對者:Fengziyin1234,SHERlocked93
在與默認導出(export default)死纏爛打了這麼多年後,我改變了主意。javascript
上個星期,我發了條推特,收到了很多出人意料的回覆:html
2019年,我要作的其中一件事就是再也不從個人 CommonJS/ES6 模塊中導出默認值。前端
導入一個默認值感受上就像拋硬幣同樣,有一半的機率會猜錯。好比我有時就會搞不清楚導入的究竟是 class 仍是 function。java
— Nicholas C. Zakas (@slicknet) January 12, 2019android
我意識到我所遇到的大多數與 JavaScript 模塊有關的問題均可以歸咎於默認導出,因而就發了這條推特。無論我用的是 JavaScript 模塊(或者 ECMAScript 模塊,不少人喜歡這麼叫它)仍是 CommonJS,都會深陷於默認導出的泥潭。那條推特收到了各類各樣的評論,不少人都在問我我是如何得出這個結論的。在這篇文章中,我將盡量地解釋個人思考歷程。ios
正如全部的推文同樣,個人推文不過是個人見解的一個縮影,而不是我完整見解的規範性參考。首先我要澄清推文裏讓人困惑的幾點:git
但願以上澄清能夠避免後文可能產生的一些誤會。程序員
據我所知,默認導出是最早從 CommonJS 流行開來的。模塊能夠經過以下方式導出某個默認值:github
class LinkedList {}
module.exports = LinkedList;
複製代碼
這段代碼導出了 LinkedList
類,可是並無規定它被引用時應該使用的名稱。假設該文件名爲 linked-list.js
,你能夠經過以下方式在其它模塊中導入它:web
const LinkedList = require("./linked-list");
複製代碼
我只是碰巧把 require()
仍是返回的值命名爲 LinkedList
,以匹配文件名 linked-list.js
,可是我也徹底能夠叫它 foo
、Mountain
或者其它隨便什麼名稱。
默認模塊導出在 CommonJS 中的流行,說明 JavaScript 模塊生來就支持這種模式:
ES6 偏好單一/默認導出的風格,並且爲默認導入提供了甜蜜的語法糖。
— David Herman June 19, 2014
所以,在 JavaScript 模塊中,你能夠經過以下方式導出默認值:
export default class LinkedList {}
複製代碼
而後,你能夠這樣來導入它:
import LinkedList from "./linked-list.js";
複製代碼
再次說明,這裏的 LinkedList
這是個隨意的選擇(若是不是特別合理的話),並無特殊含義,也能夠是 Dog
或者 symphony
諸如此類。
除了默認導出之外,CommonJS 和 JavaScript 模塊都支持命名導出。在導入時,命名導出容許保留被導出的函數、類或者變量的名稱。
在 CommonJS 中,你能夠經過在 exports
對象上添加某對鍵值來建立命名導出:
exports.LinkedList = class LinkedList {};
複製代碼
而後,你能夠在另外一個文件中使用以下方法來導入它們:
const LinkedList = require("./linked-list").LinkedList;
複製代碼
再次說明,const
以後的名字是任取的,可是爲了導出時的名稱一致,這裏我選擇使用 LinkedList
。
在 JavaScript 模塊中,命名導出看上去像這樣:
export class LinkedList {}
複製代碼
而後你能夠這樣來導入它:
import { LinkedList } from "./linked-list.js";
複製代碼
這裏,LinkedList
不能夠取任意的標識符,必須與命名導出使用的名稱一致。對於這篇文章要講的東西而言,這是與 CommonJS 惟一的重要區別。
因此說,這兩種模塊化方案都支持默認導出和命名導出。
在進一步深刻以前,我須要說明一下我本身在寫代碼時的一些我的偏好。這是我寫代碼的整體原則,與語言自己無關。
明瞭勝於晦澀。我不喜歡有祕密的代碼。某個東西是幹嗎的,應該如何調用,諸如此類,在任何可能的狀況下,都應該明確且清晰。
名稱應該在全部文件中保持一致。若是某樣東西在這個文件裏叫 Apple
,那麼在另外一個文件裏就不應叫 Orange
。Apple
永遠都是 Apple
。
儘早並常常拋出錯誤。若是某樣東西有可能缺失,那麼最好就儘早檢查它,接着,在最理想的狀況下,拋出一個錯誤,讓我知道問題在哪兒。我不想等着代碼所有執行完後才發現出了問題,而後再去搜查問題出在哪兒。
更少地抉擇意味着更快地開發速度。個人不少編程偏好都是爲了減小編碼過程當中的抉擇。每作一個決定,你都會慢上一點。這就是爲何代碼規範能夠提升開發速度的緣由。我喜歡預先決定好全部事情,而後直接放手去作。
中途打斷會拖慢開發速度。當你在編碼過程當中不得不停下來查找一些東西時,這就是我所說的『中途打斷』。打斷有時候是必要的,可是過多沒必要要的打斷則會拖你的後腿。我想寫出儘量不須要『中途打斷』的代碼。
認知負荷會拖慢開發速度。簡單來講,編碼時,你須要記憶的用來保證效率的細節越多,你的開發速度越慢。
對開發速度的關注對我而言是個很現實的問題。多年來,我一直爲本身的健康所困擾,我能用於寫代碼的精力愈來愈少。任何能幫我在保證完成度的前提下,減小編碼時間的操做都很關鍵。
在上述前提下,這裏是我在使用默認導出時遇到的主要問題,以及爲何我相信在大多數狀況下命名導出都是更好的選擇。
正如我在以前那條推文上說的,若是模塊只有一個默認導出,我很難弄清楚我導入的是什麼。若是你正在用一個不熟悉的模塊或文件,你很難弄清楚返回的是什麼。舉個例子:
const list = require("./list");
複製代碼
這裏,你預想中 list
應該是什麼?雖然不太多是基本類型數據,但從邏輯上講能夠是函數、類或者其它類型的對象。我怎麼才能肯定呢?我須要中途打斷一下。當前狀況下,這可能意味着:
list.js
這個文件,我也許會打開它,看看它導出了什麼。list.js
這個文件,那麼我或許會打開某個文檔。無論是那種狀況,你不得不把這段額外的信息記在腦海裏,以免當你須要再次從 list.js
導入時發生打斷。若是你從各類模塊中引入了不少默認值,要麼你的認知負荷會增長,要麼你不得不中途打斷屢次。二者都不理想,並且很叫人沮喪。
有人可能會說,IDE 能夠解決這些問題。那麼 IDE 應該足夠聰明,聰明到能夠弄明白正在導入的是什麼,而後告訴你。固然我是支持使用聰明的 IDE 來幫助開發者的,不過我以爲要求 IDE 來有效地使用語言特性是會有問題的。
命名導出要求模塊的消費者至少得指定導入東西的名稱。這有個好處,我能夠方便地在代碼庫中查找全部用到 LinkedList
的地方,知道它們都指代的同一個 LinkedList
。由於默認導出並不能限定導入時使用的名稱,給導入命名會爲每一個開發者帶來更多的認知負荷。你須要決定正確的命名規範,另外,你還得確保團隊中的每一個開發者對同一個事物使用相同的名稱。(固然你也能夠容許每一位開發者使用不一樣的命名,可是這會爲整個團隊帶來更多的認知負荷。)
使用命名導出意味着至少在它被用到的地方引用的都是定好的名稱。就算你選擇重命名某個導入,你也得顯示說明出來,不可能在不引用規定名稱的狀況下實現。在 CommonJS 中:
const MyList = require("./list").LinkedList;
複製代碼
在 JavaScript 模塊中:
import { LinkedList as MyList } from "./list.js";
複製代碼
在這兩種狀況下,你都得顯示地聲明 LinkedList
被改成 MyList
。
若是名稱在代碼庫中保持一致,你就能夠作到如下事情:
若是使用默認導出和特定命名的話,這些操做能夠實現嗎?我猜是能夠的,可是會複雜得多,也容易出現錯誤。
相對於默認導出,命名導出有個明顯的好處。那就是,當試圖導入模塊中不存在的東西時,命名導入會拋出錯誤。考慮如下代碼:
import { LinkedList } from "./list.js";
複製代碼
若是 list.js
中不存在 LinkedList
,則會報錯。另外,也方便像 IDE 和 ESLint1 這樣的工具在代碼執行以前檢測不存在的引用。
提到 IDE,WebStorm 能夠幫你書寫 import
語句。2 當你在打完一個當前文件內未定義的標識符後,WebStorm 會在項目內查找模塊,檢查該標識符是不是某一個文件的命名導出。這時,它會作以下事情:
import
語句。import
語句(若是打開了自動導入功能)。事實上,當使用命名導入時,WebStorm 能夠幫上不少忙。Visual Studio Code3 有一個插件能夠實現相似的功能。這種功能沒法經過默認導出實現,由於你想導入的東西沒有肯定的名稱。
當我在項目中使用默認導出時,我遇到嚴重的工做效率問題。然而這些問題並非無解的,使用命名導出和導入能夠更好地配合個人編程習慣。清晰明確的代碼和對工具的重度依賴使我成爲高效的程序員。只要命名導出能夠幫我作到這些,在可預見的將來內,我都會支持它們。固然,我沒法決定我用的第三方模塊如何導出,但我能夠控制我本身寫的模塊如何導出,我會選擇命名導出。
正如前文說的,得提醒一下,這只是我我的的見解,你也許以爲個人論證沒有足夠的說服力。這篇文章並非想勸阻任何使用默認導出,而是做爲對那些詢問我爲何中止使用默認導出的一個更好的回答。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。