HTML 内の JavaScript で関数を定義する方法がいろいろあるけど使い分けがよくわからないので調べてみる

HTML 内の JavaScript で関数を定義するには次のような方法があると思います。

- function なんちゃらと書く方法 (関数の定義)

```js
function hello1() {
    console.log('hello1');
}
```

- 無名関数を変数に突っ込む方法 (関数式)

```js
const hello2 = function () {
    console.log('hello2');
};
```

どっちも意味同じじゃん！とか思ってしまったので、以下の実験をしてみました。
参考になれば、と思います。

## iframe からアクセスしてみる

2 つの関数を `<script>` タグに記述し、同一ページおよび iframe 内からアクセスしてみます。
また、 `window` を先頭に付ける場合と付けない場合のパターンもあわせて検証してみます。

### 手順

まずは 2 つのページを作成します。

```html:page1.html
<script>
    function hello1() {
        console.log('hello1');
    }

    const hello2 = function () {
        console.log('hello2');
    };
</script>

<h1>parent</h1>
<button onclick="console.log(hello1);">hello1</button>
<button onclick="console.log(hello2);">hello2</button>
<br>
<button onclick="console.log(window.hello1);">window.hello1</button>
<button onclick="console.log(window.hello2);">window.hello2</button>

<h1>iframe</h1>
<iframe src="page2.html"></iframe>
```

```html:page2.html
<button onclick="console.log(parent.hello1);">hello1</button>
<button onclick="console.log(parent.hello2);">hello2</button>
<br>
<button onclick="console.log(parent.window.hello1);">window.hello1</button>
<button onclick="console.log(parent.window.hello2);">window.hello2</button>
```

ウェブサーバを立ち上げ、各ボタンをクリックします。（ファイルを直接ブラウザで開いただけでは iframe からのアクセスができません）

開発者ツールで出力を確認します。

### 結果

const は同一ページからでないと見えない。

**parent**

直アクセスでは hello1, hello2 ともに見えるが、 window 経由では hello2 が見えない。

| button        | output                                      |
| ------------- | ------------------------------------------- |
| hello1        | ƒ hello1() {<br>console.log('hello1');<br>} |
| hello2        | ƒ () {<br>console.log('hello2');<br>}       |
| window.hello1 | ƒ hello1() {<br>console.log('hello1');<br>} |
| window.hello2 | undefined                                   |

**iframe**

hello1 は見えるが hello2 が見えない。

| button               | output                                      |
| -------------------- | ------------------------------------------- |
| parent.hello1        | ƒ hello1() {<br>console.log('hello1');<br>} |
| parent.hello2        | undefined                                   |
| parent.window.hello1 | ƒ hello1() {<br>console.log('hello1');<br>} |
| parent.window.hello2 | undefined                                   |

## 関数の定義前後で呼び出してみる

HTML 内に以下のようなスクリプトを記述して検証します。

**定義前**

```js:hello1_before_def.js
hello1();
function hello1() {
    console.log('hello1');
}
```

```js:hello2_before_def.js
hello2();
const hello2 = function () {
    console.log('hello2');
};
```

**定義後**

```js:hello1_after_def.js
function hello1() {
    console.log('hello1');
}
hello1();
```

```js:hello2_after_def.js
const hello2 = function () {
    console.log('hello2');
};
hello2();
```

### 結果

function は定義場所があまり関係ない。
const は定義する場所が重要。

**定義前**

hello2 でエラーが発生した。定義前にアクセスできない。当然かもしれない。

| script            | output                                                                |
| ----------------- | --------------------------------------------------------------------- |
| hello1_before_def | hello1                                                                |
| hello2_before_def | Uncaught ReferenceError: Cannot access 'hello2' before initialization |

**定義後**

hello1, hello2 ともに正常に表示された。

| script           | output |
| ---------------- | ------ |
| hello1_after_def | hello1 |
| hello2_after_def | hello2 |

## ブロックスコープに入れる

以下のようなスクリプトを作成し、実行します。

```js:hello1.js
{
    function hello1() {
        console.log('hello1');
    }
}
hello1();
```

```js:hello2.js
{
    const hello2 = function () {
        console.log('hello2');
    };
}
hello2();
```

### 結果

hello2 ではスコープ制限できたが、 hello1 はスコープが制限できなかった。

| script | output                                         |
| ------ | ---------------------------------------------- |
| hello1 | hello1                                         |
| hello2 | Uncaught ReferenceError: hello2 is not defined |

## 即時実行関数の中に入れる

以下のようなスクリプトを作成し、実行します。

```js:hello1.js
(function () {
    function hello1() {
        console.log('hello1');
    }
})();
hello1();
```

```js:hello2.js
(function () {
    const hello2 = function () {
        console.log('hello2');
    };
})();
hello2();
```

### 結果

hello1, hello2 ともにスコープが制限された。

| script | output                                         |
| ------ | ---------------------------------------------- |
| hello1 | Uncaught ReferenceError: hello1 is not defined |
| hello2 | Uncaught ReferenceError: hello2 is not defined |

## 再帰関数を作ってみる

以下のようなスクリプトを作成し、実行します。

```js:hello1.js
function hello1(times) {
    if (times === 1) {
        return 'hello';
    }
    return 'hello' + hello1(times - 1);
}
console.log(hello1(3));
```

```js:hello2.js
const hello2 = function (times) {
    if (times === 1) {
        return 'hello';
    }
    return 'hello' + hello2(times - 1);
}
console.log(hello2(3));
```

### 結果

どちらも正常に表示できた。

| script | output          |
| ------ | --------------- |
| hello1 | hellohellohello |
| hello2 | hellohellohello |

## 速度は？

以下のようなスクリプトを作成し、実行します。

```js:hello1.js
function hello1() {
    return 1;
}

let sum = 0;
const start = performance.now();
for (let i = 0; i < 10000000; ++i) {
    sum += hello1();
}
console.log(sum === 10000000);
console.log(`Elapsed: ${performance.now() - start} ms`);
```

```js:hello2.js
const hello2 = function () {
    return 1;
}

let sum = 0;
const start = performance.now();
for (let i = 0; i < 10000000; ++i) {
    sum += hello2();
}
console.log(sum === 10000000);
console.log(`Elapsed: ${performance.now() - start} ms`);
```

### 結果

hello2 の方が速いように見えるが、ページを何回か更新して時間を見ているとさほど変わらなかった。

| function | result |  time   |
| -------- | :----: | :-----: |
| hello1   |  true  |  29 ms  |
| hello2   |  true  | 26.5 ms |

## まとめ

| 実験                         | function |   const    |
| ---------------------------- | :------: | :--------: |
| iframe からアクセス          |    o     |     x      |
| 定義部分の上側でアクセス     |    o     |     x      |
| ブロックスコープで制限       |    x     |     o      |
| 即時実行関数でスコープを制限 |    o     |     o      |
| 再帰関数                     |    o     |     o      |
| 実行速度                     | そんなに | かわらない |

function と const では使えるスコープに差が見られましたが、それ以外の違いはあまりわかりませんでした。
読みやすさやスコープを意識して書き分けるのが重要なのかなと思いました。

## 参考にしたページ

https://stackoverflow.com/questions/13323237/anonymous-function-vs-normal-function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function