成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

淺談模塊化加載的實(shí)現(xiàn)原理

CodeSheep / 3303人閱讀

摘要:如果你不太明白模塊化的作用,建議看看玉伯寫的一篇文章。我們可以使用自己的方式去管理代碼,不過有人已經(jīng)研究處理一套標(biāo)準(zhǔn),而且是全球統(tǒng)一,那就拿著用吧關(guān)于規(guī)范,我這里就不多說了,可以去看看草案,玉伯也翻譯了一份。

試發(fā)一彈,本文同步自:http://barretlee.com
略蛋疼的是不支持: [title][url reference]

相信很多人都用過 seajs、 requirejs 等這些模塊加載器,他們都是十分便捷的工程管理工具,簡(jiǎn)化了代碼的結(jié)構(gòu),更重要的是消除了各種文件依賴和命名沖突問題,并利用 AMD / CMD 規(guī)范統(tǒng)一了格式。如果你不太明白模塊化的作用,建議看看玉伯寫的一篇文章。

為什么他們會(huì)想到使用模塊化加載呢,我覺得主要是兩點(diǎn)。

一是按需加載,業(yè)務(wù)越來越大,基礎(chǔ)代碼也會(huì)越來越多,開發(fā)人員可能開發(fā)了一百個(gè)小工具,而且都塞在一個(gè)叫做 utils.js 的包里,但是一個(gè)頁面可能只需要三到五個(gè)小工具,如果直接去加載這個(gè) utils.js 豈不是很大的浪費(fèi),PC 端還好,主要是無線端,剩下 1KB 那都是很大的價(jià)值啊,所以呢,如今很多框架的開發(fā)都體現(xiàn)出細(xì)顆粒度的分化,像百度研究比較賣力的 tangram,阿里放滿產(chǎn)品線的 kissy,幾乎是細(xì)分到了微粒程度,這種細(xì)分方式也促進(jìn)了模塊化加載技術(shù)的發(fā)展,比如為了減少請(qǐng)求數(shù)量,kissy 的 config 中開啟 combo 就可以合并多個(gè)請(qǐng)求為一個(gè)等等。

第二點(diǎn),應(yīng)該也是從服務(wù)器那邊參考而來的,服務(wù)器腳本很多都是以文件為單位分離的,如果要利用其它文件的功能,可以輕而易舉的 require 或者 include 進(jìn)來,我沒有去研究這些加載函數(shù)的內(nèi)部實(shí)現(xiàn)原理,稍微猜猜應(yīng)該是把文件寫入到緩存,遇到 include 之類的加載函數(shù),暫停寫入,找到需要 include 的文件地址,把找到的文件接著上面繼續(xù)寫入緩存,以此類推,直到結(jié)束,然后編譯器進(jìn)行統(tǒng)一編譯。

一、模塊化加載的技術(shù)原理

先不考慮各種模塊定義規(guī)范,本文目的只是簡(jiǎn)要的分析加載原理, CMD / AMD 規(guī)范雖內(nèi)容然不多,但是要實(shí)現(xiàn)起來,工程量還是不小。文章后面會(huì)提到。

1. 數(shù)據(jù)模塊的加載

既然是模塊化加載,想辦法把模塊內(nèi)容拿到當(dāng)然是重頭戲,無論是 script 還是 css 文件的加載,一個(gè) script 或者 link 標(biāo)簽就可以搞定問題,不過我這里采用的是 ajax,目的是為了拿到 script 的代碼,也是為了照顧后面要說的 CMD 規(guī)范。

var require = function(path){
    var xhr = new XMLHttpRequest(), res;
    xhr.open("GET", path, true);
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4 && xhr.status == 200){
            // 獲取源碼
            res = xhr.responseText;
        }
    }
    xhr.send();
};

創(chuàng)建 script 便簽加載腳本不會(huì)存在跨域問題,不過拿到的腳本會(huì)被瀏覽器立馬解析出來,如果要做同異步的處理就比較麻煩了。沒有跨域的文件我們就通過上面的方式加載,如果腳本跨域了,再去創(chuàng)建標(biāo)簽,讓文檔自己去加載。

// 跨域處理
if(crossDomain){
    var script = document.createElement("script");
    script.src = path;

    (document.getElementsByTagName("head")[0] || document.body).appendChild(script);
}
2. 解析模塊的層次依賴關(guān)系

