最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

javascript執(zhí)行上下文詳解

 更新時間:2023年05月01日 21:07:42   作者:Lin__hr  
執(zhí)行上下文可以說是js代碼執(zhí)行的一個環(huán)境,存放了代碼執(zhí)行所需的變量,變量查找的作用域鏈規(guī)則以及this指向等。同時,它也是js很底層的東西,很多的問題如變量提升、作用域鏈和閉包等都可以在執(zhí)行上下文中找到答案,所以這也是我們學(xué)習(xí)執(zhí)行上下文的原因

簡介

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

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

  • 全局執(zhí)行上下文:當(dāng)進(jìn)入全局代碼時會進(jìn)行編譯,在編譯中創(chuàng)建全局執(zhí)行上下文,并生成可執(zhí)行代碼
  • 函數(shù)執(zhí)行上下文:執(zhí)行代碼的過程中,如果遇到函數(shù)調(diào)用,會編譯函數(shù)內(nèi)的代碼和創(chuàng)建函數(shù)執(zhí)行上下文,并創(chuàng)建可執(zhí)行代碼
  • eval執(zhí)行上下文:當(dāng)使用eval函數(shù)的時候,eval的代碼也會被編譯,并創(chuàng)建執(zhí)行上下文

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

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

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

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

編譯階段

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

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

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

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

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

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

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

var變量提升與let和const

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

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

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

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

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

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

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

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

作用域

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

  • 全局作用域:其中對象在代碼的任何地方都能訪問,其生命周期伴隨著頁面的生命周期
  • 函數(shù)作用域:在函數(shù)內(nèi)部定義的變量和函數(shù),只能在內(nèi)部訪問,外部不能訪問到,函數(shù)執(zhí)行結(jié)束后,函數(shù)內(nèi)部定義的變量會被銷毀(函數(shù)不會嗎???)
  • 塊級作用域:由代碼塊包含的代碼會形成一個塊級作用域(es6之前沒有),跟函數(shù)作用域類似

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

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

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

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

  • 首先,編譯創(chuàng)建該函數(shù)的執(zhí)行上下文,創(chuàng)建可執(zhí)行代碼
  • 在編譯階段,所有通過var聲明的變量(包括代碼塊里面的變量)都會被創(chuàng)建并存放在變量環(huán)境中,并初始化為undefined
  • 通過let或者const聲明的變量(不包括代碼塊碼里面的變量)都會被創(chuàng)建并存放在詞法環(huán)境中,設(shè)置為未初始化
  • 至此,編譯階段結(jié)束了,開始執(zhí)行代碼
  • 執(zhí)行代碼過程中遇到代碼塊時,會先將里面通過let或者const聲明的變量存放在詞法環(huán)境中并設(shè)置為初始化,其實,在詞法環(huán)境內(nèi)部,維護(hù)了一個小型的棧結(jié)構(gòu),棧底是函數(shù)最外層的變量,每遇到一個代碼塊,就將所包含的變量壓入詞法環(huán)境的棧結(jié)構(gòu),代碼塊執(zhí)行結(jié)束后,就將包含的變量彈出

接下來看一段代碼:

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í)行到代碼塊時,對應(yīng)的執(zhí)行上下文如下:

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

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

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

原因

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

  • 通過編譯階段,通過var聲明的變量已經(jīng)存在變量環(huán)境中并賦值為undefined,所以在執(zhí)行代碼的任何位置都能狗訪問得到,而不需要在聲明之后才能訪問
  • 而通過let聲明的變量,會被存放在詞法環(huán)境中但并未初始化(不包含代碼塊的let或const聲明的變量),所以并不能訪問,而是等到遇到let聲明語句的時候才初始化并賦值
  • 在遇到代碼塊時,會先將let和const聲明的變量存放在詞法環(huán)境中并設(shè)置為初始化,如果此時在代碼塊中在let聲明變量之前使用該變量,并不會去外部作用域找該變量,因為此時詞法作用域已經(jīng)存在改變量了,但未初始化,所以此時會報錯誤,這也是let暫時性死區(qū)的原因

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

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


在這里插入圖片描述

調(diào)用棧

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

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

  • js維護(hù)了一個棧結(jié)構(gòu),每當(dāng)遇到一個函數(shù)調(diào)用的時候,就創(chuàng)建一個執(zhí)行上下文,并壓入該棧中,
  • 這個棧叫做執(zhí)行上下文棧,也叫做調(diào)用棧
  • 當(dāng)函數(shù)執(zhí)行完畢之后,會將對應(yīng)的執(zhí)行上下文彈出棧結(jié)構(gòu)
  • 棧的容量是有限的,當(dāng)棧容量不夠的時候就有可能發(fā)生棧溢出

作用域鏈

  • 在函數(shù)中如果在當(dāng)前作用域中找不到所需要的變量,就得沿著作用域鏈往下去查找,直到找到為止
  • 我們都知道,當(dāng)一段代碼在執(zhí)行的時候,會有對應(yīng)的執(zhí)行上下文,那變量沿著作用域鏈查看的規(guī)則也是在執(zhí)行上下文中設(shè)置的
  • 在每個執(zhí)行上下文中,在變量環(huán)境中,都有一個外部引用,用來執(zhí)行外部的執(zhí)行上下文,我們把這個外部引用稱為outer
  • 上文已經(jīng)說到,變量的查找首先會從執(zhí)行上下文的詞法環(huán)境中查找,找不到就在變量環(huán)境中查找,再找不到的話就會沿著outer去外部的執(zhí)行上下文中查找
  • outer具體引用哪一個執(zhí)行上下文(作用域),是由詞法作用域決定的

詞法作用域

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

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

