RequireJS最簡實現

liu1084 7年前發布 | 8K 次閱讀 RequireJS JavaScript開發

網上有不少解析RequireJS源碼的文章,我覺得意義不大。閱讀源代碼的目的不是為了熟悉代碼,而是為了學習核心實現原理。相對RequireJS的源碼, kitty.js 的實現更簡單,更容易理解。本文正是抄了kitty.js的實現,是一個更精簡的RequireJS,用于理解RequireJS的實現原理。

github dummy-requirejs 。這個實現僅支持核心feature:

require(deps, callback) // deps 是依賴數組
define(id, deps, factory) // factory是一個函數

例子參考git中rect.js/main.js。

從實現來看,require/define是基本一致的,require的callback等同于define的factory:都會要求deps被加載且被執行,獲得deps的exports作為整個module傳入callback/factory。不同的是,factory的返回值會被作為define出來的模塊的export,可被視為模塊本身;而callback返回值被忽略。

從用法來看,define僅是定義了模塊,這個模塊可能被作為deps被其他模塊依賴,但define傳入的factory此時是不執行的;而require則會觸發各個模塊的factory執行。

實現

主要實現分為3部分內容,其中關鍵的地方在于模塊載入。

數據結構

既然是模塊加載器,并且需要處理模塊之間的依賴問題,所以設置一個哈希表保存所有的模塊。

var mods = {} // <id, Module>

function Module(id) {
    var mod = this
    mod.id = id
    mod.uri = id // 簡單起見,根據id拼出uri: abc.js
    mod.deps = []  // 依賴的模塊id列表
    mod.factory = blank // 定義模塊時的factory
    mod.callback = blank // 模塊加載完畢后回調
    mod.exports = {} // 模塊導出的對象
}

define的實現就比較簡單,主要就是往mods里添加一個Module對象,簡單來說就是:

function define(id, deps, factory) {
    var mod = getModule(id) // mods存在就返回,否則就往mods里新增
    mod.deps = deps
    mod.factory = factory
}

模塊載入

遇到require時就會產生模塊載入的動作。模塊載入時可能發生以下動作:

  • 往頁面添加script標簽以讓瀏覽器從服務端拉取js文件
  • js文件中可能遇到define從而立即添加模塊 (非AMD模塊不考慮)
  • define定義的模塊可能有其他依賴模塊,遞歸載入這些模塊,直到所有模塊載入完畢

這里的模塊載入只是把模塊js文件載入到瀏覽器環境中。以上過程對應的大概代碼為:

Module.prototype.load = function() {
    var mod = this
    if (mod.status == STATUS.FETCHING) return
    if (mod.status == STATUS.UNFETCH) {
        return mod.fetch() // 添加script標簽從服務端拉取文件
    }
    mod.status = STATUS.LOADING
    mod.remain = mod.deps.length // 所有依賴載入完畢后通知回調
    function callback() {
        mod.remain--
        if (mod.remain === 0) {
            mod.onload() // 通知回調
        }
    }
    each(mod.deps, function (dep) {
        var m = getModule(dep)  // 獲取依賴模塊對象,依賴模塊可能已經被載入也可能沒有
        if (m.status >= STATUS.LOADED || m.status == STATUS.LOADING) { // 已經載入
            mod.remain--
            return
        }
        m.listeners.push(callback)
        if (m.status < STATUS.LOADING) {
            m.load()
        }
    })
    if (mod.remain == 0) {
        mod.onload()
    }
}

load的實現由于混合了異步問題,所以理解起來會有點難。fetch的實現就是一般的往頁面添加script及設置回調的過程。在fetch完畢后會重新調用load以完成遞歸載入該模塊的依賴:

// 該函數回調時,該js文件已經被瀏覽器執行,其內容包含define則會添加模塊(當然已經被添加過了)
// 可以回頭看上面的define調用的是getModule,此時會重新設置deps/factory等屬性
function onloadListener() {
    var readyState = script.readyState;
    if (typeof readyState === 'undefined' || /^(loaded|complete)$/.test(readyState)) {
        mod.status = STATUS.FETCHED
        mod.load()
    }
}

模塊生效

模塊載入后模塊其實還沒生效,還無法使用模塊中定義的各種符號。要讓模塊生效,就得執行模塊定義的factory函數。在直接間接依賴的模塊被全部載入完成后,最終回調到我們的callback。此時可以看看require的實現:

// 前面提到require/define實現類似,所以這里創建了Module對象,只是復用代碼
function require(deps, callback) {
    var mod = new Module(getId())
    mod.deps = deps
    mod.factory = callback
    mod.callback = function () {
        mod.exec()
    }
    mod.status = STATUS.FETCHED
    mod.load()
}

就是簡單地調用了load,完成后調用了exec。exec又是一個涉及到遞歸的函數,它會遞歸執行所有模塊的factory。factory的執行需要各個模塊的exports對象,只有模塊exec后才會得到exports對象。

Module.prototype.exec = function() {
    var mod = this
    if (mod.status >= STATUS.EXECUTED) { return mod.exports }
    // 獲取依賴模塊的exports列表
    var args = mod.getDepsExport()
    var ret = mod.factory.apply(null, args)
    // factory 返回值作為該模塊的exports
    mod.exports = ret 
    mod.status = STATUS.EXECUTED
    return mod.exports
}

上面的代碼主要是實現這樣的功能:

// 將依賴[d1, d2]的exports作為參數d1,d2傳入
define('my-module', ['d1', 'd2'], function (d1, d2) {
    return {func: function() {}}
})

getDepsExport就是一個取依賴模塊exports的過程:

Module.prototype.getDepsExport = function() {
    var mod = this
    var exports = []
    var deps = mod.deps
    var argsLen = mod.factory.length < deps.length ? mod.factory.length : deps.length
    for (var i = 0; i < argsLen; i++) {
        exports.push(mod.require(deps[i]))
    }
    return exports
}

Module.require(id)用于exec目標模塊并返回其exports:

Module.prototype.require = function(dep) {
    // 由于之前已經遞歸載入過所有模塊,所以該依賴模塊必然是已經存在的,可以被exec的
    var mod = getModule(dep)
    return mod.exec()
}

于是又回到了exec,實現了遞歸執行所有依賴模塊的功能。exec主要是獲取依賴模塊exports并調用factory,所以最初的require將用戶的callback作為factory傳入那個臨時Module,最終使得調用到用戶的callback。

通過以上過程,實際上就已經走通了從define到require實現的整個過程。整個代碼不到200行。基于此可以添加更多RequireJS的附加功能。完。

 

 

來自:http://www.udpwork.com/item/16088.html

 

 本文由用戶 liu1084 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!