require.js是一個js庫,相關的基礎知識,前面轉載了兩篇博文:Javascript模塊化編程(require.js), Javascript模塊化工具require.js教程,RequireJS 參考文章javascript
1. require.js的主要做用是js的工程化,規範化:css
1)它是一個js腳本的加載器,它遵循AMD(Asynchronous Module Definition)規範,實現js腳本的異步加載,不阻塞頁面的渲染和其後的腳本的執行。html
並提供了在加載完成以後的執行相應回調函數的功能;java
2)它要求js腳本的模塊化,也就是文件化;require.js的做用之一就是加載js模塊,也就是js文件。因此咱們的js的書寫應該模塊化,也就是文件化。node
3)它能夠管理js模塊/文件之間的依賴; js模塊化,文件化以後,它們之間的依賴能夠經過require.js優雅的解決;jquery
4)require.js中提供的優化器 r.js 能夠來優化頁面中的js腳本和css文件,達到提升頁面響應速度,減小頁面所須要的http/https請求次數。在極端優化的狀況下,經過r.js優化以後的頁面只須要一次js腳本請求和一次CSS文件請求。這就極大的減小了頁面所須要的http/https請求的次數,提升了頁面的加載速度。r.js的優化分爲兩種方式:一是壓縮js和css文件,也就是去掉空格,空行,將長變量名換成短變量名之類的;二是合併多個js文件爲一個js文件,合併多個css文件爲一個。nginx
5) 經過使用require.js以後,咱們只須要在頁面引入一行<script>標籤,相似於:<script src="js/require.js" data-main="js/login.js"></script>,甚至也能夠只引入一行<style>標籤,十分優雅。注意引入一行<script>標籤並不等價於只須要一次js的http/https的請求。web
2. require.js模塊的寫法:apache
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壓縮。