我们一起来学习javascript函数式编程
前言
我以前听说过函数式编程,但没有系统的学习过函数式编程,知道react 和redux高阶函数用到了函数式编程,函数式编程也是面试官比较感兴趣的话题,所以有必要深入了解一番。
提示:以下是本篇文章正文内容,下面案例可供参考
一、函数式编程是什么?
函数式编程(Function Programming)缩写FP。 函数式编程英文的叫法是Functional Programming 缩写是FP。函数式编程是一种编程范式,我们可以认为他是一种编程的风格,他和面向对象是并列的关系。函数式编程我们可以认为是一种思维的模式,我们常听说的编程范式,还有面向过程变成和面向对象编程。 函数式编程的思维方式,是把现实世界中的事物,和事物之间的联系,抽象到程序世界中。 那这样他跟我们平常写代码有什么区别呢?用函数式编程的时候我们是不可以用if的,也没有else,因为数学中不存在if和else,也没有变量和while,整个都是数学的思维,然后用js的语法来承接。可以使用递归,因为递归是数学的概念。 1.数学中的函数书写分f(x) = y,对于给定的x只会输出唯一的y,不受外部的影响。 2.函数式编程不是用函数来编程,主要将复杂的函数合成简单的函数(计算理论,或者递归论,或者拉姆达演算),运算过程尽量写成一系列嵌套的调用。 3.通俗写法 function xx(){}区别开函数和方法。方法要与指定的对象 绑定、函数可以直接调用。 4.早于第一台计算机的诞生,函数式编程的基础模型来源于 λ (Lambda x=>x*2)演算。 5.JavaScript 是披着 C 外衣的 Lisp。(1958年,John McCarthy设计了Lisp语言,他的原意只是想做一种理论演算,用更简洁的方式定义图灵机,这种语言本质上不是一种技术,而是数学,包含了9种新思想) 6.真正的火热是随着React的高阶函数而逐步升温。 7.map & reduce他们是最常用的函数式编程的方法 特点 : a.函数是”第一等公民” b. 只用”表达式",不用"语句" 表达式是由运算符构成,并运算产生结果的语法结构。
var a = (5 + 6) / 2;
var b = (function(){ return 25;})();
foo(a*b);
语句则是由“;(分号)”分隔的句子或命令。
var a = (5 + 6) / 2;
if(a>12) { statements}
var o = {};
(function(obj){ obj.b = 23;})(o||{});
c. 没有”副作用",因为所有的互动都靠传参数。 d.不修改状态,只返回新的值,不修改系统变量。 e.应用透明,函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
二、专业术语
1.纯函数
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
var xs = [1,2,3,4,5];
xs.slice(0,3);
xs.slice(0,3);
xs.splice(0,3);
xs.splice(0,3);
优点 : 纯函数不仅可以有效降低系统的复杂度,还有很多很棒的特性,比如可缓存性
import _ from 'lodash';
var sin = _.memorize(x =>
Math.sin(x));
var a = sin(1);
var b = sin(1);
缺点 : 在不纯的版本中,checkage 不仅取决于 age,还有外部依赖的变量 min。纯的 checkage把关键数字 18 硬编码在函数内部,扩展性比较差,柯里化优雅的函数式 解决。
var min = 18;
var checkage = age => age > min;
var checkage = age => age > 18;
2.纯度和幂等性
幂等性是指执行无数次后还具有相同的效果,同一的参数运行一次函数应该与连续两次果一致。幂等性在函数式编程中与纯度相关,但有不一致。
Math.abs(Math.abs(-42))
Math.abs(Math.abs(Math.abs(-42)))
3.偏应用(partial application)函数
传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。先把部分参数传入,等需要时再调用,使用场景是不确定是否全部调用,但确定的是调用其中一部分。new promise,bind就是偏应用函数。
const partial = (f, ...args) =>
(...moreArgs) =>
f(...args, ...moreArgs)
const add3 = (a, b, c) => a + b + c
const fivePlus = partial(add3, 2, 3)
fivePlus(4)
const add1More = add3.bind(null, 2, 3)
add1More(4)
4.函数的柯里化
柯里化(Curried) 是偏应用函数的升级,偏应用函数是传多个函数,柯里化是传一个。类似于闭包,一般不应用于业务中,而应用于工具库中。
var checkage = min => (age => age > min);
var checkage18 = checkage(18);
checkage18(20);
const curry = (fn, arr = []) => (...args) =>
(arg => (arg.length === fn.length ? fn(...arg) :
curry(fn, arg)))([
...arr,
...args
]);
let curryTest = curry((a, b, c, d) => a + b + c + d);
curryTest(1, 2, 3)(4);
curryTest(1, 2)(4)(3);
curryTest(1, 2)(3, 4);
优点 : 事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法 缺点 柯里化的参数列表是从左向右,而setTimeout 参数在右,所以需要额外封装。
5.函数的反柯里化
反柯里化函数,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。
Function.prototype.unCurrying = function() {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
};
};
var push = Array.prototype.push.unCurrying(),
obj = {};
push(obj, "first", "two");
console.log(obj);
6.函数组合
柯里化会写出的洋葱代码 h(g(f(x))),为了解决函数嵌套的问题,我们需要用到“函数组合”,函数组合就是为了,柯里化好看。 函数组合的数据流是从右至左,因为最右边的函数首先执行,将数据传递给下一个函数以此类推,有人喜欢另一种方式最左侧的先执行,我们可以实现pipe(可称为管道、序列)来实现。它和compose所做的事情一样,只不过交换了数据方向。
7.函数组合子
命令式代码能够使用if-else和for这样的过程控制,函数 式则不能。所以我们需要函数组合子。其旨在管理函数程序执行流程,并在链式调用中对中间结果进行操作。 常见的函数组合子:左偏(partial),柯里化(curry),映射(map),规约(reduce),组合(compose)
8.Point Free
把一些对象自带的方法转化成纯函数,不要命名转瞬即逝的中间变量。不使用所要处理的值,只合成运算过程,译作“无值”风格
const f = str => str.toUpperCase().split(' ');
var toUpperCase = word => word.toUpperCase();
var split = x => (str => str.split(x));
var f = compose(split(' '), toUpperCase);
f("abcd efgh");
9.声明式与命令式代码
我们一般编写一条又一条指令去让计算机执行一些动作,而声明式就要优雅很多了,我们通过写表达式的方式来声明我们想干什么,而不是通过一步一步的指示。
let CEOs = [];
for(var i = 0; i < companies.length; i++)
CEOs.push(companies[i].CEO)
}
let CEOs = companies.map(c => c.CEO);
优点 函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。 不纯的函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的心智来说是极大的负担。
10.类SQL数据:函数即数据
以函数形式对数据建模,也就是函数即数据。
<script src="lodash.js"></script>
_.mixin({
"select":_.pluck, "from":_.chain,"where":_.filter,"groupby":_.sortByOrder});const persons = {}
_.from(persons).where().select().vallue();
11.惰性链、惰性求值、惰性函数
不会创建任何变量,并且有效消除所有循环。最后调用函数之前并不会真正的执行任何操作。这就是所谓的惰性链 当输入很大但只有一个小的子集有效时,避免不必要的函数调用 就是所谓的惰性求值 假如同一个函数被大量范围,并且这个函数内部又有许多判断来来检测函数,这样对于一 个调用会浪费时间和浏览器资源,所以当第一次判断完成后,直接把这个函数改写,不在需要判断。则是惰性函数
function createXHR(){
var xhr=null;
if(typeof XMLHttpRequest!='undefined'){
xhr=new XMLHttpRequest();
createXHR=function(){
return XMLHttpRequest
}
}else{...}
}
|