2010年 02月 21日

JavaScript、仮引数で宣言した変数へ代入したあとの arguments の挙動

さて問題です。以下のコードで alert されるのは何でしょう!!

(function (x) {
  x = 2;
  alert(arguments[0]);
})(1);


答えはやってみてください。ビビりました。どうやら arguments オブジェクトは、変数の参照 (値の参照ではなく) を持っています。

ECMAScript 3rd Edition 日本語訳から根拠を探すと、ちゃんと書いてありました。

  • 0 以上 length プロパティの値未満の整数 arg それぞれについて、属性 { DontEnum } のプロパティ ToString(arg) が作成される。このプロパティの初期値は対応するパラメータの呼出側に供給される実際の値である。最初の実際のパラメータ値が arg = 0、2 番目は arg = 1, 以下同様である。arg が Function オブジェクトの仮引数の数より小さい場合、このプロパティは Activation オブジェクトの対応するプロパティとその値を共有する。このことは、このプロパティの変更が Activation オブジェクトの対応するプロパティを変更すること、そしてその逆を意味する。
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/10_Execution_Contexts.html#section-10.1.8

ちゃんと説明しとくと、この動作の奇妙さは

var obj = new Object();

obj[0] = 1;
obj.foo = obj[0];

obj[0] = 2;
alert(obj.foo); //=> 1

var baz = new Object();
baz.foo = obj[0];

obj[0] = 3;
alert(baz.foo); //=> 2

というあたり前 (に感じる) 挙動崩れるところです。普通、オブジェクトの別プロパティに代入したら、他のプロパティの値も同時に変わることなんてことはありえないし、別オブジェクトのプロパティが変わるなんてもってのほかですが、Activation オブジェクトの引数関係のプロパティだけは違う、ということです。

Activation オブジェクトはそもそも何かというと、変数を保持するオブジェクトです。この、隠れたオブジェクトを見えるようにしたコードを書くと、冒頭のコードは

(function (x) {
  // // Variable Instantiation
  // var a = new Activation(); 
  // a.arguments = { };
  // a.arguments[0] = 1;
  // a["x"] = a.arguments[0];
  // // End

  // a["x"] = 2;
  x = 2;

  // alert(a.arguments[0]);
  alert(arguments[0]);
})(1);

のように (実際は Activation オブジェクトにはアクセスできない) なり、そもそも仮引数 x が保持されているオブジェクトと、渡された引数第一番目の arguments[0] は別々のオブジェクトなのにも関わらず、値が共有されて相互作用をします。

これは「変数」を保持している「オブジェクト」が存在するという仕様上の説明からすると、一貫性がない不可解な動作です。