随着前端JavaScript代码愈来愈重,如何组织JavaScript代码变得很是重要,好的组织方式,可让别人和本身很好的理解代码,也便于维护和测试。模块化是一种很是好的代码组织方式,本文试着对JavaScript模块化开发的一些基础知识和具体使用作一些阐释。javascript
“模块是为完成某一功能所需的一段程序或子程序。模块是任何robust(健壮、强壮)的应用架构不可缺乏的一部分,是系统中职责单一且可替换的部分。”html
简单理解:模块就是实现特定功能的一组方法,用用来实现代码的封装、加强代码可重用性,知足代码不断维护升级,分工合做的须要。如何开发新的模块,和复用已有模块来实现应用的功能,是咱们须要考虑的事情,理想状况下,开发者只须要实现核心的业务逻辑,其余均可以加载别人已经写好的模块。前端
关于模块,更多更详细部分请参考:深刻理解JavaScript 模块模式
java
JavaScript 的当前版本,并无为开发者们提供以一种简洁、有条理地的方式来引入模块的方法(ECMAScipt第六版表示会支持)。node
做为代替,当前的开发者们只能被迫降级使用模块模式或是对象字面量模式的各类变体。经过不少这样的方法,各模块的脚本被串在一块儿注入到 DOM 中(做为 script
标签注入到 DOM 中)。jquery
但好消息是在前端前辈们的不懈努力下,现在编写模块化的 JavaScript 目前已经变得极为简单,并摸索出一些广泛适用性的标准:AMD、CommonJSgit
CommonJSgithub
wiki地址 http://wiki.commonjs.org/ajax
JavaScript是一个强大、流行的语言,它有不少快速高效的解释器。官方JavaScript标准定义的API是为了构建基于浏览器的应用程序。然而,并无定于一个用于更普遍的应用程序的标准库。api
commonjs 是一个志愿性质的工做组,它致力于设计、规划并标准化 JavaScript API。从而填补原生JavaScript标准库过少的缺点。它的终极目标是提供一个相似Python,Ruby和Java标准库。它试图覆盖更宽泛的方面好比 IO、文件系统、promise 模式等等。这样的话,开发者可使用CommonJS API编写应用程序,而后这些应用能够运行在不一样的JavaScript解释器和不一样的主机环境中。如今很是火爆的nodejs实际上就是commonjs的一个实现。 ——CommonJS是一种规范,NodeJS是这种规范的实现。
在CommonJS中,使用全局性方法require()加载模块。假定有一个数学模块math.js,就能够像下面这样加载。
// 加载模块 var math = require('math'); // 调用模块提供的方法 math.add(2,3); // 5
注意,math.add(2, 3),在第一行require('math')以后运行,所以必须等math.js加载完成。也就是说,若是加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,由于全部的模块都存放在本地硬盘,能够同步加载完成,等待时间就是硬盘的读取时间。可是,对于浏览器,这倒是一个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
所以,浏览器端的模块,在网站性能优化正在逐步成为产业的今天,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。
AMD RequireJS介绍
AMD是“Asynchronous Module Definition”的缩写,意思就是“异步模块定义”。从名称上就能够看出,它是经过异步方式加载模块的,模块的加载不影响后续语句的执行,全部依赖加载中的模块的语句,都会放在一个回调函数中,等到该模块加载完成后,这个回调函数才运行。
它是适合script tag的,是专门为浏览器中JavaScript环境设计的规范,它有不少独特的优点,包括天生的异步及高度灵活等特性,这些特性可以解除常见的代码与模块标识间的那种紧密耦合。
RequireJS 是 AMD 规范最好的实现者之一,是一个很是小巧的 JavaScript 模块载入框架。 从架构层抽象出“模块化”开发方案,并已标准化了模块化开发,同时和其余的开发框架保持兼容。
主要用于浏览器端,但也适用于Rhino / Node 等环境,是当今最经常使用的JavaScript库之一,它兼容全部主流浏览器。
IE 6+ .......... compatible ✔
Firefox 2+ ..... compatible ✔
Safari 3.2+ .... compatible ✔
Chrome 3+ ...... compatible ✔
Opera 10+ ...... compatible ✔
使用RequireJS,咱们可以更容易地实现更复杂,更强大的JS的富客户端程序。
使用它咱们能够解决两个问题:
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
它同时还起到了隐形命名空间的做用
注:不管当前 JavaScript 代码是内嵌仍是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成。JavaScript 执行过程耗时越久,浏览器等待响应用户输入的时间就越长,虽然可使用async和defer关键字使得加载异步,但可能所以在加载过程当中丢失加载的顺序。
使用require.js的第一步,是先去官方网站下载最新版本。
下载后,假定把它放在js子目录下面,就能够加载了。
<!DOCTYPE html> <html> <head> <title>My Sample Project</title> <!-- data-main属性指定在require.js加载完成后,加载js/main.js文件. --> <script data-main="js/main" src="js/require.js"></script> </head> <body> <h1>My Sample Project</h1> </body> </html>
注意data-main属性,因为require.js默认的文件后缀名是js,因此能够把main.js简写成main。
在mian中咱们就能够很愉快地进行开发了,按照这种方式,整个页面咱们只须要引入这一个js文件就能够了。
咱们以使用jquery 为例:
// 配置jquery 模块的文件路径 require.config({ paths: { jquery: 'jquery-1.8.3', underscore: 'underscore.min' // 能够同时配置多个文件 } }); // 使用模块 require(['jquery', 'underscore'], function($, _) { console.log($().jquery, _.VERSION); });
若是这些模块在其余目录,好比js/lib目录,能够改变基目录(baseUrl)。
require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", "underscore": "underscore.min" } });
paths参数配置 模块名和路径, 路径能够是远程路径:http:http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js 后缀名可省略。
若是jquery就在main.js文件同目录下,则能够省略配置的步骤,直接使用便可。
// 使用模块 require(['jquery'], function($) { console.log($().jquery); });
AMD规范的API很是简单:
define(id?, dependencies?, factory);
define
函数接受三个参数:
jquery
或者其余用户自定义模块,没有依赖的话能够为 [] ;例如: jQuery从1.7后开始支持AMD规范,即若是jQuery做为一个AMD模块运行时,它的模块名是“jquery”。注意“jquery” 是小写的。
jQuery中的支持AMD代码以下:
if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); }
一个完整的模块定义包含模块名称,模块的依赖和回调函数,好比下面的代码:
define("adder", ["math"], function (math) { return { addTen : function (x) { return math.add(x, 10); } }; });
若是这个模块并无依赖,那么回调参数默认是 require,exports(一个空的输出对象,回调函数没有返回值的时候,默认返回 ), module( 模块自身 ),这时模块能够改写为:
define("adder", function (require, exports) { exports.addTen = function (x) { return x + 10; }; });
若是省略第一个参数,则会定义一个匿名模块,见代码:
define( function (require, exports) { exports.addTen = function (x) { return x + 10; }; });
在实际中,使用的更多的是匿名模块定义方式,由于这样更加的灵活,模块的标识和它的源代码再也不相关,开发人员能够把这个模块放在任意的位置而不须要修改代码。通常只有在要使用工具打包模块到一个文件中时,才会声明第一个参数,因此应该尽可能避免给模块命名。
在写模块的时候,也有可能没有依赖或者稍后才须要加载依赖
define(function (require, exports, module) { // …… var a = require('a'), b = require('b'); exports.action = function () { // …… }; });
上述回调函数里的require的使用将被自动进行动态加载。