致敬原文作者!原文地址:https://www.leiue.com/what-is-es-modules
ES Modules 是用于处理模块的 ECMAScript 标准。 虽然 Node.js 长期使用 CommonJS 标准,但浏览器从未有过模块系统。 每个主要决策必须首先由 ECMAScript 标准化,然后由浏览器实施。
ES Modules(ESM)是用于处理模块的 ECMAScript 标准。 虽然 Node.js 长期使用 CommonJS 标准,但浏览器从未有过模块系统。 每个主要决策(如模块系统)必须首先由 ECMAScript 标准化,然后由浏览器实施。
这个标准化过程在 ES6 中完成,浏览器开始实施这个标准,试图以相同的工作方式保持一致性,,现在 Chrome,Safari,Edge 和 Firefox(从 60 版本开始)支持 ES 模块。
ES modules(ESM) 是 JavaScript 官方的标准化模块系统。然而,它在标准化的道路上已经花费了近 10 年的时间。
可喜的是,标准化之路马上就要完成了。等到 2018 年 5 月 Firefox 60 发布之后,所有的主流浏览器就都支持 ESM 了。同时,Node 模块工作小组也正在为 Node.js 添加 ESM 支持。为 WebAssembly 提供 ESM 集成的工作也正在如火如荼的进行。
许多 JS 开发者都知道,对 ESM 的讨论从开始至今一直都没停过。但是很少有人真正理解 ESM 的工作原理。
模块到底解决了什么问题?
仔细想想,使用 JavaScript 编码在于正确地管理变量,在于给变量赋值,或者给变量赋以数值或者合并两个变量并把它们赋值给另外一个变量。
因为你的大多数代码都是在更改变量,如何组织这些变量将会对你的编码方式以及代码的维护产生重大的影响。
当一次只需要考虑几个变量的时候使得事情变得非常简单,JavaScript 有一个方式来帮助你实现这个目标,那就是 —— 作用域。因为作用域的存在,函数不能访问 定义在其他函数内部的变量。
这很棒。这意味着当你专注于实现一个函数的时候,你只需要专注于实现这个函数,而不需要担心其他的函数会影响到你这个函数里的变量。
不过,它也有一个缺陷,它使得不同的函数之间共享变量变得更加困难。
那么假如你的确想要在作用域之外共享你的变量呢?通常的做法是将它放在当前作用域之上,比如:全局作用域。
或许你还记得使用 jQuery 的那些日子,在你加载任何 jQuery 的插件之前,你必须确保 jQuery 已经存在于全局作用域内了。
这是可行的,但是会产生一些烦人的问题。
首先,你所有的 script 标签都必须放置于一个正确的顺序。那么你就必须很小心并确保这些脚本之间不会互相影响。
如果你确实不小心搞乱了顺序,那么在代码运行的时候,你的应用就会抛出异常。当函数寻找 jQuery 对象的存在 —— 也就是全局作用域之下,但是却找不到的时候,函数就会报错并停止执行。
这让代码维护变得棘手。移除旧的代码或者是 script 标签就像是玩赌场转盘一样。你无法预料到什么代码可能崩溃。代码之间的依赖关系变得隐蔽。任何函数都可以获取到全局作用域上的任何东西,所以你并没有办法知道哪个函数依赖于哪个 script 标签。
其次,由于你的变量都存在于全局作用域上,所有处于这个作用域之上的代码都可以改变这些变量。恶意代码可以通过更改这些变量来让你的代码做并非你本意的事情,或者非恶意的代码会不小心破坏你的变量。
模块如何提供帮助
模块为你提供了一个更加好的方式来组织这些变量和方法。有了模块,你可以将这些有意义的函数和变量组织在一起。
模块会将这些函数和变量放入一个模块作用域当中。模块作用域使得模块中的不同函数能够共享这些变量。
但是不同与函数作用域,模块作用域有一种方法能够使得其他的模块也可以访问这个模块的变量。他们可以显式地指定模块中的哪些变量,类或者是函数可以被其他模块访问。
当一些东西对其他模块可用的时候,这叫做 “导出(export)”。当模块的导出存在的时候,其他模块就能够显式地指定它们依赖于这个模块的某些变量,类或者函数。
因为存在这种显式的关系,你可以明确的指出当你去掉了另外一个(导出),哪个模块会崩溃掉。
一旦拥有了这种能在模块之间导出和导入变量的能力,把你的代码分割成更小并且能够互相之间独立工作的代码块就变得很容易了。 然后你就可以结合或者重组这些代码块,像组合乐高积木一样,来使用同样的模块创建不同的应用。
正因为模块如此地有用,已经存在很多给 JavaScript 添加模块的尝试。目前,有两种模块系统被广泛地使用着。CommonJS(CJS) 曾经被 Node.js 所使用。ESM(ECMAScript 模块)是一个更新的模块系统,并加入到 JavaScript 的规范当中。浏览器已经支持 ES 模块了,Node.js 也正在添加对它的支持。
现在,就让我们更加深入地来看一下这个新的模块系统是如何运作的。
ES 模块是如何运作的
当使用模块来开发的时候,会建立一个模块模块依赖图。不同依赖之间联系来自于你使用的任何 import 语句。
这些 import 语句是浏览器或者 Node 确切地知道你需要加载什么样的代码的关键之处。你需要提供一个文件来作为依赖图的入口。 从这个入口开始,根据这些 import 语句就可以找剩余所需要的代码。
但是浏览器并不能直接使用这些文件本身。它必须要经过解析并转换成一种叫做 “模块记录(Module Records)”的数据结构。只有这样,浏览器才能确切地知道这个文件里发生了什么。
在这之后,模块记录需要转变成模块实例。模块实例包含了两个要素:编码(code)和状态(state)
编码基本上就是一些系列的指令。它就像配方一样。但是只有配方本身,什么都做不了,所以还需要一些原材料来配合这些指令。
什么是状态?状态就提供了这些原材料。状态就是这些变量在任何时间点的具体值。当然,这些变量不过是内存中保存这些变量的容器的别名。
所以模块实例就结合了编码(一系列的指令)和状态(所有的变量的值)。