在前文中我們不止一次強調過模塊化編程的重要性,以及其可以解決的問題:
① 解決單文件變量命名沖突問題
② 解決前端多人協作問題
③ 解決文件依賴問題
④ 按需加載(這個說法其實很假了)
⑤ ......
為了深入了解加載器,中間閱讀過一點requireJS的源碼,但對于很多同學來說,對加載器的實現依舊不太清楚
事實上不通過代碼實現,單單憑閱讀想理解一個庫或者框架只能達到一知半解的地步,所以今天便來實現一個簡單的加載器
分與合
事實上,一個程序運行需要完整的模塊,以下代碼為例:
//求得績效系數
var performanceCoefficient = function () {
return 0.2;
};
//住房公積金計算方式
var companyReserve = function (salary) {
return salary * 0.2;
};
//個人所得稅
var incomeTax = function (salary) {
return salary * 0.2;
};
//基本工資
var salary = 1000;
//最終工資
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary);
我一份完整的工資來說,公司會有績效獎勵,但是其算法可能非常復雜,其中可能涉及到出勤率,完成度什么的,這里暫時不管
而有增便有減,所以我們會交住房公積金,也會扣除個人所得稅,最終才是我的工資
對于完整的程序來說上面的流程缺一不可,但是各個函數中卻有可能異常的復雜,跟錢有關系的東西都復雜,所以單單是公司績效便有可能超過1000行代碼
于是我們這邊便會開始分:

<script src="companyReserve.js" type="text/javascript"></script> <script src="incomeTax.js" type="text/javascript"></script> <script src="performanceCoefficient.js" type="text/javascript"></script> <script type="text/javascript"> //基本工資 var salary = 1000; //最終工資 var mySalary = salary + salary * performanceCoefficient(); mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); console.log(mySalary); </script>
上面的代碼表明上是“分”開了,事實上也造成了“合”的問題,我要如何才能很好的把它們重新合到一起呢,畢竟其中的文件可能還涉及到依賴,這里便進入我們的require與define
require與define
事實上,上面的方案仍然是以文件劃分,而不是以模塊劃分的,若是文件名發生變化,頁面會涉及到改變,其實這里應該有一個路徑的映射處理這個問題
var pathCfg = {
'companyReserve': 'companyReserve',
'incomeTax': 'incomeTax',
'performanceCoefficient': 'performanceCoefficient'
};
于是我們一個模塊便對應了一個路徑js文件,剩下的便是將之對應模塊的加載了,因為前端模塊涉及到請求。所以這種寫法:
companyReserve = requile('companyReserve');
對于前端來說是不適用的,就算你在哪里看到這樣做了,也一定是其中做了一些“手腳”,這里我們便需要依據AMD規范了:
require.config({
'companyReserve': 'companyReserve',
'incomeTax': 'incomeTax',
'performanceCoefficient': 'performanceCoefficient'
});
require(['companyReserve', 'incomeTax', 'performanceCoefficient'], function (companyReserve, incomeTax, performanceCoefficient) {
//基本工資
var salary = 1000;
//最終工資
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary);
});
這里便是一個標準的requireJS的寫法了,首先定義模塊以及其路徑映射,其中定義依賴項
require(depArr, callback)
一個簡單完整的模塊加載器基本就是這個樣子了,首先是一個依賴的數組,其次是一個回調,回調要求依賴項全部加載才能運行,并且回調的參數便是依賴項執行的結果,所以一般要求define模塊具有一個返回值
方案有了,那么如何實現呢?
實現方案
說到模塊加載,人們第一反應都是ajax,因為無論何時,能拿到模塊文件的內容,都是模塊化的基本,但是采用ajax的方式是不行的,因為ajax有跨域的問題
而模塊化方案又不可避免的要處理跨域的問題,所以使用動態創建script標簽加載js文件便成為了首選,但是,不使用ajax的方案,對于實現難度來說還是有要求
PS:我們實際工作中還會有加載html模板文件的場景,這個稍候再說
通常我們是這樣做的,require作為程序入口,調度javascript資源,而加載到各個define模塊后,各個模塊便悄無聲息的創建script標簽加載
加載結束后便往require模塊隊列報告自己加載結束了,當require中多有依賴模塊皆加載結束時,便執行其回調
原理大致如此,剩下的只是具體實現,而后在論證這個理論是否靠譜即可
核心模塊
根據以上理論,我們由整體來說,首先以入口三個基本函數來說
var require = function () {
};
require.config = function () {
};
require.define = function () {
};
這三個模塊比不可少:
① config用以配置模塊與路徑的映射,或者還有其他用處
② require為程序入口
③ define設計各個模塊,響應require的調度
然后我們這里會有一個創建script標簽的方法,并且會監聽其onLoad事件
④ loadScript
其次我們加載script標簽后,應該有一個全局的模塊對象,用于存儲已經加載好的模塊,于是這里提出了兩個需求:
⑤ require.moduleObj 模塊存儲對象
⑥ Module,模塊的構造函數
有了以上核心模塊,我們形成了如下代碼:
(function () {
var Module = function () {
this.status = 'loading'; //只具有loading與loaded兩個狀態
this.depCount = 0; //模塊依賴項
this.value = null; //define函數回調執行的返回
};
var loadScript = function (url, callback) {
};
var config = function () {
};
var require = function (deps, callback) {
};
require.config = function (cfg) {
};
var define = function (deps, callback) {
};
})();
于是接下來便是具體實現,然后在實現過程中補足不具備的接口與細節,往往在最后的實現與最初的設計沒有半毛錢關系......
代碼實現
這塊最初實現時,本來想直接參考requireJS的實現,但是我們老大笑瞇瞇的拿出了一個他寫的加載器,我一看不得不承認有點妖
于是這里便借鑒了其實現,做了簡單改造:
(function () {
//存儲已經加載好的模塊
var moduleCache = {};
var require = 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 = require;
window.define = require;
})();
首先這段代碼有一些問題:
沒有處理參數問題,字符串之類皆未處理
未處理循環依賴問題
未處理CMD寫法
未處理html模板加載相關
未處理參數配置,baseUrl什么都沒有搞
基于此想實現打包文件也不可能
......
但就是這100行代碼,便是加載器的核心,代碼很短,對各位理解加載器很有幫助,里面有兩點需要注意:
① requireJS是使用事件監聽處理本身依賴,這里直接將之放到了onLoad數組中了
② 這里有一個很有意思的東西
document.currentScript
這個可以獲取當前執行的代碼段
requireJS是在onLoad中處理各個模塊的,這里就用了一個不一樣的實現,每個js文件加載后,都會執行require(define)方法
執行后便取到當前正在執行的文件,并且取到文件名加載之,正因為如此,連script的onLoad事件都省了......
demo實現
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
</body>
<script src="require.js" type="text/javascript"></script>
<script type="text/javascript">
require(['util', 'math', 'num'], function (util, math, num) {
num = math.getRadom() + '_' + num;
num = util.formatNum(num);
console.log(num);
});
</script>
</html>
//util
define([], function () {
return {
formatNum: function (n) {
if (n < 10) return '0' + n;
return n;
}
};
});
//math
define(['num'], function (num) {
return {
getRadom: function () {
return parseInt(Math.random() * num);
}
};
});
//math
define(['num'], function (num) {
return {
getRadom: function () {
return parseInt(Math.random() * num);
}
};
});
今天我們實現了一個簡單的模塊加載器,通過他希望可以幫助各位了解requireJS或者seaJS,最后順利進入模塊化編程的行列
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。