淺談JavaScript執(zhí)行上下文與作用域馍驯,作用域鏈

???????JavaScript中執(zhí)行上下文阁危,變量對(duì)象,作用域與作用域鏈等是一系列基礎(chǔ)性的且十分重要的概念汰瘫。他們之間有著千絲萬縷的聯(lián)系狂打,深入理解這些概念以及他們之間的聯(lián)系有助于我們理解在js開發(fā)中遇到一些問題,例如:

1. es6之前混弥,使用var聲明的變量為什么會(huì)存在變量提升趴乡,過程是什么樣的,es6為什么新增let和const?
2. 塊級(jí)作用域解決了什么問題蝗拿?
3. this指向是什么時(shí)候確定晾捏,怎么確定的?

一.執(zhí)行上下文

???????執(zhí)行上下文哀托,通俗的講就是當(dāng)前js代碼的執(zhí)行環(huán)境惦辛。那如何確定當(dāng)前代碼的執(zhí)行環(huán)境?這里則要提到另一個(gè)概念仓手,執(zhí)行環(huán)境棧胖齐。JavaScript引擎在執(zhí)行代碼之前, 首先會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境棧. 然后創(chuàng)建全局執(zhí)行環(huán)境并將它壓入棧中作為棧底。之后每遇到一個(gè)函數(shù)執(zhí)行時(shí)嗽冒,都會(huì)為該函數(shù)創(chuàng)建執(zhí)行上下文呀伙。并將其推入執(zhí)行環(huán)境棧中。JavaScript中的運(yùn)行環(huán)境主要是全局環(huán)境和局部環(huán)境(函數(shù))添坊。棧遵循先入后出的原則剿另,因此處于棧頂?shù)膱?zhí)行環(huán)境將優(yōu)先執(zhí)行。棧底永遠(yuǎn)是全局環(huán)境,當(dāng)瀏覽器窗口關(guān)閉驰弄,全局環(huán)境才會(huì)出棧麻汰。當(dāng)調(diào)用一個(gè)函數(shù)時(shí),一個(gè)新的執(zhí)行上下文就會(huì)被創(chuàng)建戚篙。一個(gè)執(zhí)行上下文的生命周期可以分為如下幾個(gè)階段:

1 創(chuàng)建階段

???????在這個(gè)階段中五鲫,執(zhí)行上下文會(huì)分別創(chuàng)建變量對(duì)象,確定作用域鏈岔擂,確定this指向(即this指向是在函數(shù)被調(diào)用時(shí)才被確定)位喂。

2 代碼執(zhí)行階段

???????完成變量賦值,以及執(zhí)行其他代碼乱灵。

3 銷毀階段

???????函數(shù)執(zhí)行完畢后, 執(zhí)行環(huán)境棧便會(huì)把這個(gè)函數(shù)的執(zhí)行環(huán)境彈出, 并將控制權(quán)返回給之前的執(zhí)行環(huán)境塑崖。上面提到 執(zhí)行上下文有3個(gè)重要屬性:

  • 1.變量對(duì)象(VO)
  • 2.作用域鏈(Scope chain)
  • 3.this
    下面做逐一分析。

二.變量對(duì)象痛倚。

???????每個(gè)執(zhí)行環(huán)境都有一個(gè)與之相關(guān)聯(lián)的對(duì)象规婆,執(zhí)行環(huán)境中聲明的變量和函數(shù)都在其中,不能直接訪問該對(duì)象蝉稳,這個(gè)對(duì)象就是變量對(duì)象(VO)抒蚜。由此我們便知道VO中存儲(chǔ)了在執(zhí)行環(huán)境中定義的變量和函數(shù)聲明.通常情況下,一個(gè)VO對(duì)象有以下信息:

  • 變量
  • 函數(shù)
  • 形參

變量對(duì)象的創(chuàng)建會(huì)經(jīng)歷以下幾個(gè)過程。

