js-signals學習以及應用


說在前面:寫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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM