對於前端程式員來說閉包還是比較難以理解的, 閉包的形成與變數的作用域以及變數的生產周期密切相關,所以要先弄懂變數的作用域和生存周期。 1.變數作用域 變數的作用域,就是指變數的有效範圍,通常我們指的作用域就是函數作用域(畢竟全局的作用域沒有要指的意義,關鍵哪都能訪問) 聲明變數的時候推薦使用es6語 ...
對於前端程式員來說閉包還是比較難以理解的,
閉包的形成與變數的作用域以及變數的生產周期密切相關,所以要先弄懂變數的作用域和生存周期。
1.變數作用域
變數的作用域,就是指變數的有效範圍,通常我們指的作用域就是函數作用域(畢竟全局的作用域沒有要指的意義,關鍵哪都能訪問)
聲明變數的時候推薦使用es6語法中的let 和const 可以避免var聲明變數出現的一些不必要的錯誤而且let聲明變數只作用於當前作用域 避免使用不帶var 或者let直接聲明變數,可能會導致命名衝突。
2.變數生存周期
除了變數作用域之外,另外一個跟閉包有關的概念就是變數生存周期。
對於全局變數來說,它的生存周期就是永久,除非我們主動銷毀它,而對於函數裡面聲明的變數來說 它的生存周期會隨著函數調用解釋而被銷毀。
閉包的定義: 最簡單直白的說法就是 函數返回函數
閉包的應用:封裝私有變數、延續局部變數的壽命
1.封裝私有變數:
使用閉包可以把一些不需要暴露在全局的變數封裝成“私有變數”
如有一個計算數組偶數乘積的方法:
let num = function(arr){ let a = 1; for(let i = 0; i < arr.length; i++){ if(arr[i] % 2 === 0){ a *= arr[i]; } } return a; } console.log( num([1,2,3,4]));//輸出8
加入緩存機制提高函數性能:
let cache = {}; let num = function(arr){ let args = Array.prototype.join.call(arr,',');//輸出1,2,3,4 console.log(cache[args])//第一次調用輸出為undefined進行下一步計算 第二次調用輸出8 直接返回 //傳入相同參數就比不必進行計算 直接返回緩存提高性能 if(cache[args]){ return cache[args]; } //不是相同參數則進行計算 let a = 1; for(let i = 0; i < arr.length; i++){ if(arr[i] % 2 === 0){ a *= arr[i]; } } return cache[args] = a; } console.log( num([1,2,3,4]));//8 進行計算 console.log( num([1,2,3,4]));//8 返回緩存
這明顯能看到cache這個緩存變數只在num函數裡面被使用,與其讓它們一起暴露在全局不然把它封裝在num函數內部,減少頁面中的全局變數,以免該變數在其他地方被修改而引發錯誤
封裝後代碼如下:
let num = (function(){ let cache = {}; return function(arr){ let args = Array.prototype.join.call(arr,',');//輸出1,2,3,4 console.log(cache[args])//第一次調用輸出為undefined進行下一步計算 第二次調用輸出8 直接返回 //傳入相同參數就比不必進行計算 直接返回緩存提高性能 //判斷cache緩存對象裡面有args這個key值沒 if(args in cache){ return cache[args]; } //不是相同參數則進行計算 let a = 1; for(let i = 0; i < arr.length; i++){ if(arr[i] % 2 === 0){ a *= arr[i]; } } return cache[args] = a; } })(); console.log( num([1,2,3,4]));//8 進行計算 console.log( num([1,2,3,4]));//8 返回緩存
2.延續局部變數壽命
src屬性會自動請求伺服器數據如下
let report = function(src){ let img = new Image(); img.src = src; console.log(img.src); } report(`https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537779456907&di=c72dd79d1dbb02bb9340743bf08e99f7&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F94cad1c8a786c91723e93522c43d70cf3ac757c6.jpg`);
但是一些低版本的瀏覽器實現存在著bug,在這些瀏覽器上面使用該函數會丟失數據 因為函數調用結束後變數銷毀 我們可以用閉包封閉起來就能解決低版本瀏覽器bug
代碼如下:
let report = (function(){ let imgs = []; return function(src){ let img = new Image(); imgs.push(img); img.src = src; } })() report(`https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537779456907&di=c72dd79d1dbb02bb9340743bf08e99f7&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F94cad1c8a786c91723e93522c43d70cf3ac757c6.jpg`);
接下來要來點乾貨了
用閉包實現命令模式:
在JavaScript中閉包的各種設計模式實現裡面,閉包的運用特別廣泛,在我後續的博客中將體會到這一點
簡單編寫一段閉包實現命令模式 如果上述的閉包使用你基本會了的話不會對我們的理解造成困難
代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <button id="start">打開電腦</button> <button id="end">關閉電腦</button> <script> //命令 let Computer = { open(){ alert('打開電腦'); }, close(){ alert('關閉電腦'); } } //創建命令執行中介 let createCommand = function(receiver){ //執行 let execute = function(){ return receiver.open(); } //關閉 let undo = function(){ return receiver.close(); } return { execute, undo } }; //設置執行命令者 let setCommand = function(command){ document.querySelector('#start').onclick = function(){ command.execute();//輸出打開電腦 } document.querySelector('#end').onclick = function(){ command.undo();//輸出關閉電腦 } } //傳入命令方法 傳入執行中介 setCommand(createCommand(Computer)); </script> </body> </html>
代碼還是不難重在理解