javascript 閉包
閉包
一、閉包是什麼?
將一個詞法作用域 中的內部函式 作為一個一級值型別 到處傳遞,就形成了閉包。
怎麼去理解呢?這裡要敲黑板劃重點了,上面的概念性文字介紹了三個點:
- 詞法作用域(函式)
- 內部函式
- 一級值型別傳遞
1、先說詞法作用域
形成一個作用域最常見的就是函數了,函式內部會形成一個內部作用域,然後還有 let 、const 以及像 try/catch 結構中的 catch 分句形成的塊作用域。
let 就是為其宣告的變數隱式劫持了所在的塊作用域,這個在後面講 let 和閉包的時候會詳細說明 let 和閉包結合的用法。
通過了解可以知道,這裡的作用域其實就是函式的內部作用域。
2、內部函式
內部函式不用介紹了吧,在詞法作用域中定義的函式,傳遞後具有涵蓋自身所在作用域的閉包。
3、一級值型別傳遞
值型別傳遞方式有很多種啊,函式裡面的一級值傳遞無非就是:返回值 (return )、賦值 ( 賦值給外部變數 )、引數傳遞 ( 作為引數傳遞給外部函式 )。
現在可以畫一個基本的閉包出來了:
//三種傳遞方法①②③分開看,你可以的。 var fn;//定義全域性變數,用於內部賦值---② function foo() { var a = 2; function bar() { console.log(a); }; return bar;//返回值---① fn = bar;//賦值---② baz(bar);//引數傳遞---③ }; //定義外部函式,用於使用內部分配給全域性變數的函式---② function cat() { fn(); }; //定義外部函式,用於內部引數傳遞---③ function baz(func) { func(); }; foo();//2---① cat();//2---② baz();//2---③
再來一例:
function wait(message) { setTimeout(function timer(){ console.log(message); },1000); } wait("Hi Baby");
解析一下,按照我們前面的思路可以貫穿下來:
首先 wait(..) 裡面的作用域,作用域內部的 timer(..)函式,再將內部函式 timer(..) 傳遞給內建工具函式 setTimeout(..),setTimeout(...)有引數引用( 也就是我們傳遞的 timer(...) ),然後呼叫它。
整個過程行雲流水,然後詞法作用域在這個過程中保持完璧之身。OK!
二、迴圈中的閉包
說到迴圈閉包就要掏出大家耳熟能詳的栗子了。
for(var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); },i*1000) } 666!好!輸出了幾個6,老鐵有點懵逼,不知應該扎心還是雙擊666。
為何?
你大爺還是你大爺,即使你在每次迭代都定義了函式,但是都在共享全域性作用域中,i 還是這個 i
那要怎麼解決?
這時候在每個迭代的時候加上一個閉包作用域,並且你得把這個 i 大爺放進作用域中。
//放法可以是傳參①,可以是賦值② for(var i = 1; i <= 5; i++) { //這裡先搞一個閉包作用域,派出我們的 IIFE (function(j) {// ---① setTimeout(function timer() { console.log(j); },j*1000) })(i);// ---① (function() { var j = i;// ---② setTimeout(function timer() { console.log(j); },j*1000) })(); }
上面這個是用了閉包作用域,每次迭代都生成一個新的作用域,來封閉內部變數。
說到這裡,前面提到的 let 應該還有人記得,let 幹嘛用的,不就是劫持變數形成塊作用域嗎? 放在這裡不是恰到好處? 來一發。
for(let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) },i*1000) }
直接在定義 i 大爺的地方就 "綁架" 了他。
或者,你也可以麻煩一點,先讓他上迭代車,上車之後再 let 定義一個變數把 i 大爺賦給他,兩種都行,簡單點好。
三、總結一下閉包應用
定時器、事件監聽器、Ajax請求、跨視窗通訊、Web Workers 或者其他的非同步(或同步)任務中(balabala~~~~),只要使用了回撥函式,就是在使用閉包。
還有一處重要的模組 。
模組的兩個重要特徵:
- 有外部包裝函式(建立內部作用域)且需要被呼叫。
- 外部包裝函式返回值至少引用一個內部函式(建立包裝函式內部作用域閉包)。