JavaScriptをこれから勉強しようと思っている人へ(jQueryよりprototype.jsを薦める理由)

JavaScriptは手軽で、実用的なプログラミング言語

どのくらい手軽かといえばJavaScriptを始めるのに(HTMLとCSSは出来た方が良いけど)予備知識はほとんど必要なくて、ブラウザとエディタさえあればすぐにJavaScriptを動かすことができる(FirefoxFirebugを入れればコンソールでその場実行できるのでエディタも(リロードも)不要)。
その上実用性も十分で、最近のウェブサービスのバックエンドはPerlPHPJavaRubyPythonなどなど様々だけど、フロントエンドは必ずといっていいほどJavaScriptが使われている(FlashもあるけどフルFlashサイトでない限りJavaScriptも使われているし、そもそもFlashを表示するのにJavaScriptが使われていたり)。とにかくJavaScriptが必要とされる場面はすごく多い。

と、こんな理由でJavaScriptに対する需要は高まっているのかなーと思う。でもJavaScriptは取っ付き難いところがある。それはブラウザごとにJavaScriptの実行エンジンが異なることや、同じブラウザでもバージョンごとに細かい差異があったりするのが主な原因で、「クロスブラウザ」が凄く面倒。

クロスブラウザ対応のためのライブラリ

で、これからJavaScriptを勉強する人はどうすればバッドノウハウを乗り越えてJavaScriptを効率的に学習できるのかといえば、(すごく当たり前だけど)ライブラリを使えばOK。
JavaScriptのライブラリは沢山あるけど、汎用ライブラリで有名なところでは、prototype.js , jQuery , YUI , Dojo , MochiKit あたりかな。
割と古くから良く使われているのはprototype.js、最近人気なのはjQueryjQueryは本当によくできている。JavaScript初心者から上級者まで使えるすばらしいライブラリだと思う。もちろんprototype.jsもよくできているが、ある程度JavaScriptを書けるようになったらprototype.jsグローバル変数の使い方、prototype汚染が気になってくると思う。実際、僕はちょっと前までprototype.jsを使っていたが、最近はjQueryを使っている。jQueryは痒いとこに手が届く、気の利いたライブラリなので簡単にやりたいことを実現できるし、プラグインでいろんな機能を追加できるから本当に便利なのだ。

しかし。
初心者もjQueryを使うのがお勧めなのかというと、そうともいえないと面がある。というのは、jQueryJavaScriptの学習には適さないところがある(と思う)からだ。

JavaScriptの効率的な学習方法?

そもそもJavaScriptの効率的な学習方法とは何かといえば、僕はライブラリ依存のコードを依存をなくすように書き直してみるのが最も良いと思っている。
具体的には、「まずライブラリのマニュアルとかを参考にコードを切り貼りして動くものを書いてみて、それをライブラリに依存しないように自分で書き直してみる」という作業だ。正解といえるものがある状態でプログラミングできるので、ブラウザの仕様やバグに振り回されることを最低限に抑えてJavaScript自体を学習できるはずだ(実際そううまくいくとは限らないが)。
で、この方法を実践するのに最も適したライブラリはprototype.jsで、適さない(と思う)のはjQueryなのだ。
prototype.jsは比較的読みやすいコードになっている(ことが多い)が、逆にjQueryはこんな書き方があるのかと感心するようなコードばかりで、言い換えれば初心者が理解するのは難しいコードになっている。
この辺りは具体的にコードを見てもらうと説得力があると思うので(適当に)イベントを付加するコードを取り上げてみる。
まず、prototype.jsのEvent.observe関数の実質的な中身*1はこんな感じ。

function(element, eventName, handler) {
  element = $(element);
  var name = getDOMEventName(eventName);

  var wrapper = createWrapper(element, eventName, handler);
  if (!wrapper) return element;

  if (element.addEventListener) {
    element.addEventListener(name, wrapper, false);
  } else {
    element.attachEvent("on" + name, wrapper);
  }

  return element;
}

続いて、jQueryjQuery.event.addを見てみる。

function(elem, types, handler, data) {
	if ( elem.nodeType == 3 || elem.nodeType == 8 )
		return;

	// For whatever reason, IE has trouble passing the window object
	// around, causing it to be cloned in the process
	if ( jQuery.browser.msie && elem.setInterval != undefined )
		elem = window;

	// Make sure that the function being executed has a unique ID
	if ( !handler.guid )
		handler.guid = this.guid++;
		
	// if data is passed, bind to handler 
	if( data != undefined ) { 
		// Create temporary function pointer to original handler 
		var fn = handler; 

		// Create unique handler function, wrapped around original handler 
		handler = function() { 
			// Pass arguments and context to original handler 
			return fn.apply(this, arguments); 
		};

		// Store data in unique handler 
		handler.data = data;

		// Set the guid of unique handler to the same of original handler, so it can be removed 
		handler.guid = fn.guid;
	}

	// Init the element's event structure
	var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
		handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
			// returned undefined or false
			var val;

			// Handle the second event of a trigger and when
			// an event is called after a page has unloaded
			if ( typeof jQuery == "undefined" || jQuery.event.triggered )
				return val;
	
			val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
	
			return val;
		});
	// Add elem as a property of the handle function
	// This is to prevent a memory leak with non-native
	// event in IE.
	handle.elem = elem;

	// Handle multiple events seperated by a space
	// jQuery(...).bind("mouseover mouseout", fn);
	jQuery.each(types.split(/\s+/), function(index, type) {
		// Namespaced event handlers
		var parts = type.split(".");
		type = parts[0];
		handler.type = parts[1];

		// Get the current list of functions bound to this event
		var handlers = events[type];

		// Init the event handler queue
		if (!handlers) {
			handlers = events[type] = {};

			// Check for a special event handler
			// Only use addEventListener/attachEvent if the special
			// events handler returns false
			if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
				// Bind the global event handler to the element
				if (elem.addEventListener)
					elem.addEventListener(type, handle, false);
				else if (elem.attachEvent)
					elem.attachEvent("on" + type, handle);
			}
		}

		// Add the function to the element's handler list
		handlers[handler.guid] = handler;

		// Keep track of which events have been used, for global triggering
		jQuery.event.global[type] = true;
	});

	// Nullify elem to prevent memory leaks in IE
	elem = null;
}

prototype.jsは必要最低限に近いコードだけ記述されていてコメントもない。対してjQueryはコードもコメントも盛りだくさん。コメントが多いほうが勉強しやすい気もするけど、jQueryの場合はコメントが必要な(コメントがないと理解しにくい)コードがたくさんあると考えたほうがよさそう。

というわけで、僕はprototype.jsをお勧めする。
ちなみに、他のライブラリはどうかといえば、YUIとDojoは巨大でしっかり作られているので、やはり初心者には向かない印象。MochiKitは巨大すぎず、凝りすぎず、でprototype.jsが大体わかってきたら手を出してみるといいんじゃないかなと思う。

あと、Greasemonkeyで勉強というのもある。これはそのうち。

*1:Event.observeをtoStringしたもの http://ss-o.net/jslibs.html