如今,在程序员学习过程中基本都会发现一个叫 map 的函数。在发现 map
函数之前,你可能都会使用 for
循环来处理需要多次执行某一行为的场景。一般情况下,在这个循环过程中都会伴随一些数据变换。
命令式
例如,你团队的销售人员交给你一个很长的电邮地址列表。这些邮箱地址获取的时候并没有经过很好地校验,以至于有些是大写的,有些是小写的,还有一些是大小写混合的。使用 for
循环进行数据处理的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var mixedEmails = ['JOHN@ACME.COM', 'Mary@FooBar.com', 'monty@spam.eggs']; function getEmailsInLowercase(emails) { var lowercaseEmails = []; for (var i = 0; i < emails.length; i++) { lowercaseEmails.push(emails[i].toLowerCase()); } return lowercaseEmails; } var validData = getEmailsInLowercase(mixedEmails); |
这样的做法是有效的,但却把一个实际上简单常见的操作变得复杂。使用 for
循环的函数牵扯了很多不必要的细节。一些痛点如下:
- 需要让程序创建一个临时列表来存储复制的邮件地址值。
- 需要让程序先计算列表的长度,以此为次数访问一遍列表。
- 需要让程序创建一个计数器用来记录当前访问的位置。
- 需要告诉程序计数的方向,但顺序在这里并不重要。
这是命令式的编程方法。我们似乎在口述给电脑该怎么做这件事。
困惑
为了使之前的代码更加清晰整洁,我们改用 map
函数。在任何 map
函数的说明文档中,我们都会看到诸如 “array”、“each”、“index”之类的词。这表明,我们可以把 map
当做不那么“隆重”的 for
循环使用,事实上也是可行的。现在来修改一下之前的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var mixedEmails = ['JOHN@ACME.COM', 'Mary@FooBar.com', 'monty@spam.eggs']; function getEmailsInLowercase(emails) { var lowercaseEmails = []; emails.map(function(email) { lowercaseEmails.push(email.toLowerCase()); }); return lowercaseEmails; } var validData = getEmailsInLowercase(mixedEmails); |
这样写不仅能用,而且代码比使用 for
循环更加清楚。除了代码量更少,我们也不用再告诉程序去记录索引和遍历列表的方向了。
然而,这还不够好。这样写还是命令式的编程。我们还是指挥的太多。实际上我们牵涉了很多不必要的细节,似乎都在领着程序的手走每一步。
声明式
我们需要改变我们关于数据变换的思考方式。我们不需要想着:“电脑啊,我需要你取出列表中第一个元素,然后把它转换成小写,再存储到另一个列表中,最后返回这个列表”。相反,我们应该这样想:“电脑,我这有一个混合了大小写的邮件地址列表,而我需要一个全是小写的邮件地址列表,这是一个能够进行小写转换的函数”。
1 2 3 4 5 6 7 |
var mixedEmails = ['JOHN@ACME.COM', 'Mary@FooBar.com', 'monty@spam.eggs']; function downcase(str) { return str.toLowerCase(); } var validData = mixedEmails.map(downcase); |
毫无疑问,这种写法更易懂,同时这才是程序的本质:把你的想法告诉其他人,这个人可能是别的程序员或未来的你。上面的代码在说“有效的数据是使用小写转换函数映射后的邮箱列表”。
使用类似这样的高级方式传递想法是函数式编程的核心原则,而我们就在这样做。将单一功能、易于理解的简单部分组合起来,由此构建复杂程序。
这样的写法还有些额外的好处。下表的排序不分先后:
- 小写转换函数提供了最简化的接口:单值输入,单值输出。
- 能够改动的地方不多,所以逻辑更易于理解,也易于测试,而且不易出错。
- 逻辑单一,所以易于复用,并且与其他函数能够组合出更复杂的功能。
- 沿这样的声明式编程路线走的话,代码量会显著缩小。
虽然给 map
的第一个参数传入匿名函数很常见,还是建议把函数提出来并合理命名。这能够帮你记录下写此函数的意图,这样别的开发者就能通过函数名了解功能而不用再费劲分析代码了。
浏览器支持情况
ECMAScript 5 标准定义了原生的 map()
方法,所以浏览器兼容性较好。如果你想在 IE 9 之前的版本中使用,就需要引入一个 polyfill 或使用 Underscore、Lodash 之类的库了。
性能表现
极大多数情况下,在实际编码中 map
函数和 for
循环之间没有明显的性能差距。for
循环稍快一些,但如果你不是在写图形或物理引擎的话,这点差距没必要去考虑,当然即使如此,除非能够确定这些性能的提升对你有帮助,否则用这种方式去优化意义不大。
总结
将逻辑分成单一功能的方法并应用于数据结构上,这种编程方法会让你的代码更准确、鲁棒和易于理解。我们的理念就是尽可能通用,通用能够帮助代码重用。学习这种思考方法,不仅能帮助你提高 Javascript 水平,也能体现在其他多数编程语言上,例如 Ruby 和 Haskell。
所以,下一次当你要使用 for
循环时,重新考虑一下。记住,你要处理的数据并不一定是普通的数组,你可以去处理对象,取出它的值,再使用函数去映射,最后整理出结果数组。你甚至可以利用 Underscore 之类的库去在保留键值的情况下 map 一个对象。
你还能想出其他使用 map
函数的创新方式么?去尝试吧,你将目睹代码的缩减过程。