RailsでJavaScript（バニラ）が反応しない。

rails5では初期からjQueryのgemが導入されており、削除しない限りはデフォルトで利用できる状態です。
Railsを学び始めて、いつの間にか素のJavaScript（以降”バニラ”）よりもjQueryを使う方が慣れてしまっていたので、今回は改めてバニラを学習するためあえてjQueryを使わない方法を模索しました。

## 環境
Ruby 2.5.7
Rails 5.2.4

## 経緯
改めてバニラでコードを書き始めると割とハマるところが多くありました。
jQueryで書けるならそれでいいと言われればそれまでなのですが、そのままにしておくのは気持ち悪かったので少し向き合うことにしました。

### 即時関数が効かない
手始めにクリックイベントでコンソールログを確認しようとしました。
jQueryでは下記のように`$(function() {処理});`
という書き方をする必要があります。（理由は後述します。）

```javascript:application.js
// jQuery
$(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});
```

jQueryを使わないバニラの即時関数は下記のような書き方になります。

```javascript:application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());
```

上記二つのコードはどちらも処理は同じなのですが、バニラの方はなぜかエラーになり、コンソールログに出力されません。。。

以前はここでjQueryに切り替えていたのですが、どうしても解決したくて今回の模索に至りました。

## なぜjQueryは動いて、バニラは動かないのか？
それは`$(function() {});`に隠されていました。（隠されていません。）
`$(function() {});`は省略された書き方で、本来の形は下記のようになります。

```javascript:application.js
$(document).ready(function {
 //処理
});
```

readyメソッドはHTMLの読み込みが終わったタイミングで中の処理が行われるメソッドです。
つまりjsの処理を全て`$(function() {処理});`の中に書くことで、何も考えなくても処理を行うことができるということになります。

バニラではこの"htmlの読み込み待ち"をjQueryを使わずに書く必要があるということです。

## DOMContentLoaded
バニラではaddEventListenerメソッドのイベントに`DOMContentLoaded`を指定することで、jQueryのreadyメソッドと同じことができるようになり、コードは以下のようになります。

```javascript:application.js
window.addEventListener('DOMContentLoaded', function() {
  // 処理
});
```

## バニラでクリックイベントをするには？
ここで最初のjQueryとバニラを比較したコードを振り返ってみます。

```javascript:application.js
// jQery
$(function() {
  $("セレクタ名").on('click', function() {
    console.log('クリックされました。');
  });
});
```

このjQueryは省略せずに書くと

```javascript:application.js
// jQery
$(document).ready(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});
```
という感じになります。

次にバニラをみていきます。

```javascript:application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());
```
これは即時関数なのですが、実はこの書き方でも動作する場合もあります。

### 方法１.外部jsファイル読み込みのscriptタグをbody要素の一番下に記述する。
通常、Railsでは外部jsファイルの読み込みタグはheadタグの中に書かれていることが多いです。
しかし、headの中でjsファイルを読み込んでしまうと、bodyの中にあるエレメントをとる前にjsの関数が発動してしまうため、エラーになってしまいます。
なので、外部jsファイルの読み込みタグをbodyタグの最後に書く方法です。

```erb:application.html.erb
<head>
...
<%#= javascript_include_tag 'application' %>
...
<head>
<body>
...
<%= javascript_include_tag 'application' %>
</body>
```
こうすることで、bodyが読み込み終わったタイミングでjsファイルが読み込みを開始するので、要素の読み込みもできるということになります。
しかし、そのほかの外部ファイルやサービス、APIの読み込み設定などは全てheadタグ内に書かれるので、できれば外部jsファイルの読み込みもhead内で統一したいところです。

### 方法2.DOMContentLoadedを使う
先ほども触れましたが、addEventListenerメソッドのDOMContentLoadedイベントを使う方法です。
jsの外部ファイル読み込みタグはhead内のままに、jsファイルの方だけを書き換える必要があります。

```javascript:application.js
// バニラ
window.addEventListener('DOMContentLoaded', function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
});
```

これでHTMLが読み込まれてからfunctionが実行される形になったので、問題なく動作するようになりました。

## 注意
ちなみに、DOMContentLoadedイベントはHTMLの読み込み完了を待つだけなので、cssや画像ファイルの読み込みは含まれていません。
画像やcssの操作がある場合はaddEventListenerメソッドのloadイベントを使えば、全ての読み込みが完了してからfunctionを発火させることができます。

## まとめ
ここまで散々バニラで記述する方法をお伝えしてきましたが、jQueryと相性がいいならjQueryのままでも良いと、個人的には思います。
実際バニラを書いてみて、jQueryならどれほど簡単に書くことができるのかがわかったので・・・笑
これはRailsにも標準搭載されているという観点からも推察できます。

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

最後まで読んでいただきありがとうございました。

## 参考サイト
[JavaScriptで即時関数を使う理由](https://qiita.com/katsukii/items/cfe9fd968ba0db603b1e)
[【jQuery】$(function() {...}) について　「意味や実行されるタイミング」](https://qiita.com/bakatono_super/items/fcbc828b21599568a597)
[DOMContentLoadedイベントとloadイベントの違い[タイミング]](https://noumenon-th.net/programming/2017/06/11/domcontentloaded/)