Javascript 模块基础

用一个demo体验下所有的前端模块

主题


主流模块模式
AMD和CommonJs模块格式
客户端模块加载器
ES2015模块
模块连接

为什么模块很重要?


模块是什么?

模块是一组有特定功能的代码和数据,它封装了一些实现细节,暴露出公共的API,通过和其他模块的连接来打造出大型应用。

模块化的目的

高层次的抽象

模块化可以让你从更高的层次来思考你的应用,它让我们从一组方法的角度来思考问题,而不是每一个方法。举个例子,如果我是一个城市的建造者,我希望考虑的是怎么在城市中去布局建筑群,而不是去考虑每一个建筑里的每一块砖头的问题。

封装

模块的任务是要暴露出清晰的API供使用方使用,同时隐藏具体的实现细节。这使得维护模块的人可以重构代码,替换算法或其他实现,而不用担心影响到依赖这些模块的代码,只要API不变,就不会影响使用方。

重用

简化依赖管理

一般来说,每个模块都需要声明自己的功能所依赖的其他模块。我们都知道维护HTML页面上庞大js脚本顺序的痛点,实现一个好的模块系统可以解决掉这个问题。


Module Patterns in ES5 ( ES5时代的模块 )


**IIFE:立即调用函数表达式 **

我们都知道,全局变量污染是一件坏事。解决全局变量污染的手段之一是用IIFE。

IIFE是一个匿名函数,它在声明的同时就被调用。这对代码的结构来说有一定的好处。因为JavaScript 里,变量的作用域是对于函数而言的,IIFE只是一个函数,你可以把代码的实现逻辑封装在IIFE里,它不会被其他的代码调用。而且,IIFE是一个匿名函数,它不会在全局作用域里创建新的变量,这样,就不用担心你在IIFE里定义的一些函数或变量名和一些第三方库冲突,这是JavaScript经常出现的问题,也是模块化要解决的问题。

但是,IIFE无法解决依赖管理的问题,所以,他们还不是最理想的解决方案。


** 暴露模块模式 (Revealing Module pattern )**

这个模式利用的是JavaScript 函数作用域的特性来实现的,函数作用域可以很好地封装模块的实现细节。

和IIFE不同的是,我们要能够引用到代码里的模块,所以他们必须有一个名字,这就意味着他们会往全局变量里加入新的值。这个模式的缺陷是没有提供模块间的依赖管理形式,但它受JavaScript语言的支持。

这个模式有两种实现:单例模式和构造函数模式。

**单例模式 **

var scoreboard=function(){
         var message=' Welcome to the game! ';
         function printMessage(){
                   console.log(message);
         }
         return {
                  showMessage:printMessage
         }
}();

注意,这里看起来scoreboard是一个函数,但是注意代码结尾处有一对括号,立即调用这个函数,所以这里scoreboard变量其实已经被函数的返回值赋值了,所以它是一个拥有showMessage属性的对象,这个对象是一个新的模块,scoreboard变量是这个模块在整个应用里的唯一实例。

构造函数模式

 var Scoreboard=function(){
         var message=' Welcome to the game! ';
         function printMessage(){
                   console.log(message);
         }
         return {
                  showMessage:printMessage
         }
};
var myScoreboard=new Scoreboard();
myScoreboard.showMessage();

注意这里函数不再被立即调用,建议代表构造函数的变量首字母大写。


模块格式 vs 模块加载器


模块格式

目前主流的模块格式有:AMD 、CommonJS、UMD、System.register、ES2015。根据浏览器对其的原生支持情况,前四种可归为 non-native ,ES2015S属于native。

模块格式指的是用来定义模块的语法。

模块格式独立于模块加载器存在,如果没有模块加载器,就没有人知道怎么来运行这些语法。

模块加载器通常就是一个JavaScript的库,我们把它包含到项目中,他可以理解我们的代码使用的模块格式(使用了哪一种语法),并且他知道怎么去加载并执行用这个语法写出来的模块。模块格式和模块加载器的关系就像是JavaScript语言和浏览器的关系一样。

** AMD(Asynchronous Module Definition) 格式**

主要用于定义会在浏览器端被加载的模块。正如它的名称所表达的,AMD格式支持异步加载模块。

