天天看點

如何實作JavaScript的Map和Filter函數?

譯者按: 魯迅曾經說過,學習JavaScript最好方式莫過于敲代碼了!

原文:

Master Map & Filter, Javascript’s Most Powerful Array Functions 譯者: Fundebug

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用于學習。

這篇文章面向那些已經熟練使用for循環,但對Array.map和Array.filter并沒有特别了解的開發者。本文将會手把手去實作這兩個函數,來深入了解它們的工作原理。

Array.map

Array.map通過對輸入的數組中每一個元素進行變換,傳回由變換後的元素按序組成的新數組。原始數組的值不會被修改。假設我們相對一個數組中的每一個元素乘以3,使用for循環可以這樣寫。

for循環

var originalArr = [1, 2, 3, 4, 5];
var newArr = [];
for(var i = 0; i < originalArr.length; i++) {
    newArr[i] = originalArr[i] * 3;
}
console.log(newArr); // -> [3, 6, 9, 12, 15]
           

接下來我們将這個for循環抽象成一個函數。

multiplyByThree函數

var originalArr = [1, 2, 3, 4, 5];
function multiplyByThree(arr) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = arr[i] * 3;
    }
    return newArr;
}
var arrTransformed = multiplyByThree(originalArr);
console.log(arrTransformed); // -> [3, 6, 9, 12, 15]
           

現在我們繼續深化這個抽象思路,将multiplyByThree中對每一個元素乘以3部分抽象為一個新的函數。

var originalArr = [1, 2, 3, 4, 5];
function timesThree(item) {
    return item * 3;
}
function multiplyByThree(arr) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = timesThree(arr[i]);
    }
    return newArr;
}
var arrTransformed = multiplyByThree(originalArr);
console.log(arrTransformed); // -> [3, 6, 9, 12, 15]
           

這樣有什麼好處呢?設想如果我們想對每一個元素乘以5,或則10,我們還要把整個for循環寫一遍嗎!

如果我們對timesThree函數稍作修改,就可以輕松的複用很多代碼。

multiply函數

我們将:

function multiplyByThree(arr) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = timesThree(arr[i]);
    }
    return newArr;
}
           

重構為:

function multiply(arr, multiplyFunction) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = multiplyFunction(arr[i]);
    }
    return newArr;
}
           

我們将multiplyByThree重命名為multiply,并增加了一個參數。該參數是一個函數,定義了數組元素的變換規則。通過定義一個timesThree函數來達到實作對每一個數組元素乘以3的目的。

var originalArr = [1, 2, 3, 4, 5];
function timesThree(item) {
    return item * 3;
}
var arrTimesThree = multiply(originalArr, timesThree);
console.log(arrTimesThree); // -> [3, 6, 9, 12, 15]
           

有何優點呢?我們可以很簡單定義任何變換:

var originalArr = [1, 2, 3, 4, 5];
function timesFive(item) {
    return item * 5;
}
var arrTimesFive = multiply(originalArr, timesFive);
console.log(arrTimesFive); // -> [5, 10, 15, 20, 25]
           

Map

我們進一步抽象:

function multiply(arr, multiplyFunction) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = multiplyFunction(arr[i]);
    }
    return newArr;
}
           

将multiply改為map, multiplyFunction改為transform:

function map(arr, transform) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = transform(arr[i]);
    }
    return newArr;
}
           

我們可以将任何對單個元素操作的函數傳入map函數。比如,我們将所有字元都變換成大寫:

function makeUpperCase(str) {
   return str.toUpperCase();
}
var arr = ['abc', 'def', 'ghi'];
var ARR = map(arr, makeUpperCase);
console.log(ARR); // -> ['ABC', 'DEF, 'GHI']
           

我們定義的map函數和原生的Array.map還是有差別的:數組不再需要作為第一個參數傳入,而是在點(.)的左側。如果使用我們定義的map函數,如下:

function func(item) {
   return item * 3;
}
var arr = [1, 2, 3];
var newArr = map(arr, func);
console.log(newArr); // -> [3, 6, 9]
           

将其改寫為使用Array.map函數的形式:

function func(item) {
   return item * 3;
}
var arr = [1, 2, 3];
var newArr = arr.map(func);
console.log(newArr); // -> [3, 6, 9]
           

Arrary.map參數解析

除了變換函數外,Array.map還可以接收其它兩個參數: 數組索引(index), 原始的數組。

function logItem(item) {
    console.log(item);
}
function logAll(item, index, arr) {
    console.log(item, index, arr);
}
var arr = ['abc', 'def', 'ghi'];
arr.map(logItem); // -> 'abc', 'def', 'ghi'
arr.map(logAll); // -> 'abc', 0, ['abc', 'def', 'ghi']
                 // -> 'def', 1, ['abc', 'def', 'ghi']
                 // -> 'ghi', 2, ['abc', 'def', 'ghi']
           

