作者:河畔一角
轉發連結:https://mp.weixin.qq.com/s/Er4ZoRqSy9upZQbI0k14BA
前言
現在前端面試,大多都會問到關于事件循環、執行棧等問題,本文通過案列、圖檔等形式給大家講解這些概念,如果認真看完,我相信90%的同學可以徹底了解。
JS記憶體機制
JavaScript具有自動垃圾回收機制,周期性會檢查沒有使用的變量,進行回收釋放。是以在閉包中,如果引用了外部的變量,則無法進行釋放和回收,一般會傳參進去。
垃圾回收:找出那些不再繼續使用的變量,然後釋放其占用的記憶體,垃圾收集器會按照固定的時間間隔周期性地執行這一操作。
在JS中,每一個資料都需要一個記憶體空間,記憶體空間又分為棧記憶體(stack)與堆記憶體(heap)。
棧記憶體一般儲存基礎資料類型
Number String Null Undefined Boolean Symbol
看一個例子:
var num = 1
我們定義一個變量num,系統自動配置設定存儲空間。我們可以直接操作儲存在棧記憶體空間的值,是以基礎資料類型都是按值通路。
資料在棧記憶體中的存儲與使用方式類似于資料結構中的堆棧資料結構,遵循 後進先出的原則。
堆記憶體一般儲存引用資料類型
var user = { name:'jack' }var arr = [1,3,5]
JS的引用資料類型,比如數組Array,它們值的大小是不固定的。引用資料類型的值是儲存在堆記憶體中的對象。JavaScript不允許直接通路堆記憶體中的位置,是以我們不能直接操作對象的堆記憶體空間。
通過下面這張圖,我們就能直覺了解。
var num = 1; // 棧 var name = '前端未來'; // 棧// 變量user存在于棧中,{name:'河畔'}存在于堆記憶體中var user = { name: '河畔' }; // 變量arr存在于棧中,[1, 2, 3] 作為對象存在于堆記憶體中var arr = [1, 3, 5];
是以當我們要通路堆記憶體中的引用資料類型時,實際上我們首先是從棧中擷取了該對象的指針,然後再從堆記憶體中取得我們需要的資料。
是以,我們經常說:基本類型指派互相不影響,引用類型指派,會影響原對象。
一個例子就能看明白:
var a = 20;var b = a;b = 30;// a為20,b為30,值類型不影響console.log(a) var user = { name: '河畔' }; var info = user;info.name = 'Jack'// 列印為jack,指向同一個記憶體位址console.log(user.name)
總結:
- JavaScript具備自動垃圾回收機制
- JS記憶體分為堆記憶體和棧記憶體
- 引用類型在棧中儲存指針,在堆中儲存對象值
- 棧記憶體資料遵循先進後出
EventLoop
現在前端面試,大家都喜歡問EventLoop,但說實話,很多人看了無數篇文章,還是稀裡糊塗,今天依然通過代碼+圖檔的方式給大家示範效果。
為了更好的了解事件機制,我們需要先介紹執行棧。所有JS代碼運作都是被放入執行中執行的,遵循進棧和出棧,直到棧被清空。
執行棧
JS 代碼在運作前都會建立執行上下文,也可以了解為執行環境,JS 中有三種執行上下文:
- 全局執行上下文,預設的,在浏覽器中是 window 對象
- 函數執行上下文,JS 的函數每當被調用時會建立一個上下文。
- Eval 執行上下文,eval 函數會産生自己的上下文。
通常,我們的代碼中都不止一個上下文,那這些上下文的執行順序應該是怎樣的?從上往下依次執行?
棧,是一種資料結構,遵循先進後出的原則。JS 中的執行棧就具有這樣的結構,當引擎第一次遇到 JS 代碼時,會産生一個全局執行上下文并壓入執行棧,每遇到一個函數調用,就會往棧中壓入一個新的上下文。引擎執行棧頂的函數,執行完畢,彈出目前執行上下文。
接下來,我們看一個例子:
function foo() { console.log('1'); bar(); console.log('3');}function bar() { console.log('2');}foo();
這個毫無疑問,大家都知道答案,執行棧是怎麼調用的?
首先執行這個JS檔案,建立一個全局上下文,并壓入執行棧中,當 foo() 函數被調用時,将 foo 函數的執行上下文壓入執行棧,接着執行輸出 ‘1’;當 bar() 函數被調用,将 bar 函數的執行上下文壓入執行棧,接着執行輸出 ‘2’;bar() 執行完畢,被彈出執行棧,foo() 函數接着執行,輸出 ‘3’;foo() 函數執行完畢,被彈出執行棧,最後清空整個執行棧。這就是先進後出,Foo先被壓入執行棧,最後才被彈出執行棧
EC就是Execute Context執行上下文
總結:
- 所有JS代碼運作,都需要放入執行棧中.
- 執行上下文包含了三種(全局、函數、eval)
- 棧是一種資料結構,遵循先進後出
接下來,看一道經典面試題
console.log(1)new Promise(function(resolve){ console.log(3) resolve(100)}).then(function(data){ console.log(data)})setTimeout(function(){ console.log(4);})console.log(2)
上面的面試題列印結果:1 3 2 100 4
你能說出具體執行步驟嗎?
我們都知道JS本身是單線程的,一次隻能幹一件事兒,那麼像定時器、Promise這些它是怎麼處理的呢?實際上就要介紹quene隊列了。
主線程執行同步代碼塊,遇到定時器、Promise等異步任務時,會建立事件隊列,把他們丢到隊列裡面去,等主線程執行完成後,再回去執行隊列中的task.
是以,我們的JS執行主要包括同步任務和異步任務,整個同步任務會進入到主線程中,最後放入執行棧中執行,就是我們上面給大家講解的執行棧,接下來關注異步任務。
浏覽器的JS中,異步任務又分為宏任務和微任務,宏任務和微任務都是屬于隊列,而不是放在棧中。微任務會建立一個隊列,宏任務會建立一個隊列,而主線程執行完以後,會優先執行微任務,把微任務全部放到執行棧中執行,最後再從宏任務中取出一個放入執行棧進行執行,執行完後,再取一個,直到執行完所有的宏任務。
接下來看張圖:
左側JS圖包含了堆和棧,所有的代碼都會被放入棧中執行,我們叫執行棧,執行棧是一條主線程,先執行同步任務,中間遇到ajax、setTimeout等異步任務後,會push到queue中,最後再把隊列中事件取出來放入執行中執行,依次循環這個過程。
那我們再來看上面的例子:
console.log(1)new Promise(function(resolve){ console.log(3) resolve(100)}).then(function(data){ console.log(data)})setTimeout(function(){ console.log(4);})console.log(2)
- 建立全局上下文,并壓入執行棧中
- 把同步代碼console.log壓入執行棧中執行,列印1,并出棧
- 把同步代碼new Promise壓入執行棧中執行,列印3,并出棧
注意:new Promise 這個過程實際上是同步的,隻有resolve和reject後才是異步
- then屬于異步任務,push到微任務隊列中,并建立事件
- setTimeout屬于異步任務,push到宏任務隊列中,并建立事件
注意:宏任務和微任務是兩個隊列
- 把同步代碼console.log壓入執行棧中執行,列印2,并出棧
到此整個執行棧隻剩下全局上下文,沒有可以執行的代碼了
- 微任務先執行,是以把微任務隊列中的事件全部拿出來,放入執行棧進行執行。列印100,并出棧
- 從宏任務隊列中,隻取出一個事件放入執行棧中執行,列印4
那我們把上面例子改造一下:
console.log(1)new Promise(function(resolve){ console.log(3) resolve(100)}).then(function(data){ console.log(data)}).then(function(){ console.log(200)})setTimeout(function(){ console.log(4);})setTimeout(function(){ console.log(5);})console.log(2)
有2個then,2個setTimeout,此時學完後,您覺得應該列印多少?答案是:1 3 2 100 200 4 5
整個文章到此結束,希望大家能夠看懂!
總結:
- JavaScript具備自動垃圾回收機制
- JS記憶體分為堆記憶體和棧記憶體
- 引用類型在棧中儲存指針,在堆中儲存對象值
- 所有JS代碼運作,都需要放入執行棧中.
- 執行代碼前,會先建立執行上下文
- 執行上下文包含了三種(全局、函數、eval)
- 同步任務先執行,異步任務放隊列
- 微任務先執行,宏任務後執行
- 微任務全部拉入執行棧,宏任務一次拉一個
- 棧是先進後出,隊列是先進先出
有多少人能了解加粗的文字,上面實際上通過圖檔和代碼給大家示範過了。
以上是為大家整理的堆、棧、事件機制等概念,希望大家面試的時候能夠說出個張三、李四來,不要再被對方diss了。
推薦JavaScript經典執行個體學習資料文章
《Node + H5 實作大檔案分片上傳、斷點續傳》
《一文了解檔案上傳全過程(1.8w字深度解析)「前端進階必備」》
《【實踐總結】關于小程式掙脫枷鎖實作批量上傳》
《手把手教你前端的各種檔案上傳攻略和大檔案斷點續傳》
《位元組跳動面試官:請你實作一個大檔案上傳和斷點續傳》
《談談前端關于檔案上傳下載下傳那些事【實踐】》
《手把手教你如何編寫一個前端圖檔壓縮、方向糾正、預覽、上傳插件》
《最全的 JavaScript 子產品化方案和工具》
《「前端進階」JS中的記憶體管理》
《JavaScript正則深入以及10個非常有意思的正則實戰》
《前端面試者經常忽視的一道JavaScript 面試題》
《一行JS代碼實作一個簡單的模闆字元串替換「實踐」》
《JS代碼是如何被壓縮的「前端進階進階」》
《前端開發規範:命名規範、html規範、css規範、js規範》
《【規範篇】前端團隊代碼規範最佳實踐》
《100個原生JavaScript代碼片段知識點詳細彙總【實踐】》
《關于前端174道 JavaScript知識點彙總(一)》
《關于前端174道 JavaScript知識點彙總(二)》
《關于前端174道 JavaScript知識點彙總(三)》
《幾個非常有意思的javascript知識點總結【實踐】》
《都2020年了,你還不會JavaScript 裝飾器?》
《JavaScript實作圖檔合成下載下傳》
《70個JavaScript知識點詳細總結(上)【實踐】》
《70個JavaScript知識點詳細總結(下)【實踐】》
《開源了一個 JavaScript 版敏感詞過濾庫》
《送你 43 道 JavaScript 面試題》
《3個很棒的小衆JavaScript庫,你值得擁有》
《手把手教你深入鞏固JavaScript知識體系【思維導圖】》
《推薦7個很棒的JavaScript産品步驟引導庫》
《Echa哥教你徹底弄懂 JavaScript 執行機制》
《一個合格的中級前端工程師需要掌握的 28 個 JavaScript 技巧》
《深入解析高頻項目中運用到的知識點彙總【JS篇】》
《JavaScript 工具函數大全【新】》
《從JavaScript中看設計模式(總結)》
《身份證号碼的正規表達式及驗證詳解(JavaScript,Regex)》
《浏覽器中實作JavaScript計時器的4種創新方式》
《Three.js 動效方案》
《手把手教你常用的59個JS類方法》
《127個常用的JS代碼片段,每段代碼花30秒就能看懂-【上】》
《深入淺出講解 js 深拷貝 vs 淺拷貝》
《手把手教你JS開發H5遊戲【消滅星星】》
《深入淺出講解JS中this/apply/call/bind巧妙用法【實踐】》
《手把手教你全方位解讀JS中this真正含義【實踐】》
《書到用時方恨少,一大波JS開發工具函數來了》
《幹貨滿滿!如何優雅簡潔地實作時鐘翻牌器(支援JS/Vue/React)》
《手把手教你JS 異步程式設計六種方案【實踐】》
《讓你減少加班的15條高效JS技巧知識點彙總【實踐】》
《手把手教你JS開發H5遊戲【黃金礦工】》
《手把手教你JS實作監控浏覽器上下左右滾動》
《JS 經典執行個體知識點整理彙總【實踐】》
《2.6萬字JS幹貨分享,帶你領略前端魅力【基礎篇】》
《2.6萬字JS幹貨分享,帶你領略前端魅力【實踐篇】》
《簡單幾步讓你的 JS 寫得更漂亮》
《恭喜你獲得治療JS this的詳細藥方》
《談談前端關于檔案上傳下載下傳那些事【實踐】》
《面試中教你繞過關于 JavaScript 作用域的 5 個坑》
《Jquery插件(常用的插件庫)》
《【JS】如何防止重複發送ajax請求》
《JavaScript+Canvas實作自定義畫闆》
《Continuation 在 JS 中的應用「前端篇」》
作者:河畔一角
轉發連結:https://mp.weixin.qq.com/s/Er4ZoRqSy9upZQbI0k14BA