JavaScript 系列之閉包(一)

這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰markdown

1、閉包的定義

MDN 對閉包的定義爲:閉包

閉包是指那些可以訪問自由變量的函數app

那什麼是自由變量呢?異步

自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量。ide

由此,咱們能夠看出閉包共有兩部分組成:函數

閉包 = 函數 + 函數可以訪問的自由變量post

簡單來說:ui

閉包就是可以讀取其餘函數內部變量的函數lua

在函數 A 中還有函數 B,函數 B 調用了函數 A 中的變量,那麼函數 B 就稱爲函數 A 的閉包。url

function foo() {
  let a = 2;
  function bar() {
    console.log(a);
  }
  bar();
}
foo(); // 2
複製代碼

2、循環和閉包

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log("i: ", i);
  }, 1000);
}
console.log(i);

// 輸出
// 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
複製代碼

image.png

要理解上述代碼,首先咱們得了解一下概念:

  • JS 分爲同步任務異步任務
  • 同步任務都在主線程上執行,造成一個執行棧
  • 主線程以外,事件觸發線程管理着一個任務隊列,只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
  • 一旦執行棧中的全部同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧中,開始執行。

上述代碼中使用定時器,當 JS 引擎線程執行到該段代碼,便把定時器放到定時器線程去計時,此時 JS 引擎線程執行同步棧裏面的任務。當定時器計時完成以後,將回調函數推入消息隊列。等待棧中的代碼執行完畢以後會去讀取消息隊列中的事件。

因爲 JS 的函數做用域,當回調函數被推入消息隊列的時候沒有帶上參數。for 循環結束以後,由於 i 是用 var 定義的,因此 var 是全局變量(這裏沒有函數,若是有就是函數內部的變量),這個時候的 i 是 5。

image.png

如何解決?

// let 是塊級做用域,當前塊是循環體
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log('i: ',i);
  }, 1000);
}
console.log(i);

// 輸出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
複製代碼
//這是函數級做用域,利用閉包模擬塊級做用域
for (var i = 0; i < 5; i++) {
  (function(){
    var j = i;
    setTimeout(function() {
      console.log('i: ',j);
    }, 1000);
  })();
}
console.log(i);

// 輸出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
複製代碼
// 或者採起傳參的方式,使用了閉包
for (var i = 0; i < 5; i++) {
  (function(j){
    setTimeout(function() {
      console.log('i: ',j);
    }, 1000);
  })(i);
}
console.log(i);

// 輸出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
複製代碼
// setTimeout 的第三個參數
for (var i = 0; i < 5; i++) {
  setTimeout(function timer(j) {
    console.log('i: ', j);
  }, 1000, i);
}
console.log(i);

// 輸出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
複製代碼
相關文章
相關標籤/搜索