Posted at

JSイベント実行順序についての壮大な勘違い

More than 1 year has passed since last update.


結論

結果だけ書くと、別にイベント実行順序は全く関係ありませんでした。

完全な釣りタイトルになってる気がします。

要点をまとめると 勘違いによる、時間の浪費です。

.....

....

...

..

.

普段は主にRailsで開発してるんですけど、Railsはgemを突っ込んでいい感じになってるのか、ほとんどJSを書く機会がありません。

非同期通信するにしても、 remote: trueとか書いておけばなんかいい感じにしてくれる。。

そんなところで、非同期通信を行う際に動的にパラメータを変えなきゃいけないことがあり、特に何も考えず、クリック時に非同期処理が走るボタン(というかリンク)に対し、同時にクリックイベントを追加して、パラメータを既存の hrefを更新するようにセットしてみたのですが、なんだかこれがうまく動くことに対して、とても疑念を抱きました。

単純に考えれば、同じ要素に対してイベントの追加を実行すると先に読み込まれた方が、先に実行されます。なので、DOMの読み込み後にjsを読み込むのだとすれば、例えばjqueryなんかのライブラリを使っているのであれば、それが先に読み込まれるはず。

そして、今回はjquery-ujsを使っており、先にそれが読み込まれているのだから、htmlで記述した(クリックすると非同期通信する)要素に対し、先に登録されるのは、、Ajaxイベントの方じゃないのか...?(そもそもこの考えが全ての間違いの始まり)


とりあえず、通常の実行順


index.html

<a class='test'>test</a>



index.js

// ①

document.querySelector('.test').addEventListener('click', function(){
var that = this;
console.log('t');
setTimeout(function(){
console.log(that.innerHTML);
}, 0);
console.log('t2');
});
// ②
document.querySelector('.test').addEventListener('click', function(){
console.log(this.innerHTML='change');
console.log(this.innerHTML='change2');
});


※ここでは、本人がこういう状況だと思いこんでいる想定コードを表現しています、実際には存在しません。

DOMのイベント実行はファイルを上から読み込む。

つまり、DOMに対するイベントリスナーの追加順になるから、①、②の順に登録される。

そこからキューに登録(待ち行列(先に入れたものを先に取り出す))されるから、単純な実行順は、

 ①console.log('t')

②setTimeout(...)

③console.log('t2')

④console.log(this.innerHTML='change')

⑤console.log(this.innerHTML='change2')

(⑥console.log(that.innerHTML))

となり、setTimeout実行後に console.log(that.innerHTML)が待ち行列に入ることになる。

なので例えば、非同期処理のパラメータをsetTimeoutの上部で取得し、非同期処理に渡す場合、上部の時点での値となるが、値を後付けで作成する場合はその限りではない。

なんかsetTimeoutだとわからなくなりそう。世の中の人はよくわかるな。

現環境だと非同期どこで実行してるかよくわからんし、クリックイベントにバインドされてるのか(そんなわけない)は知らないですけど。


index.js

 // ①

$('.test').on('click', function(){
console.log('t');
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data : {params1 : console.log(this.innerHTML, 't3')},
success : function(response){
console.log(response);
},
error: function(){}
});
console.log('t2');
});
// ②
$('.test').on('click', function(){
console.log(this.innerHTML='change');
console.log(this.innerHTML='change2');
});

つまりこの状況ならどんなに頑張ろうと paramsはtestをとる。

なんでjquery...?直す気力もないわ...というかajaxよりXHRの書式の方が明示的じゃないか

だから、僕の状況は①より先に②が読み込まれてる。

そうしないと、送信しているパラメータは送られてほしい change以降の値が入らないから。

つらつら説明っぽく書いたわけですが、何を当たり前のことを言っているんだという感じですね。

実際のところはclickイベントではなくjqueryを使ってるので Ajaxsend(使ったことない)とかが使われてるはずなので、この辺りイベントタイプが変わると変わるんでしょうか。

というかその辺いじるなら Ajaxbefore(これも使ったことないから知らない)とか使うべきなんですかね。

とまあ、jquery-ujs使ってるようなので、通常のイベントリスナーの登録順でいけば、読み込みが先のはずのajaxのイベントの方が先に逝きそうなきがするのですが、多分裏でうまいことやってるんでしょうかね...

こういうのコードで確認するとかはまだしも、どう調べるんですかね。

ESやDOMの仕様書って読んだことほとんどないんですけど、のってるんですかね。


link_toの仕様を見に行く

というか、そもそも一番最初に見なきゃいけなかったやつじゃないか、これ。remote: trueにしてるからと言っても、aタグはただのリンクなのであって、非同期にしているのはrailsの機能によるものじゃないのか...?


remote: true - This will allow the unobtrusive JavaScript

driver to make an Ajax request to the URL in question instead of following the link. The drivers each provide mechanisms for listening for the completion of the Ajax request and performing JavaScript operations once they're complete


読み違えていなければ、リンク遷移の 代わりにAjaxリクエストを控えめなJSで作る的な事が書いてあるはず。

(あと、DevToolのEventListenersのAncestors Allとframework listernersを外したら、自分の追加したclickイベントしかなかった(最初に外せばよかった))


index.html

<script>

function ontest() {
alert(2);
}
</script>
<a class='test' href='/' onclick='ontest();'>test</a>


index.js

document.querySelector('.test').addEventListener('click', function(){

alert();
})

実行(クリックイベント発火)

alert(2)
alert()
ページ遷移(今回はAjax)

つまり、こういうことについてなんかおかしいって騒いでいたってこと?

そう、イベントは関係ない。


W3Cも見ておく

一応W3Cで調べてみると、、つまるところ activation behaviorであるaタグのページ遷移は、マウスのクリックイベントまたは要素フォーカス中のエンターキーによるkeydownイベントという activation triggerによって引き起こされるということ。

で、この activation behaviorは、要素のクリックによって最終的に呼び出される動作のことらしいので、aタグ要素をクリックすると、要素にバインドされてるイベントが発生し、最終的にページ遷移が発生する。

なので、今回のことで言えば、クリックしたaタグにバインドされている、クリックイベントが発生し、このaタグのhref属性を書き換えて、パラメータが追加され、その後にaタグの最終動作として、ページリクエスト(今回はAjaxにunobtrusive JavaScriptによって変更されている)が飛ぶので、特に問題なく書き換えたurl+paramsにリクエストが飛ぶというわけですな(すごいわかったぜ!的などや口調で言ってますけど、別に何もすごくない)。

非同期通信といえば、$.ajax()。そして、それを呼び出すのはクリックイベントという書き方ばかりをしていた弊害かな。今までと違うプロジェクトなのに、コードもよく調べずにいい感じにしてくれてるんだと思ってた。

ただ、がっと実行だけ調べて、気づきを書いただけなので、いろいろと間違っている気がします。

間違いあれば、いろいろツッコミいただければ幸いです。。。

実行環境はChromeです。


終わり

あと、今回の事でドキュメントは原文で読まないと意味ない(なくはないけど薄れる)って事が身にしみた。

グーグル翻訳さん最強だけど、なんかニュアンス違う意味で訳してる感じで実際の単語の意味合いを汲み取っての理解と異なっていた気がする。


参考

Event reference

link_to(name = nil, options = nil, html_options = nil, &block)

Activation triggers and behavior

What does an "Unobtrusive Javascript Driver" in Rails "do"?

控えめなJavaScript