JavaScriptの基礎を押さえて、中級JS使いを目指す

[前回の記事](https://qiita.com/qeema/items/ed18b793f90a56e3cf84)の続き。JavaScriptのわかりづらい部分をまとめて**JS使い**を目指す記事です。
中級者ぐらいになって、こなれた感じでJavaScriptのこと**JS**って略して呼びたいです。
この記事では、**this**・**巻き上げ**・**即時関数**を主に扱います。

※ VCSのdebugで検証しているので、グローバルオブジェクトは`global`になっています。
ブラウザで実行した場合は`window`になるので検証時は注意してください。

# thisについて
`this`はruby等でいう`self`のようなものだが、JavaScriptでの`this`は、呼び出し元の呼び出し方によって値が変動する。
主に[4つのパターン](https://qiita.com/takeharu/items/9935ce476a17d6258e27)(メソッドから、関数から、コンストラクタから、apply/callから)がある。
また、[この本](https://www.amazon.co.jp/JavaScript%E3%81%AE%E7%90%86%E8%A7%A3%E3%82%92%E6%B7%B1%E3%82%81%E3%81%9F%E4%BA%BA%E3%81%8C%E3%81%95%E3%82%89%E3%81%AB%E3%82%82%E3%81%86%E4%B8%80%E6%AD%A9%E5%85%88%E3%81%AB%E9%80%B2%E3%82%80%E3%81%9F%E3%82%81%E3%81%AE%E6%9C%AC-%E4%BA%94%E5%8D%81%E5%B5%90%E8%82%87-ebook/dp/B01HUNYZNW)によると、上記四つに加えて、bindした時、アロー関数から呼び出した時との記載がある。
種類も多く利用するタイミングがまだわからないものもあるので、今回は**関数**から呼び出した時、**メソッド**から呼び出した時、**コンストラクタ**から呼び出した時の三点に絞ってまとめる。[^1]

[^1]: いつか理解して利用価値がわかればまとめ直したい。

## `this`の前にちょっと関数について補足
書き方が同じで紛らわしいが、`function`で定義された関数は**メソッド**にも**コンストラクタ**[^2]にも**関数**にもなりえる。
1. **メソッド**: オブジェクトのプロパティに関数を定義したもの。
2. **コンストラクタ**: `new`を使って呼び出したもの。
3. **関数**: 通常の関数。

[^2]: コンストラクタ関数と呼ぶのか、`new`がコンストラクタ呼び出しで呼ばれる関数なのか、コンストラクタって呼ばれる部分の言葉の定義がよくわからないので、だれか知っている方いれば教えて欲しいです!

```javascript

// 1. メソッド プロパティに関数を入れる。fooが出力される。
var obj = { foo: function() { console.log('foo'); } }
obj.foo();

// 2. コンストラクタ newで呼び出されたFoo関数はコンストラクタ。頭文字を大文字にする慣習があるらしい。Foooが出力される。
function Foo() { console.log('Fooo'); }
var fooInstance = new Foo();

// 3. 通常の関数 foooが出力される。
function foo(){ console.log('fooo'); }
foo();

```

## 関数から呼び出した時の`this`
通常の**関数**として呼び出した場合、`this`はグローバルオブジェクト`global`や`window`が入る。

```javascript

var testFunc = function() {
    this.foo = 'fooo';
    global.bar = 'barrrrr';
    console.log(this === global); // true
}
testFunc();
console.log(foo); // fooo
console.log(bar); // barrrrr
```

this自体が`global`と一致している。
関数内で`this`や`global`のプロパティとして定義した値は、グローバルオブジェクトからグローバル変数と見なされていることが確認できる。

## メソッドから呼び出した時の`this`
**メソッド**から呼び出した場合、`this`は自身が所属するオブジェクトを指している。

```javascript

var obj = {
  prop: 'foo',
  testMethod: function() {
    console.log(this); // Object {val: "foo", testMethod: }
    this.prop = 'bar';
  }
}

obj.testMethod();
console.log(obj.prop); // bar
```

`testMethod`メソッド内の`this`は、自身が所属する`testMethod`メソッドの`obj`オブジェクトが入る。よって、`this.prop`は`obj`に定義されている`prop`プロパティにアクセスしていることになり、値は`foo`から`bar`に上書きされている。

## コンストラクタから呼び出した時の、`this`
コンストラクタから呼び出した場合、コンストラクタ呼び出しをしたインスタンスが`this`になる。
`new`を使用して呼び出した関数がこれに相当する。`new`を使用して関数を呼び出す場合、[暗黙的](https://qiita.com/qeema/items/ed18b793f90a56e3cf84#%E9%80%9A%E5%B8%B8%E3%81%AE%E9%96%A2%E6%95%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A8new%E4%BB%98%E3%81%8D%E3%81%AE%E9%96%A2%E6%95%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97)に`this`オブジェクトを定義して`return`してくれる。

```javascript

var TestFunc = function(foo) {
  this.prop = foo;
}

var instance1 = new TestFunc('bar1');
var instance2 = new TestFunc('bar2');

console.log(instance1.prop); // bar1
console.log(instance2.prop); // bar2
```

関数は一つだが、`this`にはインスタンス自身の値が入るため、`instance1`・`instance2`どちらのインスタンスにも別の値が入っている。


# 巻き上げについて
関数の巻き上げと、変数の巻き上げの二つがある。英語では**hoisting**というらしい。響きがかわいい。

## 変数の巻き上げ
関数内で宣言されたローカル変数は、すべて関数内の先頭で宣言されたものとみなされる。

```javascript

var global = 'global';

function func() {
  // var global; がここで暗黙的に宣言されている。これが変数巻き上げの正体
  console.log(global); // undefined
  var global = 'local';
  console.log(global); // local
}

func();
```
`func`内の一度目の`console.log`でグローバル変数の`global`が呼び出されそうに見えるが、実際は`undefined`になる。これは関数内で`var global = 'local'`と変数宣言をしている行があるため、関数内の先頭で`var global;`が暗黙的に宣言されていることによる挙動。

## 関数の巻き上げ
関数宣言で定義された関数は、コード実行時にスコープの先頭まで巻き上げられる。

```javascript

var foo = sum(1, 2);

function sum(val1, val2) {
    return val1 + val2;
}

console.log(foo); // 3

```

`sum`関数定義前に、`sum(1, 2)`と関数を利用しているがエラーにならずに`3`が出力される。JavaScriptを含めて一般的な言語は上から下に実行されるので、本来は何かしらのエラーになってしかるべきだが、関数は巻き上げによってスコープの先頭(ここでは`var foo`の前)で宣言されるので正常に動作する。

### 関数の巻き上げの注意点
関数の巻き上げは、**関数式**と**関数宣言**で挙動が変わるので注意が必要。
関数式と関数宣言は以下のコードを参照。

```javascript

var func = function() {return 0;} // 関数式
function func() {return 0;} // 関数宣言
```

関数の巻き上げのサンプルコードは関数宣言を用いたが、次は関数式で同じことを実行する。

```javascript

var foo = sum(1, 2);

var sum = function(val1, val2) {
    return val1 + val2;
}

console.log(foo); // TypeError: sum is not a function
```

関数宣言の場合は関数名が決まっているが、関数式の場合は関数名が決まっていなく、変数名を通して実行されるためエラーが発生する。

# 即時関数について
個人的にJavaScriptをちょっと嫌煙してまじめに勉強しなかった主な理由の一つが、この即時関数の書き方だった。パッと見の違和感が強い。だが、グローバルオブジェクトを汚さないためにも大事。

```javascript

// 即時関数宣言方法1
(function(val1, val2) {
    console.log(val1 + val2); // 3
})(1, 2);


// 即時関数宣言方法2
(function(val1, val2) {
    console.log(val1 + val2); // 3
}(1, 2));
```

書き方は上記の二通りがあり、同じように動作する。
`()`が多すぎて分かりづらいが、冷静に考えると通常の関数の呼び出しも`()`を付けて実行しているだけである。
念のための確認だが、関数宣言後に`()`を付けずに実行すると、当然宣言内容を参照するのみで関数は実行されない。

```javascript
function testFunc() { console.log('foo'); }

// ()なしで実行
console.log(testFunc); // function testFunc() { … }

// ()ありで実行
testFunc(); // foo
```

関数宣言の場合、省略して書くと関数名`(引数)`で実行している。ということは、即時関数は`(関数宣言)(引数)`で実行していると考えられる。こうして見ると、ある程度即時関数の書き方も腑に落ちる。
`()`演算子自体は、`(1 + 2) / 2`等で使用するように、グループ化演算子と呼ばれ優先順位を制御している。この場合`(1 + 2)`が優先して評価され、`3 / 2`になる。
即時関数の`(関数宣言)(引数)`は、`(関数宣言)`を優先して扱い、`関数(引数)`の形で実行されているということになる。これは通常の関数呼び出し方の、関数名`(引数)`とほぼ同じである。
関数を式として評価し即実行するということなので、関数宣言を囲う`()`は、`()`だけでなく他の演算子でも同様の挙動が再現可能である。

```javascript
// 関数宣言を囲う()を+や!に置き換えている
+function(val1, val2) {
    console.log(val1 + val2); // 3
}(1, 2);

!function(val1, val2) {
    console.log(val1 + val2); // 3
}(1, 2);
```

`+`や`!`が`function`の前にあり、かなりグロテスクな見た目で、ちょっとタイポを疑うレベルなので使用しないほうが良いと思うが、この書き方は、即時関数の一般的な書き方の`(関数)(引数)`の理解の助けになると思われる。

# 参照サイトなど
- http://bonsaiden.github.io/JavaScript-Garden/#function.scopes
- [JavaScriptの「this」は「4種類」？？](https://qiita.com/takeharu/items/9935ce476a17d6258e27)
- [JavaScriptの理解を深めた人がさらにもう一歩先に進むための本](https://www.amazon.co.jp/JavaScript%E3%81%AE%E7%90%86%E8%A7%A3%E3%82%92%E6%B7%B1%E3%82%81%E3%81%9F%E4%BA%BA%E3%81%8C%E3%81%95%E3%82%89%E3%81%AB%E3%82%82%E3%81%86%E4%B8%80%E6%AD%A9%E5%85%88%E3%81%AB%E9%80%B2%E3%82%80%E3%81%9F%E3%82%81%E3%81%AE%E6%9C%AC-%E4%BA%94%E5%8D%81%E5%B5%90%E8%82%87-ebook/dp/B01HUNYZNW)