変なJavaScript (+ E4X)

最近、E4X の勉強をしている(仕様はStandard ECMA-357ね)

ご存知の通り(?) MozillaJavaScript *1には E4X が使えるのだが、いろいろ罠チックなものがあったり、ECMAScriptの仕様を壊してしまっていたりと面白い(ぉ

const XHTML = new Namespace("xhtml", "http://www.w3.org/1999/xhtml"),
      LANG_JA = new Namespace("ja", "http://example.com/ja"),
      LANG_EN = new Namespace("en", "http://example.com/en");
default xml namespace = XHTML;
var xml = <root xmlns:ja={LANG_JA} xmlns:en={LANG_EN}>
  <ja:item id="1">hoge</ja:item>
  <ja:item id="2">piyo</ja:item>
  <en:item id="3">foo</en:item>
  <en:item id="4">bar</en:item>
</root>

ワイルドカード

この xml の子ノードのリストを得るには

xml.*

などとワイルドカードが使える。

属性値取得

hogeがある id 属性を得るならば

xml.*[0].@id

などと、@を付ければ良い。

名前空間

また、ja:itemのノードリストを得たいならば

xml.LANG_JA::item

ワイルドカードと組み合わせて

xml.LANG_JA::*

とすれば、要素名に関係なくリストを得られる

xml.*::*

なんてことも可能。

驚きである。なんと ECMAScript では.によるプロパティ取得は IdentifierNameとして定義されているが、そこに*@は含まれてはいけないルールとなっている。普通なら構文エラーだ。

何かが出来る予感である。

var o = {};
o.* = "hoge"; alert(o.*);
o.@a = 10;    alert(o.@a);
o.*::* = 30;  alert(o.*::*);

みんなキッチリ動いてくれる。

また、名前空間だが、別にNamespaceのオブジェクトでなくて、文字列でも構わない。

var ns = "hoge";
var o = { "hoge::piyo": 20 }
alert(o.ns::piyo);

あと、これも多分ワイルドカードからみだと思う。

0.*.0Chromeで実行しても0が返ってきて何が何だか分かららいがw

分かった。
0. === 0.0 // つまり 0
.0 === 0.0 // つまり 0
0.*.0 === 0 * 0
なんだ!

あと、

xml.LANG_JA:item === xml.LANG_JA:item // false

である。XMLオブジェクトのプロパティを取得するとき、内部ではまず空のXMLListを生成して、適合するプロパティを取得し、リストに追加して返すため、別インスタンスとなる。これは仕様にも書かれているとおりなので、そういうものなのだろう。
そのため、xml.hogehogeなど存在しないプロパティを取得すると、空のXMLListが返る。undefinedは返らないので注意。

この仕様、なのか実装なのか分からないけど、XML.prototypeのプロパティもそうで、

typeof XML.prototype.length // "xml"

となる。じゃあ、どうやってプロトタイプ拡張するんだ? っていうと、Mozillaの拡張でfunction名前空間がある。

XML.prototype.function::unco = function() alert("UNCO");
xml.unco() // "UNCO"

え? functionなんて長いコード書けない? じゃあ

var f = new Namespace("function", "@mozilla.org/js/function");
XML.prototype.f::hoge = function() alert("HOGE")
xml.hoge() // "HOGE"

ってすれば良いと思うよ!

追記

また、おかしなことをしてしまった。

default xml namespace = new Namespace("function","@mozilla.org/js/function");
var x = <root>
  <length>hoge</length>
</root>;
/*
<root xmlns="@mozilla.org/js/function">
  <length>hoge</length>
</root>
 */
x.length
/*
function length() {
  [native code]
}
 */

lengthのノード取れないwww
まぁx.*[0]とかやれば取れるけど。

*1:version 1.6から