- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。