js通過script標籤的默認加載方式是同步的,即第一個script標籤內的js加載完成後,才開始加載第二個,以此類推,直至js文件全部加載完畢。且js的依賴關係必須通過script的順序才能確保;而在js加載期間,瀏覽器將停止響應,這大大影響了用戶體驗,基於此,很多解決js以來和加載的方案出現,require js就是其中之一。
requirejs加載的模塊,一般為符合AMD標準的模塊,即用define定義,用ruturn返回暴露方法、變量的模塊;requirejs也可以加載飛AMD標準的模塊,但比較麻煩,這次不涉及。
require加載js主涉及以下方面:
html demo
<script src ="js/require.js" defer async="true" data-main="js/main" ><!--給出requirejs路徑,聲明為異步加載,指定入口模塊為main.js(可省略.js)-->
main.js
require.config({ //聲明模塊的位置paths: { "jquery":"libs/jquery" "login" : "libs/login" } //或使用baseUrl指定所有模塊的路徑baseUrl: "js/libs" }); //使用require加載模塊,第一個參數為數組,即要加載的模塊,將按數組順序加載;第二個為回調函數,在全部加載完成後執行。 require(['jquery','login'],function($,Login){ alert("jquery and login module load success!"); Login.do_login(); //some else });符合amd的login module定義
//依賴jquery的定義define(['jquery'],function($){ // some definations function do_login(){ $.post('/sessions/create',{uname:$("#uname").val(), password:$("#password").val()},function(data){ //some }); return {do_login:do_login}; } });//不依賴其他模塊的定義define(function(){ //some definations return {xx:xx};});rails沒有應用js加載器,一方面是新版本的rails的asset pipe會將所有的js文件打包為一個js文件,沒有多個js加載的狀態,另一方面turbolink使用褒貶參半的所謂pjax技術,默認鏈接改為ajax方式,只獲取html的bod部分,head部分不變動,使js的加載只在第一次打開網站時進行。
案例一: 加載JavaScript 文件
<script src="./js/require.js"></script> <script> require(["./js/a.js", "./js/b.js"], function() { myFunctionA(); myFunctionB(); }); </script>require 方法裡的這個字符串數組參數可以允許不同的值,當字符串是以”.js”結尾,或者以”/”開頭,或者就是一個URL 時,RequireJS 會認為用戶是在直接加載一個JavaScript 文件,否則,當字符串是類似”my/module”的時候,它會認為這是一個模塊,並且會以用戶配置的baseUrl 和paths 來加載相應的模塊所在的JavaScript 文件。配置的部分會在稍後詳細介紹。
這裡要指出的是,RequireJS 默認情況下並沒有保證myFunctionA 和myFunctionB 一定是在頁面加載完成以後執行的,在有需要保證頁面加載以後執行腳本時,RequireJS 提供了一個獨立的domReady 模塊,需要去RequireJS 官方網站下載這個模塊,它並沒有包含在RequireJS 中。有了domReady 模塊,案例一的代碼稍做修改加上對domReady 的依賴就可以了。
案例二: 頁面加載後執行JavaScript
<script src="./js/require.js"></script> <script> require(["domReady!", "./js/a.js", "./js/b.js"], function() { myFunctionA(); myFunctionB(); }); </script>執行案例二的代碼後,通過Firebug 可以看到RequireJS 會在當前的頁面上插入為a.js 和b.js 分別聲明了一個< script> 標籤,用於異步方式下載JavaScript 文件。 async 屬性目前絕大部分瀏覽器已經支持,它表明了這個< script> 標籤中的js 文件不會阻塞其他頁面內容的下載。
案例三:RequireJS 插入的< script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="js/a.js" src="js/a.js"></script>
使用RequireJS 來定義JavaScript 模塊
這裡的JavaScript 模塊與傳統的JavaScript 代碼不一樣的地方在於它無須訪問全局的變量。模塊化的設計使得JavaScript 代碼在需要訪問”全局變量”的時候,都可以通過依賴關係,把這些”全局變量”作為參數傳遞到模塊的實現體裡,在實現中就避免了訪問或者聲明全局的變量或者函數,有效的避免大量而且複雜的命名空間管理。
如同CommonJS 的AMD 規範所述,定義JavaScript 模塊是通過define 方法來實現的。
下面我們先來看一個簡單的例子,這個例子通過定義一個student 模塊和一個class 模塊,在主程序中實現創建student 對象並將student 對象放到class 中去。
案例四: student 模塊,student.js
define(function(){ return { createStudent: function(name, gender){ return { name: name, gender: gender }; } }; });案例五:class 模塊,class.js
define(function() { var allStudents = []; return { classID: "001", department: "computer", addToClass: function(student) { allStudents.push(student); }, getClassSize: function() { return allStudents.length; } }; } );案例六: 主程序
require(["js/student", "js/class"], function(student, clz) { clz.addToClass(student.createStudent("Jack", "male")); clz.addToClass(student.createStudent("Rose", "female")); console.log(clz.getClassSize()); // 輸出2 });student 模塊和class 模塊都是獨立的模塊,下面我們再定義一個新的模塊,這個模塊依賴student 和class 模塊,這樣主程序部分的邏輯也可以包裝進去了。
案例七: 依賴student 和class 模塊的manager 模塊,manager.js
define(["js/student", "js/class"], function(student, clz){ return { addNewStudent: function(name, gender){ clz.addToClass(student.createStudent(name, gender)); }, getMyClassSize: function(){ return clz.getClassSize(); } }; });案例八:新的主程序
require(["js/manager"], function(manager) { manager.addNewStudent("Jack", "male"); manager.addNewStudent("Rose", "female"); console.log(manager.getMyClassSize());// 輸出2 });通過上面的代碼示例,我們已經清楚的了解瞭如何寫一個模塊,這個模塊如何被使用,模塊間的依賴關係如何定義。還是有一些使用技巧需要提示一下:
盡量不要提供模塊的ID,如AMD 規範所述,這個ID 是可選項,如果提供了,在RequireJS 的實現中會影響模塊的可遷移性,文件位置變化會導致需要手動修改該ID。
每個JavaScript 文件只定義一個模塊,模塊名稱和文件路徑的查找算法決定了這種方式是最優的,多個的模塊和文件會被優化器進行優化。 避免模塊的循環依賴,如果實在避免不了,可以模塊中加上對”require”模塊的依賴,在代碼中直接用
require(”dependencyModuleName”)
配置RequireJS:
前面的介紹中,我們似乎忽略了一個基本問題,模塊名字是怎麼來的?當我在require 一個模塊時,這個模塊是如何映射到具體的JavaScript 文件上去?這就涉及到如何配置RequireJS。
最簡化的加載RequireJS 的方式如案例2 所示,在這種情況下,我們沒有指定一個baseUrl 和paths 給RequireJS,如果通過如案例10 所示方式,則data-main 指定了一個在當前index.html 目錄並行的文件夾下的/js/main.js 作為程序入口,而/js 目錄也將作為baseUrl 在其他模塊定義時候使用。
案例九:載入require.js
<script data-main="js/main" src="scripts/require.js"></script>
因此,我們前面示例中的所有模塊依賴,都可以去掉”js/”,直接寫”student”, ”class”,”manager” 等。 一種更為直接的方式顯示指定baseUrl 和paths 就是利用require.config 來設置這些參數。如案例十所示。
案例十. 配置RequireJS
<script type="text/javascript" src="./js/require.js"></script> <script type="text/javascript"> require.config({ baseUrl: "./js", paths: { "some": "some/v1" }, waitSeconds: 10 }); require( ["some/module", "my/module", "./js/a.js"], function(someModule, myModule) {} ); </script>baseUrl指明的是所有模塊的base URL,比如”my/module”所加載的script實際上就是/js/my/module.js。注意,以.js 結尾的文件加載時不會使用該baseUrl,它們仍然會使用當前index.html所在的相對路徑來加載,所以仍然要加上”./js/”。如果baseUrl沒有指定,那麼就會使用data-main中指定的路徑。
paths 中定義的路徑是用於替換模塊中的路徑,如上例中的some/module 具體的JavaScript 文件路徑是/js/some/v1/module.js 。 waitSeconds 是指定最多花多長等待時間來加載一個JavaScript 文件,用戶不指定的情況下默認為7 秒。
另外一個重要的配置是packages,它可以指定其他符合CommonJS AMD 規範的目錄結構,由此帶來了豐富的擴展性。如Dojo、jQuery 等的模塊也可以通過該配置來讓RequireJS 加載。
其他可配置的選項還包括locale、context、deps、callback等,有興趣的讀者可以在RequireJS 的官方網站查閱相關文檔。