世面上有好多JavaScript的加載器,好比 sea.js, require.js, yui loader, labJs...., 加載器的使用範圍是一些比較大的項目, 我的感受若是是小項目的話能夠不用, 我用過seaJS和requireJS, 在項目中用過requireJS, requireJS是符合AMD,全稱是(Asynchronous Module Definition)即異步模塊加載機制 , seaJS是符合CMD規範的加載器。javascript
AMD規範是依賴前置, CMD規範是依賴後置, AMD規範的加載器會把全部的JS中的依賴前置執行 。 CMD是懶加載, 若是JS須要這個模塊就加載, 不然就不加載, 致使的問題是符合AMD規範的加載器(requireJS), 可能第一次加載的時間會比較久, 由於他把全部依賴的JS所有一次性下載下來;php
常識,jQuery是支持AMD規範,並不支持CMD規範,也就是說, 若是引入的是seaJS,想要使用jQuery,要用alias配置, 或者直接把 http://cdn.bootcss.com/jquery/2.1.4/jquery.js 直接引入頁面中;css
//這是jQuery源碼的最後幾行, jQuery到了1.7才支持模塊化; // Register as a named AMD module, since jQuery can be concatenated with other // files that may use define, but not via a proper concatenation script that // understands anonymous AMD modules. A named AMD is safest and most robust // way to register. Lowercase jquery is used because AMD module names are // derived from file names, and jQuery is normally delivered in a lowercase // file name. Do this after creating the global so that if an AMD module wants // to call noConflict to hide this version of jQuery, it will work. // Note that for maximum portability, libraries that are not jQuery should // declare themselves as anonymous modules, and avoid setting a global if an // AMD loader is present. jQuery is a special case. For more information, see // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; }); };
好比咱們能夠這樣定義一個模塊:html
//文件所在的路徑地址爲:http://localhost:63342/module/script/dir2/1.js define(function() { return "!!!!"; });
也能夠這樣定義一個模塊:java
//這個文件的路徑爲http://localhost:63342/module/main.js ,並且有一個依賴, 加載器會自動去加載這個依賴, 當依賴加載完畢之後, 會把這個依賴(就是script/dir2/1.js)執行的返回值做爲這個函數的參數傳進去; require(["script/dir2/1.js"], function(module1) { console.log(module1); });
//實際上會打印出 "!!!!"
通常來講,一個模塊只能寫一個define函數, define函數的傳參主要有兩種方式:node
1:正常上能夠是一個函數;react
2:能夠是一個數組類型依賴的列表, 和一個函數;jquery
若是一個模塊寫了多個define會致使模塊失靈, 先定義的模塊被後定義的模塊給覆蓋了 ( 固然了, 通常咱們不那樣玩);git
一個模塊內能夠寫多個require, 咱們能夠直接理解require爲匿名的define模塊, 一個define模塊內能夠有多個require, 並且require過的模塊會被緩存起來, 這個緩存的變量通常是在閉包內, 並且名字多數叫modules什麼的.....;github
咱們經過加載器開發實現的模塊化開發要遵照一種規範, 規範了一個模塊爲一個JS,那麼咱們就能夠新建幾個目錄爲conroller,view, model, 也是爲了後期更好的維護和解耦:
使用的方式:
//這個模塊依賴的四個模塊,加載器會分別去加載這四個模塊;
define(["依賴0","依賴1","依賴2","依賴3"], function(依賴0,依賴1,依賴2,依賴3){
});
//返回一個空對象
define(function(){
return {};
});
//直接把require看成是define來用就行了;
require(["依賴0","依賴1","依賴2","依賴3"], function(依賴0,依賴1,依賴2,依賴3) {
//執行依賴0;
依賴0(依賴1,依賴2,依賴3);
});
//這個加載器define函數和require函數的區別是,define咱們能夠傳個name做爲第一參數, 這個參數就是模塊的名字😴😠, 好吧, 無論這些了.....;
如下爲加載器的結構,由於代碼量已經不多了, 因此每一函數都是必須的, 爲了避免影響全局, 把代碼放在匿名自執行函數內部:
(function() { 定義一個局部的difine; var define; //我偷偷加了個全局變量,好調試啊; window.modules = { }; //經過一個名字獲取絕對路徑好比傳"xx.js"會變成"http://www.mm.com/"+ baseUrl + "xx.html"; var getUrl = function(src) {}; //動態加載js的模塊; var loadScript = function(src) {}; //獲取根路徑的方法, 通常來講咱們能夠經過config.baseUrl配置這個路徑; var getBasePath = function() {}; //獲取當前正在加載的script標籤DOM節點; var getCurrentNode = function() {}; //獲取當前script標籤的絕對src地址; var getCurrentPath = function() {}; //加載define或者require中的依賴, 封裝了loadScript方法; var loadDpt = function(module) {}; //這個是主要模塊, 完成了加載依賴, 檢測依賴等比較重要的邏輯 var checkDps = function() {}; 定義了define這個方法 define = function(deps, fn, name) {}; window.define = define; //require是封裝了define的方法, 就是多傳了一個參數而已; window.require = function() { //若是是require的話那麼模塊的名字就是一個不重複的名字,避免和define重名; window.define.apply([], Array.prototype.slice.call(arguments).concat( "module|"+setTimeout(function() {},0) )); }; });
加載器源碼實現(兼容,chrome, FF, IE6 ==>> IE11), IE11沒有了readyState屬性, 也沒有currentScript屬性,坑爹啊, 沒法獲取當前正在執行的JS路徑, 因此要用hack;
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script> (function() { var define; window.modules = { }; var getUrl = function(src) { var scriptSrc = ""; //判斷URL是不是 // ./或者 // /或者 // 直接是以字符串開頭 // 或者是以http://開頭; if( src.indexOf("/") === 0 || src.indexOf("./") === 0 ) { scriptSrc = require.config.base + src.replace(/^\//,"").replace(/^\.\//,""); }else if( src.indexOf("http:") === 0 ) { scriptSrc = src; }else if( src.match(/^[a-zA-Z1-9]/) ){ scriptSrc = require.config.base + src; }else if(true) { alert("src錯誤!"); }; if (scriptSrc.lastIndexOf(".js") === -1) { scriptSrc += ".js"; }; return scriptSrc; }; var loadScript = function(src) { var scriptSrc = getUrl(src); var sc = document.createElement("script"); var head = document.getElementsByTagName("head")[0]; sc.src = scriptSrc; sc.onload = function() { console.log("script tag is load, the url is : " + src); }; head.appendChild( sc ); }; var getBasePath = function() { var src = getCurrentPath(); var index = src.lastIndexOf("/"); return src.substring(0,index+1); }; var getCurrentNode = function() { if(document.currentScript) return document.currentScript; var arrScript = document.getElementsByTagName("script"); var len = arrScript.length; for(var i= 0; i<len; i++) { if(arrScript[i].readyState === "interactive") { return arrScript[i]; }; }; //IE11的特殊處理; var path = getCurrentPath(); for(var i= 0; i<len; i++) { if(path.indexOf(arrScript[i].src)!==-1) { return arrScript[i]; }; }; throw new Error("getCurrentNode error"); }; var getCurrentPath = function() { var repStr = function(str) { return (str || "").replace(/[\&\?]{1}[\w\W]+/g,"") || ""; }; if(document.currentScript) return repStr(document.currentScript.src); //IE11沒有了readyState屬性, 也沒有currentScript屬性; // 參考 https://github.com/samyk/jiagra/blob/master/jiagra.js var stack try { a.b.c() //強制報錯,以便捕獲e.stack } catch (e) { //safari的錯誤對象只有line,sourceId,sourceURL stack = e.stack if (!stack && window.opera) { //opera 9沒有e.stack,但有e.Backtrace,但不能直接取得,須要對e對象轉字符串進行抽取 stack = (String(e).match(/of linked script \S+/g) || []).join(" ") } } if (stack) { /**e.stack最後一行在全部支持的瀏覽器大體以下: *chrome23: * at http://113.93.50.63/data.js:4:1 *firefox17: *@http://113.93.50.63/query.js:4 *opera12:http://www.oldapps.com/opera.php?system=Windows_XP *@http://113.93.50.63/data.js:4 *IE10: * at Global code (http://113.93.50.63/data.js:4:1) * //firefox4+ 能夠用document.currentScript */ stack = stack.split(/[@ ]/g).pop() //取得最後一行,最後一個空格或@以後的部分 stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉換行符 return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行號與或許存在的出錯字符起始位置 }; //實在不行了就走這裏; var node = getCurrentNode(); //IE>=8的直接經過src能夠獲取,IE67要經過getAttriubte獲取src; return repStr(document.querySelector ? node.src : node.getAttribute("src", 4)) || ""; throw new Error("getCurrentPath error!"); }; var loadDpt = function(module) { var dp = ""; for(var p =0; p<module.dps.length; p++) { //獲取絕對的地址; var dp = getUrl(module.dps[p]); //若是依賴沒有加載就直接加載; if( !modules[dp] ) { loadScript(dp); }; }; }; //主要的模塊, 檢測全部未加載的模塊中未完成了的依賴是否加載完畢,若是加載完畢就加載模塊, 若是加載過的話,並且全部依賴的模塊加載完畢就執行該模塊 //並且此模塊的exports爲該模塊的執行結果; var checkDps = function() { for(var key in modules ) { //初始化該模塊須要的參數; var params = []; var module = modules[key]; //加載完畢就什麼都不作; if( module.state === "complete" ) { continue; }; if( module.state === "initial" ) { //若是依賴沒有加載就加載依賴而且modules沒有該module就加載這個模塊; loadDpt(module); module.state = "loading"; }; if( module.state === "loading") { //若是這個依賴加載完畢 for(var p =0; p<module.dps.length; p++) { //獲取絕對的地址; var dp = getUrl(module.dps[p]); //若是依賴加載完成了, 並且狀態爲complete;; if( modules[dp] && modules[dp].state === "complete") { params.push( modules[dp].exports ); }; }; //若是依賴所有加載完畢,就執行; if( module.dps.length === params.length ) { if(typeof module.exports === "function"){ module.exports = module.exports.apply(modules,params); module.state = "complete"; //每一次有一個模塊加載完畢就從新檢測modules,看看是否有未加載完畢的模塊須要加載; checkDps(); }; }; }; }; }; //[],fn; fn define = function(deps, fn, name) { if(typeof deps === "function") { fn = deps; deps = [];//咱們要把數組清空; }; if( typeof deps !== "object" && typeof fn !== "function") { alert("參數錯誤") }; var src = getCurrentPath(); //沒有依賴, 沒有加載該模塊就新建一個該模塊; if( deps.length===0 ) { modules[ src ] = { name : name || src, src : src, dps : [], exports : (typeof fn === "function")&&fn(), state : "complete" }; return checkDps(); }else{ modules[ src ] = { name : name || src, src : src, dps : deps, exports : fn, state : "initial" }; return checkDps(); } }; window.define = define; window.require = function() { //若是是require的話那麼模塊的名字就是一個不重複的名字,避免和define重名; window.define.apply([], Array.prototype.slice.call(arguments).concat( "module|"+setTimeout(function() {},0) )); }; require.config = { base : getBasePath() }; require.loadScript = loadScript; var loadDefaultJS = getCurrentNode().getAttribute("data-main"); loadDefaultJS && loadScript(loadDefaultJS); })(); </script> </head> <body> </body> </html>
從葉大大那邊偷的一個加載器, 這個加載器有點像jQuery中延遲對象($.Deferred)有關的方法when($.when)的實現;
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script> (function () { //存儲已經加載好的模塊 var moduleCache = {}; var define = function (deps, callback) { var params = []; var depCount = 0; var i, len, isEmpty = false, modName; //獲取當前正在執行的js代碼段,這個在onLoad事件以前執行 modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN'; //簡單實現,這裏未作參數檢查,只考慮數組的狀況 if (deps.length) { for (i = 0, len = deps.length; i < len; i++) { (function (i) { //依賴加一 depCount++; //這塊回調很關鍵 loadMod(deps[i], function (param) { params[i] = param; depCount--; if (depCount == 0) { saveModule(modName, params, callback); } }); })(i); } } else { isEmpty = true; } if (isEmpty) { setTimeout(function () { saveModule(modName, null, callback); }, 0); } }; //考慮最簡單邏輯便可 var _getPathUrl = function (modName) { var url = modName; //不嚴謹 if (url.indexOf('.js') == -1) url = url + '.js'; return url; }; //模塊加載 var loadMod = function (modName, callback) { var url = _getPathUrl(modName), fs, mod; //若是該模塊已經被加載 if (moduleCache[modName]) { mod = moduleCache[modName]; if (mod.status == 'loaded') { setTimeout(callback(this.params), 0); } else { //若是未到加載狀態直接往onLoad插入值,在依賴項加載好後會解除依賴 mod.onload.push(callback); } } else { /* 這裏重點說一下Module對象 status表明模塊狀態 onLoad事實上對應requireJS的事件回調,該模塊被引用多少次變化執行多少次回調,通知被依賴項解除依賴 */ mod = moduleCache[modName] = { modName: modName, status: 'loading', export: null, onload: [callback] }; _script = document.createElement('script'); _script.id = modName; _script.type = 'text/javascript'; _script.charset = 'utf-8'; _script.async = true; _script.src = url; //這段代碼在這個場景中意義不大,註釋了 // _script.onload = function (e) {}; fs = document.getElementsByTagName('script')[0]; fs.parentNode.insertBefore(_script, fs); } }; var saveModule = function (modName, params, callback) { var mod, fn; if (moduleCache.hasOwnProperty(modName)) { mod = moduleCache[modName]; mod.status = 'loaded'; //輸出項 mod.export = callback ? callback(params) : null; //解除父類依賴,這裏事實上使用事件監聽較好 while (fn = mod.onload.shift()) { fn(mod.export); } } else { callback && callback.apply(window, params); } }; window.require = define; window.define = define; })(); </script> </head> <body> </body> </html>
寫一個MVC的小例子,代碼簡單, 高手無視, 目錄結構以下:
咱們把全部的事件放到了controller/mainController.js裏面,
define(["model/data","view/view0"],function(data, view) { var init = function() { var body = document.getElementsByTagName("body")[0]; var aBtn = document.getElementsByTagName("button"); for(var i=0; i< aBtn.length; i++) { aBtn[i].onclick = (function(i) { return function() { body.appendChild( view.getView(data[i]) ); }; })(i); }; }; return { init : init }; });
把全部的數據放到了model/data.js裏面;
define(function() { return [ {name : "qihao"}, {name : "nono"}, {name : "hehe"}, {name : "gege"} ]; })
視圖的JS放到了view的目錄下,view0.js主要負責生成HTML字符串或者DOM節點;
define(function() { return { getView : function(data) { var frag = document.createDocumentFragment(); frag.appendChild( document.createTextNode( data.name + " ") ); return frag; } } });
入口是app.js,他和load.html是同級目錄:
require(["controller/mainController"],function( controller ) { controller.init(); });
load.html這個是主界面:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title></head> <body> <button>0</button> <button>1</button> <button>2</button> <button>3</button> <script src="require.js" data-main="app.js"></script> </body> </html>
例子的源碼下載地址: 下載
下週開始複習jQuery1.8版本源碼, 而後下下週準備寫些書籍的讀後感, 包括js設計模式,js忍者,js衆妙之門,reactjs學習。
參考:
【模塊化編程】理解requireJS-實現一個簡單的模塊加載器
做者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329