一耘戚、建立arguments對(duì)象:檢查當(dāng)前上下文中的參數(shù)嗡髓,建立該對(duì)象下的屬性與屬性值。
二收津、檢查當(dāng)前上下文的函數(shù)聲明饿这,在變量對(duì)象中以函數(shù)名建立一個(gè)屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的指針撞秋。
三长捧、檢查當(dāng)前上下文中的變量聲明,每找到一個(gè)變量聲明部服,就在變量對(duì)象中以變量名建立一個(gè)屬性唆姐,屬性值為undefined(變量提升),const/let 聲明的變量沒有賦值廓八,不能提前使用奉芦,在當(dāng)前階段(執(zhí)行上下文創(chuàng)建)若 var 聲明的變量與函數(shù)同名,以函數(shù)值為準(zhǔn)剧蹂。

???????需要注意的是声功,未進(jìn)入執(zhí)行階段之前,變量對(duì)象中的屬性都不能訪問宠叼。但是進(jìn)入執(zhí)行階段之后先巴,變量對(duì)象轉(zhuǎn)變?yōu)榱?strong>活動(dòng)對(duì)象(AO)其爵,里面的屬性都能被訪問了,然后開始進(jìn)行執(zhí)行階段的操作伸蚯。所以活動(dòng)對(duì)象實(shí)際就是變量對(duì)象在真正執(zhí)行時(shí)的另一種形式摩渺。
下面用一段偽代碼來描述一下變量對(duì)象的存在形式。

function foo(){
    function bar (){
        console.log(name)
    }
    bar()

    var name = '小明'
    
}
foo()
/* foo調(diào)用時(shí)剂邮,其執(zhí)行上下文被創(chuàng)建
foo.EC = {
    VO:{ //變量對(duì)象
        this:window, //獨(dú)立調(diào)用摇幻,非嚴(yán)格模式下指向window(瀏覽器環(huán)境)
        arguments:[],函數(shù)參數(shù)
        bar: function 
        name: undefined //var聲明的變量 將被提升并賦值為undefined
    },
    [[scope chain]]:{}  // 作用域鏈 下一章節(jié)做詳細(xì)說明
} 
 */

???????需要注意一點(diǎn),let和const聲明的變量挥萌,在被變量對(duì)象收集后不會(huì)被賦值為undefined绰姻,因此不能在其聲明前調(diào)用。const引瀑、let會(huì)在聲明的地方到塊級(jí)頂部形成暫時(shí)性死區(qū)狂芋,在這區(qū)間使用該變量都會(huì)報(bào)錯(cuò)。
???????另外憨栽,全局執(zhí)行上下文比較特殊帜矾,它的變量對(duì)象,就是window對(duì)象(瀏覽器環(huán)境)徒像。this也是指向window黍特。

三.作用域與作用域鏈。

1.作用域

???????通俗的講即當(dāng)前執(zhí)行上下文中的變量和函數(shù)的可訪問(可查找)范圍锯蛀。它可以理解為一種約束或一套規(guī)則,定義在當(dāng)前作用域中的變量和函數(shù)在外部是不可訪問的次慢,例如我們在函數(shù)中聲明的變量一般情況下在函數(shù)外是無法訪問的(特殊情況后面會(huì)介紹)旁涤。
???????在es6之前,作用域有兩種迫像,即全局作用域和局部作用域(函數(shù)作用域)劈愚。es6加入了塊級(jí)作用域,什么是塊闻妓?任何一對(duì)花括號(hào)中的語句集都屬于一個(gè)塊菌羽,在這其中定義的所有變量在塊外都是不可見的,我們稱之為塊級(jí)作用域由缆。由于es6之前沒有塊級(jí)作用域注祖,因此塊中聲明的變量在塊外仍然是可以訪問的,這就會(huì)造成一些問題例如:

  • 1.變量提升導(dǎo)致局部作用域的變量覆蓋全局變量均唉。
 var name = '小明'
 function foo(){
   
   console.log(name) // undefined   var定義的變量是晨,沒有塊的概念,因此if代碼塊中的name可以被外部訪問舔箭。被提升并被賦值為undefined
   
   if (true)
   {
     var name = '小紅'
   }
   
 }
 foo() //

而有了塊級(jí)作用域罩缴,則不會(huì)有這個(gè)問題。可以使用let或const聲明塊級(jí)變量來解決上述問題箫章。

 var name = '小明'
 function foo(){
   
   console.log(name) // 小明
   
   if (true)
   {
     const name = '小紅'
   }
   
 }
 foo() //
  • 2.循環(huán)中的計(jì)數(shù)變量泄露成全局變量
for (var i = 0; i < 5; i++) {
    console.log(i)
}
console.log(i) // 5 當(dāng)i=5時(shí)循環(huán)結(jié)束烙荷,此時(shí)外部可以訪問到i

 
for (let i = 0; i < 5; i++) {
    console.log(i)
}
console.log(i) // i is not defined 塊級(jí)變量外部無法訪問

由此可見有了塊級(jí)作用域,之前用立即執(zhí)行函數(shù)生成私有作用域的做法已經(jīng)不再需要了檬寂。

2.作用域鏈

???????一般情況下當(dāng)前執(zhí)行上下文的變量取值會(huì)到當(dāng)前上下文的變量對(duì)象中取值奢讨。 但是如果在當(dāng)前變量對(duì)象中沒有查到值,就會(huì)向上層環(huán)境中變量對(duì)象的去查,直到查到全局作用域,這個(gè)查找過程形成的鏈條就叫做作用域鏈。由此可見作用域鏈?zhǔn)怯僧?dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象組成的焰薄。它和執(zhí)行上下文有關(guān),用于在處理標(biāo)識(shí)符(變量名或函數(shù)名)的時(shí)候進(jìn)行變量查詢拿诸。作用域鏈在函數(shù)調(diào)用的時(shí)候創(chuàng)建出來, 它包含了活動(dòng)對(duì)象(變量對(duì)象在執(zhí)行階段變?yōu)榛顒?dòng)對(duì)象)和該函數(shù)的內(nèi)部[[Scope]]屬性。函數(shù)的Scope 屬性是函數(shù)定義的時(shí)候創(chuàng)建的塞茅,這個(gè)屬性對(duì)應(yīng)的是一個(gè)對(duì)象列表亩码。該列表中存儲(chǔ)著與之相關(guān)聯(lián)作用域的變量對(duì)象,該對(duì)象僅能js內(nèi)部訪問野瘦。因此描沟,如果用一個(gè)數(shù)組scopeList來模擬作用域鏈,則scopeList[0]即代表當(dāng)前上下文的活動(dòng)對(duì)象鞭光,其余元素[scope]屬性中的所有對(duì)象的順序排列吏廉。該數(shù)組的最末端是全局變量對(duì)象。
下面用一段偽代碼描述作用域鏈的存在形式惰许。

