「そういえば関数の引数に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); }