中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

javascript執行上下文的過程是什么

發布時間:2023-05-04 11:44:37 來源:億速云 閱讀:121 作者:zzz 欄目:開發技術

這篇文章主要介紹“javascript執行上下文的過程是什么”,在日常操作中,相信很多人在javascript執行上下文的過程是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”javascript執行上下文的過程是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    簡介

    執行上下文可以說是js代碼執行的一個環境,存放了代碼執行所需的變量,變量查找的作用域鏈規則以及this指向等。同時,它也是js很底層的東西,很多的問題如變量提升、作用域鏈和閉包等都可以在執行上下文中找到答案,所以這也是我們學習執行上下文的原因

    執行上下文分為三種:

    • 全局執行上下文:當進入全局代碼時會進行編譯,在編譯中創建全局執行上下文,并生成可執行代碼

    • 函數執行上下文:執行代碼的過程中,如果遇到函數調用,會編譯函數內的代碼和創建函數執行上下文,并創建可執行代碼

    • eval執行上下文:當使用eval函數的時候,eval的代碼也會被編譯,并創建執行上下文

    因為執行上下文是在編譯階段創建的,所以接下來先看一下js代碼的執行過程吧

    javascript代碼的執行過程

    一段js代碼的執行過程中,先是會進行編譯階段,js引擎會將代碼進行編譯,再進入執行階段

    也就是說,js代碼是按照“段”來執行的,具體就是全局代碼就是一段代碼,函數執行也算一段代碼,編譯也是按照“段”來編譯的,也就是一整個js代碼會出現多個編譯階段

    編譯階段

    編譯階段是一個很復雜的過程,這里只是簡單的介紹:

    1、編譯階段完成兩件事情:創建執行上下文和生成可執行代碼

    2、執行上下文就包括變量環境和詞法環境和this指向等,創建執行上下文的過程:

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

    • 如果是函數聲明的話,js引擎會將函數定義添加到變量環境中,然后將函數名執行該函數的位置(內存)

    3、接著,js引擎就會把其他的代碼編譯為字節碼,生成可執行代碼

    編譯階段完成后,js引擎開始執行可執行代碼,按照順序一行一行執行,當遇到函數或者變量時,會在變量環境中尋找,找不到的話就會報錯

    如果遇到賦值語句時,就會將值賦值給變量

    var變量提升與let和const

    變量提升是指在js代碼執行過程中,js引擎把變量的聲明部分和函數聲明部分提升到代碼開頭的“行為”。變量被提升后,會給變量設置默認值undefined

    變量提升的實現并不是物理地移動代碼的位置,而是在編譯階段被js引擎放入內存中。

    1、普通變量提升會賦值為undefined,函數變量名會將整個函數提升

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

    2、函數表達式只會將變量提升,不會將函數題提升

    3、當有多個相同類型的聲明(同樣是函數聲明或者同樣是普通變量聲明),最后一個聲明會覆蓋之前的聲明

    4、當函數聲明與變量聲明同時出現時,函數聲明優先級更高

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

    作用域

    作用域其實就是一套定義函數調用和變量使用的規則,其中,就有三種作用域:

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

    • 函數作用域:在函數內部定義的變量和函數,只能在內部訪問,外部不能訪問到,函數執行結束后,函數內部定義的變量會被銷毀(函數不會嗎???)

    • 塊級作用域:由代碼塊包含的代碼會形成一個塊級作用域(es6之前沒有),跟函數作用域類似

    通過var聲明的變量沒有塊級作用域,通過const let聲明的變量有塊級作用域

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

    編譯階段生成執行上下文:

    假設js需要執行一個函數

    • 首先,編譯創建該函數的執行上下文,創建可執行代碼

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

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

    • 至此,編譯階段結束了,開始執行代碼

    • 執行代碼過程中遇到代碼塊時,會先將里面通過let或者const聲明的變量存放在詞法環境中并設置為初始化,其實,在詞法環境內部,維護了一個小型的棧結構,棧底是函數最外層的變量,每遇到一個代碼塊,就將所包含的變量壓入詞法環境的棧結構,代碼塊執行結束后,就將包含的變量彈出

    接下來看一段代碼:

    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()

    當執行到代碼塊時,對應的執行上下文如下:

    foo函數執行上
    變量環境
    a = 1, c = 3
    詞法環境
    {b = 3, d = 5}
    {b = 2}

    當代碼塊的代碼執行完畢后,對應的詞法環境里的變量就會被彈出棧

    foo函數執行上下文
    變量環境
    a = 1, c = 3
    詞法環境
    {b = 2}

    原因

    通過上面的分析,我們可以總結變量提升和塊級作用域的實現:

    • 通過編譯階段,通過var聲明的變量已經存在變量環境中并賦值為undefined,所以在執行代碼的任何位置都能狗訪問得到,而不需要在聲明之后才能訪問

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

    • 在遇到代碼塊時,會先將let和const聲明的變量存放在詞法環境中并設置為初始化,如果此時在代碼塊中在let聲明變量之前使用該變量,并不會去外部作用域找該變量,因為此時詞法作用域已經存在改變量了,但未初始化,所以此時會報錯誤,這也是let暫時性死區的原因

    單個執行上下文中變量的查找規則

    沿著詞法環境的棧頂向下查詢,如果在詞法環境的某個快中查找到了,就直接返回給js引擎,如果沒有找到,就繼續在變量環境中查找


    javascript執行上下文的過程是什么

    調用棧

    調用棧是用來管理函數調用關系的一種棧結構

    在函數調用之前,會創建對應的執行上下文,并生成對應的可執行代碼

    • js維護了一個棧結構,每當遇到一個函數調用的時候,就創建一個執行上下文,并壓入該棧中,

    • 這個棧叫做執行上下文棧,也叫做調用棧

    • 當函數執行完畢之后,會將對應的執行上下文彈出棧結構

    • 棧的容量是有限的,當棧容量不夠的時候就有可能發生棧溢出

    作用域鏈

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

    • 我們都知道,當一段代碼在執行的時候,會有對應的執行上下文,那變量沿著作用域鏈查看的規則也是在執行上下文中設置的

    • 在每個執行上下文中,在變量環境中,都有一個外部引用,用來執行外部的執行上下文,我們把這個外部引用稱為outer

    • 上文已經說到,變量的查找首先會從執行上下文的詞法環境中查找,找不到就在變量環境中查找,再找不到的話就會沿著outer去外部的執行上下文中查找

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

    詞法作用域

    詞法作用域指的是作用域有代碼中函數的聲明位置決定的,也叫做靜態作用域

    也就是說,當創建一個執行上下文的時候,其內部的outer就會根據詞法作用域去執行對應的外部執行上下文

    在外部的執行上下文中查找時,也是先從詞法環境中開始

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

    閉包

    在js中,根據詞法作用域的規則,內部函數總是可以訪問外部函數中聲明的變量,當通過調用一個外部函數返回一個內部函數后,即使該外部函數已經執行結束了,但是內部函數引用外部函數的變量依然保存在內存中,我們就把這些變量的集合稱為閉包

    function foo() {
        var myName = " 極客時間 "
        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())

    上面代碼中,由于存在閉包現象,foo函數執行結束后,內部的變量還會被保存,調用棧如下圖:

    javascript執行上下文的過程是什么

    當執行到 bar.setName 方法中的myName = "極客邦"這句代碼時,JavaScript 引擎會沿著“當前執行上下文–>foo 函數閉包–> 全局執行上下文”的順序來查找 myName 變量,你可以參考下面的調用棧狀態圖:

    javascript執行上下文的過程是什么

    閉包的回收

    • 如果閉包使用不正確,會很容易造成內存泄漏的,關注閉包是如何回收的能讓你正確地使用閉包。

    • 通常,如果引用閉包的函數是一個全局變量,那么閉包會一直存在直到頁面關閉;但如果這個閉包以后不再使用的話,就會造成內存泄漏。

    • 如果引用閉包的函數是個局部變量,等函數銷毀后,在下次 JavaScript 引擎執行垃圾回收時,判斷閉包這塊內容如果已經不再被使用了,那么 JavaScript 引擎的垃圾回收器就會回收這塊內存。

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

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

    從上下文角度講this

    執行上下文分為三種,對應的this也只有三種:全局上下文的this,函數中的this,eval中的this

    • 箭頭函數沒有自己的執行上下文

    • 全局上下文的this指向全局對象

    • 函數上下文的this根據四種綁定規則判斷this指向

    • 執行上下文包含this指向

    到此,關于“javascript執行上下文的過程是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    白山市| 民权县| 西城区| 筠连县| 搜索| 镇远县| 亚东县| 察隅县| 晋宁县| 上林县| 和林格尔县| 邹城市| 日照市| 大竹县| 周宁县| 那坡县| 三穗县| 麻阳| 扎赉特旗| 万年县| 衡东县| 大邑县| 安康市| 兰坪| 台湾省| 铁力市| 甘南县| 哈尔滨市| 娄底市| 宝清县| 甘泉县| 洱源县| 剑川县| 南安市| 舒城县| 山东| 镇康县| 长丰县| 邢台市| 应城市| 调兵山市|