題外:javascript
進行web開發3年多了,javascript(後稱js)用的也比較多,可是大部分都侷限於函數的層次,有些公共的js函數可重用性很差,形成了程序的大量冗餘,可讀性差(雖然一直保留着註釋的習慣,可是最後發現註釋遠遠不夠),影響了頁面的加載速度和性能。去年開始着手對既有前端腳本進行重構和優化,查閱了不少技術大牛分享的資料,也比較系統的閱讀了一遍《javascript權威指南》,js模塊化編程深深的吸引了我,它改變了我編寫js腳本程序的方式,同時也讓代碼的可讀性和可維護性進一步加強。html
下邊就根據本身學習和實踐過程當中對js模塊化編程的理解,分享一下個人經歷,但願對您有所幫助:前端
你們都知道,js中的變量(variable)有其做用範圍,好比:函數裏用var定義的變量在函數外是看不到的,而定義在函數外面的變量(不能有沒有var修飾)均是全局變量,在js程序的任何位置均可以訪問。嗯,實際上咱們在工做過程當中,業務邏輯比較多,而一個業務邏輯包含多個函數,函數之間共享使用某個變量,這樣問題就來了,若是另一個業務邏輯不當心定義了或者修改了這個變量,就會形成這個全局變量被污染,前一個業務邏輯就會出現髒讀,過程測試以下:java
一個很長的頁面腳本程序包含兩個子業務處理過程1和2,業務處理程序1須要定義兩個函數和一個變量,一個函數設置變量,一個函數讀取輸出變量,以下:web
1 /*****頁面業務邏輯1***begin*****/ 2 3 //定義一個全局變量,供邏輯1中的各函數共享使用 4 var test = 0; 5 function setFlag() { 6 test = 1; 7 } 8 function displayFlag() { 9 console.log(test); 10 } 11 12 /*****頁面業務邏輯1***end*****/
其餘業務處理程序腳本:編程
1 /* 2 * …………………………………… 3 * 中間業務邏輯,篇幅很長 4 * …………………………………… 5 */
業務處理程序2開始,邏輯處理也定義了兩個函數和一個變量,一個函數設置變量,一個函數讀取變量進行其餘處理,不幸的是,這個全局變量採用了同業務邏輯1相同的名字:app
1 /*****頁面業務邏輯2***begin*****/ 2 3 //定義一個全局變量,供邏輯1中的各函數共享使用 4 var test = 0; 5 function setVarable() { 6 test = 1; 7 } 8 function displayV() { 9 console.log(test); 10 } 11 12 /*****頁面業務邏輯2***end*****/
程序過程在進行邏輯2後再進行邏輯1,此時出現了意外:模塊化
1 setVarable(); //邏輯2不當心修改了該值 2 3 displayFlag(); //error:預期輸出1,可是卻髒讀成了2
輸出結果以下:函數
很明顯,實際輸出的結果並非指望的結果,此外還有另一種狀況,若是某個js腳本程序被共享爲一個共用的腳本塊,在多個地方調用(引入)這個腳本塊時,也會很容易出現這個問題。性能
而模塊化編程(Module)的出現就解決了這個問題,除此以外模塊化編程還有其餘幾個特色:
1. 維護一個乾淨前端腳本的變量環境,保護必定做用範圍內定義的全局變量不被範圍外程序的污染;
2. 前端腳本程序的可重用性大大提升,可讀性和可維護性進一步加強;
3. 能夠組合多個module腳本,抽象成一個公共的腳本庫,提升代碼的開發效率;
前面說過,函數內部定義的變量函數外看不到(即不可用),爲了保護變量環境的做用域,這正是咱們須要的結果,故把整個業務處理邏輯扔到一個函數裏實現就能夠實現一個模塊的定義,改寫上面邏輯1和邏輯2的代碼以下:
1 /*****頁面業務邏輯1********/ 2 function HandleOne() { 3 var test = 0; 4 this.setFlag = function() { 5 test = 1; 6 } 7 this.displayFlag = function() { 8 console.log("這是邏輯1中的變量值:" + test); 9 } 10 //返回this對象,以訪問module裏定義的函數 11 return this; 12 } 13 14 /* 15 * …………………………………… 16 * 中間業務邏輯,篇幅很長 17 * …………………………………… 18 */ 19 20 /*****頁面業務邏輯2********/ 21 function HandleTwo() { 22 var test; 23 this.setVarable = function() { 24 test = 2; 25 } 26 this.displayV = function() { 27 console.log("這是邏輯2中的變量值:" + test); 28 } 29 //返回this對象,以訪問module裏定義的函數 30 return this; 31 } 32 33 var H1 = HandleOne(); 34 var H2 = HandleTwo(); 35 36 H2.setVarable(); //邏輯2修改了本身的變量 37 38 H1.displayFlag(); //邏輯1輸出本身的變量 39 40 H2.displayV(); //邏輯2輸出本身的變量
輸出結果以下:
由上圖可知,在模塊化編程下,每一個模塊內部使用的共用變量都很好的被保護起來了,不在收到外面其餘邏輯處理的干擾,可是上述過程須要咱們定義兩個函數模塊,若是咱們不想額外定義任何中間變量,咱們能夠採用匿名函數來從新實現上述過程,代碼改寫以下:
1 /*****頁面業務邏輯1********/ 2 var H1 = (function() { 3 var test = 0; 4 this.setFlag = function() { 5 test = 1; 6 } 7 this.displayFlag = function() { 8 console.log("這是邏輯1中的變量值:" + test); 9 } 10 //返回this對象,以訪問module裏定義的函數 11 return this; 12 } ()); 13 14 /* 15 * …………………………………… 16 * 中間業務邏輯,篇幅很長 17 * …………………………………… 18 */ 19 20 /*****頁面業務邏輯2********/ 21 var H2 = (function() { 22 var test; 23 this.setVarable = function() { 24 test = 2; 25 } 26 this.displayV = function() { 27 console.log("這是邏輯2中的變量值:" + test); 28 } 29 //返回this對象,以訪問module裏定義的函數 30 return this; 31 } ()); 32 33 H2.setVarable(); //邏輯2修改了本身的變量 34 35 H1.displayFlag(); //邏輯1輸出本身的變量 36 37 H2.displayV(); //邏輯2輸出本身的變量
上面的是用匿名函數實現的模塊化封裝,輸出的結果同實體函數時同樣,是否是比實體函數時更加簡潔了?!
注:上述過程當中咱們在每一個模塊中返回了this對象,是由於咱們須要在後續的邏輯中調用該模塊中的函數,若是在實踐過程當中模塊處理程序不須要被外部邏輯調用,而只是在模塊內部輸出結果便可的話,咱們只需返回模塊最終處理的結果值或者不須要返回語句,依據具體狀況具體分析。
經過上述的例子咱們能夠總結出模塊化的通常思路:
1. 把相關聯的一系列函數及變量的定義放在一個函數(匿名函數也行)中便可造成一個模塊,模塊中的變量和函數的做用域僅限於模塊內部,外部沒法直接調用,模塊能夠返回既定邏輯的處理結果。
2. 若是須要在模塊外部提供調用模塊中函數或者變量的接口,則須要將模塊中函數或變量的定義用this標記,而後在模塊最後返回這個this對象(函數中的this對象指的是window對象)。
模塊化的編程思路以下:
1 //實體函數時的模塊化思路 2 function Moudle() { 3 4 var theResult; 5 6 //do something here 7 8 //這一句無關緊要,有則返回最終的處理結果 9 return theResult; 10 } 11 //執行模塊過程,有返回值時能夠接收返回值 12 Moudle(); 13 14 //匿名函數時的模塊化思路 15 var result = (function() { 16 var theResult; 17 18 //do something here 19 20 //這一句無關緊要,有則返回最終的處理結果 21 return theResult; 22 });
另:大部分web開發的後臺語言都採用C#或者java,熟悉這兩種語言的童鞋都知道,它們內部封裝了不少函數庫(包),C#中要用using引入,java中要用import引入,這些庫或者包都是把一系列相關聯的函數、變量、類等對象封裝到一個命名空間中,方便後續調用的更加方便、清晰,javascript也能夠實現這種命名空間式的封裝,拿以前的web版掃雷小遊戲爲例,遊戲中定義了四個類(即四個模塊,模塊總體做爲一個對象,可根據需求擴展更多):PlayBombGame、BombObjectList、BombObject、BombImgObject,咱們能夠把這四個模塊對象封裝到一個叫games.BombGame的命名空間中,代碼以下:
1 //初始化外層命名空間 2 var games; 3 if (!games) games = {}; 4 //初始化web版掃雷遊戲的命名空間(方法一); 5 games.BombGame = {}; 6 games.BombGame.HoleControlClass = PlayBombGame; 7 games.BombGame.BombListClass = BombObjectList; 8 games.BombGame.BombClass = BombObject; 9 games.BombGame.ImageClass = BombImgObject;
調用遊戲接口初始化時以下:
1 var GameObj = new games.BombGame.HoleControlClass("Timer", "BombNum", "ContentSection", "TryAgain", 16, 30, 99); 2 GameObj.play();
固然,子命名空間games.BombGame的初始化還有其餘幾種方法,代碼以下:
1 //初始化web版掃雷遊戲的命名空間(方法二:返回對象列表); 2 games.BombGame = (function namespace() { 3 //能夠用局部變量或者函數作一些其餘的事情 4 5 //返回命名空間中的對象列表 6 return { 7 HoleControlClass:PlayBombGame, 8 BombListClass:BombObjectList, 9 BombClass:BombObject, 10 ImageClass:BombImgObject 11 }; 12 } ()); 13 14 //初始化web版掃雷遊戲的命名空間(方法二:返回類對象); 15 games.BombGame = (new function namespace() { 16 //能夠用局部變量或者函數作一些其餘的事情 17 18 //將對象列表賦值給對象屬性 19 this.HoleControlClass = PlayBombGame; 20 this.BombListClass = BombObjectList; 21 this.BombClass = BombObject; 22 this.ImageClass = BombImgObject; 23 } ()); 24 25 //初始化web版掃雷遊戲的命名空間(方法三:匿名函數封裝賦值過程); 26 games.BombGame = {}; 27 games.BombGame = (new function namespace() { 28 //能夠用局部變量或者函數作一些其餘的事情 29 30 //初始化 31 games.BombGame.HoleControlClass = PlayBombGame; 32 games.BombGame.BombListClass = BombObjectList; 33 games.BombGame.BombClass = BombObject; 34 games.BombGame.ImageClass = BombImgObject; 35 } ());
模塊化編程的舉例(例1,文檔元素指定點插入的通用方法):
1 var Insert = (function() { 2 //判斷insertAdjacentHTML的支持性,若是支持,直接返回對象。
3 if (document.createElement("div").insertAdjacentHTML) { 4 return { 5 before: function(e, h) { e.insertAdjacentHTML("beforebegin", h); }, 6 after: function(e, h) { e.insertAdjacentHTML("afterend", h); }, 7 atStart: function(e, h) { e.insertAdjacentHTML("afterbegin", h); }, 8 atEnd: function(e, h) { e.insertAdjacentHTML("beforeend", h); } 9 }; 10 } 11 //根據要添加的類容建立文檔碎片
12 function fragment(html) { 13
14 var elt = document.createElement("div"); 15 var flag = document.createDocumentFragment(); 16 elt.innerHTML = html; 17
18 while (elt.firstChild) { 19 flag.appendChild(elt.firstChild); 20 } 21 return flag; 22 } 23
24 var Insert = { 25
26 before: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); }, 27 after: function(elt, html) { elt.parentNode.insertBefore(fragment(html), elt); }, 28 atstart: function(elt, html) { elt.insertBefore(fragment(html), elt.firstChild); }, 29 atend: function(elt, html) { elt.appendChild(fragment(html)); } 30 }; 31 //將新的方法綁定到元素的原型鏈中,以便元素對象能夠直接調用插入方法
32 Element.prototype.insertAdjacentHTML = function(pos, html) { 33 switch (pos) { 34 case "beforebegin": return Insert.before(this, html); 35 case "afterend": return Insert.after(this, html); 36 case "afterbegin": return Insert.atstart(this, html); 37 } 38 }; 39 //返回對象
40 return Insert; 41 } ());
(例2,文檔初始化事件的通用封裝):
1 var whenReady = (function() { 2 this.ready = false; 3 this.funcs = []; 4 5 function handle(e) { 6 if (this.ready) { 7 return; 8 } 9 10 if (e.type === "readystatechange" && document.readyState !== "complete") { 11 return; 12 } 13 14 for (var i = 0; i < this.funcs.length; i++) { 15 funcs[i].call(document); 16 } 17 18 this.ready = true; 19 this.funcs = null; 20 } 21 22 if (document.addEventListener) { 23 document.addEventListener("DOMContentLoaded", handle, false); 24 document.addEventListener("readystatechange", handle, false); 25 window.addEventListener("load", handle, false); 26 } 27 else { 28 document.attachEvent("onreadystatechange", handle); 29 window.attachEvent("onload", handle); 30 } 31 32 return function whenReady(f) { 33 if (this.ready) { 34 f.call(document); 35 } 36 else { 37 this.funcs.push(f); 38 } 39 } 40 } ());
~~~以上是我對js模塊化編程的理解,若有紕漏,還請各位技術大牛指出完善~~~