模塊之間存在依賴關(guān)系是十分正常的,如一個(gè)工程的文件結(jié)構(gòu)如下:

project/
├── css/
│   └── main.css
├── js/
│   ├── require.js
│   └── modlues/
│       ├── a.js
│       ├── b.js
│       └── c.js
└── index.html

而這里幾個(gè)模塊的依賴關(guān)系是:

            ┌> a.js -> b.js
index.html -|
            └> c.js

// a.js
require("./js/test/b.js");

// b.js
console.log("i am b");

// c.js
console.log("i am c");

我們要從 index.html 中利用 require.js 獲取這一連串的依賴關(guān)系,一般采用的方式就是正則匹配。如下:先拿到 function 的代碼,然后正則匹配出第一層的依賴關(guān)系,接著加載匹配到關(guān)系的代碼,繼續(xù)匹配。

// index.html


整個(gè)函數(shù)的入口是 start,正則表達(dá)式為:

var r = /require((.*))/g;

var start = function(str){
    while(match = r.exec(str)) {
        console.log(match[1]);
    }
};

由此我們拿到了第一層的依賴關(guān)系,

["./js/modlues/a.js", "./js/modlues/c.js"]

接著要拿到 a.js 和 b.js 的文件層次依賴,之前我們寫了一個(gè) require 函數(shù),這個(gè)函數(shù)可以拿到腳本的代碼內(nèi)容,不過這個(gè) require 函數(shù)要稍微修改下,遞歸去查詢和下載代碼。

var cache = {};
var start = function(str){
    while(match = r.exec(str)) {
        console.log(match && match[1]);
        // 如果匹配到了內(nèi)容,下載 path 對(duì)應(yīng)的源碼
        match && match[1] && require(match[1]);
    }
};

var require = function(path){
    var xhr = new XMLHttpRequest(), res;
    xhr.open("GET", path, true);
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4 && xhr.status == 200){
            res = xhr.responseText;
            // 緩存文件
            cache[path] = res;
            // 繼續(xù)遞歸匹配
            start(res);
        }
    }
    xhr.send();
};

上面的代碼已經(jīng)可以很好地拿到文件遞歸關(guān)系了。

3. 添加事件機(jī)制,優(yōu)化管理代碼

但是我們有必要先把 responseText 緩存起來,如果不緩存文件,直接 eval 得到的 responseText 代碼,想想會(huì)發(fā)生什么問題~ 如果模塊之間存在循環(huán)引用,如:

            ┌> a.js -> b.js
index.html -|
            └> b.js -> a.js

那 start 和 require 將會(huì)陷入死循環(huán),不斷的加載代碼。所以我們需要先拿到依賴關(guān)系,然后解構(gòu)關(guān)系,分析出我們需要加載哪些模塊。值得注意的是,我們必須按照加載的順序去 eval 代碼,如果 a 依賴 b,先去執(zhí)行 a 的話,一定會(huì)報(bào)錯(cuò)!

有兩個(gè)問題我糾結(jié)了半天,上面的請(qǐng)求方式,何時(shí)會(huì)結(jié)束?用什么方式去記錄文件依賴關(guān)系?

最后還是決定將 start 和 require 兩個(gè)函數(shù)的相互遞歸修改成一個(gè)函數(shù)的遞歸。用一個(gè)對(duì)象,發(fā)起請(qǐng)求時(shí)把 URL 作為 key,在這個(gè)對(duì)象里保存 XHR 對(duì)象,XHR 對(duì)象請(qǐng)求完成后,把抓取到的新請(qǐng)求再用同樣的方式放入這個(gè)對(duì)象中,同時(shí)從這個(gè)對(duì)象中把自己刪除掉,然后判斷這個(gè)對(duì)象上是否存在 key, 如果存在說明還有 XHR 對(duì)象沒完成。

var r = /require(s*"(.*)"s*)/g;
var cache = {};    // 文件緩存
var relation = []; // 依賴過程控制
var obj = {};      // xhr 管理對(duì)象

//輔助函數(shù),獲取鍵值數(shù)組
Object.keys = Object.keys || function(obj){
    var a = [];
    for(a[a.length] in obj);
    return a ;
};

// 入口函數(shù)
function start(str){
    while(match = r.exec(str)){
        obj[match[1]] = new XMLHttpRequest();
        require(obj[match[1]], match[1]);
    }
}

