Javascript函数式编程小结
发布于 8 年前 作者 ycczkl 4945 次浏览 来自 分享

Javascript函数式编程小结

源起

函数式编程近几年非常流行,经常可以在网上看到别人讨论相关话题. 我机缘巧合之下在github上看到有人提到一个讲js函数式编程的视频,看过之后突然茅塞顿开,瞬间把之前零碎的关于函数式编程的知识一下子都联系了起来, 于是自己希望趁有空把这些知识总结一下, 这样既可以回顾下知识耶没准能帮到一些对函数式编程感兴趣的朋友们.

为什么需要函数式编程

其实千万别被这看似高深的名字吓怕了, 其实我们码农用这种编程思想无非是为了能更爽的写好代码. 总结一下函数式编程的优点有以下两个优点:

  1. Less bug: 可读性高, 逻辑上更容易理解.
  2. less Time: 更高的抽象层级, 代码高度可复用.

高阶函数(Higher-order functions)

这里又一个炫酷屌的名字: “高阶函数”. 别怕, 本文并不涉及高等数学的知识就可以让你了解高阶函数的概念. 但是等等! 高阶函数跟函数式编程又有毛关系? 其实简单的说就是高阶函数的特性让我们可以实现函数式编程. 废话不多说, 下面就简单说下什么是高阶函数:

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.

从定义可以知道高阶函数就是可以把函数作为变量传入别的函数, 或者一个函数的返回值是一个函数的函数. 如果上面的话把你绕晕了那么我来简单的概括下就是函数可以被当作变量在程序中传来传去. 下面举几个例子:

//把函数当成变量
var foo = (x) => console.log(x)
//把函数当作参数传入另一个函数
var bar = (y, fn) => fn(y)
foo('FP is good') // FP is good
bar('FP is great', foo) //FP is great

下面这个例子更能体现高阶函数的便利性, 如果我们要遍历数组并且打印数组元素的信息, 你会怎么做?

var arr = [1,1,2,3,5,8]
function traverseArray(arr) {
  for (let i = 0; i < arr.length; i++) {
    console.log(`index: ${i}, value: ${arr[i]}`)
  }
}
traverseArray(arr)

Easy enough! 如果用函数式编程的思维重写上面的代码应该是这个样子的:

function forEach(arr, fn) {
  for (let i = 0; i < arr.length; i++) {
    fn(i, arr[i], arr)
  }
}

forEach(arr, (index, value) => console.log(`index: ${index}, value: ${value}`))

WTF!? 说好的函数式编程更简洁代码量更少呢! 明显第二种写法写的代码更多啊. 别急, 其实es5的时候JS已经把一系列高阶函数设置好了,比如forRach, map, reduce, filter, every.. 让我们用js的array.forEach重写上面的代码:

arr.forEach((v, k) => console.log(`index: ${k}, value: ${v}`))

怎么样, 一样代码解决战斗。 也许看到这里你还是觉得没什么意思,毕竟虽然代码量少了些,但也没感觉有什么本质性的变化. 下面我会举几个比较常见的例子,通过例子让我们一起体会这种有趣的编程思想,也许看过之后你的想法会有所改变.

常用方法举例

先介绍下几个常用的函数, filter, map, reduce:

var animals = [
  {name: 'a' , species: 'dog', weight: 11},
  {name: 'b', species: 'cat', weight: 10},
  {name: 'c', species: 'fish', weight: 1},
  {name: 'd', species: 'cat', weight: 8},
  {name: 'e', species: 'rabbit', weight: 3}
]
// 找到所有种类为猫的动物
animals.filter((animal) => animal.species === 'cat')
// [ { name: 'b', species: 'cat' }, { name: 'd', species: 'cat' } ]

// 返回所有动物的名字
animals.map((animal) => animal.name)
// [ 'a', 'b', 'c', 'd', 'e' ]

// 最重动物的体重
animals.reduce((pre, animal) => pre + animal.amount, 0)
//11

怎么样? 如果用循环来写肯定不可能一行代码就搞定吧. 让我们看一些更深入更有意思的例子吧. 比如这道比较经典的算法题: 给定一个字符串"abcdaabc"统计每个字符的出现次数.

// 一般做法是这样的
var str="abcdaabc"
var count = {};
var i;

for(i=0;i<str.length;i++){
    var chr = arr.charAt(i);
    if( typeof count[chr] === "undefined"){
        count[chr] = 1;
    }else{
        count[chr]++;
    }
}

// 利用函数式编程思想的方法是这样的
var res = str.split('')
             .reduce((pre, cur) => (pre[cur]++ || (pre[cur] = 1), pre), {})

由上面的例子可见, 高阶函数的特性让我们在处理这类问题时比普通的方法要方便的多. 在更实际的应用场景中函数式编程的便利性更能充分的体现, 其中一个经典的例子是统计文本中出现频率最高的十个单词:

var fs = require('fs');
var content = fs.readFileSync('words.txt').toString();
var words = content.split(/[\s.,\/:\n]+/);
// 把单词全部变为小写并利用上一个例子的方法统计单词出现的次数
var tally = words.map((word) => word.toLowerCase())
                 .reduce((pre, cur) => (pre[cur]++ || (pre[cur]=1), pre), {})
//把object的key变成数组并进行排序
var top10 = Object.keys(tally)
                  .map((key) => {
                    return {word: key, count: tally[key]}
                  })
                  .sort((a, b) => b.count - a.count)
                  .slice(0, 10)
console.log(top10)

上面这个例子充分体现了函数式编程思想的魅力,代码精简并且可读性高! 想一想如果你要用循环来写有多多少行代码! 好了, 总结完毕, 最后附上参考资料.

参考资料

Functional programming in JavaScript Functional Programming By Example Eloquent javascript - chapter6

8 回复

取了个高大上的标题,就讲了个高阶函数 😑

@dd1994 刚入门,只懂些浅显的 哈哈

@i5ting 谢谢,发现之前star过 回头细读

@i5ting 这本书最后三章好难啊,完全看不懂 - -

回到顶部