先來看一個函數:
function f(){ var b = "b"; return function(){ return b; } }
這個函數含有一個局部變量b,它在全局空間裡面是不可見的。而f()的返回值是另一個匿名函數,該函數有自己的私有的空間,可以訪問f()的空間和全局空間,所以b對它來說是可見的。
var n = f(); n();//此時控制台輸出"b"
因為f()是一個全局函數,它可以在全局空間被調用,當然也可以將其返回值賦值給另一個全局變量n,從而生成一個可以訪問f()私有空間的新的全局函數。
再來看看下面這個跟上面差不多的函數,在這裡f()不再返回函數了,而是直接在函數體內創建一個新的全局函數
var n; function f(){ var b = "b"; n = function(){ return b; } } f(); n();//調用f(),然後再調用n(),控制台輸出"b"
由於n()沒有使用var語句,因此它是全局的,同時它也可以訪問f()的作用域,所以哪怕n()最後升級為全局函數,但它依然可以保留對f()作用域的訪問權。
函數通常會讓自身的參數視為局部變量,所以我們創建返回函數時,也可以令其返回父級函數的參數,例如:
function f(arg){ var n = function(){ return arg; }; arg++; return n; } var m = f(123); m();//調用函數,輸出124
這種閉包所導致的bug往往很難被發現,因為它們表面上看起來很正常,來看一下下面的函數
function f(){ var a = []; var i; for (i = 0; i < 3; i++){ a[i] = function(){ return i; } } return a; } //下面來運行一下函數,並將結果賦值給數組a var a = f(); a[0]();//輸出3 a[1]();//輸出3 a[2]();//輸出3
為啥不是0、1、2呢?為啥會這樣呢?原來在這裡創建的三個閉包,它們都指向了一個共同的局部變量i,但是,閉包不會記錄它們的值,它們所擁有的的只是一個i的連接(即引用),因此只能返回i當前值,因為i結束循環時值為3,所以這三個函數都指向一個共同值3
如何糾正?顯然,需要a[i]指向三個不同的變量,下面是解決方案之一:
function f(){ var a = []; var i; for (i = 0; i < 3; i++){ a[i] = (function(x){ return function(){ return x; } })(i); } return a; } //下面來運行一下函數,並將結果賦值給數組a var a = f(); a[0]();//輸出0 a[1]();//輸出1 a[2]();//輸出2
這裡使用了自調函數,不再直接返回i的值,而是將i傳遞給自調函數,i賦值給了局部變量x,這樣一來,每次迭代x就會擁有各自不同的值了。
解決方案二:
function f(){ function aa(x){ return function(){ return x; } } var a = []; var i; for (i = 0; i < 3; i++){ a[i] = aa(i); } return a; }
方案二不使用自調函數,而是定義了一個內部函數實現相同的功能,每次迭代操作中,將i的值“本地化”。
這個例子是通過一個匿名自調函數來實現的,定義的全局函數setValue()和getValue(),確保局部變量temp的不可直接訪問性。
下面是一個接受數組輸入的初始化函數,其中定義了一個私有指針,該指針會指向數組中的下一個元素。
function setup(x){ var i = 0; return function() { return x[i++]; }; } //調用setup(),創建我們所需的next()函數 var next = setup(['a', 'b', 'c']); //重復調用next(),就可以不停的獲取下一個元素 next();//輸出"a" next();//輸出"b" next();//輸出"c"