JavaScript の参照渡しと使用メモリ

JavaScript の参照渡し

JavaScriptでは、引数がどのように渡されるかはデータの型によって異なります。

  • プリミティブ型(数値、文字列、真偽値、null、undefined)の場合は、値渡し(コピー)されます。
  • オブジェクト型(配列、関数、オブジェクト)の場合は、参照渡しされます。

以下は、参照渡しの例です。

javascript
function modifyObject(obj) {
    obj.value = 'fuga';
}

const myObject = { value: 'hoge' };
console.log(myObject.value); // hoge
modifyObject(myObject);
console.log(myObject.value); // fuga

この場合、myObjectは参照渡しされているため、関数内で変更された内容が外部にも反映されます。

プリミティブ型の例も示しておきます。

javascript
function modifyPrimitive(value) {
    value = 10;
}

let myValue = 5;
modifyPrimitive(myValue);
console.log(myValue); // 5

この場合、myValueは値渡しされているため、関数内の変更は外部には影響しません。

使用メモリ

変数がオブジェクト型であれば、た関数に渡すときに参照渡しされ、コピーは作成されません。よって、例え数MBの変数であろうとオブジェクト型である場合、参照渡しになるためメモリリークの心配は少ないです。

使用メモリの確認方法

ブラウザの開発者ツールを使用してメモリ使用量を確認する手順は以下の通りです。

  1. ブラウザの開発者ツールを開く(例:Chromeであれば、F12キーを押すか、右クリックして「検証」を選択)。
  2. 「メモリ」タブを選択。
  3. メモリスナップショットを作成し、使用されているメモリの詳細を確認できます。

Node.jsの場合

Node.jsでは、process.memoryUsage()メソッドを使用してメモリ使用量を確認できます。以下はその例です。

javascript
const used = process.memoryUsage();
for (let key in used) {
  console.log(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}

このコードは、現在のNode.jsプロセスで使用されているメモリをMB単位で出力します。

大きな配列でメモリ使用量を実験してみた

大きな変数を作成して、プリミティブと参照渡しでメモリ使用量がどう変わるか実験してみました。

html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>メモリ使用量のテスト</title>
  <script>
    let largePrimitive;
    let largeObject;
    let newPrimitive;
    let newObject;

    const modifyPrimitive = (value) => {
      let newValue = value + ' modified';
      return newValue;
    };

    const modifyObject = (obj) => {
      let newObj = { ...obj, value: obj.value + ' modified' };
      return newObj;
    };

    const passVariables1 = () => {
      // プリミティブ型の変数を関数に渡す
      newPrimitive = modifyPrimitive(largePrimitive);
      console.log("After passing primitive:");
      console.log(largePrimitive);
    };

    const passVariables2= () => {
      // オブジェクト型の変数を関数に渡す
      newObject = modifyObject(largeObject);
      console.log("After passing object:");
      console.log(largeObject.value);
    };

    console.log("Before allocation:");

    // プリミティブ型の変数
    largePrimitive = 'a'.repeat(100 * 1024 * 1024); // 100MBの文字列
    console.log("After primitive allocation:");
    console.log(typeof largePrimitive); // string
    //console.log(largePrimitive.charAt(0)); // 実際に変数を使用してメモリ割り当てを強制する

    // オブジェクト型の変数
    largeObject = { value: 'a'.repeat(100 * 1024 * 1024) }; // 100MBの文字列を含むオブジェクト
    console.log("After object allocation:");
    console.log(typeof largeObject); // object
    //console.log(largeObject.value.charAt(0)); // 実際に変数を使用してメモリ割り当てを強制する

    window.onload = () => {
      document.getElementById('pass1').addEventListener('click', passVariables1);
      document.getElementById('pass2').addEventListener('click', passVariables2);
    };
  </script>
</head>
<body>
  <button id="pass1">変数を関数に渡す(プリミティブ)</button>
  <button id="pass2">変数を関数に渡す(参照渡し)</button>
</body>
</html>

結論

実験してみた結果、プリミティブでも参照私でもメモリ使用量に変化がありませんでした。プリミティブで渡した場合にメモリ使用量がどんどん増えていくといったことにもなりませんでした。ネット上の巷でも言及されていますが、JSの参照渡しはいわゆるC言語などの参照渡しの挙動とはずいぶん違うのかもしれません。また、メモリのスナップショットを撮りながら分かったことは、巨大な文字列やオブジェクトを生成したタイミングでは、メモリに展開されていない様でした。変数がアクセスされるタイミングでメモリ確保が行われているようです。

JavaScriptのエンジンによっては、変数が使用されるまで実際にはメモリを割り当てない最適化が行われることがあります。そのため、初期化時にすぐにメモリが割り当てられていないように見えることがあります。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム