【每日一題】說下你對變量的作用域鏈的理解

人生苦短,總需要一點儀式感。比如學前端~
全局變量和局部變量
“全局變量”指的是定義在所有函數(shù)之外的變量(也就是定義在全局代碼中的變量)
“局部變量”與之相對,所指的是在某個函數(shù)中定義的變量。
其中,函數(shù)內的代碼可以訪問自己上層函數(shù)的變量,也可以訪問全局變量,這樣就構成了作用域鏈。
特殊的:隱式聲明一個變量(沒有使用 var 等語句就聲明的變量),該變量不管在哪個作用域里,就會被默認為是全局變量。
作用域鏈
JavaScript 引擎執(zhí)行一段代碼時,會創(chuàng)建對應的執(zhí)行上下文并推入到執(zhí)行棧中。
查找一個變量的時候,會先從當前代碼所在的上下文的環(huán)境記錄中查找,
如果找到,直接返回這個變量的值;
如果沒有找到,就會到上一級執(zhí)行上下文的環(huán)境記錄中查找,找不到會一直向上,直到全局上下文的環(huán)境記錄。
這樣有多個執(zhí)行上下文的環(huán)境記錄構成的鏈表,就叫做作用域鏈。
當前執(zhí)行上下文 - 上一級 - 上上級 - …… - 全局上下文。
作用域鏈包含了執(zhí)行環(huán)境有權訪問的變量、函數(shù)的有序訪問。它是一個由變量對象(VO/AO)組成的單項鏈表,主要用來進行變量查找。
js 內部有一個[[scope]]屬性,這個屬性就是指向作用域鏈的頂端
var val = "全局變量";
function AA(y) {
var val = "局部變量";
function BB() {
var z = 0;
alert(val);
}
BB();
}
AA(5);
簡單分析上面的流程:
全局執(zhí)行環(huán)境:[[scope]] ---> VO[AA,val],只有全局VO, [[scope]]直接指向VO。
函數(shù) AA 執(zhí)行環(huán)境:[[scope]] ---> VO[y,BB,val, VO[AA,val]],首先全局VO壓入棧底,然后函數(shù)AA VO壓入棧頂,[[scope]] 屬性指向棧頂,變量、函數(shù)搜索就從棧頂開始。
函數(shù) BB 執(zhí)行環(huán)境:[[scope]]---> VO[z, VO[y,BB,val], VO[AA,val]],首先全局 VO 壓入棧底,然后依次 AA、BB 壓入棧,BB 處于棧頂,[[scope]]屬性直接指向 BB 的 VO。
應用場景:比如調用 BB,進入 BB 的執(zhí)行環(huán)境,在執(zhí)行 alert 的時候,首先會去查找 val 的聲明,會先在作用域鏈的頂端查找,沒查到就會沿著作用域鏈繼續(xù)往下查找,直到查找到AA的變量對象就停止。
總結:
函數(shù)執(zhí)行的時候,就將當前函數(shù)的VO放在鏈表開頭,后面依次是上層函數(shù),最后是全局對象。變量查找則依次從鏈表的頂端開始。
js有個內部[[scope]],這個屬性包含了函數(shù)的作用域對象的集合,這個集合就稱為函數(shù)的作用域鏈。它決定了哪些變量或者函數(shù)能在當前函數(shù)中被訪問以及它的訪問順序。
VO 和 AO
VO:全局變量對象(Varibale Object) , 指向全局對象window,包含定義的全局變量。AO:活動變量對象(Activation Object),其實也是變量對象,可以理解為VO在函數(shù)中的叫法。不過他除了包含局部的變量,還包括函數(shù)內部的形參、arguments對象、this對象等。
區(qū)分作用域與this
變量的作用域區(qū)別于 this指針:變量作用域是靜態(tài)的,在變量聲明后就確定的,也就是說變量聲明在哪里,他的作用域就是哪里(特殊一點:沒有用關鍵詞聲明的是全局);
而 this指針 則是動態(tài)的,根據(jù)最后的調用情況判斷其指向誰,甚至根據(jù)call、apply、bind、new等影響能被手動改變。

