前幾天看了下webpack打包出來的js,豁然開朗以爲實現一個模塊化工具穩穩的,真開始寫的時候才發現too young。javascript
// 定義模塊apple:
define('apple',['orange'],function(orange){
return orange
})
// 定義模塊orange:
define('orange',[],function(){
return {
name:'orange',
color:'white',
size:'small',
}
})
// 使用定義好的模塊:
var a = require(['apple','jquery'],function(apple,$){
console.log(apple)
console.log($('<div>123</div>'))
})
===>輸出
{
name:'orange',
color:'white',
size:'small',
},
n.fn.init [div]
複製代碼
let paths = {
apple:'./apple.js',
orange:'./orange.js',
jquery:'https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js',
}
複製代碼
// 存放全部註冊require的模塊,收集他們的依賴,以及回調
let reqs = {}
// 保存加載好的模塊
let modules = {}
複製代碼
function require(deps,callback) {
...
}
複製代碼
將本次執行require看作一個任務,並在reqs對象中註冊下,模塊加載完成後將執行本reqs中的全部任務。css
// 任務名稱從0開始,最先註冊的任務爲reqs[0],隨後++
let id = 0
// 建立執行模塊
reqs[id] = {
deps,
id,
callback,
}
id++
複製代碼
第一個require執行完後reqs對象將變爲html
{
0:{
callback:function(){..},
id:0,
deps:['apple','jquery']
}
}
複製代碼
而後循環deps數組,建立script標籤依次加載依賴的模塊:java
for(let item of deps) {
// 若是modules變量中尚未保存本模塊,首先在模塊中初始化本模塊:1.建立模塊name,2.建立watcher屬性用於記錄是哪一個註冊reqs任務引用了我這個模塊。而後建立script標籤異步加載本模塊。加載完成以後執行loadComplete方法。若是本模塊在modoles裏已存在,說明本模塊已加載過,那麼直接把reqs的任務id push到watchers裏。
if(!modules[name]) {
// 初始化模塊,並記住哪一個reqs任務引用了本模塊。
modules[name] = {
// 存放依賴此模塊的模塊名
watchers:[id],
name:name,
}
var node = document.createElement('script');
node.type = 'text/javascript';
node.charset = 'utf-8';
node.setAttribute('data-requiremodule', name);
node.async = true;
document.body.appendChild(node)
node.addEventListener('load', loadComplete, false);
node.src = paths[name]
}else{
modules[name].watchers.push(id)
}
}
複製代碼
但須要注意的是,node.load方法會在下載好的js執行完以後纔會執行。意思就是說若是加載的apple.js裏有console.log("apple模塊加載好了")
,而loadComplete裏有console.log("執行script的onload方法")
,那麼執行順序是1.apple模塊加載好了;2.執行script的onload方法。由於apple模塊裏執行了define方法,因此先看define的定義。node
本方法採用amd規範,接收三個變量:1.本模塊名稱,2.本模塊的依賴模塊,3.本模塊的執行結果。jquery
function define(name, deps, callback){
...
}
複製代碼
在modules變量中註冊本模塊,若是本模塊有依賴,執行require方法先加載依賴,等依賴加載完只有執行callback獲取模塊的結果;若是本模塊沒有依賴,執行本模塊的callback方法獲得本模塊的結果。webpack
modules[name].callback = callback
if(deps.length === 0) {
modules[name].result = callback()
}else{
// 若是有依賴,要先執行依賴
require(deps,function(){
modules[name].result = callback(...arguments)
})
}
複製代碼
以orange模塊爲例,define方法執行完以後modules變量爲git
{
orange:{
result:{
name:'orange',
color:'white',
size:'small',
}
}
}
複製代碼
定義模塊是amd規範:define.amd = true
github
下面真正到了loadComplete方法。也就是script的onload回調。web
本方法主要的任務是:執行之前註冊的那些依賴本模塊的reqs任務。若是reqs任務的finish=true,說明模塊已經執行過了,跳過。若是reqs任務沒有執行過,那麼拿到reqs任務deps屬性,也就是依賴哪些模塊,若是全部的模塊都有result(結果),執行本任務的callback,並將finish置爲true.
function loadComplete(evt){
var node = evt.currentTarget || evt.srcElement;
node.removeEventListener('load', loadComplete);
let name = node.getAttribute('data-requiremodule')
modules[name].watchers.map((item)=>{
if(reqs[item].finish) return
let completed = true
let args = []
reqs[item].deps.map(item2=>{
if(!modules[item2].result) {
completed = false
}else{
args.push(modules[item2].result)
}
})
if(completed) {
reqs[item].callback(...args)
reqs[item].finish = true
reqs[item].completed = true
}
})
}
複製代碼
參考:
仿照require1k實現相似requirejs的模塊加載庫。代碼
果真複雜的多,根據註釋走了一遍流程,基本上流程走的通。
參考: