摘要:由于函數(shù)被調(diào)用了,線程會(huì)從剛剛保存的變量中取出內(nèi)容,去解析執(zhí)行它。最后,當(dāng)線程遇到離開上下文的標(biāo)識(shí),便離開上下文,并把的結(jié)果一并返回出去。
原文鏈接,歡迎關(guān)注我的博客
我相信很多前端初學(xué)者一開始都會(huì)被執(zhí)行上下文這個(gè)概念弄暈,或者說似懂非懂。對(duì)于工作兩年的我來說,說來實(shí)在慚愧,雖然知道它大概是什么,但總覺得沒有一個(gè)更為清晰的認(rèn)識(shí)(無法把它的工作過程描述清楚),因此最近特意溫習(xí)了一遍,寫下了這篇文章
執(zhí)行上下文要說清它的大體工作流程,需要提前說明三個(gè)基本概念,分別是thread of exection(線程)、variable envirnoment(變量環(huán)境)、call Stack(調(diào)用棧),這些概念我們或多或少接觸過,接下來我會(huì)通過一段示例代碼,和一系列圖片,進(jìn)一步解釋這三個(gè)概念在執(zhí)行上下文的運(yùn)作流程。
一段代碼const num = 2; function addOne(input) { const output = input + 1; return output; } const result = addOne(2);這段代碼做了什么
在運(yùn)行上面這些代碼前,js 引擎做的第一件是就是創(chuàng)建一個(gè)global execution context,也就是全局執(zhí)行上下文:
先看圖中的黑色箭頭,它表示線程thread的執(zhí)行順序,眾所周知 js 是單線程的,它會(huì)一行行、從上往下去執(zhí)行代碼;而右邊的global memory,它用于存儲(chǔ)當(dāng)前上下文中的數(shù)據(jù),由于線程目前處于全局上下文環(huán)境,故加了個(gè)global的前綴。
在這段代碼中,第一行我們聲明了一個(gè)名為num的不可變變量,并賦值為4, 因此global memory中就會(huì)分配內(nèi)存,存儲(chǔ)這個(gè)變量:
接著繼續(xù),當(dāng)線程執(zhí)行到第二行時(shí),問題就來了:我們創(chuàng)建了一個(gè)addOne的變量,并把一個(gè)函數(shù)賦值于它,那在global memory里,到底存的是個(gè)啥?為了解答這個(gè)問題,我特意打印了一下:
function addOne(input) { const output = input + 1; return output; } console.log(addOne);
看,我們竟然把函數(shù)里的內(nèi)容完完整整打印出來了,很明顯,它存的是一個(gè)函數(shù)內(nèi)部的“文本信息”。
其實(shí)很容易理解,當(dāng)執(zhí)行第二行的時(shí)候,該函數(shù)并沒有被調(diào)用,因此線程不會(huì)立刻解析里面的內(nèi)容,而是把它內(nèi)部的信息以“文本內(nèi)容”的形式保存下來,當(dāng)需要執(zhí)行的時(shí)候,才去解析變量里的函數(shù)內(nèi)容,這也很好地解析了為什么函數(shù)內(nèi)的異常僅會(huì)在函數(shù)被調(diào)用時(shí)才拋出來。
因此這時(shí)global execution context長(zhǎng)這樣:
由于addOne里保存的是函數(shù)內(nèi)容,目前對(duì)于線程而言它是未知的,因此我們這里特意用一個(gè)帶有輸入輸出箭頭的函數(shù)圖標(biāo),代表它是一個(gè)未被解析的函數(shù)。
我們繼續(xù)執(zhí)行第三步:還是創(chuàng)建了一個(gè)變量result,但此時(shí)它被賦予undefined,因?yàn)榫€程暫時(shí)無法從addOne這個(gè)函數(shù)里獲知它的返回值。
由于addOne函數(shù)被調(diào)用了,線程會(huì)從剛剛保存的addOne變量中取出內(nèi)容,去解析、執(zhí)行它。這時(shí) js 就創(chuàng)建了一個(gè)新的執(zhí)行上下文——local execution context,即當(dāng)前的執(zhí)行上下文,這是一個(gè)船新的上下文,因此我特意用一個(gè)新圖片去描述它:
首先這個(gè)memory我加了個(gè)local前綴,表面當(dāng)前存儲(chǔ)的都是此上下文中的變量數(shù)據(jù)。無論是上述的global memory,亦或是現(xiàn)在、或未來的local memory,我們可以用一個(gè)更為專業(yè)的術(shù)語variable envirnoment去描述這種存儲(chǔ)環(huán)境。
此外,這個(gè)黑箭頭我特意畫了個(gè)拐角,意味它是從全局上下文進(jìn)來的,local memory首先會(huì)分配內(nèi)存給變量input,它在調(diào)用時(shí)就被2賦值了,緊接著又創(chuàng)建了一個(gè)output標(biāo)簽并把計(jì)算結(jié)果3賦值給它。最后,當(dāng)線程遇到離開上下文的標(biāo)識(shí)——return,便離開上下文,并把ouput的結(jié)果一并返回出去。
此時(shí),這個(gè)上下文就沒用了(被執(zhí)行完了),于是乎垃圾回收便盯上了它,選擇一個(gè)恰當(dāng)?shù)臅r(shí)機(jī)把里面的local memory刪光光。
這時(shí)候有同學(xué)會(huì)問道:你這個(gè)只是普通場(chǎng)景,那閉包怎么解釋呢?
其實(shí)閉包是個(gè)比較大的話題,這里也可以簡(jiǎn)單描述下:當(dāng)return的是一個(gè)函數(shù)的話,它返回的不僅是函數(shù)本身,還會(huì)把local memory中被引用的變量作為此函數(shù)的附加屬性一并返回出去,這就好比印魚喜歡吸附在鯊魚身上一般,鱔魚無論去哪都帶著它,因此,無論這個(gè)函數(shù)在哪里被調(diào)用,它都能在它本身附帶的local memory中找到那個(gè)變量。如果你把返回的函數(shù)console.log出來,也是能夠找到它的,這里就不詳說,關(guān)于閉包的更多概念(包括在函數(shù)式編程中的使用),有興趣的童鞋可以看看這篇文章:Partial & Curry - 函數(shù)式編程
go on,回到了global execution context,result不再孤零零地undefined,而是拿到了可愛的3:
到這里,我們的線程就完成了所有工作,可以歇息了,等等.....好像漏了什么沒講.....對(duì),就是Call Stack!
剛剛我們?nèi)谥v解thread of exection多么努力地一行行解析執(zhí)行代碼,variable envirnoment多么勤快地存儲(chǔ)變量,那call stack干了什么?是在偷懶嗎?
其實(shí)并不是,call stack起到了非常關(guān)鍵的作用:有了它,線程隨時(shí)可以知道自己目前處于哪個(gè)上下文。
試想一下,我們?cè)趯懘a的過程中往往喜歡在各種函數(shù)內(nèi)穿插著各種子函數(shù),比如遞歸,因此勤勞的線程就得不斷地進(jìn)入上下文、退出上下文、再進(jìn)入、再進(jìn)入、再退出、再進(jìn)入,久而久之線程根本就不知道自己處于哪個(gè)上下文中,也不知道應(yīng)該在哪個(gè)memory中取數(shù)據(jù),全都亂套了,因此必須通過一種方式去跟蹤、記錄目前線程所處的環(huán)境。
而call stack就是一種數(shù)據(jù)結(jié)構(gòu),用于“存儲(chǔ)”上下文,通過不斷推入push、推出pop上下文的方式,跟蹤線程目前所處的環(huán)境——線程無需刻意記住自己身處何方,只要永遠(yuǎn)處于最頂層執(zhí)行上下文,就是當(dāng)前函數(shù)執(zhí)行的正確位置
程序一開始運(yùn)行的時(shí)候,call stack先會(huì)push個(gè)global execution context:
接著我們?cè)谏鲜龃a第三行調(diào)用了addOne,call stack立刻將addOne的上下文push進(jìn)去,待執(zhí)行到return標(biāo)識(shí)后,再pop出來:
同樣的,即使在復(fù)雜的情況,只要遵循push、pop以及時(shí)刻處于最頂層上下文的原則,線程就可以一直保持在正確的位置上:
值得一提的是,call stack層數(shù)是有上限的,因此稍加不注意,你寫的遞歸可能會(huì)造成棧溢出了。
簡(jiǎn)單來說,上下文就是個(gè)可以用于執(zhí)行代碼的環(huán)境,與它相關(guān)的有三個(gè)重要的概念:
thread of exection(執(zhí)行線程) 它的主要職責(zé)是,從上到下,一行一行地解析和執(zhí)行代碼,不會(huì)同時(shí)多處執(zhí)行,也就是我們常掛在嘴邊的“js 是單線程的啦”
variable envirnoment(變量環(huán)境) 活躍的用于存儲(chǔ)數(shù)據(jù)的內(nèi)存
call stack(調(diào)用棧) 一種用于存儲(chǔ)上下文,跟蹤當(dāng)前線程所屬的上下文位置的數(shù)據(jù)結(jié)構(gòu)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105623.html
摘要:引擎對(duì)堆內(nèi)存中的對(duì)象進(jìn)行分代管理新生代存活周期較短的對(duì)象,如臨時(shí)變量字符串等。內(nèi)存泄漏對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第4天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃...
摘要:本計(jì)劃一共期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃,點(diǎn)擊查看前端進(jìn)階的破冰之旅本期推薦文章深入之執(zhí)行上下文棧和深入之變量對(duì)象,由于微信不能訪問外鏈,點(diǎn)擊閱讀原文就可以啦。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第二天。 本計(jì)劃一共28期,每期...
摘要:通過例子再來回顧一下閉包的理解所謂的閉包就是可以創(chuàng)建一個(gè)獨(dú)立的環(huán)境,每個(gè)閉包里面的環(huán)境都是獨(dú)立的,互不干擾。此時(shí)就是一個(gè)閉包,這樣寫有些麻煩,我們對(duì)此改進(jìn)一下。參考文章初識(shí)中的閉包從閉 走在前端的大道上 本篇將自己讀過的相關(guān) javascript閉包 文章中,對(duì)自己有啟發(fā)的章節(jié)片段總結(jié)在這(會(huì)對(duì)原文進(jìn)行刪改),會(huì)不斷豐富提煉總結(jié)更新。 首先著重回顧一下 全局變量 和 局部變量 全局變...
摘要:緊接著發(fā)現(xiàn),于是又停了,瀏覽器下載并執(zhí)行完,繼續(xù)。,發(fā)現(xiàn),遂將中文字展示了出來。的執(zhí)行時(shí)間是在所有元素解析完成之后,事件觸發(fā)之前。的執(zhí)行時(shí)間是在當(dāng)前腳本下載完成后,所以多個(gè)是執(zhí)行順序是不固定的。至此,完美的結(jié)構(gòu)出爐了。 現(xiàn)代瀏覽器性能優(yōu)化-JS篇 眾所周知,JS的加載和執(zhí)行會(huì)阻塞瀏覽器渲染,所以目前業(yè)界普遍推薦把script放到之前,以解決js執(zhí)行時(shí)找不到dom等問題。但隨著現(xiàn)代瀏覽器...
摘要:緊接著發(fā)現(xiàn),于是又停了,瀏覽器下載并執(zhí)行完,繼續(xù)。,發(fā)現(xiàn),遂將中文字展示了出來。的執(zhí)行時(shí)間是在所有元素解析完成之后,事件觸發(fā)之前。的執(zhí)行時(shí)間是在當(dāng)前腳本下載完成后,所以多個(gè)是執(zhí)行順序是不固定的。至此,完美的結(jié)構(gòu)出爐了。 現(xiàn)代瀏覽器性能優(yōu)化-JS篇 眾所周知,JS的加載和執(zhí)行會(huì)阻塞瀏覽器渲染,所以目前業(yè)界普遍推薦把script放到之前,以解決js執(zhí)行時(shí)找不到dom等問題。但隨著現(xiàn)代瀏覽器...
閱讀 3306·2023-04-26 02:40
閱讀 4653·2021-09-22 15:22
閱讀 1596·2021-09-22 10:02
閱讀 3490·2021-08-11 10:23
閱讀 1399·2019-08-30 15:55
閱讀 2500·2019-08-30 12:48
閱讀 593·2019-08-30 11:04
閱讀 710·2019-08-29 16:29