1. 程式人生 > >JavaScript設計模式基礎之閉包(終)

JavaScript設計模式基礎之閉包(終)

對於前端程式設計師來說閉包還是比較難以理解的,

閉包的形成與變數的作用域以及變數的生產週期密切相關,所以要先弄懂變數的作用域和生存週期。

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>

程式碼還是不難重在理解