资讯专栏INFORMATION COLUMN

JavaScript 函数式编程到底是个啥

denson / 1994人阅读

摘要:函数是一等公民。其实闭包本身也是函数式编程的一个应用。劣势不能算是严格意义上的函数式语言,很多函数式编程的特性并没有。

随着大前端时代的到来,在产品开发过程中,前端所占业务比重越来越大、交互越来越重。传统的老夫拿起JQuery就是一把梭应付当下重交互页面已经十分乏力。于是乎有了Angular,React,Vue这些现代框架。

但随之而来的还有大量的新知识新名词,如MVC,MVVM,Flux这些设计模式就弄得很多同学傻傻分不清。这时候又见到别人讨论什么函数式编程,更是一脸懵逼了。

我们大多听过面向对象编程,面向过程编程,那啥又是函数式编程呢?在我们前端开发中又有哪些应用场景?我抱着这个疑惑,初步的学习了下。(此文仅是学习,无甚干货)。

函数式编程 定义

函数式编程(Functional Programming,后面简称FP),维基百科的定义是:

是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

我来尝试理解下这个定义,好像就是说,在敲代码的时候,我要把过程逻辑写成函数,定义好输入参数,只关心它的输出结果。而且可以把函数作为输入输出。感觉好像平常写js时,就是这样的嘛!

特性

网上FP的定义与特性琳琅满目。各种百科、博客、一些老师的网站上都有大同小异的介绍。为了方便阅读,我列下几个好像比较重要的特性,并附上我的第一眼理解。

函数是一等公民。就是说函数可以跟其他变量一样,可以作为其他函数的输入输出。喔,回调函数就是典型应用。

不可变量。就是说,不能用var跟let咯。按这要求,我似乎有点难写代码。

纯函数。就是没有副作用的函数。这个好理解,就是不修改函数外部的变量。

引用透明。这个也好理解,就是说同样的输入,必定是同样的输出。函数内部不依赖外部状态,如一些全局变量。

惰性计算。大意就是:一个表达式绑定的变量,不是声明的时候就计算出来,而是真正用到它的时候才去计算。

还有一些衍生的特性,如柯里化与组合,三言两语说不清,就不阐述了,有兴趣的同学可以自己再了解了解。

FP在JavaScript中的应用

React就是典型的FP。它不同于Vue这样的MVVM框架,它仅仅是个View层。
ReactView = render(data) 它只关心你的输入,最终给你返回相应视图。所以你休想在react组件中去修改父组件的状态,更没有与dom的双向绑定。

这个是框架上的应用,那么在我们平常书写JavaScript时有哪些应用呢?换句话说,平常书写js时候,遇到什么情况,我们采用FP会更好。

从最常见的入手吧,如典型的操作数组:

// 从users中筛选出年龄大于15岁的人的名字
const users = [
  {
    age: 10,
    name: "张三",
  }, {
    age: 20,
    name: "李四"
  }, {
    age: 30,
    name: "王五"
  }
];

// 过程式
const names = [];
for (let i = 0; i < users.length; i++) {
  if (users[i].age > 15) {
    names.push(users[i].name);
  }
}
// 函数式
const names = users.filter(u => u.age > 15).map(u => u.name);

嗯,代码精简了很多,但是貌似带来了更大的开销。如果是非常大的数据,非常多的筛选工作,那就会循环多次。

这里得想到刚刚的惰性计算。按照惰性求值的要求,应该是要最后返回结果时,才真正去筛选年纪并得到姓名数组。

然而JavaScript的数组并不支持惰性求值。这时候我们得上一些工具库,如Lodash。可以看下它文档中的例子:_.chain。

好像也没好到哪里去啊,不就是把多行代码变一行嘛?说的那么玄乎,还多了性能开销,然后又跟我说得上个工具库。。。

说的好像很有道理,但是for循环是有个弊端的,它产生了变量i,而这个变量又是不可控的,如果业务逻辑一复杂,谁知道它循环到什么时候i有没有发生变化,然后导致循环出问题呢?

我们再看一个与DOM交互的场景:
假如页面有一个按钮button,我们需要求出用户点击了几次,但是一秒钟内重复点击的不算。传统方法会这么写。

var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector("button");
button.addEventListener("click", () => {
  if (Date.now() - lastClick >= rate) {
    console.log(`Clicked ${++count} times`);
    lastClick = Date.now();
  }
});

妥,完全没问题。但是发现多了很多状态,count,rate,lastClick,还得对比来对比去。那如果用FP会是怎么样的呢?

抱歉。。。没法写。。。除非很强大的编程能力,自己封装好方法去处理。所以在这里,我们可以上个工具---Rx.js,上述的例子就是rxjs中引用的,我们看它是如何优雅地处理的。

var button = document.querySelector("button");
Rx.Observable.fromEvent(button, "click")
  .throttleTime(1000) // 每隔1000毫秒才能触发事件
  .scan(count => count + 1, 0) // 求值,默认值是0
  .subscribe(count => console.log(`Clicked ${count} times`)); // 订阅结果、输出值

巧夺天工!再也不用去管理状态了,不需要声明一堆变量,修改来修改去,判断来判断去,简直完美。

平常我们有很多需要更新dom的异步操作,如搜索行为:用户连续输入查询值,如果停顿半秒就执行搜索,如果搜索了多次,发起了多次请求,那只返回最终输入的那次搜索结果。

闭上眼想想,你之前是怎么实现的。反正我都是设置开始时间,结束时间,上次时间,等等变量。繁琐,而且不可控。

当我们以FP的思想去实现时,就会想方设法的减少变量,来优雅程序。最常见的方法就是用下别人的工具库来实现它。当然有些简单的场景也可以自己实现,最主要的还是要有这个意识。

