【JavaScript】プロパティはいくつまで繋げられるのか？

# 疑問

JavaScriptのオブジェクトって、`.`を使うことでプロパティの中身を見れますよね？

```javascript:こんな感じ
const sample = {
    num: 10,
};

sample.num; // 10
```

そして、プロパティの中身がオブジェクトだった場合は、さらに`.`を繋げるとそのプロパティが見れますよね？

```javascript:こんな感じ
const sample = {
    obj: {
        a: 'sampleA',
        b: 'sampleB'
    }
};

sample.obj.a; // 'sampleA'
```

また、メソッドの返り値がオブジェクトだった場合も繋げることができます。

```javascript:getElementByIdの例
document.getElementById('sample').innerText = 'Hello World!';
```

今まで出してきた例は`.`の数が1つや2つでしたが、では一体 **この呼び出すプロパティの数に上限はあるのか？** と疑問に思ったため、調べてみることにしました。


## 環境

* OS：MacOS Sonoma 14.1 (Apple M1)
* 実行環境：Chrome 120.0.6099.234
* エディタ：Chrome デベロッパーツールのコンソールで作業


# 実験内容

:::note info
結果だけ見たい方は、この節は読み飛ばして下の[結果](#結果)から読んでください。
:::

実験に使ったコードはこちらです。

```javascript:実験に使ったコード
str = 'window.'.repeat(1166).slice(0, -1);
eval(str);
```
上のコードは、`window.window...window`みたいな文字列を生成して、それを実行するものです。


## 関数とかwindowの解説

### window

`window`オブジェクトの`window`プロパティは`window`自身を返すみたいなので、これを利用することにしました。
この仕様を使えば`window.window.window.window...`という感じでプロパティを呼び出すことができます。

詳しくはMDNを見てください。

https://developer.mozilla.org/ja/docs/Web/API/Window/window


### string.prototype.repeat

こちらは呼び出された文字を指定した回数繰り返す関数です。
これを使うとホラーっぽい文字が作れたりします。

```javascript:repeatの使用例
const help = 'たすけて'.repeat(100);
console.error(help); // たすけてたすけてたすけてたすけてた以下略
```

...あなたは今後こんな感じの文字を見た時に`repeat`を思い出す呪いにかかりました。
...嘘です。かかってないです。すみませんでした許してください。

詳しくはMDNを見てください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/repeat


### slice(0, -1)

これは文字列から最後の文字を消すのに使っています。

上で解説した`repeat`メソッドは、実験に使ったコードだと下のように使っています。

```javascript:repeatを使っているところ
'window.'.repeat(1166);
```

ただ、これだと生成された文字列が`window.win以下略.window.`のように最後が`.`で終わってしまいます。
これだと構文エラーが出てしまうので、`slice(0, -1)`とすることで最後の`.`を消すようにしています。

`slice`の詳しい情報はMDNを見てください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice


## eval

> `eval()` 関数は、文字列として表現された JavaScript コードを評価します。ソースはスクリプトとして解釈されます。

`eval`は文字列をコードとして実行する関数です。
この関数を使うことで、大量の`window`という文字をコードとして実行しています。

:::note alert
警告: 文字列から JavaScript を実行することは、非常に大きなセキュリティリスクを伴います。`eval()`を使用すると、悪意のある者が任意のコードを実行することがあまりにも簡単になります。下記の[eval() を使わないでください!](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/eval#eval_%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84%E3%81%A7%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84!)を参照してください。
（MDNのコピペ）
:::

詳しくはMDNを参照してください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/eval


## コード解説

もう一度コード全文を乗っけておきます。

```javascript:実験に使ったコード
str = 'window.'.repeat(1166).slice(0, -1);
eval(str);
```

処理はこんな感じです。

1. `repeat`メソッドで大量の`window.`を繋げた文字を出力する
1. `slice(0, -1)`で最後にある`.`を消す
1. `str`変数に上で生成した文字列が格納される
1. `eval`関数で`str`を実行する

このコードは、**`repeat(ここ)`の数値を変える**ことによって`window`を繋げる回数を変えることができます。
なので実験の時は**ここの数をいくつにしたらエラーが出るのか**、というふうにします。

:::note warn
今思えば関数にした方が良かったような気もしますが、その辺は雑な実験だったので見なかったことにしてください...
:::

という感じで、上のコードを手動で数値を変えながら実行しました。
...はい、自動化した方が良かったですね。
今ならこうすると思います。

```javascript:改善案
for (let i = 0; true; i++) {
    try {
        str = 'window.'.repeat(i).slice(0, -1);
        eval(str);
    } catch (e) {
        console.log(i); // エラーが出た回数を出力
        console.error(e); // エラー内容を出力
        break;
    }
}
```


# 結果

試してみたところ、[私の環境](#環境)だと**1165回**まではエラーが出ませんでした。
結構たくさんいけるような、意外と少ないような...

:::note info
追記 2024/3/4
Chromeを 122.0.6261.94 にアップデートしたら、**1189回**まではエラーが出なくなりました。
バージョンによって挙動が違うかもしれません。
:::


そして、1166回以降だと**以下のエラーが発生**しました。
```text:エラー内容
VM15896:2 Uncaught RangeError: Maximum call stack size exceeded
    at <anonymous>:2:6
```

ちなみに上のエラーは一言で言うと「**無限ループしてるよ**」ということみたいです。
詳しくは以下を見てみてください。

https://qiita.com/H40831/items/c56cb495a04f65a4e026

* * *

これって環境によって変わるんでしょうかね？
少し気になります。

試したい方用に実験用コード（改善版）をもう一度乗っけておきます。
コピペして使ってください。

```javascript:実験用コード
for (let i = 0; true; i++) {
    try {
        str = 'window.'.repeat(i).slice(0, -1);
        eval(str);
    } catch (e) {
        console.log(i);
        console.error(e);
        break;
    }
}

/* 私の環境で出力された結果：
1166 -> 1190(chrome 122.0.6261.94)
VM1248:7 RangeError: Maximum call stack size exceeded
    at <anonymous>:4:14
*/
```

もし試してみた方がいたら、なんて数値が出力されたか教えて欲しいです。
また、この回数の仕様などに関する情報を持っている方がいましたら、ぜひコメントください。