var a = 1
function foo() {
    var b = 2
    console.log(a)
    function bar() {
        var c = 3
        console.log(b)
    }
    bar()
}
foo()
/* 
 js默認(rèn)進(jìn)入全局執(zhí)行環(huán)境 
 foo的定義階段
 foo.[[scope]] = { 該屬性保存了與該函數(shù)相關(guān)的變量對(duì)象席覆,顯然
                   foo的上層環(huán)境是window,window變量對(duì)象沒有arguments屬性
    GO:{
        this:window,
        window:{...},
        a:undefined
    }
 }
 
 foo進(jìn)入執(zhí)行階段  bar函數(shù)才會(huì)被定義
 此時(shí)由于foo函數(shù)已經(jīng)執(zhí)行汹买,因此其變量對(duì)象已經(jīng)被創(chuàng)建
 bar.[[scope]] = {
     AO(foo):{
         this:window, 函數(shù)被調(diào)用時(shí)確定this指向佩伤,顯然foo為獨(dú)立調(diào)用,非嚴(yán)格模式下this指向window
         arguments:[],
         b:2
     },
     GO:{
        this:window,
        window:{...},
        document:{...},
        a:1
    }
 }
*/
// 執(zhí)行階段
// foo函數(shù)調(diào)用時(shí)
/* 
foo.EC = { //foo函數(shù)的執(zhí)行上下文
    AO:{
        this:window,
        arguments:[],
        b:2,
        bar:function
    },
    [[scope chain]]:{ foo的作用域鏈
        AO:AO, // 推入作用域鏈頂部的活動(dòng)對(duì)象
        GO:{....} // [[scope]]中的全局對(duì)象
    } 
}
bar.EC = {
    AO:{
        this:window,
        arguments:[],
        c:3
    },
    [[scope chain]]:{
        AO(bar):AO,
        AO(foo):{
         this:window,
         arguments:[],
         b:2
        },
        GO:{
            this:window,
            window:{...},
            document:{...},
            a:1
        }
    }
}
*/

???????簡單總結(jié)一下晦毙,函數(shù)定義的時(shí)生巡,會(huì)創(chuàng)建一個(gè)[[scope]]屬性 。這個(gè)屬性對(duì)應(yīng)的是與當(dāng)前函數(shù)相關(guān)執(zhí)行環(huán)境的變量對(duì)象列表见妒。 函數(shù)調(diào)用時(shí)孤荣,當(dāng)前函數(shù)的執(zhí)行上下文被創(chuàng)建,創(chuàng)建變量對(duì)象须揣,確定作用域鏈 盐股,確定this指向。 變量對(duì)象主要保存當(dāng)前上下文的參數(shù)返敬,變量以及函數(shù)遂庄。作用域鏈頂部是當(dāng)前上下文的活動(dòng)對(duì)象,其余元素是scope屬性的對(duì)象列表劲赠,最底層是全局變量對(duì)象涛目。而this指向則主要看是誰調(diào)用的該函數(shù)(或者說函數(shù)調(diào)用時(shí)被哪個(gè)對(duì)象所擁有)秸谢。

參考:
https://www.bilibili.com/video/BV1hE411A7D3
http://www.reibang.com/p/330b1505e41d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霹肝,隨后出現(xiàn)的幾起案子估蹄,更是在濱河造成了極大的恐慌,老刑警劉巖沫换,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臭蚁,死亡現(xiàn)場離奇詭異,居然都是意外死亡讯赏,警方通過查閱死者的電腦和手機(jī)垮兑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漱挎,“玉大人系枪,你說我怎么就攤上這事】牧拢” “怎么了私爷?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膊夹。 經(jīng)常有香客問我衬浑,道長,這世上最難降的妖魔是什么放刨? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任工秩,我火速辦了婚禮,結(jié)果婚禮上宏榕,老公的妹妹穿的比我還像新娘拓诸。我一直安慰自己,他們只是感情好麻昼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馋辈,像睡著了一般抚芦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迈螟,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天叉抡,我揣著相機(jī)與錄音,去河邊找鬼答毫。 笑死褥民,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洗搂。 我是一名探鬼主播消返,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼载弄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了撵颊?” 一聲冷哼從身側(cè)響起宇攻,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倡勇,沒想到半個(gè)月后逞刷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妻熊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年夸浅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扔役。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帆喇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厅目,到底是詐尸還是另有隱情番枚,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布损敷,位于F島的核電站葫笼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拗馒。R本人自食惡果不足惜路星,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诱桂。 院中可真熱鬧洋丐,春花似錦、人聲如沸挥等。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肝劲。三九已至迁客,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辞槐,已是汗流浹背掷漱。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榄檬,地道東北人卜范。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像鹿榜,于是被迫代替她去往敵國和親海雪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锦爵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容