實現一個requirejs原型demo

前言

前幾天看了下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]
複製代碼

實現流程

  1. 首先定義模塊名稱與對應的路徑,也就是實現require.config功能,簡化版以下:
let paths = {
    apple:'./apple.js',
    orange:'./orange.js',
    jquery:'https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js',
}
複製代碼
  1. 而後建立一個變量用於保存加載好的模塊,建立一個變量收集全部執行require的地方。
// 存放全部註冊require的模塊,收集他們的依賴,以及回調
let reqs = {}

// 保存加載好的模塊
let modules = {}
複製代碼
  1. 建立require方法,使用與amd相似的方式,接收兩個參數:1.依賴的模塊數組,2.模塊加載完後將要執行的回調。
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)
    }
}
複製代碼
  1. 按理說模塊加載完成後就會執行loadComplete方法。

但須要注意的是,node.load方法會在下載好的js執行完以後纔會執行。意思就是說若是加載的apple.js裏有console.log("apple模塊加載好了"),而loadComplete裏有console.log("執行script的onload方法"),那麼執行順序是1.apple模塊加載好了;2.執行script的onload方法。由於apple模塊裏執行了define方法,因此先看define的定義。node

  1. 建立define方法

本方法採用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',
        }
    }
}
複製代碼
  1. 定義模塊是amd規範:define.amd = truegithub

  2. 下面真正到了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
        }
    })
}
複製代碼

代碼

參考:

瀏覽器加載 CommonJS 模塊的原理與實現

更大的目標

仿照require1k實現相似requirejs的模塊加載庫。代碼

果真複雜的多,根據註釋走了一遍流程,基本上流程走的通。

參考:

requirejs 源碼與架構分析

requirejs源碼學習筆記(一)

require1k

相關文章
相關標籤/搜索