大概几个月前,受题叶影响开始关注函数式编程,在看完一本 js 的函数式之后,对函数式有了点基本的了解。

函数式给我的感觉,最大的一个主旨是是编程中所有过程可控,尤其在 js 这种没有原则的语言中,过程可控制有为重要。

函数式

到底什么是函数式,他和命令式编程和面向对象有什么区别。(知乎上已经有很多讨论了,感兴趣的话,我在结尾的地方贴了一些链接。)
总的来说,在函数式中,函数是一等公民,函数能作为变量的值,函数可以是另一个函数的参数,函数可以返回另一个函数等等。

函数式中有些个人感觉比较有意思的点

纯函数

最喜欢纯函数了,什么是纯函数呢?纯函数有三个重要的点:

  1. 函数的结果只受函数参数影响。
  2. 函数内部不使用能被外部函数影响的变量。
  3. 函数的结果不影响外部变量。

这有什么好处呢?当你有一堆函数对某个数据进行操作的时候,就能轻松定位到哪些函数出了问题,这也就是 Redux 的中心思想,控制状态的 Reducer 就是一个纯函数。

不可变

说到纯函数,不得不聊聊不可变。

FB 在推出 React 之后, immutable.js 也跟着火起来了。不可变,顾名思义,就是变量或者结构在定义之后不能再发生值的变动,所有操作只是产生新的值而不是去覆盖之前的变量。这样去控制数据,能够让数据流动更加可控。

在 jQuery 大行其道的代码中,最为人称赞的莫过于其链式操作了,但不知道有没有人跟我一样遇到过一些问题,比如我对一个节点进行操作,搞着搞着当前节点不是这个节点了,当我需要对这个节点再进行一次操作的时候,只能新开一个链式。

在函数中,流永远只是对当前的那个初始的数据进行操作,而且当加上惰性链,所有过程更加容易控制。

什么是惰性链?

回想一下 jq 的链式操作,是不是每次一个链接加上去就立马生效,当你写完一堆链式之后,发现不对,如果不能一眼看出是哪步错了,只能一次次去删最后的操作来找原因。而惰性链是在你写完链式时候并不会执行,而是在最后跟上一个执行用的函数,才会去执行前面的所有函数。

mixins

打个比方,当我们需要类型判断的时候,红皮书里一般都是各种 if,不知道平时写的时候也是这样,用个 utils 写各种类型判断的函数,然后挨个 if。

用 mixins 思路的话,大概可以这么去做:

dispach(
function(s){ return isString(s) ? s : undefined },
function(s){ return isArray(s) ? stringifyArray(s) : undefined },
function(s){ return isObject(s) ? stringfyObject(s) : undefined },
function(s){ return s.toString() }
)

这样只要有个函数返回的肯定是字符串了。

函数响应式编程

响应式编程(反应式编程)

响应式编程是一种面向数据流和变化传播的编程范式,数据更新是相关联的。比如很多时候,在写界面的时候,我们需要对事件做处理,伴随着前端事件的增多,对于事件的处理愈发需要更加方便的处理。

设想一下,平时在处理事件的时候,一单上了复杂度,比如输入的时候,需要停止输入的时候才进行,这个时候又只能输入长度大于2才进行事件,当还是之前的数据的话不进行事件,可以考虑一下这个情况下如何去写。

拿 RxJs 中的一端例子举例

var keyup = Rx.Observable.fromEvent($input, 'keyup')
      .map(function (e) {
        return e.target.value; 
      })
      .filter(function (text) {
        return text.length > 2; 
      })
      .debounce(750)
      .distinctUntilChanged(); 

是否十分简洁。

把函数范式里的一套思路和响应式编程合起来就是函数响应式编程。

不知道有没有发现其实 Promise 也是响应式编程的一种。

举个寒冬大大的代码,在一个按钮上绑定两个事件,一个是 5s 后触发,一个是用户点击。

function wait(duration){
    return new Promise(function(resolve, reject) {
        setTimeout(resolve,duration);
    })
}

