深入理解javascript系列(十六):深入高階函式

Panthon發表於2018-06-19

由於這兩天,廣州-東莞-惠州三日遊,所以更新速度有所放慢...

前面我們說過,簡單點理解高階函式,則凡是接收一個函式作為引數的函式,就是高階函式...

大神說,高階函式是一個高度封裝的過程,理解它需要一點想象力。所以本次就藉助幾個例子,來理解高階函式的封裝。

1.  陣列map方法封裝的思考過程

我們知道陣列有一個map(對映)方法,它對陣列中的每一項執行給定的函式,返回每次函式呼叫的結果並組成陣列。簡單來說,就是遍歷陣列的每一項,並且在map的第一個引數中進行運算處理後返回計算結果,最終返回一個由所有計算結果組成的新陣列。

//宣告一個遍歷的資料array
var array = [1,2,3,4];

//map方法第一個引數為一個回撥函式,該函式擁有三個引數
//第一個參數列示array陣列中的每一項
//第二個參數列示當前遍歷的索引值
//第三個參數列示陣列本身
//該函式中的this指向map方法第二個引數,若該引數不存在,則this指向丟失

var newArray = array.map(function(item, i, array) {
    console.log(item, i, array, this) //這個this是什麼?
    return item + 1;
}, {a: 1})

//newArray為一個新陣列,有map遍歷的結果組成
console.log(newArray);複製程式碼

在上面的例子中,我們詳細分析了map的所有細節。現在需要思考的是,如果要我們自己來封裝這樣一個方法,應該怎麼辦?

因為所有的陣列遍歷方法,其實都是在for迴圈基礎之上封裝的,因此我們可以從for迴圈開始考慮。

當然,一個for迴圈的過程其實很好封裝,其難點在於,for迴圈裡面對陣列每一項所做的事情很難用一個固定的模式把他封裝起來,在不同的場景下,for迴圈對資料的處理肯定是不一樣的。那麼應該怎麼辦呢?

在封裝函式時,對於一個不確定的變數,我們可以用往函式中傳入引數的方式來指定。如:

function add(a) {
    return a + 10;
}複製程式碼

同樣的道理,對於一個不確定的處理過程,我們可以用往函式中傳入另外一個函式的方式來自定義這個處理過程。因此,基於這個思路,我們可以按照如下方式來封裝map方法。

Array.prototype._map = function(fn, context) {
    //首先定義一個陣列來儲存每一項的運算結果,最後返回
    var temp = [];
    if(typeof fn == 'function') {
        var k = 0;
        var len = this.length;
        //封裝for迴圈過程
        for(; k<len; k++) {
            //將每一項的運算操作丟進fn裡
            //利用call方法指定fn的this指向與具體引數
            temp.push(fn.call(context, this[k], k, this))
        }
    }else {
        console.error('TypeError: '+ fn +' is not a function.');
    }

    //返回每一項運算結果組成的新陣列
    return temp;
}
 
var newArr = [1,2,3,4]._map(function(item) {
    return item + 1;
})複製程式碼

回過頭反思map方法的封裝過程可以發現,其實我們封裝的是一個陣列的for迴圈過程。每一個陣列在使用for迴圈遍歷時,雖然無法確認在for迴圈中到底發生了什麼,但是可以確定的是,它們一定會使用for迴圈。

因此我們把“都會使用for迴圈”這個公共邏輯封裝起來,而具體要做什麼事,則以一個函式作為引數的形式,來讓使用者自定義。這個被作為引數傳入的函式,就可以稱之為基礎函式。而我們封裝的map方法,就可以稱之為高階函式。

高階函式的使用思路正在於此,它其實是一個封裝公共邏輯的過程。

假設我們正在做一個音樂社群的專案。

很顯然,在進入這個專案的每一個頁面時,都必須判斷當前使用者是否已經登入。因為登入與未登入所展示的頁面肯定是有很多差別的。不僅如此,在確認使用者登入之後,還需得到使用者的具體資訊,如暱稱、姓名、VIP等級、許可權範圍等。

因此使用者狀態的判斷邏輯,是每個頁面都必須要做的一個公共邏輯,那麼在學習了高階函式之後,我們就可以用高階函式來做這件事。

為了強化模組化思維,我們繼續使用模組化的方式來完成這個例子。在這裡,我們可以利用自執行函式來劃分模組。

首先需要一個高階函式來專門處理獲取使用者狀態的邏輯,因此可以單獨將這個高階函式封裝為一個獨立的模組。

//高階函式withLogin,用來判斷當前使用者狀態
(function() {
    
    //用隨機數的方式來模擬一個獲取使用者資訊的方法
    var getLogin = function() {
        var a = parseInt(Math.random() * 10).toFixed(0));
        if(a % 2 == 0) {
            return {login: false}
        }
        
        return {
            login: true,
            userinfo: {
                nickname: 'pan',
                vip: 1,
                userid: 'music1111'
            }
        }
    }
    
    var withLogin = function(basicFn) {
        var loginInfo = getLogin();
        
        //將loginInfo以引數的形式傳入基礎函式中
        return basicFn.bind(null, loginInfo);
    }

    window.withLogin = withLogin;
})();複製程式碼

假設我們要展示主頁,則可以通過renderIndex的方法來渲染。當然,渲染主頁仍然是一個單獨的模組。

(function() {
    var withLogin = window.withLogin;

    var renderIndex = function(loginInfo) {
        //這裡處理index頁面的邏輯

        if(loginInfo.login) {
            //處理已經登入之後的邏輯
        }else {
            //這裡處理未登入的邏輯
        }
    }
    
    //對外暴露介面時,使用高階函式包一層,來判斷當前頁面的登入狀態
    window.renderIndex = withLogin(renderIndex);
})();複製程式碼

同樣的道理,當我們想要展示其它頁面,例如個人主頁時,則可以使用renderPersonal方法,如下所示。

(function() {
    var withLogin = window.withLogin;
    var renderPersonal = function(loginInfo) {
        if(loginInfo.login) {
            //do something
        }else {
           // do other something
        }
    }
    
    window.renderPersonal = withLogin(renderPersonal);
})();複製程式碼

當我們使用高階函式封裝每個頁面的公共邏輯之後,會發現我們的程式碼邏輯變得非常清晰,而且更加統一。當再寫新的頁面邏輯時,就在此基礎之上完成即可,而不用再去考慮已經封裝過的邏輯。

最後,在合適的時機使用這些渲染函式即可。

(function() {
    window.renderIndex();
})();複製程式碼

這些都是我以往的學習筆記。如果您看到此筆記,希望您能指出我的錯誤。有這麼一個群,裡面的小夥伴互相監督,堅持每天輸出自己的學習心得,不輸出就出局。希望您能加入,我們一起終身學習。歡迎新增我的個人微訊號:Pan1005919589


相關文章