JavaScript 模块(1):初学者指南

703 查看

如果你刚接触 JavaScript,想必已经被“module bundlers vs. module loaders”、“Webpack vs. Browserify”和“AMD vs. CommonJS” 等诸如此类的行业术语所吓到。

JavaScript 模块系统听起来挺吓人的,但明白它是每个 Web 开发者所必备的要求。

在这篇文章中,我将抛开这些行业术语,用通俗易懂的语言(和一些代码案例)向你解释清楚。希望你能从中收益!

注意:为了让文章更易理解,我分为两部分进行讲述:第一部分会深入解释「模块是什么」和「为什么要使用它们」。第二部分(下周发布)讲述「模块打包意味着什么」和「用不同方式实现模块打包」。

Part 1:你能再次解释模块是什么吗?

优秀的作者会将他的书分为章和节。同理,优秀的程序员能将他的程序划分为各个模块。

就像书的章节,模块就是词(或代码,视情况而定)的集群。

好的模块拥有以下特点:不同功能是高度独立的,并且它们允许被打乱、移除或在必要时进行补充,而不会扰乱系统作为一个整体。

为什么使用模块?

使用模块有诸多好处,如利于建立一个扩展性强的、互相依赖的代码库。而在我看来,其最重要是:

1)可维护性:根据定义,模块是独立的。一个设计良好的模块意在尽可能减少对代码库的依赖,所以它才能单独地扩展与完善。更新一个从其它代码段解耦出来的独立模块显然来得更简单。

回到书的案例,如果书的某个章节需要进行小改动,而该改动会牵涉到其它所有章节,这无疑是个梦魇。相反,如果每章节都以某种良好方式进行编写,那么改动某章节,则不会影响其它章节。

2)命名空间:在 JavaScript 中,如果变量声明在顶级函数的作用域外,那么这些变量都是全局的(意味着任何地方都能读写它)。因此,造成了常见的“命名空间污染”,从而导致完全无关的代码却共享着全局变量。

无关的代码间共享着全局变量是一个严重的 编程禁忌

我们将在本文后面看到,模块通过为变量创建一个私有空间,从而避免了命名空间的污染。

3)可重用性:坦诚地讲:我们都试过复制旧项目的代码到新项目上。例如,我们复制以前项目的某些功能方法到当前项目中。

该做法看似可行,但如果发现那段代码有更好的实现方式(即需要改动),那么你就不得不去追溯并更新任何你所粘贴到的任何地方。

这无疑会浪费大量的时间。因此可复用的模块显然让你编码轻松。

如何整合为模块?

整合为模块的方式有很多。下面就看看其中的一些方法:

模块模式(Module pattern)

模块模式用于模仿类(由于 JavaScript 并不支持原生的类),以致我们能在单个对象中存储公有和私有变量与方法——类似于其它编程语言(如 Java 或 Python )中的类的用法。模块模式不仅允许我们创建公用接口 API(如果我们需要暴露方法时),而且也能在闭包作用域中封装私有变量和方法。

下面有几种方式能实现模块模式(module pattern)。第一个案例中,我将会使用匿名闭包。只需将所有代码放进匿名函数中,就能帮助我们实现目标(记住:在 JavaScript 中,函数是唯一创建新作用域的方式)。

Example 1:匿名闭包(Anonymous closure


通过这种结构,匿名函数拥有自身的求值环境或”闭包“,并立即执行它。这就实现了对上级(全局)命名空间的隐藏。

这种方法的好处是:能在函数内使用本地变量,而不会意外地重写已存在的全局变量。当然,你也能获取全局变量,如:


这里需要注意的是,匿名函数必须被小括号包裹住,这是因为当语句以关键字 function 开头时,它会被认为是一个函数的声明语句(记住,JavaScript 中不能拥有未命名的函数声明语句)。因此,该括号会创建一个函数表达式代替它。欲知详情,可点击 这里

Example 2:全局导入(Global import 

另一个常见的方式是类似于 jQuery 的全局导入(global import)。该方式与上述的匿名闭包相似,特别之处是传入了一个全局变量作为参数:


在该案例中,globalVariable 是唯一的全局变量。这个相对于匿名闭包的优势是:提前声明了全局变量,能让别人更清晰地阅读你的代码。

Example 3:对象接口(Object interface

使用一个独立的对象接口创建模块,如: