JavaScript作用域?qū)W習筆記

總結(jié)來說很簡單:

  • 函數(shù)運行在定義時的作用域中
  • 變量查找會從當前作用域開始查找,找不到則到下一層作用域查找,直到找到并返回或者返回 undefined

實際例子

var name = "global"
function echo() {
  console.log(name)
}

echo()

打印global毫無疑問,因為在echo()函數(shù)作用域沒有找到,就到外層作用域中尋找,找到了值為globalname變量。

如何查找

在調(diào)用某個函數(shù)時,會從函數(shù)這個對象上拿到[[scope]]屬性對應(yīng)的作用域鏈,并且將函數(shù)的變量對象也放入到這個作用域鏈中,而該作用域鏈是在函數(shù)定義時就確定了的。

同樣是上面的例子,作用域鏈的變化是這樣的:

// 調(diào)用 echo() 函數(shù)前
scopes = {
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

重點在echo對象上有[[scope]]屬性,指向的就是最外面的這個scopes,即作用域鏈。

在調(diào)用echo()后,會將該函數(shù)的變量對象:

{
  this: Window
}

放到echo對象的[[scope]]屬性對應(yīng)的scopes上,那就變成了這樣:

// 調(diào)用 echo() 時
scopes = {
  1: {
    this: Window
  },
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

echo函數(shù)內(nèi)尋找name就是指會先在

{
  this: Window
}

這個變量對象上尋找名為name的鍵,如果沒有找到就向下(將 0 視為下)尋找,所以就會在:

{
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
}

這個變量對象上尋找,然后找到了值為globalname并返回。

進階問題

嘗試解答下面代碼會打印出什么?


var name = "global"

function echo () {
  console.log(name) // 打印什么?
}  

function change() {
  var name = "local"
  echo()
}

change() 

具體分析

按照上面的分析過程,在調(diào)用change()之前,作用域鏈應(yīng)該是這樣的:

// 調(diào)用 change() 前
scopes = {
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    change: {
      name: 'change',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

多了change這個鍵值對,其他沒什么,然后調(diào)用change函數(shù):

// 調(diào)用 change 函數(shù)時
scopes = {
  1: {
    name: 'local',
    this: Window
  },
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    change: {
      name: 'change',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

如果在change函數(shù)內(nèi)尋找name變量,找到的肯定是local毫無疑問。最后是echo函數(shù)的調(diào)用,也是這段代碼的意義所在,調(diào)用時是這樣的:

// 調(diào)用 echo 函數(shù)時
scopes = {
  1: {
    this: Window
  },
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    change: {
      name: 'change',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

所以在echo函數(shù)內(nèi)尋找不到name,就到下層變量對象找,找到了global。所以這段代碼最終是打印出了global。

圖片可能更為直觀,灰色塊為作用域鏈,即scopes,綠色塊為變量對象,如下所示 ↓

模擬流程

淺拷貝?

雖然到目前為止能夠解釋絕大部分的作用域問題,但還存在疑問:

[[scope]]: scopes

這里是指

在一個函數(shù)被定義的時候, 會將它定義時刻的scope chain鏈接到這個函數(shù)對象的[[scope]]屬性。

我們都知道,對象是引用類型,那從 2 -> 3 的過程中,尤其是echo函數(shù)執(zhí)行時,先從echo函數(shù)上拿到[[scope]]屬性,這時候不應(yīng)該是:

scopes = {
  1: {
    name: 'local',
    this: Window
  },
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    change: {
      name: 'change',
      function: function () {console.log(name)},
      [[scope]]: scopes
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

這樣的嗎,因為[[scope]]保存的是scopes的內(nèi)存地址啊,所以我能夠自己解釋為

在函數(shù)定義時,將scopes進行淺拷貝并保存到[[scope]]屬性上。

所以上面的例子,作用域鏈嚴格來說應(yīng)該是這樣的:

scopes = {
  0: {
    name: 'global',
    echo: {
      name: 'echo',
      function: function () {console.log(name)},
      [[scope]]: {
        0: {...}
      }
    },
    change: {
      name: 'change',
      function: function () {console.log(name)},
      [[scope]]: {
         0: {...}
      }
    },
    console,
    parseInt: function () {...}
    // 等一些全局變量與方法
  }
}

但是真的是這樣嗎?

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容