本期的主題是呼叫堆疊,本計劃一共28期,每期重點攻克一個面試重難點,如果你還不瞭解本進階計劃,文末點選檢視全部文章。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
堆疊的內容和執行順序我就不說了,前面兩篇已經介紹過了。
但是今天補充一個知識點:某些情況下,呼叫堆疊中函式呼叫的數量超出了呼叫堆疊的實際大小,瀏覽器會丟擲一個錯誤終止執行。
對於下面的遞迴就會無限制的執行下去,直到超出呼叫堆疊的實際大小,這個是瀏覽器定義的。
function foo() {
foo();
}
foo();
複製程式碼
現在正式開始今天的主題,記憶體空間詳解
棧資料結構
棧的結構就是後進先出**(LIFO)**,如果讀過前面兩篇文章應該是相當熟悉了。文中使用乒乓球盒子的結構來解釋。
處於盒子中最頂層的乒乓球5,它一定是最後被放進去,但可以最先被使用。而我們想要使用底層的乒乓球1,就必須將上面的4個乒乓球取出來,讓乒乓球1處於盒子頂層。
堆資料結構
堆資料結構是一種樹狀結構。它的存取資料的方式與書架和書非常相似。我們只需要知道書的名字就可以直接取出書了,並不需要把上面的書取出來。JSON格式的資料中,我們儲存的key-value
可以是無序的,因為順序的不同並不影響我們的使用,我們只需要關心書的名字。
佇列
佇列是一種先進先出(FIFO)的資料結構,這是事件迴圈(Event Loop)的基礎結構,事件迴圈我們會在第8期詳解介紹。
變數的存放
首先我們應該知道記憶體中有棧和堆,那麼變數應該存放在哪裡呢,堆?棧?
- 1、基本型別 --> 儲存在棧記憶體中,因為這些型別在記憶體中分別佔有固定大小的空間,通過按值來訪問。基本型別一共有6種:Undefined、Null、Boolean、Number 、String和Symbol
- 2、引用型別 --> 儲存在堆記憶體中,因為這種值的大小不固定,因此不能把它們儲存到棧記憶體中,但記憶體地址大小的固定的,因此儲存在堆記憶體中,在棧記憶體中存放的只是該物件的訪問地址。當查詢引用型別的變數時, 先從棧中讀取記憶體地址, 然後再通過地址找到堆中的值。對於這種,我們把它叫做按引用訪問。
在計算機的資料結構中,棧比堆的運算速度快,Object是一個複雜的結構且可以擴充套件:陣列可擴充,物件可新增屬性,都可以增刪改查。將他們放在堆中是為了不影響棧的效率。而是通過引用的方式查詢到堆中的實際物件再進行操作。所以查詢引用型別值的時候先去棧查詢再去堆查詢。
幾個問題
問題1:
var a = 20;
var b = a;
b = 30;
// 這時a的值是多少?
複製程式碼
問題2:
var a = { name: '前端開發' }
var b = a;
b.name = '進階';
// 這時a.name的值是多少
複製程式碼
問題3:
var a = { name: '前端開發' }
var b = a;
a = null;
// 這時b的值是多少
複製程式碼
現在來解答一下,三個問題的答案分別是20
、‘進階’
、{ name: '前端開發' }
- 對於問題1,a、b都是基本型別,它們的值是儲存在棧中的,a、b分別有各自獨立的棧空間,所以修改了b的值以後,a的值並不會發生變化。
- 對於問題2,a、b都是引用型別,棧記憶體中存放地址指向堆記憶體中的物件,引用型別的複製會為新的變數自動分配一個新的值儲存在變數物件中,但只是引用型別的一個地址指標而已,實際指向的是同一個物件,所以修改
b.name
的值後,相應的a.name
也就發生了改變。 - 對於問題3,首先要說明的是
null
是基本型別,a = null
之後只是把a儲存在棧記憶體中地址改變成了基本型別null,並不會影響堆記憶體中的物件,所以b的值不受影響。
記憶體空間管理
JavaScript的記憶體生命週期是
- 1、分配你所需要的記憶體
- 2、使用分配到的記憶體(讀、寫)
- 3、不需要時將其釋放、歸還
JavaScript有自動垃圾收集機制,最常用的是通過標記清除的演算法來找到哪些物件是不再繼續使用的,使用a = null
其實僅僅只是做了一個釋放引用的操作,讓 a 原本對應的值失去引用,脫離執行環境,這個值會在下一次垃圾收集器執行操作時被找到並釋放。
在區域性作用域中,當函式執行完畢,區域性變數也就沒有存在的必要了,因此垃圾收集器很容易做出判斷並回收。但是全域性變數什麼時候需要自動釋放記憶體空間則很難判斷,因此在開發中,需要儘量避免使用全域性變數。
思考題
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
a.x // 這時 a.x 的值是多少
b.x // 這時 b.x 的值是多少
複製程式碼
參考
進階系列目錄
- 【進階1期】 呼叫堆疊
- 【進階2期】 作用域閉包
- 【進階3期】 this全面解析
- 【進階4期】 深淺拷貝原理
- 【進階5期】 原型Prototype
- 【進階6期】 高階函式
- 【進階7期】 事件機制
- 【進階8期】 Event Loop原理
- 【進階9期】 Promise原理
- 【進階10期】Async/Await原理
- 【進階11期】防抖/節流原理
- 【進階12期】模組化詳解
- 【進階13期】ES6重難點
- 【進階14期】計算機網路概述
- 【進階15期】瀏覽器渲染原理
- 【進階16期】webpack配置
- 【進階17期】webpack原理
- 【進階18期】前端監控
- 【進階19期】跨域和安全
- 【進階20期】效能優化
- 【進階21期】VirtualDom原理
- 【進階22期】Diff演算法
- 【進階23期】MVVM雙向繫結
- 【進階24期】Vuex原理
- 【進階25期】Redux原理
- 【進階26期】路由原理
- 【進階27期】VueRouter原始碼解析
- 【進階28期】ReactRouter原始碼解析
交流
進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,覺得不錯點個star。
我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!