一个轻量级的JavaScript库:Reactor.js
2013-05-23 21:42 阅读(198)

Reactor是一个针对reactive programming的轻量级JavaScript库,它提供了reative变量,这些变量会根据需求进行自动更新。

下面是一个简单的例子:

var foo = Signal(1);
var bar = Signal(function(){
  return foo() + 1;
});
var moo = Observer(function(){
  console.log(bar());
});
 
foo();
// 1
bar();
// 2 
foo(6);
// console logs 7
 
foo();
// 6
bar();
// 7

通过上面的例子,可以看出Reactor具有以下三大特点:

概述

Reactor由两大组件组成:Signals和Observers。

一个signal需要依赖于其它signal中的数值来确定自己的值。同样,一个observer函数也是依赖signal来确定下一步要做的事情。

当一个signal更新时,它会自动更新所有相关的signals和observers。这时,在整个应用程序中,signals和observers形成一个传递曲线图。signals是传递曲线图的内在因素,而observers则是外在因素。

下面是一个使用Reactor的例子。

// The model is just an array of strings wrapped in a Signal

noteList = Signal(["sample note", "another sample note"]);
 
// The code to display the notes is wrapped in an Observer

// This code is automatically retriggered when the noteList is modified

Observer(function(){
  var noteListElement = document.getElementById("noteList");
  noteListElement.innerHTML = '';
 
  
// "noteList().length" causes a read of noteList

  
// This automatically sets noteList as a dependency of this Observer

  
// When noteList is updated it automatically retriggers the whole code block

  for (var i = 0; i < noteList().length; i++) {
    var noteElement = document.createElement("div");
    noteElement.textContent = noteList()[i];
    noteListElement.appendChild(noteElement);
  }
 
});
 
// The input only needs to modify the Signal

// The UI update is automatically handled by the Observer

var noteInputElement = document.getElementById("noteInput");
noteInputElement.onkeydown = function(event){
  if (event.keyCode === 13) {
    noteList.push(noteInputElement.value);
    noteInputElement.value = '';
  }
};

Reactor中使用Signals和Observer函数只需很小的内存,就能轻易的操作相应的变量、代码块、交换读取和调用响应的函数。

与其它的库比较

正如Bacon.jsKnockout.js一样,Reactor库也是基于相同reactive的原则。不同之处是Reactor始终保持着轻量级,后添加的语法和模板都保持在最小状态。Reactor库还设置了自动更新功能,这就不需要专门设置监听器(反馈系统)。

Signals

Signal是个依赖于其它Signals中的数值。

Reactor提供了一个称为Signal的全局函数。提供一个数值作为返回Signal对象。

var foo = Signal(7); 
实现Signal 对象函数功能。读取signal中的值,这个没有任何形参。
foo();
// returns 7
改变Signal中的数值,传递一个新的参数。
foo(9);
// sets the signal's value to 9
Signal可以取任何类型的值,如:数值型、字符串型、布尔型、数组型、对象型。
foo(2.39232);
foo("cheese cakes");
foo(true);
foo(["x", "y", "z"]);
foo({"moo": bar});
如果提供Signal的是函数,则返回函数的值来替代原来的函数
var foo = Signal(function(){
  return 2 * 3;
});
 
foo();
// returns 6 instead of the function
Signals自身的数值可依赖于其他的Signals的函数调用。如果不同的Signal中的数值读取同一给定的函数,那么这些Signal将自动的被设置为依赖关系。这就意味着依赖关系更新,那么被依赖Signals的数值也会更新。
var foo = Signal(7);
var bar = Signal(function(){
  return foo() * foo();
// since foo is read here,

                        
// is is registered as a dependency of bar

});
 
foo();
// returns 7 as expected

bar();
// returns 49 since it is defined as foo() * foo()

 
foo(10);
// this updates the value of foo

         
// and the value of bar as well

 
bar();
// returns 100 since it was automatically updated together with foo

值得注意的是,这里没有声明任何的监听器或者追踪绑定。Reactor能自动的找到具有依赖关系的signal函数定义。

自动更新使signals链接在一起形成更多复杂依赖关系的曲线图。

var firstName = Signal("Gob");
var lastName = Signal("Bluth");
 
// fullName depends on both firstName and lastName

var fullName = Signal(function(){ 
  return firstName() + " " + lastName();
});
 
// barbarianName depends only on firstName

var barbarianName = Signal(function(){
  return firstName() + " the Chicken"
});
 
// comicTitle depends on barbrianName and fullName and therefore

// indirectly depending on firstName and lastName

var comicTitle = Signal(function(){
  return "He who was once " + fullName() + " is now " + barbarianName();
});
 
firstName();
// "Gob"

lastName();
// "Bluth"

