外層函數嵌套內層函數, 內層函數使用外層函數的局部變量,把內層函數做爲外層函數的返回值。面試
function A() {
let a = 1
function B() {
console.log(a)
}
return B
}
複製代碼
用閉包解決遞歸問題閉包
function factorial(num) {
if(num<= 1) {
return 1;
} else {
return num * factorial(num-1)
}
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4) // 報錯 。
//最好是return num* arguments.callee(num-1),arguments.callee指向當前執行函數,可是在嚴格模式下不能使用該屬性也會報錯,因此藉助閉包來實現
// 使用閉包實現遞歸
function newFactorial = (function f(num){
if(num<1) {return 1}
else {
return num* f(num-1)
}
})
//這樣就沒有問題了,實際上起做用的是閉包函數f,而不是外面的函數newFactorial
複製代碼
用閉包模仿塊級做用域異步
例1:函數
for(var i=0; i<10; i++){
console.log(i)
}
alert(i) // 變量提高,彈出10
//爲了不i的提高能夠這樣作
(function () {
for(var i=0; i<10; i++){
console.log(i)
}
)()
alert(i) // undefined 由於i隨着閉包函數的退出,執行環境銷燬,變量回收
複製代碼
例2:性能
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i)
}, 1000);
})(i);
}
複製代碼
封裝私有變量學習
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
複製代碼
在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量x,而且,從外部代碼根本沒法訪問到變量x。換句話說,閉包就是攜帶狀態的函數,而且它的狀態能夠徹底對外隱藏起來。this
讀取函數內部變量spa
讓變量的值始終保持在內存中code
一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬,被垃圾回收機制回收。可是,在建立了一個閉包之後,這個函數的做用域就會一直保存到閉包不存在爲止。對象
function makeAdd(x) {
return function(y) {
return x + y;
};
}
var add1 = makeAdder(5);
var add2 = makeAdder(10);
console.log(add1(4)); // 9
console.log(add2(3)); // 13
// 釋放對閉包的引用
add5 = null;
add10 = null;
複製代碼
閉包只能取得包含函數中任何變量的最後一個值,這是由於閉包所保存的是整個變量對象,而不是某個特殊的變量。
function test(){
var arr = [];
for(var i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); // 連續打印 10 個 10
複製代碼
閉包中的this
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(obj.getName()()); // The Window
//將這一部分解:console.log( function(){return this.name;};() );
複製代碼
改變做用域
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(obj.getName()()); // The Window
//將這一部分解:console.log( function(){return this.name;};() );
複製代碼
閉包的缺點就是常駐內存會增大內存使用量,而且使用不當很容易形成內存泄露。
若是不是由於某些特殊任務而須要閉包,在沒有必要的狀況下,在其它函數中建立函數是不明智的,由於閉包對腳本性能具備負面影響,包括處理速度和內存消耗。
這些面試題很是有意思,堅持一下,來我們繼續往下看>_<
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//問:三行a,b,c的輸出分別是什麼?
//答案:
//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1
複製代碼
(1)先肯定這三個函數的關係
這段代碼中出現了三個fun函數,因此第一步先搞清楚,這三個fun函數的關係,哪一個函數與哪一個函數是相同的。
function fun(n,o) {
console.log(o)
return {
fun:function(m){
//...
}
};
}
複製代碼
先看第一個fun函數,屬於標準具名函數聲明,是新建立的函數,他的返回值是一個對象字面量表達式,屬於一個新的object。這個新的對象內部包含一個也叫fun的屬性,經過上述介紹可得知,屬於匿名函數表達式,即fun這個屬性中存放的是一個新建立匿名函數表達式。
注意:全部聲明的匿名函數都是一個新函數。
因此第一個fun函數與第二個fun函數不相同,均爲新建立的函數。最內層的return出去的fun函數不是第二層fun函數,是最外層的fun函數。因此,三個fun函數的關係也理清楚了,第一個等於第三個,他們都不等於第二個。
(2)函數是怎樣調用的
爲了方便看把代碼從新寫一下
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//問:三行a,b,c的輸出分別是什麼?
複製代碼
第一行 a
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
複製代碼
第一個fun(0)是在調用第一層fun函數。第二個fun(1)是在調用前一個fun的返回值的fun函數,因此:第後面幾個fun(1),fun(2),fun(3),函數都是在調用第二層fun函數。
遂:
在第一次調用fun(0)時,o爲undefined;
第二次調用fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;
第三次調用fun(2)時m爲2,但依然是調用a.fun,因此仍是閉包了第一次調用時的n,因此內部調用第一層的fun(2,0);因此o爲0。
第四次同理;
即:最終答案爲 undefined,0,0,0
第二行 b
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
複製代碼
先從fun(0)開始看,確定是調用的第一層fun函數;而他的返回值是一個對象,因此第二個fun(1)調用的是第二層fun函數,後面幾個也是調用的第二層fun函數。
遂:
在第一次調用第一層fun(0)時,o爲undefined;
第二次調用 .fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;
第三次調用 .fun(2)時m爲2,此時當前的fun函數不是第一次執行的返回對象,而是第二次執行的返回對象。而在第二次執行第一層fun函數時時(1,0)因此n=1,o=0,返回時閉包了第二次的n,遂在第三次調用第三層fun函數時m=2,n=1,即調用第一層fun函數fun(2,1),因此o爲1;
第四次調用 .fun(3)時m爲3,閉包了第三次調用的n,同理,最終調用第一層fun函數爲fun(3,2);因此o爲2;
即最終答案:undefined,0,1,2
第三行 c
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
複製代碼
根據前面兩個例子,能夠得知:
fun(0)爲執行第一層fun函數,.fun(1)執行的是fun(0)返回的第二層fun函數,這裏語句結束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,因此c中閉包的也是fun(1)第二次執行的n的值。c.fun(2)執行的是fun(1)返回的第二層fun函數,c.fun(3)執行的也是fun(1)返回的第二層fun函數。
遂:
在第一次調用第一層fun(0)時,o爲undefined;
第二次調用 .fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;
第三次調用 .fun(2)時m爲2,此時fun閉包的是第二次調用的n=1,即m=2,n=1,並在內部調用第一層fun函數fun(2,1);因此o爲1;
第四次.fun(3)時同理,但依然是調用的第二次的返回值,遂最終調用第一層fun函數fun(3,1),因此o還爲1
即最終答案:undefined,0,1,1
循環中使用閉包解決 var 定義函數的問題
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
複製代碼
首先由於 setTimeout 是個異步函數,全部會先把循環所有執行完畢,這時候 i就是 6 了,因此會輸出一堆 6。
解決辦法兩種,第一種使用閉包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
複製代碼
第二種就是使用 setTimeout 的第三個參數
for ( var i=1; i<=5; i++) {
setTimeout( function timer(j) {
console.log( j );
}, i*1000, i);
}
複製代碼
第三種就是使用 let 定義 i 了
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
複製代碼
由於對於 let 來講,他會建立一個塊級做用域,至關於
{ // 造成塊級做用域
let i = 0
{
let ii = i
setTimeout( function timer() {
console.log( ii );
}, i*1000 );
}
i++
{
let ii = i
}
i++
{
let ii = i
}
...
}
複製代碼
若是您有更好的建議,或者對該篇文章的知識補充,請留言,實踐後會及時補充的哦,謝謝>-<!
該篇文章多方參考,學習筆記。