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

詳解JS中的堆棧,事件循環(huán),執(zhí)行上下文和作用域以及閉包

 更新時(shí)間:2024年01月11日 08:20:03   作者:easylee  
這篇文章主要為大家詳細(xì)介紹了JavaScript中的堆棧,事件循環(huán),執(zhí)行上下文和作用域以及閉包的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

1. 堆棧

在JavaScript中,內(nèi)存堆是內(nèi)存分配的地方,調(diào)用棧是代碼執(zhí)行的地方。

原始類型的保存方式:在變量中保存的是值本身,所以原始類型也被稱之為值類型。

對(duì)象類型的保存方式:在變量中保存的是對(duì)象的“引用”,所以對(duì)象類型也被稱之為引用類型。

調(diào)用棧理解非常簡(jiǎn)單,當(dāng)遇見(jiàn)一個(gè)方法時(shí)推入調(diào)用棧中,執(zhí)行一個(gè)方法彈出棧,每一個(gè)方法稱為一個(gè)調(diào)用幀。

2. 事件循環(huán)

理解了堆棧之后,接著來(lái)看一下與之相關(guān)的事件循環(huán)。

首先需要明確的是JavaScript是單線程語(yǔ)言,所有代碼都執(zhí)行在一個(gè)線程中,這通常會(huì)導(dǎo)致一個(gè)問(wèn)題,當(dāng)一個(gè)方法耗時(shí)過(guò)長(zhǎng),整個(gè)頁(yè)面隨之卡住,所以為了避免這種情況發(fā)生,JavaScript中存在事件循環(huán)的機(jī)制(并非JavaScript創(chuàng)造),來(lái)循環(huán)執(zhí)行事件,堵塞的事件通過(guò)循環(huán)在后期再來(lái)判斷是否執(zhí)行完成,比如讀取接口,后期再來(lái)看接口是否請(qǐng)求完成,請(qǐng)求完成之后再執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)(接口請(qǐng)求是瀏覽器提供的能力,不占用單線程)。

事件循環(huán)也就是將任務(wù)分為同步任務(wù)和異步任務(wù),任務(wù)按照順序進(jìn)行執(zhí)行。

事件循環(huán)中一個(gè)重要概念是宏任務(wù)和微任務(wù),宏任務(wù)也就是線程中首先一輪執(zhí)行的函數(shù),微任務(wù)也就是宏任務(wù)里面的任務(wù),類似進(jìn)程和線程的關(guān)系,宏任務(wù)是進(jìn)程,微任務(wù)是線程,下面來(lái)看一下三者之間的關(guān)系:

事件循環(huán),其實(shí)循環(huán)的就是宏任務(wù)和微任務(wù),當(dāng)宏任務(wù)中有微任務(wù)時(shí),執(zhí)行里面的微任務(wù)。

下面來(lái)看一下在JavaScript中具體哪些函數(shù)是宏任務(wù),哪些是微任務(wù)

  • macro-task(宏任務(wù)):包括整體代碼script,setTimeout,setInterval
  • micro-task(微任務(wù)):Promise,process.nextTick(node代碼, 類似vue中this.$nextTick)

具體來(lái)看一下執(zhí)行流程:

  • 整體script作為第一個(gè)宏任務(wù)進(jìn)入主線程;
  • 遇到setTimeout 、 setInterval,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)事件隊(duì)列中;
  • 遇到process.nextTick(),其回調(diào)函數(shù)被分發(fā)到微任務(wù)事件隊(duì)列中;
  • 遇到Promisenew Promise函數(shù)體內(nèi)容直接執(zhí)行。then等回調(diào)部分被分發(fā)到微任務(wù)事件隊(duì)列中;
  • 微任務(wù)在宏任務(wù)執(zhí)行后開(kāi)始執(zhí)行,比如微任務(wù)屬于第一個(gè)宏任務(wù),那么第一個(gè)宏任務(wù)執(zhí)行完,就執(zhí)行第一個(gè)宏任務(wù)里面的微任務(wù),也就是說(shuō) script 里面要是包含微任務(wù),那么是先于 setTimeout 等第二輪執(zhí)行的宏任務(wù)的;
  • 第一輪執(zhí)行完成后,開(kāi)始第二輪,也就是setTimeout 、 setInterval 回調(diào)函數(shù)里面的內(nèi)容,屬于第二輪宏任務(wù),如果里面包含微任務(wù),那么緊接著回調(diào)函數(shù)里面內(nèi)容執(zhí)行完之后開(kāi)始執(zhí)行;
  • 如果微任務(wù)里面還包含微任務(wù),那么是緊接著外層的微任務(wù)開(kāi)始執(zhí)行的。

注意在node有一些不同,存在下面的優(yōu)先級(jí)順序:process.nextTick() > Promise.then() > setTimeout > setImmediate

