JS進階之路之——作用域(二)

最小授權原則

最小授權原則是指在軟件設計中,應該最小限度地暴露必要內容,而將其他內容都“隱藏”起來,比如某個模塊或對象的 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 並不常見,但它具有以下優勢:

  1. 匿名函數在棧追蹤中不會顯示出有意義的函數名,使得調試很困難。

  2. 如果沒有函數名,當函數需要引用自身時只能使用已經過期的arguments.callee引用, 比如在遞歸中。另一個函數需要引用自身的例子,是在事件觸發後事件監聽器需要解綁 自身。

  3. 匿名函數省略了對於代碼可讀性/可理解性很重要的函數名。一個描述性的名稱可以讓 代碼不言自明。

因此具名函數的 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 執行之後當作參數傳遞進去。儘管這種模式略顯冗長,但有些人認爲它更易理解。


發佈了24 篇原創文章 · 獲贊 40 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章