【JavaScript】関数について知ったことを書く

## はじめに
JavaScriptの関数に関する機能について学んだことを簡単に書き記していきます。


## 関数の引数について

```JavaScript

fuction sum(a, b) {
  return a + b;
}

alert( sum(1, 2, 3, 4, 5));//結果:3

```

関数が定義された際は、引数を2つとして定義しています。
ここで、**定義された関数を呼び出された際は、5つの引数を指定していますが、必要以上の引数でもエラーになりません。**
エラーにはなりませんが、最初の2つだけが使用されます。

### 任意の数の引数は3つのドットにより、関数定義できる

```JavaScript

fuction sumAll(...args) {
    let sum = 0;

    for (let arg of args) sum += arg;

    return sum;
}
alert( sumAll(1));//結果:1
alert( sumAll(1, 2)) //結果:3
alert( sumAll(1, 2, 3))//結果:6
```

`...args`の意味するところはパラメータを配列にまとめることであり、`args`は配列名として扱われます。

## 最初の引数を指定して残りの引数をまとめる

```JavaScript

function showName(firstName, lastName, ...titles) {
    alert( firstName + '' + lastName );

    alert( titles[0] );
    alert( titles[1] );
    alert( titles[2] );
    alert( titles.length );
}

showName("山田", "花子", "次郎", "三郎");
//結果:山田花子, 次郎, 三郎, 2
```

引数を`firstName, lastName, ...titles`とすることで、第1引数、第2引数を変数として扱い、残りの引数を配列として扱うことができる。



## `arguments`変数によりすべての引数を取得することができる

```JavaScript

function showName () {
    alert( arguments.length );
    alert( arguments[0]);
    alert( arguments[1]);
}

showName("太郎", "花子")
//結果:2, 太郎,　花子
showName("太郎")
//結果:1, 太郎, undefined
```

### `arguments`の注意点:
* 配列のように見えて厳密には配列ではない
* 反復可能である
* アロー関数は`arguments`を持たない

## スプレッド演算子を使って反復可能オブジェクトを引数に展開する

スプレッド演算子(`...`)はどのような場面で使われるか。
例えば、**配列**から最大値を返す組み込み関数などを使用するときに使われます。

```JavaScript

alert( Math.max(3, 5, 1)); //5

let arr = [3, 5, 1];
alert( Math.max(arr));//NaN

```

上記コードで配列として保持している変数(`arr`)を引数の中で展開したい場合に使用する。

```JavaScript
let arr = [3, 5, 1];

alert( Math.max(...arr)); //5

let arr1 = [1, 2, 3 ,4];
let arr2 = [1, 2, 3, 5];

alert( Math.max(...arr1, ..arr2) );//5
```

## 配列をマージするためにスプレッド演算子を使用する

通常の値とスプレッド演算子を組み合わせることで配列をマージすることができます。

```JavaScript
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [0, ...arr, 2, ...arr2];

alert(merged); // 0,3,5,1,2,8,9,15 
```

## レキシカル環境とは？

レキシカル環境とはなんであるか

JSではすべての実行中の関数やコードブロック、スクリプト全体は**レキシカル環境**と呼ばれる関連オブジェクトを持っている。

レキシカル環境オブジェクトは2つの部分から構成されている。（この辺が意味わからん）
* 環境レコード。プロパティとしてすべてのローカル変数をもつオブジェクトであること
* 外部のレキシカル環境への参照。通常、直近の外部のレキシカルなコードに関連付けられている。

これにより、変数は特別な内部オブジェクト、環境レコードのプロパティである。
変数を取得、変更するとは、そのオブジェクトのプロパティを取得または変更するということである。

→（自分の見解）変数や関数はオブジェクトとして扱われ、プロパティ（アトリビュート）を保持している。

レキシカル環境とは一体何であるかをつかむために、下記のコードにおいて、レキシカル環境が変わる様子を示す。

```JavaScript

let phrase;

phrase = "Hello";
phrase = "Bye";

```

