typeOf 'aki_mana'


チラシの裏的メモです。

自作のJavaScriptOO用ライブラリの開発目標。

プロパティディスクリプタを指定できる今の JavaScript OO って、オブジェクトを堅牢なものにできる。

writable: false は定数、メソッドに。
configuable: false はすべてのプロパティに。

で、単一継承しかできないJavaScriptも、多重継承したようなオブジェクトを生成する方法論として Mixin がある。

Function.prototype に enumerable:false で、且つ、上記のように変更禁止なAPIを加える。

オブジェクトは、こんな感じで定義する。



function MyConstructor(){}

MyConstructor
    .inheritsFrom(superCtor1, superCtor2) // 2個以上は 内部で Mixin コンストラクタを動的生成。これを単一継承する。
    .objectVars({
        MY_CONST : "constVal"
        
    })
    .objectMethods({
        staticMethod: function(){}
        
    })
    .instanceAccessor({
        myGetter: function(){}
    ,   mySetter: function(v){}
        // 両方定義したいときはディスクリプタの set: get: だけ書く。
    ,   myAccessor: {
            get: function(){}
        ,   set: function(v){}
        }
    })
    .instanceVars({
        instanceVariable: "defaultValue"
        
    })
    .instanceMethods({
        instanceMethod: function(){}
        
        
    })
;


クラス定義の方法って、まだまだフリーダムだと思った。
個人的には、Mixin をコンストラクタレベルで作る辺りが JavaScript のかゆいところに手を伸ばした感じ。

ただ、コンストラクタ内部で初期化の定義を書くと、Mixinに対応できなくなる。いっそのこと、ネームドNOOP限定にしようか。
あとは instanceof 演算子も Mixin ではツカエナイ。
複数のコンストラクタとそのprototype を解析して、一定の条件下で合成した全く新しいコンストラクタを作り、それを継承するのが理由。instanceOf() というメソッドを用意する必要がありそう。

こんなビルトイン拡張しちゃうと、IDEを使ってる人や、AltJSで JavaScript出力させる人には絶対に受け入れられないんだろうなぁ。

PureJavaScriptの割りに クラスベースOOっぽく、コードがクラス図になったような印象すらあるんだけど…。


あ、ちなみに、enumerable って、あまり意味がないからね。
「for - in で表示しない=プライベート」と勘違いしてる人は注意。
「public だけど書き換えできない」っていうプロパティのほうが、圧倒的に堅牢だから。


ビルトイン拡張を禁ずるより、ビルトイン拡張を自作の機能よりも優先的に解説したほうがいいかもしれない。
そんな風に思う今日この頃。



結果、2003 - 2005 に提案された 継承に関するページが上方に表示された。

ECMAScript のバージョンが上がって、JavaScript OOP における疑似クラスの作り方も豊富になってるんだけど、特にレガシーなブラウザに対応する必要があるときは使えそうなのでメモ。


Function.prototype.inheritsFrom = function( parentClassOrObject ){ 
	if ( parentClassOrObject.constructor == Function ) 
	{ 
		//Normal Inheritance 
		this.prototype = new parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject.prototype;
	} 
	else 
	{ 
		//Pure Virtual Inheritance 
		this.prototype = parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject;
	} 
	return this;
};

this.prototype.constructor を書き換えてる理由は、tihs.prototype = new Parent の時点で Parent になってしまうから。オーバーライドして矯正する必要があるんですね。

次に、this.prototype.parent に代入する行については、派生オブジェクトにメソッドを定義する際、親オブジェクトの変数やメソッドを this.parent.parentPropName という形式で参照できるようにする独自の実装と読める。

ただし、親のメソッドを呼ぶ時は、親メソッド内で this ステートメントを利用している可能性があるため、 this.parnt.method.call(this, arg0, arg1) とか this.parnt.method.apply(this, [args]) を用いて、参照した親オブ派生オブジェクト自身であると明示しなければならないかも。

独自実装のプロパティとはいえ、オーバーライドしたメソッドを用意する場合には便利な手法。



ネームド関数を new 演算子で具象化する書き方なら instanceof も意図したとおりに利用できそう。
関数名=グローバルスコープ上で変数となるので、多重定義できてしまうのが注意点。


