深刻理解javascript系列(六):做用域與做用域鏈

在javascript中,做用域是用來規範變量函數可訪問範圍的一套規則javascript

6.1  做用域

最多見的做用域有兩種:全局做用域與函數做用域。

6.1.1 全局做用域

全局做用域中聲明的變量與函數能夠在項目代碼的任何地方使用。前端

通常來講,如下3種狀況能夠擁有全局做用域java

1.  全局對象下擁有的屬性與方法。(回憶一下,咱們在變量對象系列說過的全局上下文的特殊性)數組

window.name
window.location
window.top
...複製代碼

2.  在最外層聲明的變量與方法性能優化

咱們知道,全局上下文中的變量對象就是全局對象window,因此在全局上下文中聲明的變量與方法其實也就是window的屬性與方法(記着:javascript中聲明的全部變量都保存在變量對象中),因此他們也擁有全局做用域。bash

3.  在非嚴格模式下,函數做用域中那些未定義卻賦值的變量與方法都會自動變成window的屬性與方法。微信

在實踐中,不管是從避免多人協做帶來的衝突的角度考慮,仍是從性能優化的角度考慮,咱們都要儘量少地自定義全局變量和方法。閉包

6.1.2 函數做用域

函數做用域中聲明的變量與方法,只能被下一層子做用域訪問,而不能被其它不相干的做用域訪問。模塊化

function foo() {
    var a = 20;
    var b = 30;
}
foo();

function bar() {
    return a + b;
}
bar();    //由於做用域的限制,bar中沒法訪問到變量a和b,所以執行報錯複製代碼

function foo() {
    var a = 20;
    var b = 30;

    function bar() {        return a + b;
    }
    return bar();

}foo();    //50 bar中的做用域爲foo的子做用域,所以能訪問到變量a和b複製代碼

在ES6之前(如今ES2018已出),ECMAScript沒有塊級做用域,所以使用時須要特別注意,必定是在函數環境中才能生成新的做用域,下面的狀況則不會有做用域的限制。函數

var arr = [1,2,3,4,5];

for(var i = 0; i<arr.length; i++) {
    console.log('i',i);
}
console.log(i);   // i == 5複製代碼

由於沒有塊級做用域,所以單獨的'{}'並不會產生新的做用域。這個時候i的值會被保留下來,在for循環結束後仍然可以被訪問。所以,在ES6以前咱們須要模擬塊級做用域。

6.1.3  模擬塊級做用域

若是沒有塊級做用域則會給咱們的開發帶來一些困擾(初學javascript的如寫個選項卡)。例如,上面的for循環的例子中,i值在做用域中仍然能夠被訪問,那麼這個值就會對做用域中其它同名的變量形成干擾,所以咱們須要模擬一個塊級做用域。咱們知道一個函數可以生成一個做用域,所以這個時候,能夠利用函數來達到咱們的目的。

var arr = [1,2,3,4,5];

(function(){
    for(var i = 0; i<arr.length; i++) {    console.log('i',i);
}})()

console.log(i);   // i is not defined複製代碼

這種方式叫作函數自執行。

經過這種方式,咱們就能夠限定變量i值僅僅只在for循環中生效,而不會對其它代碼形成干擾。

自執行函數的寫法有不少,這裏就不作累述。

當咱們使用ECMAScript5時,每每經過函數自執行的方式來實現模塊化。而模塊化是實際開發中須要重點掌握的開發思惟(模塊化相關話題我會在以後分享本身的筆記)。

6.2  做用域鏈

做用域鏈(Scope Chain)是當前執行環境與上層執行環境的一系列變量對象組成的,它保證了當前執行環境對符合訪問權限變量和函數的有序訪問。

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }
    
    return innerTest();
}
test();複製代碼

請先按照call stack的調用方式,在你的大腦中執行本次代碼。

在上面的例子中,前後建立了全局函數test和函數innerTest的執行上下文(當test(),表示其執行上下文入棧)。假設它們的變量對象分別爲VO(global)、VO(test)、VO(innerTest),那麼innerTest的做用域鏈則同時包含了這三個變量對象。

因此innerTest的執行上下文可表示以下。

innerTestEC = {
    VO: {...},                                        //變量對象
    scopeChain: [VO(innerTest),VO(test),VO(global)],  //做用域鏈
    this: {}
}複製代碼

能夠用一個數組來表示做用域鏈的有序性。數組的第一項scopeChain[0]爲做用域鏈的最前端,而數組的最後一項則爲做用域鏈的最末端。全部做用域鏈的最末端都是全局變量對象。

不少人會用父子關係或者包含關係來理解當前做用域與上層做用域之間的關係。但我更喜歡@陽波大神的描述:以當前上下文的變量對象爲起點,以全局變量對象爲終點的單方向通道。


理解做用域鏈相當重要,可是更多的知識還須要結合閉包來理解。

這些都是我以往的學習筆記。若是您看到此筆記,但願您能指出個人錯誤。有這麼一個羣,裏面的小夥伴互相監督,堅持天天輸出本身的學習心得,不輸出就出局。但願您能加入,咱們一塊兒終身學習。歡迎添加個人我的微信號:Pan1005919589

相關文章
相關標籤/搜索