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

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

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

簡(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引擎開(kāi)始執(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ù)聲明部分提升到代碼開(kāi)頭的“行為”。變量被提升后,會(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開(kāi)始引入了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ì)被銷(xiāo)毀(函數(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é)束了,開(kāi)始執(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)境中查找


在這里插入圖片描述

調(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)境中開(kāi)始

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)用棧如下圖:

在這里插入圖片描述

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

在這里插入圖片描述

閉包的回收

  • 如果閉包使用不正確,會(huì)很容易造成內(nèi)存泄漏的,關(guān)注閉包是如何回收的能讓你正確地使用閉包。
  • 通常,如果引用閉包的函數(shù)是一個(gè)全局變量,那么閉包會(huì)一直存在直到頁(yè)面關(guān)閉;但如果這個(gè)閉包以后不再使用的話,就會(huì)造成內(nèi)存泄漏。
  • 如果引用閉包的函數(shù)是個(gè)局部變量,等函數(shù)銷(xiāo)毀后,在下次 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指向

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

相關(guān)文章

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

    原生js封裝的ajax方法示例

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

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

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

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

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

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

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

    js判斷登陸用戶名及密碼是否為空的簡(jiǎn)單實(shí)例

    下面小編就為大家?guī)?lái)一篇js判斷登陸用戶名及密碼是否為空的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-05-05
  • JavaScript 閉包深入理解(closure)

    JavaScript 閉包深入理解(closure)

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

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

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

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

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

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

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

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

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

最新評(píng)論

祁连县| 徐州市| 玉山县| 宜兰市| 宁陵县| 集贤县| 噶尔县| 鄂尔多斯市| 全州县| 涿鹿县| 广德县| 新泰市| 罗田县| 墨竹工卡县| 凤冈县| 桐庐县| 宾川县| 昆山市| 轮台县| 衡山县| 渭南市| 寻乌县| 定日县| 静安区| 迁西县| 阿合奇县| 根河市| 汕尾市| 武隆县| 怀仁县| 泾川县| 四子王旗| 金塔县| 河西区| 上虞市| 淅川县| 荣成市| 富锦市| 霸州市| 和静县| 文昌市|