5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2024-02-08

疑問

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

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

sample.num; // 10

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

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

sample.obj.a; // 'sampleA'

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

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

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

環境

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

実験内容

結果だけ見たい方は、この節は読み飛ばして下の結果から読んでください。

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

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

上のコードは、window.window...windowみたいな文字列を生成して、それを実行するものです。

関数とかwindowの解説

window

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

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

string.prototype.repeat

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

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

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

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

slice(0, -1)

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

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

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

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

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

eval

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

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

警告: 文字列から JavaScript を実行することは、非常に大きなセキュリティリスクを伴います。eval()を使用すると、悪意のある者が任意のコードを実行することがあまりにも簡単になります。下記のeval() を使わないでください!を参照してください。
(MDNのコピペ)

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

コード解説

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

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

処理はこんな感じです。

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

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

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

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

改善案
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回まではエラーが出ませんでした。
結構たくさんいけるような、意外と少ないような...

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

そして、1166回以降だと以下のエラーが発生しました。

エラー内容
VM15896:2 Uncaught RangeError: Maximum call stack size exceeded
    at <anonymous>:2:6

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


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

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

実験用コード
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
*/

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

5
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?