你不知道的javascript讀書筆記

你不知道的javascript讀書筆記


作用域

作用域就是作用的範圍,指它在函數哪些範圍內可以使用,要是在其他不可使用範圍想使用的話,就要重新定義

詞法作用域

詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你自己寫代碼時將變量和塊作用域寫在哪裏決定的,因此詞法分析器處理代碼時會保持作用域不變

函數中的作用域

看下面一段代碼

function foo(a) { 
    var b = 2;
    // 一些代碼
    function bar() { 
        // ...
    }
    // 更多的代碼 
    var c = 3;
}

在這段代碼中,foo(..) 的作用域中包含了標識符 a、b、c 和 bar。

bar(…)擁有自己的作用域,全局作用域也有自己的作用域氣泡,它只包含了一個標識符:foo。

由於標識符 a、b、c 和 bar 都附屬於 foo(..) 的作用域,因此無法從 foo(..) 的外部 對它們進行訪問。也就是說,這些標識符全都無法從全局作用域中進行訪問,因此下面的代碼會導致 ReferenceError 錯誤:

bar();//報錯
console.log(a,b,c);//報錯

函數作用域的含義是指,屬於這個函數的全部變量都可以在整個函數的範圍內使用及複用。這種設計方案是非常有用的,能充分利用 JavaScript 變量可以根據需要改變值類型的“動態”特性。

隱藏內部實現

對函數的傳統認知就是先聲明一個函數,然後再向裏面添加代碼。但反過來想也可以帶來一些啓示:從所寫的代碼中挑選出一個任意的片段,然後用函數聲明對它進行包裝,實際上就是把這些代碼“隱藏”起來了。
實際的結果就是在這個代碼片段的周圍創建了一個作用域氣泡,也就是說這段代碼中的任何聲明(變量或函數)都將綁定在這個新創建的包裝函數的作用域中,而不是先前所在的作用域中。換句話說,可以把變量和函數包裹在一個函數的作用域中,然後用這個作用域來“隱藏”它們。

爲什麼“隱藏”變量和函數是一個有用的技術?

有很多原因促成了這種基於作用域的隱藏方法。它們大都是從最小特權原則中引申出來的,也叫最小授權或最小暴露原則。

這個原則是指在軟件設計中,應該最小限度地暴露必要內容,而將其他內容都“隱藏”起來,比如某個模塊或對象的 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(..) 所控制。功能性和最終效果都沒有受影響,但是設計上將具體內容私有化了,設計良好的軟件都會 依此進行實現。

規避衝突

“隱藏”作用域中的變量和函數所帶來的另一個好處,是可以避免同名標識符之間的衝突, 兩個標識符可能具有相同的名字但用途卻不一樣,無意間可能造成命名衝突。衝突會導致變量的值被意外覆蓋。

1、全局命名空間

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

這些庫通常會在全局作用域中聲明一個名字足夠獨特的變量,通常是一個對象。這個對象被用作庫的命名空間,所有需要暴露給外界的功能都會成爲這個對象(命名空間)的屬性,而不是將自己的標識符暴漏在頂級的詞法作用域中。

2、模塊管理

另外一種避免衝突的辦法和現代的模塊機制很接近,就是從衆多模塊管理器中挑選一個來使用。使用這些工具,任何庫都無需將標識符加入到全局作用域中,而是通過依賴管理器 的機制將庫的標識符顯式地導入到另外一個特定的作用域中。

顯而易見,這些工具並沒有能夠違反詞法作用域規則的“神奇”功能。它們只是利用作用域的規則強制所有標識符都不能注入到共享作用域中,而是保持在私有、無衝突的作用域中,這樣可以有效規避掉所有的意外衝突。

函數作用域

立即執行函數和普通聲明函數的區別與作用?
兩者最重要的區別就是它們的名稱標識符將會綁定在何處

//代碼片段1:
var a = 2;
function foo(){
    var a = 3;
    console.log(a);//3
} 
foo();
console.log(a);//2

//代碼片段2:
var a = 2;
(function foo(){
    var a = 3;
    console.log(a);//3
})();
console.log(a);//2

比較前面兩段代碼,第一個片段的foo被綁定在所在作用域中,可以直接通過foo()來調用,第二個片段中foo被綁定在函數表達式自身的函數中而不是所在作用域中。換句話說 (function foo(){…})作爲函數表達式意味着foo只能在…所代表的位置中被訪問,外部作用域則不行。foo變量名被隱藏在自身中意味着不會非必要的污染外部作用域

匿名函數的缺點

1、在棧追蹤沒有名字,很難調試
2、沒有函數名,在需要引用自身時只能使用已經過期的arrguments.callee引用,比如:在遞歸中,另一個函數需要引用自身的例子,是在事件觸發後事件監聽器需要解綁自身(不太明白)。
行內函數表達式非常強大且有用——匿名和具名之間的區別並不會對這點有任何影響,給函數表達式指定一個函數名就可以解決上述問題了。

setTimeout(function timer(){ //函數表達式有名字了
    console.log("hahahahaha")
},1000)
發佈了28 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章