JS入門難點(diǎn)解析8-作用域筋现,作用域鏈唐础,執(zhí)行上下文,執(zhí)行上下文棧等分析

(注1:如果有問(wèn)題歡迎留言探討矾飞,一起學(xué)習(xí)一膨!轉(zhuǎn)載請(qǐng)注明出處,喜歡可以點(diǎn)個(gè)贊哦H髀佟)
(注2:更多內(nèi)容請(qǐng)查看我的目錄豹绪。)

1. 簡(jiǎn)介

JS入門難點(diǎn)系列到此,我們將進(jìn)行一個(gè)階段性的總結(jié)微谓。將前面所學(xué)的有關(guān)作用域森篷,LHS输钩,RHS,執(zhí)行上下文棧仲智,執(zhí)行上下文买乃,變量對(duì)象,作用域鏈钓辆,this等知識(shí)點(diǎn)串聯(lián)起來(lái)剪验,使大家有一個(gè)更新清晰的認(rèn)識(shí)。

2. 概念回顧

  1. 作用域:其實(shí)是一種規(guī)則與概念前联,我們并不能直接訪問(wèn)他功戚,是用來(lái)幫我們區(qū)分變量是在哪里定義的。作用域有三種(ES6之前):全局作用域似嗤,局部(函數(shù))作用域啸臀,eval作用域。ES6之后又多了一個(gè)塊級(jí)作用域烁落。比如乘粒,我們經(jīng)常會(huì)這么說(shuō),在全局作用域定義了一個(gè)變量a伤塌,在當(dāng)前函數(shù)作用域定義了一個(gè)變量a灯萍。雖然是a,但你可以分辨這其實(shí)是兩個(gè)a每聪,因?yàn)槠渌幍淖饔糜虿煌?/p>

  2. 作用域鏈:很容易與作用域弄混旦棉。作用域鏈其實(shí)是在進(jìn)入執(zhí)行上下文以后創(chuàng)建的。是由當(dāng)前的活動(dòng)對(duì)象和當(dāng)前函數(shù)的[[scope]]屬性拼接而成药薯。如果在全局執(zhí)行上下文绑洛,則當(dāng)前作用域鏈僅有全局變量對(duì)象。要記住童本,全局變量對(duì)象始終在作用域鏈的頂端诊笤。而在代碼執(zhí)行時(shí),對(duì)變量的查找巾陕,不管是LHS,還是RHS纪他,都是從當(dāng)前作用域開(kāi)始鄙煤,順著作用域鏈向前查找。

  3. LHS茶袒,RHS:引擎的查詢方式有兩種梯刚,即LHS和RHS。代碼中出現(xiàn)變量時(shí)薪寓,如果目的是要進(jìn)行存儲(chǔ)亡资,也就是我們關(guān)心的是要找到變量的容器本身澜共,來(lái)進(jìn)行不同數(shù)據(jù)的存儲(chǔ)賦值操作,而不關(guān)心現(xiàn)在這個(gè)容器里面存的是什么锥腻,就會(huì)用到LHS嗦董。而如果我們的目的只是拿這個(gè)變量來(lái)用,也就是只關(guān)心這個(gè)變量存儲(chǔ)的內(nèi)容是啥瘦黑,而不需要關(guān)心這個(gè)變量存在哪個(gè)容器京革,那么就會(huì)用到RHS。

  4. 執(zhí)行上下文:JavaScript 的可執(zhí)行代碼(executable code)有以下三類:全局代碼幸斥、函數(shù)代碼匹摇、eval代碼。當(dāng)JS引擎遇到這三類代碼時(shí)甲葬,會(huì)開(kāi)始做準(zhǔn)備工作廊勃,創(chuàng)建一個(gè)“執(zhí)行上下文(execution context)"。執(zhí)行上下文的生命周期有兩個(gè)階段:準(zhǔn)備階段和代碼執(zhí)行階段经窖。在準(zhǔn)備階段坡垫,會(huì)做三件事,即用arguments創(chuàng)建當(dāng)前執(zhí)行上下文的活動(dòng)對(duì)象钠至,確定當(dāng)前執(zhí)行上下文的作用域鏈葛虐,和綁定當(dāng)前執(zhí)行上下文的this屬性。

  5. 執(zhí)行上下文棧:當(dāng)JS執(zhí)行到一個(gè)函數(shù)的時(shí)候棉钧,就會(huì)創(chuàng)建該函數(shù)的“執(zhí)行上下文(execution context)"屿脐。JS代碼中可能出現(xiàn)為數(shù)眾多的函數(shù),所以JavaScript 引擎創(chuàng)建了執(zhí)行上下文棧(Execution context stack宪卿,ECS)來(lái)管理執(zhí)行上下文的诵。

  6. 變量對(duì)象:變量對(duì)象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲(chǔ)了在執(zhí)行上下文中定義的變量和函數(shù)聲明佑钾。全局上下文中的變量對(duì)象就是全局對(duì)象西疤。在函數(shù)上下文中,我們用活動(dòng)對(duì)象(activation object, AO)來(lái)表示變量對(duì)象休溶〈蓿活動(dòng)對(duì)象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過(guò)函數(shù)的 arguments 屬性初始化兽掰。arguments 屬性值是 Arguments 對(duì)象芭碍。

  7. this:this 提供了一種更優(yōu)雅的方式來(lái)隱式“傳遞”一個(gè)對(duì)象引用,因此可以將 API 設(shè)計(jì)得更加簡(jiǎn)潔并且易于復(fù)用孽尽。this 是在運(yùn)行時(shí)進(jìn)行綁定的窖壕,并不是在編寫時(shí)綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。this 的綁定和函數(shù)聲明的位置沒(méi)有任何關(guān)系瞻讽,只取決于函數(shù)的調(diào)用方式鸳吸。

