js模塊化加載器實現

背景

自es6之前,JavaScript是天生模塊化缺失的,即缺乏相似後端語言的class,
做用域也只以函數做爲區分。這與早期js的語言定位有關,
做爲一個只須要在網頁中嵌入幾十上百行代碼來實現一些基本的交互效果的腳本語言,
確實用不着嚴格的組織代碼規範。可是隨着時代的發展,js承擔的任務愈來愈重,
從原先的script引入幾十行代碼便可的狀態變成如今多人協做文件衆多的地步,
管理和組織代碼的難度愈來愈大,模塊化的需求也愈來愈迫切。
在此背景下,衆多的模塊化加載器便應運而生。javascript

模塊化規範和實現

前文提到在es6模塊化出現以前,爲了解決模塊化的需求,出現了衆多的模塊化機制例如cmd,amd等。遵循不一樣規範有sea.js, require.js等實現。html

  • AMD:
    Asynchronous Module Definition 異步模塊定義。瀏覽器端模塊化開發的規範,
    模塊將被異步加載,模塊加載不影響後面語句的運行。全部依賴某些模塊的語句均放置在回調函數中。
    AMD 是 RequireJS 在推廣過程當中對模塊定義的規範化的產出。require.js詳情參考前端

    //依賴前置,jquery模塊先聲明 define(['jquery'], function ($) { /***/ })
  • CommonJS:
    CommonJS是服務器端模塊的規範,Node.js採用了這個規範。Node.JS首先採用了js模塊化的概念。CommonJS規範參考java

    //同步加載 var $ = require('jquery'); /****/ module.exports = myFunc;
  • CMD:
    CMD(Common Module Definition) 通用模塊定義。該規範是SeaJS推廣過程當中發展出來的。
    與AMD區別:
    AMD是依賴關係前置,CMD是按需加載。更多參考jquery

    define(function (require, exports, module) { // 就近依賴 var $ = require('jquery'); /****/ })
  • UMD:
    Universal Module Definition 通用模塊規範。
    基於統一規範的目的出現,看起來沒那麼簡約,可是支持amd和commonjs以及全局模塊模式。webpack

    //作的工做其實就是這麼粗暴,判斷當前用的什麼就以當前規範來定義 (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; }));

    綜上所訴,各個不一樣的規範都有各自的優勢,具體使用須要是各自項目狀況而定。沒有好很差只有適用與否。git

模塊加載器實現原理淺析

以上各類模塊加載器關於模塊化的實現都有各自的特色,而且是比較成型完善的體系。本文也無心去從新實現一個大而全的模塊加載器。
本着學習的態度,簡單對其中的部分原理進行部分探究。
全部的模塊加載器的實現都要有如下步驟:es6

  • 動態建立腳本節點
    模塊化歸根到底仍是要在瀏覽器中加載腳本。
  • 解析依賴模塊路徑
    模塊化的初衷就是解決各個文件的依賴問題,因此各個模塊之間的依賴分析必不可少。
    該部分須要控制腳本加載隊列,對遞歸依賴進行分析,按順序進行加載
  • 模塊緩存
    將每一個模塊的內容根據特定規則保存起來,爲調用作準備。
    對於沒有聲明name的模塊要匹配的話就須要根據currentScript獲取文件名。而後進行緩存.
基礎方法聲明

既然是有個加載器,固然是會指定一些規則來讓使用者遵循。不然也實現不了相應的方法。不一樣的框架的實現方式也是不一樣的,不過速途同歸。
做爲一個模塊加載器(簡單歸簡單),基本的接口以下:github

  • 定義模塊(define):
    define(deps,func,name)參數以下
    1 deps: 依賴模塊,支持數組,或者省略
    2 func: 自身func,即接受依賴模塊做爲參數的回調
    3 name: 模塊對應的name,若是不存在則根據當前路徑來命名。
    功能無非是根據不一樣的狀態將該模塊處理後的屬性,例如name等。存入模塊隊列(modules變量)中。web

    modules[ src ] = { name : name || src, src : src, dps : [], exports : (typeof fn === "function")&&fn(), state : "complete" };
  • 依賴模塊接口(require):
    require(deps,func)參數同define。這裏就不實現支持commonjs的方式了,即依賴必須前置聲明。

    //不支持 var a = require('a'); //支持 require( ['a'], function(a) { var a1 = a; }); 

    這樣而來require模塊的徹底能夠經過define來實現了。

動態建立腳本

歸根到底前端模塊化的實質仍是經過script標籤來引入相應文件(基於服務端的模塊加載器非此類實現,例如webpack等)。
因此必不可少的須要進行建立和引入。主要用到的createElement方法來建立以及appendChild插入dom中。

/** * @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=路徑的原則

 
//此處的a對應的路徑即爲base+a.js. require('a', function(){ //abcc } ) 

固然實際狀況中的匹配是很複雜的,簡單實現就不考慮那麼多。
對於匿名模塊的存在,是能夠經過document.currentScript獲取當前路徑手動給其增長標識的。
腳本路徑無外乎一下幾種狀況:

  • 相對路徑:
    此種路徑只須要獲取當前跟路徑拼接處理便可。(爲了簡化處理,此處入口文件在項目根目錄下)
  • http網絡路徑:
    此路徑直接不變便可.
  • 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接口存在緩存中。依賴模塊解析完成以後進行執行。
至此,關於模塊化的探究就基本結束了。說來原理你們都知道。無非就是解析一下模塊路徑,而後動態建立腳本,控制下加載就能夠了。實現如下仍是有不少收穫的

參考文章
如水穿石,厚積纔可薄發
相關文章
相關標籤/搜索