JavaScript高級之閉包詳解
1. 閉包的概念
來看一般函數的執行和啟發:
function stop() { var num = 0; console.log(num); } stop(); // 打印出來num是0 console.log(num); // 報錯 函數未定義
1. 此時,函數的外部無法訪問函數內部的變量
2. 函數內部定義的變量不會一直存在,隨著函數的運行結束而消失
閉包的概念:
1. 是一個函數,這個函數有權訪問另一個作用域中的變量。
2. 另一種說法,當內部函數的生命周期大於外部函數的聲明周期,而內部函數以某一種方式被外部作用域訪問時,閉包就產生瞭。
來看如下閉包的代碼和解釋:
function fn() { var num = 10; // function fun() { // console.log(num); // } // return fun; return function () { console.log(num); // 10 } } var f = fn(); f();
我們可以拆解為幾個部分:
1. fn函數裡面有內部的返回值且就是一個函數。
2. return的這個函數內部打印瞭num變量。為什麼能夠打印num變量,原因在於作用域鏈的訪問機制,下面會補充作用域和作用域鏈的知識點。
3. 我們在外部用f變量接受瞭fn(),也就是接受瞭fn的返回值【內部函數】
4. 緊接著調用f,也就是調用瞭fn裡面的內部函數。最終能夠打印10
知識點的補充:
1. 作用域:
變量在某個范圍內起作用,超出瞭這個范圍,就不起作用。這個范圍就是作用域。作用域在函數的定義時就產生,而不是函數調用時產生的。
2. 作用域鏈:
一句話概括:根據【內部函數可以訪問外部函數變量】,采用就近原則一層一層向上查找變量,這個機制就叫作作用域鏈。
函數A包含瞭函數B,那麼函數B就是函數A的內部函數,
而內部函數如果要使用一個變量,首先看自己內部有沒有這個變量,
如果沒有,就會去緊挨著的上一級查找,【就近原則】
如果函數一層一層都找不到,最後才會去全局變量下面找。
var a = 1; var b = 11; function fn1() { var a = 2; var b = '22'; fn2(); function fn2() { var a = 3; fn3(); function fn3() { var a = 4; console.log(a); // 4 console.log(b); // '22' } } } fn1();
3. 垃圾回收機制
可以參考這位大哥對於JS垃圾回收機制的描述:
//www.jb51.net/article/229425.htm
我們結合這三個概念看閉包的作用
2. 閉包的作用:
我們把函數A叫作外層的函數,這個函數內部有一個函數B。
外部用一個變量f接受函數A的返回值【函數B】
而函數A作用域的變量叫作num
1. 能夠在函數的外部訪問函數內部的變量【搭建外部訪問內部作用域的通道】
原理:上面其實有解釋過。
第一要理解,作用鏈的原理看上面。函數B能夠調用函數A的變量num
第二要理解,首先函數A的返回值是函數B【內部函數】,其次這個返回值要在函數外部用變量f接受,接受以後就能夠調用函數B,函數B就會訪問函數A的變量num。而這個內部函數B就是閉包函數啦。
2. 能夠延長函數內部變量的生命周期。
第一個作用帶來第二個作用。js的變量存在垃圾回收機制,如果函數執行完畢,變量會被清除,內存也會消除。可是如果利用閉包,變量可以不被立即清除。
原因是,外部的變量f接受瞭一個函數A的內部函數B,而這個內部函數訪問瞭函數A作用域的變量num,隻要函數B執行且變量f一直存在,那麼變量num就會一直存在。不會因為函數A的執行結束就消失。
參考瞭下面的文章,講的非常詳細,推薦看。
JavaScript閉包詳解
3. 閉包示例
後面會補充閉包的一些應用。
我們要想起什麼場合用閉包,閉包不能濫用。
3.1 點擊li,輸出當前li的索引號
<ul class="nav"> <li>榴蓮</li> <li>臭豆腐</li> <li>鯡魚罐頭</li> <li>大豬蹄子</li> </ul> <script> // 閉包應用-點擊li輸出當前li的索引號 // 1. 我們可以利用動態添加屬性的方式 var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { lis[i].onclick = function () { console.log(i); // 四個4 } } </script>
原理:上圖這樣寫,打印出來的i永遠都是4。原因是,此時首先是非嚴格模式,在非嚴格模式下,for循環是同步執行任務,而按鈕點擊再執行是異步任務,同步執行完畢,i加到瞭4.再執行異步任務打印i,都是4。
改法1:用閉包
1.for循環生成四個立即執行函數
2. 立即執行函數是閉包的一種應用。立即執行函數裡面的所有函數包括【點擊 回調】函數都可以使用立即執行函數的傳遞的形參。
for (var i = 0; i < lis.length; i++) { (function (i) { // console.log(i); lis[i].onclick = function () { console.log(i); } })(i); }
改法2:var—>let
點擊對應小li,打印i是對應索引號。使用let是ES6語法,此時for有塊級作用域
var lis = document.querySelector('.nav').querySelectorAll('li'); for (let i = 0; i < lis.length; i++) { lis[i].onclick = function () { // console.log(i); console.log(i); } }
改法3:用設置自定義屬性index的方法
var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { // 註意這裡是var不是let lis[i].index = i; // 註意這裡是lis[i]不是this.index,此時沒有點擊,哪裡來的this lis[i].onclick = function () { console.log(this.index); } }
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- JS中 querySelector 與 getElementById 方法區別
- JavaScript深入介紹WebAPI的用法
- Javascript事件的捕獲方式和冒泡方式詳解
- JavaScript DOM API的使用教程及綜合案例
- JavaScript中let避免閉包造成問題