function waitFor(element,event,useCapture){
    return new Promise(function(resolve, reject) {
        element.addEventListener(event,function listener(event){
            resolve(event)
            this.removeEventListener(event, listener, useCapture);
        },useCapture)
    })
}

var btn = document.getElementById('button');
Promise.race([wait(5000), waitFor(btn, click)]).then(function(){
    console.log('run!')
})

把两个事件绑定到按钮上,这两个事件函数同时也是 promise,这样只要有一个触发了就会执行,相比普通代码

btn.addEventListener(event, eventHandler, false);
setTimeout(eventHandler, 5000);
function eventHandler(){
  console.log('run!');
}

当需要增加更多事件组合的时候,更加容易拓展。

刚才的代码,用 Rx.js 实现会更加简洁

var btn = document.getElementById('button');
var logRun = Rx.Observable.fromEvent(btn, 'click')
             .merge(Rx.Observable.timer(3000))
             .subscribe(e => {
               console.log('run!');
               logRun.dispose(); // 如果是一次性的就移除observable
             });

先这样,若有不对的地方请告知,感谢~

14 条评论
严墨 · 10月22日

上面的在一个按钮上绑定两个事件,一个是 5s 后触发,一个是用户点击例子代码有问题。
Promise.race(iterable)-参数iterable应该是一个可迭代对象,例如 Array
所以正确的代码应该是这样子的

<button id="button">竞争</button>
function wait (duration) {
  console.log('wait!')
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, duration)
  })
}

function waitFor (element, event, useCapture) {
  console.log('waitFor!')
  return new Promise(function (resolve, reject) {
    element.addEventListener(event, function listener (event) {
      console.log(event, listener, useCapture)
      resolve(event)
      this.removeEventListener(event, listener, useCapture)
    }, useCapture)
  })
}

var btn = document.getElementById('button')
Promise.race([wait(5000), waitFor(btn, 'click')]).then(function () {
  console.log('run!')

+1 回复

公子 · 2015年08月30日

在 jQuery 大行其道的代码中,最为人成赞的莫过于其链式操作了

有错字

回复

leozdgao · 2015年08月30日

回复

AlanWei · 2015年08月30日

刚看了jquery那些描述就看不下去了,jQuery的链式调用可以使用end()方法返回调用链的上一次选择的集合。还有惰性链怎么就能解决你说的那个问题了?

回复

Fakefish 作者 · 2015年08月30日

好吧 我并不知道这个,我再想个其他的例子

回复

AlanWei · 2015年08月31日

我不清楚你是做什么开发的。我是做.Net开发的, 在.Net里,有一个LINQ技术, LINQ就是懒求值(比如EF), 其实懒求值真的很难Debug,因为直到最后一个调用链,才去执行求值,你F11跟进函数内部啥都不是。(个人观点

回复

Fakefish 作者 · 2015年08月31日

嗯好 我在这方面经验还不足

回复

AlanWei · 2015年08月31日

互相交流学习,我是个刚入门的菜鸟。

回复

tinkgu · 2015年12月30日

问一下, 讲js函数式的书哪本好?谢谢

回复

Fakefish 作者 · 2015年12月30日

我就看了 javascript函数式编程

回复

tinkgu · 2015年12月30日

谢谢, 上面那本gitbook也很不错, 回复好快呀, 受宠若惊

回复

mrcode · 2016年09月05日

这位兄台 看的真仔细 哈哈哈哈

回复

严墨 · 10月23日

看到了你改变了,

Promise.race([wait(5000), waitFor(btn, click)]).then(function(){
    console.log('run!')
})

虽然已经改为迭代对象了,但是waitFor(btn, click)这个click应该是一个字符串waitFor(btn, 'click')不然会报click不存在的错,个人意见就是把代码最好还是自己先实验一下,不然一般读者很难找到你的错误,与君共勉

回复

0

只是谈思路,代码也是裸写的伪代码嘛

Fakefish 作者 · 10月24日
载入中...
Fakefish Fakefish

4.1k 声望

发布于专栏

Fakefish

我说的都是错的,除非你证明我是对的。

48 人关注