1.スクリプト開始時、レキシカル環境は空
2.`let phrase`が定義された。初期値がないため、`undefined`が格納される
3.`phrase`が代入される
4.`phrase`が新しい値を参照する


**要約**
変数は特別な内部オブジェクトのプロパティで、現在の実行ブロック/関数/スクリプトと関連付けられています。
変数を使った作業は、実際にはそのオブジェクトのプロパティを使って作業しています

**関数呼び出しの間、2つのレキシカル環境がある。**
内部と外部→ローカル変数とグローバル変数

### 自分のレキシカル環境に対するイメージ
レキシカル環境とは変数や関数が定義あるいは宣言され、格納されている場所そのもののことを指す。
変数にアクセスする時、最初に内部のレキシカル環境を探し、次に外部のレキシカル環境で検索する。


## コードブロックとループについてのレキシカル環境
レキシカル環境はコードブロックにも存在します。
コードブロックが実行され、ブロック内のローカル変数を含む時それらが作成されます。

#### if文を例として

```JavaScript
let pharse = "こんにちは"

if (true) {
    let user = "太郎"

    alert(`${pharse}, ${user}`)
}

alert(user);// Uncaught ReferenceError: user is not defined
```

実行が`if`ブロックに来た時、`if`だけのレキシカル環境が作成されます。
作成されたレキシカル環境はその外部への参照をもつ`phrase`を見つけることができます。
しかし、`if`の中で宣言されたすべての変数と関数式はレキシカル環境の中にあり、外部からは見えません。
例えば、`if`が終わった後にある`alert`は`user`が見えないためエラーになります。

## let,const,var
`let`,`const`はレキシカル環境に関してまったく同じように振る舞います。
`let`と`const`の違いといえば、一度代入された変数に再度代入できるかどうかかなと。

`var`,`let`の違いは、

* `var`はブロックスコープを持たない
* `var`宣言はグローバルスクリプトの開始時(または関数の開始時)に処理される

### `var`はブロックスコープを持たない

```JavaScript

if (true) {
    var testvar = true;
    let testlet
}

alert(testvar); //結果:true
alert(testlet); //結果:testlet is not defined
```

### `var`宣言はグローバルスクリプトの開始時(または関数の開始時)に処理される

