argumentsオブジェクトと仮引数の謎挙動

JavaScriptのargumentsはなかなか不思議なオブジェクトで、配列のようで配列でなかったり、関数スコープで暗黙に作られていたり、きわめつけは…

function hoge(a, b) {
   console.log(a);
   arguments[0] = 5;
   console.log(a);
}
hoge(1, 2);
1
5

!?

function fuga(a, b) {
   console.log(arguments[0]);
   a = 7;
   console.log(arguments[0]);
}
fuga(1, 2);
1
7

!!??

arguments の値と仮引数の値が連動するという摩訶不思議な挙動を示します。


何故このような挙動をするのか、 ECMA-262 5th 仕様書を紐解いてみました。10章6節 Arguments Object によると…

  1. Add name as an element of the list mappedNames.
  2. Let g be the result of calling the MakeArgGetter abstract operation with arguments name and env.
  3. Let p be the result of calling the MakeArgSetter abstract operation with arguments name and env.
  4. Call the DefineOwnProperty internal method of map passing ToString(indx), the Property Descriptor {Set: p, Get: g, Configurable: true}, and false as arguments.

argumentsオブジェクトへの数値インデックスアクセスに対して、setter/getterが定義されているようです。JavaScriptコードで表すと以下のようなイメージになります。

var a, b;
var arguments = Object.defineProperties(new Arguments(), {
  "0" : {
    get: function() { return a; },
    set: function(val) { a = val; } 
  },
  "1" : {
    get: function() { return b; },
    set: function(val) { b = val; } 
  }
});

なお、ECMA-262 3rd Editionでは、Activation Objectという見えないオブジェクトが半ば強引に定義され、仮引数やargumentsオブジェクトはActivation Objectのプロパティとされていました。5th Editionでは不自然なActivation Objectは削除され、getter/setterを使って同様の動作をするように定義しなおされています。