通过代码一步步的深入
- map
- reduce
- filter
...
- find
参考自
本文重写了代码、添加了个人的理解,更加精简
先看看 js 中的基础循环代码
- while
- // 所有字母大写
- function capitalize(str){
- return str.replace(/[a-z]/g, s => s.toUpperCase())
- }
- const company = [
- 'apple',
- 'google',
- 'amazon',
- 'facebook'
- ]
使用
循环来大写数组中每个元素
- while
- let i = 0
- const len = company.length
- const newCompany = []
- while(i<len) {
- newCompany.push(capitalize(company[i]))
- i++
- }
这种循环太常见了,一个初始值为 0 计数器
,每次循环需要递增
- i
的值,并通过
- i
来判断循环终止条件。
- len
简单的
循环代码如下:
- for
- let i = 0
- const len = company.length
- const newCompany = []
- for(; i<len; i++) {
- newCompany.push(capitalize(company[i]))
- }
比起
- while
循环把计数器
- for
放在循环顶部,避免了
- i
中忘记计数器自增自增而引起的死循环。
- while
但是后退一步看,我们只是要把每一项大写,但是并不在乎循环中的计数器
- i
因为这种对数组每一项进行循环操作的模式很常见,所以有了 ES2015 中的
循环
- for..of
- const newCompany = []
- for(let item of company) {
- newCompany.push(capitalize(item))
- }
用了
省了计数器和循环终止的判断,也不需要抽取出数组每一项来处理
- for...of
虽然
更简洁,但还是要初始
- for...of
并执行
- newCompany
- push
如何做到更加精简
如果现在有两个数组需要
呢
- capitalize
- const company = [
- 'apple',
- 'google',
- 'amazon',
- 'facebook'
- ]
- const animal = [
- 'dog',
- 'cat',
- 'pig'
- ]
两次循环 简单粗暴:
- const newCompany = []
- for(let item of company) {
- newCompany.push(capitalize(item))
- }
- const newAnimal = []
- for(let item of animal) {
- newAnimal.push(capitalize(item))
- }
这看起来不是很 DRY (Don't repeat yourself) ,所以可以重构下
- function capitalizeArray(arr){
- const newArr = []
- for(let item of arr){
- newArr.push(capitalize(item))
- }
- return newArr
- }
- const newCompany = capitalizeArray(company)
- const newAnimal = capitalizeArray(animal)
这样看起来还行,但是如果新加一个功能,只是首字母大写呢
- // 首字母大写
- function capitalizeFirst(str){
- return str.replace(/( |^)[a-z]/, s => s.toUpperCase())
- }
如果这样写,下面的代码依然不是很 DRY
- function capitalizeArray(arr){
- const newArr = []
- for(let item of arr){
- newArr.push(capitalize(item))
- }
- return newArr
- }
- function capitalizeFirstArray(arr){
- const newArr = []
- for(let item of arr){
- newArr.push(capitalizeFirst(item))
- }
- return newArr
- }
那就继续重构,抽象成给定一个数组和一个函数,然后将数组中每一项映射到新的数组的模式
- function map(fn, arr) {
- const newArr = []
- for(let item of arr){
- newArr.push(fn(item))
- }
- return newArr
- }
但代码里依然有个循环,只能写成递归的形式
- function map(fn, arr) {
- if(arr.length == 0) return []
- return fn(arr[0]).concat(map(fn, arr.slice(1)))
- }
递归的代码极简,但在旧浏览器里有性能问题,所以我们就直接用 js 内置的
好了
- map
同样简单粗暴
- const newCompany = company.map(capitalize)
- const firstUpperCompany = company.map(capitalizeFirst)
- const newAnimal = animal.map(capitalize)
- const firstUpperAnimal = animal.map(capitalizeFirst)
像函数式编程一样,分而治之,两个用来做大写字符串的函数只关注本身的功能,不关心数据从哪来;同时
只负责传递函数,不关心函数是干什么的。
- map
在输出和输入的相同长度的数组上很好用。
- map
但是如果再添加一个数字数组或者在一个 list 中找出最短的字符串呢。
如下,有个 worker 的数组,包含姓名和薪水
- const workers = [{
- name: 'Jack',
- salary: 1000
- },
- {
- name: 'Tony',
- salary: 2000
- },
- {
- name: 'Lily',
- salary: 3000
- },
- {
- name: 'Sara',
- salary: 4000
- }]
找出 salary 最高的那位
- let highestSalary = {
- salary: 0
- }
- for (let w of workers) {
- if (w.salary > highestSalary.salary) {
- highestSalary = w
- }
- }
上面的代码那样写也没什么问题。为了深入一点,我们现在需要所有人 salary 的总和,代码如下
- let sumSalary = 0
- for(let w of workers) {
- sumSalary += w.salary
- }
上面两个例子中都需要在循环之前定义一个变量,然后在每次循环中处理一个循环项后并更新这个变量。
为了深入理解这个循环,我们把循环内部拆解为函数,找出相似之处
- // slary 对比
- function greaterSlary(a, b) {
- return a.salary > b.salary ? a : b
- }
- function addSalary(sum, worker) {
- return sum + worker.salary
- }
- const initialHighest = { slary: 0 }
- let temp = initialHighest
- for(let w of workers) {
- temp = greaterSlary(temp, w)
- }
- const highestSalary = temp
- const initialSumSalary = 0
- temp = initialSumSalary
- for(let w of workers) {
- temp = addSalary(temp, w)
- }
- const sumSalary = temp
上面的代码两个循环非常相似,唯一不同的是初始值和循环中的调用的函数。两者都将数组最终降为单个值,所以我们自己写
- reduce
- function reduce(fn, initial, arr) {
- let temp = initial
- for(let item of arr) {
- temp = fn(temp, item)
- }
- return temp
- }
但是 js 里面内置了
,我们直接拿来用就行了
- reduce
- const highestSalary = workers.reduce(greaterSlary, {
- slary: 0
- }) const sumSalary = workers.reduce(addSalary, 0)
使用了原生的
分离了循环代码后,代码的复杂度降低了很多。
- reduce
可以很好的处理数组的每一项
- map
可以把一个数组缩减到一个单一值
- reduce
但是想处理一个数组中的多个项目呢?首先给
数组加点数据
- workers
- const workers = [{
- name: 'Jack',
- salary: 1000,
- gender: 'm'
- },
- {
- name: 'Tony',
- salary: 2000,
- gender: 'm'
- },
- {
- name: 'Lucy',
- salary: 2500,
- gender: 'f'
- },
- {
- name: 'Lily',
- salary: 3000,
- gender: 'f'
- },
- {
- name: 'Sara',
- salary: 4000,
- gender: 'f',
- } {
- name: 'Kaka',
- salary: 5000,
- gender: 'm'
- }]
两个问题:
用个普通的
循环:
- for
- const femaleWorkers = []
- for(let w of workers) {
- if(w.gender == 'f') {
- femaleWorkers.push(w)
- }
- }
- const highSalaryWorkers = []
- for(let w of workers) {
- if(w.salary > 2500) {
- highSalaryWorkers.push(w)
- }
- }
上面两块代码唯一不同的是
判断,所以把
- if
转换成函数
- if
- function isFemale(worker) {
- return worker.gender === 'f'
- }
- function isHighSlary(worker) {
- return worker.salary > 2500
- }
- const femaleWorkers = []
- for(let w of workers) {
- if(isFemale(w)) {
- femaleWorkers.push(w)
- }
- }
- const highSalaryWorkers = []
- for(let w of workers) {
- if(isHighSlary(w)) {
- highSalaryWorkers.push(w)
- }
- }
我们在把这种只返回
- true
的函数成为
- false
(谓词函数) 然后使用
- predicate
来决定数组中元素是否保留
- predicate
把
函数抽象到一个 filter 中
- predicate
- function filter(predicate, arr) {
- let temp = []
- for(let item of arr) {
- if(predicate(item)) {
- temp = temp.concat(item)
- }
- }
- return temp
- }
- const femaleWorkers = filter(isFemale, workers)
- const highSalaryWorkers = filter(isHighSlary, workers)
同样 js 也内置了
函数,直接使用
- filter
- const femaleWorkers = workers.filter(isFemale)
- const highSalaryWorkers = workers.filter(isHighSlary)
只需要写一个功能单一而专注的过滤函数,然后调用
即可。
- filter
如果需要找出 Sara ,
是没问题的
- filter
- function isSara(worker){
- return worker.name === 'Sara'
- }
- const Sara = workers.filter(isSara)[0]
这样的代码并不高效,因为只有一个 Sara ,找到后就可以停止查找操作了。
所以写一个
函数, 返回第一个符合项
- find
- function find(predicate, arr) {
- for(let item of arr) {
- if(predicate(item)) {
- return item
- }
- }
- }
- const Sara = find(isSara, workers)
函数实现非常简单,js 也内置了
- find
- find
- const Sara = workers.find(isSaraworkers)
和
一样,代码简洁且专注。
- filter
使用内置的这些迭代函数
- map
- reduce
- filter
- find
在上面每一个例子中,我们把问题分解,使用更小而纯粹的函数即可找到解决方案。
因为循环中总是在处理或者构建一个数组,或者两者同时在做,因此通过 js 内置的数组处理函数几乎可以消除代码中绝大多数循环,写出复杂性更低更利于维护的代码。
来源: http://www.tuicool.com/articles/VfEFfui