// 遞歸請(qǐng)求
var require = function(xhr, path){
    //記錄依賴過程
    relation.push(path);

    xhr.open("GET", path, true);
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4 && xhr.status == 200){
            var res = xhr.responseText;
            // 緩存文件
            cache[path] = res;
            // 從xhr對(duì)象管理器中刪除已經(jīng)加載完畢的函數(shù)
            delete obj[path];

            // 如果obj為空則觸發(fā) allLoad 事件
            Object.keys(obj).length == 0 ? Event.trigger("allLoad") : void 0;
            //遞歸條件
            while(match = r.exec(res)){
                obj[match[1]] = new XMLHttpRequest();
                require(obj[match[1]], match[1]);
            }
        }
    }
    xhr.send();
};

上面的代碼已經(jīng)基本完成了文件依賴分析,文件的加載和緩存工作了,我寫了一個(gè),有興趣可以看一看。這個(gè)demo的文件結(jié)構(gòu)為:

project/
├── js/
│   ├── require.js
│   └── test/
│       ├── a.js
│       ├── b.js
│       ├── c.js
│       ├── d.js
│       └── e.js
└── index.html

//文件依賴關(guān)系為
                       ┌> c.js
            ┌> a.js ->-|
index.html -|          └> d.js
            └> b.js -> e.js

戳我 → Demo

4. CMD 規(guī)范的介紹

上面寫了一大堆內(nèi)容,也實(shí)現(xiàn)了模塊加載器的原型,但是放在實(shí)際應(yīng)用中,他就是個(gè)廢品,回到最開始,我們?yōu)槭裁匆褂媚K化加載。目的是為了不去使用麻煩的命名空間,把復(fù)雜的模塊依賴交給 require 這個(gè)函數(shù)去管理,但實(shí)際上呢,上面拿到的所有模塊都是暴露在全局變量中的,也就是說,如果 a.js 和 b.js 中存在命名相同的變量,后者將會(huì)覆蓋前者,這是我們不愿意看到的。為了處理此類問題,我們有必要把所有的模塊都放到一個(gè)閉包中,這樣一來,只要不使用 window.vars 命名,閉包之間的變量是不會(huì)相互影響的。我們可以使用自己的方式去管理代碼,不過有人已經(jīng)研究處理一套標(biāo)準(zhǔn),而且是全球統(tǒng)一,那就拿著用吧~

關(guān)于 CMD 規(guī)范,我這里就不多說了,可以去看看草案,玉伯也翻譯了一份,。每一模塊有且僅有一個(gè)對(duì)外公開的接口 exports,如:

define(function(require, exports) {

  // 對(duì)外提供 foo 屬性
  exports.foo = "bar";

  // 對(duì)外提供 doSomething 方法
  exports.doSomething = function() {};

});

剩下的工作就是針對(duì) CMD 規(guī)范寫一套符合標(biāo)準(zhǔn)的代碼接口,這個(gè)比較瑣碎,就不寫了。

二、額外的話題

上面的代碼中提到了關(guān)于 Event 的事件管理。在模塊全部加在完畢之后,需要有個(gè)東西告訴你,所以順手寫了一個(gè) Event 的事件管理器。

// Event
var Event = {};
Event.events = [];
Event.on = function(evt, func){
    for(var i = 0; i < Event.events.length; i++){
        if(Event.events[i].evt == evt){
            Event.events[i].func.push(func);
            return;
        }
    }

    Event.events.push({
        evt: evt,
        func: [func]
    });
};
Event.trigger = function(evt){
    for(var i = 0; i < Event.events.length; i++){
        if(Event.events[i].evt == evt){
            for(var j = 0; j < Event.events[i].func.length; j++){
                Event.events[i].func[j]();
            }
            return;
        }
    }
};
Event.off = function(evt){
    for(var i = 0; i < Event.events.length; i++){
        Event.events.splice(i, 1);
    }       
};

我覺得 seajs 是一個(gè)很不錯(cuò)的模塊加載器,如果感興趣,可以去看看他的源碼實(shí)現(xiàn),代碼不長,只有一千多行。模塊的加載它采用的是創(chuàng)建文本節(jié)點(diǎn),讓文檔去加載模塊,實(shí)時(shí)查看狀態(tài)為 interactive 的 script 標(biāo)簽,如果處于交互狀態(tài)就拿到他的代碼,接著刪除節(jié)點(diǎn)。當(dāng)節(jié)點(diǎn)數(shù)目為 0 的時(shí)候,加載工作完成。