3.關(guān)鍵點(diǎn)劃分

這么多的概念,在JS編譯執(zhí)行過(guò)程中速勇,很容易讓大家繞暈晌砾。但其實(shí),只要把握住整個(gè)JS編譯執(zhí)行的關(guān)鍵點(diǎn)快集,一切困難都會(huì)迎刃而解贡羔。那么這個(gè)關(guān)鍵點(diǎn)是什么呢?其實(shí)就是函數(shù)執(zhí)行个初。

我們來(lái)看乖寒,只有在執(zhí)行到一個(gè)函數(shù)時(shí),才會(huì)創(chuàng)建函數(shù)執(zhí)行上下文并壓入函數(shù)執(zhí)行上下文棧,不管在當(dāng)前執(zhí)行上下文的準(zhǔn)備階段或者是在執(zhí)行該函數(shù)前做了哪些操作,我們只需要關(guān)心函數(shù)執(zhí)行那一刻的快照迹栓,包括此時(shí)該函數(shù)的[[scope]]屬性值,此時(shí)函數(shù)執(zhí)行上下文棧逐虚,此時(shí)傳入的arguments,此時(shí)函數(shù)的調(diào)用方式谆膳。然后進(jìn)入該函數(shù)上下文叭爱,函數(shù)上下文會(huì)根據(jù)arguments來(lái)初始化活動(dòng)對(duì)象AO,通過(guò)AO+[[scope]]來(lái)確定作用域鏈漱病,并通過(guò)調(diào)用方式確定this綁定买雾。然后進(jìn)入函數(shù)執(zhí)行上下文的代碼執(zhí)行階段,直到遇見(jiàn)下一個(gè)函數(shù)執(zhí)行杨帽,周而復(fù)始或者執(zhí)行完畢還沒(méi)有遇到下一個(gè)函數(shù)執(zhí)行漓穿,執(zhí)行完畢的函數(shù)上下文就會(huì)逐個(gè)出棧并銷毀,直到程序關(guān)閉注盈,全局執(zhí)行環(huán)境銷毀晃危。

4. 流程分析

我們以下面這段代碼為例來(lái)進(jìn)行分析:

var scope = "global scope";

function checkscope() {
    var scope = "local scope";

    function f() {
        return scope;
    }

    return f();
}

checkscope();