其实我们平常已经写了一些FP了,只是我们没意识到,或者没怎么写好。就好比闭包,很多人都不了解闭包的概念,但实际上已经写了很多闭包代码。其实闭包本身也是函数式编程的一个应用。

鉴于我自己理解也不深,没法多阐述FP的应用,大家如果有兴趣,可以多了解了解。

FP在JavaScript中的优劣势

总结一下FP的优劣,以便于我们在实际开发中,能更好的抉择是否采用FP。

优势

更好的管理状态。因为它的宗旨是无状态,或者说更少的状态。而平常DOM的开发中,因为DOM的视觉呈现依托于状态变化,所以不可避免的产生了非常多的状态,而且不同组件可能还相互依赖。以FP来编程,能最大化的减少这些未知、优化代码、减少出错情况。

更简单的复用。极端的FP代码应该是每一行代码都是一个函数,当然我们不需要这么极端。我们尽量的把过程逻辑以更纯的函数来实现,固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响。

更优雅的组合。往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。参考上面第二点,更强的复用性,带来更强大的组合性。

隐性好处。减少代码量,提高维护性。

劣势

JavaScript不能算是严格意义上的函数式语言,很多函数式编程的特性并没有。比如上文说的数组的惰性链求值。为了实现它就得上工具库,或者自己封装实现,提高了代码编写成本。

跟过程式相比,它并没有提高性能。有些地方,如果强制用FP去写,由于没有中间变量,还可能会降低性能。

代码不易读。这个因人而异,因码而已。特别熟悉FP的人可能会觉得这段代码一目了然。而不熟悉的人,遇到写的晦涩的代码,看着一堆堆lambda演算跟匿名函数 () => () => () 瞬间就懵逼了。看懂代码,得脑子里先演算半小时。

学习成本高。一方面继承于上一点。另一方面,很多前端coder,就是因为相对不喜欢一些底层的抽象的编程语言,才来踏入前端坑,你现在又让他们一头扎入FP,显得手足无措。

总结

个人觉得,FP还是好的。对于开发而言,确确实实能优化我们的代码,熟悉之后,也能提高编程效率。对于编程本身而言,也能拓展我们的思维,不局限在过程式的编程代码。

在编写JS中,可以尽量的运用FP的思维,如不可变量、纯函数、惰性求值。但也不必教条式的遵循函数式编程,一定要怎样怎样。比如我们看下知乎大V某温的一个回答:传送门。

唉,做个页面仔不容易啊。但是不想当大牛的页面仔不是好页面仔!

参考

函数式编程入门教程-阮一峰

函数编程语言-维基百科

前端开发js函数式编程真实用途体现在哪里?-知乎答者

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83560.html

相关文章

  • JavaScript 函数编程到底个啥

    摘要:函数是一等公民。其实闭包本身也是函数式编程的一个应用。劣势不能算是严格意义上的函数式语言,很多函数式编程的特性并没有。 随着大前端时代的到来,在产品开发过程中,前端所占业务比重越来越大、交互越来越重。传统的老夫拿起JQuery就是一把梭应付当下重交互页面已经十分乏力。于是乎有了Angular,React,Vue这些现代框架。 但随之而来的还有大量的新知识新名词,如MVC,MVVM,Fl...

    Aomine 评论0 收藏0
  • 非科班如何理解闭包,原型,原型链,继承

    摘要:闭包,原型,原型链,继承对象若干属性的集合输出的集中类型标识,其中上面的四种属于简单的值类型,不是对象。在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢,特别是在循环中由于所有的对象的原型链都会找到,因此所有的对象都会有的方法。 闭包,原型,原型链,继承 对象——若干属性的集合 typeof输出的集中类型标识,其中上面的四种(undefined, number, strin...

    xuexiangjys 评论0 收藏0
  • 2017-06-22 前端日报

    摘要:前端日报精选新的长度单位你知道么高阶函数一点通的故事隔行扫描算法专题之数组去重全家桶每个人都能做的网易云音乐腾讯前端团队社区中文前端推荐第天听说你缺少一个顺手的图床知乎专栏译怎样创建定制表单组件碎语掘金函数式编程到底是个啥 2017-06-22 前端日报 精选 CSS 新的长度单位 fr 你知道么?高阶函数一点通png的故事:隔行扫描算法JavaScript专题之数组去重 · Issu...

    songze 评论0 收藏0
  • 一段代码,带你理解js执行上下文的工作流程

    摘要:由于函数被调用了,线程会从刚刚保存的变量中取出内容,去解析执行它。最后,当线程遇到离开上下文的标识,便离开上下文,并把的结果一并返回出去。 原文链接,欢迎关注我的博客 我相信很多前端初学者一开始都会被执行上下文这个概念弄晕,或者说似懂非懂。对于工作两年的我来说,说来实在惭愧,虽然知道它大概是什么,但总觉得没有一个更为清晰的认识(无法把它的工作过程描述清楚),因此最近特意温习了一遍,写下...

    developerworks 评论0 收藏0
  • JavaScript深入浅出

    摘要:理解的函数基础要搞好深入浅出原型使用原型模型,虽然这经常被当作缺点提及,但是只要善于运用,其实基于原型的继承模型比传统的类继承还要强大。中文指南基本操作指南二继续熟悉的几对方法,包括,,。商业转载请联系作者获得授权,非商业转载请注明出处。 怎样使用 this 因为本人属于伪前端,因此文中只看懂了 8 成左右,希望能够给大家带来帮助....(据说是阿里的前端妹子写的) this 的值到底...

    blair 评论0 收藏0

发表评论

0条评论

denson

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<