JS函数式编程【译】4.3 基本上函数式的编程

基本上函数式的编程

一个没有副作用的程序是什么样?那是一个什么都做不了的程序。

用包含了不可避免的副作用的函数式代码进行编程可以叫做“基本上函数式的编程(Mostly functional programming)”。 最好的实践是:在同一代码基上运用多种范例并且利用其长处。基本上函数式的编程是如何用纯的、 传统的函数式编程建模:将尽可能多的逻辑放在纯函数中,接口用命令式代码。

这也是我们将要写的一个小应用的方式。

在这个例子里,我们有个老板,他让我们为公司写个web应用来追踪员工的状态。在这个虚拟的公司里所有的员工只有一个工作: 使用我们的网站。员工在开始工作时会签入,离开时会签出。但是这还不够,它还需要在内容有变化时自动更新, 这样我们的老板就不用不停刷新页面。

我们将用Lazy.js作为我们的函数式库。并且我们也要懒一点:我们不去考虑如何处理用户的登陆注销、 WebSocket、数据库等等,而是假设已经有一个通用的应用对象已经为我们提供了这些完美的API。

现在,我们先搞定丑陋的部分,让它们别碍事儿。这些部分是连接和创建副作用的。

function Receptor(name, available) {
  this.name = name;
  this.available = available; // mutable state
  this.render = function() {
    output = '';
    output += this.available ?
      this.name + ' is available' :
      this.name + ' is not available';
    output += '';
    return output;
  }
}
var me = new Receptor;
var receptors = app.getReceptors().push(me);
app.container.innerHTML = receptors.map(function(r) {
  return r.render();
}).join('');

对于显示状态列表这就足够了,不过我们想让它是响应式的,这是我们的第一个障碍。

Lazy.js库会把这些对象存在一个序列里,而不会立刻计算它们的值,直到调用toArray()方法。 这样我们就可以利用其惰性的优点来写出有那么点函数式响应式的程序。

var lazyReceptors = Lazy(receptors).map(function(r) {
  return r.render();
});
app.container.innerHTML = lazyReceptors.toArray().join('');

由于Receptor.render()方法返回新的HTML而不是修改现有的HTML,所以只需要把innerHTML参数设置为它的输出。

我们还不得不相信我们的用户管理通用程序还会为我们提供可用的回调方法。

app.onUserLogin = function() {
  this.available = true;
  app.container.innerHTML = lazyReceptors.toArray().join('');
};
app.onUserLogout = function() {
  this.available = false;
  app.container.innerHTML = lazyReceptors.toArray().join('');
};

这样,任何用户登录或者注销的时候,lazyReceptors将会被重新计算,打印出状态列表最新的值。

处理事件

如果应用没有在用户登录注销时提供回调怎么办?回调往往比较凌乱,会很快让程序变成意面代码。 作为替代,我们可以通过直接观察用户来搞定。如果用户聚焦于网页,那么他/她肯定是活跃并可用的。 我们可以利用JavaScript的focus和blur事件。

window.addEventListener('focus', function(event) {
  me.available = true;
  app.setReceptor(me.name, me.available); // just go with it
  container.innerHTML = lazyReceptors.toArray().join('');
});
window.addEventListener('blur', function(event) {
  me.available = false;
  app.setReceptor(me.name, me.available);
  container.innerHTML = lazyReceptors.toArray().join('');
});

等等,难道事件不能使响应式的吗?它们可以惰性计算吗?在Lazy.js里是可以的,而且有个好用的方法。

var focusedReceptors = Lazy.events(window,
  "focus").each(function(e) {
  me.available = true;
  app.setReceptor(me.name, me.available);
  container.innerHTML = lazyReceptors.toArray().join('');
});
var blurredReceptors = Lazy.events(window,
  "blur").each(function(e) {
  me.available = false;
  app.setReceptor(me.name, me.available);
  container.innerHTML = lazyReceptors.toArray().join('');});

很简单吧。

通过使用Lazy.js库处理事件,我们可以为事件创建一个无限序列。每次事件触发的时候, Lazy.each()函数会再遍历一次。
很惭愧,我真没理解Lazy的事件处理方法有什么优势,而且上面的那个提示也没看懂😳
我去找Lazy.js的API, 但跟本书说的这个不太像,不知道是版本不一样还是完全不是一个库,反正是没有events方法。

到目前为止我们的老板还算喜欢这个应用,不过她指出,如果一个员工在一天离开前没有关闭网页而注销, 那么应用仍会说这个员工是可用状态。

为了查明一个员工在网站上是否是活跃的,我们可以监视键盘和鼠标事件,我们认为30分钟没有操作就是不可用状态。

var timeout = null;
var inputs = Lazy.events(window, "mousemove").each(function(e) {
  me.available = true;
  container.innerHTML = lazyReceptors.toArray().join('');
  clearTimeout(timeout);
  timeout = setTimeout(function() {
    me.available = false;
    container.innerHTML = lazyReceptors.toArray().join('');
  }, 1800000); // 30 minutes
});

Lazy.js库让我们把事件作为可以映射的无限流非常容易。这之所以可能是因为它用了函数组合来控制执行顺序。

但是还有个小问题,如果没有我们可利用的事件,而都是属性值的改变该怎么办?下一节我们会深入研究这个问题。

posted @ 2015-08-24 09:21  tolg  阅读(1797)  评论(0编辑  收藏  举报