日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從零開始編寫自己的JavaScript框架(一)

1. 模塊的定義和加載

為新城等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及新城網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為做網(wǎng)站、網(wǎng)站設(shè)計(jì)、新城網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

1.1 模塊的定義

一個(gè)框架想要能支撐較大的應(yīng)用,首先要考慮怎么做模塊化。有了內(nèi)核和模塊加載系統(tǒng),外圍的模塊就可以一個(gè)一個(gè)增加。不同的JavaScript框架,實(shí)現(xiàn)模塊化方式各有不同,我們來選擇一種比較優(yōu)雅的方式作個(gè)講解。

先問個(gè)問題:我們做模塊系統(tǒng)的目的是什么?如果覺得這個(gè)問題難以回答,可以從反面來考慮:假如不做模塊系統(tǒng),有什么樣的壞處?

我們經(jīng)歷過比較粗放、混亂的前端開發(fā)階段,頁面里充滿了全局變量,全局函數(shù)。那時(shí)候要復(fù)用js文件,就是把某些js函數(shù)放到一個(gè)文件里,然后讓多個(gè)頁面都來引用。

考慮到一個(gè)頁面可以引用多個(gè)這樣的js,這些js互相又不知道別人里面寫了什么,很容易造成命名的沖突,而產(chǎn)生這種沖突的時(shí)候,又沒有哪里能夠提示出來。所以我們要有一種辦法,把作用域比較好地隔開。

JavaScript這種語言比較奇怪,奇怪在哪里呢,它的現(xiàn)有版本里沒package跟class,要是有,我們也沒必要來考慮什么自己做模塊化了。那它是要用什么東西來隔絕作用域呢?

在很多傳統(tǒng)高級語言里,變量作用域的邊界是大括號,在{}里面定義的變量,作用域不會傳到外面去,但我們的JavaScript大人不是這樣的,他的邊界是function。所以我們這段代碼,i仍然能打出值:

 
 
 
  1. for (var i=0; i<5; i++) {
  2.     //do something
  3. }
  4. alert(i);

那么,我們只能選用function做變量的容器,把每個(gè)模塊封裝到一個(gè)function里?,F(xiàn)在問題又來了,這個(gè)function本身的作用域是全局的,怎么辦?我們想不到辦法,拔劍四顧心茫然。

我們有沒有什么可參照的東西呢?這時(shí)候,腦海中一群語言飄過: C語言飄過:“我不是面向?qū)ο笳Z言哦~不需要像你這么組織哦~”,“死開!” Java飄過:“我是純面向?qū)ο笳Z言哦,連main都要在類中哦,編譯的時(shí)候通過裝箱清單指定入口哦~”,“死開!” C++飄過:“我也是純面向?qū)ο笳Z言哦”,等等,C++是純面向?qū)ο蟮恼Z言嗎?你的main是什么???main是特例,不在任何類中!

啊,我們發(fā)現(xiàn)了什么,既然無法避免全局的作用域,那與其讓100個(gè)function都全局,不如只讓一個(gè)來全局,其他的都由它管理。

本來我們打算自己當(dāng)上帝的,現(xiàn)在只好改行先當(dāng)個(gè)工商局長。你想開店嗎?先來注冊,不然封殺你!于是良民們紛紛來注冊。店名叫什么,從哪進(jìn)貨,賣什么的,一一登記在案,為了方便下面的討論,我們連進(jìn)貨的過程都讓工商局管理起來。

店名,指的就是這里的模塊名,從哪里進(jìn)貨,代表它依賴什么其他模塊,賣什么,表示它對外提供一些什么特性。

好了,考慮到我們的這個(gè)注冊管理機(jī)構(gòu)是個(gè)全局作用域,我們還得把它掛在window上作為屬性,然后再用一個(gè)function隔離出來,要不然,別人也定義一個(gè)同名的,就把我們覆蓋掉了。

 
 
 
  1. (function() {
  2.     window.thin = {
  3.         define: function(name, dependencies, factory) {
  4.             //register a module
  5.         }
  6.     };
  7. })();

