JavaScript-函数式编程
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
命令式编程中“典型”的方法和过程都深深地根植于它们所在的环境中,通过状态、依赖和有效作用达成;纯函数与此相反,它与环境无关,只要我们愿意,可以在任何地方运行它
面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩...以及整个丛林
函数与其他数据类型一样,处于平等地位
可作为变量一样被传递、返回或者在函数中嵌套函数。可作为参数。使用总有返回值的表达式而不是语句
函数应该纯天然,无副作用
副作用是指,函数内部与外部互动,产生运算以外的其他结果。 例如在函数调用的过程中,利用并修改到了外部的变量,那么就是一个有副作用的函数。
由于函数式编程不修改外部变量,所以根本不存在线程锁的问题。多线程并发的时候不用担心使用的变量被其他线程所修改
举个例子:
概括而言,副作用包含:
- 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
- 写日志
- 在屏幕输出
- 写文件
- 发网络请求
- 触发任何外部进程
- 调用另一个有副作用的函数
引用透明
纯函数的运行不应该依赖于外部变量或状态,只依赖于输入的参数。对于相同的输入参数,返回的结果一定相同。
immutable
如果要改变变量,则需要把数据 deep copy 出去进行修改
几个函数式编程的普通例子:
// 非函数式编程 let a = 1; function increase() { a++; } increase(); console.log(a); // 2 // 函数式编程 var a = 1; function increase(a) { // 通过参数引用,不依赖外部数据 return a + 1; // 不改变外部数据,返回一个新值 } console.log(increase(a)); // 2 console.log(a); // 1
// 比较 Array 中的 slice 和 splice let test = [1, 2, 3, 4, 5]; // slice 为纯函数,返回一个新的数组 console.log(test.slice(0, 3)); // [1, 2, 3] console.log(test); // [1, 2, 3, 4, 5] // splice则会修改参数数组 console.log(test.splice(0, 3)); // [1, 2, 3] console.log(test); // [4, 5]
// 函数式编程-函数作为返回参数 const add = (x) => { return plus = (y) => { return x + y; } }; let plus1 = add(1); let plus2 = add(2); console.log(plus1(1)); // 2 console.log(plus2(1)); // 3
函数式编程的几个要点
把函数当成变量来用,关注于描述问题而不是怎么实现
函数之间没有共享的变量
函数间通过参数和返回值来传递数据
在函数里没有临时变量
function add(a, b) { return a + b; } // 执行 add 函数,一次传入两个参数即可 add(1, 2) // 3 // 假设有一个 curry 函数可以做到柯里化 var addCurry = curry(add); addCurry(1)(2) // 3
柯里化
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下参数且返回结果的新函数
概念摘要
即: 传入一个(或很少量的)参数调用父函数,父函数返回一个可接受多个参数的子函数。例:
const add = (x) => { return (y, z) => { return x + y + z } } let increase = add(1); console.log(increase(2, 3)); // 6
函数式编程 + 柯里化,将提取成柯里化的函数部分配置好之后,可作为参数传入,简化操作流程。
// 给 list 中每个元素先加 1,再加 5,再减 1 let list = [1, 2, 3, 4, 5]; //正常做法 let list1 = list.map((value) => { return value + 1; }); let list2 = list1.map((value) => { return value + 5; }); let list3 = list2.map((value) => { return value - 1; }); console.log(list3); // [6, 7, 8, 9, 10] // 柯里化 const changeList = (num) => { return (data) => { return data + num } }; let list1 = list.map(changeList(1)).map(changeList(5)).map(changeList(-1)); console.log(list1); // [6, 7, 8, 9, 10]
创建柯里化函数
// 安装 lodash 依赖 // $ npm install lodash let curry = require('lodash').curry; let list = [1, 2, 3, 4]; let filter = curry(function(f, ary) { return ary.filter(f); }); let map = curry(function(f, ary) { return ary.map(f); }); let filterFun = (x) => { return (y) => { return x < y } } let mapFun = (x) => { return (y) => { return x + y } } let filteredList = filter(filterFun(3), list); let mapedList = map(mapFun(1), list); console.log(filteredList); // [4] console.log(mapedList); // [2, 3, 4, 5]
用途
我们会讲到如何写出这个 curry 函数,并且会将这个 curry 函数写的很强大,但是在编写之前,我们需要知道柯里化到底有什么用?
举个例子
// 示意而已 function ajax(type, url, data) { var xhr = new XMLHttpRequest(); xhr.open(type, url, true); xhr.send(data); } // 虽然 ajax 这个函数非常通用,但在重复调用的时候参数冗余 ajax('POST', 'www.test.com', "name=kevin") ajax('POST', 'www.test2.com', "name=kevin") ajax('POST', 'www.test3.com', "name=kevin") // 利用 curry var ajaxCurry = curry(ajax); // 以 POST 类型请求数据 var post = ajaxCurry('POST'); post('www.test.com', "name=kevin"); // 以 POST 类型请求来自于 www.test.com 的数据 var postFromTest = post('www.test.com'); postFromTest("name=kevin");
编写这个 curry 函数了第一版
// 第一版 var curry = function (fn) { var args = [].slice.call(arguments, 1); return function() { var newArgs = args.concat([].slice.call(arguments)); return fn.apply(this, newArgs); }; };
我们可以这样使用:
function add(a, b) { return a + b; } var addCurry = curry(add, 1, 2); addCurry() // 3
//或者 var addCurry = curry(add, 1); addCurry(2) // 3
//或者 var addCurry = curry(add); addCurry(1, 2) // 3
第二版优化
// 第二版 function sub_curry(fn) { var args = [].slice.call(arguments, 1); return function() { return fn.apply(this, args.concat([].slice.call(arguments))); }; } function curry(fn, length) { length = length || fn.length; var slice = Array.prototype.slice; return function() { if (arguments.length < length) { var combined = [fn].concat(slice.call(arguments)); return curry(sub_curry.apply(this, combined), length - arguments.length); } else { return fn.apply(this, arguments); } }; }
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
待更新