下面來(lái)看一個(gè)具體的例子:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

第一輪事件循環(huán)流程分析如下:

  • 整體script作為第一個(gè)宏任務(wù)進(jìn)入主線程,遇到console.log,輸出1。
  • 遇到setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中。我們暫且記為setTimeout1。
  • 遇到process.nextTick(),其回調(diào)函數(shù)被分發(fā)到微任務(wù)Event Queue中。我們記為process1
  • 遇到Promise,new Promise直接執(zhí)行,輸出7。then被分發(fā)到微任務(wù)Event Queue中。我們記為then1。
  • 又遇到了setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中,我們記為setTimeout2。
宏任務(wù)Event Queue微任務(wù)Event Queue
setTimeout1process1
setTimeout2then1
  • 上表是第一輪事件循環(huán)宏任務(wù)結(jié)束時(shí)各Event Queue的情況,此時(shí)已經(jīng)輸出了1和7。
  • 我們發(fā)現(xiàn)了process1then1兩個(gè)微任務(wù)。
  • 執(zhí)行process1,輸出6。
  • 執(zhí)行then1,輸出8。

好了,第一輪事件循環(huán)正式結(jié)束,這一輪的結(jié)果是輸出1,7,6,8。那么第二輪時(shí)間循環(huán)從setTimeout1宏任務(wù)開(kāi)始:

首先輸出2。接下來(lái)遇到了process.nextTick(),同樣將其分發(fā)到微任務(wù)Event Queue中,記為process2。new Promise立即執(zhí)行輸出4,then也分發(fā)到微任務(wù)Event Queue中,記為then2。

宏任務(wù)Event Queue微任務(wù)Event Queue
setTimeout2process2
then2
  • 第二輪事件循環(huán)宏任務(wù)結(jié)束,我們發(fā)現(xiàn)有process2then2兩個(gè)微任務(wù)可以執(zhí)行。
  • 輸出3。
  • 輸出5。
  • 第二輪事件循環(huán)結(jié)束,第二輪輸出2,4,3,5。
  • 第三輪事件循環(huán)開(kāi)始,此時(shí)只剩setTimeout2了,執(zhí)行。
  • 直接輸出9。
  • process.nextTick()分發(fā)到微任務(wù)Event Queue中。記為process3。
  • 直接執(zhí)行new Promise,輸出11。
  • then分發(fā)到微任務(wù)Event Queue中,記為then3
宏任務(wù)Event Queue微任務(wù)Event Queue
process3
then3
  • 第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個(gè)微任務(wù)process3then3。
  • 輸出10。
  • 輸出12。
  • 第三輪事件循環(huán)結(jié)束,第三輪輸出9,11,10,12。

整段代碼,共進(jìn)行了三次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。 (請(qǐng)注意,node環(huán)境下的事件監(jiān)聽(tīng)依賴libuv與前端環(huán)境不完全相同,輸出順序可能會(huì)有誤差)。

3. 執(zhí)行上下文

接著來(lái)看一下執(zhí)行上下文,簡(jiǎn)而言之,執(zhí)行上下文是評(píng)估和執(zhí)行 JavaScript 代碼的環(huán)境的抽象概念。每當(dāng) Javascript 代碼在運(yùn)行的時(shí)候,它都是在執(zhí)行上下文中運(yùn)行。

JavaScript 中有三種執(zhí)行上下文類型:

  • 全局執(zhí)行上下文 — 這是默認(rèn)或者說(shuō)基礎(chǔ)的上下文,任何不在函數(shù)內(nèi)部的代碼都在全局上下文中。它會(huì)執(zhí)行兩件事:創(chuàng)建一個(gè)全局的 window 對(duì)象(瀏覽器的情況下),并且設(shè)置 this 的值等于這個(gè)全局對(duì)象。一個(gè)程序中只會(huì)有一個(gè)全局執(zhí)行上下文。
  • 函數(shù)執(zhí)行上下文 — 每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí), 都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的上下文。每個(gè)函數(shù)都有它自己的執(zhí)行上下文,不過(guò)是在函數(shù)被調(diào)用時(shí)創(chuàng)建的。函數(shù)上下文可以有任意多個(gè)。每當(dāng)一個(gè)新的執(zhí)行上下文被創(chuàng)建,它會(huì)按定義的順序(將在后文討論)執(zhí)行一系列步驟。
  • Eval 函數(shù)執(zhí)行上下文 — 執(zhí)行在 eval 函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文,但由于 JavaScript 開(kāi)發(fā)者并不經(jīng)常使用 eval,所以在這里不會(huì)討論。

