require.js 最佳實踐


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM