如何实现JavaScript的Map和Filter函数?

译者按: 鲁迅曾经说过,学习JavaScript最好方式莫过于敲代码了!

原文: Master Map & Filter, Javascript’s Most Powerful Array Functions

译者: Fundebug

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

这篇文章面向那些已经熟练使用for循环,但对Array.mapArray.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']

Array.map

我们定义的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];

Array.filter

我们将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']

版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/07/26/master_map_filter_by_hand_written/

您的用户遇到BUG了吗?

体验Demo 免费使用

.copyright * { box-sizing: border-box; }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏lonelydawn的前端猿区

lambda+reduce的一句艰深代码

一句话一脸懵逼 某天晚上看到一句lambda+reduce 组合的代码,看的头都炸了,愣是没看懂,不过也可能因为稀疏的脑神经经过一天的摧残已经运转不动了,这两天...

1918
来自专栏HTML5学堂

为什么不要在 JavaScript 中使用位操作符?

如果你的第一门编程语言不是 JavaScript,而是 C++ 或 Java,那么一开始你大概会看不惯 JavaScript 的数字类型。在 JavaScrip...

35210
来自专栏xingoo, 一个梦想做发明家的程序员

堆排序

构建堆的时间复杂度为O(n),而第I次调整堆的时间复杂度为O(logi),因此,无论什么情况下时间复杂度都为O(nlogn)。 算法思想:   首先,对数组从n...

1928
来自专栏码洞

《快学 Go 语言》第 3 课 —— 分支与循环

上面这个等式每一个初学编程的同学都从老师那里听说过。它并不是什么严格的数据公式,它只是对一般程序的简单认知。数据结构是内存数据关系的静态表示,算法是数据结构从一...

963
来自专栏写代码的海盗

脱掉Golang的第一层衣裳 golang入坑系列

海鳖曾欺井内蛙,大鹏张翅绕天涯。强中更有强中手,莫向人前满自夸。 各位看官,现在开始脱衣裳。你不用脱,自个衣裳要穿好了,别脱下来。我们是来学Golang的,不...

2603
来自专栏take time, save time

你所能用到的数据结构(七)

十、装配火车的乐趣       国庆放假结束了,第一天真是不想来上班啊,接着国庆之前的吧,上一篇写的是利用数组实现堆栈的结构,使用数组的两个致命的弱点是大小必须...

3388
来自专栏chenjx85的技术专栏

leetcode-371-Sum of Two Integers

2266
来自专栏C语言及其他语言

[每日一题]数字整除

C语言的奇葩之一就是明明可以直接除以17解决的问题偏偏要搞得这么麻烦 但我们能有什么办法呢,只能说是对思想的锻炼了呗! 题目描述 定理:把一个至少两位的正整...

3147
来自专栏Golang语言社区

Go语言中反射的正确使用

介绍 反射是元数据编程的一种形式,指的是程序获得本身结构的一种能力。不同语言的反射模型实现不一样,本文中的反射,仅仅指的是Go语言中的反射模型。 反射有两个问题...

3576
来自专栏Java爬坑系列

【Java入门提高篇】Day1 抽象类

  基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分...

1846

扫码关注云+社区