avalon本身實現了一套可被替換的模塊加載系統(AMD loader)。具體什麼是AMD loader可參看doJo官方博客關於AMD loader的翻譯講解,看完以後,再繼續往下看,會比較清楚些。javascript
模塊加載系統可替換原理參見javascript 閉包暴露句柄,可替換的前提是前面加載的amd loader文件將define
和require
函數暴露給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(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分兩種加載,先介紹一個簡單的,複雜的則是經過AMD loader加載的。加載方式是構造<link href='...' rel='stylesheet' id='$sepcialURL'>
並插入到head最上邊。這個地方有個疑問是,css樣式覆蓋會按照後來覆蓋前面的樣式來嗎?有待測試。jquery
text的加載是經過ajax作的,並將結果賦值給exports,保存在avalon.modules下面。git
js加載會有對前面提到的avalon.config.shim進行處理(說實在的這個處理只有看完源碼後方能使用無誤。糾結~),具體的加載功能由loadJS
函數來完成。loadJS
經過建立<script class='$日期'>
節點並插入到head上來加載js文件,當加載完成後,會將class屬性改寫掉,並將define
函數定義的factory函數和回調函數執行一下。若是加載失敗了,寫個日誌唄。github
define(id, deps, factory)
define的實現會牽扯到用戶傳參的循環依賴,例如:加載a須要先加載b,加載b又須要先加載a。因此上面的數據結構$moduleId
和deps
就變的十分有用了。根據這個參數的關係,就可找到是否存在循環依賴。
define還會調用require函數來實現JS文件的加載。
define仍是require函數的屬性值哦。innerRequire.define=function(){...}
ajax
ready!
是avalon內置模塊,主要用來等待遊覽器掃完dom樹以後,再執行。avalon.ready
也就是基於此的,上下avalon.ready
源碼。
avalon.ready = function(fn) { innerRequire("ready!", fn)// require('ready!',fn);使用innerRequire是預防avalon的AMD loader被替代掉}
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() //若是成功,則再執行一次,以防有些模塊就差本模塊沒有安裝好 } } }
源碼很棒且註釋很全,直接上源碼。做者司徒正美還在博文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>
<!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函數,會不會簡單些。