小編已經有一段時間沒有更新文章了,最近一直在考慮接下來要更新什么內容。接下來,小編會圍繞以下三個方面更新文章。實際項目中遇到的問題和解決方案、Vue源碼解析、代碼重構、關於數據可視化。小編也會按照這個順序,逐步的去更新。期待着一起進步。
今天就先和大家一起聊一聊我理解的閉包。在聊這個問題之前,先了解一下變量的定義域。
在js中,變量定義域有全局作用域和局部作用域之說。es6中新出現的變量聲明關鍵字,就是為了解決部分變量作用域混亂引入的。全局作用域在這就不談了。主要說說函數的作用域。
一、作用域
簡單一點說,函數的作用域,就是函數的花括號內部,先看兩個例子,或許能對這個概念理解的更好一點
function f1(){ let n = 999 console.log(n) } f1() // 999 function f2(){ let n = 999 } alert(n); // 報錯
二、函數的返回值
要說閉包之前,我得先說一下函數返回值。關於函數的返回值,小編也是年初才有了一個更深層次的理解。沒有返回值的函數,執行之后會返回undefined,有返回值的函數,執行之后就變成了對應的返回值。就像這樣
// 沒有返回值的函數 function f1(){ alert(666) } console.log(f1()) // 出現彈窗之后,在控制台輸出undefind // 存在返回值 function f2(){ alert(666) return 'over' } console.log(f2()) // 出現彈窗之后,在控制台輸出over。當然,可以返回字符串,也可以返回Bealon,還可以返回函數。
三、函數嵌套
在《重構——改善既有代碼的設計》中,提出了js語法是允許函數內部嵌套函數的,但並不是所有的編程語言都可以的,所謂代碼嵌套,就是在函數內部又有函數聲明,就像這樣:
function outer(){ let name = 'lilei' function inner(){ console.log(name) } }
四、閉包
前面說明了在js中的局部變量作用域的問題,在實際項目中,就是需要在函數外部,訪問函數內部的變量,這個時候,按照局部變量作用域的問題。似乎是不可能的,閉包的出現,解決了這個問題。
function outer(){ let name = 'lilei' function inner(){ return name } return inner }
上面是一個典型的閉包函數,在使用這個閉包函數的時候,我們可以這樣:
let g = outer() console.log(g()) // lilei
至此,已經解決了在全局訪問函數內的局部變量。但是小編在回家的路上在想,為了實現這個功能,是不是不用這個麻煩,我通過這樣的函數,也是可以滿足需求的。
function outer(){ let name = 'lilei' return name } console.log(outer()) // lilei
確實上面的代碼和通過閉包最終在控制台輸出的內容是一樣的,那為什么還要引入閉包呢?小編也是想了接近一周才明白的,這就好比變量->函數->類,每層往上都是逐步提升的過程,通過函數可以實現更多的邏輯,比如對數據進行處理,僅僅靠變量是不能實現的。
五、閉包的實際應用
上面小編介紹了閉包,那么在實際項目中有什么應用呢?先看下面代碼:
1、隱藏內部變量名稱和函數執行暫停
function outer() { let name = 1 function inner() { return name ++ } return inner } let g = outer() console.log(g()) // 2 console.log(g()) // 3 console.log(g()) // 4 console.log(g()) // 5
2、setTimeout函數傳遞參數
默認的setTimeout是這樣的
小編也曾經這樣試驗過
function f1(p) { console.log(p) } setTimeout(f1(666),3000) // 並沒有延時,直接輸出666
要想通過延時來實現對函數傳遞參數,這時候,閉包的作用就顯現出來了。
function f1(a) { function f2() { console.log(a); } return f2; } var fun = f1(1); setTimeout(fun,1000); // 一秒之后打印出1
3、回調
定義行為,然后把它關聯到某個用戶事件上(點擊或者按鍵)。代碼通常會作為一個回調(事件觸發時調用的函數)綁定到事件。就像下面這塊代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>測試</title> </head> <body> <a href="#" id="size-12">12</a> <a href="#" id="size-20">20</a> <a href="#" id="size-30">30</a> <script type="text/javascript"> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(20); var size16 = changeSize(30); document.getElementById('size-12').onclick = size12; document.getElementById('size-20').onclick = size14; document.getElementById('size-30').onclick = size16; </script> </body> </html>
4、函數防抖
在事件被觸發n秒后再執行回調,如果在這n秒內又被觸發,則重新計時。
實現的關鍵就在於setTimeout
這個函數,由於還需要一個變量來保存計時,考慮維護全局純凈,可以借助閉包來實現。就像下面這樣:
/* * fn [function] 需要防抖的函數 * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助閉包 return function() { if(timer){ clearTimeout(timer) //進入該分支語句,說明當前正在一個計時過程中,並且又觸發了相同事件。所以要取消當前的計時,重新開始計時 timer = setTimeOut(fn,delay) }else{ timer = setTimeOut(fn,delay) // 進入該分支說明當前並沒有在計時,那么就開始一個計時 } } }
六、使用類實現類似閉包中隱藏內部變量功能
上面是一個關於閉包的實際應用,小編在晚上睡不着覺的時候,想起同樣的需求,是不是也可以通過類來實現呢?最后經過一頓折騰,答案是肯定的,就像這樣:
class Adder{ constructor(c){ this._c = c } increace(){ this._c ++ } decreace(){ this._c -- } get finalNum(){ return this._c } } let c = new Adder(1) c.increace() console.log(c.finalNum) // 2 c.increace() console.log(c.finalNum) // 3 c.increace() console.log(c.finalNum) // 4 c.decreace() console.log(c.finalNum) // 3
參考文章:https://www.cnblogs.com/gg-qq/p/11399152.html
https://www.cnblogs.com/pikachuworld/p/5325868.html
https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout