マイナー・マイナー

隠れた名作の発掘が生きがい。

Array型とかObject型は引数が参照渡しなので、忘れているとうわーってなる


スポンサードリンク

「そういえば関数の引数にArray型を指定した場合って参照渡しだった!」ということを忘れていてちょっとハマったことがあったので、悔しかったからその挙動をメモします。JavaScriptで非同期処理関数の引数に渡したArrayの要素を変更したら、非同期処理後にArrayの要素が変わってうわーってなる挙動を検証します。


「0.1秒後にArrayの要素を表示する関数(非同期とみなす)」をメイン処理で呼び出した後にArrayの要素数を変更したときの挙動を検証するコードを下記のように書きました。比較のためにNumber型、Object型の検証もしています。結果はコメントの通り。


検証コード:

function dispAsync(dispNumber, dispArray, dispObject1, dispObject2) {
  setTimeout(function() {
    console.log("3 Number : " + dispNumber);  // 3 Number: 100
    console.log("3 Array : " + dispArray); // 3 Array: a,b
    console.log("3 Object1 : " + dispObject1.message); // 3 Object1: Hoge
    console.log("3 Object2 : " + dispObject2.message); // 3 Object2: Piyora
  }, 100);
}

var myNumber = 100;
var myArray = ["a", "b", "c"];
var myObject1 = {message:"Hoge"};
var myObject2 = {message:"Piyo"};

dispAsync(myNumber, myArray, myObject1, myObject2);

console.log("1 Number : " + myNumber);  // 1 Number: 100
console.log("1 Array : " + myArray);  // 1 Array: a,b,c
console.log("1 Object1 : " + myObject1.message);  // 1 Object1: Hoge
console.log("1 Object2 : " + myObject2.message);  // 1 Object2: Piyo

myNumber = 200;
myArray.pop();
myObject1 = {message:"Hogera"};
myObject2.message = "Piyora";

console.log("2 Number : " + myNumber);  // 2 Number : 200
console.log("2 Array : " + myArray);  // 2 Array : a,b
console.log("2 Object1 : " + myObject1.message);  // 2 Object1 : Hogera
console.log("2 Object2 : " + myObject2.message);  // 2 Object2 : Piyora


Number型は値渡しなので、dispAsyncに渡した値がそのまま表示されます。Array型は参照渡しなので、setTimeoutで指定された関数が実行される前にdispArrayに引き渡したmyArrayの要素数を変更すると、表示される要素数も変更後のそれとなります。


Object型を関数の引数に指定する場合、そのアドレスがコピーされます。myObject1を関数に引き渡した後、myObject1自身を別のオブジェクトに変更(オブジェクトのアドレスを別のものに変更)しても、関数は引き渡された時点のオブジェクトを参照します。もし、myObject2のように、Objectのプロパティを関数に引き渡した後で変更した場合、引き渡されたオブジェクトのアドレスは変わっていないので、変更されたプロパティを参照することになります。


というわけで、setTimeout等の非同期処理の前には、引数の値をディープコピーするなどの対策を忘れないようにしよう。

function dispAsync(dispNumber, dispArray, dispObject1, dispObject2) {
  var copiedDispArray = [];
  for (var i = 0; i < dispArray.length; i++) {
    copiedDispArray.push(dispArray[i]);
  }
 
  var copiedDispObject1 = {message: dispObject1.message};
  var copiedDispObject2 = {message: dispObject2.message};
 
  setTimeout(function() {
    console.log("3 Number : " + dispNumber);  // 3 Number: 100
    console.log("3 Array : " + copiedDispArray); // 3 Array: a,b,c
    console.log("3 Object1 : " + copiedDispObject1.message); // 3 Object1: Hoge
    console.log("3 Object2 : " + copiedDispObject1.message); // 3 Object2: Piyo
  }, 100);
}