require.js 最佳实践【转】
https://www.cnblogs.com/digdeep/p/4607131.html
require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(require.js), Javascript模块化工具require.js教程,RequireJS 参考文章
1. require.js的主要作用是js的工程化,规范化:
1)它是一个js脚本的加载器,它遵循AMD(Asynchronous Module Definition)规范,实现js脚本的异步加载,不阻塞页面的渲染和其后的脚本的执行。
并提供了在加载完成之后的执行相应回调函数的功能;
2)它要求js脚本的模块化,也就是文件化;require.js的作用之一就是加载js模块,也就是js文件。所以我们的js的书写应该模块化,也就是文件化。
3)它可以管理js模块/文件之间的依赖; js模块化,文件化之后,它们之间的依赖可以通过require.js优雅的解决;
4)require.js中提供的优化器 r.js 可以来优化页面中的js脚本和css文件,达到提高页面响应速度,减少页面所需要的http/https请求次数。在极端优化的情况下,通过r.js优化之后的页面只需要一次js脚本请求和一次CSS文件请求。这就极大的减少了页面所需要的http/https请求的次数,提高了页面的加载速度。r.js的优化分为两种方式:一是压缩js和css文件,也就是去掉空格,空行,将长变量名换成短变量名之类的;二是合并多个js文件为一个js文件,合并多个css文件为一个。
5) 通过使用require.js之后,我们只需要在页面引入一行<script>标签,类似于:<script src="js/require.js" data-main="js/login.js"></script>,甚至也可以只引入一行<style>标签,十分优雅。注意引入一行<script>标签并不等价于只需要一次js的http/https的请求。
2. require.js模块的写法:
require.js要求我们的js模块,也就是js文件按照一定的格式书写:也就是最好通过define()函数来写js模块,比如:math.js
1
2
3
4
5
6
7
8
|
define( function (){ var add = function (x,y){ return x+y; }; return { add:add }; }); |
math.js通过define()函数,定义了一个符合require.js要求的js模块,它的返回值是一个对象,有一个属性add,它是一个函数。通过下的方式就可以来调用该js模块中定义的函数:
1
2
3
4
5
6
7
8
9
10
|
require.config({ baseUrl: "/ems/js/" , paths:{ "math" : "math" } }); require([ "math" ], function (math){ alert(math.add(100,20)); }); |
require.config的主要作用是配置 模块ID/模块名称 和 它对应的js文件所在的位置。上面的那个配置就是将 /ems/js/math.js(ems是项目名称) 文件配置成一个ID为math的模块,然后通过 require(["math"], function(math)(){}); 就可以异步来加载 /ems/js/math.js 文件,加载完成之后,执行回调函数。回调函数中调用了math模块中的add方法。
在看一个例子:
上面的define()函数定义了一个jquery的分页插件(文件名:jquery.pagination.js),它符合require.js模块的规范。define(['jquery'], function($)... 表示该模块依赖于 jquery 模块,并向回调函数传入jquery的全局对象 $, 那么这里的 ['jquery'] 又来自哪里呢?它其实来自于:
1
2
3
4
5
6
|
require.config({ baseUrl: "/ems/js/" , paths: { "jquery" : "jquery.min" } }); |
该配置将 /ems/js/jquery.min.js 配置成require.js的模块,模块ID为"jquery",所以我们才能使用 define(['jquery'], function($) 来引用"jquery"模块。
define(["xxx","yyy"], function(xxx,yyy){}); define函数定义符合require.js规范的模块,数组参数指定该模块依赖的所有模块,那么这些被依赖的模块异步加载完成之后,然后执行回调函数,回调函数的返回值就是该模块的定义。返回值一般是一个对象,或者一个函数。然后该模块又可以被其它模块所依赖和使用。比如上面的: jquery.pagination.js,它定义了一个jquery的分页插件,那么通过下面的配置,我就可以使用它:
1
2
3
4
5
6
7
8
9
10
11
12
|
require.config({ baseUrl: "/ems/js/" , paths: { "jquery" : "jquery.min" , "pagination" : "jquery.pagination" } }); require([ "pagination" ], function (pagination){ $.patination(20); // .... }); |
再看一个例子(文件名:dateUtil.js):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
define( function (){ var dateFormat = function (fmt, date){ if (!(date instanceof Date)) return ; var o = { "M+" : date.getMonth() + 1, // 月份 "d+" : date.getDate(), //日 "H+" : date.getHours(), //24小时制 "h+" : date.getHours()%12 == 0 ? 12 : date.getHours()%12, //12小时制 "m+" : date.getMinutes(), //分 "s+" : date.getSeconds(), //秒 "q+" : Math.floor((date.getMonth() + 3) / 3), //季度 "S" : date.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "" ).substr(4 - RegExp.$1.length)); for ( var k in o) if ( new RegExp( "(" + k + ")" ).test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (( "00" + o[k]).substr(( "" + o[k]).length))); return fmt; }; return { format:dateFormat }; }); |
通过下面的配置,就可以使用dataUtil.js中的format()函数来格式化日期对象:
1
2
3
4
5
6
7
8
9
10
|
require.config({ baseUrl: "/ems/js/" , paths: { "dateUtil" : "dateUtil" } }); require([ "dateUtil" ], function (dateUtil){ alert(dateUtil.format( "yyyy-MM-dd" , new Date()); }); |
我们在页面中引入上面的文件(文件名:main.js),就可以看到执行效果:
1
2
3
4
5
6
7
8
9
|
<!DOCTYPE html> <html> <head> </head> <body> <span>body1111</span> <script src= "js/require.js" data-main= "js/main.js" ></script> </body> </html> |
执行结果:
3. require.config函数
require.config如上面所说,主要是定义 模块ID 和 它所对应的js文件的位置。参数是一个json格式的对象,baseUrl属性指定paths中的路径的相对路径。paths是一个key/value的键值对形式,key表示模块ID,value表示相对于baseUrl的相对路径,需要省略文件后缀 .js 。还有一个shim的常用属性,用来配置不符合require.js规范的js模块(没有使用define()来书写),使之也能被我们的require()函数来使用。但是shim配置的模块,无法通过cnd使用。其使用方法参见前面的转载文章。
4. require()函数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
require.config({ paths: { "jquery" : "jquery.min" , "math" : "math" , "dateUtil" : "dateUtil" } }); require([ 'jquery' , 'math' , "dateUtil" ], function ($, math, dateUtil){ alert($( "span" ).text()); alert(math.add(1,30)); alert(dateUtil.format( "yyyy-MM-dd" , new Date())); }); |
require()函数,第一个参数,引入依赖的require模块, 在所有依赖异步加载完成之后,将这些模块的返回的对象或者返回的函数传入回调函数,那么在回调函数中就可以使用这些被依赖的模块的功能了。比如上面使用 math.add(1,30)。
5. r.js 优化(合并压缩js和CSS文件)
按照require方式进行模块化之后,必然会产生很多的js文件,它们通过环环相扣的方式,按照依赖关系异步加载,那么必然会导致js文件所需要的http/https请求极大的增加,这时,就应该使用 r.js 来优化了。它可以将一个页面比如 login.jsp, 需要的所有的js文件合并成一个js文件,也就是说只需要一次http/https请求就行了。所以就解决了js模块化之后http/https请求增多的问题,并且还减少到了只需要一次请求。同时r.js还可以压缩js文件。并且正对css文件也可以同样的方式减小合并压缩。优化一般在开发完成之后,发布之前进行。
1)js文件合并压缩:
比如开发时,某页面如下:
其main.js文件如下:
1
2
3
4
5
6
7
8
9
10
11
|
require.config({ baseUrl: "/emsjs/" , paths: { "jquery" : "jquery.min" , "dateUtil" : "dateUtil" } }); require([ 'jquery' , 'dateUtil' ], function ($, dateUtil){ // ... }); |
那么显然该页面有 3个 CSS文件,2个js文件。那么针对js文件,我们可以使用node.js来合并:
我们看到将 jquery.js, dateUtil.js, main.js 三个文件合并压缩成了一个文件:login.js, 那么我们在页面中就只需要引入login.js文件就行了。
1
|
<script src= "js/require.min.js" data-main= "js/main.js" ></script> |
改成:
1
|
<script src= "js/require.min.js" data-main= "js/login.js" ></script> |
2) CSS文件合并压缩:
合并压缩之前,需要先定义一个main.css文件:
1
2
3
|
@import url (bootstrap.min.css); @import url (bootstrap-responsive.min.css); @import url (matrix-login.css); |
然后调用命令合并压缩:
四个CSS文件合并成了一个css文件:login.css。我们看下压缩之后的login.css:
上面三行CSS的link:
1
2
3
|
<link rel= "stylesheet" href= "css/bootstrap.min.css" /> <link rel= "stylesheet" href= "css/matrix-login.css" /> <link rel= "stylesheet" href= "css/bootstrap-responsive.min.css" /> |
就可以换成一行:
1
|
<link rel= "stylesheet" href= "css/login.css" /> |
r.js的优化的详细介绍,可以参考前面转载的 RequireJS 参考文章 中的进阶的三篇文章。也可以参考require.js官网关于r.js的介绍。
6. require.js 最佳实践
前面说了那么多,最后才说到require.js的最佳实践。
1)使用 define() 定义符合require规范的模块;
2)使用require.config() 配置模块ID和它对应的js模块所在文件路径;require.config()是将define()定义的模块和require()依赖的模块连接起来;
3)使用require()指定其所依赖的模块,在回调中实现页面上需要的功能,当然define()函数也需要指定其所依赖的模块;
require()和define()函数其实十分相似,都指定依赖的模块,都有回调函数;
4)使用r.js合并优化。这里最重要。合并优化涉及到一个取舍问题,比如前面的 jquery.min.js 是否应该被合并进去呢?因为jquery.min.js是一个通用的js库文件,那么其实几乎每一个页面都需要改文件,那么其实我们只是在第一次访问该网站时,需要下载一次jquery.min.js文件,其后使用的都是缓存中的,status都是304;但是如果我们每个页面都将 jquery.min.js 合并进该页面的唯一的 js 文件,那么jquery.min.js就会被每个页面所下载,因为每个页面都合并了它。个人是觉得不应该将jquery.min.js这样的通用库合并进去的,而是应该放入cnd中,这样既不会受到浏览器访问同一个域名时,并发数量的限制,也可以使其能够被缓存。但是 304 好像也是需要发送一次http/https请求的?所以如何取舍呢?CSS文件bootstrap.min.css也遇到相似的取舍问题。
个人倾向于不合并jquery.min.js和bootstrap.min.css等类似的基础文件。所以最佳require.js的实践就是,每个页面只引入一个js文件,该js文件不合并jquery.min.js文件以及类似的js文件,合并其它所有依赖的js文件。每个页面除了bootstrap.min.css类似的基础文件需要的<link >之外,还引入一个合并其它所有需要的css文件的<link>标签。
7. 关于压缩
关于压缩,上面说到了使用 r.js 进行压缩是指去掉空格,空行,将长变量名换成短的等等;压缩还有另外一层压缩:配置tomcat或者nginx/apache等web服务器,也是可以配置对CSS/JS/HTML等进行压缩的,一般浏览器都支持(能解压)服务器端进行的gzip压缩。