總結(jié)一下,執(zhí)行上下文大體分為全局和函數(shù)執(zhí)行上下文,也就是執(zhí)行環(huán)境,函數(shù)可以讀取外部函數(shù)的變量,通常也稱為閉包,通過(guò)這個(gè)原理,相比靜態(tài)語(yǔ)言,可以更靈活的獲取外部的參數(shù)。

執(zhí)行上下文的不同,直接導(dǎo)致 this 值內(nèi)容的不同。

同時(shí)一個(gè)執(zhí)行上下文將會(huì)創(chuàng)建一個(gè)上面的執(zhí)行棧,而不是所有的執(zhí)行上下文的所有方法共用一個(gè)執(zhí)行棧。

4. 作用域

作用域這個(gè)內(nèi)容非常簡(jiǎn)單,基本上所有語(yǔ)言都存在作用域,在JavaScript中,需要注意一點(diǎn),函數(shù)中創(chuàng)建的值是在創(chuàng)建的時(shí)候獲得的,而不是調(diào)用,通過(guò)代碼來(lái)看一下:

let x = 10
function fn() {
  x = 20
  console.log(x)
}
function foo() {
  x = 30
  fn()  // 20
}
foo()

上面代碼打印的值仍然是20,因?yàn)閯?chuàng)建 fn 函數(shù)時(shí),對(duì)應(yīng)的作用域里面的值為20,而不是調(diào)用 fn 時(shí),foo函數(shù)作用域里面的值。

這里有一個(gè)注意點(diǎn),我們來(lái)看下面的代碼:

let x = 10
function fn() {
  console.log(x)
}
function foo() {
  x = 30
  fn()  // 30
}
foo()

上面的代碼會(huì)打印30,這是怎么回事,不是說(shuō)再創(chuàng)建的位置取值嗎?

答案是,確實(shí)是在創(chuàng)建的位置,但是先執(zhí)行的foo函數(shù),把外層的x的值變更了,下面的代碼能解釋這個(gè)問(wèn)題:

let x = 10
function fn() {
  console.log(x)
}
function foo() {
  let x = 30
  fn()  // 10
}
foo()

可以看到,打印的其實(shí)并不是foo函數(shù)里的值,而是創(chuàng)建函數(shù)時(shí)的值。

接著我們要理一下,什么是創(chuàng)建時(shí)的值,這里要引出一個(gè)概念,作用域鏈,也就是取值的鏈條:

  • 現(xiàn)在當(dāng)前作用域查找a,如果有則獲取并結(jié)束,如果沒(méi)有則繼續(xù);
  • 如果當(dāng)前作用域是全局作用域,則證明a未定義,結(jié)束,否則繼續(xù);
  • (不是全局作用域,那就是函數(shù)作用域)將創(chuàng)建該函數(shù)的作用域作為當(dāng)前作用域;
  • 跳轉(zhuǎn)到第一步。
var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a)	// 10
    console.log(b) // 20
  }
  return bar
}
var x = fn()
var b = 200
x()

總結(jié)一下

函數(shù)上下文環(huán)境是在函數(shù)執(zhí)行時(shí)創(chuàng)建的,同時(shí)在上下文中生成了對(duì)應(yīng)的變量,同一個(gè)函數(shù)根據(jù)傳遞進(jìn)來(lái)的參數(shù)不同,里面的變量也會(huì)不同;

而作用域是函數(shù)創(chuàng)建時(shí)就產(chǎn)生了,作用域作用域,說(shuō)白了就是這個(gè)函數(shù)自己的地盤(pán),無(wú)論是否調(diào)用,反正這個(gè)函數(shù)都擁有這個(gè)地盤(pán)了;

只有當(dāng)調(diào)用時(shí)才會(huì)創(chuàng)建上下文環(huán)境,并且可能不止一個(gè),比如通過(guò)傳遞不同參數(shù),可能會(huì)創(chuàng)建多個(gè)上下文環(huán)境,上下文環(huán)境說(shuō)白了就是在這個(gè)環(huán)境中變量的值是什么,以便使用。

5. 閉包

前面鋪墊了那么多內(nèi)容,主要是用于引出閉包,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。 在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)“。 在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的橋梁。

下面我們來(lái)看看閉包運(yùn)用的兩種形式:

第一,函數(shù)作為返回值:

function fn() {
  var max = 100
  return function bar(x) {
    if (x > max) {
      console.log(x)
    }
  }
}
var f1 = fn()
f1(115)

上面返回的內(nèi)部函數(shù)就是一個(gè)閉包,它可以讀取其外部fn函數(shù)的max值,從這種情況來(lái)說(shuō),下面的情況也是閉包:

var max = 100
function fn() {
  console.log(max)
}
fn()