本文沒有考慮 css 文件的加載問題,我們可以把它當(dāng)做一個(gè)沒有 require 關(guān)鍵詞的 js 文件,或者把它匹配出來之后另作處理,因?yàn)樗遣豢赡艽嬖谀K依賴關(guān)系的。

然后就是很多很多細(xì)節(jié),本文的目的并不是寫一個(gè)類似 seajs 的模塊管理工具,只是稍微說幾句自己對(duì)這玩意兒的看法,如果說的有錯(cuò),請(qǐng)多多吐槽!

三、參考資料

https://github.com/seajs/seajs/issues seajs issues

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87461.html

相關(guān)文章

  • 2018 淺談前端面試那些事

    摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。 雖然今年沒有換工作的打算 但為了跟上時(shí)代的腳步 還是忍不住整理了一份最新前端知識(shí)點(diǎn) 知識(shí)點(diǎn)匯總 1.HTML HTML5新特性,語義化瀏覽器的標(biāo)準(zhǔn)模式和怪異模式xhtml和html的區(qū)別使用data-的好處meta標(biāo)簽canvasHTML廢棄的標(biāo)簽IE6 bug,和一些定位寫法css js放置位置和原因...

    LiuRhoRamen 評(píng)論0 收藏0
  • 2018 淺談前端面試那些事

    摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。 雖然今年沒有換工作的打算 但為了跟上時(shí)代的腳步 還是忍不住整理了一份最新前端知識(shí)點(diǎn) 知識(shí)點(diǎn)匯總 1.HTML HTML5新特性,語義化瀏覽器的標(biāo)準(zhǔn)模式和怪異模式xhtml和html的區(qū)別使用data-的好處meta標(biāo)簽canvasHTML廢棄的標(biāo)簽IE6 bug,和一些定位寫法css js放置位置和原因...

    stormgens 評(píng)論0 收藏0
  • 2018 淺談前端面試那些事

    摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。 雖然今年沒有換工作的打算 但為了跟上時(shí)代的腳步 還是忍不住整理了一份最新前端知識(shí)點(diǎn) 知識(shí)點(diǎn)匯總 1.HTML HTML5新特性,語義化瀏覽器的標(biāo)準(zhǔn)模式和怪異模式xhtml和html的區(qū)別使用data-的好處meta標(biāo)簽canvasHTML廢棄的標(biāo)簽IE6 bug,和一些定位寫法css js放置位置和原因...

    Hujiawei 評(píng)論0 收藏0
  • 如何設(shè)計(jì)大型網(wǎng)站前端 JavaScript 框架

    摘要:前端單元測(cè)試,推薦淘寶開源的工具,簡(jiǎn)單易用,支持眾多測(cè)試框架,也支持調(diào)試。這些也是設(shè)計(jì)前端框架時(shí)需要權(quán)衡的重要方面。最后,其實(shí)大型網(wǎng)站不一定要設(shè)計(jì)自己的前端框架,完全可以選用現(xiàn)有的框架。 有人在知乎上提問如何設(shè)計(jì)大型網(wǎng)站的前端 JavaScript 框架,有不少回答,其中得贊較多的兩個(gè)回答如下: 相對(duì)大型的項(xiàng)目在前端 JS 方面有幾個(gè)需要達(dá)成的目標(biāo): 1. 代碼邏輯分層 ...

    Yuanf 評(píng)論0 收藏0
  • 淺談網(wǎng)站性能之前端性能優(yōu)化

    摘要:淺談網(wǎng)站性能之前端性能優(yōu)化性能優(yōu)化的目的無非是減少用戶流量消耗,提升用戶首屏體驗(yàn),提升用戶訪問速度,讓用戶專注內(nèi)容本身。前端性能優(yōu)化減少請(qǐng)求數(shù)量基本原理在瀏覽器與服務(wù)器進(jìn)行通信時(shí),主要是通過進(jìn)行通信。 最近項(xiàng)目慢慢走上正軌,需求趨于平穩(wěn),這才想起需要對(duì)整站進(jìn)行性能優(yōu)化。經(jīng)過一段時(shí)間的學(xué)習(xí),結(jié)合現(xiàn)在項(xiàng)目的實(shí)際性能情況,發(fā)現(xiàn)確實(shí)有許多地方可以進(jìn)行優(yōu)化。于是就開始了我的前端性能優(yōu)化之旅。以下...

    Winer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<