如今前端面試,大多都會問到關於事件循環、執行棧等問題,本文經過案列、圖片等形式給你們講解這些概念,若是認真看完,我相信90%的同窗能夠完全理解。前端
JavaScript
具備自動垃圾回收機制,週期性會檢查沒有使用的變量,進行回收釋放。因此在閉包中,若是引用了外部的變量,則沒法進行釋放和回收,通常會傳參進去。web
垃圾回收:找出那些再也不繼續使用的變量,而後釋放其佔用的內存,垃圾收集器會按照固定的時間間隔週期性地執行這一操做。面試
在JS
中,每個數據都須要一個內存空間,內存空間又分爲棧內存(stack)與堆內存(heap)。ajax
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) 複製代碼
總結:
如今前端面試,你們都喜歡問EventLoop,但說實話,不少人看了無數篇文章,仍是稀裏糊塗,今天依然經過代碼+圖片的方式給你們演示效果。
爲了更好的理解事件機制,咱們須要先介紹執行棧。全部JS代碼運行都是被放入執行中執行的,遵循進棧和出棧,直到棧被清空。
JS 代碼在運行前都會建立執行上下文,也能夠理解爲執行環境,JS 中有三種執行上下文:
一般,咱們的代碼中都不止一個上下文,那這些上下文的執行順序應該是怎樣的?從上往下依次執行?
棧,是一種數據結構,遵循先進後出的原則。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執行上下文
總結:
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)
複製代碼
1
,並出棧
3
,並出棧
注意:new Promise 這個過程其實是同步的,只有resolve和reject後纔是異步
注意:宏任務和微任務是兩個隊列
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
整個文章到此結束,但願你們可以看懂!
總結:
有多少人能理解加粗的文字,上面實際上經過圖片和代碼給你們演示過了。
以上是爲你們整理的堆、棧、事件機制等概念,但願你們面試的時候可以說出個張3、李四來,不要再被對方diss了。
微信公衆號:前端將來
我的微信:
本文使用 mdnice 排版