avalonJS-源碼閱讀(前)

avalon模塊加載

avalon本身實現了一套可被替換的模塊加載系統(AMD loader)。具體什麼是AMD loader可參看doJo官方博客關於AMD loader的翻譯講解,看完以後,再繼續往下看,會比較清楚些。javascript

模塊加載配置

模塊加載系統可替換原理參見javascript 閉包暴露句柄,可替換的前提是前面加載的amd loader文件將definerequire函數暴露給window對象。具體如何替換可執行php

avalon.config.plugins.loader(false)//不推薦,但有效
//或者
avalon.config({loader:false})

固然了,若是更改了amd loader的話,可不要用avalon.config.plugins.text|css(url)了。
avalon.config.nocache可用來配置是否去除緩存,測試的時候
avalon可經過配置avalon.config.[shim|paths],來加載一些不符合amd加載規則的數據源,例如加載csdn上的jquery等。
paths意味着地址。這裏須要注意的是,paths的設定時,須要:,也就是全路徑,不要像這樣www.abc.com,而是http:www.abc.com
shim則是一個object。css

//shim
{//參考下面的數據結構
    $moduleId:{
        src:$url
        exports:$fn|$string // string說明加載的js文件會在window下存放引用,例如jquery, "$"==>window["$"]
        deps:...
    }
}

模塊加載數據結構

模塊加載所用到的數據先放出來,方便源碼閱讀推斷。html

//avalon.modules 存儲模塊信息
{
    $moduleId:{//默認值
        id:$string//隨機生成或指定
        exports:$object|$string,//暴露出引用
        deps:$array|$object,//依賴的部件 爲$object時,會添加當前部件的加載狀態,依賴的deps 又都會在avalon.modules下有各自的狀態記錄
        state:$number//加載狀態 2:加載過 1:正在加載
        factory:$fn//模塊自己,會被存放到factorys中,他的this指代window對象
        args:$array//保存依賴模塊的返回值
    }
}
//factorys數組下的factory函數
factory.delay=$fn//檢查依賴,延遲加載
factory.id=$string//用來debug用的

require實現

require(deps, factory, baseUrl)
require 默認id的生成是經過"callback"+setTimeout("1")來實現UniId。
require 函數會先調用loadSource遍歷deps是否都加載完,沒加載完的,會根據加載文件類型調用不一樣的函數去完成異步加載,並將其要加載的依賴放入loadings存儲,經過各類合適的時機(例如,完成一個js模塊的加載時)調用checkDeps來將loadings處理掉,更新依賴狀態,進而加載本身。
這裏須要注意的是,factory的this,指代window對象(不知爲什麼)。java

動態加載

動態加載劃分爲三種:js、css、text(文本),分別對應一個函數。並對url #?後綴進行刪除。
加載css時,前面添加 css! 。node

css加載

css分兩種加載,先介紹一個簡單的,複雜的則是經過AMD loader加載的。加載方式是構造<link href='...' rel='stylesheet' id='$sepcialURL'>並插入到head最上邊。這個地方有個疑問是,css樣式覆蓋會按照後來覆蓋前面的樣式來嗎?有待測試。jquery

text加載

text的加載是經過ajax作的,並將結果賦值給exports,保存在avalon.modules下面。git

js加載

js加載會有對前面提到的avalon.config.shim進行處理(說實在的這個處理只有看完源碼後方能使用無誤。糾結~),具體的加載功能由loadJS函數來完成。
loadJS經過建立<script class='$日期'>節點並插入到head上來加載js文件,當加載完成後,會將class屬性改寫掉,並將define函數定義的factory函數和回調函數執行一下。若是加載失敗了,寫個日誌唄。github

define實現

define(id, deps, factory)
define的實現會牽扯到用戶傳參的循環依賴,例如:加載a須要先加載b,加載b又須要先加載a。因此上面的數據結構$moduleIddeps就變的十分有用了。根據這個參數的關係,就可找到是否存在循環依賴。
define還會調用require函數來實現JS文件的加載。
define仍是require函數的屬性值哦。innerRequire.define=function(){...}ajax

ready!