是以,你可以再變換函數中使用索引和原始的數組。比如:你想要将一個清單變為帶序号的清單,則需要使用索引(index)參數:

function multiplyByIndex(item, index) {
    return (index + 1) + '. ' + item;
}
var arr = ['bananas', 'tomatoes', 'pasta', 'protein shakes'];
var mappedArr = arr.map(multiplyByIndex);
console.log(mappedArr); // ->
// ["1. bananas", "2. tomatoes", "3. pasta", "4. protein shakes"]
           

是以,我們自己實作的map函數也應該支援這兩個參數:

function map(arr, transform) {
    var newArr = [];
    
    for(var i = 0; i < arr.length; i++) {
        newArr[i] = transform(arr[i], i, arr);
    }
    return newArr;
}
           

當然,Array.map函數還有一些錯誤檢查和執行優化的代碼,我們定義的map隻編碼了核心功能。

Array.filter

Array.filter将數組中不滿足條件的元素過濾,我們可以用for循環加上Array.push來實作。

for-loop

下面這段JS代碼将所有大于5的元素篩選出來:

var arr = [2, 4, 6, 8, 10];
var filteredArr = [];
for(var i = 0; i < arr.length; i++) {
    if(arr[i] > 5) {
        filteredArr.push(arr[i]);
    }
}
console.log(filteredArr); // -> [6, 8, 10]
           

我們可以抽象這段代碼,定義為一個函數:

function filterLessThanFive(arr) {
    var filteredArr = [];
    for(var i = 0; i < arr.length; i++) {
        if(arr[i] > 5){
            filteredArr.push(arr[i]);
        }
    }
    return filteredArr;
}
var arr1 = [2, 4, 6, 8, 10];
var arr1Filtered = filterLessThanFive(arr1);
console.log(arr1Filtered); // -> [6, 8, 10]
           

進一步抽象,将過濾條件抽出來:

function isGreaterThan5(item) {
    return item > 5;
}
function filterLessThanFive(arr) {
    var filteredArr = [];
    for(var i = 0; i < arr.length; i++) {
        if(isGreaterThan5(arr[i])) {
            filteredArr.push(arr[i]);
        }
    }
    return filteredArr;
}
var originalArr = [2, 4, 6, 8, 10];
var newArr = filterLessThanFive(originalArr);
console.log(newArr); // -> [6, 8, 10]
           

将過濾條件函數作為參數傳入:

function filterBelow(arr, greaterThan) {
    var filteredArr = [];
    for(var i = 0; i < arr.length; i++) {
        if(greaterThan(arr[i])) {
            filteredArr.push(arr[i]);
        }
    }
    return filteredArr;
}
var originalArr = [2, 4, 6, 8, 10];
           

大功告成!我們可以使用如下代碼來取出所有大于5的元素:

function isGreaterThan5(item) {
    return item > 5;
}
var newArr = filterBelow(originalArr, isGreaterThan5);
console.log(newArr); // -> [6, 8, 10];
           

我們将filterBelow重命名為filter, greaterThan重命名為testFunction:

function filter(arr, testFunction) {
    var filteredArr = [];
    for(var i = 0; i < arr.length; i++) {
        if(testFunction(arr[i])) {
            filteredArr.push(arr[i]);
        }
    }
    return filteredArr;
}
           

這就是一個基本的Array.filter函數了!

var arr = ['abc', 'def', 'ghijkl', 'mnopuv'];
function longerThanThree(str) {
    return str.length > 3;
}
var newArr1 = filter(arr, longerThanThree);
var newArr2 = arr.filter(longerThanThree);
console.log(newArr1); // -> ['ghijkl', 'mnopuv']
console.log(newArr2); // -> ['ghijkl', 'mnopuv']
           

同樣,Array.filter也有索引(index)和原始數組這兩個額外參數。

function func(item, index, arr) {
    console.log(item, index, arr);
}
var arr = ['abc', 'def', 'ghi'];
arr.filter(func); // -> 'abc', 0, ['abc', 'def', 'ghi']
                  // -> 'def', 1, ['abc', 'def', 'ghi']
                  // -> 'ghi', 2, ['abc', 'def', 'ghi']
           
如何實作JavaScript的Map和Filter函數?

版權聲明:

轉載時請注明作者Fundebug以及本文位址:

https://blog.fundebug.com/2017/07/26/master_map_filter_by_hand_written/

繼續閱讀