さて、上記コードを Object.create() を使って実現できるモダンなJavaScriptだとこんな感じ?


Function.prototype.inheritsFrom = function( parentClassOrObject ){ 
	if( Object.create && typeof parentClassOrObject==="function" ){
		this.prototype = Object.create(parentClassOrObject.prototype, {
			constructor: {
				value: this,
				enumerable: true,
			},
			parent: {
				value: parentClassOrObject.prototype,
				enumerable: true,
			}

		});
	}
	else if ( parentClassOrObject.constructor == Function ) 
	{ 
		//Normal Inheritance 
		this.prototype = new parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject.prototype;
	} 
	else 
	{ 
		//Pure Virtual Inheritance 
		this.prototype = parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject;
	} 
	return this;
};
webを漁ると、Object.create() は使うな。 __proto__ おススメ。といった具合でいろんな情報であふれてるんだけど。古いブラウザでもプロトタイプベースの単一継承を作れるのが Function.prototype.inheritsFrom() の特徴。



JsonML は何度もネタにしてるんだけど、使いづらさを改善すれば簡便になるのではないかと。

JsonML のダメなところは、
  1. XMLに完全対応していない点
  2. オブジェクト解析環境の不備がある(JSON.stringify() に依存してる)点。
たとえば、こんな XML ファイルがあって、JsonML本家のライブラリでは DTD関連が処理できないのだけど


<?xml version="1.0" ?>
<!DOCTYPE members [
  <!ELEMENT members (member*)>
  <!ELEMENT member (name,addr,contact? )>
  <!ATTLIST member num CDATA #REQUIRED>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT addr (#PCDATA)>
  <!ELEMENT contact EMPTY>
  <!ATTLIST contact tel CDATA #REQUIRED>
  <!ATTLIST contact e-mail CDATA #IMPLIED>
] >
<!-- コメント1 -->
<members>
  <member num="01">
    <name>山田太郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="xx@xxxxx" />
  </member>
  <member num="02">
    <name>山田花子</name>
    <addr>大阪府XXXX</addr>
    <contact tel="xx-xxxx-xxxx" />
  </member>
  <!--
  <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member>
  -->
</members>


これを、開発中のライブラリでJsonMLに変換->JSON出力してみるとこうなる。


[""
, ["?", {
      "target":"xml"
      "version":"1.0"
    }
  ]
, ["!"
  , " コメント1 "
  ]
, ["!DOCTYPE", {
      "rootElement":"members"
    }
  , ["!ELEMENT", {
        "name":"members"
        "content":"(member*)"
      }
    ]
  , ["!ATTLIST", {
        "targetElement":"member"
      }
    , [{"name":"num","dataType":"CDATA"}]
    ]
  , ["!ELEMENT", {
        "name":"name"
        "content":"(#PCDATA)"
      }
    ]
  , ["!ELEMENT", {
        "name":"addr"
        "content":"(#PCDATA)"
      }
    ]
  , ["!ELEMENT", {
        "name":"contact"
        "content":"EMPTY"
      }
    ]
  , ["!ATTLIST", {
        "targetElement":"contact"
      }
    , [{"name":"tel","dataType":"CDATA"}]
    ]
  , ["!ATTLIST", {
        "targetElement":"contact"
      }
    , [{"name":"e-mail","dataType":"CDATA"}]
    ]
  ]
, ["members"
  , ["member", {
        "num":"01"
      }
    , ["name"
      , "山田太郎"
      ]
    , ["addr"
      , "東京都XXXXX"
      ]
    , ["contact", {
          "tel":"xx-xxxx-xxxx"
          "e-mail":"xx@xxxxx"
        }
      ]
    ]
  , ["member", {
        "num":"02"
      }
    , ["name"
      , "山田花子"
      ]
    , ["addr"
      , "大阪府XXXX"
      ]
    , ["contact", {
          "tel":"xx-xxxx-xxxx"
        }
      ]
    ]
  , ["!"
    , "
  <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member>
  "
    ]
  ]
]
JSON.stringify() の結果に比べて、マークアップ構造が確認しやすいはず。

マークアップ言語として出力してみるとこう。


