1. 程式人生 > >【JavaScript高階】8、函式高階(閉包)

【JavaScript高階】8、函式高階(閉包)

引入

需求: 點選某個按鈕, 提示"點選的是第n個按鈕"
此時傳統方法想要在函式內部使用函式外部變數時,操作十分麻煩,必須將其變為呼叫函式的屬性的屬性值,通過this.屬性使用,因此採用回撥函式內傳入,相當於內部函式應用了巢狀的外部函式的變數,實際上這就是閉包。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>00_引入</title>
</head>
<body>

<button>測試1</button>
<button>測試2</button>
<button>測試3</button>
<!--
需求: 點選某個按鈕, 提示"點選的是第n個按鈕"
-->
<script type="text/javascript">
  /*var btns = document.getElementsByTagName("button")
  for (var i = 0,length = btns.length; i < length; i++) {
    var btn = btns[i]
    btn.index = i
    btn.onclick = function () {
      alert('第'+(this.index+1)+'個按鈕')
    }
  }*/
  var btns = document.getElementsByTagName("button")
  for (var i = 0,length = btns.length; i < length; i++) {
    (function (j) {
      var btn = btns[j]
      btn.onclick = function () {
        alert('第'+(j+1)+'個按鈕')
      }
    })(i)
  }

</script>
</body>

</html>

 一、理解閉包

1. 如何產生閉包?
  * 當一個巢狀的內部(子)函式引用了巢狀的外部(父)函式的變數(函式)時, 就產生了閉包
2. 閉包到底是什麼?
  * 使用chrome除錯檢視
  * 理解一: 閉包是巢狀的內部函式(絕大部分人)
  * 理解二: 包含被引用變數(函式)的物件(極少數人)
  * 注意: 閉包存在於巢狀的內部函式中
3. 產生閉包的條件?
  * 函式巢狀
  * 內部函式引用了外部函式的資料(變數/函式)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>01_理解閉包</title>
</head>
<body>

<script type="text/javascript">
  function fn1() {
    var a = 3
    var b = '123'
    function fn2() {
      console.log(a)  // 3
    }
    fn2()
  }
  fn1()
</script>
</body>
</html>

 二、常見的閉包

1. 將函式作為另一個函式的返回值
2. 將函式作為實參傳遞給另一個函式呼叫
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_常見的閉包</title>
</head>
<body>
<script type="text/javascript">
  // 1. 將函式作為另一個函式的返回值
  function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f()  // 3
  f()  // 4
  f = fn1()  //只有每次呼叫外部函式才能產生新的閉包
  f()  // 3
  f()  // 4

  // 2. 將函式作為實參傳遞給另一個函式呼叫
  function showDelay(msg,time) {
    setTimeout(function () {
      alert(msg)
    },time)
  }
  showDelay("chenwei",1000)

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

 三、閉包的作用

1. 使用函式內部的變數在函式執行完後, 仍然存活在記憶體中(延長了區域性變數的生命週期)
2. 讓函式外部可以操作(讀寫)到函式內部的資料(變數/函式)

問題:
  1. 函式執行完後, 函式內部宣告的區域性變數是否還存在? 一般是不存在, 存在於閉包中的變數才可能存在(執行閉包的函式物件沒有成為垃圾物件)
  2. 在函式外部能直接訪問函式內部的區域性變數嗎? 不能,但我們可以通過閉包讓外部操作(讀寫)到函式內部的資料(變數/函式)

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>03_閉包的作用</title>

</head>
<body>
<script type="text/javascript">
  function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    function fn3() {
      a--
      console.log(a)
    }
    return fn3
  }
  var f = fn1()  // 此時函式內fn2、fn3函式變數都不存在了,此時a還存在是因為f函式引用變數指向閉包
  f()  // 1
  f()  // 0

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

四、閉包的生命週期

1. 產生: 在巢狀內部函式定義執行完時就產生了(不是在呼叫)
2. 死亡: 在巢狀的內部函式成為垃圾物件時
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>04_閉包的生命週期</title>

</head>
<body>
<script type="text/javascript">
  function fn1() {
    //此時閉包就已經產生了(函式提升, 內部函式物件已經建立了)
    var a = 2
    function fn2 () {
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f() // 3
  f() // 4
  f = null //閉包死亡(包含閉包的函式物件成為垃圾物件,a此時不存在了)
</script>
</body>
</html>

 五、閉包的應用—自定義js模組(方法1)

閉包的應用 : 定義JS模組
  * 具有特定功能的js檔案
  * 將所有的資料和功能都封裝在一個函式內部(私有的)
  * 只向外暴露一個包含n個方法的物件或函式
  * 模組的使用者, 只需要通過模組暴露的物件呼叫方法來實現對應的功能
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_閉包的應用_自定義JS模組</title>
</head>
<body>
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>
</body>
</html>
function myModule() {
  //私有資料
  var msg = "My name is Onedean"
  function doSomething() {
    console.log('doSomething() ' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing() ' + msg.toLowerCase())
  }
  return {
    doSomething:doSomething,
    doOtherthing:doOtherthing
  }
}

六、閉包的應用—自定義js模組(方法2) 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_閉包的應用_自定義JS模組2</title>
</head>
<body>
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>
</body>
</html>
(function myModule2(window) {  //此處和結尾加window引數也可以不加,加了是為了產品上線時打包壓縮window有可替換的字元
  var msg = 'My name is ChenWei'
  function doSomething() {
    console.log('doSomething() ' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing() ' + msg.toLowerCase())
  }
  window.myModule2 =  {  // 將myModule2匿名函式暴露為全域性的物件屬性
    doSomething:doSomething,
    doOtherthing:doOtherthing
  }
})(window)

 七、閉包的缺點及解決

1. 缺點
  * 函式執行完後, 函式內的區域性變數沒有釋放, 佔用記憶體時間會變長
  * 容易造成記憶體洩露
2. 解決
  * 能不用閉包就不用
  * 及時釋放

八、面試題1

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

<script type="text/javascript">
  //程式碼片段一
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;  // 沒有引用外部巢狀函式的變數,所以沒有閉包,同時這裡getNameFunc函式外的作用域為全域性window所以此處this為window
      };
    }
  };
  alert(object.getNameFunc()());  // The Window  //!!!坑:object.getNameFunc()()直接呼叫內部函式,此時的this是window全部物件

  //程式碼片段二
  var name2 = "The Window";
  var object2 = {
    name2 : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name2;  // 引用了外部巢狀函式的變數that,所以有閉包
      };
    }
  };
  alert(object2.getNameFunc()()); // My Object  //object.getNameFunc()呼叫的是物件getNameFunc方法的函式,此時的this=呼叫物件object2,內部閉包that=this

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

 九、面試題2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>07_面試題2</title>
</head>
<body>
<script type="text/javascript">
  function fun(n,o) {
    console.log(o)
    return {
      fun:function(m){
        return fun(m,n)
      }
    }
  }
  var a = fun(0)  // undefined
  a.fun(1) // 0  //產生了新的閉包,但是沒有引用變數接收,新的閉包又釋放了,始終用的是a中的閉包
  a.fun(2) // 0
  a.fun(3) // 0

  var b = fun(0).fun(1).fun(2).fun(3)//undefined 0 1 2

  var c = fun(0).fun(1)  // undefined 0
  c.fun(2)  // 1
  c.fun(3)// 1
</script>
</body>
</html>