js做用域與命名空間

儘管 JavaScript 支持一對花括號建立的代碼段,可是並不支持塊級做用域;
而僅僅支持 函數做用域javascript

function test() { // 一個做用域
    for(var i = 0; i < 10; i++) { // 不是一個做用域
        // count
    }
    console.log(i); // 10
}

注意: 若是不是在賦值語句中,而是在 return 表達式或者函數參數中,{...} 將會做爲代碼段解析,
而不是做爲對象的字面語法解析。若是考慮到 自動分號插入,這可能會致使一些不易察覺的錯誤。java

譯者注若是 return 對象的左括號和 return 不在一行上就會出錯。ajax

// 譯者注:下面輸出 undefined
function add(a, b) {
    return 
        a + b;
}
console.log(add(1, 2));

JavaScript 中沒有顯式的命名空間定義,這就意味着全部對象都定義在一個全局共享的命名空間下面。數組

每次引用一個變量,JavaScript 會向上遍歷整個做用域直到找到這個變量爲止。
若是到達全局做用域可是這個變量仍未找到,則會拋出 ReferenceError 異常。閉包

隱式的全局變量

// 腳本 A
foo = '42';

// 腳本 B
var foo = '42'

上面兩段腳本效果不一樣。腳本 A 在全局做用域內定義了變量 foo,而腳本 B 在當前做用域內定義變量 foo模塊化

再次強調,上面的效果徹底不一樣,不使用 var 聲明變量將會致使隱式的全局變量產生。函數

// 全局做用域
var foo = 42;
function test() {
    // 局部做用域
    foo = 21;
}
test();
foo; // 21

在函數 test 內不使用 var 關鍵字聲明 foo 變量將會覆蓋外部的同名變量。
起初這看起來並非大問題,可是當有成千上萬行代碼時,不使用 var 聲明變量將會帶來難以跟蹤的 BUG。oop

// 全局做用域
var items = [/* 數組 */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // subLoop 函數做用域
    for(i = 0; i < 10; i++) { // 沒有使用 var 聲明變量
        // 幹活
    }
}

外部循環在第一次調用 subLoop 以後就會終止,由於 subLoop 覆蓋了全局變量 i
在第二個 for 循環中使用 var 聲明變量能夠避免這種錯誤。
聲明變量時絕對不要遺漏 var 關鍵字,除非這就是指望的影響外部做用域的行爲。網站

局部變量

JavaScript 中局部變量只可能經過兩種方式聲明,一個是做爲函數參數,另外一個是經過 var 關鍵字聲明。ui

// 全局變量
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // 函數 test 內的局部做用域
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

fooi 是函數 test 內的局部變量,而對 bar 的賦值將會覆蓋全局做用域內的同名變量。

變量聲明提高(Hoisting)

JavaScript 會提高變量聲明。這意味着 var 表達式和 function 聲明都將會被提高到當前做用域的頂部。

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

上面代碼在運行以前將會被轉化。JavaScript 將會把 var 表達式和 function 聲明提高到當前做用域的頂部。

// var 表達式被移動到這裏
var bar, someValue; // 缺省值是 'undefined'

// 函數聲明也會提高
function test(data) {
    var goo, i, e; // 沒有塊級做用域,這些變量被移動到函數頂部
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // 出錯:TypeError,由於 bar 依然是 'undefined'
someValue = 42; // 賦值語句不會被提高規則(hoisting)影響
bar = function() {};

test();

沒有塊級做用域不只致使 var 表達式被從循環內移到外部,並且使一些 if 表達式更難看懂。

在原來代碼中,if 表達式看起來修改了所有變量 goo,實際上在提高規則被應用後,倒是在修改局部變量

若是沒有提高規則(hoisting)的知識,下面的代碼看起來會拋出異常 ReferenceError

// 檢查 SomeImportantThing 是否已經被初始化
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

實際上,上面的代碼正常運行,由於 var 表達式會被提高到全局做用域的頂部。

var SomeImportantThing;

// 其它一些代碼,可能會初始化 SomeImportantThing,也可能不會

// 檢查是否已經被初始化
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

譯者注在 Nettuts+ 網站有一篇介紹 hoisting 的文章,其中的代碼頗有啓發性。

// 譯者注:來自 Nettuts+ 的一段代碼,生動的闡述了 JavaScript 中變量聲明提高規則
var myvar = 'my value';  

(function() {  
    alert(myvar); // undefined  
    var myvar = 'local value';  
})();

名稱解析順序

JavaScript 中的全部做用域,包括全局做用域,都有一個特別的名稱 this 指向當前對象。

函數做用域內也有默認的變量 arguments,其中包含了傳遞到函數中的參數。

好比,當訪問函數內的 foo 變量時,JavaScript 會按照下面順序查找:

  1. 當前做用域內是否有 var foo 的定義。
  2. 函數形式參數是否有使用 foo 名稱的。
  3. 函數自身是否叫作 foo
  4. 回溯到上一級做用域,而後從 #1 從新開始。

注意: 自定義 arguments 參數將會阻止原生的 arguments 對象的建立。

命名空間

只有一個全局做用域致使的常見錯誤是命名衝突。在 JavaScript中,這能夠經過 匿名包裝器 輕鬆解決。

(function() {
    // 函數建立一個命名空間

    window.foo = function() {
        // 對外公開的函數,建立了閉包
    };

})(); // 當即執行此匿名函數

匿名函數被認爲是 表達式;所以爲了可調用性,它們首先會被執行。

( // 小括號內的函數首先被執行
function() {}
) // 而且返回函數對象
() // 調用上面的執行結果,也就是函數對象

有一些其餘的調用函數表達式的方法,好比下面的兩種方式語法不一樣,可是效果如出一轍。

// 另外兩種方式
+function(){}();
(function(){}());

結論

推薦使用匿名包裝器譯者注也就是自執行的匿名函數)來建立命名空間。這樣不只能夠防止命名衝突,
並且有利於程序的模塊化。

另外,使用全局變量被認爲是很差的習慣。這樣的代碼傾向於產生錯誤和帶來高的維護成本。

相關文章
相關標籤/搜索