ローカルIDとタスクコンテキスト

 さて、マルチタスク化の際にまず問題になるのは、DOM要素のIDやタスク内変数の独立性である。本記事ではそれらについて解説していく。

 HTMLではタグにアクセスしてDOM操作をするという処理が多いが、下記例のようにタグを特定する際にIDを介する。

<div id="tag1"></div>
var applicationData="This's data.";
window.onload=function(){
	var tag=document.getElementById("tag1");
	tag.style.display='none';
	tag.innerHTML=applicationData;
}

 IDとはページに対してユニークであるため、一つのページに複数のタスク(この場合、物理タスクでも論理タスクでもよい。独立して動作するアプリケーション。)があった場合に、それぞれのタスクに定義したIDが競合する可能性がある。また、アプリーケーション内でグローバル変数を使うとタスク同士でグローバル変数が競合することもある。そこでIDにプレフィックスを付けて競合を防ぎたいが、その際に付けるプレフィックスはアプリケーション名ではなく、タスクID(タスクを起動する際に発行される数字。)とする。同じアプリケーションを複数起動する可能性もあり、その際に名前がぶつかっては困るからである。

 以下に実装例を示す。

Sys={
	tid_:0, //last task id
	task:null, //current task object
	tasks:{}, // all task list
	gid:function(name){return (this.task.id+"_"+name);},
	lid:function(name){var i=name.indexOf("_");return (i!=-1)?name.substr(i+1):name;},
	cid2tid:function(name){var i=name.indexOf("_");return (i!=-1)?name.substr(0,i):0;},
	$:function(name){return document.getElementById(this.task.id+"_"+name);},
	getContext:function(){return this.task;},
	tid:function(){return this.task.id;},
	selectTask_:function(task){this.task=task;}
}
Task=function(){
	this.id=++Sys.tid_;
	Sys.tasks[this.id]=this;
}
Task.prototype={
	lock:function(){
		var task=Sys.getContext();
		Sys.selectTask_(this);
		return task;
	},
	unlock:function(task){NSys.selectTask_(task);}
}

 大雑把に使い方を説明すると、Sys.gid(name)でタスク内コントロールをHTMLのIDに変換し、Sys.$(name)でDOM elementを取得する。Sys.getContext()で自タスクのコンテキストを取得し、Sys.tid()で現在のタスクIDを取得する。上記クラスを用いた書き方は下記のようになる。

var task1=new Task();

(function(){
	var l=task1.lock();
	// the beginning of task.oncreate
	Sys.getContext().applicationData="This's data.";
	document.write('<div id="'+Sys.gid('tag1')+'"></div>');
	// the end of task.oncreate
	task1.unlock(l);
})();

window.onload=function(){
	var l=task1.lock();
	// the beginning of task.oninit
	var tag=Sys.$("tag1");
	tag.style.display='none';
	tag.innerHTML=Sys.getContext().applicationData;
	// the end of task.oninit
	task1.unlock(l);
}

 なお、lock()/unlock()は一見排他処理に見えるかもしれないが、実際はタスクのカレントコンテキストを切り替えているだけである。lock()の返り値はlock前のタスクオブジェクトで、これをunlock()時に引数渡しすることで、リカーシブ・ロックのような使い方も可能になる。Win32APIのSelectObject()のような使い方だと言えばわかりやすい(人もいる)だろう。

 「マルチタスク」対応のためにlock()/unlock()をいちいち呼び出すのは面倒くさそうに見えるが、より実用的な実装ではイベントハンドラにてこれらの呼び出しを隠蔽する。参考のためにTeaOS的な書き方をすると下記のようになる。

(function(){
var App=Class.create({
	initialize:function(args){
		this.data="This's data.";
		Sys.find('frame').write('<div id="'+Sys.gid('tag1')+'"></div>');
	},
	handler:function(ev){
		switch(ev.type){
		case 't.init':{
			var tag=Sys.$("tag1");
			tag.style.display='none';
			tag.innerHTML=this.data;
			break;}
		default:break;}
	}
});
Sys.registerExecutable({
	create:function(args){return new App(args);}
});
})();

さて、余談であるが、この例ではHTML文書を動的に生成している。document.createElement()などのDOM操作を用いて、文書を生成したほうがコードの見栄えが良く、また、型チェックの問題が生じなくて良いという意見もあるかと思う。しかし、私は以下の理由から、出来るだけHTML文書を文字列で生成する方式を使うようにしている。(もちろん、DOM操作のほうが便利な場合はDOM操作を使うが。)

  • 文字列のHTML文書を渡した場合、構文解析するのはブラウザ内のネイティブコードであり高速。一方、Javascriptで細かくDOM操作の手続きを記述するとインタープリタによる処理となるためオーバーヘッドが多くなり遅い。
  • クライアントサイドで処理をしたくない場合に、サーバサイドJavascriptに処理を委譲するという選択肢もあるが、サーバサイドで複雑なDOMオブジェクトを構築すれば表示する際に多くの文字列への変換手続きを経なければならず遅くなるし、実装も困難になる。すなわち、文字列を生成する方法のほうがサーバサイドとクライアントサイドの実装がシームレスに可換である。
  • DOMに対する操作は親のDOMが生成された後のタイミングで可能になる。そのため、onload後に何度もDOM操作をすることになり、ブラウザの画面がフラッシュが何度も発生し表示が遅くなる可能性がある。(ただし、ブラウザの実装による。)
カテゴリー: html5_multitask   タグ: , , ,   この投稿のパーマリンク

コメントを残す