说在前面:写js时候,当一个变量一旦发生变化,就自动执行相应的程序,而不用手动执行,js-signals可以很方便的解决这个问题。
一.js-signals简介
js-signals是用于在JavaScript中的软件组件之间定义和触发基于事件的消息的库,它是一个类似于Event Emitter / Dispatcher或Pub / Sub系统的事件/消息系统,主要区别在于每个事件类型都有自己的控制器功能,而不依赖于字符串来广播/订阅事件,它还具有通常在其他系统上不可用的一些额外功能。
本人因为项目需要,寻找关于js-signals的学习资料,发现都是英文的,所以自己写个学习小结,也希望帮助后来需要学习的同学们。后面第三部分用户指南,会详细介绍js-signals的具体用法。
github:https://github.com/millermedeiros/js-signals
example:http://www.javascriptoo.com/js-signals
官方使用详解:https://millermedeiros.github.io/js-signals/
官方documentation:https://millermedeiros.github.io/js-signals/docs/
作者博客:http://blog.millermedeiros.com/
http://blog.millermedeiros.com/js-signals-custom-eventmessaging-system-for-javascript/
二.不同设计模式实现之间的比较
下面的比较只是订阅事件类型、调度和删除事件侦听器的基本功能。它不是基于可用的功能,而是基于每个概念的基本实现与使用每个概念的利弊之间的差异。但通常在于大多数情况下,“智能实施”和“黑客”可以避免列出的一些缺点。
所有的实现完成相同的任务,并且基于相同的设计模式(Observer),它们也有许多共同之处,但运行方式上却有些许不同。本文主要是为了帮助你选择哪种实现最适合你的工作流以及你要解决的问题种类。
第一种模式:事件发射器/目标/调度器(Event Emitter/Target/Dispatcher)
- 调度自定义事件的每个对象都需要继承自EventEmitter / EventTarget / EventDispatcher对象或实现正确的接口。
- 使用字符串来定义事件类型。
- DOM2/DOM3 Events基于这样的模式。
代码示例
myObject.addEventListener('myCustomEventTypeString', handler);
myObject.dispatchEvent(new Event('myCustomEventTypeString'));
myObject.removeEventListener('myCustomEventTypeString', handler);
优点
- 完全控制 target object ,并确保只监听特定目标发送的事件。
- 可以调度任意事件类型而不修改目标对象。
- 对于每种 target/object/event 都使用相同的方法。
- 易理解代码。
- 事件 target 通常是 object 本身,这使得事件冒泡更有逻辑。事件冒泡指的是在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)。
- 流行。
缺点
- 倾向于使用继承而不是组合。。
- 使用字符串来定义事件类型,容易出现拼写错误并且 auto-complete 无法正常工作。
- 事件处理程序通常只接受一个参数(Event Object)。
- 如果你想传递额外的数据,你必须创建一个自定义的事件对象来实现正确的接口或扩展一个基本的事件对象,这个过程通常是复杂的。
第二种模式:发布/订阅(pub / sub)
- 使用单个对象向多个订户广播消息。
- 不是必要条件,但大多数实现都使用静态集中对象作为广播者。
- 使用字符串来定义事件类型。
- 消息与事件的目标之间没有任何关系。
代码示例
globalBroadcaster.subscribe('myCustomEventTypeString', handler);
globalBroadcaster.publish('myCustomEventTypeString', param1, param2, ...);
globalBroadcaster.unsubscribe('myCustomEventTypeString', handler);
优点
- 任何对象都可以发布/订阅任何事件类型。
- 轻巧。
- 易于使用/实现。
缺点
- 任何对象都可以发布/订阅任何事件类型。(是的,它是pro也是con)
- 使用字符串来定义事件类型。
- 容易出错。
- 代码不能自动完成(除非您将value作为变量/常量存储)。
- 通过命名规范来避免消息被错误的订阅者拦截
第三种模式:信号(js-signals)
- 每个事件类型都有自己的控制器。
- 事件类型不依赖于字符串。
代码示例
myObject.myCustomEventType.add(handler);
myObject.myCustomEventType.dispatch(param1, param2, ...);
myObject.myCustomEventType.remove(handler);
优点
- 不依赖字符串。
- 代码完成友好。
- 尝试调度或收听不存在的事件类型会引发错误(有助于您早点找到错误)。
- 不需要创建常量来存储字符串值。
- 对每个听众和事件类型进行粒度控制。
- 每个信号都有一个特定的目标/容器。
- 容易定义对象派发的signal。
- 轻松控制事件广播和订户,避免错误的对象对事件做出反应。
- 事件处理程序可以接受任意数量的参数。
- 通常在观察者模式的其他实现上不存在的便利方法,如:
- 启用/禁用每个事件类型的事件分派。
- 删除附加到特定事件类型的所有事件侦听器。
- 第一次执行后自动删除侦听器的选项。
- 将执行上下文绑定到事件处理程序,避免作用域问题。
- 删除/分离匿名侦听器。
- 等等...
- 倾向于使用组合而不是继承。
- 不会混淆原型链。
缺点
- 无法调度任意事件。(在大多数情况下也是优点)
- 每个事件类型是一个对象成员。(在大多数情况下也是优点)
- 如果有多个事件类型,会导致命名空间混乱。
- 不会将事件类型和目标对象传递给callback使得很难使用通用的handler(工作于多个事件类型和目标)。
- 与大多数人习惯不同。
命名约定
建议您始终以过去时态命名信号。这个惯例是基于AS3-Signals的建议,其背后的原因是避免与常规属性混淆并保持一致性。尝试组合多个单词,如果您尝试描述的事件没有过去时的形式或如果您有命名冲突。
三.用法示例
1.引入js-signals:
node.js
var Signal = require('signals');
var mySignal = new Signal();
AMD
define(['signals'], function(Signal){
var mySignal = new Signal();
});
Browser globals
<script src="js/libs/signals.min.js"></script>
<script>
var Signal = signals.Signal;
var mySignal = new Signal();
</script>
2.声明Signal对象:
//store local reference for brevity
var Signal = signals.Signal;
3.用户自定义对象:
//custom object that dispatch signals
var myObject = {
started : new Signal(), //past tense is the recommended signal naming convention
stopped : new Signal()
};
4.添加/调度/删除单个监听器(Single Listener):给started 信号绑定事件 onstarted(function)
function onStarted(param1, param2){
alert(param1 + param2);
}
myObject.started.add(onStarted); //添加监听器
myObject.started.dispatch('foo', 'bar'); //给 started 信号绑定的事件传送参数(param1, param2),即('foo', 'bar')
myObject.started.remove(onStarted); //删除监听器
5.添加/调度/删除多个监听器(Multiple Listeners)
function onStopped(){
alert('stopped');
}
function onStopped2(){
alert('stopped listener 2');
}
myObject.stopped.add(onStopped);
myObject.stopped.add(onStopped2);
myObject.stopped.dispatch();
myObject.stopped.removeAll(); //remove all listeners of the `stopped` signal
6.多次调度(Multiple Dispatches),也就是多次运行handler
var i = 0;
myObject.started.add(function(){
i += 1;
alert(i);
});
myObject.started.dispatch(); //will alert 1,调度一次
myObject.started.dispatch(); //will alert 2,调度两次
7.Multiple Dispatches + addOnce()
var i = 0;
myObject.started.addOnce(function(){
i += 1;
alert(i);
});
myObject.started.dispatch(); //will alert 1
myObject.started.dispatch(); //nothing happens
8.启用/禁用信号(Enable/Disable Signal)
var i = 0;
myObject.started.add(function(){
i += 1;
alert(i);
});
myObject.started.dispatch(); //will alert 1
myObject.started.active = false;
myObject.started.dispatch(); //nothing happens
myObject.started.active = true;
myObject.started.dispatch(); //will alert 2
9.Stop/Halt Propagation(停止/终止 传播)
方法一:直接调用signal.halt()停止
myObject.started.add(function(){
myObject.started.halt(); //防止下一个listener在队列中被执行
});
myObject.started.add(function(){ alert('second listener'); //不会被调用,因为第一个监听器停止传播
});
myObject.started.dispatch();
方法二:通过返回值 false 停止
myObject.started.add(function(){
return false; //if handler returns `false` will also stop propagation
});
myObject.started.add(function(){
alert('second listener'); //won't be called since first listener stops propagation
});
myObject.started.dispatch();
10.设置侦听器处理程序的执行上下文
var foo = 'bar';
var obj = {
foo : 10
}; function handler1(){
alert(this.foo);
}
function handler2(){
alert(this.foo);
}
//note that you cannot add the same handler twice to the same signal without removing it first
myObject.started.add(handler1); //默认执行上下文,参数是windows.foo
myObject.started.add(handler2, obj); //设置不同的上下文,传入obj.foo
myObject.started.dispatch(); //first handler will alert "bar", second will alert "10".
11.设置监听器的优先级
var handler1 = function(){
alert('foo');
};
var handler2 = function(){
alert('bar');
};
myObject.started.add(handler1); //默认优先级为0,add(handler,context,priority)
myObject.started.add(handler2, null, 2); //设置优先级为2,'handler2' 将比'handler1' 提早运行
myObject.started.dispatch(); //will alert "bar" than "foo"
12.启用/禁用单个SignalBinding
var handler1 = function(){
alert('foo bar');
};
var handler2 = function(){
alert('lorem ipsum');
};
var binding1 = myObject.started.add(handler1); //methods `add()` and `addOnce()` returns a SignalBinding object
myObject.started.add(handler2);
myObject.started.dispatch(); //execute handler1 , will alert "foo bar" than "lorem ipsum"
binding1.active = false; //disable a single binding
myObject.started.dispatch(); //will alert "lorem ipsum"
binding1.active = true;
myObject.started.dispatch(); //will alert "foo bar" than "lorem ipsum"
13.手动执行信号处理程序:不用signal.dispatch(),用 SignalBinding.execute()
var handler = function(){
alert('foo bar');
};
var binding = myObject.started.add(handler); //methods `add()` and `addOnce()` returns a SignalBinding object
binding.execute(); //will alert "foo bar"
14.检索匿名监听器:本人觉得可能是系统默认绑定的handler
var binding = myObject.started.add(function(){
alert('foo bar');
});
var handler = binding.getListener(); //reference to the anonymous function
15.删除/分离匿名监听器
var binding = myObject.started.add(function(){
alert('foo bar');
});
myObject.started.dispatch(); //will alert "foo bar"
binding.detach();//分离监听器
alert(binding.isBound()); //will alert `false`
myObject.started.dispatch(); //nothing happens
16.检查绑定是否只执行一次
var binding1 = myObject.started.add(function(){
alert('foo bar');
});
var binding2 = myObject.started.addOnce(function(){
alert('foo bar');
});
alert(binding1.isOnce()); //alert "false"
alert(binding2.isOnce()); //alert "true"
17.更改监听器执行上下文
var foo = 'bar';
var obj = {
foo : "it's over 9000!"
};
var binding = myObject.started.add(function(){
alert(this.foo);//this 指的是 windows,也就是说默认绑定的上下文环境是windows
});
myObject.started.dispatch(); //will alert "bar"
binding.context = obj;
myObject.started.dispatch(); //will alert "it's over 9000!"
18.将默认参数添加到信号调度
var binding = myObject.started.add(function(a, b, c){
alert(a +' '+ b +' '+ c);
});
binding.params = ['lorem', 'ipsum']; //set default parameters of the binding
myObject.started.dispatch('dolor'); //will alert "lorem ipsum dolor"
类似于:
var binding = myObject.started.add(function(a, b, c){
alert(a +' '+ b +' '+ c);
});
myObject.started.dispatch('lorem', 'ipsum','dolor'); //will alert "lorem ipsum dolor"
19.检查信号是否绑定某个特定的监听器
function onStart(a){
console.log(a);
}
myObject.started.add(onStart);
myObject.started.has(onStart); // true
20.记住以前分派的值/忘记值
myObject.started.memorize = true; // default is false
myObject.started.dispatch('foo'); // add()/addOnce() will automatically fire listener if signal was dispatched before
// will log "foo" since it keeps record of previously dispatched values
myObject.started.addOnce(console.log, console); // dispatching a new value will overwrite the "memory"
myObject.started.dispatch('lorem');
// will log "lorem"
myObject.started.addOnce(console.log, console); myObject.started.forget(); // forget previously dispatched values (reset signal state)
myObject.started.addOnce(console.log, console); // won't log till next dispatch (since it "forgot")
myObject.started.dispatch('bar'); // log "bar"
21.一次性监听多个信号见 CompoundSignal repository
四.库函数解析
1. signals ,Signals命名空间
2.Signal类
var mySignal = new Signal();//mySignal 是 Signals 类的一个对象。
Signal类属性
mySignal.active = false/true; //该信号监听调度是否停止,默认true可用
mySignal.memorize = false/true; //该信号是否记录之前的调度参数并且自动执行,默认为false不记录
mySignal.VERSION = ''; //记录当前信号的版本
Signals类方法
add();添加监听器
//add(listener, listenerContext, priority);
//listener 属于function类型,是信号操作函数
//listenerContext 属于object类型,是监听器的执行上下文环境,可选
//priority 属于Number类型,是该监听器的优先级,高优先级的先执行,默认为0,可选
//返回值是 SignalBinding 类型
mySignal.add(listener, listenerContext, priority);
addOnce();添加一次性监听器,运行完一次即可自动remove
//add(listener, listenerContext, priority);
//listener 属于function类型,是信号操作函数
//listenerContext 属于object类型,是监听器的执行上下文环境,可选
//priority 属于Number类型,是该监听器的优先级,高优先级的先执行,默认为0,可选
//返回值是 SignalBinding 类型
mySignal.addOnce(listener, listenerContext, priority);
dispatch();调度/广播信号到所有监听器,加入队列,即让监听器运行
//dispatch(params),paras可选
mySignal.dispatch();
dispose();删除所有相关的信号对象和绑定,无返回值
mySignal.dispose();
forget();清除记忆的arguments,无返回值
mySignal.forget();
getNumListeners();返回该信号绑定的监听器的个数
mySignal.getNumListeners();
halt();停止分发事件,blocking the dispatch to next listeners on the queue.
mySignal.halt();
has();检查该信号是否与特定监听器绑定
//listener ; 监听器的执行函数
//context ;执行的上下文环境,可选
// 返回值为true 或者 false
mySignal.has(listener, context);
remove();删除监听器
//listener ; 监听器的执行函数
//context ;执行的上下文环境,可选
//返回监听器的执行函数handler
mySignal.remove(listener, context);
removeAll();删除该信号的所有监听器
mySignal.removeAll();
toString();返回当前的object的
3.SignalBinding类
var binding = myObject.started.add(function(){
alert('foo bar');
});
等同于
//SignalBinding(signal, listener, isOnce, listenerContext(可选), priority(可选))
var binding = new SignalBinding(myObject.started, handler, false, myObject, 0)
function handler(){
alert('foo bar');
}
SignalBinding类属性
binding.active = false/true; //该信号监听调度是否停止,默认true可用
binding.context = object; //绑定的上下文环境
binding.params = ''; //默认在Signal.dispatch()期间传递给监听器的参数
SignalBinding类方法
binding.detach();//分离信号和监听器
binding.execute();//手动执行
binding.getListener();//返回绑定的特定监听函数
binding.getSignal();//返回绑定的特定信号量
binding.isBound();//判断是否绑定成功,true or false
binding.isOnce();//判断是否是一次绑定
binding.toString()//转换为字符串
函数简易查询索引地址:https://millermedeiros.github.io/js-signals/docs/symbolindex.html
五.应用实例
<!DOCTYPE html>
<html>
<head>
<title>test signal</title>
<script type="text/javascript" src="signals.js"></script>
</head>
<body>
<script type="text/javascript"> //custom object that defines a `started` signal
var myObject = {
started: new signals.Signal()
}; // a handler function
function onStarted(param1, param2){
alert(param1 + param2);
} // listen for the `started` signal and then run the `onStarted` handler
myObject.started.add(onStarted); //add listener // dispatch the `started` signal, running the handler, passing in parameters
myObject.started.dispatch('foo', 'bar'); </script>
</body>
</html>