1. 程式人生 > >js的基礎(平民理解的執行上下文/呼叫堆疊/記憶體棧/值型別/引用型別)

js的基礎(平民理解的執行上下文/呼叫堆疊/記憶體棧/值型別/引用型別)

  與以前的切圖比較,現在的前端開發對js的要求似乎越來越高,在開發中,我們不僅僅是要知道如何運用現有的框架(react/vue/ng),

而且我們對一些基礎的知識的依賴越來越大。

  現在我們就用平民的方法講解下執行上下文/呼叫堆疊/記憶體棧。

  理解下 javascript 在執行中,javascript 引擎(v8) 對我們載入的程式碼做了寫什麼?

  我們整一段非常簡單的 js 程式碼來分析 v8 引擎和執行上下文/呼叫堆疊/記憶體棧的關係。

<script>
  var a = 1;
  function say() {
    var c = 4
    console.log(c)   console.log(a)   var d = 5
  }
  console.log(b)  console.log(a)
  say()
  var b = 2;
</script>

  1、在 v8 引擎載入到這段程式碼

  2、引擎解析程式碼,建立一個 全域性執行上下文(呼叫堆疊/記憶體棧)

   3、解析程式碼,將全域性的所有的變數/宣告函式新增到全域性執行上下文的記憶體棧中。

  這裡會將 變數 a 和 b / 函式 say 新增到記憶體棧中。所有的全域性的變數和函式都會新增進去

  開始執行的樣子:

4.將全域性的執行方法,都 push 到呼叫堆疊中

  步驟 1 :

  呼叫 console.log(b) 方法,執行到 console.log(b) 這一步的時候,全域性執行上下文樣子:

  在從開始執行到這裡的過程中,我們對一些變數做了賦值 a = 1 , b 併為賦值,仍是 undefined 。

  這裡列印的是一個 undefined 

  這裡就是為什麼會出現 變數提升 的原因

  這裡 console.log(b) 執行完成後,會直接 pop 彈出 呼叫堆疊。

   步驟 2 :

   呼叫 console.log(a) , 在console.log(b) 到 console.log(a) 並未有任何賦值操作。

   所以記憶體棧中未有任何變化。

  但是呼叫堆疊裡面會有變化,在這裡面console.log(b)已經執行完成,會被 pop 推出棧,然後到了console.log(a),試圖樣子:

 

  這裡列印 a 是 1

  結束完成之後,console.log(a) 會被彈出棧。

  步驟 3:

  呼叫 say() 函式,將 say() push 的呼叫堆疊中。

  建立一個新的 say 的執行上下文。

  這裡為 say 重新建立了一個 執行上下文 。並且將它內部的 變數和函式推入它對應的 記憶體棧 中。

  將 c : 4 , d : 5 推到 記憶體棧中。

   步驟 4 :

  解析 say 函式,並且執行內部的 console.log(c) , console.log(a) 。

  

  這裡堆疊一次執行 console.log(c) , console.log(a) .

  執行完成之後會被彈出來。

  列印 4 , 5 。

  步驟 5 :

  當 say () 執行完畢後,回收記憶體棧已經呼叫棧。

  完成!!

  前端 javascript的 堆疊理解 

  在理解這些時候,我們可以先看下 值型別 和 引用型別,值型別 和 引用型別 的儲存方式來區分前端的堆疊。

  值型別/原始型別 : undefined 、null 、number 、string 、boolean

  引用型別/堆型別 : object 、function 、array 

  值型別 :

  儲存在記憶體棧中,分配固定的空間,所以儲存的值是不可變的。

  js 程式碼執行的或者某個方法執行的時候,都會有自己獨立的執行上下文,其中包括記憶體棧和呼叫棧,該執行上下文中定義的值型別就會儲存在該記憶體棧中。

  當該方法執行結束,這個記憶體棧就會銷燬,值型別也隨之移除

  值型別的特點:

  1、任何方法都無法改變值型別的

var name = 'xiao';
name.toUpperCase(); // 輸出 'XIAO'
console.log(name); // 輸出  'xiao'

  該字串呼叫了 toUpperCase() 方法,但是並未改變 name 的值

  2、相同型別的值型別的比較是它們值的比較

  相同型別的值型別在比較時候,只有它們的值相等,則它們才相等。

  3、值型別都存放在記憶體棧中

  上面已經介紹過了。

  4、儲存的是值本身的值,不是指向的地址。

  引用型別:

  值儲存在堆記憶體中,佔用的空間也不固定,所以儲存的值是可以變的

  當我們建立一個物件的時候,我們會將該物件儲存在執行的記憶體堆中,以便與反覆利用。

  當被某一個變數引用的時候,會給變數該記憶體堆中的該物件的地址,並且把該物件的地址儲存在記憶體棧中,而不是該物件的本身

  當引用該變數所在的方法或者是執行上下文執行結束的時候,並不會銷燬,只有在沒有被任何引用的時候,該物件才會被銷燬。

  這裡的這種銷燬方法就是垃圾回收中的一種,叫做標記回收

  標記回收( 以物件為例,當這個物件每被引用一次,就在該物件的標記上面 +1 ,當某一個引用該物件的變數不引用了就 -1 ,當

  標記為 0 的時候,被系統檢測到之後,就會銷燬。 )

  引用型別的特點:

  1、引用型別的值是可變的

var obj = {a:1}
obj.b = 2

  2、引用型別在記憶體棧中儲存物件地址,在堆記憶體中儲存物件的本身

  3、引用型別的比較(===),是比較它們儲存的物件的堆記憶體地址

  4、引用型別的賦值,當給某一個變數複製的時候,實際是將該物件的堆記憶體地址複製給變數

  總結 :

  前端的基礎(閉包,原型,作用域),在這裡我們只要理解了執行上下文,我們就可以很好的區分作用域和閉包,

  因為它們都相當於一個方法,當它們執行的時候,都會相應的建立自己的執行上下文,並且有自己的記憶體棧和呼叫棧。

  當它們執行的時候都會對應的運用自己的記憶體中的變數。

  值型別和引用型別的區分,我們能很快的作出型別的比較。

  通過變數的儲存方式來區分每一個變數的種類和功能。