Underscore源码分析(1)_函数式编程概述

243 阅读3分钟

写作一直断断续续。笔者想出一个系列文章,对underscore的源码做个分析。

函数式编程思想概述

JavaScript作为一种典型的多范式编程语言,这两年随着react的火热,函数式编程的概念也开始流行起来;

Rxjs 、lodash、underscore等多种开源库都使用了函数式的特性。

纯函数

定义:

相对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。

例子:数学公式 y=f(x)

在JavaScript中,对于数组的操作有些是纯的,有些事不纯的。

var arr = [1, 2, 3, 4, 5];

// 纯函数
arr.slice(0, 3); // => [1,2,3]
arr.slice(0, 3); // => [1,2,3]

// 非纯函数
arr.splice(0, 3); // => [1, 2, 3]
arr.splice(0, 3); // => [4, 5]

/* 
array.slice(start, end)
  slice() 方法以新的数组对象,返回数组中被选中的元素。
  slice() 方法选择从给定的 start 参数开始的元素,并在给定的 end 参数处结束,但不包括。
  注释:slice() 方法不会改变原始数组。


array.splice(index, howmany, item1, ....., itemX)
  splice() 方法向/从数组添加/删除项目,并返回删除的项目。
  注释:splice() 方法会改变原始数组。
*/

函数式编程为何排斥不纯的函数?

非纯函数中,函数的行为需要由外部的系统环境决定。也就是说此函数的行为不仅取决于输入的参数age,还取决于一个外部变量 timeOfLife。

这种对于外部状态的依赖,是造成系统复杂性大幅提高的主要原因。

var timeOfLife = 20;
// 纯函数
function test(age) {
  return age > 20;
}

// 非纯函数
function test(age) {
  return age > timeOfLife;
}

函数柯里化

定义:向函数传递一部分参数来调用它,让它返回一个函数去处理剩下的参数

事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法。

var timeOfLife = 20;
// 上一步纯函数中我们把 20 硬编码到函数内部,这种作法可扩展性比较差,
// 那么我们通过函数柯里化,就可以优化这里的内容

function test(timeOfLife) {
  return function(age) {
    return age > timeOfLife;
  }
}
var testing = test(20);
testing(18);  // => false

函数组合

为避免写出不优雅的包菜式代码 h(g(f(x))),我们就需要函数组合

我们定义的compose就像双面胶一样,可以把任何两个纯函数结合到一起,也可以扩展出N个函数的N面胶。

这种灵活的组合,让我们可以拼积木一样优雅的组合函数式代码。

// 两个函数的组合
var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  }
}
var mult = function(x) {
  return x*5;
}
var add = function(x) {
  return x+1;
}
compose(mult, add)(2); // => 15

声明式与命令式

命令式代码:通过编写一条有一条的指令,让计算机执行一些动作,其中一般会涉及许多繁杂的细节。

声明式代码:通过写表达式的方式,声明我们想干什么,而不是通过一步一步的指示。

声明式代码,是函数式编程的一个明显好处——编写。优化代码时能更专注。

// 命令式
var rest = [];
var arr = [4, 9, 16, 25, 4, 16];
for (var i=0; i<arr.length; i++) {
  if (rest.indexOf(arr[i]) === -1) {
    rest.push(arr[i]);
  }
}


// 声明式
var rest = arr.map(Math.sqrt);

总结

  • 函数对于外部状态的依赖,是造成系统复杂性大大提高的主要原因
  • 代码书写中让函数尽可能的纯净
  • 函数式编程不是万能的,它和OOP一样,只是一种编程范式
  • 为降低软件复杂度,OOP的方式是靠良好的封装、继承、多态以及接口定义。函数式编程则是靠纯函数以及他们的组合、柯里化等技术。