在這個(gè)module方法內(nèi)部,應(yīng)當(dāng)怎么去實(shí)現(xiàn)呢?我們的module應(yīng)當(dāng)有一個(gè)地方存儲,但存儲是要在工商局內(nèi)部的,不是隨便什么人都可以看到的,所以,這個(gè)存儲結(jié)構(gòu)也放在工商局同樣的作用域里。

用什么結(jié)構(gòu)去存儲呢?工商局備案的時(shí)候,店名不能跟已有的重復(fù),所以我們發(fā)現(xiàn)這是用hash的很好場景,考慮到JavaScript語言層面沒有hash,我們弄個(gè)Object來存。

 
 
 
  1. (function() {
  2.     var moduleMap = {};
  3.     window.thin = {
  4.         define: function(name, dependencies, factory) {
  5.             if (!moduleMap[name]) {
  6.                 var module = {
  7.                     name: name,
  8.                     dependencies: dependencies,
  9.                     factory: factory
  10.                 };
  11.                 moduleMap[name] = module;
  12.             }
  13.             return moduleMap[name];
  14.         }
  15.     };
  16. })();

現(xiàn)在,模塊的存儲結(jié)構(gòu)就搞好了。

1.2 模塊的使用

存的部分搞好了,我們來看看怎么取。現(xiàn)在來了一個(gè)商家,賣木器的,他需要從一個(gè)賣釘子的那邊進(jìn)貨,賣釘子的已經(jīng)來注冊過了,現(xiàn)在要讓這個(gè)木器廠能買 到釘子?,F(xiàn)在的問題是,兩個(gè)商家處于不同的作用域,也就是說,它們互相不可見,那通過什么方式,我們才能讓他們產(chǎn)生調(diào)用關(guān)系呢?

個(gè)人解決不了的問題還是得靠政府,有困難要堅(jiān)決克服,沒有困難就制造困難來克服?,F(xiàn)在困難有了,該克服了。商家說,我能不能給你我的進(jìn)貨名單,你幫我查一下它們在哪家店,然后告訴我?這么簡單的要求當(dāng)然一口答應(yīng)下來,但是采用什么方式傳遞給你呢?這可犯難了。

我們參考AngularJS框架,寫了一個(gè)類似的代碼:

 
 
 
  1. thin.define("A", [], function() {
  2.     //module A
  3. });
  4. thin.define("B", ["A"], function(A) {
  5.     //module B
  6.     var a = new A();
  7. });

看這段代碼特別在哪里呢?模塊A的定義,毫無特別之處,主要看模塊B。它在依賴關(guān)系里寫了一個(gè)字符串的A,然后在工廠方法的形參寫了一個(gè)真真切切的A類 型。嗯?這個(gè)有些奇怪啊,你的A類型要怎么傳遞過來呢?其實(shí)是很簡單的,因?yàn)槲覀兟暶髁艘蕾図?xiàng)的數(shù)組,所以可以從依賴項(xiàng),挨個(gè)得到對應(yīng)的工廠方法,然后創(chuàng) 建實(shí)例,傳進(jìn)來。

 
 
 
  1. use: function(name) {
  2.     var module = moduleMap[name];
  3.     if (!module.entity) {
  4.         var args = [];
  5.         for (var i=0; i
  6.             if (moduleMap[module.dependencies[i]].entity) {
  7.                 args.push(moduleMap[module.dependencies[i]].entity);
  8.             }
  9.             else {
  10.                 args.push(this.use(module.dependencies[i]));
  11.             }
  12.         }
  13.         module.entity = module.factory.apply(noop, args);
  14.     }
  15.     return module.entity;
  16. }

#p#

我們可以看到,這里面遞歸獲取了依賴項(xiàng),然后當(dāng)作參數(shù),用這個(gè)模塊的工廠方法來實(shí)例化了一下。這里我們多做了一個(gè)判斷,如果模塊工廠已經(jīng)執(zhí)行過,就緩存在entity屬性上,不需要每次都創(chuàng)建。以此類推,假如一個(gè)模塊有多個(gè)依賴項(xiàng),也可以用類似的方式寫,毫無壓力:

 
 
 
  1. thin.define("D", ["A", "B", "C"], function(A, B, C) {
  2.     //module D
  3.     var a = new A();
  4.     var b = new B();
  5.     var c = new C();
  6. });

注意了,D模塊的工廠,實(shí)參的名稱未必就要是跟依賴項(xiàng)一致,比如,以后我們代碼較多,可以給依賴項(xiàng)和模塊名稱加命名空間,可能變成這樣:

 
 
 
  1. thin.define("foo.D", ["foo.A", "foo.B", "foo.C"], function(A, B, C) {
  2.     //module D
  3.     var a = new A();
  4.     var b = new B();
  5.     var c = new C();
  6. });

這段代碼仍然可以正常運(yùn)行。我們來做另外一個(gè)測試,改變形參的順序:

 
 
 
  1. thin.define("A", [], function() {
  2.     return "a";
  3. });
  4. thin.define("B", [], function() {
  5.     return "b";
  6. });
  7. thin.define("C", [], function() {
  8.     return "c";
  9. });
  10. thin.define("D", ["A", "B", "C"], function(B, A, C) {
  11.     return B + A + C;
  12. });
  13. var D = thin.use("D");
  14. alert(D);

試試看,我們的D打出什么結(jié)果呢?結(jié)果是"abc",所以說,模塊工廠的實(shí)參只跟依賴項(xiàng)的定義有關(guān),跟形參的順序無關(guān)。我們看到,在AngularJS里面,并非如此,實(shí)參的順序是跟形參一致的,這是怎么做到的呢?

我們先離開代碼,思考這么一個(gè)問題:如何得知函數(shù)的形參名數(shù)組?對,我們是可以用func.length得到形參個(gè)數(shù),但無法得到每個(gè)形參的變量名,那怎么辦呢?

AngularJS使用了一種比較極端的辦法,分析了函數(shù)的字面量。眾所周知,在JavaScript中,任何對象都隱含了toString方法, 對于一個(gè)函數(shù)來說,它的toString就是自己的實(shí)現(xiàn)代碼,包含函數(shù)簽名和注釋。下面我貼一下AngularJS里面的這部分代碼:

 
 
 
  1. var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  2. var FN_ARG_SPLIT = /,/;
  3. var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
  4. var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  5. function annotate(fn) {
  6.   var $inject,
  7.       fnText,
  8.       argDecl,
  9.       last;
  10.   if (typeof fn == 'function') {
  11.     if (!($inject = fn.$inject)) {
  12.       $inject = [];
  13.       fnText = fn.toString().replace(STRIP_COMMENTS, '');
  14.       argDecl = fnText.match(FN_ARGS);
  15.       forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
  16.         arg.replace(FN_ARG, function(all, underscore, name){
  17.           $inject.push(name);
  18.         });
  19.       });
  20.       fn.$inject = $inject;
  21.     }
  22.   } else if (isArray(fn)) {
  23.     last = fn.length - 1;
  24.     assertArgFn(fn[last], 'fn');
  25.     $inject = fn.slice(0, last);
  26.   } else {
  27.     assertArgFn(fn, 'fn', true);
  28.   }
  29.   return $inject;
  30. }

可以看到,這個(gè)代碼也不長,重點(diǎn)是類型為function的那段,首先去除了注釋,然后獲取了形參列表字符串,這段正則能獲取到兩個(gè)結(jié)果,***個(gè)是 全函數(shù)的實(shí)現(xiàn),第二個(gè)才是真正的形參列表,取第二個(gè)出來split,就得到了形參的字符串列表了,然后按照這個(gè)順序再去加載依賴模塊,就可以讓形參列表不 對應(yīng)于依賴項(xiàng)數(shù)組了。

AngularJS的這段代碼很強(qiáng)大,但是要損耗一些性能,考慮到我們的框架首要原則是簡單,甚至可以為此犧牲一些靈活性,我們不做這么復(fù)雜的事情了。

