浅析函数式编程

Posted by WWJ on July 20, 2018

函数式编程

面向对象编程和函数式编程

面向对象编程关注的是数据,而函数式编程则关注的是动作 如果要实现一个数先加4然后再乘于4,面向对象编程实现如下

function sum (x) {
	return (x + 4) * 4;
}

而函数式编程则关注的是【加4】、【乘4】,这两个动作 函数式编程的核心就是纯函数,那到底什么是纯函数呢?

纯函数

  • 相同输入相同输出
  • 无副作用(只要跟外部环境发生交互就都是副作用)

从“副作用”这个概念来讲,更多的情况在于改变系统状态, 比如:

  • 访问函数内部以外的系统状态
  • 修改以参数传递过来的对象
  • 发起Http请求
  • 保留用户输入
  • 查询DOM

控制增变 我们都知道slicesplice都是对数组进行增删改的2个方法,但是具体区别是什么呢?

let arr = [1,2,3,4,5];
let tmpArr = arr.splice(0, 3);
console.log(tmpArr); // [1,2,3];
console.log(arr); // [4,5];

let arr = [1,2,3,4,5];
let tmpArr = arr.splice(0, 3);
console.log(tmpArr); // [1,2,3];
console.log(arr); // [1,2,3,4,5];

很显然从上面的例子中可以发现,用splice操作数组的时候,愿数组arr被修改了,而用slice对数组进行同样操作的时候,愿数组依然保持不变。如果我们避免使用传人函数的对象的增变方法,那么我们的函数就有期望编程一个不会修改系统外部变量的一个纯函数了。

为了更容易区分什么是纯函数,什么不是纯函数,以下例出了几个例子。

// 是纯函数
function add(x,y){
    return x + y
}
// 输出不确定,不是纯函数
function random(x){
    return Math.random() * x
}
// 有副作用,不是纯函数
function setColor(el,color){
    el.style.color = color ;
}
// 输出不确定、有副作用,不是纯函数,就是如果改变了count的值,那么输入就会变得不确定了
var count = 0;
function addCount(x){
    count+=x;
    return count;
}

// 是纯函数,因为count不会再改变,即相同的输入一定会得到相同的输出
const count = 0;
function addCount(x) {
	count += x;
	return count;
}

// 不是纯函数,与例4一样,依赖外部变量obj.a,一旦obj.a发生了改变的话,那么相同的输入就得不到相同的输出
let obj = { a : 1 }
function A(x) {
	return obj.a + x;
}

// 基本上是纯函数,除非obj = {
//		get id():{
//			return Math.random();
//		}
//	}
let obj = { a : 1 }
function A(_obj) {
	return _obj.a
}

函数合成

将代表各个动作的函数合成一个函数

function add4(x) {
	return x + 4;
}
function multi4(x) {
	return x * 4;
}
console.log(multi4(add4(1))) // 20

函数compose实现

function compose(f, g) {
	return function (x){
		return f(g(x))
	}
}
const calculate = compose(multi4, add4);
console.log(calculate(1)); // 20

通用compose

// 前一个函数的返回值时后一个函数的参数
function compose() {
    let args = arguments;
    let start = idx = args.length - 1;
    return function() {
	    // 首先要拿到第一个函数(也相当于是入口函数)的返回值,保存到result变量中,以作为第二个函数的参数
        var result = args[start].apply(this, arguments);
        // while循环就是求最后返回的结果,有几个参数就循环几遍
        while (--idx) result = args[idx].call(this, result);
        return result;
    };
}

function addHello(str){
    return 'hello '+str;
}
function toUpperCase(str) {
    return str.toUpperCase();
}
function reverse(str){
    return str.split('').reverse().join('');
}

var composeFn=compose(reverse,toUpperCase,addHello);

console.log(composeFn('ttsy'));  // YSTT OLLEH

函数柯里化

把接受多个参数的函数变成接收单个参数的函数,并返回接收剩余参数的有返回结果的新函数 作用: 降低通用型,提高适用性

function add (fn) {
	let _args = [];
	return function () {
		// 收集完成,执行fn
		if (arguments.length === 0) {
			return fn.apply(this, _args)
		}
		//收集参数
		Array.prototype.push.apply(_args, [].slice.call(arguments));
		return arguments.callee;
	}
}

const sum = add(function() {
	let allArr = Array.prototype.slice.call(arguments)
	return allArr.reduce(function(a, b) {
		return a + b;
	})
});
console.log(sum(2,3)(4)(5)()) // 14;

Javascript原生数组与高阶函数

  • Array.map():遍历数组,数组中的每一个元素都调用一个提供的函数返回的结果
    let arr = [1, 2, 3, 4, 5];
    let tmpArr = arr.map((item, idx) => {
      return item * idx;
    })
    console.log(tmpArr); // [0, 2, 6, 12, 20]
    
  • Array.reduce():从左到右遍历数组,接收2个参数,第一个是一个执行函数,接收的函数中的第一个参数代表当前部分和,第二个参数代表当前遍历的元素,每一个遍历执行这个函数,第二个数初始值 ```javascript // Array.prototype.reduce源码解析 let arr = [1, 2, 3, 4, 5]; Array.prototype.myReduce = function(fn, init) { let prev = init; let idx = 0; if (!init) { prev = this[0]; idx = 1; } for (let i = idx; i < this.length; i++) { prev = fn(prev, this[i]) } return prev; } let add = arr.myReduce(function(prev, curr) { return prev + curr; }) console.log(add) // 15

let add = arr.myReduce(function(prev, curr) { return prev + curr; }, 100) console.log(add) // 115

* `Array.filter()`:筛选出符合提供函数条件的元素
```javascript
let arr = [1, 2, 3, 4, 5];
let tmpArr = arr.filter(function(item) {
	return item % 2
})
console.log(tmpArr); // [1, 3, 5];
  • Array.some():返回boolean,只要有一个符合条件,遍历终止
    let arr = [1, 2, 3, 4, 5];
    console.log(arr.some(function(item) {
      return item > 5;
    })); // false
    
  • Array.every():返回一个boolean,只要有一个不符合条件,遍历终止
    let arr = [1, 2, 3, 4, 5];
    console.log(arr.every(function(item) {
      return item > 5;
    })); // false
    
  • Array.sort():排序
    arr.sort(function(a, b) {
      return a - b; //正序
    })
    arr.sort(function(a, b) {
      return b - a; //倒序
    })''
    

后续继续跟进,希望能深入理解函数式编程