js模块化目前通用的规范有两种:commonJS和AMD规范。其中commonJS规范适用于服务端的js模块化开发,而AMD就是适用于浏览器的模块化开发标准。
关于AMD和commJS规范,我们需要知道一些开发工作背景(即以前遇到过的一些难题)
相信从事前端行业,我们都曾遇到这样的烦恼——文件加载之殇。这是我个人取的名字,要知道,我们现在的前段工作变得越来越复杂,加载的文件、插件和框架越来越多,有的插件依赖着好几个框架,最糟糕的是这几个框架之间还相互有着依赖,当一切准备就绪之后,不给力的带宽为你送来致命一击。
天,光是如此描述也足以让人抓狂了不是吗?
来,我们看一下实际场景:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>js模块化</title> <script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/jquery.min"></script> </head> <body> <h1>注意了!要加载模块了!</h1> </body> </html>
看看上面的代码,bootstrap.min.js这个文件需要依赖jQuery.min才能运行,那么这样的书写方式会导致什么结果呢?
那么第二种场景来了:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>js模块化</title> <script type="text/javascript" src="js/1.js"></script> <script type="text/javascript" src="js/2.js"></script> <script type="text/javascript" src="js/3.js"></script> <script type="text/javascript" src="js/4.js"></script> <script type="text/javascript" src="js/5.js"></script> <script type="text/javascript" src="js/6.js"></script> <script type="text/javascript" src="js/7.js"></script> <script type="text/javascript" src="js/8.js"></script> <script type="text/javascript" src="js/9.js"></script> <script type="text/javascript" src="js/10.js"></script> <script type="text/javascript" src="js/11.js"></script> <script type="text/javascript" src="js/12.js"></script> <script type="text/javascript" src="js/13.js"></script> <script type="text/javascript" src="js/14.js"></script> <script type="text/javascript" src="js/15.js"></script> </head> <body> <h1>注意了!要加载模块了!</h1> </body> </html>
千万不要觉得这是个玩笑,实际的项目开展中你很有可能遇到某一个页面需要十几个甚至是更多的js来支持,试想一下这么多的js同时加载会是一个什么样子呢?
举例这两种场景的目的其实就是为了引出AMD规范的特点和重要性。
AMD能干什么?
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
代码示例如下:
require([module], callback);
[module]是一个数组,里面是需要加载的依赖模块,第二个参数是回调函数。
实际例子:
require(['math'], function (math) { math.add(2, 3); });
上面的例子可以看出,math.add()需要在math模块加载完成之后才能运行,这就不会存在因为模块的安排导致的各种错误,比如找不到模块方法而报错。
AMD怎么玩儿?
首先推荐一个流行的AMD规范的库——require.js
然后我们回顾一下上面的场景2,当一张页面要加载15个js文件的时候,我们设想这个页面会面临什么问题?大概会有:渲染阻塞、js文件加载顺序处理、请求数过多等等。
require.js给我们带来的是什么呢?
1、实现js文件的异步加载,避免网页失去响应;
2、管理模块之间的依赖性,便于代码的编写和维护。(四个人的团队,大家各自书写各自的功能,然后暴露出模块名称变量即可)
而require.js的使用方式跟正常的使用方式一样,比如,js目录下,你有一个require.js。
<script type="text/javascript" src="js/require.js"></script>
当然,这样的加载方式不也是阻塞加载么?
我们可以这么做
<script src="js/require.js" defer async="true" ></script>
async属性表明这个js文件需要异步加载,而defer是针对IE的异步加载说明,这样的话,js文件加载就不会阻塞页面渲染了。
require.js加载之后,接下来就是我们自己的文件了,假如在js目录下你有一个main.js的文件,那么就这么写:
<script src="js/require.js" data-main="js/main"></script>
data-main的属性作用是,指定网页程序主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
上面的“主模块”,就是整个网页程序的入口模块,可以理解为所有代码都从这儿开始运行。
而main.js内部的结构是什么样的呢?来看看
// main.js require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){ // some code here });
可以看到,main.js文件是参照典型的AMD方式来加载模块。
然而问题又来了,这里只是展示了入口模块的代码结构,那些被加载的模块内部又是什么样子呢?符合AMD和require.js标准的模块是怎么写的呢?
接着看:
// math.js define(function (){ var add = function (x,y){ return x+y; }; return { add: add }; });
上面展示的是一个math.js模块,这个模块带有一个add方法。
现在,看到了入口程序模块和普通成员模块的书写方法之后,我们来玩儿一个简单的【模块化开发】
我们的主模块代码如下:
//入口程序引入了M1和M2两个模块,在程序中使用module1和module2作为模块别名 require(['M1','M2'],function(M1,M2){ M2.helloWord('模块加载成功了'); M1.showTime(); })
M1模块如下:
define(function(){ function showTime(){ alert('现在时间是:2017年7月3日'); } return { showTime:showTime } })
M2模块如下:
define(function(){ function helloWord(str,fn){ alert(str); } return { helloWord:helloWord } })
index代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>js模块化</title> <script src="js/require.js" data-main="js/main" defer async="true"></script> </head> <body> <h1>注意了!要加载模块了!</h1> </body> </html>
通过上面的代码,我们看到了一些require.js神奇的地方,接下来,我们继续:
如果要加载的模块名称很麻烦怎么办?
有的模块名称会很麻烦,比如“jQuery.min.js”,我们在加载的时候难道别称就是jQuery.min么?这样很明显会被requirejs误会,但是起一个别的别称显然不太符合书写规范,也对以后代码维护埋下隐患,怎么办呢?可以像这样:
require.config({ paths:{ "jquery":"jquery.min", "backbone":"backbone.min" } }) require(['M3','M4'],function(M3,M4){ M3.sayHi(); M4.sayBye(); })
我们假设“jQuery.min”和“backbone.min”这两个文件都和入口文件在同一个目录下,那么在入口文件顶部对require.js进行config的路径映射配置即可,配置属性为paths
还有另一种想法是这样的,我们需要的模块和入口文件不在一个目录中,这时候我们可以挨个儿设置路径,当然,也可以统一设置,怎么做呢?
//统一设置 require.config({ baseUrl:"js/lib", //设置根目录的时候需要将当前入口文件所处的目录带上 paths:{ "jquery":"jquery.min", "backbone":"backbone.min" } }); //单独设置 require.config({ paths:{ "jquery":"lib/jqeury.min", "backbone":"lib/backbone.min" } }) require(['M3','M4'],function(M3,M4){ M3.sayHi(); M4.sayBye(); })
提问:如果需要的文件在CDN上呢?
到这里,我们学会了如何使用require.js以及require.js模块的基本写法,那么还有个问题
如果我有的模块并非是require.js规范的模块又改如何加载呢?
require.js加载非规范的模块可以这么办
首先当然是一个HTML页面index1:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="js/require.js" data-main="js/main2"></script> </head> <body> <h1>此处演示加载非AMD规范的模块</h1> </body> </html>
然后是一个简单的模块,此处说明一下,一般情况对外暴露对象的方式都是注册到window,就像之前我们写基础模块一样,这是模块M5
//将代码封装后注册到window全局中 (function(window){ var module5=new Object(); module5.saySth=function(){ alert('这是一个非AMD规范被调用了'); } return window.module5=module5; })(window)
最后就是入口文件的配置和加载——main2
require.config({ paths:{ "M5":"M5" //paths配置非规范模块M5的路径 }, shim:{ //shim配置非规范模块的依赖以及暴露的对象映射名称 'M5':{ //此处的“M5”是文件名,如果这个M5的文件名是M5.min.js那么此处书写的就是M5.min deps:[], //deps表示M5依赖的模块,如果有则写入数组,如果没有则为空数组或者不写 exports:'module5' //M5在全局暴露的是一个module5的对象名称,用于在require加载模块后使用 } } }); require(['M5'],function(M5){ //M5只是M5模块引入后的名称映射,可以改写成为任何形式,当然,这样写显然更符合书写规范 M5.saySth(); })
到这里,相信以往我们自己书写的相关代码都可以得到整理,以用于模块化加载