現(xiàn)在我們嘗試用之前所學(xué)走一遍代碼的編譯執(zhí)行流程:

  1. 執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文老客,全局上下文被壓入執(zhí)行上下文棧僚饭。
ECStack = [
    globalContext
];
  1. 全局上下文初始化。(初始化全局環(huán)境的VO胧砰,確定全局環(huán)境的Scope浪慌,綁定全局環(huán)境的this。)當(dāng)然朴则,在此階段,完成了全局作用域的變量聲明和函數(shù)聲明,并且進(jìn)行了全局作用域的變量提升和函數(shù)提升乌妒。
globalContext = {
    VO: {
        global: window,
        scope: undefined,
        checkscope: reference to function checkscope
    },
    Scope: [globalContext.VO],
    this: globalContext.VO
}
  1. checkScope函數(shù)執(zhí)行前階段汹想。初始化的同時(shí),checkscope 函數(shù)被創(chuàng)建撤蚊,保存全局環(huán)境的作用域鏈到函數(shù)checkscope的內(nèi)部屬性[[scope]]古掏。(checkScope在此處是使用函數(shù)聲明,所以猜測(cè)其[[scope]]屬性是在全局環(huán)境初始化階段侦啸,但如果此時(shí)使用變量賦值“var checkscope = function (){...}”這種寫法槽唾,那么此時(shí)checkScope屬性的寫入是在全局環(huán)境的執(zhí)行階段)。但是[[scope]]屬性的創(chuàng)建和寫入時(shí)機(jī)其實(shí)并不是重點(diǎn)光涂,我們說(shuō)了重點(diǎn)是對(duì)關(guān)鍵點(diǎn)的劃分庞萍。就是checkscope函數(shù)執(zhí)行那一刻,[[scope]]屬性是一定已經(jīng)創(chuàng)建并寫入了全局執(zhí)行環(huán)境的作用域鏈即可忘闻。并且執(zhí)行了其他代碼钝计。
checkscope.[[scope]] = [
    globalContext.VO
];
globalContext = {
    VO: {
        global: window,
        scope: "global scope",
        checkscope: reference to function checkscope
    },
    Scope: [globalContext.VO],
    this: globalContext.VO
}
  1. 執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文齐佳,checkscope 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧.
ECStack = [
    checkscopeContext,
    globalContext
];
  1. checkscope 函數(shù)執(zhí)行環(huán)境初始化(用 arguments 創(chuàng)建活動(dòng)對(duì)象checkscopeContext.AO私恬,利用活動(dòng)對(duì)象checkscopeContext.AO與checkscope.[[scope]]形成checkscope 函數(shù)執(zhí)行環(huán)境的作用域鏈checkscopeContext.Scope。綁定this到undefined(非嚴(yán)格模式下會(huì)綁定到全局對(duì)象)炼吴。在此階段本鸣,進(jìn)行了加入形參、函數(shù)聲明硅蹦、變量聲明荣德,函數(shù)提升,變量提升等操作提针。
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope: undefined,
        f: reference to function f(){}
    },
    Scope: [AO, globalContext.VO],
    this: undefined
}
  1. checkscope 函數(shù)執(zhí)行環(huán)境初始化的同時(shí)命爬,或者準(zhǔn)確說(shuō),f函數(shù)執(zhí)行之前辐脖,f函數(shù)的[[scope]]屬性創(chuàng)建并寫入饲宛。并且f函數(shù)執(zhí)行前的的其他代碼也會(huì)執(zhí)行。
f.[[scope]] = [
    checkscopeContext.AO,
    globalContext.VO
];
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope: "local scope",
        f: reference to function f(){}
    },
    Scope: [AO, globalContext.VO],
    this: undefined
}
  1. 執(zhí)行f函數(shù)嗜价,創(chuàng)建 f 函數(shù)執(zhí)行上下文艇抠,f 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧。
ECStack = [
    fContext,
    checkscopeContext,
    globalContext
];
  1. f 函數(shù)執(zhí)行環(huán)境初始化(參考第5步)久锥。
