嵌套函數和閉包


你可以在一個函數里面嵌套另外一個函數。嵌套(內部)函數對其容器(外部)函數是私有的。它自身也形成了一個閉包。一個閉包是一個可以自己擁有獨立的環境與變量的的表達式(通常是函數)。

既然嵌套函數是一個閉包,就意味着一個嵌套函數可以”繼承“容器函數的參數和變量。換句話說,內部函數包含外部函數的作用域。

可以總結如下:

  • 內部函數只可以在外部函數中訪問。
  • 內部函數形成了一個閉包:它可以訪問外部函數的參數和變量,但是外部函數卻不能使用它的參數和變量。

下面的例子展示了嵌套函數:

function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41

由於內部函數形成了閉包,因此你可以調用外部函數並為外部函數和內部函數指定參數:

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8

保存變量

注意到上例中 inside 被返回時 x 是怎么被保留下來的。一個閉包必須保存它可見作用域中所有參數和變量。因為每一次調用傳入的參數都可能不同,每一次對外部函數的調用實際上重新創建了一遍這個閉包。只有當返回的 inside 沒有再被引用時,內存才會被釋放。

這與在其他對象中存儲引用沒什么不同,但是通常不太明顯,因為並不能直接設置引用,也不能檢查它們。

多層嵌套函數

函數可以被多層嵌套。例如,函數A可以包含函數B,函數B可以再包含函數C。B和C都形成了閉包,所以B可以訪問A,C可以訪問B和A。因此,閉包可以包含多個作用域;他們遞歸式的包含了所有包含它的函數作用域。這個稱之為作用域鏈。(稍后會詳細解釋)

思考一下下面的例子:

function A(x) {
  function B(y) {
    function C(z) {
      console.log(x + y + z);
    }
    C(3);
  }
  B(2);
}
A(1); // logs 6 (1 + 2 + 3)

在這個例子里面,C可以訪問B的y和A的x。這是因為:

  1. B形成了一個包含A的閉包,B可以訪問A的參數和變量
  2. C形成了一個包含B的閉包
  3. B包含A,所以C也包含A,C可以訪問B和A的參數和變量。換言之,C用這個順序鏈接了B和A的作用域

反過來卻不是這樣。A不能訪問C,因為A看不到B中的參數和變量,C是B中的一個變量,所以C是B私有的。

命名沖突

當同一個閉包作用域下兩個參數或者變量同名時,就會產生命名沖突。更近的作用域有更高的優先權,所以最近的優先級最高,最遠的優先級最低。這就是作用域鏈。鏈的第一個元素就是最里面的作用域,最后一個元素便是最外層的作用域。

看以下的例子:

function outside() {
  var x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

outside()(10); // returns 20 instead of 10

命名沖突發生在return x上,inside的參數xoutside變量x發生了沖突。這里的作用鏈域是{insideoutside, 全局對象}。因此insidex具有最高優先權,返回了20(insidex)而不是10(outsidex)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM