在復雜的邏輯下
JavaScript 需要被模塊化
模塊需要封裝起來
只留下供外界調用的接口
閉包是 JavaScript 中實現模塊封裝的關鍵
也是很多初學者難以理解的要點
雖然 JavaScript 天生就是一副隨隨便便的樣子但是隨著浏覽器能夠完成的事情越來越多這門語言也也越來越經常地擺出正襟危坐的架勢在復雜的邏輯下 JavaScript 需要被模塊化模塊需要封裝起來只留下供外界調用的接口閉包是 JavaScript 中實現模塊封裝的關鍵也是很多初學者難以理解的要點最初我也陷入迷惑之中現在我自信對這個概念已經有了比較深入的理解為了便於理解文中試圖 封裝一個比較簡單的對象
我們試圖在頁面上維護一個計數器對象 ticker 這個對象維護一個數值 n 隨著用戶的操作我們可以增加一次計數(將數值 n 加上 )但不能減少 n 或直接改變 n 而且我們需要時不時查詢這個數值
門戶大開的 JSON 風格模塊化
一種門戶大開的方式是
復制代碼 代碼如下:
var ticker = {
n:
tick:function(){
this
n++;
}
};
這種方式書寫自然而且確實有效我們需要增加一次計數時就調用 tickertick() 方法需要查詢次數時就訪問 tickern 變量但是其缺點也是顯而易見的模塊的使用者被允許自由地改變 n 比如調用 tickern 或者 tickern= 我們並沒有對 ticker 進行封裝 n 和 tick() 看上去是 ticker 的“成員”但是它們的可訪問性和 ticker 一樣都是全局性的(如果 ticker 是全局變量的話)在封裝性上這種模塊化的方式比下面這種更加可笑的方式只好那麼一點點(雖然對有些簡單的應用來說這一點點也足夠了)
復制代碼 代碼如下:
var ticker = {};
var tickerN =
;
var tickerTick = function(){
tickerN++;
}
tickerTick();
值得注意的是在 tick() 中我訪問的是 thisn ——這並不是因為 n 是 ticker 的成員而是因為調用 tick() 的是 ticker 事實上這裡寫成 tickern 會更好因為如果調用 tick() 的不是 ticker 而是其他什麼東西比如
復制代碼 代碼如下:
var func = ticker
tick;
func();
這時調用 tick() 的其實是 window 而函數執行時會試圖訪問 windown 而出錯
事實上這種“門戶大開”型的模塊化方式往往用來組織 JSON 風格的數據而不是程序比如我們可以將下面這個 JSON 對象傳給 ticker 的某個函數來確定 ticker 從 開始計數每次遞進
復制代碼 代碼如下:
var config = {
nStart:
step:
}
作用域鏈和閉包
來看下面的代碼注意我們已經實現了傳入 config 對 ticker 進行自定義
復制代碼 代碼如下:
function ticker(config){
var n = config
nStart;
function tick(){
n += config
step;
}
}
console
log(ticker
n); //
>undefined
你也許會疑惑怎麼 ticker 從對象變成了函數了?這是因為 JavaScript 中只有函數具有作用域從函數體外無法訪問函數內部的變量 ticker() 外訪問 tickern 獲得 undefined 而 tick() 內訪問 n 卻沒有問題從 tick() 到 ticker() 再到全局這就是 JavaScript 中的“作用域鏈”
可是還有問題那就是——怎麼調用 tick() ? ticker() 的作用域將 tick() 也掩蓋了起來解決方法有兩種
•)將需要調用方法作為返回值正如我們將遞增 n 的方法作為 ticker() 的返回值
•)設定外層作用域的變量正如我們在 ticker() 中設置 getN
復制代碼 代碼如下:
var getN;
function ticker(config){
var n = config
nStart;
getN = function(){
return n;
};
return function(){
n += config
step;
};
}
var tick = ticker({nStart:step:});
tick();
consolelog(getN()); // >
請看這時變量 n 就處在“閉包”之中在 ticker() 外部無法直接訪問它但是卻可以通過兩個方法來觀察或操縱它
在本節第一段代碼中 ticker() 方法執行之後 n 和 tick() 就被銷毀了直到下一次調用該函數時再創建但是在第二段代碼中 ticker() 執行之後 n 不會被銷毀因為 tick() 和 getN() 可能訪問它或改變它浏覽器會負責維持n我對“閉包”的理解就是用以保證 n 這種處在函數作用域內函數執行結束後仍需維持可能被通過其他方式訪問的變量 不被銷毀的機制
可是我還是覺得不大對勁?如果我需要維持兩個具有相同功能的對象 ticker 和 ticker 那該怎麼辦? ticker() 只有一個總不能再寫一遍吧?
new 運算符與構造函數
如果通過 new 運算符調用一個函數就會創建一個新的對象並使用該對象調用這個函數在我的理解中下面的代碼中 t 和 t 的構造過程是一樣的
復制代碼 代碼如下:
function myClass(){}
var t
= new myClass();
var t
= {};
t
func = myClass;
t
func();
t
func = undefined;
t 和 t 都是新構造的對象 myClass() 就是構造函數了類似的 ticker() 可以重新寫成
復制代碼 代碼如下:
function TICKER(config){
var n = config
nStart;
this
getN = function(){
return n;
};
this
tick = function(){
n += config
step;
}
}
var ticker = new TICKER({nStart:step:});
tickertick();
consolelog(tickergetN()); // >
var ticker = new TICKER({nStart:step:});
tickertick();
tickertick();
consolelog(tickergetN()); // >
習慣上構造函數采用大寫注意 TICKER() 仍然是個函數而不是個純粹的對象(之所以說“純粹”是因為函數實際上也是對象 TICKER() 是函數對象)閉包依舊有效我們無法訪問 tickern
原型 prototype 與繼承
上面這個 TICKER() 還是有缺陷那就是 tickertick() 和 tickertick() 是互相獨立的!請看每使用 new 運算符調用 TICKER() 就會生成一個新的對象並生成一個新的函數綁定在這個新的對象上每構造一個新的對象浏覽器就要開辟一塊空間存儲 tick() 本身和 tick() 中的變量這不是我們所期望的我們期望 tickertick 和 tickertick 指向同一個函數對象
這就需要引入原型
JavaScript 中除了 Object 對象其他對象都有一個 prototype 屬性這個屬性指向另一個對象這“另一個對象”依舊有其原型對象並形成原型鏈最終指向 Object 對象在某個對象上調用某方法時如果發現這個對象沒有指定的方法那就在原型鏈上一次查找這個方法直到 Object 對象
函數也是對象因此函數也有原型對象當一個函數被聲明出來時(也就是當函數對象被定義出來時)就會生成一個新的對象這個對象的 prototype 屬性指向 Object 對象而且這個對象的 constructor 屬性指向函數對象
通過構造函數構造出的新對象其原型指向構造函數的原型對象所以我們可以在構造函數的原型對象上添加函數這些函數就不是依賴於 ticker 或 ticker 而是依賴於 TICKER 了
你也許會這樣做
復制代碼 代碼如下:
function TICKER(config){
var n = config
nStart;
}
TICKER
prototype
getN = function{
// attention : invalid implementation
return n;
};
TICKER
prototype
tick = function{
// attention : invalid implementation
n += config
step;
};
請注意這是無效的實現因為原型對象的方法不能訪問閉包中的內容也就是變量 n TICK() 方法運行之後無法再訪問到 n 浏覽器會將 n 銷毀為了訪問閉包中的內容對象必須有一些簡潔的依賴於實例的方法來訪問閉包中的內容然後在其 prototype 上定義復雜的公有方法來實現邏輯實際上例子中的 tick() 方法就已經足夠簡潔了我們還是把它放回到 TICKER 中吧下面實現一個復雜些的方法 tickTimes() 它將允許調用者指定調用 tick() 的次數
復制代碼 代碼如下:
function TICKER(config){
var n = config
nStart;
this
getN = function(){
return n;
};
this
tick = function(){
n += config
step;
};
}
TICKER
prototype
tickTimes = function(n){
while(n>
){
this
tick();
n
;
}
};
var ticker
= new TICKER({nStart:
step:
});
ticker
tick();
console
log(ticker
getN()); //
>
var ticker
= new TICKER({nStart:
step:
});
ticker
tickTimes(
);
console
log(ticker
getN()); //
>
這個 TICKER 就很好了它封裝了 n 從對象外部無法直接改變它而復雜的函數 tickTimes() 被定義在原型上這個函數通過調用實例的小函數來操作對象中的數據
所以為了維持對象的封裝性我的建議是將對數據的操作解耦為盡可能小的單元函數在構造函數中定義為依賴於實例的(很多地方也稱之為“私有”的)而將復雜的邏輯實現在原型上(即“公有”的)
最後再說一些關於繼承的話實際上當我們在原型上定義函數時我們就已經用到了繼承! JavaScript 中的繼承比 C++ 中的更……呃……簡單或者說簡陋在 C++ 中我們可能會定義一個 animal 類表示動物然後再定義 bird 類繼承 animal 類表示鳥類但我想討論的不是這樣的繼承(雖然這樣的繼承在 JavaScript 中也可以實現)我想討論的繼承在 C++ 中將是定義一個 animal 類然後實例化了一個 myAnimal 對象對這在 C++ 裡就是實例化但在 JavaScript 中是作為繼承來對待的
JavaScript 並不支持類浏覽器只管當前有哪些對象而不會額外費心思地去管這些對象是什麼 class 的應該具有怎樣的結構在我們的例子中 TICKER() 是個函數對象我們可以對其賦值(TICKER=)將其刪掉(TICKER=undefined)但是正因為當前有 ticker 和 ticker 兩個對象是通過 new 運算符調用它而來的 TICKER() 就充當了構造函數的作用而 TICKERprototype 對象也就充當了類的作用
以上就是我所了解的 JavaScript 模塊化的方法如果您也是初學者希望能對您有所幫助如果有不對的地方也勞駕您指出
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/20429.html