RequireJS是一個很是小巧的JavaScript模塊載入框架,是AMD規範最好的實現者之一。最新版本的RequireJS壓縮後只有14K,堪稱很是輕量。它還同時能夠和其餘的框架協同工做,使用RequireJS必將使您的前端代碼質量得以提高。javascript
1、AMD 介紹
css
前端開發在近一兩年發展的很是快,JavaScript做爲主流的開發語言獲得了史無前例的熱捧。大量的前端框架出現了,這些框架都在嘗試着解決一 些前端開發中的共性問題,可是實現又不盡相同。在這個背景下,CommonJS社區誕生了,爲了讓前端框架發展的更加成熟,CommonJS鼓勵開發人員 一塊兒在社區裏爲一些完成特定功能的框架制定規範。AMD(Asynchronous Module Definition)就是其中的一個規範。html
傳統JavaScript代碼的問題前端
讓咱們來看看通常狀況下JavaScript代碼是如何開發的:經過<script>標籤來載入JavaScript文件,用全局變量 來區分不一樣的功能代碼,全局變量之間的依賴關係須要顯式的經過指定其加載順序來解決,發佈應用時要經過工具來壓縮全部的JavaScript代碼到一個文 件。當Web項目變得很是龐大,前端模塊很是多的時候,手動管理這些全局變量間的依賴關係就變得很困難,這種作法顯得很是的低效。java
AMD(Asynchronous Module Definition)的引入jquery
從名稱上看便知它是適合script tag的。也能夠說AMD是專門爲瀏覽器中JavaScript環境設計的規範。它吸收了CommonJS的一些優勢,但又不照搬它的格式。開始AMD做爲CommonJS的transport format 存在,因沒法與CommonJS開發者達成一致而獨立出來。它有本身的wiki 和討論組 。git
AMD提出了一種基於模塊的異步加載JavaScript代碼的機制,它推薦開發人員將JavaScript代碼封裝進一個個模塊,對全局對象的依 賴變成了對其餘模塊的依賴,無須再聲明一大堆的全局變量。經過延遲和按需加載來解決各個模塊的依賴關係。模塊化的JavaScript代碼好處很明顯,各 個功能組件的鬆耦合性能夠極大的提高代碼的複用性、可維護性。這種非阻塞式的併發式快速加載JavaScript代碼,使Web頁面上其餘不依賴 JavaScript代碼的UI元素,如圖片、CSS以及其餘DOM節點得以先加載完畢,Web頁面加載速度更快,用戶也獲得更好的體驗。github
CommonJS的AMD規範中只定義了一個全局的方法,如清單1所示。web
define(id?, dependencies?, factory);
該方法用來定義一個JavaScript模塊,開發人員能夠用這個方法來將部分功能模塊封裝在這個define方法體內。apache
id表示該模塊的標識,爲可選參數。
dependencies是一個字符串Array,表示該模塊依賴的其餘全部模塊標識,模塊依賴必須在真正執行具體的factory方法前解決,這 些依賴對象加載執行之後的返回值,能夠以默認的順序做爲factory方法的參數。dependencies也是可選參數,當用戶不提供該參數時,實現 AMD的框架應提供默認值爲[「require」,」exports」,「module」]。
factory是一個用於執行改模塊的方法,它可使用前面dependencies裏聲明的其餘依賴模塊的返回值做爲參數,若該方法有返回值,當該模塊被其餘模塊依賴時,返回值就是該模塊的輸出。
CommonJS在規範中並無詳細規定其餘的方法,一些主要的AMD框架如RequireJS、curl、bdload等都實現了define方法,同時各個框架都有本身的補充使得其API更實用。
AMD設計出一個簡潔的寫模塊API:
define(id?, dependencies?, factory);
其中:
define(function() { return { mix: function(source, target) { } }; });
define(['base'], function(base) { return { show: function() { // todo with module base } } });
page.js
define(['data', 'ui'], function(data, ui) { // init here });
define({
users: [],
members: []
});
define('index', ['data','base'], function(data, base) { // todo });
define(function(require, exports, module) { var base = require('base'); exports.show = function() { // todo with module base } });
2、RequireJS
RequireJS會讓你以不一樣於往常的方式去寫JavaScript。你將再也不使用script標籤在HTML中引入JS文件,以及不用經過script標籤順序去管理依賴關係。
一、簡單示例
固然也不會有阻塞(blocking)的狀況發生。好,以一個簡單示例開始。
<!doctype html> <html> <head> <title>requirejs入門(一)</title> <meta charset="utf-8"> <!--引入require.js(實際上除了require.js,其它文件模塊都再也不使用script標籤引入)---> <script data-main="main" src="require.js"></script> </head> <body> ... </body> </html>
main.js
require.config({ paths: { jquery: 'jquery-1.7.2' } }); require(['jquery'], function($) { alert($().jquery); });
main.js中就兩個函數調用require.config和require。
這裏require函數的第一個參數是數組,數組中存放的是模塊名(字符串類型),數組中的模塊與回調函數的參數一一對應。這裏的例子則只有一個模塊「jquery」。
jQuery中的支持AMD代碼以下
if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); }
咱們知道jQuery最終向外暴露的是全局的jQuery和 $。以下
// Expose jQuery to the global object window.jQuery = window.$ = jQuery;
若是將jQuery應用在模塊化開發時,其實能夠不使用全局的,便可以不暴露出來。須要用到jQuery時使用require函數便可,
把目錄r1放到apache或其它web服務器上,訪問index.html。
網絡請求以下
咱們看到除了require.js外main.js和jquery-1.7.2.js也請求下來了。而它們正是經過requirejs請求的。
頁面上會彈出jQuery的版本
這是一個很簡單的示例,使用requirejs動態加載jquery。
二、寫一個本身的模塊:選擇器
爲演示方便這裏僅實現經常使用的三種選擇器id,className,attribute。RequireJS使用define來定義模塊。
本例目的:
<!doctype html> <html> <head> <title>requirejs入門(二)</title> <meta charset="utf-8"> <style type="text/css"> .wrapper { width: 200px; height: 200px; background: gray; } </style> </head> <body> <div class="wrapper"></div> <script data-main="js/main" src="require.js"></script> </body> </html>
注意:
selector.js代碼
define(function() { function query(selector,context) { var s = selector, doc = document, regId = /^#[\w\-]+/, regCls = /^([\w\-]+)?\.([\w\-]+)/, regTag = /^([\w\*]+)$/, regNodeAttr = /^([\w\-]+)?\[([\w]+)(=(\w+))?\]/; var context = context == undefined ? document : typeof context == 'string' ? doc.getElementById(context.substr(1,context.length)) : context; if(regId.test(s)) { return doc.getElementById(s.substr(1,s.length)); } // 略... } return query; });
define的參數爲一個匿名函數,該匿名函數執行後返回query,query爲函數類型。query就是選擇器的實現函數。
main.js 以下
require.config({ baseUrl: 'js' }); require(['selector'], function(query) { var els = query('.wrapper'); console.log(els) });
require.config方法執行配置了baseUrl爲「js」,baseUrl指的模塊文件的根目錄,能夠是絕對路徑或相對路徑。這裏用的是相對路徑。相對路徑指引入require.js的頁面爲參考點,通常是index.html。
把目錄r2放到apache或其它web服務器上,訪問index.html。
網絡請求以下
main.js和selector.js都請求下來了。
selector.js下載後使用query獲取頁面class爲「.wrapper」的元素,控制檯輸出了該元素。以下
3、寫一個具備依賴的事件模塊
具備依賴的事件模塊event提供三個方法bind、unbind、trigger來管理DOM元素事件。
event依賴於cache模塊,cache模塊相似於jQuery的$.data方法。提供了set、get、remove等方法用來管理存放在DOM元素上的數據。
示例實現功能: 爲頁面上全部的段落P元素添加一個點擊事件,響應函數會彈出P元素的innerHTML。
爲了獲取元素,用到了上一例寫的selector.js。不在重複貼其代碼
<!doctype html> <html> <head> <title>requirejs入門(三)</title> <meta charset="utf-8"> <style type="text/css"> p { width: 200px; background: gray; } </style> </head> <body> <p>p1</p><p>p2</p><p>p3</p><p>p4</p><p>p5</p> <script data-main="js/main" src="require.js"></script> </body> </html>
cache.js
define(function() { var idSeed = 0, cache = {}, id = '_ guid _'; // @private function guid(el) { return el[id] || (el[id] = ++idSeed); } return { set: function(el, key, val) { if (!el) { throw new Error('setting failed, invalid element'); } var id = guid(el), c = cache[id] || (cache[id] = {}); if (key) c[key] = val; return c; }, // 略去... }; });
cache模塊的寫法沒啥特殊的,與selector不一樣的是返回的是一個JS對象。
event.js 以下
define(['cache'], function(cache) { var doc = window.document, w3c = !!doc.addEventListener, expando = 'snandy' + (''+Math.random()).replace(/\D/g, ''), triggered, addListener = w3c ? function(el, type, fn) { el.addEventListener(type, fn, false); } : function(el, type, fn) { el.attachEvent('on' + type, fn); }, removeListener = w3c ? function(el, type, fn) { el.removeEventListener(type, fn, false); } : function(el, type, fn) { el.detachEvent('on' + type, fn); }; // 略去... return { bind : bind, unbind : unbind, trigger : trigger }; });
event依賴於cache,定義時第一個參數數組中放入「cache」便可。第二個參數是爲函數類型,它的參數就是cache模塊對象。
這樣定義後,當require事件模塊時,requirejs會自動將event依賴的cache.js也下載下來。
main.js 以下
require.config({ baseUrl: 'js' }); require(['selector', 'event'], function($, E) { var els = $('p'); for (var i=0; i<els.length; i++) { E.bind(els[i], 'click', function() { alert(this.innerHTML); }); } });
依然先配置了下模塊的根目錄js,而後使用require獲取selector和event模塊。
回調函數中使用選擇器$(別名)和事件管理對象E(別名)給頁面上的全部P元素添加點擊事件。
注意:require的第一個參數數組內的模塊名必須和回調函數的形參一一對應。
把目錄r3放到apache或其它web服務器上,訪問index.html。網絡請求以下
咱們看到當selector.js和event.js下載後,event.js依賴的cache.js也被自動下載了。這時點擊頁面上各個P元素,會彈出對應的innerHTML。以下
總結:
當一個模塊依賴(a)於另外一個模塊(b)時,定義該模塊時的第一個參數爲數組,數組中的模塊名(字符串類型)就是它所依賴的模塊。
當有多個依賴模時,須注意回調函數的形參順序得和數組元素一一對應。此時requirejs會自動識別依賴,且把它們都下載下來後再進行回調。
說明和其餘問題:
一、路徑與後綴名
在 require 一個 js 文件的時候,通常不須要加上後綴名。若是加上後綴名,會按照絕對路徑加載。沒有後綴名,是按照下面的路徑加載:
<script data-main=
"js/main"
src=
"js/require-jquery.js"
></script
>
也就是默認加載 data-main 指定的目錄,即 js/main.js 文件所在的目錄。固然,你能夠經過配置文件修改。
二、define 定義模塊方法只能用在獨立的js文件中,不能在頁面中直接使用。
不然會報 Mismatched anonymous define() module 錯誤。
三、和其餘第三方js類庫是否衝突?
不會衝突。通常比較規範的類庫,都會給本身的js加上命名空間。好比 wojilu 舊有的 wojilu.common.js ,其實就是放在 wojilu 命名空間中(固然是經過更原始的方式實現命名空間的)。
在經過 RequireJS 加載這些第三方的 js 的時候,徹底不要有任何擔心。
固然,若是第三方類庫可以使用 RequireJS 的方式進行改造,那是最好。好比 wojilu 中大多數js 都按照 RequireJS 的方式進行了改造。可是,若是你不改造,也是徹底沒關係的。
四、在代碼中 require 一個文件屢次,是否會致使瀏覽器反覆加載?
不會,這是 RequrieJS 的優勢,即便你反覆 require 它,它只加載一次。
參考: