【初級】知ってるつもりだったJavaScriptの基礎知識

この記事は、プログラマーになって1年間Swiftばかりを書いてきた僕が
JavaScriptをやることになり1ヶ月。数々のハマったポイントを自分なりにまとめたものになります。
なお、JSを勉強するにあたって、[JavaScript本格入門](https://www.amazon.co.jp/%E6%94%B9%E8%A8%82%E6%96%B0%E7%89%88JavaScript%E6%9C%AC%E6%A0%BC%E5%85%A5%E9%96%80-~%E3%83%A2%E3%83%80%E3%83%B3%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AB%E3%82%88%E3%82%8B%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%81%AE%E5%BF%9C%E7%94%A8%E3%81%BE%E3%81%A7-%E5%B1%B1%E7%94%B0-%E7%A5%A5%E5%AF%9B/dp/477418411X)が大変参考になりました。
この記事も主にこの本から学んだことが中心です。


## 定数・変数

ES2015で、let, const宣言が追加された。

| 宣言 | 説明     |
| :------------- | :------------- |
| const       | 定数。再代入不可。ブラケットでブロックスコープを作ることができる      |
| let       | ブラケットでブロックスコープを作ることができる       |
| var       | 変数(極力使わない)       |

なんとJSにはブロックスコープがなかった。

``` JavaScript
console.log("var宣言");

for (var i = 0; i < 5; i++) {
  console.log(i);
}

console.log(i); // forブロックの外側からiを参照できてしまう
```

let宣言を使うことで、JSでもブロックスコープを使えるようになった。

``` JavaScript
console.log("let宣言");

for (let i = 0; i < 5; i++) {
  console.log(i);
}

console.log(i); // Uncaught Reference Error!
```

### const, letの使い分け
基本constを使う。再代入したらエディタがエラーを出してくれるので、エラーが出たらletを使用する。

## nullとundefinedの違い
JavaScriptにはnullだけではなく、undefinedという概念が存在する。定義の違いは下記

|  | 定義     |
| :------------- | :------------- |
| null       | 値が存在しない |
| undefined   | 未定義 |

## nullチェック
### JavaScript

``` JavaScript
let elm = document.getElementById('hoge');
let undy; // 未定義の場合、暗黙的にundefinedが格納されている

if (elm === null) return; // id="hoge"が存在しないのでガード成功
if (elm === undefined) return; // elmの定義はされているので、チェックを抜けてしまう
if (undy === undefined) return; // undyは未定義なのでガード成功

alert("ちくしょう！来やがったな！");
```

nullかundefinedならガードしたいのであれば、
厳密じゃない等価演算子(==)を使ってnullと比較することでチェックできる。

`if (undy == null) return;`


### jQuery
jQueryオブジェクトのnullチェックを行う場合、たとえそのDOM要素が存在しなくともjQueryオブジェクトは生成されるため、単純なオブジェクトのチェックだとnullチェックできない

``` JavaScript
let $elm = $('.hoge');
if ($elm == null) return; // チェックを抜けてしまう
```

jQueryオブジェクトの配列要素0にアクセスすることで、DOM要素自体のチェックが可能

``` JavaScript
let $elm = $('.hoge');
if (!$elm[0]) return;
```

参考：
[JavaScriptやjQueryでの変数が「空かどうか」のチェック方法](http://webukatu.com/wordpress/javascript%E3%82%84jquery%E3%81%A7%E3%81%AE%E5%A4%89%E6%95%B0%E3%81%8C%E3%80%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%80%8D%E3%81%AE%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E6%96%B9%E6%B3%95/)


## ライフサイクル
ブラウザでロードされるタイミングで呼ばれるイベントハンドラが存在する

### JavaScript
DOMContentLoadedの方がwindow.onloadより早い。
初期化処理は基本、このイベント内で行う

画像の大きさなどを取得したい場合は、window.onloadを使用する

| イベント | 定義     |
| :------------- | :------------- |
| DOMContentLoaded       | DOM生成時 |
| window.onload   | ページ読み込み完了時(画像なども含む) |

``` JavaScript
document.addEventListener('DOMContentLoaded', function() {
  console.log('DOM読み込み完了');
}, false);

window.onload = function() {
  console.log('ページ読み込み完了');
};
```

### jQuery

`$(function() {});`は$(document).readyの省略形

``` JavaScript
$(document).ready(function() {
  console.log('DOM読み込み完了');
});

$(function() {
  console.log('DOM読み込み完了');
});
```


## 関数リテラル
JavaScriptの関数は、オブジェクトの一種。
### 関数リテラルとは
関数は、変数に代入することが可能。
関数リテラルと言う。

``` JavaScript
let testFunc = function(){処理};
testFunc();
```

## 変数の巻き上げ(hoisting)
* JavaScriptは、関数のいかなる場所で宣言した変数も **内部的に、先頭で宣言されたことになる**
* しかも宣言部分だけが先頭に移動し、 **代入部分は移動しない**

というルールが存在する。。マジか:fearful:

### var宣言の場合

``` JavaScript
var yourname = 'global';

function say() {
  console.log('君の名は' + yourname); // ① 君の名はundefined

  var yourname = 'local';
  console.log(yourname); // ② local
}
```

マジでした。
varの例だと、①が'global'だと思いきや、ローカルの `yourname` が巻き上がって
```君の名はundefined``` になる。

### let宣言の場合

``` JavaScript
let yourname = 'global';

function say() {
  console.log('君の名は' + yourname); // ① ReferenceError: yourname is not defined

  let yourname = 'local';
  console.log(yourname);
}
```

let宣言の場合、巻き上げは発生しますが`君の名はundefined` にならず、
ReferenceErrorという実行時エラーを投げてくれます。
なお、[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone_と_let_に関するエラー)曰く
> let宣言の場合、ブロックの始めから変数宣言が実行されるまで、変数は "temporal dead zone (TDZ)" の領域の中にいる

仕様なため、エラーを投げられるようです。
※ コメントいただき、ありがとうございます！


### 関数の場合

``` JavaScript
function foo() {
  bar(); // ① 'bar'

  function bar() {
    console.log('bar');
  }
}

function foo2() {
  bar(); // ② ReferenceError: bar is not defined

  let bar = function () {
    console.log('bar');
  }
}
```

巻き上げは関数でも同様に起こる。

* function命令①の場合は、後方参照が可能。
* 関数リテラルの②の場合は、 `bar` 部分だけ巻き上がっているため、後方参照が不可。

対処としては、**関数内の変数は先頭で定義すること** とのこと

詳しくは、下記の記事をご参照ください。
[知らないと怖い「変数の巻き上げ」とは？](http://analogic.jp/hoisting/)

## クロージャ
「ローカル変数を参照している関数内関数」のことをクロージャと言う。
クロージャを日本語訳すると、"関数閉包"。

``` JavaScript
function closure(memberCount) {
  let counter = memberCount;

  return function inner() { // ①
    return counter += 1;
  }
}

let myClosure = closure(10);
// ②
myClosure(); // 11
myClosure(); // 12
```

上記の例のinnerはクロージャ。
①の時点で、counterをインクリメントした値が返ってくるように見えるが、実際はインクリメントする関数innerが返って来ている。

②の時点で、クロージャがまだローカル変数を参照しているので、メソッド呼び出し後、破棄されるはずのcounterの参照が保持されている。
関数内関数が親の変数を参照できることを、キャプチャと言う

### 即時関数
即時関数は、宣言と実行が同時に行える関数のこと
2値の合計を返してくれる関数リテラルを即時実行関数で表現する

``` JavaScript

let count1 = 100;
let count2 = 200;

let result = function (param1, param2) {
  return param1 + param2;
}(count1, count2); // 関数末尾の()が即時実行の命令。引数2つを渡して実行!

console.log(result); // 300が出力される。
```

resultにはparam1とparam2を足した無名関数が格納されているように見えるが、即時関数を使うことで実際の結果だけを取得できている

### クロージャを使うと何が嬉しいのか
JSにはprivateな変数を宣言する命令がないため、クロージャを活用するとグローバルスコープを汚染しない書き方ができる。

``` JavaScript
// アンチパターン
let index = 0;
let xxx..
let yyy..
let zzz...
・
・
// 200行目
function counter() {
  console.log(index++);
}
```

グローバル変数をむやみに定義してしまうと、下記のような弊害がある(JSに限った話じゃないですが)

* グローバル変数の状態を後から追うとき対象範囲がコード全体になる
* 変数の競合が起きやすい and 名前を考える時間が余分にかかる


``` JavaScript
let counter = (function() {
  let index = 0;
  return function() {
    console.log(index += 1);
  };
}()); // 即時関数

counter(); // 1
counter(); // 2
counter(); // 3

console.log(index); // index is not defined
```

クロージャと即時関数を使うことで、indexを

 * プライベート変数にすることができた
 * indexのことを意識する範囲を狭くできた


## Class
ES2015から他言語同様、class構文が書けるようになった。
他言語でおなじみのprivate publicなどのアクセス修飾子はなく、すべてのプロパティがpublicになる

``` JavaScript
class Member {
  // コンストラクタ
  constructor(firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
  }
  // ゲッター
  get firstname() {
    return this._firstname;
  }
  // セッター
  set firstname(val) {
    this._firstname = val;
  }
  // メソッド定義
  getName() {
    return this.lastname + this.firstname;
  }
}
// インスタンス化
let hiroshi = new Member('ひろし', '山田');
console.log(hiroshi.getName()); // 山田ひろし
console.log(hiroshi.firstname); // ひろし
```


## (ES2015以前)プロトタイプベースのオブジェクト指向
JavaScriptのclass構文は、classという概念が存在するわけではなく、実態はfunction。
もともとJSで実装されていたプロトタイプベースの
オブジェクト指向のシンタックスシュガーなので、もともとの実装を知ることは重要。

### クラス定義
1. コンストラクタを宣言した関数を作る
2. new演算子でインスタンス化

new演算子の構文は `new constructor[([arguments])]` であり、
constructor = 自分で定義した関数といえる。

``` JavaScript
// コンストラクタを宣言した関数
let Member = function(firstname, lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
  // メソッド定義
  this.getName = function() {
    return this.lastname + this.firstname;
  }
}

// new演算子でコンストラクタ関数のインスタンス化
let mem = new Member('ひろし', '山田');
```

## Prototype
### コンストラクタ内でメソッドを定義する問題点
インスタンスが生成されるたびに、メソッドのコピーを生成されてしまう
上記例だと、 `getName()` メソッドの実態がインスタンス × n個生成されてしまう。

### そこでPrototypeオブジェクトを使う
JavaScriptは、関数を生成した瞬間に自動でprototypeというプロパティを用意している。
prototypeプロパティはデフォルトで空のオブジェクトを参照している。

``` JavaScript
  function proto(){}
  console.dir(proto.prototype);
```

定義した覚えのないprototypeオブジェクトが..ある
そしてJSの関数は、すごい..Objectです。

<img width="277" alt="prot.png" src="https://qiita-image-store.s3.amazonaws.com/0/30423/d90741e2-49fc-066e-b7b1-9548d39a0a19.png">


prototypeプロパティにメンバを格納することで、参照型のprototypeオブジェクト
に対してインスタンスも参照することになる。

↓

prototypeプロパティにメソッドを格納することで、無駄なコピーを防ぐことができる。

書き方

``` JavaScript
let Member = function(firstname, lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
}
// prototypeを使ってメソッド定義
Member.prototype.getName = function() {
  return this.lastname + this.firstname;
}
```

オブジェクトリテラルを使った書き方

``` JavaScript
let Member = function(firstname, lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
}

Member.prototype = {
  // key: value
  setName: function() {
    // 処理
  },
  getName: function() {
    // 処理
  }
}
```

## this
JavaScriptは呼ばれる状況に応じてthisの参照先が変わる..変わるんです。

|      |   場所   | thisの参照先 |
| :------------- | :------------- | :------------- |
|1     | 関数内      | グローバルオブジェクト      |
|2     | イベントリスナー内      | イベントの発生元(buttonとか)      |
|3     | コンストラクタ      | 生成したインスタンス      |
|4     | オブジェクト内      | 所属しているオブジェクト      |

４. の例

``` JavaScript
let member = {
  name: 'hiroshi',
  age: 29,
  getName: function() {
    return this.name;
  }
};

console.log(member.getName()); // 'hiroshi'
```

参考
[JavaScriptの「this」は「4種類」？？](http://qiita.com/takeharu/items/9935ce476a17d6258e27)

## 名前空間
名前空間を使うことで、グローバルスコープの汚染を防ぐことができる。
実態は空のオブジェクト。

宣言。
論理演算子 `||` を使って、MyAppの名前の競合がなかったら、空のオブジェクトを生成する。

``` JavaScript
let MyApp = MyApp || {}
```

名前空間にメンバを格納。

``` JavaScript
MyApp.member = {
  firstname: 'hiroshi',
  lastname: 'yamada',
  age: 29
}

MyApp.member.prototype.getName = function() {
  return this.lastname + this.firstname;
}
```

ファイル間でメソッドを共有

``` JavaScript
// util.js

(function(Util) {
  // ②
  Util.formatDate = function(date) {
    // 処理
  };
}(this.Util = this.Util || {})); // ①

// app.js

(function(Util) {
  let today = Date();
  // ④
  Util.formatDate(today);
}(this.Util)); // ③
```

1. this(globalオブジェクト)にUtilオブジェクトを格納
2. 静的メソッドを宣言
3. 即時関数の引数にthis.Utilを渡す
4. Utilのメソッドを使用する

参考：
[【脱初心者JavaScript】名前空間のイロハ](http://qiita.com/kenju/items/0d8f85df205ea4978508)