JavaScriptで循環配列を文字列化したらどうなるか?

JavaScriptのオブジェクトは文字列化しても単に "[object Object]"と表示されるだけなのに、配列は文字列化すると "1,2,3,4" のようになる。便利なようであまり便利でもない。

var ary = [1,[2,[3,4]],5]
var s = ary.toString();  // s は "1,2,3,4,5"

各要素も再帰的に文字列化してくれるが、構造が見えない。

では、配列が循環したらどうなるのか?

var ary = [1,2,3,4];
ary[2] = ary;
var s = ary.toString();

3番目の要素を自分自身で置き換えたので、循環構造になるので普通に文字列化してしまうと、

[1,2,[1,2,[1,2[.........

のように無限長文字列になってしまう。

実際にブラウザで確認してみた。

Firefox, Safari, IE は下のようになった。

[1,2,,4]

3番目の要素がundefinedのときと見た目が同じで区別がつかない。

Operaでは摩可不思議なオブジェクトができた。aryが循環配列のとき以下のようになった。

s = typeof ary;                 // s は "object"
x = ary instanceof Array        // x は true
alert(ary);                     // 何も起こらない
n = ary.length;                 // n は 4
z = ary.toSting();
typeof z;                       // "undefined"
a = "[" + ary
typeof a;                       // "undefined"
b = hello + ary;                // helloは未定義変数なので当然 ReferenceError
c = ary + world;                // worldはに定義変数だがReferenceErrorは発ししない。cはundefinedのまま。

循環配列aryに対して、型変換が伴う演算があると、それ以降が無効になるような動作をしているようだ。

なんか、ブラウザ毎にもっと愉快な結果となるようなものを期待していたが、Opera以外は面白い結果じゃなかったので残念。

ECMA262 3rd editionの規格書では、このような循環配列を文字列化した場合のことは特に言及されていない。とういか、だから規格通りに実装したら"[1,2,[1,2,[1,2[...."と無限長文字列になる。ということは実装不可能。なので、どう実装してもよいといえばよいのかも知れない。

ちなみに、Rubyでやったら次のようになった。この方が遥かによい。

% irb
irb(main):001:0> ary = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):002:0> ary[2] = ary
=> [1, 2, [...], 4]
irb(main):003:0> ary.to_s
=> "[1, 2, [1, 2, [...], 4], 4]"

ついでに、Emacs Lispでは以下のようになった。

> (setq ary [1 2 3 4])
[1 2 3 4]
> (aset ary 2 ary)
[1 2 #0 4]
> (aref ary 2)
[1 2 #0 4]

配列ではなくリストでも同様。以上は複数のデータが相互に参照し合って循環した場合も同様の表現になる。

CommonLispでは確か循環リストをどう出力するかは、規格で明確に書かれていたと思う。JavaScriptはちょっとそのあたりが抜けている。