目前,一個典型的前端項目技術框架的選型主要包括如下三個方面:javascript
- JS模塊化框架。(Require/Sea/ES6 Module/NEJ)
- 前端模板框架。(React/Vue/Regular)
- 狀態管理框架。(Flux/Redux)
系列文章將從上面三個方面來介紹相關原理,而且嘗試本身造一個簡單的輪子。
本篇介紹的是JS模塊化。
JS模塊化是隨着前端技術的發展,前端代碼爆炸式增加後,工程化所採起的必然措施。目前模塊化的思想分爲CommonJS、AMD和CMD。有關三者的區別,你們基本都多少有所瞭解,並且資料不少,這裏就再也不贅述。html
模塊化的核心思想:前端
根據上面的核心思想,能夠看出要設計一個模塊化工具框架的關鍵問題有兩個:一個是如何將一個模塊執行並能夠將結果輸出注入到另外一個模塊中;另外一個是,在大型項目中模塊之間的依賴關係很複雜,如何使模塊按正確的依賴順序進行注入,這就是依賴管理。java
下面以具體的例子來實現一個簡單的基於瀏覽器端的AMD模塊化框架(相似NEJ),對外暴露一個define函數,在回調函數中注入依賴,並返回模塊輸出。要實現的以下面代碼所示。git
define([
'/lib/util.js', //絕對路徑
'./modal/modal.js', //相對路徑
'./modal/modal.html',//文本文件
], function(Util, Modal, tpl) {
/* * 模塊邏輯 */
return Module;
})複製代碼
先不考慮一個模塊的依賴如何處理。假設一個模塊的依賴已經注入,那麼如何加載和執行該模塊,並輸出呢?
在瀏覽器端,咱們能夠藉助瀏覽器的script標籤來實現JS模塊文件的引入和執行,對於文本模塊文件則能夠直接利用ajax請求實現。
具體步驟以下:github
var a = document.createElement('a');
a.id = '_defineAbsoluteUrl_';
a.style.display = 'none';
document.body.appendChild(a);
function getModuleAbsoluteUrl(path) {
a.href = path;
return a.href;
}
function parseAbsoluteUrl(url, parentDir) {
var relativePrefix = '.',
parentPrefix = '..',
result;
if (parentDir && url.indexOf(relativePrefix) === 0) {
// 以'./'開頭的相對路徑
return getModuleAbsoluteUrl(parentDir.replace(/[^\/]*$/, '') + url);
}
if (parentDir && url.indexOf(parentPrefix) === 0) {
// 以'../'開頭的相對路徑
return getModuleAbsoluteUrl(parentDir.replace(/[\/]*$/, '').replace(/[\/]$/, '').replace(/[^\/]*$/, '') + url);
}
return getModuleAbsoluteUrl(url);
}複製代碼
對於JS文件,利用script標籤實現。代碼以下:ajax
var head = document.getElementsByTagName('head')[0] || document.body;
function loadJsModule(url) {
var script = document.createElement('script');
script.charset = 'utf-8';
script.type = 'text/javascript';
script.onload = script.onreadystatechange = function() {
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
/* * 加載邏輯, callback爲define的回調函數, args爲全部依賴模塊的數組 * callback.apply(window, args); */
script.onload = script.onreadystatechange = null;
}
};
}複製代碼
對於文本文件,直接用ajax實現。代碼以下:數組
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
textContent = '';
xhr.onreadystatechange = function(){
var DONE = 4, OK = 200;
if(xhr.readyState === DONE){
if(xhr.status === OK){
textContent = xhr.responseText; // 返回的文本文件
} else{
console.log("Error: "+ xhr.status); // 加載失敗
}
}
}
xhr.open('GET', url, true);// url爲文本文件的絕對路徑
xhr.send(null);複製代碼
一個模塊的加載過程以下圖所示。
瀏覽器
依賴分析
在模塊加載後,咱們須要解析出每一個模塊的絕對路徑(path)、依賴模塊(deps)和回調函數(callback),而後也放在模塊信息中。模塊對象管理邏輯的數據模型以下所示。網絡
{
path: 'http://asdas/asda/a.js',
deps: [{}, {}, {}],
callback: function(){ },
status: 'pending'
}複製代碼
依賴循環
模塊極可能出現循環依賴的狀況。也就是a模塊和b模塊相互依賴。依賴分爲強依賴和弱依賴。強依賴是指,在模塊回調執行時就會使用到的依賴;反之,就是弱依賴。對於強依賴,會形成死鎖,這種狀況是沒法解決的。但弱依賴能夠經過現將一個空的模塊引用注入讓一個模塊先執行,等依賴模塊執行完後,再替換掉就能夠了。強依賴和弱依賴的例子以下:
//強依賴的例子
//A模塊
define(['b.js'], function(B) {
// 回調執行時須要直接用到依賴模塊
B.demo = 1;
// 其餘邏輯
});
//B模塊
define(['a.js'], function(A) {
// 回調執行時須要直接用到依賴模塊
A.demo = 1;
// 其餘邏輯
});複製代碼
// 弱依賴的例子
// A模塊
define(['b.js'], function(B) {
// 回調執行時不會直接執行依賴模塊
function test() {
B.demo = 1;
}
return {testFunc: test}
});
//B模塊
define(['a.js'], function(A) {
// 回調執行時不會直接執行依賴模塊
function test() {
A.demo = 1;
}
return {testFunc: test}
});複製代碼
對於define函數,須要遍歷全部的未處理js腳本(包括內聯和外聯),而後執行模塊的加載。這裏對於內聯和外聯腳本中的define,要作分別處理。主要緣由有兩點:
var handledScriptList = [];
window.define = function(deps, callback) {
var scripts = document.getElementsByTagName('script'),
defineReg = /s*define\s*\(\[.*\]\s*\,\s*function\s*\(.*\)\s*\{/,
script;
for (var i = scripts.length - 1; i >= 0; i--) {
script = list[i];
if (handledScriptList.indexOf(script.src) < 0) {
handledScriptList.push(script.src);
if (script.innerHTML.search(defineReg) >= 0) {
// 內斂腳本直接進行模塊依賴檢查。
} else {
// 外聯腳本的首先要監聽腳本加載
}
}
}
};複製代碼
上面就是對實現一個模塊化工具所涉及核心問題的描述。完整的代碼點我。