鍍金池/ 教程/ HTML/ 作用域與命名空間
arguments 對象
類型轉換
構造函數(shù)
instanceof 操作符
自動分號插入
為什么不要使用 eval
對象使用和屬性
作用域與命名空間
this 的工作原理
typeof 操作符
相等與比較
閉包和引用
數(shù)組遍歷與屬性
Array 構造函數(shù)
原型
hasOwnProperty 函數(shù)
undefined 和 null
函數(shù)聲明與表達式
setTimeout 和 setInterval
for in 循環(huán)

作用域與命名空間

盡管 JavaScript 支持一對花括號創(chuàng)建的代碼段,但是并不支持塊級作用域; 而僅僅支持函數(shù)作用域

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

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

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

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

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

每次引用一個變量,JavaScript 會向上遍歷整個作用域直到找到這個變量為止。 如果到達全局作用域但是這個變量仍未找到,則會拋出 ReferenceError 異常。

隱式的全局變量

    // 腳本 A
    foo = '42';

    // 腳本 B
    var foo = '42'

上面兩段腳本效果不同。腳本 A 在全局作用域內(nèi)定義了變量 foo,而腳本 B 在當前作用域內(nèi)定義變量 foo。

再次強調(diào),上面的效果完全不同,不使用 var 聲明變量將會導致隱式的全局變量產(chǎn)生。

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

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

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

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

外部循環(huán)在第一次調(diào)用 subLoop 之后就會終止,因為 subLoop 覆蓋了全局變量 i。 在第二個 for 循環(huán)中使用 var 聲明變量可以避免這種錯誤。 聲明變量時絕對不要遺漏 var 關鍵字,除非這就是期望的影響外部作用域的行為。

局部變量

JavaScript 中局部變量只可能通過兩種方式聲明,一個是作為函數(shù)參數(shù),另一個是通過 var 關鍵字聲明。

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

    function test(i) {
        // 函數(shù) test 內(nèi)的局部作用域
        i = 5;

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

fooi 是函數(shù) test 內(nèi)的局部變量,而對 bar 的賦值將會覆蓋全局作用域內(nèi)的同名變量。

變量聲明提升

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'

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

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

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

    test();

沒有塊級作用域不僅導致 var 表達式被從循環(huán)內(nèi)移到外部,而且使一些 if 表達式更難看懂。

在原來代碼中,if 表達式看起來修改了全局變量 goo,實際上在提升規(guī)則被應用后,卻是在修改局部變量。

如果沒有提升規(guī)則(hoisting)的知識,下面的代碼看起來會拋出異常 ReferenceError

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

實際上,上面的代碼正常運行,因為 var 表達式會被提升到全局作用域的頂部。

    var SomeImportantThing;

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

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

譯者注在 Nettuts+ 網(wǎng)站有一篇介紹 hoisting 的文章,其中的代碼很有啟發(fā)性。

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

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

名稱解析順序

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

函數(shù)作用域內(nèi)也有默認的變量arguments,其中包含了傳遞到函數(shù)中的參數(shù)。

比如,當訪問函數(shù)內(nèi)的 foo 變量時,JavaScript 會按照下面順序查找:

  1. 當前作用域內(nèi)是否有 var foo 的定義。
  2. 函數(shù)形式參數(shù)是否有使用 foo 名稱的。
  3. 函數(shù)自身是否叫做 foo。
  4. 回溯到上一級作用域,然后從 #1 重新開始。

注意: 自定義 arguments 參數(shù)將會阻止原生的 arguments 對象的創(chuàng)建。

命名空間

只有一個全局作用域?qū)е碌某R婂e誤是命名沖突。在 JavaScript中,這可以通過 匿名包裝器 輕松解決。

    (function() {
        // 函數(shù)創(chuàng)建一個命名空間

        window.foo = function() {
            // 對外公開的函數(shù),創(chuàng)建了閉包
        };

    })(); // 立即執(zhí)行此匿名函數(shù)

匿名函數(shù)被認為是表達式;因此為了可調(diào)用性,它們首先會被執(zhí)行。

    ( // 小括號內(nèi)的函數(shù)首先被執(zhí)行
    function() {}
    ) // 并且返回函數(shù)對象
    () // 調(diào)用上面的執(zhí)行結果,也就是函數(shù)對象

有一些其他的調(diào)用函數(shù)表達式的方法,比如下面的兩種方式語法不同,但是效果一模一樣。

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

結論

推薦使用匿名包裝器譯者注也就是自執(zhí)行的匿名函數(shù))來創(chuàng)建命名空間。這樣不僅可以防止命名沖突, 而且有利于程序的模塊化。

另外,使用全局變量被認為是不好的習慣。這樣的代碼容易產(chǎn)生錯誤并且維護成本較高。

上一篇:typeof 操作符下一篇:instanceof 操作符