<?xml version="1.0"?>
<!-- コメント1 -->
<!-- コメント2 -->
<!DOCTYPE members[
  <!ELEMENT members (member*)>
  <!ATTLIST member num CDATA>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT addr (#PCDATA)>
  <!ELEMENT contact EMPTY>
  <!ATTLIST contact tel CDATA>
  <!ATTLIST contact e-mail CDATA>
]>
<members>
  <member num="01">
    <name>山田太郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="xx@xxxxx" />
  </member>
  <member num="02">
    <name>山田花子</name>
    <addr>大阪府XXXX</addr>
    <contact tel="xx-xxxx-xxxx" />
  </member>
  <!-- <member num="03">
    <name>山田次郎</name>
    <addr>東京都XXXXX</addr>
    <contact tel="xx-xxxx-xxxx" e-mail="yy@xxxxx" />
  </member> -->
</members>

まぁ、妥協できる範囲で相互変換できるようになった。

XMLへの対応ってことで、HTMLとは違い、< と > とで囲まれた部分は文書要素でなくてもタグと考える。
処理命令やDTD周辺も文書要素のように考え、処理を行うのが特徴。

特に骨が折れたのは、DTD(文書型定義)用タグの扱い。
XMLの基本機能なんだけど、文書ツリーの書き方と違うシンタックスになるので、文字列処理するしかなかった。
文書型定義にはXML文書のツリーで行う代替手法を用る時代だけど、XML基本機能のDTDは対応すべきかなと考えてる。



DTDチャンク(DTD用タグ)となる <!ATTLIST > には子要素として、Attr(ATTRIBUTE_NODE)を与えることにした。
JsonMLには ATTRIBUTE_NODE を扱う文法がなかったので、element の文法に '[' attribute-list ']' を追加して対応。


XML宣言だと 要素としてタグ名が "?", 属性は { target:"xml", version:"1.0" } という具合にライブラリ内で予約する。DTD関係のタグも同様にして、JsonMLの要素のようにふるまうことができれば、DOMライクなAPIでビルドすることもできそう。

node はもちろん、WebWorker 内や onDOMContentLoaded 発生前のシングルページアプリケーションでも動く。Built-Inオブジェクトの Array 構造だからね。
JsonML関連で DOMライクなAPI実装は FakeElement ってのを作ってたので、リファクタ中。
dom ライクなAPIに セレクタ関連のAPIは未実装だったけど、この辺も実装すれば、JsonMLは文字列化した交換用データというより、JavaScriptアプリケーションで高速処理できる内部処理用データとして便利なのは変わらないと思ってる。


巷では変なObject ベースの変なデータ構造が流行ってるなと思ったら、Node.js でXMLを使いたいニーズがあって、 xml2js が流行ってたんですねぇ。あのデータ構造は、個人的に腑に落ちないので、頑張って開発します。



もう古い?時事ニュースからのネタ

誤解されないためにも最初に申し上げますが、
あくまでもネタですのであしからず。

地球の円周距離は 40,000 km と定められてて、5週は 200,000km
1年間に一人が一睡もせずに使用できる時間は 365 x 24 = 8760[h]

ここで、小学校でも習う「距離・速さ・時間」の計算をすると、
200,000 ÷ 8760 = 22 [km/h] の速度で移動する計算
一睡もせずにという条件は無理。

プロの運転手で1日6時間を移動に使うと考えると、88[km/h]
まぁ、高速道路も利用していると考えれば、お咎めのない安全運転といえるでしょう。

でも、移動に時間を割くことができない人のケースを考える。
例えば、1日あたり3時間までしか移動しない場合は176[km/h]
もっと少ない時間しか移動していない場合、速さだって増えます。

冒頭でも挙げてるけど、複数の車両を組織として運用する場合は問題もなくなる。

ただ、運行する車両ごとに時間、走行メーター(出発地、目的地)の記録を付けていれば、
堂々と「必要だった」と言えたのに。ダンマリになると、こういう風にくだらないネタを考えてしまう。

「業務上の記録って大事」だと思った。


Pure JavaScript で マークアップ言語を解析する際、JsonMLにコンバートするのが手っ取り早い。

けど、サーバーから取得したばかりのマークアップ言語のソースって、
Processing Instruction (XML宣言などの処理命令)や DTD用の <!doctype> 要素があって、
上手に無視してルート要素から解析したいのが本音。

で、表題の通り。
<!DOCTYPE> って、おまじないのように単純に考えていいのは、HTMLコーダー限定で、
本来は、マークアップの整合性を確認するためのルールをXML解析器に知らせる内容が列挙されてる。

<!DOCTYPE> 要素を用いてDTDを有効化するパターンは5種類かな。


1)[] の間に定義を書く(基本)
<!DOCTYPE rootElem [
    <-- 文書型定義 -->
]>

