たとえば、 Cometサービスの JavaScript APIを実装するときにこの問題を解決する必要がありました。 この問題は非常に頻繁に発生し、 こことここですでにハブで検討されていますが、次のコード要件に基づいて問題の解決策を書くことにしました:
- クロスブラウジング
- 依存関係なし
- 最小コードサイズ
- シンプルさと便利さ
私はミニライブラリをシグナルとスロットのスタイルで実装しました。
これは非常に便利なモデルであり、この例では最適であると思われます。 このアプローチの利点は、相互作用するコンポーネントの接続性が弱いことです。 つまり、信号とスロットのモデルは次の機能を提供します。
- 信号を発信するコードは、この信号を処理するコードについて何も知らない場合があります。 彼は、このコードが利用可能かどうか、またはそれがボイドにブロードキャストされるかどうかをまったく知りません。
- シグナルを受信するコードは、送信者について何も知りません。
- 一般的なのは、メッセージ形式だけです。
以下に例を示します。サブスクライブされたすべての関数に何らかのイベントを通知する必要があります。
これを行うには、次を実行します。
tabSignal().emitAll('', "") // tabSignal().emit( '', "" ) //
すべてのコードが機能し、誰かがこのイベントにサブスクライブした場合、彼はデータを受け取ります。
イベントをサブスクライブするには、サブスクライブしているイベントの名前を転送し、イベントが発生した場合にコールバックする必要があります。
tabSignal().connect('', function(param, signal_name){ });
スロット名を渡すこともできます。これは、イベント通知の登録を突然取り消す場合に必要になることがあります。
tabSignal().connect("",'', function(param, signal_name){} );
ここで、paramにはメッセージ自体が含まれます。 また、signal_nameはシグナルの名前です。1つのコールに署名した場合に便利です。
イベントの購読を解除する必要がある場合のコードを次に示します。
tabSignal().disconnect("", '');
データを別の入力に転送するために、ライブラリは単にそれをブラウザのローカルストレージに書き込みます。 ライブラリは、データを受信するために、onstorageイベントにサブスクライブします。誰かがローカルストレージに何かを書き込むと、すべてのタブで発生します。
ライブラリ自体にマスタータブを選択する機能を負荷しなかったので、ここで説明します。 同時に、その動作のアルゴリズムを分析します。 しかし、最初に、マスタータブを探す必要がある理由を説明します。 すでに述べたように、私は彗星サービス用のJavaScript APIを開発していました。
Cometテクノロジーを使用すると、サーバーの主導でブラウザーにメッセージを送信できます。 これには多くの用途がありますが、最も明白なのは、ユーザー間またはユーザーと技術サポート間のチャットを作成することです。 または、たとえば、Twitterで表示される新しいツイートを動的にロードします。
プッシュ通知をブラウザーに送信するには、ブラウザーと彗星サーバーの間に常に開いている接続が必要です。 しかし、多くの人が複数のタブでサイトを開きます。開いているタブの1つだけが彗星サーバーへの実際の接続を持ち、開いているすべてのタブがこの接続を使用すると便利です。 このアプローチは、サーバーのリソースを節約するだけでなく、同時に開かれる接続の数の制限という非常に重要な問題も解決します。
たとえば、Chromeは、1つのドメインに対して6つまでのリクエストを開き、開いているすべてのタブに対して合計で255までのリクエストを開きます。どのドメインでもかまいません。 したがって、各タブでコメットサーバーとの個別の接続を維持している場合、6つまでのタブを開くことができ、それで終わりです。
したがって、このタスクに基づいて、マスタータブが開いているタブの最初になり、閉じられた場合、残りのタブのランダムなタブがマスターになることを決定しました。 これを行うために、マスタータブは、150ミリ秒ごとにすべてのタブにメッセージを送信します。
タブを開くと、マスタータブから通知を受信するようにサブスクライブします。
次に、タイマーを少なくとも300ミリ秒に設定します。この時間内にマスターから通知を受信しない場合、マスターはいないと考えており、私たちは彼のためです。 このような場合、50msごとにマスタータブであるという通知の送信を開始し、マスタータブから通知を受信した場合、設定されたタイマーをキャンセルしてすぐに元に戻します-マスタータブがその存在を私たちに思い出させるまで300ms未満。
コード実装
function tryStartMasterTab(masterCallback, slaveCallback) { var time_id = false; var start_timer = 2000; if( window.InTryStartMasterTab !== undefined ) { console.log(" "); return InTryStartMasterTab; } console.log(" tryStartMasterTab"); InTryStartMasterTab = 0; var setAsMaster = function(){ // tabSignal().disconnect("comet_msg_connect", 'comet_msg_master_signal'); // tabSignal().emitAll('comet_msg_master_signal'); // setInterval(function() { tabSignal().emitAll('comet_msg_master_signal'); console.log(" !"); $("#consultantHolder").html(" !"); }, start_timer/8); InTryStartMasterTab = 1; if(masterCallback) { masterCallback(); } }; // , // start_timer tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', function() { if(time_id !== false) // { console.log(" slave!, clearTimeout(time_id="+time_id+")"); $("#consultantHolder").html(" slave!"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(InTryStartMasterTab === 0) { if(slaveCallback) slaveCallback(); } InTryStartMasterTab = -1; }); // , start_timer time_id = setTimeout(setAsMaster, start_timer ); }
しかしbeliyadmが指摘したように、このアプローチは多くのタブで失敗することがあり、 MarcusAureliusからのアドバイスを使用して、マスタータブの選択プロセスに優先順位システムを導入しました。
優先順位は、ミリ秒+ 0〜10000の乱数を含むタブのオープン時間であり、結果が小さいほど優先順位が高くなります。
改善された優先度の実装
function tryStartMasterTab(masterCallback, slaveCallback) { var time_id = false; var interval_id = false; var start_timer = 2000; if( window.InTryStartMasterTab !== undefined ) { console.log(" "); return InTryStartMasterTab; } console.log(" tryStartMasterTab"); InTryStartMasterTab = 0; var Today = new Date(); var TabId = (Today.getTime() *1000 + Today.getMilliseconds())*10000 + Math.floor( Math.random()*10000); var slaveloop = function(EventData) { if(time_id !== false) // { console.log(" slave!, clearTimeout(time_id="+time_id+")"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(InTryStartMasterTab === 0) { if(slaveCallback) slaveCallback(); } InTryStartMasterTab = -1; }; var setAsMaster = function(){ // , TabId tabSignal().emitAll('comet_msg_new_master', TabId); time_id = setTimeout(function() { // tabSignal().disconnect("comet_msg_connect", 'comet_msg_master_signal'); // tabSignal().emitAll('comet_msg_master_signal', TabId); // interval_id = setInterval(function() { tabSignal().emitAll('comet_msg_master_signal', TabId); console.log(" !"); }, start_timer/8); InTryStartMasterTab = 1; if(masterCallback) { masterCallback(); } }, start_timer); }; // tabSignal().connect('comet_msg_new_master', function(EventTabId) { if(EventTabId == TabId) { // ventTabId == TabId . return; } if(EventTabId > TabId) { // TabId return; } // EventTabId < TabId // . . if(time_id !== false) // { console.log(" slave!, clearTimeout(time_id="+time_id+")"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(interval_id !== false) // slave { clearTimeout( interval_id ); // , // start_timer tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', slaveloop); } slaveCallback(); }); // , // start_timer tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', slaveloop); // , start_timer time_id = setTimeout(setAsMaster, start_timer ); }
最後に、 オンラインデモを紹介します。
リポジトリTabSignal.js