從上面兩段代碼可以看出,所有的函數(shù)其實(shí)只要函數(shù)內(nèi)部能夠讀取了其外部的變量,都可以稱為閉包,也就是說(shuō),所有函數(shù)都是閉包,因?yàn)橐粋€(gè)函數(shù)最少也是可以讀取全局環(huán)境下的變量的,只是第二段代碼通常不是閉包的常見(jiàn)使用形式,常見(jiàn)的使用形式還是將函數(shù)作為返回值

第二,函數(shù)作為參數(shù)傳遞:

var max = 10
var fn = function (x) {
  if (x > 100) {
    console.log(x) // 不打印任何東西
  }
}
;(function (f) {
  var max = 100
  f(15)
})(fn)

函數(shù)作為參數(shù)傳遞,進(jìn)入另一個(gè)函數(shù)作為另一個(gè)函數(shù)的內(nèi)容,此時(shí)傳遞的這個(gè)函數(shù)就是一個(gè)閉包,注意一下,這里的max根據(jù)前面的作用域原則,是讀取函數(shù)定義時(shí)的max,而不是調(diào)用時(shí)。

以上就是詳解JS中的堆棧,事件循環(huán),執(zhí)行上下文和作用域以及閉包的詳細(xì)內(nèi)容,更多關(guān)于JS事件循環(huán) 執(zhí)行上下文 作用域 閉包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Javascript remove 自定義數(shù)組刪除方法

    Javascript remove 自定義數(shù)組刪除方法

    Javascript自定義數(shù)組刪除方法remove(),需要的朋友可以參考下。
    2009-10-10
  • JavaScript實(shí)現(xiàn)電燈開(kāi)關(guān)小案例

    JavaScript實(shí)現(xiàn)電燈開(kāi)關(guān)小案例

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)電燈開(kāi)關(guān)小案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • Javascript簡(jiǎn)單改變表單元素背景的方法

    Javascript簡(jiǎn)單改變表單元素背景的方法

    這篇文章主要介紹了Javascript簡(jiǎn)單改變表單元素背景的方法,涉及javascript操作頁(yè)面元素樣式的基本技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下
    2015-07-07
  • 原生JS實(shí)現(xiàn)圖片跑馬燈特效

    原生JS實(shí)現(xiàn)圖片跑馬燈特效

    這篇文章主要為大家詳細(xì)介紹了原生JS實(shí)現(xiàn)圖片跑馬燈特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • JavaScript如何檢測(cè)并處理頁(yè)面卡頓

    JavaScript如何檢測(cè)并處理頁(yè)面卡頓

    頁(yè)面卡頓通常指的是網(wǎng)頁(yè)的響應(yīng)速度慢,用戶的操作不能及時(shí)得到反饋,本文將為大家介紹如何使用JavaScript實(shí)現(xiàn)檢測(cè)與處理頁(yè)面卡頓,需要的可以了解下
    2025-03-03
  • 淺談$_FILES數(shù)組為空的原因

    淺談$_FILES數(shù)組為空的原因

    下面小編就為大家?guī)?lái)一篇淺談$_FILES數(shù)組為空的原因。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • BootStrap tooltip提示框使用小結(jié)

    BootStrap tooltip提示框使用小結(jié)

    這篇文章主要為大家詳細(xì)介紹了BootStrap tooltip提示框使用小結(jié) ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • jQuery scrollFix滾動(dòng)定位插件

    jQuery scrollFix滾動(dòng)定位插件

    這篇文章主要介紹了jQuery scrollFix滾動(dòng)定位插件,當(dāng)用戶向上或向下滾動(dòng)頁(yè)面到一定位置時(shí),目標(biāo)元素開(kāi)始固定定位(position:fixed),當(dāng)回滾到原位置時(shí)目標(biāo)元素恢復(fù)到原狀態(tài),需要的朋友可以參考下
    2015-04-04
  • 權(quán)威JavaScript 中的內(nèi)存泄露模式

    權(quán)威JavaScript 中的內(nèi)存泄露模式

    權(quán)威JavaScript 中的內(nèi)存泄露模式...
    2007-08-08
  • js date 格式化

    js date 格式化

    本文將以yyyy-MM-dd HH:mm:ss為例介紹js date 格式化的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02

最新評(píng)論

汉阴县| 西藏| 右玉县| 西畴县| 兰州市| 兰考县| 通化市| 松江区| 岗巴县| 郸城县| 阿坝| 宜兰县| 年辖:市辖区| 鹿泉市| 木里| 盖州市| 马龙县| 双流县| 安义县| 合山市| 温泉县| 盘锦市| 如皋市| 应城市| 江陵县| 扶沟县| 临武县| 方正县| 盖州市| 南岸区| 明光市| 贺州市| 鹤峰县| 光山县| 陆川县| 四川省| 浮梁县| 珠海市| 犍为县| 霍林郭勒市| 孟村|