函数式编程(一)

在我工作时,总有一些总结性的想法,但是我并不擅长总结,只爱说大白话,直到我开始接触函数式编程,我才明白,这就是我一直想表达的写代码方式,哈哈,真是吃了没文化的亏。

我们常听说的编程范式有面向过程编程、面向对象编程,以及函数式编程。

  • 面向过程编程: 字面意思,就是小时候写的流水账的日记一搬,我今天要吃早餐,就要先起床,下地,煎鸡蛋,等等,初入编程的我们基本都是这样的。
  • 面向对象编程:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系。到了公司多写几个需求,多封装几个小组件,便有了这一层感悟。
  • 函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
    程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
    x -> f(联系、映射) -> y,y=f(x)
    函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x和y的关系
    相同的输入始终要得到相同的输出(纯函数)
    函数式编程用来描述数据(函数)之间的映射

上面这几行天花乱坠的一直在表达这个意思:大白话:函数式编程就是你高中数学的学的那个函数的意思,当输入确定了,输出也会确定。
(定义虽绕,但是他是十分严谨科学的,要多读)

比如来看这么一个简单例子,实现一个两个数相加

// 非函数式 
let num1 = 2 
let num2 = 3
let 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) 
})

你看,这就是对循环的抽象,不需要关心循环具体实现的细节了,代码又简洁,又灵活。

常用高阶函数

  • forEach
  • map
  • filter
  • every
  • some
  • find ...
//我们再挑一个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基本工资10000
let 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里去


一目了然

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342