ready!是avalon內置模塊,主要用來等待遊覽器掃完dom樹以後,再執行。avalon.ready也就是基於此的,上下avalon.ready源碼。

avalon.ready = function(fn) {
    innerRequire("ready!", fn)//  require('ready!',fn);使用innerRequire是預防avalon的AMD loader被替代掉}

函數介紹

checkDeps

checkDeps是用來檢查存放在loadings模塊的依賴是否都完成加載,若是依賴都完成了加載,而本身沒有,則去執行本身的factory工廠函數。這個函數用到了loop來跳過雙for循環break問題,能夠拿來借鑑下。

function checkDeps() {
    //檢測此JS模塊的依賴是否都已安裝完畢,是則安裝自身
    //只要loadings有一個
    loop: for (var i = loadings.length, id; id = loadings[--i];) {

        var obj = modules[id],
            deps = obj.deps
        for (var key in deps) {
            if (ohasOwn.call(deps, key) && modules[key].state !== 2) {
                continue loop
            }
        }
        //若是deps是空對象或者其依賴的模塊的狀態都是2
        if (obj.state !== 2) {
            loadings.splice(i, 1) //必須先移除再安裝,防止在IE下DOM樹建完後手動刷新頁面,會屢次執行它
            fireFactory(obj.id, obj.args, obj.factory)
            checkDeps() //若是成功,則再執行一次,以防有些模塊就差本模塊沒有安裝好
        }
    }
}

獲取當前scriptURL

源碼很棒且註釋很全,直接上源碼。做者司徒正美還在博文getBasePath 函數中進行了詳細的講解,有興趣的能夠去了解下。

//getCurrentScript(true);
function getCurrentScript(base) {
    // 參考 https://github.com/samyk/jiagra/blob/master/jiagra.js
    var stack
    try {
        a.b.c() //強制報錯,以便捕獲e.stack
    } catch (e) { //safari的錯誤對象只有line,sourceId,sourceURL
        stack = e.stack
        if (!stack && window.opera) {
            //opera 9沒有e.stack,但有e.Backtrace,但不能直接取得,須要對e對象轉字符串進行抽取
            stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
        }
    }
    if (stack) {
        /**e.stack最後一行在全部支持的瀏覽器大體以下:
         *chrome23:
         * at http://113.93.50.63/data.js:4:1
         *firefox17:
         *@http://113.93.50.63/query.js:4
         *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
         *@http://113.93.50.63/data.js:4
         *IE10:
         *  at Global code (http://113.93.50.63/data.js:4:1)
         *  //firefox4+ 能夠用document.currentScript
         */
        stack = stack.split(/[@ ]/g).pop() //取得最後一行,最後一個空格或@以後的部分
        stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉換行符
        return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行號與或許存在的出錯字符起始位置
    }
    var nodes = (base ? DOC : head).getElementsByTagName("script") //只在head標籤中尋找
    for (var i = nodes.length, node; node = nodes[--i]; ) {
        if ((base || node.className === subscribers) && node.readyState === "interactive") {//subscribers="$"+(new Date-0)
            return node.className = node.src
        }
    }
}

一個有趣的加載測試

這個測試時關於加載順序的。

<!DOCTYPE html>
<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<div ms-controller="hello">
<h1>Hello, {{name}}</h1>
</div>
<script>
avalon.ready(function(){
	var model = avalon.define('hello', function(vm){
        vm.name = 'Avalon';
    });
    avalon.scan();
})   
</script>
</body>
</html>

請問avalon何時將model刷進頁面去的,爲何?和下面的代碼有什麼區別?

<!DOCTYPE html>
<html>
<head>
<script src="avalon.js"></script>
</head>
<body>
<div ms-controller="hello">
<h1>Hello, {{name}}</h1>
</div>
<script>
var model = avalon.define('hello', function(vm){
    vm.name = 'Avalon';
});
</script>
</body>
</html>

作完這個練習,我相信你必定會對dom加載有個新的認識。

小結

整個實現,爲factory函數依賴注入廢了很多勁,不知道添上angularJS的annotate函數,會不會簡單些。

相關文章
相關標籤/搜索