您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“怎么編寫(xiě)自己的JavaScript框架”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
1. 模塊的定義和加載
1.1 模塊的定義
一個(gè)框架想要能支撐較大的應(yīng)用,首先要考慮怎么做模塊化。有了內(nèi)核和模塊加載系統(tǒng),外圍的模塊就可以一個(gè)一個(gè)增加。不同的JavaScript框架,實(shí)現(xiàn)模塊化方式各有不同,我們來(lái)選擇一種比較優(yōu)雅的方式作個(gè)講解。
先問(wèn)個(gè)問(wèn)題:我們做模塊系統(tǒng)的目的是什么?如果覺(jué)得這個(gè)問(wèn)題難以回答,可以從反面來(lái)考慮:假如不做模塊系統(tǒng),有什么樣的壞處?
我們經(jīng)歷過(guò)比較粗放、混亂的前端開(kāi)發(fā)階段,頁(yè)面里充滿(mǎn)了全局變量,全局函數(shù)。那時(shí)候要復(fù)用js文件,就是把某些js函數(shù)放到一個(gè)文件里,然后讓多個(gè)頁(yè)面都來(lái)引用。
考慮到一個(gè)頁(yè)面可以引用多個(gè)這樣的js,這些js互相又不知道別人里面寫(xiě)了什么,很容易造成命名的沖突,而產(chǎn)生這種沖突的時(shí)候,又沒(méi)有哪里能夠提示出來(lái)。所以我們要有一種辦法,把作用域比較好地隔開(kāi)。
JavaScript這種語(yǔ)言比較奇怪,奇怪在哪里呢,它的現(xiàn)有版本里沒(méi)package跟class,要是有,我們也沒(méi)必要來(lái)考慮什么自己做模塊化了。那它是要用什么東西來(lái)隔絕作用域呢?
在很多傳統(tǒng)高級(jí)語(yǔ)言里,變量作用域的邊界是大括號(hào),在{}里面定義的變量,作用域不會(huì)傳到外面去,但我們的JavaScript大人不是這樣的,他的邊界是function。所以我們這段代碼,i仍然能打出值:
for (var i=0; i<5; i++) { //do something } alert(i);
那么,我們只能選用function做變量的容器,把每個(gè)模塊封裝到一個(gè)function里?,F(xiàn)在問(wèn)題又來(lái)了,這個(gè)function本身的作用域是全局的,怎么辦?我們想不到辦法,拔劍四顧心茫然。
我們有沒(méi)有什么可參照的東西呢?這時(shí)候,腦海中一群語(yǔ)言飄過(guò): C語(yǔ)言飄過(guò):“我不是面向?qū)ο笳Z(yǔ)言哦~不需要像你這么組織哦~”,“死開(kāi)!” Java飄過(guò):“我是純面向?qū)ο笳Z(yǔ)言哦,連main都要在類(lèi)中哦,編譯的時(shí)候通過(guò)裝箱清單指定入口哦~”,“死開(kāi)!” C++飄過(guò):“我也是純面向?qū)ο笳Z(yǔ)言哦”,等等,C++是純面向?qū)ο蟮恼Z(yǔ)言嗎?你的main是什么???main是特例,不在任何類(lèi)中!
啊,我們發(fā)現(xiàn)了什么,既然無(wú)法避免全局的作用域,那與其讓100個(gè)function都全局,不如只讓一個(gè)來(lái)全局,其他的都由它管理。
本來(lái)我們打算自己當(dāng)上帝的,現(xiàn)在只好改行先當(dāng)個(gè)工商局長(zhǎng)。你想開(kāi)店嗎?先來(lái)注冊(cè),不然封殺你!于是良民們紛紛來(lái)注冊(cè)。店名叫什么,從哪進(jìn)貨,賣(mài)什么的,一一登記在案,為了方便下面的討論,我們連進(jìn)貨的過(guò)程都讓工商局管理起來(lái)。
店名,指的就是這里的模塊名,從哪里進(jìn)貨,代表它依賴(lài)什么其他模塊,賣(mài)什么,表示它對(duì)外提供一些什么特性。
好了,考慮到我們的這個(gè)注冊(cè)管理機(jī)構(gòu)是個(gè)全局作用域,我們還得把它掛在window上作為屬性,然后再用一個(gè)function隔離出來(lái),要不然,別人也定義一個(gè)同名的,就把我們覆蓋掉了。
(function() { window.thin = { define: function(name, dependencies, factory) { //register a module } }; })();
在這個(gè)module方法內(nèi)部,應(yīng)當(dāng)怎么去實(shí)現(xiàn)呢?我們的module應(yīng)當(dāng)有一個(gè)地方存儲(chǔ),但存儲(chǔ)是要在工商局內(nèi)部的,不是隨便什么人都可以看到的,所以,這個(gè)存儲(chǔ)結(jié)構(gòu)也放在工商局同樣的作用域里。
用什么結(jié)構(gòu)去存儲(chǔ)呢?工商局備案的時(shí)候,店名不能跟已有的重復(fù),所以我們發(fā)現(xiàn)這是用hash的很好場(chǎng)景,考慮到JavaScript語(yǔ)言層面沒(méi)有hash,我們弄個(gè)Object來(lái)存。
(function() { var moduleMap = {}; window.thin = { define: function(name, dependencies, factory) { if (!moduleMap[name]) { var module = { name: name, dependencies: dependencies, factory: factory }; moduleMap[name] = module; } return moduleMap[name]; } }; })();
現(xiàn)在,模塊的存儲(chǔ)結(jié)構(gòu)就搞好了。
1.2 模塊的使用
存的部分搞好了,我們來(lái)看看怎么取。現(xiàn)在來(lái)了一個(gè)商家,賣(mài)木器的,他需要從一個(gè)賣(mài)釘子的那邊進(jìn)貨,賣(mài)釘子的已經(jīng)來(lái)注冊(cè)過(guò)了,現(xiàn)在要讓這個(gè)木器廠能買(mǎi) 到釘子?,F(xiàn)在的問(wèn)題是,兩個(gè)商家處于不同的作用域,也就是說(shuō),它們互相不可見(jiàn),那通過(guò)什么方式,我們才能讓他們產(chǎn)生調(diào)用關(guān)系呢?
個(gè)人解決不了的問(wèn)題還是得靠政府,有困難要堅(jiān)決克服,沒(méi)有困難就制造困難來(lái)克服?,F(xiàn)在困難有了,該克服了。商家說(shuō),我能不能給你我的進(jìn)貨名單,你幫我查一下它們?cè)谀募业?,然后告訴我?這么簡(jiǎn)單的要求當(dāng)然一口答應(yīng)下來(lái),但是采用什么方式傳遞給你呢?這可犯難了。
我們參考AngularJS框架,寫(xiě)了一個(gè)類(lèi)似的代碼:
thin.define("A", [], function() { //module A }); thin.define("B", ["A"], function(A) { //module B var a = new A(); });
看這段代碼特別在哪里呢?模塊A的定義,毫無(wú)特別之處,主要看模塊B。它在依賴(lài)關(guān)系里寫(xiě)了一個(gè)字符串的A,然后在工廠方法的形參寫(xiě)了一個(gè)真真切切的A類(lèi) 型。嗯?這個(gè)有些奇怪啊,你的A類(lèi)型要怎么傳遞過(guò)來(lái)呢?其實(shí)是很簡(jiǎn)單的,因?yàn)槲覀兟暶髁艘蕾?lài)項(xiàng)的數(shù)組,所以可以從依賴(lài)項(xiàng),挨個(gè)得到對(duì)應(yīng)的工廠方法,然后創(chuàng) 建實(shí)例,傳進(jìn)來(lái)。
use: function(name) { var module = moduleMap[name]; if (!module.entity) { var args = []; for (var i=0; i<module.dependencies.length; i++) { if (moduleMap[module.dependencies[i]].entity) { args.push(moduleMap[module.dependencies[i]].entity); } else { args.push(this.use(module.dependencies[i])); } } module.entity = module.factory.apply(noop, args); } return module.entity; }
我們可以看到,這里面遞歸獲取了依賴(lài)項(xiàng),然后當(dāng)作參數(shù),用這個(gè)模塊的工廠方法來(lái)實(shí)例化了一下。這里我們多做了一個(gè)判斷,如果模塊工廠已經(jīng)執(zhí)行過(guò),就緩存在entity屬性上,不需要每次都創(chuàng)建。以此類(lèi)推,假如一個(gè)模塊有多個(gè)依賴(lài)項(xiàng),也可以用類(lèi)似的方式寫(xiě),毫無(wú)壓力:
thin.define("D", ["A", "B", "C"], function(A, B, C) { //module D var a = new A(); var b = new B(); var c = new C(); });
注意了,D模塊的工廠,實(shí)參的名稱(chēng)未必就要是跟依賴(lài)項(xiàng)一致,比如,以后我們代碼較多,可以給依賴(lài)項(xiàng)和模塊名稱(chēng)加命名空間,可能變成這樣:
thin.define("foo.D", ["foo.A", "foo.B", "foo.C"], function(A, B, C) { //module D var a = new A(); var b = new B(); var c = new C(); });
這段代碼仍然可以正常運(yùn)行。我們來(lái)做另外一個(gè)測(cè)試,改變形參的順序:
thin.define("A", [], function() { return "a"; }); thin.define("B", [], function() { return "b"; }); thin.define("C", [], function() { return "c"; }); thin.define("D", ["A", "B", "C"], function(B, A, C) { return B + A + C; }); var D = thin.use("D"); alert(D);
試試看,我們的D打出什么結(jié)果呢?結(jié)果是"abc",所以說(shuō),模塊工廠的實(shí)參只跟依賴(lài)項(xiàng)的定義有關(guān),跟形參的順序無(wú)關(guān)。我們看到,在AngularJS里面,并非如此,實(shí)參的順序是跟形參一致的,這是怎么做到的呢?
我們先離開(kāi)代碼,思考這么一個(gè)問(wèn)題:如何得知函數(shù)的形參名數(shù)組?對(duì),我們是可以用func.length得到形參個(gè)數(shù),但無(wú)法得到每個(gè)形參的變量名,那怎么辦呢?
AngularJS使用了一種比較極端的辦法,分析了函數(shù)的字面量。眾所周知,在JavaScript中,任何對(duì)象都隱含了toString方法, 對(duì)于一個(gè)函數(shù)來(lái)說(shuō),它的toString就是自己的實(shí)現(xiàn)代碼,包含函數(shù)簽名和注釋。下面我貼一下AngularJS里面的這部分代碼:
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, fnText, argDecl, last; if (typeof fn == 'function') { if (!($inject = fn.$inject)) { $inject = []; fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ arg.replace(FN_ARG, function(all, underscore, name){ $inject.push(name); }); }); fn.$inject = $inject; } } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn'); $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; }
可以看到,這個(gè)代碼也不長(zhǎng),重點(diǎn)是類(lèi)型為function的那段,首先去除了注釋?zhuān)缓螳@取了形參列表字符串,這段正則能獲取到兩個(gè)結(jié)果,***個(gè)是 全函數(shù)的實(shí)現(xiàn),第二個(gè)才是真正的形參列表,取第二個(gè)出來(lái)split,就得到了形參的字符串列表了,然后按照這個(gè)順序再去加載依賴(lài)模塊,就可以讓形參列表不 對(duì)應(yīng)于依賴(lài)項(xiàng)數(shù)組了。
AngularJS的這段代碼很強(qiáng)大,但是要損耗一些性能,考慮到我們的框架首要原則是簡(jiǎn)單,甚至可以為此犧牲一些靈活性,我們不做這么復(fù)雜的事情了。
1.3 模塊的加載
到目前為止,我們可以把多個(gè)模塊都定義在一個(gè)文件中,然后手動(dòng)引入這個(gè)js文件,但是如果一個(gè)頁(yè)面要引用很多個(gè)模塊,引入工作就變得比較麻煩,比如 說(shuō),單頁(yè)應(yīng)用程序(SPA)一般比較復(fù)雜,往往包含數(shù)以萬(wàn)計(jì)行數(shù)的js代碼,這些代碼至少分布在幾十個(gè)甚至成百上千的模塊中,如果我們也在主界面就加載它 們,載入時(shí)間會(huì)非常難以接受。但我們可以這樣看:主界面加載的時(shí)候,并不是用到了所有這些功能,能否先加載那些必須的,而把剩下的放在需要用的時(shí)候再去加 載?
所以我們可以考慮***的AJAX,從服務(wù)端獲取一個(gè)js的內(nèi)容,然后……,怎么辦,你當(dāng)然說(shuō)不能eval了,因?yàn)閾?jù)說(shuō)eval很evil啦,但是它 evil在哪里呢?主要是破壞全局作用域啦,怎么怎么,但是如果這些文件里面都是按照我們規(guī)定的模塊格式寫(xiě),好像也沒(méi)有什么在全局作用域的……,好吧。
算了,我們還是用最簡(jiǎn)單的方式了,就是動(dòng)態(tài)創(chuàng)建script標(biāo)簽,然后設(shè)置src,添加到document.head里,然后監(jiān)聽(tīng)它們的完成事件, 做后續(xù)操作。真的很簡(jiǎn)單,因?yàn)槲覀兊目蚣懿恍枰紤]那么多種情況,不需要AMD,不需要require那么麻煩,用這框架的人必須按照這里的原則寫(xiě)。
我也偷懶了,只是貼一下代碼,順便解釋一下,界面把所依賴(lài)的js文件路徑放在數(shù)組里,然后挨個(gè)創(chuàng)建script標(biāo)簽,src設(shè)置為路徑,添加到 head中,監(jiān)聽(tīng)它們的完成事件。在這個(gè)完成時(shí)間里,我們要做這么一些事情:在fileMap里記錄當(dāng)前js文件的路徑,防止以后重復(fù)加載,檢查列表中所 有文件,看看是否全部加載完了,如果全加載好了,就執(zhí)行回調(diào)。
require: function (pathArr, callback) { for (var i = 0; i < pathArr.length; i++) { var path = pathArr[i]; if (!fileMap[path]) { var head = document.getElementsByTagName('head')[0]; var node = document.createElement('script'); node.type = 'text/javascript'; node.async = 'true'; node.src = path + '.js'; node.onload = function () { fileMap[path] = true; head.removeChild(node); checkAllFiles(); }; head.appendChild(node); } } function checkAllFiles() { var allLoaded = true; for (var i = 0; i < pathArr.length; i++) { if (!fileMap[pathArr[i]]) { allLoaded = false; break; } } if (allLoaded) { callback(); } } }
“怎么編寫(xiě)自己的JavaScript框架”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。