typeOf 'aki_mana'


JavaScript標準の機能ではあるんだけど、可読性が悪くなるので使われない方法。

  • 比較演算子(&&, ||)はレフトハンドで字句解析される。
  • && による比較対象ステートメントの列挙は  false(イディオム:!1)で列挙の終端に抜ける
  • || による比較対象ステートメントの列挙は  true(イディオム:!0)で列挙の終端に抜ける
  • ()カッコで コンマを使えば任意のステートメントを列挙できる。
  • ()カッコで囲った複数ステートメントを比較対象ステートメントにするとき、最後のステートメントを比較対象ステートメントの評価値とする。
※終端に抜ける機能を使って緻密な制御構文を書き上げることができるのが比較演算子。

たとえば、RGB色空間とHSV色空間の相互変換 Javascript版(2010-09-03) で紹介されたコードスニペットを
if else や switch case の制御構文を必要最小限にすると、こうなる。



function RGBtoHSV(r, g, b, coneModel){
	var h // 0..360
	,	s // 0..255
	,	v // 0..255
	,	max = Math.max(r, g, b)
	,	min = Math.min(r, g, b)
	,	roundAngle = function(angle){
			for(;angle<0;)angle+=360;
			return angle % 360;
		}
	;
	((max == min) && ((h= 0),                               !0)) ||
	((max ==   r) && ((h= 60 * (g - b) / (max - min) +   0),!0)) ||
	((max ==   g) && ((h= 60 * (b - r) / (max - min) + 120),!0)) ||
	                  (h= 60 * (r - g) / (max - min) + 240)
	;
	h = roundAngle(h);
	(coneModel&&(s = max - min,!0))||(s=max&&((max - min)/max*255)||0);
	//v = max;
	//return [h,s,v];
	return [h,s,max];
}


function HSVtoRGB(h, s, v){
	if (s == 0) return (v = Math.round(v)),[v,v,v]; 

	var rgb // 0..255
	,	roundAngle = function(angle){
			for(;angle<0;)angle+=360;
			return angle % 360;
		}
	,	i,f,p,q,t
	;

	h = roundAngle(h),
	s /= 255,
	
	i = Math.floor(h / 60) % 6,
	f = (h / 60) - i,
	p = v * (1 - s),
	//q = v * (1 - f * s),
	//t = v * (1 - (1 - f) * s);
	// ビット演算が増えるが、ビット演算よりコストの高い他の演算回数を抑制。
	i&1 ? (q = v * (1 - f * s)) : (t = v * (1 - (1 - f) * s));

	return (
	(i==0 && [v, t, p]) ||
	(i==1 && [q, v, p]) ||
	(i==2 && [p, v, t]) ||
	(i==3 && [p, q, v]) ||
	(i==4 && [t, p, v]) ||
	         [v, p, q]
	).map(Math.round);
}


可読性を保つためにブラケットや演算子の前後で整形してるけど、ワンライナーにするとセミコロン(;)の行数に収まることが分かると思う
  • RGBtoHSV ... 5行
  • HSVtoRGB ... 4行


手作業でminify するならこの方法がツカエル!!

イネーヨ。そんなやつww


ちなみに、この方法を一部で採用されているのが、H2MD.js です。
有償ライブラリってことで直接触れるわけにもいかなかったのだけど、
色変換のコードで丁度いい教材があったので紹介。

可読性が悪くなる=プロプライエタリなJavaScript製品を作るなら積極採用せよ!とも考えられるのです。



とはいえ、まだ、0.1.2 のベータにも満たない段階。

syntax sugar


function Ctor(){};
Ctor
	.const({
		CONST: "CONST_VAL"
	})
	.static({
		staticVarName: "STATIC_VAR"
	,	staticFuncName: function(){/* omitted */}
	})
	.member({
		varName: "memberVal"
	,	methodName: function (){/*omitted*/}
	})
	.fix({
		objectName: "Ctor"
	})
	.extra({
		accessorVal: "defaultAccessorVal"
	,	buf: []
	,	onDefaultEventHandler: function(){
			return 'rsltOfInternalProcess'
		}
	})
	.accessor({
		accessorName: {
			set: function(v){
				this.varName = v;
				// or this.accessorVar = v;
				// or this.undefinedVarName = v;
			}
		,	get: function(){
			}
		}
	,	onlySetterName: function(v){/* omitted */}
	,	onlyGetterName: function(){/* omitted */}
	})
;

var instance = Ctor.create({/* init values */});
var clone = instance.clone();

Property definition API

const( dataPropSettings )

property-value enumeration read/write
no function enumerable readonly
function cant define

static( dataPropSettings )

property-value enumeration read/write
no function enumerable read-write
function enumerable readonly

member( dataPropSettings )

property-value enumeration read/write
no function enumerable read-write
function enumerable readonly

fix( dataPropSettings )

property-value enumeration read/write
no function enumerable readonly
function cant define

extra( dataPropSettings )

property-value enumeration read/write
no function no enumerate read-write
function enumerable read-write

accessor( accessorPropSettings )

property-value enumeration read/write
アクセサプロパティ enumerable ----

Prototypical inherits API

  • single inheritance
  • Mixin (Conditionally multiple inheritance)
#1 single inheritance
SubCtor.inheritsFrom(SuperCtor)

#2 Mixin
SubCtor.inheritsFrom(SuperCtor, MixCtor1, MixCtor2, ..)



"robust.0_1_2.min.js" で配備します。

  • コンストラクタは Named NOOPであること。
  • configurable は全て false になります。継承しない限りパッチを当てられない仕様。
  • inheritsFrom() は他のAPIに優先してメソッドチェーンに記述しなければならない。
  • new 演算子が嫌いって人のためにスタティックな crate() メソッドを実装。
  • clone() インスタンスメソッドも実装。
ES5以降のJavaScript実行環境用で書いたつもりだけど、ブラウザでしか動作確認できていません。

ライセンスは未定。現状は公開環境で使わないことを条件でお願いします。

Mixin をコンストラクタレベルで実装するのに苦労した。



チラシの裏的メモです。

自作の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 だけど書き換えできない」っていうプロパティのほうが、圧倒的に堅牢だから。


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

追記)2016-07-15
とりあえず、成果物。APIの名称は変わりますが。


結果、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 が流行ってたんですねぇ。あのデータ構造は、個人的に腑に落ちないので、頑張って開発します。



このページのトップヘ