クライアントサイドJS

##Webアプリケーションの機能
webアプリケーションの機能を実現するためには、サーバーサイドの処とクライアントサイドの処理が必要である。サーバーサイドの処理のための言語は多くあるが、クライアントサイドの機能を実現するための言語はJavaScriptのみであると考えれても構わない。webアプリケーションの基本機能は以下の通り。

- ドラッグアンドドロップ
- 非同期読み込み
- キーボードショートカット
- アニメーション


JavaScriptの役割の一つにアプリケーションの見た目のわかりやすさや使いやすさといったユーザー体験を提供することがある。逆に言えば、Javascriptはインターフェースの実現に注力すべきで、JavaScriptだけですべての機能を実現すべきではない。理由としては

- 多くのブラウザでJSを実行しないという設定が可能であること
- ユーザー独自でJSを追加実行する機能を持つブラウザがあること

つまり意図した通りにJSが実行できないことがあるからである。



##Webページを表示する時の流れ

基本的なHTML

```html:html
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <img src="dummy.URL" alt="">
</body>
</html>

```

1. HTMLをパース
2. 外部JSファイル及び、CSSファイルを読み込む
3. JavaScriptがパースされた時点で実行
3. DOMツリーの構築完了
4. 画像ファイルなどの外部ソースをロード
5. すべて完了

DOMツリー構築完了後に画像ファイルなどが読み込まれることがポイント。
また、DOMツリーが構築された時点でJavascriptを実行することでユーザーにとっての待ち時間を減らすことができる。

###Javascriptの記述方法と実行タイミング

1. \<script\>タグ内に記述
2. 外部JSファイルの読み込み
3. Onload　
4. DOMCountLoaded 
5. 動的ロード

####1. \<script\>タグ内に記述

実行されるのは\<script\>タグが解析された直後なので、タグ以降のDOM要素は解析されない。そのため\</body\>閉じタグの直前に記述することで、他のすべてのDOM要素を解析した後に\<script\>タグを読み込むようにすることができる。

```html:html
<script>
    const id = document.getElementById('sampleId');
    id.classList.toggle('js-class')
</script>
```

####2. 外部JSファイルの読み込み
ファイルが読み込まれるのは\<script\>タグが解析された直後で、実行されるのはファイルが読み込まれた直後。1と同様に、\</body\>閉じタグの直前に記述すること。

```html:html
<script src="js/sample.js"></script>
```

####3. Onload　
onloadイベントハンドラに処理を記述することで、ページの読み込み完了後に処理を実行できる。ページ読み込み完了後なので、すべてのDOM要素を操作できる。
\<body\>タグ内に記述する方法と、JSファイル内に記述する方法がある。

```html:html
<body onload="alert('hello')">
```
```js:js
window.onload = function(){ alert('hello'); }
```

しかし、この場合すべてのページを読み込んだあとになるので大きな画像ファイルがあった場合、その画像ファイルの読み込みを待ってJSが実行されることになる。そのためJSの実行されるまでに必要以上に時間がかかってしまう。


####4. DOMCountLoaded 

onloadの待機時間問題を解決するのが、DOMCountLoaded。画像の読み込みを待つのと同時にJSの実行を行う。



```js:js
document.addEventListener('DOMContentLoaded', function() {
  alert('hello');
})
```
上記は以下の処理と同じ

```js:js
$(() => { /*DOMCountLoadedと同じタイミングで処理*/ });
```

####5. 動的ロード

この方法を用いた場合、JSファイルのダウンロードから実行が開始されるまではその他の処理をブロックしない。

```js:js
var script = document.createElement('script');
script.src = 'http://example.com/hoge.js';
document.body.appendChild(script);
```
しかし、この方法でDOMに追加した場合、src属性に書かれているURLに非同期的に通信が発生し、読み込みが完了したものから実行されるため、実行の順番を確保しないといけない場合は、ただ追加すればよいわけではない。

