1. 程式人生 > >變量作用域與解構賦值

變量作用域與解構賦值

方法 ber undefine 作用域 定義變量 模式 變量 bob def

在JavaScript中,用var申明的變量實際上是有作用域的。

如果一個變量在函數體內部申明,則該變量的作用域為整個函數體,在函數體外不可引用該變量:

‘use strict‘;
function foo() {
    var x = 1;
    x = x + 1;
}

x = x + 2; // ReferenceError! 無法在函數體外引用變量x

如果兩個不同的函數各自申明了同一個變量,那麽該變量只在各自的函數體內起作用。換句話說,不同函數內部的同名變量互相獨立,互不影響:

‘use strict‘;

function foo() {
    var x = 1;
    x = x + 1;
}

function bar() {
    var x = ‘A‘;
    x = x + ‘B‘;
}

由於JavaScript的函數可以嵌套,此時,內部函數可以訪問外部函數定義的變量,反過來則不行:

‘use strict‘;
function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar可以訪問foo的變量x!
    }
    var z = y + 1; // ReferenceError! foo不可以訪問bar的變量y!
}

如果內部函數和外部函數的變量名重名怎麽辦?來測試一下:

‘use strict‘;

function foo() {
var x = 1;
function bar() {
var x = ‘A‘;
console.log(‘x in bar() = ‘ + x); // ‘A‘
}
console.log(‘x in foo() = ‘ + x); // 1
bar();
}

foo();

結果:x in foo() = 1
x in bar() = A

這說明JavaScript的函數在查找變量時從自身函數定義開始,從“內”向“外”查找。如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量。

變量提升

JavaScript的函數定義有個特點,它會先掃描整個函數體的語句,把所有申明的變量“提升”到函數頂部:

‘use strict‘;

function foo() {
    var x = ‘Hello, ‘ + y;
    console.log(x);
    var y = ‘Bob‘;
}

foo();

雖然是strict模式,但語句var x = ‘Hello, ‘ + y;

並不報錯,原因是變量y在稍後申明了。但是console.log顯示Hello, undefined,說明變量y的值為undefined。這正是因為JavaScript引擎自動提升了變量y的聲明,但不會提升變量y的賦值。

對於上述foo()函數,JavaScript引擎看到的代碼相當於:

function foo() {
    var y; // 提升變量y的申明,此時y為undefined
    var x = ‘Hello, ‘ + y;
    console.log(x);
    y = ‘Bob‘;
}

由於JavaScript的這一怪異的“特性”,我們在函數內部定義變量時,請嚴格遵守“在函數內部首先申明所有變量”這一規則。最常見的做法是用一個var申明函數內部用到的所有變量:

function foo() {
    var
        x = 1, // x初始化為1
        y = x + 1, // y初始化為2
        z, i; // z和i為undefined
    // 其他語句:
    for (i=0; i<100; i++) {
        ...
    }
}

全局作用域

不在任何函數內定義的變量就具有全局作用域。實際上,JavaScript默認有一個全局對象window,全局作用域的變量實際上被綁定到window的一個屬性:

‘use strict‘;

var course = ‘Learn JavaScript‘;
alert(course); // ‘Learn JavaScript‘
alert(window.course); // ‘Learn JavaScript‘

因此,直接訪問全局變量course和訪問window.course是完全一樣的。

你可能猜到了,由於函數定義有兩種方式,以變量方式var foo = function () {}定義的函數實際上也是一個全局變量,因此,頂層函數的定義也被視為一個全局變量,並綁定到window對象:

‘use strict‘;

function foo() {
    alert(‘foo‘);
}

foo(); // 直接調用foo()
window.foo(); // 通過window.foo()調用

名字空間

全局變量會綁定到window上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名沖突,並且很難被發現。

減少沖突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:

// 唯一的全局變量MYAPP:
var MYAPP = {};

// 其他變量:
MYAPP.name = ‘myapp‘;
MYAPP.version = 1.0;

// 其他函數:
MYAPP.foo = function () {
    return ‘foo‘;
};

把自己的代碼全部放入唯一的名字空間MYAPP中,會大大減少全局變量沖突的可能。

許多著名的JavaScript庫都是這麽幹的:jQuery,YUI,underscore等等。

局部作用域

由於JavaScript的變量作用域實際上是函數內部,我們在for循環等語句塊中是無法定義具有局部作用域的變量的:

‘use strict‘;

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用變量i
}

為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量:

‘use strict‘;

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    i += 1;
}
 

變量作用域與解構賦值