1.3 模塊的加載

到目前為止,我們可以把多個(gè)模塊都定義在一個(gè)文件中,然后手動(dòng)引入這個(gè)js文件,但是如果一個(gè)頁面要引用很多個(gè)模塊,引入工作就變得比較麻煩,比如 說,單頁應(yīng)用程序(SPA)一般比較復(fù)雜,往往包含數(shù)以萬計(jì)行數(shù)的js代碼,這些代碼至少分布在幾十個(gè)甚至成百上千的模塊中,如果我們也在主界面就加載它 們,載入時(shí)間會非常難以接受。但我們可以這樣看:主界面加載的時(shí)候,并不是用到了所有這些功能,能否先加載那些必須的,而把剩下的放在需要用的時(shí)候再去加 載?

所以我們可以考慮***的AJAX,從服務(wù)端獲取一個(gè)js的內(nèi)容,然后……,怎么辦,你當(dāng)然說不能eval了,因?yàn)閾?jù)說eval很evil啦,但是它 evil在哪里呢?主要是破壞全局作用域啦,怎么怎么,但是如果這些文件里面都是按照我們規(guī)定的模塊格式寫,好像也沒有什么在全局作用域的……,好吧。

算了,我們還是用最簡單的方式了,就是動(dòng)態(tài)創(chuàng)建script標(biāo)簽,然后設(shè)置src,添加到document.head里,然后監(jiān)聽它們的完成事件, 做后續(xù)操作。真的很簡單,因?yàn)槲覀兊目蚣懿恍枰紤]那么多種情況,不需要AMD,不需要require那么麻煩,用這框架的人必須按照這里的原則寫。

所以,說真的我們這里沒那么復(fù)雜啦,要是你們想看更詳細(xì)原理的不如去看這個(gè),解釋得比我好哎:http://coolshell.cn/articles/9749.html#jtss-tsina

我也偷懶了,只是貼一下代碼,順便解釋一下,界面把所依賴的js文件路徑放在數(shù)組里,然后挨個(gè)創(chuàng)建script標(biāo)簽,src設(shè)置為路徑,添加到 head中,監(jiān)聽它們的完成事件。在這個(gè)完成時(shí)間里,我們要做這么一些事情:在fileMap里記錄當(dāng)前js文件的路徑,防止以后重復(fù)加載,檢查列表中所 有文件,看看是否全部加載完了,如果全加載好了,就執(zhí)行回調(diào)。

#p#

 
 
 
  1. require: function (pathArr, callback) {
  2.     for (var i = 0; i < pathArr.length; i++) {
  3.         var path = pathArr[i];
  4.         if (!fileMap[path]) {
  5.             var head = document.getElementsByTagName('head')[0];
  6.             var node = document.createElement('script');
  7.             node.type = 'text/javascript';
  8.             node.async = 'true';
  9.             node.src = path + '.js';
  10.             node.onload = function () {
  11.                 fileMap[path] = true;
  12.                 head.removeChild(node);
  13.                 checkAllFiles();
  14.             };
  15.             head.appendChild(node);
  16.         }
  17.     }
  18.     function checkAllFiles() {
  19.         var allLoaded = true;
  20.         for (var i = 0; i < pathArr.length; i++) {
  21.             if (!fileMap[pathArr[i]]) {
  22.                 allLoaded = false;
  23.                 break;
  24.             }
  25.         }
  26.         if (allLoaded) {
  27.             callback();
  28.         }
  29.     }
  30. }

1.4 小結(jié)

