前端基礎(chǔ)框架的思考和嘗試
近日我一直在思考類似的關(guān)于js模塊和文件管理的方式。正好團(tuán)隊(duì)里也正有這樣的需求,于是,經(jīng)歷了好幾天的苦思冥想,稍微做了些嘗試。下面會(huì)細(xì)細(xì)道來。
【js模塊和文件的管理】
基于這個(gè)title,前提是我們已經(jīng)明確了我們有了一個(gè)組件或者js methods 的lib,我們暫且把它叫做庫,庫里面存儲(chǔ)了很多我們常用的東西,比如js插件,封裝好的methods
以及其他的一些lib組件。為了更好的管理我們這些顆?;膉s文件,我們的庫通常都是呈顆粒化的?;谶@種情況,我們可以說一個(gè)js文件就對應(yīng)一個(gè)模塊module,他有他相對獨(dú)立的功能。這種管理模式是目前大多數(shù)主流框架的文件和模塊管理模式,如YUI,EXT等,這樣的好處是,可以按需調(diào)用。并且調(diào)用的模塊一目了然。但是這樣也有一個(gè)弊端,就是如果一個(gè)頁面需要多個(gè)模塊的支持,那么自然就需要加載對應(yīng)的多個(gè)模塊的js文件,http連接數(shù)自然會(huì)增加。這對網(wǎng)站的性能來說當(dāng)然是不好的。所以,YUI等成熟的框架自然不會(huì)遺漏這個(gè)問題,他們也有一套自己注冊和管理模塊的機(jī)制(可以參考YUI的register和loader模塊)
當(dāng)然,jQuery憑借他易用的api風(fēng)格和強(qiáng)大的選擇器也贏得了很大的市場,但是我們通常喜歡把jQuery叫做一個(gè)方法庫,而不是框架的原因是它相對于其他框架而言的話,對模塊和文件的管理就稍遜一籌。雖然他后來的新版本也提供了自己的模塊管理機(jī)制...
但是,這并不存在誰對誰錯(cuò),誰好誰壞的問題,只是各自的側(cè)重點(diǎn)不同而已。建站者選擇誰只是看誰更適合自己而已。有些企業(yè)覺得YUI的架構(gòu)模式更適合自己,于是選擇了跟他相似的模式,于是有了百度的Tangram,淘寶的kissy,有的企業(yè)覺得jQuery更適合現(xiàn)在的自己,于是選擇的jQuery,比如豆瓣,于是也有了克軍的輕量級前端框架Do。我相信每個(gè)團(tuán)隊(duì)能夠出一套自己的框架或者庫都是不容易的,都是需要時(shí)間積累的,所以我從不輕易地評論別人的成果。
【主流的思路】
由于不是簡單的把頁面上加載的<script>轉(zhuǎn)變成動(dòng)態(tài)scriptNode添加,所以需要考慮的問題其實(shí)并不少。
比如我們要加載一個(gè)新模塊a,對應(yīng)的顆?;募閍.js,那么我們大概可以表示為
這當(dāng)然是最簡單的一種情況,復(fù)雜的情況通常都發(fā)生在存在模塊依賴的時(shí)候,假如我們引用了jQuery,然后用jq寫了一個(gè)插件a,那么我們加載順序就得是:
如果模塊依賴進(jìn)一步復(fù)雜,比如:
- a : require [b, c] //a 模塊需要b,c模塊
- b : require [c, d] //b模塊需要c, d模塊
- c : require [f] //c模塊需要f模塊
- d : no-require
- f : no-require
那么我們的加載順序應(yīng)該是怎么樣的呢,就應(yīng)該是:
或者: d.js --> f.js --> c.js --> b.js --> a.js
有人會(huì)想會(huì)不會(huì)存在循環(huán)依賴的問題,比如 a依賴b, b依賴a,這在理論上是不可能的,因?yàn)閖s是順序執(zhí)行的,如果出現(xiàn)循環(huán)依賴的情況的話就永遠(yuǎn)不知道誰在前誰在后...
基于這種思路,我們需要在調(diào)用的時(shí)候告知程序,a模塊需要b模塊和c模塊,同時(shí)b模塊又需要c和d模塊,如果想復(fù)雜一點(diǎn),這種循環(huán)的遞歸和排序以及判斷重復(fù)加載的問題就很頭疼。
另:由于js是單線程的,一個(gè)模塊的加載是需要時(shí)間的,而且沒有編譯過程,只有在運(yùn)行到加載函數(shù)的時(shí)候再去找它所依賴的代碼片段,有可能新模塊還沒加載完全,js仍在繼續(xù)執(zhí)行調(diào)用新模塊中的方法,就會(huì)報(bào)錯(cuò)。解決的辦法由兩個(gè),一個(gè)是創(chuàng)建一個(gè)阻塞的環(huán)境,讓每次的加載函數(shù)的執(zhí)行時(shí)間和加載時(shí)間一樣長,不過如果這樣的話其實(shí)就和在head里直接引用js文件的方式一樣了。當(dāng)所需加載模塊過多時(shí),性能永遠(yuǎn)是個(gè)大問題。
另外一個(gè)方法就是創(chuàng)建一個(gè)非阻塞的環(huán)境,通過回調(diào)函數(shù)來執(zhí)行接下來的代碼,這樣在加載多模塊時(shí)對性能有很大提升,就是函數(shù)體中的代碼邏輯顯得稍微亂一點(diǎn)??梢钥吹?,YUI就是用的這種方式,另外豆瓣的Do也是這種方式。畢竟,性能為王。
【script的插入方式】
1.動(dòng)態(tài)創(chuàng)建scriptNode,設(shè)置其src,插入到head中;
2.xmlHttp同步載入js,解析請求回來的reponseText,一是存在兼容性問題,還有跨域問題,以及對客戶端網(wǎng)絡(luò)高依賴的問題。
綜上,相比較而言,采用的模式為***種。
【目的】
1.讓js模塊和html文檔分離開來,讓程序自動(dòng)去探尋模塊依賴的層級順序,而不是編碼者。
2.讓js模塊的調(diào)用和管理盡可能的容易,各取所需。
3.創(chuàng)建非阻塞的loading模式,提高頁面性能。
【使用】
由于篇幅所限,具體代碼說明我就不細(xì)說了,這里放出源碼,有興趣的朋友可以看看:
- /**
- * Author : hongru.chen
- * version : 0.1
- * name : Module_v0.1.js
- **/
- (function () {
- if (typeof JS === 'undefined') arguments[0].JS = this.JS = {
- scriptQueue:[] //用來加載的隊(duì)列
- };
- var extend = function (destination, source, override) {
- if (!override) override = true;
- for (var property in source) {
- if (override && !(property in destination)) destination[property] = source[property];
- }
- return destination;
- };
- var createScriptNode = function(url) {
- var script = document.createElement("script");
- script.type = "text/javascript";
- script.charset = "utf-8";
- script.src = url;
- return script;
- };
- var head = function () {
- return document.getElementsByTagName("head")[0]
- }();
- //遞歸遍歷依賴模塊,輸出loading隊(duì)列
- var _queue = function (name) {
- !JS._regedModules[name]['inQueue'] && JS.scriptQueue.push(name);
- JS._regedModules[name]['inQueue'] = true;
- if (JS._regedModules[name]['require']) {
- var reqL = JS._regedModules[name]['require'];
- for (var i=0; i < reqL.length; i++) {
- if (JS._regedModules[reqL[i]]['inQueue']) return JS.scriptQueue;
- JS._regedModules[reqL[i]]['inQueue'] = true;
- JS.scriptQueue.push(reqL[i]);
- arguments.callee(reqL[i]);
- }
- } else {
- return JS.scriptQueue;
- }
- };
- // 添加入模塊隊(duì)列
- JS.add = function (name, options, r) {
- return new JS.add.prototype.register(name, options, r);
- }
- JS.add.prototype.register = function (name, options, r) {
- if (!JS._regedModules) JS._regedModules = {};
- if (JS._regedModules[name]) throw new Error('warning: module "'+name+'" is already added!');
- JS._regedModules[name] = {};
- if (typeof options == 'object') {
- extend(JS._regedModules[name], options)
- }
- else {
- var arg = arguments;
- JS._regedModules[name]['url'] = arg[1];
- if (!!arg[2]) JS._regedModules[name]['require'] = arg[2];
- if (!!arg[3]) {
- for (var i=3; i<arg.length; i++) {
- JS._regedModules[name][arg[i]] = arg[i];
- }
- }
- }
- this.add = function (n, p, r) {
- return JS.add(n, p, r);
- }
- return this;
- }
- // 加載模塊
- JS.use = function (n, callback, logId) {
- var _q = _queue(n) || this.scriptQueue;
- this.load(_q, callback, logId);
- }
- JS.load = function (arr, callback, logId) {
- var i = 0;
- if (arr.length === 0) {
- !!callback && callback();
- return;
- }
- var curModule = arr.pop();
- if (!!logId && typeof logId == 'string') {
- var log = document.createElement('p');
- log.innerHTML = 'module "' + curModule + '" is loaded';
- document.getElementById(logId).appendChild(log);
- }
- __load(curModule, function () {
- JS.load(arr, callback, logId);
- });
- }
- __load = function (name, cb) {
- if (JS._regedModules[name]['isLoaded']) return;
- var path = JS._regedModules[name]['url'];
- var _s = createScriptNode(path);
- head.appendChild(_s);
- _s.onload = _s.onreadystatechange = function () {
- if ((!this.readyState) || this.readyState === "loaded" || this.readyState === "complete" ) {
- JS._regedModules[name]['isLoaded'] = true;
- !!cb && cb();
- _s.onload = _s.onreadystatechange = null;
- }
- }
- _s.onerror = function () {
- _s.onload = _s.onerror = undefined;
- throw new Error (_s.src + ' loaded failed!');
- head.removeChild(_s);
- }
- }
- })(window);
引入了這樣的模塊管理機(jī)制后,所有的頁面都只需要加載一個(gè)文件,就是上面的Module_v0.1.js;里面提供了一個(gè)命名空間JS,提供了兩個(gè)主要的方法JS.add和JS.use.你可以這樣使用它:
- <html>
- <head>
- <script type="text/javascript" src="Module_v.01.js"></script>
- <script type="text/javascript">
- JS.add(...)
- JS.use(...)
- </script>
- </head>
- <body>...</body>
- </html>
具體的使用方式可以如下:
- JS.add('a', {
- url : 'http://www.cnblogs.com/1293183133/js/***.js',
- require : ['b','c']
- })
- JS.add('b', {
- url : 'http://www.cnblogs.com/1293183133/js/***.js'
- })
- JS.add('c', {
- url : 'http://www.cnblogs.com/1293183133/js/***.js'
- })
- JS.use('a', function () {
- //you code
- })
如果你覺得這樣的調(diào)用方式太麻煩,可以寫成方法鏈?zhǔn)降恼{(diào)用: add的***個(gè)參數(shù)是自定義的模塊名,第二個(gè)參數(shù)可以是對象{},里面包含,對應(yīng)模塊的js文件的url,和此模塊的依賴模塊,如果沒有就不寫。
第二個(gè)參數(shù)也可以不是對象,比如上面例子里后面幾個(gè)add,就直接是***個(gè)為模塊名,第二個(gè)為對應(yīng)模塊的js文件url,第三個(gè)參數(shù)可選,為所依賴的模塊名,是一個(gè)Array.
use 的***個(gè)參數(shù)為調(diào)用模塊名,第二個(gè)參數(shù)為回調(diào)函數(shù)。
【關(guān)注的幾個(gè)問題】
1。模塊會(huì)不會(huì)重復(fù)加載?
-- 不會(huì),當(dāng)判斷此模塊之前已加載過,就會(huì)直接執(zhí)行回調(diào)。不會(huì)重復(fù)加載。
2。add的模塊有重名時(shí)怎么處理?
--如果add的模塊有重名,理論上是不允許,如果發(fā)現(xiàn),會(huì)報(bào)錯(cuò)提醒。
3。add模塊的時(shí)候需不需要考慮add的順序?
--不需要,會(huì)自動(dòng)甄別所依賴的模塊,按依賴優(yōu)先級載入。如果不放心,還提供了一個(gè)log日志功能,監(jiān)測是否按正確順序載入。只需在use方法調(diào)用的時(shí)候,寫上第三個(gè)參數(shù),如:
- <div id="log"></div>
- <script>
- JS.use('a', function () {
- //todo
- },'log')
- </script>
第三個(gè)參數(shù)為所要顯示載入信息的dom元素id。結(jié)果會(huì)如下顯示:
4。這樣的方式對性能到底有沒有好處?
--答案,有的,下面上普通的阻塞和非阻塞方式載入jQuery源碼的對比圖:
直接頁面上載入js的方式
- <script type="text/javascript" src="http://common.cnblogs.com/script/jquery.js?99"></script>
***一條長長的載入時(shí)間就是jq的載入時(shí)間,故用戶看到完整頁面的時(shí)間為整個(gè)文件全部載入完成后的時(shí)間,大概為500ms左右(這單單是一個(gè)空文檔載入jquery文件的時(shí)間)。
以非阻塞方式載入時(shí):
- <script type="text/javascript">
- JS.add('jq', 'http://common.cnblogs.com/script/jquery.js?111');
- JS.use('jq', function () {
- alert($)
- })
- </script>
為了保證每次載入不會(huì)從緩存中讀取,我加了個(gè)版本號?111;可以發(fā)現(xiàn),上面的藍(lán)線為用戶感知到的頁面download時(shí)間,在20ms左右,而jq文件的加載在藍(lán)線后面,所以說這部分時(shí)間是用戶感知不到的。對用戶體驗(yàn)的提升應(yīng)該是大有幫助的。就是給服務(wù)器增加了并發(fā)連接數(shù)。
好的,文章大概到這里,可能有人會(huì)說,基本上同樣的事情Do.js,using.js,require.js等小眾型框架已經(jīng)都有了 ,為什么要自己再寫?恩...別人的東西始終是別人的,自己做過的東西才真正是自己的。
我當(dāng)然不敢說比別的大牛們考慮的更周到,效率更高,但是希望能在自己的編碼中得到一點(diǎn)提高吧。
【編輯推薦】
- 前端之王能否續(xù)寫輝煌 JavaScript服務(wù)器端開發(fā)現(xiàn)狀
- Web開發(fā)有多難?前端后端都很煩
- 2010 Web前端技術(shù)趨勢及總結(jié) Facebook摘全明星MVP
- 基礎(chǔ)知識:UPS電源系統(tǒng)防雷措施概述
- 亡羊補(bǔ)牢行不通 基礎(chǔ)設(shè)施安全要與私有云建設(shè)動(dòng)態(tài)適應(yīng)
- 分布式Java應(yīng)用:基礎(chǔ)與實(shí)踐
- PHP框架發(fā)展存四誤區(qū) 死穴不除難成大器
- 小試一下微軟開發(fā)框架LightSwitch
- 私有云部署實(shí)戰(zhàn):制定框架并建立配置數(shù)據(jù)庫