一个轻量级的JavaScript库:Reactor.js

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

下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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可以自动追踪程序的所有运行过程。
  • 无需为代码设置一个专门的框架,任何变量的值都能轻松的替换掉原先使用过的数值。
  • 用户不需要专门学习该语言的语法、类、对象或者方法。

概述

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

  • Singals用于改变超时的数据,且相互依赖。
  • Observers是功能函数,在signal发生改变时会触发。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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库还设置了自动更新功能,这就不需要专门设置监听器(反馈系统)。

  • 与Knockout库比起来,Reactor库无需提供语义绑定可直接使用HTML,用户设置合适的HTML供Observers函数调用。
  • 与Bacon库比起来,Reactor库无需帮助事件处理流程。

Signals

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

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

1
var  foo = Signal(7); 

实现Signal 对象函数功能。读取signal中的值,这个没有任何形参。

1
foo(); // returns 7

改变Signal中的数值,传递一个新的参数。

1
foo(9); // sets the signal's value to 9

Signal可以取任何类型的值,如:数值型、字符串型、布尔型、数组型、对象型。

1
2
3
4
5
foo(2.39232);
foo( "cheese cakes" );
foo( true );
foo([ "x" "y" "z" ]);
foo({ "moo" : bar});

如果提供Signal的是函数,则返回函数的值来替代原来的函数。

1
2
3
4
5
var  foo = Signal( function (){
   return  2 * 3;
});
 
foo(); // returns 6 instead of the function

Signals自身的数值可依赖于其他的Signals的函数调用。如果不同的Signal中的数值读取同一给定的函数,那么这些Signal将自动的被设置为依赖关系。这就意味着依赖关系更新,那么被依赖Signals的数值也会更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
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链接在一起形成更多复杂依赖关系的曲线图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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应尽可能的设置为只读,这样可避免任何外界干扰,否则将出现下列问题:

  • 修改HTML
  • 写盘
  • 交互式登陆
  • 触发警告

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

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

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

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

Observers

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

  • 在所有的signals更新完成后,才会触发observers
  • signal每更新一次,observers只进行一次触发
  • observers之间不存在相互依赖问题

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

1
2
3
4
5
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也进行自动计算和触发的。

1
2
foo( "a new random string" ); // triggers bar which
                             // alerts "a new random string"

如同Signals,Observer函数功能也被更新。

1
2
3
4
5
6
7
8
9
// 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传递空值。

1
bar( null ); // disables the observer

Working with Arrays and Objects

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

  • foo.set(key,value) instead of foo()[key] = value
  • foo.pop() instead of foo().pop()
  • foo.push(value) instead of foo().push(value)
  • foo.reverse() instead of foo().reverse()
  • foo.shift() instead of foo().shift()
  • foo.unshift(value) instead of foo().unshift(value)
  • foo.sort(comparison) instead of foo().sort(comparison)
  • foo.splice(start, length) instead of foo().splice(start, length)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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"

总 结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值