require.js 最佳實踐

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壓縮。

相關文章
相關標籤/搜索