你不知道的Javascript(上卷)讀書筆記之三 ---- 函數做用域與塊做用域

1. 函數中的做用域 程序員

函數做用域的含義是指屬於這個函數的所有變量均可以在整個函數範圍內使用以及複用數據結構

 

2. 隱藏內部實現閉包

函數常用於隱藏」內部實現」,能夠把變量和函數包裹在一個函數的做用域中,而後用這個做用域來隱藏它們。函數

這種基於做用域的隱藏方法基於軟件設計中的最小特權原則(最小受權/最小暴露原則),好比模塊的API設計。工具

引伸一下,若是變量和函數都放在全局做用域中,那麼就會暴露過多的變量和函數,從而違背了最小特權原則,而這些變量本該是私有的,應該阻止外部訪問的。額外的多餘的訪問權限可能被有意或者無心的以非預期的方式調用,從而致使超出了原本應該的使用條件,會使程序變得」危險」。這有點相似於Java的OOP中的封裝,咱們能夠經過函數隱藏內部,從而封裝好程序。spa

        隱藏內部實現是爲了規避衝突設計

爲了保持變量或者函數的可讀性,咱們老是但願儘量想要起更加簡單的標識符。可是,在大型多人開發項目中,標識符越簡單,越容易重名衝突。標識符越複雜,雖然不容易發生重名衝突,可是可讀性會變弱,並且繁瑣冗長的代碼容易引發程序員的反感,特別是在API十分的繁雜的狀況下。調試

同時,在軟件設計過程當中,存在某些狀況要求使用一樣的標識符名稱,這種狀況不是沒有,偏偏相反,十分常見。(就好比Java的集合庫,假如經常使用的集合類的添加方法,名稱徹底不同,那真的是一件很恐怖的事情,這意味着咱們須要耗費不少腦容量去記憶這些API)code

在這種狀況下,隱藏內部聲明是惟一的最佳選擇。可使用Javascript Object 去存儲標識符、封裝成對象、並隱藏內部函數的具體實現。對象

        a.全局命名空間

變量衝突的一個典型例子存在於全局做用域中。當程序加載了多個第三方庫時,若是他沒有妥善的將內部私有的變量或者函數隱藏起來,就會很容易引起衝突

這些庫一般會在全局做用域中聲明一個名字足夠獨特的變量,一般是一個對象。這個對象被用做庫的命名空間,全部要暴露給外界的功能都會變成這個對象的屬性。

        b.模塊管理

還有另一種避免衝突的方法,它與現代的模塊機制十分接近,這種方式並非將庫加載到全局做用域中,而是經過依賴管理器將庫的標識符顯示的導入到另一個特定的做用域中.

 

3. 函數做用域

函數表達式和函數聲明

函數聲明進行調用:

function foo() {
    var a = 3;
    console.log(a);
}
foo();

函數表達式:

(function foo(){ 
    var a = 3; 
    console.log(a); 
})();

        3.1 匿名和具名

若是你仍是不理解函數表達式是什麼意思,咱們其實常常用到:

setTimeout(function() { 
    console.log(「I waited 1 second」); 
}, 1000);

這叫匿名函數表達式。函數表達式是能夠匿名的,函數聲明則不能夠。看起來函數表達式簡單快捷,幾近完美,可是它仍是有缺點的:

        A.匿名函數在棧追蹤時,不會顯示有意義的函數名,調試困難

        B.因爲沒有函數名,所以函數在引用自身只能使用已通過期的arguments.callee 引用,一種例子是遞歸,還有一種例子是事件觸發後事件監聽器要解綁自身

        C.匿名函數省略了對於代碼理解比較重要的函數名,一個描述性的名稱可讓代碼不言自明。

 

        3.2 當即執行函數表達式(IIFE)

給一個函數包含在()括號內部,所以成爲了一個表達式,經過在末尾加上一個(),會使 得這個表達式當即執行。

IIFE的一個進階用法就是把他們看成函數調用而且傳遞參數進去。

例如:

var a = 2; 
(function() IIFE(global){ 
    var a = 3; 
    console.log(a); 
    console.log(global.a); 
})(window); 
console.log(a);

IIFE有一種變化的用戶是倒置代碼的運行順序,將要運行的函數放在第二位,在IIFE 執行以後看成參數傳遞進去。

var a = 2; 
(function IIFE(def){ 
    def(window); 
})(function def(global){ 
    var a = 3; 
    console.log(a); 
    console.log(global.a); 
});

 

4 塊做用域

        4.1 with關鍵字是Js中爲數很少的塊做用域的一個例子,用with從對象中建立出的做用域僅僅在with聲明中有效

        4.2 try、catch

        4.3 let

ES6提供了let關鍵字,提供了除了var以外的另一種變量聲明方式,它會將變量綁定在循環所在的塊做用域上。

可使用顯式的{}塊,顯式地將變量使用let綁定在塊做用域上

可是使用let聲明的變量不會進行變量提高。

使用let 還有額外的好處:

                a. 垃圾收集

function process(data) { 
    // 在這裏作點有趣的事情 
} 
var someReallyBigData = { .. }; 
process( someReallyBigData ); 
var btn = document.getElementById( "my_button" ); 
btn.addEventListener( "click", function click(evt) { 
    console.log("button clicked"); 
}, /*capturingPhase=*/false );

click 函數的點擊回調並不須要 someReallyBigData 變量。 理論上這意味着當 process(..) 執行後, 在內存中佔用大量空間的數據結構就能夠被垃圾回收了。 可是, 因爲 click 函數造成了一個覆蓋整個做用域的閉包, JavaScript 引擎極有可能依然保存着這個結構( 取決於具體 實現)。

塊做用域能夠打消這種顧慮, 可讓引擎清楚地知道沒有必要繼續保存 someReallyBigData 了:

function process(data) { 
    // 在這裏作點有趣的事情 
}
//在這個塊中定義的內容能夠銷燬了! 
{ 
    let someReallyBigData = { .. }; 
    process( someReallyBigData ); 
} 
var btn = document.getElementById( "my_button" ); 
btn.addEventListener( "click", function click(evt){ 
    console.log("button clicked"); 
}

爲變量顯式聲明塊做用域,並對變量進行本地綁定是很是有用的工具, 能夠把它添加到你的代碼工具箱中了。

                b.let循環

for(let i = 0; i < 10; i++) 
    console.log(i);

與正常使用var變量的for循環不一樣,var變量聲明可能會污染全局空間,將變量綁定在全局做用域上,使用let進行變量聲明能夠直接把變量綁定在塊級做用域上。

        4.4 const

除了 let 之外, ES6 還引入了 const, 一樣能夠用來建立塊做用域變量, 但其值是固定的( 常量)。 以後任何試圖修改值的操做都會引發錯誤。

相關文章
相關標籤/搜索