1. 程式人生 > >【JavaScript高階】6、函式高階(執行上下文與執行上下文棧)

【JavaScript高階】6、函式高階(執行上下文與執行上下文棧)

一、變數提升與函式提升

1. 變數宣告提升
  * 通過var定義(宣告)的變數, 在定義語句之前就可以訪問到
  * 值: undefined
2. 函式宣告提升
  * 通過function宣告的函式, 在之前就可以直接呼叫
  * 值: 函式定義(物件)
3. 問題: 變數提升和函式提升是如何產生的?
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>01_變數提升與函式提升</title>
</head>
<body>
<script type="text/javascript">
  console.log('-----')
  /*
  面試題 : 輸出 undefined
   */
  var a = 3
  function fn () {
    console.log(a)  // undefined
    var a = 4
  }
  fn()

  console.log(b) //undefined  變數提升
  fn2() //可呼叫  函式提升
  // fn3() //不能  變數提升

  var b = 3
  function fn2() {
    console.log('fn2()') // fn2()
  }

  var fn3 = function () {
    console.log('fn3()')
  }
</script>
</body>
</html>

二、執行上下文

1. 程式碼分類(位置)
  * 全域性程式碼
  * 函式(區域性)程式碼
2. 全域性執行上下文
  * 在執行全域性程式碼前將window確定為全域性執行上下文
  * 對全域性資料進行預處理
    * var定義的全域性變數==>undefined, 新增為window的屬性
    * function宣告的全域性函式==>賦值(fun), 新增為window的方法
    * this==>賦值(window)
  * 開始執行全域性程式碼
3. 函式執行上下文
  * 在呼叫函式, 準備執行函式體之前, 建立對應的函式執行上下文物件(虛擬的, 存在於棧中)
  * 對區域性資料進行預處理
    * 形參變數==>賦值(實參)==>新增為執行上下文的屬性
    * arguments==>賦值(實參列表), 新增為執行上下文的屬性
    * var定義的區域性變數==>undefined, 新增為執行上下文的屬性
    * function宣告的函式 ==>賦值(fun), 新增為執行上下文的方法
    * this==>賦值(呼叫函式的物件)
  * 開始執行函式體程式碼
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_執行上下文</title>
</head>
<body>
<script type="text/javascript">
  //全域性執行上下文
  console.log(a1,window.a1)  // undefined undefined
  window.a2()  // 'a2()'
  console.log(this)  // window
  var a1 = 10
  function a2() {
   console.log('a2()')
  }
  console.log(a1)  // 10

  console.log('----------------------')
  //函式執行上下文
  function fn(a1) {
    console.log(a1)  // 2
    console.log(a2)  // undefined
    a3()  // 'a3()'
    console.log(this)  // window
    console.log(arguments)  // 2 3
    var a2 = 3
    function a3() {
      console.log('a3()')
    }
  }
  fn(2,3)

</script>
</body>
</html>

 三、執行上下文棧

1. 在全域性程式碼執行前, JS引擎就會建立一個棧來儲存管理所有的執行上下文物件
2. 在全域性執行上下文(window)確定後, 將其新增到棧中(壓棧)
3. 在函式執行上下文建立後, 將其新增到棧中(壓棧)
4. 在當前函式執行完後,將棧頂的物件移除(出棧)
5. 當所有的程式碼執行完後, 棧中只剩下window
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>03_執行上下文棧</title>
</head>
<body>
<script type="text/javascript">
  var a = 10
  var bar = function (x) {
    var b = 5
    foo(x + b)
  }
  var foo = function (y) {
    var c = 5
    console.log(a + c + y)  // 10+5+15=30
  }
  bar(10)
</script>

</body>
</html>

四、執行上下文棧2

1. 依次輸出什麼?
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
2. 整個過程中產生了幾個執行上下文?  5
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>04_執行上下文棧2</title>
</head>
<body>

<script type="text/javascript">
  console.log('gb: '+ i)  // gb:undefined
  var i = 1
  foo(1)  // fb:1 fb:2 fb:3 fe:3 fe:2 fe:1 ge:1 全域性入棧一次,呼叫函式入棧四次,總共五次執行上下文
  function foo(i) {
    if (i == 4) {
      return
    }
    console.log('fb:' + i)
    foo(i + 1) //遞迴呼叫: 在函式內部呼叫自己
    console.log('fe:' + i)
  }
  console.log('ge: ' + i)
</script>
</body>
</html>

 五、面試題

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_面試題</title>
  <style>

  </style>
</head>
<body>
<div style=""></div>
<script type="text/javascript">

  /*
   測試題1:  先執行變數提升, 再執行函式提升
   */
  function a() {}
  var a
  console.log(typeof a) // 'function'


  /*
   測試題2:
   */
  if (!(b in window)) {  // 全域性b變數提升存在,所以執行不了if內語句
    var b = 1
  }
  console.log(b) // undefined

  /*
   測試題3:
   */
  var c = 1
  function c(c) {
    console.log(c)
    var c = 3
  }
  //c(2) // c(2)會報錯,前面變數提升完,這裡c賦值為1,所以c不是函式無法呼叫

</script>
</body>
</html>