到此為止,我們的簡易框架的模塊定義系統(tǒng)就完成了。完整的代碼如下:

 
 
 
  1. (function () {
  2.     var moduleMap = {};
  3.     var fileMap = {};
  4.     var noop = function () {
  5.     };
  6.     var thin = {
  7.         define: function(name, dependencies, factory) {
  8.             if (!moduleMap[name]) {
  9.                 var module = {
  10.                     name: name,
  11.                     dependencies: dependencies,
  12.                     factory: factory
  13.                 };
  14.                 moduleMap[name] = module;
  15.             }
  16.             return moduleMap[name];
  17.         },
  18.         use: function(name) {
  19.             var module = moduleMap[name];
  20.             if (!module.entity) {
  21.                 var args = [];
  22.                 for (var i=0; i
  23.                     if (moduleMap[module.dependencies[i]].entity) {
  24.                         args.push(moduleMap[module.dependencies[i]].entity);
  25.                     }
  26.                     else {
  27.                         args.push(this.use(module.dependencies[i]));
  28.                     }
  29.                 }
  30.                 module.entity = module.factory.apply(noop, args);
  31.             }
  32.             return module.entity;
  33.         },
  34.         require: function (pathArr, callback) {
  35.             for (var i = 0; i < pathArr.length; i++) {
  36.                 var path = pathArr[i];
  37.                 if (!fileMap[path]) {
  38.                     var head = document.getElementsByTagName('head')[0];
  39.                     var node = document.createElement('script');
  40.                     node.type = 'text/javascript';
  41.                     node.async = 'true';
  42.                     node.src = path + '.js';
  43.                     node.onload = function () {
  44.                         fileMap[path] = true;
  45.                         head.removeChild(node);
  46.                         checkAllFiles();
  47.                     };
  48.                     head.appendChild(node);
  49.                 }
  50.             }
  51.             function checkAllFiles() {
  52.                 var allLoaded = true;
  53.                 for (var i = 0; i < pathArr.length; i++) {
  54.                     if (!fileMap[pathArr[i]]) {
  55.                         allLoaded = false;
  56.                         break;
  57.                     }
  58.                 }
  59.                 if (allLoaded) {
  60.                     callback();
  61.                 }
  62.             }
  63.         }
  64.     };
  65.     window.thin = thin;
  66. })();

測試代碼如下:

 
 
 
  1. thin.define("constant.PI", [], function() {
  2.     return 3.14159;
  3. });
  4. thin.define("shape.Circle", ["constant.PI"], function(pi) {
  5.     var Circle = function(r) {
  6.         this.r = r;
  7.     };
  8.     Circle.prototype = {
  9.         area : function() {
  10.             return pi * this.r * this.r;
  11.         }
  12.     }
  13.     return Circle;
  14. });
  15. thin.define("shape.Rectangle", [], function() {
  16.     var Rectangle = function(l, w) {
  17.         this.l = l;
  18.         this.w = w;
  19.     };
  20.     Rectangle.prototype = {
  21.         area: function() {
  22.             return this.l * this.w;
  23.         }
  24.     };
  25.     return Rectangle;
  26. });
  27. thin.define("ShapeTypes", ["shape.Circle", "shape.Rectangle"], function(Circle, Rectangle) {
  28.     return {
  29.         CIRCLE: Circle,
  30.         RECTANGLE: Rectangle
  31.     };
  32. });
  33. thin.define("ShapeFactory", ["ShapeTypes"], function(ShapeTypes) {
  34.     return {
  35.         getShape: function(type) {
  36.             var shape;
  37.             switch (type) {
  38.                 case "CIRCLE": {
  39.                     shape = new ShapeTypes[type](arguments[1]);
  40.                     break;
  41.                 }
  42.                 case "RECTANGLE":  {
  43.                     shape = new ShapeTypes[type](arguments[1], arguments[2]);
  44.                     break;
  45.                 }
  46.             }
  47.             return shape;
  48.         }
  49.     };
  50. });
  51. var ShapeFactory = thin.use("ShapeFactory");
  52. alert(ShapeFactory.getShape("CIRCLE", 5).area());
  53. alert(ShapeFactory.getShape("RECTANGLE", 3, 4).area());

在這個(gè)例子里定義了四個(gè)模塊,每個(gè)模塊只需要定義自己所直接依賴的模塊,其他的可以不必定義。也可以來這里看測試鏈接:http://xufei.github.io/thin/demo/demo.0.1.html


分享文章:從零開始編寫自己的JavaScript框架(一)
鏈接地址:http://www.dlmjj.cn/article/copsjhj.html