當系統大了、程式複雜了、寫的人多了,名字的問題就是個大問題。目前來說,解決名字問題的最好辦法就是命名空間。
使用命名空間,可以避免變量或對象名稱産生的沖突,同時,也有助于組織代碼,有更強的可維護性和可讀性。
javascript不提供原生的命名空間支援,但我們可以利用javascript的一些語言特性,實作類似的效果。
1.閉包
簡單的說,閉包就是,一個函數可以使用函數之外定義的變量,那麼這個函數就是閉包。
var msg = "你好,朋友";
function sayhello() {
alert(msg);
}
sayhello();
這就是一個最簡單的閉包執行個體。函數sayhello調用了函數外部定義的變量msg,是以函數sayhello就是閉包。
function extfunc() {
var n = 1;
function internal_func(){
alert(n++);
}
return internal_func();
extfunc();
這個例子稍微複雜一些,其内部函數(internal_func )是一個閉包,因為它使用外部函數的變量n的值。extfunc()的最後一步調用了internal_func(),增加了變量n的值,并列印它。
通過閉包可以讓函數内部變量的值始終保持在記憶體中。比如下面的例子,函數fn()無論執行多少次,其内部變量n的值都是1,因為函數每次執行的時候都會重新配置設定記憶體,并初始化其内部變量,而且執行完畢,會釋放函數内部變量的記憶體。
function fn() {
alert(n++);
fn();
現在,我們用閉包做一些處理。
function in_fn(){
return in_fn;
var f = fn();
f();
我們發現,每次執行,函數fn()内部的變量n的值都會加一,說明變量n的值始終儲存在記憶體中了。
這個例子中,函數fn()最後傳回了閉包函數in_fn(),也就是說var f=fn()執行後,f其實是函數in_fn()的引用,此時,函數執行個體fn()在f引用銷毀之前不會銷毀,是以,其内部變量n可以儲存在記憶體中。
2.自調用匿名函數
自調用匿名函數,或者說,立即執行函數表達式,英文縮寫iife,是一個立即執行的匿名函數表達式,我們把這種代碼結構稱為“自調用匿名函數”。
(function() {
alert("我是一個自調用匿名函數");
})();
該代碼定義了一個匿名函數,而且會立即執行。其實,這段代碼等同于下面的代碼,但這種表達方式更加簡單直接。
function fn(){
};
下面的方式也是合法的。
(function(){
}());
當然,自調用匿名函數是可以帶參數的。
(function(window){
window.msg = "我被自調用匿名函數初始化";
})(window);
alert(window.msg);
這裡,自調用匿名函數執行的時候,将window對象傳給了它,它給window對象設定了一個屬性msg,并初始化。
很明顯的是,對于自調用匿名函數,我們不能調用兩次,是以,它非常适合做一次性或者初始化性質的工作。
要特别注意的是,自調用匿名函數用在指派函數表達式中的情況。先看下面的例子。
var fn = function(){
return "這是函數執行結果";
alert(fn);
其fn是一個指派函數,我們列印出來的是函數的代碼:
}();
這時候fn就不是一個函數的位址了,而是匿名函數執行後的結果:
3.命名空間
是以,我們可以通過函數作用域來實作javascrpt的命名空間 ,也就是利用自調用匿名函數,建立一個特殊的函數作用域,将該作用域中的代碼和外界隔離開,進而實作命名空間的概念。
比如jquery架構的命名空間:
(function(window,undefined){
var jquery = (function(){
var jquery = function(){
alert("建立jquery對象");
};
return jquery;
})();
window.jquery = window.$ = jquery;
執行$(),或者jquery(),顯示:
該代碼通過自調用匿名函數建立了一個和外界隔離的對象jquery,并通過全局變量$(window.$)和jquery(window.jquery)對外公開,其jquery對象起到了命名空間的作用。同時利用閉包的特性,讓命名空間對象的值始終保持在記憶體中。
4.子產品化
雖然我們通過javascript的閉包、自調用匿名函數等特性,建立了命名空間,或者說我們建立了命名空間模式,可以很好地解決名字問題。但随着系統的增大,需要團隊協作完成一個項目,此時,多檔案就是必須的了。多檔案開發環境下,子產品化程式設計就是我們必須要掌握的一項技術。
我們可以通過擴充模式,來實作多人、多檔案開發。比如,a定義了一個命名空間module_a,為了表述的統一,這裡我們稱為子產品module_a。
var module = {};
module.moduleproperty = "子產品module_a的原始屬性moduleproperty";
module.modulemethod = function () {
alert("子產品module_a的原始方法modulemethod");
};
window.module_a = module;
該代碼寫在檔案file_a.js中。現在,b要負責module_a子產品其他方法的實作,他可以在檔案file_b.js中,通過擴充模式,給module_a添加方法、或者屬性。
(function(module){
module.anothermethod = function () {
alert("子產品module_a的擴充方法anothermethod");
};
})(window.module_a);
下面,我們用一個測試檔案測試一下:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="file_a.js"></script>
<script src="file_b.js"></script>
<title>document</title>
</head>
<body>
<script type="text/javascript">
module_a.modulemethod();
module_a.anothermethod();
</script>
</body>
</html>
很好,運作順利:
但是,如果我們對file_a.js和file_b.js兩個檔案的引用順序錯了的話,就會産生錯誤。
錯誤的原因是我們在擴充module_a時,module_a還沒有定義,也就是還不存在。這個問題可以通過寬模式擴充解決。
file_a.js檔案代碼的修改:
})(window.module_a || {});
file_b.js檔案代碼的修改:
alert("子產品module_a的擴充方法anothermethod");
也就是說,我們在給自調用匿名函數傳遞參數的時候,就判斷全局變量是否存在,如果不存在則直接建立一個object對象給它。這樣,我們就可以可以建立靈活多變的子產品,可以将他們無順序加載,以寬松的方式擴充。