资讯专栏INFORMATION COLUMN

JavaScript 函数式真正的浅析

fox_soyoung / 2138人阅读

摘要:入门的导语废话最近两年你要说函数式编程不火的话那是不可能的是人都知道函数式编程很火为什么函数式编程会火呢在于它的思想很强大很强势尤其是前端的更是在上完全使用纯函数函数式的好处渐渐被发掘出来笔者最近看了一些函数式方面的东东现在发出来给大家学习

0x00 入门的导语(废话)

最近两年你要说函数式编程不火的话, 那是不可能的, 是人都知道函数式编程很火.为什么函数式编程会火呢, 在于它的思想, 很强大, 很强势!尤其是前端的redux更是在reducer上完全使用纯函数, 函数式的好处渐渐被发掘出来, 笔者最近看了一些函数式方面的东东, 现在发出来给大家学习学习, 顺便我也学习学习怎么写文章... :P

常用的函数式库:

ramda 设计很棒的一个库

lodash 比较常用的一个库

underscore 应该也不错的一个库

0x01 纯函数

定义: 相同输入一定得到相同输出且运行过程中不修改,不读取外部环境的变量的函数

说出来肯定不好理解, 还是要看看代码. 就好像你不看国足比赛永远不知道国足为什么会输给月薪几百块的叙利亚.

// Array.slice 对于固定输入一定是固定输出, 且不依赖外部变量, 啥? 依赖了arr变量吗?
// 其实这种写法和Array.prototype.slice(arr, 0, 3); 是一样的. 这样就理解了,
// 你还学到一个东西 Array.slice是不会修改原数组滴!
var arr = [1,2,3,4,5];
arr.slice(0,3);

 // Array.splice 会修改xs, 所以是不纯的, 所以相同的输入不会有相同的输出!
var xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []

纯函数的好处: 不会去修改外部变量就不会产生线程安全问题.可以极大的减少系统复杂程度

0x02 函数的柯里化

看! 代码!

// 调用 doWht("我", "家里", "饭");
let doWhat = (who, where, what) => {
  return who + "在" + where + "做" + what
}

// 柯里化后的等价效果
// 调用 doWhat("我")("家里")("饭")
let doWhat = who => where => what => {
  return who + "在" + where + "做" + what
}

// 假设现在知道是"我"在"家", 至于做什么是不知道的
// tmp函数就已经帮我们保存了值, 这样是非常灵活的.
let doWhatCurry = doWhat("我")("家里")

上面提到的库里都有一个叫curry的函数会将一个普通的函数柯里化.

0x03 函数的组合

函数组合是将函数组合在一起, 生成一个新的函数

// h(g(f(x))) 这是以前调用函数的方式
var add1 = x => x + 1
var mul5 = x => x * 5
// compose会生成一个新的函数, 接收的参数全部传给add1, 然后add1的返回值传给mul5(注意注意!, mul5的参数个数只能有一个!!!), 然后compose生成的新的函数的返回值就是mul5的返回值.
compose(mul5, add1)(2)

函数组合非常强大, 能够通过组合的方式来生成新的函数, 这是非常爽的. 如果你运用灵活, 会极大的减少你的代码量(如果不能减少别喷我啊), compose的实现在上面提到的三个库中都有实现.

0x04 声明式与命令式风格

命令式的风格让我们通过代码引导机器, 让机器一步一步完成我们要的任务; 而声明式则是直接告诉机器我要做啥, 更直观.

//命令式
var persons = [...]
for (var i = 0; persons.length; ++i) {
  persons[i] = persons[i].toUppercase()
}

//声明式
var persons = [...]
persons.map(person => person.toUppercase())
0x05 Point free风格
// 假定如果 
let map = fn => list => list.map(fn);
let add = (a, b) => a + b;

// 函数incrementAll不是point free 风格
// 因为这里提到了numbers参数, 需要给出一个命名.
// 这样定义函数会导致我们需要多命名一个变量. 麻烦!
let incrementAll = (numbers) => map(add(1))(numbers);

// Point free风格的定义方法
// 假设add被柯里化过了
let incrementAll = map(add(1))

现在是推荐使用point free风格的代码(定义函数时), 这会减少我们不必要的命名. 多用这种风格哦!

0x06 容器(Functor)

容器代表了一个值, 一个任意值. 他就好像是函数式编程里的变量,函数的一个铠甲.可以让你的变量,函数在工程的战场中所向披靡!

var Container = function(x) {
  this.__value = x;
}

Container.of = x => new Container(x);

Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}

Container.of(3).map(x => x+1).map(x => x*5)
// of用来构建容器, map用来变换容器
// Functor可以做很多很多事情, 具体的? 往下介绍.
// Maybe就是在普通容器上新增了一个检查空值的行为. 
var Maybe = function(x) {
  this.__value = x;
}

Maybe.of = function(x) {
  return new Maybe(x);
}

Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}

// 例子, 如果name是空的话就会输出空了
var functor = Maybe.of({name: ‘mrcode"})
functor
    .map(value => value.age)
    .map(String.prototype.upperCase)
    .map(value => console.log(value))

这个Maybe到底有啥用呢? 就是空值检测, 看上面的例子, 如果不进行判空的话, 第二个map就会调用String.prototype.upperCase函数, 会抛出异常的, 怕了吧? :P, 而且, 现在很多语言,swift等都添加了类似的支持. optional

Maybe只能判空, 但是Either才是真正的处理错误的容器, Either有两个子类, Left和Right.

// Promise是通过catch方法来接收错误的 如:
doSomething()
    .then(async1)
    .then(async2)
    .catch(e => console.log(e));

// 完全一样    
var Left = function(x) {
  this.__value = x;
}
var Right = function(x) {
  this.__value = x;
}

// 完全一样
Left.of = function(x) {
  return new Left(x);
}
Right.of = function(x) {
  return new Right(x);
}

// 这里不同!!!
Left.prototype.map = function(f) {
  return this;
}
Right.prototype.map = function(f) {
  return Right.of(f(this.__value));
}

// 应用:
var getAge = user => user.age ? Right.of(user.age) : Left.of("ERROR!")
getAge({name: "stark", age: "21"}).map(age => "Age is " + age);
//=> Right("Age is 21")

getAge({name: "stark"}).map(age => "Age is " + age);
//=> Left("ERROR!")

Left会跳过所有执行过程, 直达结果, 这就好像Right是流程图里一个又一个指向下一个任务的箭头, 而Left直接指向了结果, 是错误的结果.

0x07 IO

诶, 函数式编程里, 涉及到IO总是让人尴尬的, 蓝瘦的很..幸好, 有一种叫做IO的东西专门处理IO这种东西(别嫌绕哈), 看代码,

// 没毛病
var IO = function(f) {
    this.__value = f;
}

// ??? 看不懂, 待会解释..
IO.of = x => new IO(_ => x);

// ??? 这是啥子鬼????
IO.prototype.map = function(f) {
    return new IO(compose(f, this.__value))
};

权威解答: 这里的IO里存的是一个函数, 包裹了外部环境变量的函数, 我们传入了一个函数, 这个函数里包含了实际的值,会进行IO操作. 我们把不纯的IO操作放到了这个函数里, 总体上看, 我们的IO对象, 是不会执行这些不纯的操作的. 它依然是纯的, 因为IO操作压根就没执行内部包含的函数, 这个函数是外部调用者去执行的. 也就是说, 不纯的操作是外部的人干的, 和我们的IO对象一丢丢关系都木有!(干得漂亮!) 看一个例子.

var io_document = new IO(_ => window.document);
io_document.map(function(doc){ return doc.title });
// 得到IO(documen.title)

科普: 这里你没有得到document.title, 你得到的仅仅是一个会返回document.title的一个函数, 这个函数是不纯的, 但是执行不是由上面的代码执行的, 锅在调用函数的人身上! 上面的代码依然是"纯"的!

0x08 Monad

看这个部分的时候建议看一下IO的实现, 好好理解一下, 我知道有点烧脑, 但是看一下没坏处!玩过Promise的都知道, Promise.then传进去的函数可以返回一个新的Promise. Promise就是Monad.

0x09 函数式编程的应用 react中的纯组件
// 固定的输入得到固定的输出 纯组件极大的增加了react的灵活程度
// app 的状态交给一些状态机管理 比如redux
var Text = props => (
    
{props.text}
)
redux中的reducer
// 输入当前状态和action, 输出nowState
reducer(currentState, action) => newState
0x10 总结一下

确实是这样, 不总结的话就不像是一篇文章了, 还是总结下吧:

纯函数的概念以及函数柯里化和函数的组合

容器概念, Container和Maybe, Either的派生Left,Right, IO作用.

函数式编程的应用

参考文章

JavaScript函数式编程3

好了, 再见 古德曼~

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

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

相关文章

  • 从数组入手浅析Vue响应原理

    摘要:响应式原理为了探究这一切的原因,我再次点开了的官网。在官网很下面的位置,找到了关于响应式原理的说明。因此,新添加到数组中的对象中的属性,就成了非响应式的属性了,改变它自然不会让组件重新渲染。响应式属性的对象,有这个对象就代表是响应式的。   最近在用Vue开发一个后台管理的demo,有一个非常常规的需求。然而这个常规的需求中,包含了大量的知识点。有一个产品表格,用来显示不同产品的信息。...

    dkzwm 评论0 收藏0
  • SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数编程

    摘要:函数式编程,一看这个词,简直就是学院派的典范。所以这期周刊,我们就重点引入的函数式编程,浅入浅出,一窥函数式编程的思想,可能让你对编程语言的理解更加融会贯通一些。但从根本上来说,函数式编程就是关于如使用通用的可复用函数进行组合编程。 showImg(https://segmentfault.com/img/bVGQuc); 函数式编程(Functional Programming),一...

    csRyan 评论0 收藏0
  • JavaScript系列--浅析原型链与继承

    摘要:综上所述有原型链继承,构造函数继承经典继承,组合继承,寄生继承,寄生组合继承五种方法,寄生组合式继承,集寄生式继承和组合继承的优点于一身是实现基于类型继承的最有效方法。 一、前言 继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。 接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名...

    draveness 评论0 收藏0

发表评论

0条评论

fox_soyoung

|高级讲师

TA的文章

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