溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

javascript執(zhí)行上下文的過(guò)程是什么

發(fā)布時(shí)間:2023-05-04 11:44:37 來(lái)源:億速云 閱讀:110 作者:zzz 欄目:開發(fā)技術(shù)

這篇文章主要介紹“javascript執(zhí)行上下文的過(guò)程是什么”,在日常操作中,相信很多人在javascript執(zhí)行上下文的過(guò)程是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”javascript執(zhí)行上下文的過(guò)程是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

    簡(jiǎn)介

    執(zhí)行上下文可以說(shuō)是js代碼執(zhí)行的一個(gè)環(huán)境,存放了代碼執(zhí)行所需的變量,變量查找的作用域鏈規(guī)則以及this指向等。同時(shí),它也是js很底層的東西,很多的問(wèn)題如變量提升、作用域鏈和閉包等都可以在執(zhí)行上下文中找到答案,所以這也是我們學(xué)習(xí)執(zhí)行上下文的原因

    執(zhí)行上下文分為三種:

    • 全局執(zhí)行上下文:當(dāng)進(jìn)入全局代碼時(shí)會(huì)進(jìn)行編譯,在編譯中創(chuàng)建全局執(zhí)行上下文,并生成可執(zhí)行代碼

    • 函數(shù)執(zhí)行上下文:執(zhí)行代碼的過(guò)程中,如果遇到函數(shù)調(diào)用,會(huì)編譯函數(shù)內(nèi)的代碼和創(chuàng)建函數(shù)執(zhí)行上下文,并創(chuàng)建可執(zhí)行代碼

    • eval執(zhí)行上下文:當(dāng)使用eval函數(shù)的時(shí)候,eval的代碼也會(huì)被編譯,并創(chuàng)建執(zhí)行上下文

    因?yàn)閳?zhí)行上下文是在編譯階段創(chuàng)建的,所以接下來(lái)先看一下js代碼的執(zhí)行過(guò)程吧

    javascript代碼的執(zhí)行過(guò)程

    一段js代碼的執(zhí)行過(guò)程中,先是會(huì)進(jìn)行編譯階段,js引擎會(huì)將代碼進(jìn)行編譯,再進(jìn)入執(zhí)行階段

    也就是說(shuō),js代碼是按照“段”來(lái)執(zhí)行的,具體就是全局代碼就是一段代碼,函數(shù)執(zhí)行也算一段代碼,編譯也是按照“段”來(lái)編譯的,也就是一整個(gè)js代碼會(huì)出現(xiàn)多個(gè)編譯階段

    編譯階段

    編譯階段是一個(gè)很復(fù)雜的過(guò)程,這里只是簡(jiǎn)單的介紹:

    1、編譯階段完成兩件事情:創(chuàng)建執(zhí)行上下文和生成可執(zhí)行代碼

    2、執(zhí)行上下文就包括變量環(huán)境和詞法環(huán)境和this指向等,創(chuàng)建執(zhí)行上下文的過(guò)程:

    • 如果是普通變量的話,js引擎會(huì)將該變量添加到變量環(huán)境中并初始化為undefined

    • 如果是函數(shù)聲明的話,js引擎會(huì)將函數(shù)定義添加到變量環(huán)境中,然后將函數(shù)名執(zhí)行該函數(shù)的位置(內(nèi)存)

    3、接著,js引擎就會(huì)把其他的代碼編譯為字節(jié)碼,生成可執(zhí)行代碼

    編譯階段完成后,js引擎開始執(zhí)行可執(zhí)行代碼,按照順序一行一行執(zhí)行,當(dāng)遇到函數(shù)或者變量時(shí),會(huì)在變量環(huán)境中尋找,找不到的話就會(huì)報(bào)錯(cuò)

    如果遇到賦值語(yǔ)句時(shí),就會(huì)將值賦值給變量

    var變量提升與let和const

    變量提升是指在js代碼執(zhí)行過(guò)程中,js引擎把變量的聲明部分和函數(shù)聲明部分提升到代碼開頭的“行為”。變量被提升后,會(huì)給變量設(shè)置默認(rèn)值undefined

    變量提升的實(shí)現(xiàn)并不是物理地移動(dòng)代碼的位置,而是在編譯階段被js引擎放入內(nèi)存中。

    1、普通變量提升會(huì)賦值為undefined,函數(shù)變量名會(huì)將整個(gè)函數(shù)提升

    console.log(fn);  // [Function: fn]
    console.log(a);  // undefined
    function fn() {
        console.log(111);
    }
    var a = 1

    2、函數(shù)表達(dá)式只會(huì)將變量提升,不會(huì)將函數(shù)題提升

    3、當(dāng)有多個(gè)相同類型的聲明(同樣是函數(shù)聲明或者同樣是普通變量聲明),最后一個(gè)聲明會(huì)覆蓋之前的聲明

    4、當(dāng)函數(shù)聲明與變量聲明同時(shí)出現(xiàn)時(shí),函數(shù)聲明優(yōu)先級(jí)更高

    當(dāng)然,變量提升有很多的缺陷,所以從es6開始引入了let和const關(guān)鍵字,通過(guò)let和const聲明的變量不具有變量提升特性,同時(shí)也支持塊級(jí)作用域,先看一下作用域吧

    作用域

    作用域其實(shí)就是一套定義函數(shù)調(diào)用和變量使用的規(guī)則,其中,就有三種作用域:

    • 全局作用域:其中對(duì)象在代碼的任何地方都能訪問(wèn),其生命周期伴隨著頁(yè)面的生命周期

    • 函數(shù)作用域:在函數(shù)內(nèi)部定義的變量和函數(shù),只能在內(nèi)部訪問(wèn),外部不能訪問(wèn)到,函數(shù)執(zhí)行結(jié)束后,函數(shù)內(nèi)部定義的變量會(huì)被銷毀(函數(shù)不會(huì)嗎???)

    • 塊級(jí)作用域:由代碼塊包含的代碼會(huì)形成一個(gè)塊級(jí)作用域(es6之前沒(méi)有),跟函數(shù)作用域類似

    通過(guò)var聲明的變量沒(méi)有塊級(jí)作用域,通過(guò)const let聲明的變量有塊級(jí)作用域

    那js是如何var的變量提升和支持塊級(jí)作用域的呢?這就得從執(zhí)行上下文的角度說(shuō)起

    編譯階段生成執(zhí)行上下文:

    假設(shè)js需要執(zhí)行一個(gè)函數(shù)

    • 首先,編譯創(chuàng)建該函數(shù)的執(zhí)行上下文,創(chuàng)建可執(zhí)行代碼

    • 在編譯階段,所有通過(guò)var聲明的變量(包括代碼塊里面的變量)都會(huì)被創(chuàng)建并存放在變量環(huán)境中,并初始化為undefined

    • 通過(guò)let或者const聲明的變量(不包括代碼塊碼里面的變量)都會(huì)被創(chuàng)建并存放在詞法環(huán)境中,設(shè)置為未初始化

    • 至此,編譯階段結(jié)束了,開始執(zhí)行代碼

    • 執(zhí)行代碼過(guò)程中遇到代碼塊時(shí),會(huì)先將里面通過(guò)let或者const聲明的變量存放在詞法環(huán)境中并設(shè)置為初始化,其實(shí),在詞法環(huán)境內(nèi)部,維護(hù)了一個(gè)小型的棧結(jié)構(gòu),棧底是函數(shù)最外層的變量,每遇到一個(gè)代碼塊,就將所包含的變量壓入詞法環(huán)境的棧結(jié)構(gòu),代碼塊執(zhí)行結(jié)束后,就將包含的變量彈出

    接下來(lái)看一段代碼:

    function foo(){
        var a = 1
        let b = 2
        {
          let b = 3
          var c = 4
          let d = 5
          console.log(a)
          console.log(b)
        }
        console.log(b) 
        console.log(c)
        console.log(d)
    }   
    foo()

    當(dāng)執(zhí)行到代碼塊時(shí),對(duì)應(yīng)的執(zhí)行上下文如下:

    foo函數(shù)執(zhí)行上
    變量環(huán)境
    a = 1, c = 3
    詞法環(huán)境
    {b = 3, d = 5}
    {b = 2}

    當(dāng)代碼塊的代碼執(zhí)行完畢后,對(duì)應(yīng)的詞法環(huán)境里的變量就會(huì)被彈出棧

    foo函數(shù)執(zhí)行上下文
    變量環(huán)境
    a = 1, c = 3
    詞法環(huán)境
    {b = 2}

    原因

    通過(guò)上面的分析,我們可以總結(jié)變量提升和塊級(jí)作用域的實(shí)現(xiàn):

    • 通過(guò)編譯階段,通過(guò)var聲明的變量已經(jīng)存在變量環(huán)境中并賦值為undefined,所以在執(zhí)行代碼的任何位置都能狗訪問(wèn)得到,而不需要在聲明之后才能訪問(wèn)

    • 而通過(guò)let聲明的變量,會(huì)被存放在詞法環(huán)境中但并未初始化(不包含代碼塊的let或const聲明的變量),所以并不能訪問(wèn),而是等到遇到let聲明語(yǔ)句的時(shí)候才初始化并賦值

    • 在遇到代碼塊時(shí),會(huì)先將let和const聲明的變量存放在詞法環(huán)境中并設(shè)置為初始化,如果此時(shí)在代碼塊中在let聲明變量之前使用該變量,并不會(huì)去外部作用域找該變量,因?yàn)榇藭r(shí)詞法作用域已經(jīng)存在改變量了,但未初始化,所以此時(shí)會(huì)報(bào)錯(cuò)誤,這也是let暫時(shí)性死區(qū)的原因

    單個(gè)執(zhí)行上下文中變量的查找規(guī)則

    沿著詞法環(huán)境的棧頂向下查詢,如果在詞法環(huán)境的某個(gè)快中查找到了,就直接返回給js引擎,如果沒(méi)有找到,就繼續(xù)在變量環(huán)境中查找


    javascript執(zhí)行上下文的過(guò)程是什么

    調(diào)用棧

    調(diào)用棧是用來(lái)管理函數(shù)調(diào)用關(guān)系的一種棧結(jié)構(gòu)

    在函數(shù)調(diào)用之前,會(huì)創(chuàng)建對(duì)應(yīng)的執(zhí)行上下文,并生成對(duì)應(yīng)的可執(zhí)行代碼

    • js維護(hù)了一個(gè)棧結(jié)構(gòu),每當(dāng)遇到一個(gè)函數(shù)調(diào)用的時(shí)候,就創(chuàng)建一個(gè)執(zhí)行上下文,并壓入該棧中,

    • 這個(gè)棧叫做執(zhí)行上下文棧,也叫做調(diào)用棧

    • 當(dāng)函數(shù)執(zhí)行完畢之后,會(huì)將對(duì)應(yīng)的執(zhí)行上下文彈出棧結(jié)構(gòu)

    • 棧的容量是有限的,當(dāng)棧容量不夠的時(shí)候就有可能發(fā)生棧溢出

    作用域鏈

    • 在函數(shù)中如果在當(dāng)前作用域中找不到所需要的變量,就得沿著作用域鏈往下去查找,直到找到為止

    • 我們都知道,當(dāng)一段代碼在執(zhí)行的時(shí)候,會(huì)有對(duì)應(yīng)的執(zhí)行上下文,那變量沿著作用域鏈查看的規(guī)則也是在執(zhí)行上下文中設(shè)置的

    • 在每個(gè)執(zhí)行上下文中,在變量環(huán)境中,都有一個(gè)外部引用,用來(lái)執(zhí)行外部的執(zhí)行上下文,我們把這個(gè)外部引用稱為outer

    • 上文已經(jīng)說(shuō)到,變量的查找首先會(huì)從執(zhí)行上下文的詞法環(huán)境中查找,找不到就在變量環(huán)境中查找,再找不到的話就會(huì)沿著outer去外部的執(zhí)行上下文中查找

    • outer具體引用哪一個(gè)執(zhí)行上下文(作用域),是由詞法作用域決定的

    詞法作用域

    詞法作用域指的是作用域有代碼中函數(shù)的聲明位置決定的,也叫做靜態(tài)作用域

    也就是說(shuō),當(dāng)創(chuàng)建一個(gè)執(zhí)行上下文的時(shí)候,其內(nèi)部的outer就會(huì)根據(jù)詞法作用域去執(zhí)行對(duì)應(yīng)的外部執(zhí)行上下文

    在外部的執(zhí)行上下文中查找時(shí),也是先從詞法環(huán)境中開始

    function fn() {
        console.log(a);
    }
    function fn1() {
        let a = 1
        fn()
    }
    let a = 3  // let聲明的變量是在詞法環(huán)境中的
    fn1()  // 3

    閉包

    在js中,根據(jù)詞法作用域的規(guī)則,內(nèi)部函數(shù)總是可以訪問(wèn)外部函數(shù)中聲明的變量,當(dāng)通過(guò)調(diào)用一個(gè)外部函數(shù)返回一個(gè)內(nèi)部函數(shù)后,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中,我們就把這些變量的集合稱為閉包

    function foo() {
        var myName = " 極客時(shí)間 "
        let test1 = 1
        const test2 = 2
        var innerBar = {
            getName:function(){
                console.log(test1)
                return myName
            },
            setName:function(newName){
                myName = newName
            }
        }
        return innerBar
    }
    var bar = foo()
    bar.setName(" 極客邦 ")
    bar.getName()
    console.log(bar.getName())

    上面代碼中,由于存在閉包現(xiàn)象,foo函數(shù)執(zhí)行結(jié)束后,內(nèi)部的變量還會(huì)被保存,調(diào)用棧如下圖:

    javascript執(zhí)行上下文的過(guò)程是什么

    當(dāng)執(zhí)行到 bar.setName 方法中的myName = "極客邦"這句代碼時(shí),JavaScript 引擎會(huì)沿著“當(dāng)前執(zhí)行上下文–>foo 函數(shù)閉包–> 全局執(zhí)行上下文”的順序來(lái)查找 myName 變量,你可以參考下面的調(diào)用棧狀態(tài)圖:

    javascript執(zhí)行上下文的過(guò)程是什么

    閉包的回收

    • 如果閉包使用不正確,會(huì)很容易造成內(nèi)存泄漏的,關(guān)注閉包是如何回收的能讓你正確地使用閉包。

    • 通常,如果引用閉包的函數(shù)是一個(gè)全局變量,那么閉包會(huì)一直存在直到頁(yè)面關(guān)閉;但如果這個(gè)閉包以后不再使用的話,就會(huì)造成內(nèi)存泄漏。

    • 如果引用閉包的函數(shù)是個(gè)局部變量,等函數(shù)銷毀后,在下次 JavaScript 引擎執(zhí)行垃圾回收時(shí),判斷閉包這塊內(nèi)容如果已經(jīng)不再被使用了,那么 JavaScript 引擎的垃圾回收器就會(huì)回收這塊內(nèi)存。

    • 所以在使用閉包的時(shí)候,你要盡量注意一個(gè)原則:如果該閉包會(huì)一直使用,那么它可以作為全局變量而存在;但如果使用頻率不高,而且占用內(nèi)存又比較大的話,那就盡量讓它成為一個(gè)局部變量。

    var bar = {
        myName:"time.geekbang.com",
        printName: function () {
            console.log(myName)
        }    
    }
    function foo() {
        let myName = " 極客時(shí)間 "
        return bar.printName
    }
    let myName = " 極客邦 "
    let _printName = foo()
    _printName()
    bar.printName()

    從上下文角度講this

    執(zhí)行上下文分為三種,對(duì)應(yīng)的this也只有三種:全局上下文的this,函數(shù)中的this,eval中的this

    • 箭頭函數(shù)沒(méi)有自己的執(zhí)行上下文

    • 全局上下文的this指向全局對(duì)象

    • 函數(shù)上下文的this根據(jù)四種綁定規(guī)則判斷this指向

    • 執(zhí)行上下文包含this指向

    到此,關(guān)于“javascript執(zhí)行上下文的過(guò)程是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI