《你不知道的JavaScript》-- 精讀(三)

知識點

1.函數中的做用域

函數做用域的含義是指,屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用(事實上在嵌套的做用域中也可使用)。bash

function foo(a){
    var b = 2;
    // 一些代碼
    function bar(){
        // ...
    }
    // 更多的代碼
    var c = 3;
}
bar(); // 失敗
console.log(a,b,c); // 三個全都失敗
複製代碼

2.隱藏內部實現

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

爲何「隱藏」變量和函數是一個有用的技術?ui

最小特權原則:在軟件設計中應該最小限度地暴露必要內容,而將其它內容都「隱藏」起來,好比某個模塊或對象的API設計。spa

function doSomething(a){
    b = a + doSomethingElse(a * 2){
        console.log(b * 3);
    }
}
function doSomethingElse(a){
    return a - 1;
}
var b;
doSomething(2); // 15

// 最小特權原則改進
function doSomething(a){
    function doSomethingElse(a){
        return a - 1;
    }
    var b;

    b = a + doSomethingElse(a * 2){
        console.log(b * 3);
    }
}

doSomething(2); // 15
複製代碼

3.規避衝突

「隱藏」做用域中的變量和函數所帶來的另外一個好處,是能夠避免同名標識符之間的衝突。設計

function foo(){
    function bar(a){
        // var i = 3;
        i = 3; // 修改for循環所屬做用域中的i
        console.log(a+i);
    }
    for(var i = 0; i < 10; i++){
        bar(i * 2); // 糟糕,無限循環了!
    }
}
複製代碼

可使用var i = 3修改上面的代碼,獲得正確的結果。在這種狀況下使用做用域來「隱藏」內部聲明是最佳選擇。code

3.1 全局命名空間

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

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

var MyReallyCoolLibrary = {
    awesome: "stuff",
    doSomething: function(){
        // ...
    }
    doAnotherThing: function(){
        // ...
    }
}
複製代碼

3.2 模塊管理

另一種避免衝突的方法和現代的模塊機制很接近,就是從衆多模塊管理器中挑選一個來使用。作用域

4.函數做用域

在任意代碼片斷外部添加包裝函數,能夠將內部的變量和函數定義「隱藏」起來,外部做用域沒法訪問包裝函數內部的任何內容。string

var a = 2;
function foo(){ // 添加這一行
    var a = 3;
    console.log(a);
} // 以及這一行
foo(); //  以及這一行
console.log(a);
複製代碼

這樣會致使的問題是,首先必須聲明一個具名函數foo(),意味着foo這個名稱會「污染」所在做用域(在這個例子中是全局做用域)。其次,必須顯示地經過函數名(foo())來調用才能運行。

JavaScript提供了可以同時解決函數不須要函數名,而且可以自動運行的方案。

var a = 2;
(function foo(){
var a = 3;
console.log(a); // 3
})()
console.log(a); // 2
複製代碼

函數聲明和函數表達式的區別是function是不是聲明的的第一個詞,若是是就是函數聲明。

(function foo(){..})做爲函數表達式意味着foo只能在..所表明的位置中被訪問。外部做用域則不行。foo變量被隱藏在自身中意味着不會非必要地污染外部做用域。

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

始終給函數表達式命名是一個最佳實踐。

setTimeout(function timeoutHandler(){ // 快看,我有名字了!
    console.log("I waited 1 second");
},1000)
複製代碼

(function(){...})(),第一個()將函數變成表達式,第二個()執行了這個函數。

IIFE的進階用法是把它們當作函數調用並傳遞參數進去。

var a = 2;
(function IIFE(global){
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})(window)
console.log(a); // 2
複製代碼

IIFE的另外一種用途是倒置代碼的運行順序。

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

函數表達式def定義在片斷的第二部分,而後當作參數(這個參數也叫作def)被傳遞進IIFE函數定義的第一部分中。最後,參數def(也就是傳遞進去的函數)被調用,並將window傳入當作global參數的值。

6.塊做用域

6.1 let

let 關鍵字能夠將變量綁定到所在的任意做用域中(一般是{..}內部)。let爲其聲明的變量隱式地劫持了所在的塊做用域。

var foo = true;
if(foo){
    let bar = foo * 2;
    bar = something(bar);
    console.log(bar);
}
console.log(bar); // ReferenceError
複製代碼
var foo = true;
if(foo){
    { // 顯式的塊
        let bar = foo * 2;
        bar = something(bar);
        console.log(bar);
    }
}
console.log(bar); // ReferenceError
複製代碼

let 進行的聲明不會在塊做用域中進行提高。

{
    console.log(bar); // ReferenceError
    let bar = 2; 
}
複製代碼

6.2 const

var foo = true;
if(foo){
    var a = 2;
    const b = 3; // 包含在if中的塊做用域常量
    a = 3; // 正常
    b = 4; // 錯誤!
}
console.log(a); // 2
console.log(b); // ReferenceError
複製代碼

總結

函數是JavaScript中最多見的做用域單元。本質上,聲明在一個函數內部的變量或函數會在所處的做用域中「隱藏」起來,這是有意爲之的良好的軟件設計原則。

但函數不是惟一的做用域單元。塊做用域指的是變量和函數不只能夠屬於所處的做用域,也能夠屬於某個代碼塊(一般指{...}內部)。

巴拉巴拉

關於標籤

一朋友在我對本身產生嚴重質疑的時候對我說,不要輕易給本身加標籤,固然也不要輕易給別人下定義。聊這個事情的時候,並無醍醐灌頂,卻是最近經歷的一些事,讓我對他說的話,有了一些不同的感悟。好比,你某件事沒有作好,你的上級可能會以爲你沒作好的緣由是不夠認真,經驗不足,能力不夠等,一旦他把其中的某個詞加在你身上,你的第一反應可能他是對的,我就是這樣的,甚至還會自我加註更多相似的。我想說這樣是不對的,由於你可能無心中就多了一個困擾本身的負面標籤。正確的作法應該是,他也許是對的,我還有須要提高,改正的地方,儘可能避免正面的去贊成他的話,而是從積極的角度說能夠改正,提高的地方,不少事,是沒有絕對的對錯和好壞的,一個原則是,儘可能不要給本身加註負面的具體的標籤,由於會給人更深的印象,讓人永遠記住黑歷史。

相關文章
相關標籤/搜索