`var`による変数宣言は関数の開始時に処理されます。
しかし、変数は代入されるまでは`undefined`です。(**宣言がブロック内で最初に行われることを[巻き上げ処理](https://developer.mozilla.org/ja/docs/Glossary/Hoisting)という**)

```JavaScript

function sayHi() {
    phrase = "Hello";

    alert(phrase);

    var phrase;
}
```

上記のコードは以下のコードと同じです。

```JavaScript
function sayHi() {
    var phrase;
   
    phrase = "Hello";

    alert(phrase);

}
```

## スケジューリングについて

関数をすぐには実行させず、ある時点で実行するようにしたいことがあります。

そのために2つのメソッドがあります。

* `setTimeout`:指定時間経過後、一度だけ関数を実行します。
* `setInterval`:指定した間隔で、定期的に関数を実行します。

## setTimeout

`setTimeout`の構文
`let timerId = setTimeout(func|code, delay[, arg1, arg2...])`

引数について

* `func|code`:関数もしくはコードの文字列。通常は関数であり、コードの文字列は推奨されていない
* `delay`:実行前の遅延時間であり、単位はミリ秒
* `arg1,arg2...`:関数の引数。


## setInterval

`setInterval`の構文は`setTimeout`と同じ。
`let timerId = setInterval(func|code, delay[, arg1, arg2...])`

呼び出しを停止する際は`clearInterval()`を使用します。

```JavaScript
//2秒のインターバルで繰り返し
let timerId = setInterval(() => alert('tick'), 2000);

//5秒後に停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
```


## デコレータ

デコレータとは任意の関数に対して振る舞いを変更する関数のことを指します。


下記コードの説明です。仮に頻繁に呼び出される関数があったとします。頻繁に呼び出される関数に対して、再計算を行わず結果をキャッシュしておく機能を追加したデコレータの実装です。

```JavaScript
fucntion slow(x) {
    alert(`Call with ${x}`);
    return x;
}

function cachingDecorator(func) {
    let cache = new Map();

    return function(x) {
        if (cache.has(x)) {
            return cache.get(x);
        }

        let result = func(x);

        cache.set(x, result);
        return result;
    };
}

slow = cachingDecorator(slow);

alert(slow(1));
alert(slow(2));

```

このような実装を行ったデコレータにはいくつかのメリットが挙げられます。

* デコレータ(`cachingDecorator`)は再利用可能であること
* `slow`自身の複雑性は増加しない
* 複数のデコレータと組み合わせることができます。



上記コードのようにオブジェクトメソッドで動作するには適していません。

```JavaScript

let worker = {
    someMethod() {
        return 1;
    },

    slow(x) {
        alert(`Call with ${x}`);
        return x * this.someMethod();
    }
};

function cachingDecorator(func) {
    let cache = new Map();

    return function(x) {
        if (cache.has(x)) {
            return cache.get(x);
        }

        let result = func(x);

        cache.set(x, result);
        return result;
    };
}

alert(worker.slow(1));

worker.slow = cachingDecorator(worker.slow);

alert(worker.slow(2));//「Call with 2」 の後の結果がTypeError: this is undefined

```

### func.call()について

上記のコードの`function cachingDecorator(func)`の`let result = func(x);`の実装にて、`func(x)`として呼び出されているため、`this is undefined`となります。

これを修正する方法として組み込みの関数メソッド[func.call(obj, arg1, arg2, ...)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/call)を使用する方法があります。

```JavaScript
function sayName() {
    alert(this.name);
}

let user = { name: "John"};
let admin = { name: "Admin"};

sayName.call(user);
sayName.call(admin);
```

```JavaScript

let worker = {
    someMethod() {
        return 1;
    },

    slow(x) {
        alert(`Call with ${x}`);
        return x * this.someMethod();
    }
};

function cachingDecorator(func) {
    let cache = new Map();

    return function(x) {
        if (cache.has(x)) {
            return cache.get(x);
        }

        let result = func.call(this, x);

        cache.set(x, result);
        return result;
    };
}

alert(worker.slow(1));

worker.slow = cachingDecorator(worker.slow);

alert(worker.slow(2));
```


## 関数バインディングについて
オブジェクトメソッド`setTimeout`使ったり、オブジェクトメソッドを渡す場合、`this`が`undefined`になる場合があります。

以下、例

```JavaScript
let user = {
    firstName: "Taro",
    sayHi() {
        alert(`Hello! ${this.firstName}!`);
    }
};

setTimeout(user.sayHi, 1000);//Hello! undefined!

```

**ブラウザ**では、`setTimeout`メソッドが呼びだされる時、`this`は`window`を指します。(ここでいう`window`とはブラウザオブジェクトの`window`)
`this.firstName`は`window.firstName`を取得しようとするため、`this`は`undefined`になります。

主な解決手段は2つ。

* ラップされた関数を使う
* 組み込みメソッド`bind`を使用する


### ラップされた関数を使用する。
先のコードをラップされた関数を使用してみます。

```JavaScript

let user = {
    firstName: "Taro",
    sayHi() {
        alert(`Hello! ${this.firstName}!`);
    }
};

setTimeout(function() {
    user.sayHi(); //Hello! Taro!
}, 1000)

```



### `bind`を使用する

関数の組み込みメソッドとして`func.bind(this)`があります。`func.bind(this)`の引数で指定されたオブジェクトを呼び出し元のfuncに渡すことができます。

```JavaScript

let user = {
    firstName: "Taro",
};

function func () {
    alert(`${this.firstName}`)
}

setTimeout(func.bind(user), 1000);//Taro
```

## 参考文献
https://ja.javascript.info/
https://developer.mozilla.org/ja/docs/Web/JavaScript