
在我工作时,总有少量总结性的想法,但是我并不擅长总结,只爱说大白话,直到我开始接触函数式编程,我才明白,这就是我一直想表达的写代码方式,哈哈,真是吃了没文化的亏。
我们常听说的编程范式有面向过程编程、面向对象编程,以及函数式编程。
上面这几行天花乱坠的一直在表达这个意思:大白话:函数式编程就是你高中数学的学的那个函数的意思,当输入确定了,输出也会确定。
(定义虽绕,但是他是十分严谨科学的,要多读)
比方来看这么一个简单例子,实现一个两个数相加
// 非函数式 let num1 = 2 let num2 = 3let sum = num1 + num2 console.log(sum) // 函数式 function add (n1, n2) { return n1 + n2 }let sum = add(2, 3) console.log(sum) 函数式就会有一个函数,叫add,当输入的n1,n2确定时,是不是得到结果肯定是确定的!这是多么的可控!而且又是多么的能加以复用!完全安全无反作用!给我整激动了。
1.函数是一等公民
2.高阶函数
3.闭包
一等公民这个词可以大概看下这个 函数是一等公民,总结来说,在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。比方说字符串就是一等公民。
在 js 中函数就是一个普通的对象 (可以通过 new Function() ),我们可以把函数存储到变量/数组中,它还可以作为另一个函数的入参数和返回值。对吧,看起来挺废话的,不要焦急。
高阶函数 (Higher-order function) 就是HOF,用react的同学看到这个是不是有点熟习,对,react的高阶组件叫HOC,其本质就是个高阶函数,那么啥是高阶函数呢
函数作为参数
你手动实现一个forEach就明白了,forEach的功能是循环数组,并对每一项做你想做的操作,来,上代码
// forEach function forEach (array, fn) { for (let i = 0; i < array.length; i++) { fn(array[i]) }}多简单,这里的fn就是函数作为参数,我们想想这样有什么好处,它是不是让我们的forEach方法更灵活,而且在调用的时候,也不需要考虑它内部是如何实现的,爱用Lodash的小伙伴看到这请开始有少量感悟。
函数作为返回值
前台会经常遇到这样一个场景,客户连续屡次点击了提交按钮,但是我们希望绑定的那个submit函数只执行一次,这就需要我们来实现一个once函数,让传进去的函数即便被屡次调用,确只执行一次,上代码。
function once(fn) { let flag = false; // 设定一个标识位,当fn执行后变成true return function () { if(!flag) { flag = true; return fn.apply(this, arguments) } } }// 这里来定义这个提交的函数let submit = once(function (num) { console.log(`提交的数字是${num}`) });// 试一下submit(10) // 只有这个会输出submit(20)submit(30)这个例子可能开始有点绕了,return来return去的,请务必自己手动实现一下,屡清思路,多写几遍就好了,这里的关键就是第三行那,return回一个函数,这个函数来判断能否需要执行入参的那个函数。
高阶函数的意义
笼统可以帮我们屏蔽细节,只要要关注我们想要的功能,高阶函数是用来笼统通用的问题,再看一遍这个例子。
// 面向过程的方式 let array = [1, 2, 3, 4] for (let i = 0; i < array.length; i++) { console.log(array[i]) }// 高阶函数 let array = [1, 2, 3, 4] forEach(array, item => { console.log(item) })你看,这就是对循环的笼统,不需要关心循环具体实现的细节了,代码又简洁,又灵活。
常用高阶函数
//我们再挑一个some实现一下function some(array, fn) { let result = false; for(let i = 0 ; i < array.length; i++) { if(fn(array[i])){ result = true; break } } return result}通过上面的forEach和some,他们都可以接收一个函数,这就是高阶函数,通过把一个函数传给另一个函数,可以让这个函数更灵活
闭包:可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
老生常谈的问题了,它只是定义比较绕,其实概念并不复杂
我们经常会在不经意间就使用它,比方我们上面那个once的例子
function once(fn) { let flag = false; return function () { // 假如once执行,返回的是这个函数,我们叫它f1 if(!flag) { flag = true; return fn.apply(this, arguments) } } }// 这里来定义这个提交的函数let submit = once(function (num) { console.log(`提交的数字是${num}`) });// 这里once执行后,submit=f1,once从执行栈中释放,// 但是f1这个函数仍然需要从它的外部的once函数中的获得flag变量,所以这里的flag变量不会被释放// 试一下submit(10) // 只有这个会输出submit(20)闭包的本质就是:函数在执行的时候会放到一个执行栈受骗函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员由于被外部引用不能释放,因而内部函数仍然可以访问外部函数的成员
我们来实现一个闭包的案例
现在要写一个统计员工工资的需求,假设这里是阿里,工资由基本工资+绩效,员工分p5,p6等职级,每个职级的基本工资不一样, 同职级本月绩效也不一样
// 多数人很可能就写成了这样function getSalary(base, jixiao) { // base是基本工资 return base + jixiao;}let zhangsan = getSalary(10000, 3000); // 假设张三是p5,p5基本工资10000let lisi = getSalary(20000, 2000); // 假设李四是p6,p6基本工资20000这样的话,每次都还要输入各个职级的基本工资
// 使用闭包function makeSalary(base) { return function (jixiao) { return base + jixiao }}let getSalaryP5 = makeSalary(10000);let getSalaryP6 = makeSalary(20000);let zhangsan = getSalaryP5(3000);let lisi = getSalaryP6(2000)用控制台查看闭包,使用上面的例子

断点到执行makeSalary前,可以看到
CallStack(函数的调用栈):此时是anonymous,是个匿名函数,由于这外面就有一层script,他就相当于一个匿名函数在执行
Scope(作用域):可以看到当前Script的作用域中,有各种变量,因为js有变量提升,现在都是undefined;Global是全局作用域,指向window
按F11,向下走进makeSalary里

CallStack:栈顶出现了makeSalary函数
Scope:Local,当前作用域,出现base=10000,this指向window
继续到makeSalary执行完毕

CallStack:makeSalary执行完出栈了
Scope:刚才的Local作用域也移除掉了,Script中 getSalaryP5得到了执行结果,是个函数
代码继续走走走,走到执行getSalaryP5里去

CallStack:这个匿名函数进来了
Scope:重点看这个多出来的Closure,它就是闭包,(makeSalary)代表外部函数,里面的base就是相关的变量,所以正如之前所说,外部函数执行完就会移除,但是跟闭包相关的变量会被缓存下来
继续走到执行getSalaryP6里去

一目了然
整理这玩意真是太吃力了,删了改,改了删的,下节整理纯函数及柯里化
函数式编程(二)