还是用前面的例子。当player模块被请求时,模块加载器会调用define方法,执行define里的第二个参数函数,这个函数会返回模块API的对象。概念上这和暴露模块模式非常相似,但是它可以明确模块的依赖,并且暴露了更少的对象到全局变量上。

//player.js
define([],function(){
         var message=' Welcome to the game! ';
         function printMessage(){
                   console.log(message);
         }
         return {
                  showMessage:printMessage
         }
});



CommonJS格式

更常用服务器端的JavaScript里,多用于Node.js里。Node内置了支持CommonJS格式的模块加载器。但是,我们也可以通过使用 SystemJS 这个模块加载器在浏览器端的应用里使用CommonJs。CommonJS和AMD总体上非常相似,它可以管理依赖、为每个模块定义API。和AMD不同的是,CommonJS的模块不再被写在一个function里,可读性更高。

UMD ( universal Module Definition ) 格式

尝试兼容AMD和CommonJs两种格式。如果你要在浏览器端和服务器端加载同样的模块,那么就可以考虑使用这种模块格式。在服务器端,它将会被Node的CommonJS解析,在浏览器端,它会被像RequireJs这样的加载器解析。

System.register 格式

是为SystemJS 模块加载器而设计的。SystemJS可以加载多种不同的模块格式,所以不一定要使用System.register来写。

ES2015
和ES2015模块格式相比,上面四种定义模块的语法并不是内置在JavaScript语言中的。他们是独立于语言存在的。ES2015的模块格式是由ES2015,最新版的JavaScript 语言支持的。但是目前浏览器并不能支持ES2015所有的新特性,这其中就包括模块语法。所以在使用ES2015的模块语法来编码时,需要一个转译的步骤,来把你写的 JavaScript 模块转换成为那些能在模块加载器支持下能浏览器端被执行的模块格式。

针对以上各类的模块格式有对应不同的模块加载器。比如RequireJS 主要对应AMD格式的模块,而像 SystemJS 可同时解析AMD、CommonJS、UMD、System.register等模块格式。


Modules in ES2015


ES2015模块拥有天然的语言支持的优势,属于模块里的 “ 皇室血统 ”。

和其它模块格式相比,它同样可以管理依赖、封装实现细节、暴露明确的API的功能。不同的是,它不需要模块加载器的支持,但是因为目前浏览器还不能支持ES2015的模块特性,在使用的时候需要一个转译的过程。需要先把模块装换成AMD、CommonJS或其他的模块格式,然后再用加载器来解析。

比起其他模块格式,在模块的导入和导出上,ES2015拥有比较大的灵活性。

导入

  • 支持导入依赖的模块
  • 支持导入整个模块,也可以导入模块的一部分
  • 支持为模块创建别名

导出

  • 支持暴露模块API
  • 支持在声明时导出,也可以一次性导出一个列表
  • 支持指定一个默认导出


Module Bundlers


Module bundlers 解决的是和模块加载器一样的问题,只不过他是在编译阶段( build step) 而不是在运行时阶段(runtime)完成的。

一个bundler 会跟踪应用的模块依赖链,这和模块加载器相似。但是,它并不是在需要某个依赖的时刻去下载它,它是按照顺序将整个依赖链接起来。bundling之后,浏览器就不需要再下载那么多份js文件了,这有可能会减少应用启动的时间,不过,这点并不能百分百保证,开发者必须自己亲自去测试效果。

如果把非常多很大的模块链接到一个文件里,那么有可能应用会花更多的时间在加载上面,这种场景下,使用模块加载器可能会更合适。

Browserify

Browserify 想把为NodeJS 开发的模块应用到运行在浏览器端的JavaScript应用里。这点从它的名字就可以看出来(It attempts to Browserify Node.js modules )所以它bundle的是CommonJS格式的模块。

Webpack

  • 和Browserify不同的是,Webpack 可以bundle AMD、CommonJS、ES2015格式的模块。
  • 它提供了code split 的功能,可以把代码组织成几个bundles,这样可以在下载的时候得到更好的优化效果。
  • Webpack还可以用来bundle CSS、Image和其他静态资源。
  • 通过使用loader可以在bundle之前对各类文件进行预处理。

这里使用以上提到的各类前端模块格式实现同一个demo.有兴趣的可以看看哈:https://github.com/qinghaitvxq/Javascript-Module-Fundamentals

你可能感兴趣的:(Javascript 模块基础)