fullName();
// "Gob Bluth"

barbarianName();
// "Gob the Chicken"

comicTitle();
// "He who was once Gob Bluth is now Gob the Chicken"

 
firstName("Michael");
// updating firstname automatically updates

                      
// fullName, barbarianName, and comicTitle

 
firstName();
// "Michael"

lastName();
// "Bluth"

fullName();
// "Michael Bluth"

barbarianName();
// "Michael the Chicken"

comicTitle();
// "He who was once Michael Bluth is now Michael the Chicken"

signals应尽可能的设置为只读,这样可避免任何外界干扰,否则将出现下列问题:

在复杂的曲线图中,尤其是函数传递结束之前,数值的改变极可能引起级联反应,导致一些依赖于signals的定义不停被中断。

上述的例子中,更新fistName会引起fullName和barbarianName两个函数同时更新。这将导致comicTitle被更新两次。

然而,如果comcTitle定义允许写磁盘,将会引发一些问题。因为comicTitle更新两次,将进行两次不同的写磁盘。在没有完成函数传递结束之前,第一次写磁盘会发生错误。

observers函数可以解决上述问题。

Observers

Observers与Signals比起来,主要有3方面的差别:

observers是用于外部响应的,而signals适用于内部状态。在所有signals没有更新完前,signal可能进行多次追踪。如果signals用于外界响应,引起的触发是错误的、冗余的。observers则在所有函数调用后才追踪,并且仅更新一次。这样就不会出现上述问题。
observers的创建与signals的途径是相同的:

var foo = Signal("random string");
var bar = Observer(function(){ 
// Triggers first on creation

  alert(foo());                
// Reads from foo and alerts "random string"

});                            
// Is automatically registered as a dependent

                                
// and triggers again whenever foo is changed
如同Signals,当相应的Signals更新后,具有依赖关系的observers也进行自动计算和触发的。
foo("a new random string");
// triggers bar which

                            
// alerts "a new random string"
如同Signals,Observer函数功能也被更新。
// change bar update the html instead of alerting

// triggers once immediately after updating

bar(function(){
  fooElement = document.getElementById("foo");
  fooElement.textContent = foo();
});
 
foo("this string will be logged now");
// triggers bar which now

                                       
// logs the string instead
不允许observer传递空值。
bar(null);
// disables the observer

Working with Arrays and Objects

当更新arrays和objects时,可使用Reactor中的convenience方法直接代替更新objects。可以这样使用,如下所示:

如果signal拥有自己的array的值,直接更新该array将不更新有关signal的依赖。这是因为signal对象仍然是自己,经检测没有发生任何变化。反而,使用提供convenience方法更新array,则会检测出有变化。

// foo initialized as a signal with an array as its value

var foo = Signal(["a", "b", "c"]);
 
// bar initialized as a signal whos value depends on foo

var bar = Signal(function(){
  return foo().join("-");
});
 
foo();
// ["a","b","c"]

bar();
// "a-b-c"

 
// Updating foo's array directly does not trigger an update of bar

foo().push("d");
foo();
// ["a","b","c","d"]

bar();
// "a-b-c"

 
// Instead, updating using the convenience method does trigger the update of bar

foo.push("e");
foo();
// ["a","b","c","d","e"]

bar();
// "a-b-c-d-e"
总 结

var stringSignal = Signal("a string");   
// Signals can be set to any value

var booleanSignal = Signal(true);
var numberSignal = Signal(1);
 
var dependentSignal = Signal(function(){ 
// If given a function, the signal's value

                                          
// is the return value of the function

                                          
// instead of the function itself

 
  return numberSignal() + 1;             
// Reading from another signal automatically sets it

                                          
// as a dependency

});                    
 
var stringSignal("a new string value");  
// To update a signal just pass it a new value

                                          
// this automatically updates all

                                          
// its dependents as well

 
var arraySignal = Signal([               
// Signals can even be arrays or objects

  stringSignal,                          
// which contain other signals

  booleanSignal,
  numberSignal
]);
 
var alertObserver = Observer(function(){ 
// Observers are just like signals except:

  alert(arraySignal().join(","));        
// They are updated last

});                                      
// They are only updated once per propagation

                                          
// They cannot be depended on by signals

 
arraySignal.set(4, "a new value!")       
// Convenience method for setting properties

                                          
// on an Array or Object Signal

 
arraySignal.push("foo");                 
// Convenience methods for updating an array Signal

arraySignal.pop();
arraySignal.unshift("bar");
arraySignal.shift();
arraySignal.reverse();
arraySignal.sort();
arraySignal.splice(1, 2, "not a signal");

如果你喜欢使用CoffeeScript,Reactor变得更为简单!

文章来源:Reactor.js  由 CSDN整理