1. 程式人生 > 其它 >再談JS閉包

再談JS閉包

閉包是外層函式作用域的引用

需求決定供給,閉包的出現是為了解決函式在定義作用域外執行時如何按照詞法作用域查詢變數的問題。

JS中的函式是第一公民,所以可以作為值被返回,從而在其他地方執行。當一個函式在定義作用域以外的地方執行時,如果按照正常的詞法作用域,那麼無法訪問到定義時所在的外層函式作用域,所以閉包實際上補足了這一點。

對於一個有閉包的函式來說,訪問順序變為「本地作用域-閉包-外層作用域」,這裡的閉包作為定義時外層函式作用域的引用,避免了作用域被垃圾回收,可以在任意位置進行訪問。

function a() {
  let b = 1;
  function c() {
    b += 1;
    console.log(b);
  }
  return c;
}
let d = a();
debugger;
d();

之所以一定是函式作用域,因為全域性作用域最後回收,塊作用域訪問不到,兩者不需要使用閉包來保留狀態,只有函式作用域在執行完成後立刻銷燬,所以需要閉包作為引用來保留狀態。

疑問:當我在巢狀函式的外層添加了一個塊作用域,閉包竟然沒有了

function a() {
  { // 新增塊
    let b = 1;
    function c() {
      b += 1;
      console.log(b);
    }
    return c;
  } // 新增塊
}
let d = a();
debugger;
d();

引用了自由變數就會建立閉包

閉包的建立取決於是否引用了外層函式作用域的變數(自由變數),和是否返回函式無關。

function a() {
  let b = 1;
  function c() {
    b += 1;
    console.log(b);
  }
  debugger;
  c();
}
a();

引用了誰的變數就建立誰的閉包

閉包不一定是父級作用域,也可以是更外面的函式作用域,取決於引用了誰的變數。

function a() {
  let a_1 = 'a_1';
  function b() {
    function c() {
      // 這裡引用了函式a的變數
      console.log(a_1);
    }
    debugger;
    c();
  }
  b();
}
a();

引用多個作用域就建立多個閉包

如果同時引用了多個外層作用域的變數,就會建立多個閉包。

function a() {
  let a_1 = 'a_1';
  function b() {
    let b_1 = 'b_1';
    function c() {
      console.log(a_1);
      console.log(b_1);
    }
    debugger;
    c();
  }
  b();
}
a();

總結

從上面的實驗結果看,閉包實際上是巢狀函式訪問詞法作用域的一種特定模式,不論你是否返回內部函式,只要引用了外部函式作用域的變數,就會建立閉包,或者說內部函式是通過閉包的方式來訪問外層變數。之所以會這樣,還是因為函式是第一公民,存在定義和執行作用域不一致的情況,如果沒有這一點,閉包也就不必存在了。