作者:河畔一角
轉發連結: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