2) <-- 文書型定義 --> 部分をファイルにしてアクセスする
<!DOCTYPE rootElem SYSTEM "システム識別子(URI)">

3)広く使われる外部DTDはこんな感じに
<!DOCTYPE rootElem PUBLIC "公開識別子">

4)広く使われる外部DTDでも詳細な内容を外部ファイルとして用意するとき
<!DOCTYPE rootElem PUBLIC "公開識別子" "システム識別子(URI)">

5)外部ファイルに、ちょこっとだけ文書型定義を加えるとき。
<!DOCTYPE rootElem SYSTEM "システム識別子(URI)" [
    <-- 文書型定義 -->
]>


ここで、1)と5)に着目。
<!DOCTYPE >要素の内部に直書きしちゃうパターンでも上手に除去しなくちゃならんということ。

考えたのがこれ。


var parseDocTypeElement = function( ml ){
  var RE_TAG = /\x3c|\x3e/g
-   , RE_DTD = /\x21(DOCTYPE|ATTLIST|ELEMENT|ENTITY)/i
+   , RE_DTD = /\x21(DOCTYPE|ATTLIST|ELEMENT|ENTITY|NOTATION)/i
    , ptrs = [] // length === depth
    , inComment = false
    , doctypeElement // doctype tag
    , rootElement
    , dtdBegin = 0
    , dtdEnd = -1
  ;

  ml.replace(RE_TAG, function($0, offset, src){
    var m, str;
    switch($0){
    case '<':
      str = src.substring(offset);
      if( src.substr(offset,2)==="--" ) inComment = true;
      else if( m = str.match(RE_DTD) ){
        if(m[1].toLowerCase() === "doctype") {
          dtdBegin = offset +  m.index + m[1].length + 1;
          if(m=src.substring( dtdBegin ).match(/\s*([^\s]+)\s*/)){
            rootElement = m[1];
            dtdBegin += m[0].length;
          }
        }
        ptrs.push({ dtdTag: m[1], offset: offset + m.index + m[1].length + 1 });
      }
      break;
    case '>':
      str = src.substring(offset);
      if( inComment && src.substr(offset-2,2)!=="--" ) inComment = false;
      else { ptrs.pop(); if( !ptrs.length ) dtdEnd = offset; }
      break;
    }
  });
  doctypeElement = ml.substring(0, dtdEnd+1);
  return {
     ml : rootElement && ml.replace(doctypeElement,'') || ml
    , doctypeElement : rootElement && doctypeElement || null
    , rootElement : rootElement || null
    , dtd : rootElement && ml.substring(dtdBegin, dtdEnd) || null
  }
};

返却される オブジェクトの ml プロパティは <!DOCTYPE> 要素の記述を除去した内容。
で、ルート要素やDTDの定義部分も取得しておけば、使いやすいかも。

コメントの書式がネストするとバグると思う(この辺は、しっかりテストしていない)。

追記)2016-03-21
nodeType で dtd chunk となるタグに NOTATION があったので修正。
JavaScript 関連書籍において DOM の nodeType を詳説するものがなかったので調べてみた。

NODE TYPE タグ
CDATA_SECTION_NODE <![CDATA[CDATAテキスト]]>
DOCUMENT_TYPE_NODE <!DOCTYPE target [resource]>
ENTITY_NODE <!ENTITY [%] name [SYSTEM|PUBLIC publicID] resource [NDATA notation] >
NOTATION_NODE <!NOTATION name [SYSTEM|PUBLIC publicID] resource";>
PROCESSING_INSTRUCTION_NODE <?xml version="1.0"?> など

ENTITY_REFERENCE_NODE は &~; で表現される文字列実体参照っぽい。
ATTRIBUTE_NODE は DTD内の <!ATTLIST > で定義されたものやDOM APIで任意に追加/参照する属性の文字列っぽい。

このページのトップヘ