javascript函数式编程上

我们一起来学习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; //表达式:(5 + 6) / 2
var b = (function(){ return 25;})(); //表达式: (function(){ return 25;})()
foo(a*b); //表达式: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];
// Array.slice是纯函数,因为它没有副作用,对于固定的
//输入,输出总是固定的
xs.slice(0,3);//[1, 2, 3]
xs.slice(0,3);//[1, 2, 3]
xs.splice(0,3);//[1, 2, 3]
xs.splice(0,3);//[4, 5]

优点
纯函数不仅可以有效降低系统的复杂度,还有很多很棒的特性,比如可缓存性

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)) //42
Math.abs(Math.abs(Math.abs(-42))) //42

3.偏应用(partial application)函数

传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。先把部分参数传入,等需要时再调用,使用场景是不确定是否全部调用,但确定的是调用其中一部分。new promise,bind就是偏应用函数。

// 带⼀个函数参数 和 该函数的部分参数
const partial = (f, ...args) =>
 (...moreArgs) =>
 f(...args, ...moreArgs)
const add3 = (a, b, c) => a + b + c
// 偏应用 `2` 和 `3` 到 `add3` 给你⼀个单参数的函数
const fivePlus = partial(add3, 2, 3) 
fivePlus(4) //9

//bind实现
const add1More = add3.bind(null, 2, 3) 
add1More(4) //9
//bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:add3.bind(null,2,3),add3函数体内的this指向的是null;
//先传递两个参数2,3,再二次执行传4

4.函数的柯里化

柯里化(Curried) 是偏应用函数的升级,偏应用函数是传多个函数,柯里化是传一个。类似于闭包,一般不应用于业务中,而应用于工具库中。

var checkage = min => (age => age > min);
var checkage18 = checkage(18);//先传一个参数18
checkage18(20);//再执行传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); //返回10
//[1, 2, 3].length不等于(a, b, c, d) => a + b + c + d).length,则执行curry(fn,[1,2,3]),curry(fn,[1,2,3])(4),arg现在是[1,2,3,4],arg.length等于(a, b, c, d) => a + b + c + d).length则执行1+2+3+4结果为10
curryTest(1, 2)(4)(3); //返回10
curryTest(1, 2)(3, 4); //返回10

优点
事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法
缺点
柯里化的参数列表是从左向右,而setTimeout 参数在右,所以需要额外封装。

5.函数的反柯里化

反柯里化函数,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

Function.prototype.unCurrying = function() {
//Function.prototype,就是所有函数的原型;
//Function.prototype上面承载了用于继承给所有函数的那些属性,例如:call、bind、apply
 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);//{0: 'first', 1: 'two', length: 2}

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(' ');

//简化后,抽离了两个函数toUpperCase和split
var toUpperCase = word => word.toUpperCase();
var split = x => (str => str.split(x));
var f = compose(split(' '), toUpperCase);
//好处 1.toUpperCase,split 函数可以公用,2.更加快速执行和查找方法,使用"."查找方法特别慢,
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数据:函数即数据

以函数形式对数据建模,也就是函数即数据。

//select p.firstname from persons p where ...group by ..
<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{...}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值