詳しくはコチラ　
[動的にJSを実行する方法](https://qiita.com/w650/items/adb108649a0e2a86f334)



##JSのデバッグ方法
* console.log()
* debugger
* ネットワーク監視

###console

```js:js
console.log('コンソール出すぜ');
```
様々なコンソールの種類についてはこちら
[JavascriptのChromeでのデバッグ方法個人的まとめ2016](https://qiita.com/snoguchi/items/8f6bb62a3166eca23ac3)
<br>

###debugger

```js:js
const arr = [1, 4, 5];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
  debugger;
}
//配列一つひとつ回るごとに検証を行う。
```
<br>

ブラウザのdevtoolで、ブレイクポイントを設定し、ステップ実行することができる。
ステップ実行処理には4通り

* 処理再開・・・処理が再開
* ステップオーバー・・・関数呼び出しがあっても、処理せずに次に進む
* ステップイン・・・関数呼び出しがあれば、その関数へ処理遷移して進む
* ステップアウト・・・関数が終了するまで実行し、関数を抜ける。

###ネットワーク監視
どういったHTTP通信がされているのか？
ちゃんとステータスコードやレスポンスが返ってきているのか？を確認する場合に、Networkタブの利用する


##クロスブラウザ対応
Webブラウザを構成するものとして、HTMLレンダリングエンジンとJavaScriptエンジンがあり、主要ブラウザによって採用しているものが異なる。違うエンジンを搭載したブラウザに対応するために2種類の方法がある。

####ユーザーエージェントで判断する
        
- navigatorオブジェクトのuserAgentプロパティ取得できる


```js:js
var user = navigator.userAgent;
//"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"

user.indexOf('MSIE') > 0
//indexOfは最初に値が現れたインデックスを返し、なければ-1を返す
```        

####機能の有無で判断する

- addEventListener()やattachEvent()(現在は非標準)など、機能を持っているかいないかで分ける
- ユーザーエージェントで判断すると、ユーザーエージェントがどの機能を有しているかをしっている必要がある。またwebブラウザが新しい機能を実装するたびに、書き換える必要がある。基本的には機能実装の有無でクロスブラウザ対策するが、IE6ではこの処理、IE7ではこの処理というような、特定ブラウザの特定バージョンだけ処理を変えたいときはユーザーエージェントで判断する。


##windowオブジェクト
クライアントサイドJSではwindowオブジェクトがグローバルオブジェクトになる。プロパティはいかの種類がある。

* navigator
* location
* history
* frames



```js:js
const user = navigator.userAgent;
//"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"

//URLの取得
const url = location.href;
//"https://s.codepen.io/......

//画面遷移
location.href = 'https://dummy.URL';

//ブラウザの履歴に残らない画面遷移
location.replace = 'https://dummy.URL';

//プロトコルの取得
var pr = location.protocol;
//https:

//Historyオブジェクトは履歴をたどる
history.back(); //履歴を戻る
history.forward(); //履歴を進める
history.go(-2); //履歴を2回分戻る

//windowに複数のフレームが存在している時フレームをたどる
window.frames[1];
```        


##DOMについて

参考記事　
[【DOM基礎】ノードの取得／属性の取得・設定](https://qiita.com/KDE_SPACE/items/e21bb31dd4d9c162c4a6)

**DOM(Document Object Model)**とはHTMLドキュメントやXMLドキュメントをプログラムから利用するためのAPI。DOMではこれらのドキュメントをオブジェクトのツリー状の集合として取り扱う。それを**DOMツリー**という。DOMのなかの一つひとつのオブジェクトを**ノード**と呼ぶ。

###1. DOMはAPIである

DOM APIを利用することでドキュメントを制御する事ができる。
Javascriptを利用することで、DOM APIとつながる事ができる。


###2. DOMツリーについて

```html:html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>タイトル</title>
    <meta chareset="utf-8">
</head>
<body>
    <h1>大見出し</h1>
    <!-- 本文が入る -->
    <p id="text">テキストテキストテキスト</p>
</body>
</html>
```
上記をDOMツリーで表すと

```
document *1
 └ html *2
    ├ head  *2
    │   ├ 空白 *3
    │   ├ title *2 
    │   ├ 空白 *3 
    │   ├ meta *2 
    │   └ 空白 *3 
    ├ 空白 *3
    └ body  *2
        ├ 空白 *3 
        ├ h1 *2 
        │  └ タイトル *3
        ├ 空白 *3 
        ├ コメント *4 
        ├ 空白 *3 
        ├ p *2 
        │  └ テキストテキストテキスト *3
        └ 空白 *3 

*1: ドキュメントノード
*2: 要素ノード
*3: テキストノード
*4: コメントノード
```
* ルートはdocumentオブジェクトから始まる。
* 空白は空白ノードというテキストノードの一種
* オブジェクトの集まりをツリー状にすると上記の通り


###3. ノードについて

DOMの中の一つ一つのオブジェクトをノードと呼ぶ。
要素とノードは別のもの。ノードは要素のスーパータイプに当たる。
ノードには以下の種類があり、様々に分かれている。
要素だけがノードではない。

| ノードの種類 | 概要 |
|:--|:--|
| ドキュメントノード | ドキュメント全体を表すDocumentオブジェクト |
| 要素ノード | 要素を表すオブジェクト |
| テキストノード | テキストを表すオブジェクト |
| コメントノード | コメントを表すオブジェクト |
| 属性ノード | 属性を表すオブジェクト |

 
###ノードのプロパティを利用して取得

```js:js
//子ノードへの参照
document.childNodes[1];

//最初の子ノード。
document.firstChild;

//兄弟要素の中で次のノード
document.nextSibling;
```
ただし、上記のプロパティを利用すると空白のテキストノードを取得してしまう。
そのため、要素ノードを取り出すためのプロパティが存在する。

```js:js
//子要素ノードへの参照
document.children[1];

//最初の子ノード。
document.firstElementChild;

//兄弟要素の中で次のノード
document.nextElementSibling;
```

しかし上記の方法でも、要素を取得するとなると不便である。
そのため、要素ノードを取得するためのメソッドが存在する。


**getElementを利用する方法**

```js:js
//idを取得
document.getElementById('id');

//クラス名で取得
document.getElementsByClassName('class');

//タグ名で取得
document.getElementsByTagName('div');

//name属性から取得
document.getElementsByName('名前');
```
getElement系を利用した場合、取得するオブジェクトはライブオブジェクトである。

**querySelectorを利用する方法**

```js:js
//複数の要素があっても、最初の要素だけ取得
document.querySelector('#id') === document.getElementById('id'); //true

//該当する全ての要素を取得する
document.querySelectorAll('div') === document.getElementsByTagName('div');
```

###ライブオブジェクト

* getElement系を利用した場合、取得するオブジェクトはNodeListで、それはライブオブジェクトである。
* querySelectorを利用した場合、取得するオブジェクトはStaticNodeList。ライブオブジェクトではない。


#####ライブオブジェクトとはなにか

JSの操作で文章に変更が加えられたときに、同時にその内容が変化するということ。生きている、動的なオブジェクトであるということ。

```js:js
//ライブオブジェクトの場合
const divs = document.getElementsByTagName('div');
console.log(divs.length);  // -> 0
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);
console.log(divs.length); // -> 1 変化あり
//divsはappendされる前から取得済みだったが、lengthは変化する


//ライブオブジェクトではない場合
const divs = document.querySelectorAll('div');
console.log(divs.length);  // -> 0
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);
console.log(divs.length); // -> 0 変化なし
```


#####ライブオブジェクトの注意点
* forループで無限ループする
* 一度配列に変換してから実行した方がパフォーマンス性が高い

######forループで無限ループする
以下の例は、div要素を取得して、それに対して入れ子のようにdiv要素を追加していく処理。現在ある要素にだけ子要素を足していきたい場合、以下の処理では無限に入れ子が続いてしまう。


```js:js
const divs = document.getElementsByTagName('div');

for (let i = 0; i < divs.length ; i++) {
  let newDIv = document.createElement('div');
  let newText = document.createTextNode('newDiv');
  newDIv.appendChild(newText);
  divs[i].appendChild(newDIv)
}
```

理由はforループが回るごとにdivsのlengthが取得されてしまうから。
そのため、divs.lengthは最初に定義しておく必要がある。

```js:js
const divs = document.getElementsByTagName('div');

for (let i = 0; len = divs.length; i < len ; i++) {
  let newDIv = document.createElement('div');
  let newText = document.createTextNode('newDiv');
  newDIv.appendChild(newText);
  divs[i].appendChild(newDIv)
}
```


######配列に変換

配列への変換は、Arrayクラスと、callを使いオブジェクトを返す。

```js:js
Array.prototype.slice.call(document.getElementsByTagName('span')
```
<br>
以下は、html内の全てのspan要素を取得して、100回分それらをコンソールに表示する処理を表している。そのまま取得したNodeListと、配列に変換したもの、2つの処理を表している。パフォーマンス性の違いは以下の通りである。

```js:js

let elem;
elem = document.getElementsByTagName('span');

//そのまま処理
var statTime1 = new Date();
for (let i = 0; i < 100; i++) {
  for (let j = 0; j < elem.length ; j++) {
    console.log(elem[j])
  }
}
var endTime1 = new Date();

//配列に変換処理
var statTime2 = new Date();
len = elem.length
let elem2 = Array.prototype.slice.call(document.getElementsByTagName('span'));
for (let i = 0; i < 100; i++) {
  for (let j = 0;j < len; j++) {
    console.log(elem2[j])
  }
}
var endTime2 = new Date();

console.log(endTime1 - statTime1); //699
console.log(endTime2 - statTime2); //377
```
やはり、配列に変換したもののほうが、処理が早くなる結果が返る。

<br>

####ノードの追加、作成、置換、削除

```js:js
//作成
let newDIv = document.createElement('div');
let newText = document.createTextNode('newDiv');

//追加
newDIv.appendChild(newText);

//置換
newDiv.replaceChild(newNode, oldNode);

//削除
newDiv.removeChild(newText);
```

####HTMLの変更、text内部の変更

```js:js
//作成
const div = document.getElementById('id');

//htmlの変更
div.innerHTML = '<div>hello world</div>';

//テキストの変更
div.textContent = 'hell world';
```

####DocumentFragmentの利用

10個のdiv要素を画面に追加する場合のDOMパフォーマンスを改善するにはどうすべきか？10回描画処理をするのであれば、パフォーマンス性が悪い。

フラグメントを作成し、それに格納。forループ終了後にそのフラグメントを親要素にappendすれば、描画の処理は1回で済ませることができる。

```js:js
//最初にフラグメントを作成
let fragment = document.createDocumentFragment();

//ループを回す
for (let i = 0; i < 10; i++) {
  newDiv = document.createElement('div');
  fragment.appendChild(newDiv);
}

//最後に描画処理を行う。
document.getElementById('parent').appendChild(fragment);
```