【JavaScript】配列を引数渡しすると破壊的に動作する話【メモ書き】


はじめに

こちらの記事は、これ("はじめに")を追記している現在、5200 viewsを超えており、
たくさんの方に見ていただけていること、感謝致します。

本記事では、コメントでいただいたことを元にある程度の表現・言い回しの修正をしておりますが、
あくまで私がJavaScriptを使っていて思ったとおりに動作しなかったという事実から、
本記事を書いた当時、自力で調べた知識で構成されており、
JavaScriptの仕様を完全に理解しているわけではありません
そのため、この記事では当時の私がやりたかったことを実現する方法が述べられており、
それ以外については、その限りではありません

詳細な仕様等の確認は、本記事ではなく、
本記事コメント欄JavaScriptに参照渡し/値渡しなど存在しない - Qiitaなどを参照していただくことを強く推奨します。

本記事がJavaScriptのさらなる理解への道標になると幸いです。

特に、上記記事はこの修正をしているタイミングで読ませていただきましたが(遅い)、
本記事執筆当時は知らなかったことが詳細に書かれており、大変勉強になりました。
ありがとうございました。

JavaScriptって難しいですね。
だけどその苦しみこそが楽しいと感じてしまうのは私だけでしょうか?
私はどうやら、少なくともプログラミングにおいてはドMなようです。
尚、今回の追記においては、以下の本編の内容は一切変更しておりません。
予めご了承ください。正直、ほぼほぼ書き直さなきゃならなくなるので面倒なのです。


 本編

この記事は、筆者が配列をシャッフルするアルゴリズムを思いついたが既存だった話【JavaScript/Rubyサンプルコードあり】 - Qiitaを書いていたときに気づいたメモです。


まず、このコードを見て欲しい。

const swapAry1 = (ary) => {
  const newAry = [];
  const len = ary.length;
  for (let i = 0; i < len; i++){
    newAry.push(ary.pop());
  }

  return newAry;
} 

a = [1, 2, 3, 4, 5];
console.log('swapAry1');
console.log(a);
console.log(swapAry1(a));
console.log(a);

swapAry1は引数で渡された配列のすべての順番を逆にする関数だ。
swapAry1の実行時、swapAry1ではarypop()により破壊されている。
が、これらを実行したとき、

swapAry1
[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[ 1, 2, 3, 4, 5 ]

となるだろうと、思うかもしれない。(つまり、引数で渡した配列aまで破壊されているとは思わないかもしれない。)

しかし、実際には

swapAry1
[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[]

となり、引数で渡した配列aまで破壊されている。

どうやら配列を引数にとったとき、「参照渡し」(コメントでご指摘があった通り、『参照の値渡し』や『オブジェクト渡し』が正確な表現のようです。詳細はコメント欄にて。)がされているらしく、仮引数の配列の要素を変更するとそれが実引数の配列にまで影響するようである。参照の値渡しと値渡しについてはJavaScriptで配列のコピー(値渡し) - Qiitaを参照してもらえれば、と。私のこの説明よりわかりやすいと思います。

ついでに、アロー関数以外でもそうなのか試してみました。

const swapAry2 = function(ary){
  const newAry = [];
  const len = ary.length;
  for (let i = 0; i < len; i++){
    newAry.push(ary.pop());
  }

  return newAry;
};

a = [1, 2, 3, 4, 5];
console.log('swapAry2');
console.log(a);
console.log(swapAry2(a));
console.log(a);

function swapAry3(ary){
  const newAry = [];
  const len = ary.length;
  for (let i = 0; i < len; i++){
    newAry.push(ary.pop());
  }

  return newAry;
};

a = [1, 2, 3, 4, 5];
console.log('swapAry3');
console.log(a);
console.log(swapAry3(a));
console.log(a);

実行結果は

swapAry2
[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[]
swapAry3
[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[]

となりました。ダメです!
破壊的なものが欲しいときにはこれでも問題ないですが。

これを防ぐには、このようにすると良いでしょう。

const swapAry = (initAry) => {
  const ary = initAry.slice();
  const newAry = [];
  const len = ary.length;
  for (let i = 0; i < len; i++){
    newAry.push(ary.pop());
  }

  return newAry;
} 
a = [1, 2, 3, 4, 5];
console.log('swapAry');
console.log(a);
console.log(swapAry(a));
console.log(a);

実行結果

swapAry
[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[ 1, 2, 3, 4, 5 ]