最小授權原則是指在軟件設計中,應該最小限度地暴露必要內容,而將其他內容都“隱藏”起來,比如某個模塊或對象的 API 設計。
這個原則可以延伸到如何選擇作用域來包含變量和函數。如果所有變量和函數都在全局作 用域中,當然可以在所有的內部嵌套作用域中訪問到它們。但這樣會破壞前面提到的最小 特權原則,因爲可能會暴漏過多的變量或函數,而這些變量或函數本應該是私有的,正確 的代碼應該是可以阻止對這些變量或函數進行訪問的。
例如:
function doSomething(a) {
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething( 2 ); // 15
在這個代碼片段中,變量 b 和函數 doSomethingElse(..) 應該是 doSomething(..) 內部具體 實現的“私有”內容。給予外部作用域對 b 和 doSomethingElse(..) 的“訪問權限”不僅 沒有必要,而且可能是“危險”的,因爲它們可能被有意或無意地以非預期的方式使用, 從而導致超出了 doSomething(..) 的適用條件。更“合理”的設計會將這些私有的具體內容隱藏在 doSomething(..) 內部,
例如:
function doSomething(a) {
function doSomethingElse(a) {
return a - 1;
}
var b;
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
doSomething( 2 ); // 15
現在,b 和 doSomethingElse(..) 都無法從外部被訪問,而只能被 doSomething(..) 所控制。 功能性和最終效果都沒有受影響,但是設計上將具體內容私有化了,設計良好的軟件都會 依此進行實現。
規避衝突
當我們的程序代碼逐漸多起來,難免會出現變量衝突。那麼如何規避衝突就顯得額外重要。
函數可以把標識符嚴謹的"隱藏"起來,外部無法訪問到,利用這個特性我們可以很好的規避衝突。
function foo() {
var a = 1;
}
function bar() {
var a = 2;
}
foo和bar中定義了相同的變量a,但是卻不會相互造成影響。因爲函數可以很好的把標識符"隱藏"起來。
變量衝突的一個典型例子存在於全局作用域中。當程序中加載了多個第三方庫時,如果它 們沒有妥善地將內部私有的函數或變量隱藏起來,就會很容易引發衝突。 這些庫通常會在全局作用域中聲明一個名字足夠獨特的變量,通常是一個對象。這個對象 被用作庫的命名空間,所有需要暴露給外界的功能都會成爲這個對象(命名空間)的屬 性,而不是將自己的標識符暴漏在頂級的詞法作用域中。
例如:
var myLibrary = {
name: 'echo', getName: function() {
console.log( this.name );
}
}
函數聲明 VS 函數表達式
函數聲明和函數表達式判別的依據是:函數的生命是否以function關鍵詞開始。 以關鍵詞function 開始的聲明是函數聲明,其餘的函數聲明全部是函數表達式。
//函數聲明function foo() {}
//函數表達式var foo = function () {};
(function() {})();
具名函數 VS 匿名函數
具名函數 擁有名字的函數
function foo() {}
var foo = function bar() {}
setTimeout( function foo() {} )+function foo() {}();
需要注意:函數聲明一定要是具名函數。
匿名函數 沒有名字的函數
var foo = function () {}setTimeout( function foo() {} )-function foo() {}();
立即執行函數(IIFE)
vara=2;
(function foo() {
var a=3;
console.log( a ); // 3})();
console.log( a ); // 2
該函數是以()開始,不是以關鍵詞function開始,因此IIFE是函數表達式
函數名對 IIFE 當然不是必須的,IIFE 最常見的用法是使用一個匿名函數表達式。雖然使 用具名函數的 IIFE 並不常見,但它具有以下優勢:
匿名函數在棧追蹤中不會顯示出有意義的函數名,使得調試很困難。
如果沒有函數名,當函數需要引用自身時只能使用已經過期的arguments.callee引用, 比如在遞歸中。另一個函數需要引用自身的例子,是在事件觸發後事件監聽器需要解綁 自身。
匿名函數省略了對於代碼可讀性/可理解性很重要的函數名。一個描述性的名稱可以讓 代碼不言自明。
因此具名函數的 IIFE 也是一個值得推廣的實踐。
(function() {}())
這也是IIFE的一種表達方式,功能上和上面那種方式是一致的。選擇哪種全憑個人愛好。
IIFE 也可以和其他形式的函數一樣實現參數的傳遞(多說一句:參數傳遞是按值傳遞)。
(function foo(a) { console.log(a);})(3);
這個模式的另外一個應用場景是解決 undefined 標識符的默認值被錯誤覆蓋導致的異常(雖 然不常見)。將一個參數命名爲 undefined,但是在對應的位置不傳入任何值,這樣就可以 保證在代碼塊中 undefined 標識符的值真的是 undefined:
undefined = true; // 給其他代碼挖了一個大坑!絕對不要這樣做!
(function IIFE( undefined ) {
var a; if (a === undefined) {
console.log( "Undefined is safe here!" );
}})();
UMD (Universal Module Definition)
IIFE 還有一種變化的用途是倒置代碼的運行順序,將需要運行的函數放在第二位,在 IIFE 執行之後當作參數傳遞進去。儘管這種模式略顯冗長,但有些人認爲它更易理解。