首頁  >  文章  >  web前端  >  JavaScript進階系列—作用域與命名空間

JavaScript進階系列—作用域與命名空間

黄舟
黄舟原創
2017-02-08 09:35:361310瀏覽
  • 隱式的全域變數

  • 局部變數

  • 變數宣告提升(Hoisting)

  • 名稱順序解析

  • 儘管JavaScript 支援一對花括號建立的程式碼段,但是並不支援區塊級作用域; 而僅支援函數作用域。
  • function test() { // 一个作用域
        for(var i = 0; i < 10; i++) { // 不是一个作用域
            // count
        }
        console.log(i); // 10
    }

    譯者註:如果 return 物件的左括號和 return 不在一行上就會出錯。

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

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

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

上面兩段腳本效果不同。腳本 A 在全域作用域內定義了變數 foo,而腳本 B 在目前作用域內定義變數 foo。


再次強調,上面的效果完全不同,不使用 var 宣告變數將會導致隱式的全域變數產生。

// 脚本 A
foo = &#39;42&#39;;

// 脚本 B
var foo = &#39;42&#39;

在函數 test 內不使用 var 關鍵字宣告 foo 變數將會覆寫外部的同名變數。 起初這看起來並不是大問題,但是當有成千上萬行程式碼時,不使用 var 宣告變數將會帶來難以追蹤的 BUG。

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

外部迴圈在第一次呼叫 subLoop 之後就會終止,因為 subLoop 覆寫了全域變數 i。 在第二個 for 迴圈中使用 var 宣告變數可以避免這種錯誤。 宣告變數時絕對不要遺漏 var 關鍵字,除非這就是期望的影響外部作用域的行為。

局部變數

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

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

function subLoop() {
    // subLoop 函数作用域
    for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
        // 干活
    }
}

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

變數宣告提升(Hoisting)


JavaScript 會提升變數宣告。這表示 var 表達式和 function 宣告都會被提升到目前作用域的頂端。

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

function test(i) {
    // 函数 test 内的局部作用域
    i = 5;

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

上面程式碼在運作前將會被轉換。 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];
    }
}

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

在原來程式碼中,if 表達式看起來修改了全域變數 goo,實際上在提升規則被應用後,卻是在修改局部變數。

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

// var 表达式被移动到这里
var bar, someValue; // 缺省值是 &#39;undefined&#39;

// 函数声明也会提升
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 依然是 &#39;undefined&#39;
someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
bar = function() {};

test();

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

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

譯者註:在 Nettuts+ 網站上有一篇介紹 hoisting 的文章,其中的程式碼很有啟發性。

var SomeImportantThing;

// 其它一些代码,可能会初始化 SomeImportantThing,也可能不会

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

名稱解析順序

JavaScript 中的所有作用域,包括全域作用域,都有一個特別的名稱 this 指向目前物件。函數作用域內也有預設的變數 arguments,其中包含了傳遞到函數中的參數。例如,當存取函數內的 foo 變數時,JavaScript 會依照下面順序來找出:

目前作用域內是否有 var foo 的定義。


函數形式參數是否有使用 foo 名稱的。


函數本身是否叫做 foo。

  1. 回溯到上一級作用域,然後從 #1 重新開始。

  2. 命名空間

    只有一個全局作用域導致的常見錯誤是命名衝突。在 JavaScript中,這可以透過 匿名包裝器 輕鬆解決。
  3. (注意: 自訂 arguments 參數將會阻止原生的 arguments 物件的建立。)

    // 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
    var myvar = &#39;my value&#39;;  
    
    (function() {  
        alert(myvar); // undefined  
        var myvar = &#39;local value&#39;;  
    })();

    匿名函數被認為是 表達式;因此為了可調用性,它們首先會被執行。
  4. (function() {
        // 函数创建一个命名空间
    
        window.foo = function() {
            // 对外公开的函数,创建了闭包
        };
    
    })(); // 立即执行此匿名函数
  5. 有一些其他的呼叫函數表達式的方法,例如下面的兩種方式語法不同,但是效果一模一樣。

    ( // 小括号内的函数首先被执行
    function() {}
    ) // 并且返回函数对象
    () // 调用上面的执行结果,也就是函数对象
  6. 結論

推薦使用匿名包裝器(譯者註:也就是自執行的匿名函數)來建立命名空間。這樣不僅可以防止命名衝突, 而且有利於程式的模組化。

另外,使用全域變數被認為是不好的習慣。這樣的程式碼容易產生錯誤且維護成本較高。

以上就是JavaScript進階系列—作用域與命名空間的內容,更多相關內容請關注PHP中文網(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn