Meteor开发平台入门 互动版

函数反应式编程 - FRP

函数反应式编程/Functional Reactive Programming是指采用函数式编程/FP来实现 反应式计算。函数式编程是又一个相当有品位的术语,我们现在不需要过多深入,先记住, 函数式编程是以函数为核心的编程模式,它总是试图使用函数来实现一个具体的功能。

≡ 使用函数实现处理单元

如果你和我一样,曾经念过初中,相信你还记得数学中的函数 - 从自变量到因变量的映射

frp

没错,我们可以把处理单元看做一个函数,自变量是输入(A和B),因变量是输出(C)。使用 JavaScript的函数来实现数学中的函数很简单,JavaScript函数的参数就是自变量/输入,返回值 就是因变量/输出:

var processor = function(A,B){
  return A + B;
}

任何时刻,我们向这个函数传入输入数据,就可以获得输出数据。现在,需要一种机制检测输入数据的变化,这样,就可以在输入变化时触发对处理单元的调用了。

≡ 输入数据变化检测

JavaScript没有内置的对任意变量进行变化检测的支持,所以我们需要跳出来思考一下。

如果,我们能够在其他代码/Mutation Code试图修改A和B时,截获修改请求,是不是 就可以了:

fpr-intercept

首先我们需要定义一个新的类ReactiveData来封装对原始数据的修改接口。ReactiveData对象 的value属性保存原始数据,任何代码都需要通过其set()方法才能修改原始数据,在set()方法内, 我们调用依赖于这个数据的计算过程(为了便于理解,先假设只有一个计算过程依赖于这个数据):

var ReactiveData = function(initVal){
  this.value = initVal;
  this.dependent = null;
};
ReactiveData.prototype.set = function(newVal){
  //修改原始数据
  this.value = newVal;
  //触发执行依赖于此数据的计算过程
  this.denendent && this.dependent.call(null);
}

≡ 声明计算过程对数据的依赖

什么时候声明一个计算过程依赖于ReactiveData有多种途径。一种方法是在计算过程 读取数据的时候进行设定 —— 如果一个计算过程需要读取一个数据,那可以断定这个计算 过程依赖于这个数据了。现在我们为ReactiveData类增加get()接口,在get()方法内部, 插入我们的登记行为:

ReactiveData.prototype.get = function(){
  //登记依赖于此数据的计算过程
  this.dependent = computeContext;
  //返回原始数据
  return this.value;
}

你看到,为了避免给get()方法不符合人性地增加参数,我们使用一个全局变量 computeContext来表征当前的计算过程 —— 我们可以在启动一个反应式计算的时刻, 设定这个全局变量。现在让我们提供一个启动反应式计算的方法:

var ReactiveRun = function(compution){
  //设定当前计算过程
  computeContext = compution;
  //初次调用
  compution.call(null);
}

≡ 测试我们的反应式计算小框架

基本框架已经具备,现在我们可以定义两个ReactiveData对象ra和rb,以及依赖于ra和rb的处理单元processor:

var ra = new ReactiveData(0),
    rb = new ReactiveData(0),
    processor = function(a,b){return a.get() + b.get();};

可以启动反应式计算了,在计算过程compution中,我们对输入变量ra和rb执行processor处理, 并打印计算结果:

var compution = function(){
  var c = processor(ra,rb);
  console.log(c);
};
ReactiveRun(compution);

一旦反应式计算过程启动,那么对变量ra和rb的修改将自动触发重新计算。现在, 我们编写一段测试代码来不断的修改ra和rb的值:

//mutation code
SetInterval(function(){
  var va = Math.random()*1000,
      vb = Math.random()*1000;
  a.set(va);
  b.set(vb);
},1000)

执行以下命令复位test应用、删除源文件:

~/test$ meteor reset↵ ~/test$ rm -rf \*↵

执行以下命令拷贝frp示例代码,运行并查看运行结果:

~/test$ cp ~/demos/frp/* .↵ ~/test$ meteor↵

阅读frp示例代码,理解反应式计算的实现原理