下面是一个简单的例子:
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.js和Knockout.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 offoo()[key] = value
foo.pop()
instead offoo().pop()
foo.push(value)
instead offoo().push(value)
foo.reverse()
instead offoo().reverse()
foo.shift()
instead offoo().shift()
foo.unshift(value)
instead offoo().unshift(value)
foo.sort(comparison)
instead offoo().sort(comparison)
foo.splice(start, length)
instead offoo().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