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
define(function(){
var add = function(x,y){
return x+y;
};
return{
add:add
};
});
math.js通過define()函數,定義了一個符合require.js要求的js模塊,它的返回值是一個對象,有一個屬性add,它是一個函數。通過下的方式就可以來調用該js模塊中定義的函數:
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方法。
在看一個例子:
/**
* This jQuery plugin displays pagination links inside the selected elements.
*
* @author Gabriel Birke (birke *at* d-scribe *dot* de)
* @version 1.2
* @param {int} maxentries Number of entries to paginate
* @param {Object} opts Several options (see README for documentation)
* @return {Object} jQuery Object
*/
define(['jquery'], function($){
jQuery.fn.pagination = function(maxentries, opts){
opts = jQuery.extend({
items_per_page:10,
num_display_entries:10,
current_page:0,
num_edge_entries:0,
link_to:"#",
prev_text:"Prev",
next_text:"Next",
ellipse_text:"...",
prev_show_always:true,
next_show_always:true,
callback:function(){return false;}
},opts||{});
return this.each(function() {
function numPages() {
return Math.ceil(maxentries/opts.items_per_page);
}
function getInterval() {
var ne_half = Math.ceil(opts.num_display_entries/2);
var np = numPages();
var upper_limit = np-opts.num_display_entries;
var start = current_page>ne_half?Math.max(Math.min(current_page-ne_half, upper_limit), 0):0;
var end = current_page>ne_half?Math.min(current_page+ne_half, np):Math.min(opts.num_display_entries, np);
return [start,end];
}
function pageSelected(page_id, evt){
current_page = page_id;
drawLinks();
var continuePropagation = opts.callback(page_id, panel);
if (!continuePropagation) {
if (evt.stopPropagation) {
evt.stopPropagation();
}
else {
evt.cancelBubble = true;
}
}
return continuePropagation;
}
function drawLinks() {
panel.empty();
var interval = getInterval();
var np = numPages();
var getClickHandler = function(page_id) {
return function(evt){ return pageSelected(page_id,evt); };
}
var appendItem = function(page_id, appendopts){
page_id = page_id<0?0:(page_id<np?page_id:np-1);
appendopts = jQuery.extend({text:page_id+1, classes:""}, appendopts||{});
if(page_id == current_page){
var lnk = jQuery("<span class='current'>"+(appendopts.text)+"</span>");
}else{
var lnk = jQuery("<a>"+(appendopts.text)+"</a>")
.bind("click", getClickHandler(page_id))
.attr('href', opts.link_to.replace(/__id__/,page_id));
}
if(appendopts.classes){lnk.addClass(appendopts.classes);}
panel.append(lnk);
}
if(opts.prev_text && (current_page > 0 || opts.prev_show_always)){
appendItem(current_page-1,{text:opts.prev_text, classes:"prev"});
}
if (interval[0] > 0 && opts.num_edge_entries > 0)
{
var end = Math.min(opts.num_edge_entries, interval[0]);
for(var i=0; i<end; i++) {
appendItem(i);
}
if(opts.num_edge_entries < interval[0] && opts.ellipse_text)
{
jQuery("<span>"+opts.ellipse_text+"</span>").appendTo(panel);
}
}
for(var i=interval[0]; i<interval[1]; i++) {
appendItem(i);
}
if (interval[1] < np && opts.num_edge_entries > 0)
{
if(np-opts.num_edge_entries > interval[1]&& opts.ellipse_text)
{
jQuery("<span>"+opts.ellipse_text+"</span>").appendTo(panel);
}
var begin = Math.max(np-opts.num_edge_entries, interval[1]);
for(var i=begin; i<np; i++) {
appendItem(i);
}
}
if(opts.next_text && (current_page < np-1 || opts.next_show_always)){
appendItem(current_page+1,{text:opts.next_text, classes:"next"});
}
}
var current_page = opts.current_page;
maxentries = (!maxentries || maxentries < 0)?1:maxentries;
opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0)?1:opts.items_per_page;
var panel = jQuery(this);
this.selectPage = function(page_id){ pageSelected(page_id);}
this.prevPage = function(){
if (current_page > 0) {
pageSelected(current_page - 1);
return true;
}
else {
return false;
}
}
this.nextPage = function(){
if(current_page < numPages()-1) {
pageSelected(current_page+1);
return true;
}
else {
return false;
}
};
drawLinks();
opts.callback(current_page, this);
});
};
return jQuery.fn.pagination;
});
上面的define()函數定義了一個jquery的分頁插件(文件名:jquery.pagination.js),它符合require.js模塊的規范。define(['jquery'], function($)... 表示該模塊依賴於 jquery 模塊,並向回調函數傳入jquery的全局對象 $, 那么這里的 ['jquery'] 又來自哪里呢?它其實來自於:
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的分頁插件,那么通過下面的配置,我就可以使用它:
require.config({
baseUrl:"/ems/js/",
paths: {
"jquery": "jquery.min",
"pagination": "jquery.pagination"
}
});
require(["pagination"], function(pagination){
$.patination(20);// ....
});
再看一個例子(文件名:dateUtil.js):
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()函數來格式化日期對象:
require.config({
baseUrl:"/ems/js/",
paths: {
"dateUtil": "dateUtil"
}
});
require(["dateUtil"], function(dateUtil){
alert(dateUtil.format("yyyy-MM-dd", new Date());
});
我們在頁面中引入上面的文件(文件名:main.js),就可以看到執行效果:
<!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()函數
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文件合並壓縮:
比如開發時,某頁面如下:
<!DOCTYPE html>
<html>
<head>
<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" />
</head>
<body>
<!--Header-part-->
<div id="header">
<h1><a href="javascript:;">Admin</a></h1>
</div>
<!--close-Header-part-->
<!--top-Header-menu-->
<div id="user-nav" class="navbar navbar-inverse">
<ul class="nav">
<li class=""><span id="top_header">xxxx系統</span></li>
<li><span id="cur_user">當前登陸用戶:</span></li>
<li id="top_logout" style="float:right;">
<a href="${ctx}/logout"><i class="icon icon-share-alt"></i></a>
</li>
</ul>
</div>
<script src="js/require.min.js" data-main="js/main.js"></script>
</body>
</html>
其main.js文件如下:
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文件就行了。
<script src="js/require.min.js" data-main="js/main.js"></script>
改成:
<script src="js/require.min.js" data-main="js/login.js"></script>
2) CSS文件合並壓縮:
合並壓縮之前,需要先定義一個main.css文件:
@import url(bootstrap.min.css); @import url(bootstrap-responsive.min.css); @import url(matrix-login.css);
然后調用命令合並壓縮:

四個CSS文件合並成了一個css文件:login.css。我們看下壓縮之后的login.css:

上面三行CSS的link:
<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" />
就可以換成一行:
<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壓縮。