在外部的執(zhí)行上下文中查找時,也是先從詞法環(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ù)總是可以訪問外部函數(shù)中聲明的變量,當(dāng)通過調(diào)用一個外部函數(shù)返回一個內(nèi)部函數(shù)后,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中,我們就把這些變量的集合稱為閉包

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

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

在這里插入圖片描述

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

在這里插入圖片描述

閉包的回收

  • 如果閉包使用不正確,會很容易造成內(nèi)存泄漏的,關(guān)注閉包是如何回收的能讓你正確地使用閉包。
  • 通常,如果引用閉包的函數(shù)是一個全局變量,那么閉包會一直存在直到頁面關(guān)閉;但如果這個閉包以后不再使用的話,就會造成內(nèi)存泄漏。
  • 如果引用閉包的函數(shù)是個局部變量,等函數(shù)銷毀后,在下次 JavaScript 引擎執(zhí)行垃圾回收時,判斷閉包這塊內(nèi)容如果已經(jīng)不再被使用了,那么 JavaScript 引擎的垃圾回收器就會回收這塊內(nèi)存。
  • 所以在使用閉包的時候,你要盡量注意一個原則:如果該閉包會一直使用,那么它可以作為全局變量而存在;但如果使用頻率不高,而且占用內(nèi)存又比較大的話,那就盡量讓它成為一個局部變量。
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

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

  • 箭頭函數(shù)沒有自己的執(zhí)行上下文
  • 全局上下文的this指向全局對象
  • 函數(shù)上下文的this根據(jù)四種綁定規(guī)則判斷this指向
  • 執(zhí)行上下文包含this指向

參考:https://time.geekbang.org/column/intro/100033601

相關(guān)文章

  • 原生js封裝的ajax方法示例

    原生js封裝的ajax方法示例

    這篇文章主要介紹了原生js封裝的ajax方法,結(jié)合實例形式分析了JavaScript封裝ajax操作及調(diào)用方法相關(guān)操作技巧,非常簡單實用,需要的朋友可以參考下
    2018-08-08
  • JavaScript如何使用Promise實現(xiàn)分批處理接口請求

    JavaScript如何使用Promise實現(xiàn)分批處理接口請求

    當(dāng)我們在開發(fā)時遇到需要同時發(fā)起百條接口請求怎么辦呢,本文主要來和大家介紹一下JavaScript如何使用Promise實現(xiàn)分批處理接口請求,需要的可以參考下
    2024-03-03
  • JS中forEach()和map()的區(qū)別講解

    JS中forEach()和map()的區(qū)別講解

    forEach和map方法都是基于數(shù)組的迭代器實現(xiàn)的,它們的主要區(qū)別在于對回調(diào)函數(shù)的處理方式,這篇文章主要介紹了JS中forEach()和map()的區(qū)別,需要的朋友可以參考下
    2024-02-02
  • Javascript獲取當(dāng)前時間函數(shù)和時間操作小結(jié)

    Javascript獲取當(dāng)前時間函數(shù)和時間操作小結(jié)

    這篇文章主要介紹了Javascript獲取當(dāng)前時間函數(shù)和時間操作小結(jié),本文根據(jù)項目實際需求總結(jié)而來,需要的朋友可以參考下
    2014-10-10
  • js判斷登陸用戶名及密碼是否為空的簡單實例

    js判斷登陸用戶名及密碼是否為空的簡單實例

    下面小編就為大家?guī)硪黄猨s判斷登陸用戶名及密碼是否為空的簡單實例。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-05-05
  • JavaScript 閉包深入理解(closure)

    JavaScript 閉包深入理解(closure)

    最近在網(wǎng)上查閱了不少Javascript閉包(closure)相關(guān)的資料,寫的大多是非常的學(xué)術(shù)和專業(yè)。對于初學(xué)者來說別說理解閉包了,就連文字?jǐn)⑹龆己茈y看懂。撰寫此文的目的就是用最通俗的文字揭開Javascript閉包的真實面目。
    2009-05-05
  • 淺析js預(yù)加載/延遲加載

    淺析js預(yù)加載/延遲加載

    本文主要介紹了js預(yù)加載和延時加載2種技術(shù),簡單分析了他們的實現(xiàn)方式和優(yōu)缺點,非常的實用,有需要的朋友參考下
    2014-09-09
  • 如何寫出優(yōu)雅的JS 代碼

    如何寫出優(yōu)雅的JS 代碼

    在開發(fā)中,變量名,函數(shù)名一般要做到清晰明了,盡量做到看名字就能讓人知道你的意圖,所以變量和函數(shù)命名是挺重要,今天來看看如果較優(yōu)雅的方式給變量和函數(shù)命名。
    2021-05-05
  • javascript實現(xiàn)數(shù)字驗證碼的簡單實例

    javascript實現(xiàn)數(shù)字驗證碼的簡單實例

    本篇文章主要是對javascript實現(xiàn)數(shù)字驗證碼的簡單實例進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-02-02
  • JS 事件延遲執(zhí)行說明分析

    JS 事件延遲執(zhí)行說明分析

    為了避免用戶鼠標(biāo)無意識劃過,而觸發(fā)事件。浪費客戶端資源。
    2010-05-05

最新評論

静乐县| 当阳市| 普陀区| 抚松县| 和静县| 通城县| 曲麻莱县| 兴化市| 石门县| 高雄市| 朔州市| 乌拉特中旗| 奉新县| 濉溪县| 天峨县| 无棣县| 嘉兴市| 正定县| 深水埗区| 紫阳县| 长岭县| 镇宁| 永登县| 普安县| 石城县| 突泉县| 元氏县| 罗定市| 宕昌县| 平凉市| 游戏| 沂水县| 曲阜市| 河间市| 利川市| 淮南市| 台东市| 元谋县| 高州市| 安义县| 德庆县|