閉包:如大家所知,通俗講,可以訪問其他函數內部變量的函數
// 創建閉包最常見的方式函數作為返回值 function fn() { let name = "小帆"; return function() { console.log(name); }; } let log = fn(); log(); //打印“小帆” --外部函數訪問內部變量
下面來實現一個簡單的demo:計數器
var number = 0; function creat() { number = number + 1; console.log(number ); } creat(); //確實實現了需求
//但是如果需要第二個計數器呢? //確定要像下面這樣寫嗎? var num = 0; function creat_1() { num = num + 1; console.log(num ); } creat_1();
如果我們需要的更多計數器,上面的寫法就感覺代碼很冗余了,這個時候可以考慮一下閉包:
function createNum() { var number = 0; return function() { number = number + 1; console.log(number ); }; } var fun1 = createNum(); fun1(); //1 fun1(); //2 var fun2 = createNum(); fun2(); //1 fun2(); //2
有一種經典題目:for循環里的定時器引發的思考
下面這道題的結果是多少?
for (var i = 0; i < 4; i++) { setTimeout(function() { console.log(i); }, 300); }
依次打印0,1,2,3 ???,然而打印的全都是4
原因:JS 執行的時候首先會先執行主線程,異步相關的會存到異步隊列里,當主線程執行完畢開始執行異步隊列, 主線程執行完畢后,此時 i 的值為 4,說以在執行異步隊列的時候,打印出來的都是 4(這里需要大家對 event loop 有所了解(js 的事件循環機制))
要使其依次打印0,1,2,3,則可以考慮閉包:
//方法一: //這個是通過自執行函數返回一個函數,然后在調用返回的函數去獲取自執行函數內部的變量,此為閉包 for (var i = 0; i < 4; i++) { setTimeout( (function(i) { return function() { console.log(i); }; })(i), 300 ); } //方法二: // 大部分都認為方法一和方法二都是閉包,我認為方法一是閉包,而方法二是通過創建一個自執行函數,使變量存在這個自執行函數的作用域里 for (var i = 0; i < 4; i++) { (function(i) { setTimeout(function() { console.log(i); }, 300); })(i); }
再舉一個例子:獲取多個元素並添加點擊事件
var op = document.querySelectorAll("p"); for (var j = 0; j < op.length; j++) { op[j].onclick = function() { console.log(j); }; } //log出來的值是一樣的 // 解決辦法一: for (var j = 0; j < op.length; j++) { (function(j) { op[j].onclick = function() { alert(j); }; })(j); } // 解決辦法二: for (var j = 0; j < op.length; j++) { op[j].onclick = (function(j) { return function() { alert(j); }; })(j); }
既然這樣,那每次遇到這種問題的時候我是不是可以都這樣使用閉包?
最后說一下,閉包有它的好處,也有它的壞處
