溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么編寫自己的JavaScript框架

發布時間:2021-11-17 16:45:48 來源:億速云 閱讀:196 作者:iii 欄目:web開發

本篇內容介紹了“怎么編寫自己的JavaScript框架”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1. 模塊的定義和加載

1.1 模塊的定義

一個框架想要能支撐較大的應用,首先要考慮怎么做模塊化。有了內核和模塊加載系統,外圍的模塊就可以一個一個增加。不同的JavaScript框架,實現模塊化方式各有不同,我們來選擇一種比較優雅的方式作個講解。

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

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

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

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

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

for (var i=0; i<5; i++) {     //do something } alert(i);

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

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

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

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

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

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

(function() {     window.thin = {         define: function(name, dependencies, factory) {             //register a module         }     }; })();

在這個module方法內部,應當怎么去實現呢?我們的module應當有一個地方存儲,但存儲是要在工商局內部的,不是隨便什么人都可以看到的,所以,這個存儲結構也放在工商局同樣的作用域里。

用什么結構去存儲呢?工商局備案的時候,店名不能跟已有的重復,所以我們發現這是用hash的很好場景,考慮到JavaScript語言層面沒有hash,我們弄個Object來存。

(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];         }     }; })();

現在,模塊的存儲結構就搞好了。

1.2 模塊的使用

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

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

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

thin.define("A", [], function() {     //module A });  thin.define("B", ["A"], function(A) {     //module B     var a = new A(); });

看這段代碼特別在哪里呢?模塊A的定義,毫無特別之處,主要看模塊B。它在依賴關系里寫了一個字符串的A,然后在工廠方法的形參寫了一個真真切切的A類 型。嗯?這個有些奇怪啊,你的A類型要怎么傳遞過來呢?其實是很簡單的,因為我們聲明了依賴項的數組,所以可以從依賴項,挨個得到對應的工廠方法,然后創 建實例,傳進來。

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; }

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

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模塊的工廠,實參的名稱未必就要是跟依賴項一致,比如,以后我們代碼較多,可以給依賴項和模塊名稱加命名空間,可能變成這樣:

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(); });

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

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打出什么結果呢?結果是"abc",所以說,模塊工廠的實參只跟依賴項的定義有關,跟形參的順序無關。我們看到,在AngularJS里面,并非如此,實參的順序是跟形參一致的,這是怎么做到的呢?

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

AngularJS使用了一種比較極端的辦法,分析了函數的字面量。眾所周知,在JavaScript中,任何對象都隱含了toString方法, 對于一個函數來說,它的toString就是自己的實現代碼,包含函數簽名和注釋。下面我貼一下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; }

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

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

1.3 模塊的加載

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

所以我們可以考慮***的AJAX,從服務端獲取一個js的內容,然后&hellip;&hellip;,怎么辦,你當然說不能eval了,因為據說eval很evil啦,但是它 evil在哪里呢?主要是破壞全局作用域啦,怎么怎么,但是如果這些文件里面都是按照我們規定的模塊格式寫,好像也沒有什么在全局作用域的&hellip;&hellip;,好吧。

算了,我們還是用最簡單的方式了,就是動態創建script標簽,然后設置src,添加到document.head里,然后監聽它們的完成事件, 做后續操作。真的很簡單,因為我們的框架不需要考慮那么多種情況,不需要AMD,不需要require那么麻煩,用這框架的人必須按照這里的原則寫。

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

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();         }     } }

“怎么編寫自己的JavaScript框架”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女