fContext = {
    AO: {
        arguments: {
            length: 0
        }
    },
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
    this: undefined
}
  1. f函數(shù)中代碼執(zhí)行家淤。需要對(duì)scope進(jìn)行RHS查找。查找從當(dāng)前作用域開(kāi)始沿著作用域鏈向上瑟由。(或者說(shuō)從作用域鏈中的當(dāng)前活動(dòng)對(duì)象開(kāi)始沿著作用域鏈向上查找)絮重。記住,這里是兩種說(shuō)法,但是作用域與活動(dòng)對(duì)象并不等同青伤。
// 查找過(guò)程:
1. fContext.AO.scope 沒(méi)有該變量聲明(即該變量不在當(dāng)前作用域督怜,記住不是為undefined,因?yàn)樵撟兞靠赡艽嬖诤萁牵侵禐閡ndefined)号杠,繼續(xù)
2. checkscopeContext.AO.scope 有該變量聲明,獲取其值為"local scope"
  1. f 函數(shù)執(zhí)行完畢丰歌,返回"local scope"姨蟋。f 函數(shù)上下文從執(zhí)行上下文棧中彈出。
ECStack = [
    checkscopeContext,
    globalContext
];
  1. checkscope 函數(shù)在執(zhí)行完f處獲取f執(zhí)行的返回值"local scope"立帖,函數(shù)繼續(xù)向下執(zhí)行眼溶。

  2. checkScope執(zhí)行完畢,返回獲取到的返回值"local scope"厘惦。checkScope 函數(shù)上下文從執(zhí)行上下文棧中彈出偷仿。

ECStack = [
    globalContext
];
  1. 代碼執(zhí)行流回到全局執(zhí)行環(huán)境中調(diào)用checoscope處,拿到checkScope返回值并繼續(xù)向下執(zhí)行宵蕉。

  2. 直到程序終止酝静,或者頁(yè)面關(guān)閉。全局上下文出棧并銷毀羡玛。

參考

JS入門難點(diǎn)解析3-作用域
JS入門難點(diǎn)解析4-執(zhí)行上下文棧
JS入門難點(diǎn)解析5-變量對(duì)象
JS入門難點(diǎn)解析6-作用域鏈
JS入門難點(diǎn)解析7-this
一道js面試題引發(fā)的思考
JavaScript深入之執(zhí)行上下文
深入理解javascript作用域系列第五篇——一張圖理解執(zhí)行環(huán)境和作用域
前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解
Javascript變量的作用域和作用域鏈詳解
JavaScript關(guān)于作用域别智、作用域鏈和閉包的理解
理解js中的作用域,作用域鏈以及初探閉包

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市稼稿,隨后出現(xiàn)的幾起案子薄榛,更是在濱河造成了極大的恐慌,老刑警劉巖让歼,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敞恋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谋右,警方通過(guò)查閱死者的電腦和手機(jī)硬猫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)改执,“玉大人啸蜜,你說(shuō)我怎么就攤上這事”补遥” “怎么了衬横?”我有些...
    開(kāi)封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)终蒂。 經(jīng)常有香客問(wèn)我蜂林,道長(zhǎng)遥诉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任噪叙,我火速辦了婚禮突那,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘构眯。我一直安慰自己,他們只是感情好早龟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布惫霸。 她就那樣靜靜地躺著,像睡著了一般葱弟。 火紅的嫁衣襯著肌膚如雪壹店。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天芝加,我揣著相機(jī)與錄音硅卢,去河邊找鬼。 笑死藏杖,一個(gè)胖子當(dāng)著我的面吹牛将塑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝌麸,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼点寥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了来吩?” 一聲冷哼從身側(cè)響起敢辩,我...
    開(kāi)封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弟疆,沒(méi)想到半個(gè)月后戚长,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怠苔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年同廉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘀略。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恤溶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帜羊,到底是詐尸還是另有隱情咒程,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布讼育,位于F島的核電站帐姻,受9級(jí)特大地震影響稠集,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饥瓷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一剥纷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呢铆,春花似錦晦鞋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至娜谊,卻和暖如春确买,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纱皆。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工湾趾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人派草。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓搀缠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親澳眷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胡嘿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345