はじめに
実務で複雑な画面の制御をしている時、気付かないうちに大量ループの沼にハマり画面がフリーズした経験があります。
その時のサンプルコードと状況を共有しようと思います。
ある操作をしないと発生しないバグだったので見つけるまでに時間がかかりました。
※スマホ用とPC用のラジオボタンがある画面で、表示する画面幅によりラジオボタンの表示非表示を切り替えていた。画面制御が異なっていたので同じ処理に落とすことができなかった。
実際のサンプルコード
下記が実際のサンプルコードです。
function selPlan(plan){
if (plan == 'planA') {
// プランAが選択された時の処理(スマホ用)
}else if (plan == 'planB') {
// プランBが選択された時の処理(スマホ用)
}else if (plan == 'planC') {
// プランCが選択された時の処理(スマホ用)
}
$('#pc_radio_btn_a').on('click', function(){
// プランAが選択された時の処理(PC用)
});
$('#pc_radio_btn_b').on('click', function(){
// プランBが選択された時の処理(PC用)
});
$('#pc_radio_btn_c').on('click', function(){
// プランCが選択された時の処理(PC用)
});
}
何が起きたか
メソッド内にイベントリスナーを書き、スマホ用とPC用がぱっと見わかるため当時の私は可読性が高くいいコードだと思っていました。
しかし、タブレット上の表示を考慮し両方のラジオボタンを複数回押すという操作をすると画面がフリーズしました。
もちろん原因はわかりません。
コンソールに何も表示されない為、最初は全くわかりませんでした。
。。。
しばらくして、
「メソッド内にイベントリスナーを書くと無限ループしない?」
と先輩に言われていたのを思い出しました!
試しにログを仕込んでみると。
function selPlan(plan){
// 省略
$('#pc_radio_btn_a').on('click', function(){
// プランAが選択された時の処理(PC用)
console.log('pc_radio_btn_a clicked!');
});
// 省略
}
コンソール上には物凄い数のログが出てきました!
pc_radio_btn_a clicked! 10000
えっ!これって1万回も実行されてる!?
1回のクリックに対して、イベントハンドラが1万回も重複して発火していました。
まさに「1回のクリックに対して、実行回数が雪だるま式に増えていく恐怖」
なぜそうなったのか
大きな原因は、イベントリスナーの「二重登録(多重登録)」。
on('click', ...) は、指定した要素に「クリックされたらこれを実行して」という予約リストに処理を追加する命令。
上書きではなく追加です。
selPlan(plan)を実行するたびにこのイベントリスナーを追加していたので発生したみたいでした。
解決策
この大量ループを経験して、イベントリスナーは1回だけ登録する!
という事が大事だと実感しました。
私のとった解決策は「イベントリスナーを別のメソッドに移動する」でした。
function selPlan(plan){
if (plan == 'planA') {
// プランAが選択された時の処理(スマホ用)
}else if (plan == 'planB') {
// プランBが選択された時の処理(スマホ用)
}else if (plan == 'planC') {
// プランCが選択された時の処理(スマホ用)
}
}
// PC用ラジオボタンのイベントリスナー
function selPlanPC(){
$('#pc_radio_btn_a').on('click', function(){
// プランAが選択された時の処理(PC用)
});
$('#pc_radio_btn_b').on('click', function(){
// プランBが選択された時の処理(PC用)
});
$('#pc_radio_btn_c').on('click', function(){
// プランCが選択された時の処理(PC用)
});
}
メソッド名は仮のものをつけてますが、上記のようにイベントリスナー専用のメソッドを作成し、そのメソッドが1度だけ呼ばれるような制御に修正しました。
これにより一件落着しました!
おわりに
最近は保守性の高いソースにできるようリファクタリング練習をしているので、個人的には最後のコードも改善点いっぱいあると思いますが。。。
大量ループから脱出するという問題は解決できたので良かったです。
サーバー処理と違ってフロントはこういう落とし穴もあるんだと学びました。
普段、フルスタックでやっているので両方の追及は難しいですが。。。
いちエンジニアとしてこの先も成長していきたいです!
今回の失敗を糧に、さらにフロントエンドへの理解を深めていきたいと思います!
厳密にはループ処理ではないですが、クリックのたびに処理が倍増していく状態でした。
おまけ
もう1つ解決策があったみたいです。
.off('click')とすることで、前に登録したイベントリスナーを削除する。という処理が実行されるみたいです。
その時の状況に合った方法を選択してみて下さい。
$('#pc_radio_btn_a').off('click').on('click', function(){
// プランAが選択された時の処理(PC用)
});