自es6之前,JavaScript是天生模塊化缺失的,即缺乏相似後端語言的class,
做用域也只以函數做爲區分。這與早期js的語言定位有關,
做爲一個只須要在網頁中嵌入幾十上百行代碼來實現一些基本的交互效果的腳本語言,
確實用不着嚴格的組織代碼規範。可是隨着時代的發展,js承擔的任務愈來愈重,
從原先的script引入幾十行代碼便可的狀態變成如今多人協做文件衆多的地步,
管理和組織代碼的難度愈來愈大,模塊化的需求也愈來愈迫切。
在此背景下,衆多的模塊化加載器便應運而生。
html
前文提到在es6模塊化出現以前,爲了解決模塊化的需求,出現了衆多的模塊化機制例如cmd,amd等。遵循不一樣規範有sea.js, require.js等實現。前端
//依賴前置,jquery模塊先聲明 define(['jquery'], function ($) { /***/ })
//同步加載 var $ = require('jquery'); /****/ module.exports = myFunc;
define(function (require, exports, module) { // 就近依賴 var $ = require('jquery'); /****/ })
//作的工做其實就是這麼粗暴,判斷當前用的什麼就以當前規範來定義 (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS module.exports = factory(require('jquery')); } else { // 全局變量 root.returnExports = factory(root.jQuery); } }(this, function ($) { // methods function myFunc(){}; // exposed public method return myFunc; }));
綜上所訴,各個不一樣的規範都有各自的優勢,具體使用須要是各自項目狀況而定。沒有好很差只有適用與否。jquery
以上各類模塊加載器關於模塊化的實現都有各自的特色,而且是比較成型完善的體系。本文也無心去從新實現一個大而全的模塊加載器。
本着學習的態度,簡單對其中的部分原理進行部分探究。
全部的模塊加載器的實現都要有如下步驟:webpack
既然是有個加載器,固然是會指定一些規則來讓使用者遵循。不然也實現不了相應的方法。不一樣的框架的實現方式也是不一樣的,不過速途同歸。
做爲一個模塊加載器(簡單歸簡單),基本的接口以下:git
modules[ src ] = { name : name || src, src : src, dps : [], exports : (typeof fn === "function")&&fn(), state : "complete" };
//不支持 var a = require('a'); //支持 require( ['a'], function(a) { var a1 = a; });
這樣而來require模塊的徹底能夠經過define來實現了。es6
歸根到底前端模塊化的實質仍是經過script標籤來引入相應文件(基於服務端的模塊加載器非此類實現,例如webpack等)。
因此必不可少的須要進行建立和引入。主要用到的createElement方法來建立以及appendChild插入dom中。github
/** * @param src string * 此處的src爲路徑,即define裏的字段 * */ 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 ); };
由前面建立腳本可知,須要解析腳本路徑來分別區分當前不一樣路徑。
路徑和模塊的對應關係遵循id=路徑的原則web
//此處的a對應的路徑即爲base+a.js. require('a', function(){ //abcc } )
固然實際狀況中的匹配是很複雜的,簡單實現就不考慮那麼多。
對於匿名模塊的存在,是能夠經過document.currentScript獲取當前路徑手動給其增長標識的。
腳本路徑無外乎一下幾種狀況:npm
var getUrl = function(src) { var scriptSrc = ""; //判斷URL是不是 //相對路徑'/'或者'./'開頭的,獲取當前根路徑替換掉其餘字符便可。 if( src.indexOf("/") === 0 || src.indexOf("./") === 0 ) { scriptSrc = require.config.base + src.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; };
此處還須要獲取當前的根路徑,模塊化加載一定會有script來加載加載器js。因此能夠據此來判斷當前路徑。
關於兼容性的處理,這裏就不在講述。編程
//去除&?等字符 var repStr = function(str) { return (str || "").replace(/[\&\?]{1}.+/g,"") || ""; }; if(document.currentScript) return repStr(document.currentScript.src);
腳本加載以後,須要根據模塊不一樣的狀態進行處理。模塊主要分如下狀態:
1 init:
初始化,即剛進行模塊相關屬性的處理,未進行模塊解析。即將進行模塊加載處理
2 loading:
模塊解析中,即將完成
3 complete:
模塊解析完成,將參數對象,exports接口存在緩存中。依賴模塊解析完成以後進行執行。
至此,關於模塊化的探究就基本結束了。說來原理你們都知道。無非就是解析一下模塊路徑,而後動態建立腳本,控制下加載就能夠了。實現如下仍是有不少收穫的