1. 程式人生 > >讀書筆記《你不知道的JavaScript上卷》1.3函式作用域和塊作用域

讀書筆記《你不知道的JavaScript上卷》1.3函式作用域和塊作用域

函式作用域和塊作用域

函式作用域:屬於這個函式的全部變數都可以在整個函式的範圍內訪問(事實上在巢狀的作用域中也可以訪問)。


3.1 函式中的作用域

函式可以形成作用域,這個大家都知道的,如下程式碼:

function foo() {
    var a = 2;
    console.log("函式foo中的a:" + a);//列印 函式foo中的a:2
}
foo();
console.log("函式foo外的a:" + a);//報錯 ReferenceError

函式foo裡面形成了一個作用域所以它內部定義的變數a,只有它自己能夠訪問,外部是無法訪問的沒有找到該變數故報了一個ReferenceError引用錯誤。

3.2 隱藏內部實現

由於JS中函式就是一個作用域,同時JS中又有作用域鏈的存在,因此定義全域性的變數或函式是很危險的,當專案龐大的時候過多的全域性變數(或全域性函式)很可能會發生覆蓋,從而導致專案難以維護,所以JS可以在函式中定義另一個函式當某個函式只會在這個函式的內部呼叫時,通常寫在這個函式的內部,這樣對於全域性來說隱藏了這個函式。如下例中的doSomethingElse方法,全域性就無法方法

function doSomething() {
    function doSomethingElse() {
        //一些程式碼
    }
    doSomethingElse();//函式內部可以呼叫 但外部不能
} doSomethingElse();//報錯 這裡不能呼叫

3.3 避免衝突

通常避免衝突有兩種方法:

  • 全域性名稱空間
  • 模組管理

這裡我們重點說一下全域性名稱空間,它在第三方庫中運用的比較多,具體做法如下:

var $ = {//全域性名稱空間為:$
    add:function(){},
    remove:function(){}
};
$.add();//通過全域性名稱空間呼叫裡面的方法

3.4 立即執行函式

立即執行函式(Immediately Invoked Function Expression),也就是IIFE,表示會立即執行的函式,它在平時寫程式碼的時候用的還是比較多的。

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

如上,立即執行函式可以有效地避免變數地覆蓋,其實就是利用作用域來隱藏內部實現。為什麼它會立即執行呢,原因在上面程式碼的第5行,最後的括號表示執行該函式,就跟呼叫函式一樣,當然如果有引數也可以傳遞引數。上面的函式名IIFE是可以去掉的,在JS中如果函式是宣告,那麼函式名必須要寫以保證之後呼叫可以用“函式名()”這種形式來呼叫;如果只是為了返回它的引用(也就是首地址,就跟上面一樣,或者當引數傳遞的時候)。那麼名稱可以寫也可以不寫,並且這個名稱並不會汙染全域性,因為個函式並不是宣告,所以也就不可以用“函式名()”來呼叫,不過寫上函式名在除錯的時候會更直觀
立即執行函式還有另外一種形式,如下:

(function(){ .. }())

3.5 立即執行函式的用途

3.5.1 避免undefined被覆蓋

undefined = true; // 如果有人if(a == undefined){}時肯定會奔潰的
(function IIFE( undefined ){
    var a;
    if (a === undefined) {
        console.log( "這裡的undefined是對的!" );
    }
})();

由於最後一行呼叫的時候什麼也沒有傳,所以第2行undefined的值是正真的沒有宣告,所以undefined外面被覆蓋也就不起作用了。

3.5.1 倒置程式碼

可以利用立即執行函式使得傳遞的引數有我們自己決定,函式的內容由別人來決定,如下程式碼:

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

我們定義的IIFE函式傳遞的引數是一個函式,它呼叫的時候會傳遞一個window物件,而當第四行正真傳遞了一個def的函式進去的時候,其呼叫是在第三行,那麼引數global的值就是window。

3.6 塊作用域

塊作用域:通常,程式語言用花括號(也就是這個{})括起來所形成的作用域就是塊作用域。

通常情況下JS用花括號括起來是沒有塊作用域的!!!,如下:

for (var i=0; i<10; i++) {
}
console.log( i );//列印 10

for迴圈10次什麼都不做,在它後面列印i也可以得到值,因為上面的花括號並不能形成作用域,這個其他語言如Java有很大的不同。
上面說到的是通常情況下,那麼特殊情況有哪些呢?

3.6.1 with

上一節說過with從物件中會創建出自己的作用域。

3.6.2 try/catch

JS中也有異常處理就是try/catch,catch塊會形成塊作用域,但是try並不會形成塊作用域,如下:

try {
    undefined(); // 這行程式碼會有異常
} catch (err) {
    console.log( err ); // 列印err的值
}
console.log( err ); // ReferenceError: `err` not found      

3.6.2 let/const

let和const是ES6引入的,他們所在的塊是擁有作用域的,其中let是變數類似於var,但是所在塊有該變數的作用域;const與let類似,但是表示常量,值是不能修改的,修改就報錯。這裡給的例子是上面迴圈的例子,如下,這裡的第三行程式碼在花括號外面是訪問不到變數i的。

for (let i=0; i<10; i++) {
}
console.log( i ); // ReferenceError