Hatena::Groupbugrammer

蟲!虫!蟲!

Esehara Profile Site (by Heroku) / Github / bookable.jp (My Service)
過去の記事一覧はこちら

なにかあったら「えせはら あっと Gmail」まで送って頂ければ幸いです。
株式会社マリーチでは、Pythonやdjango、また自然言語処理を使ったお仕事を探しています

 | 

2011-06-10

[]精読jQuery 1日目 21:20

参考

第1回 jQueryライブラリ(1?171行目):jquery.jsを読み解く|gihyo.jp … 技術評論社

no title

1行目

/*!
 * jQuery JavaScript Library v1.6.1
 * http://jquery.com/
 *
 * Copyright 2011, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 * Copyright 2011, The Dojo Foundation
 * Released under the MIT, BSD, and GPL Licenses.
 *
 * Date: Thu May 12 15:04:36 2011 -0400
 */

1行目から15行目は著作権表記である。jQueryMIT及びGPL Version 2にライセンスされている。まだ、jQueryに混ざっているのはSizzle.jsというものであり、このSizzleはMIT,BSD,GPL Licenseに登録されている。

MITライセンスマサチューセッツ工科大学を起源とし、BSDライセンスをベースに作られている。MITライセンスは、著作権表記+無保障というだけで、どれだけでも再利用が可能になっている。また、GPL Version2に関しては、著作権表記の他に、無保障であり、かつソースコードが手に入る手段を明記しなければならない。だが、Javascriptの場合は、圧縮のために可読性が低くなる場合以外、ソースコードが読めなくなるということは殆ど無い……と思う。

Sizzle.jsは、jQueryの作者が作ったセレクタエンジンだ。セレクタとは、CSSなどで適応する要素を選択するものである。jQuery全体を使うより、セレクタエンジンのみが欲しければ、Sizzleという選択肢もある。

16行目

(function( window, undefined ) {
………
})();
無名関数、ローカル変数

全体のコードが無名関数になっている。無名関数とは、文字通り命名せずに関数を宣言すること。無名関数を使用するメリットとしては、関数を使い捨てにすることが可能であること、また他の関数変数名と衝突しなくて済むといったことがあげられる。ここでは、jQuery全体をローカルスコープに代入している。

スコープとは、その変数が有効な範囲のことである。スコープにはローカル変数グローバル変数がある。変数の優先順位は、ローカル変数のほうが先に来る。そして、ローカル変数が優先されると、グローバル変数が汚されることはない。ローカル変数か否か、を判定するのは、その関数の内部にて宣言(var)されたかどうかで判定される。ここで気を付けなければならないのは、例えばfor文でiを利用するさいに宣言をしていないと、グローバル変数の扱いになってしまうことがある。

さて、もう少しスコープについて見てみよう。ローカル変数は、関数の全体で有効である。また、ローカル変数はCallオブジェクトに格納される。このCallオブジェクトは、他に関数引数を格納している。グローバル変数はGlobalオブジェクトに格納されている。このようにして、グローバル変数とローカル変数が衝突しないようにする。また、ローカル変数を探すときに、過去に遡って変数を探し出す。このことをスコープ・チェーンと呼ばれる。*1

さて、Javascriptでは、出来るだけグローバル変数を使わないようにするのが、一つのテクニックとされている。その理由として、まず変数名や関数名の衝突を避けるということが一つと、上記のスコープ・チェーンにより、アクセスする効率が低い。またjavascriptではガベージコレクションが行われている。ガベージコレクションとは、プログラム全体において、その関数が使われなくなった時点で、その関数に割り当てられたメモリを開放する方法のことである。ローカル変数の場合、寿命がグローバル変数に比べて短いため、直ぐに開放されるのに対して、グローバル変数は残り続けてしまうというデメリットが存在する。そのため、グローバル変数を使われることは推奨されない。

window,undefined

さて、いきなりwindowを引数に取っている。windowは、ブラウザウィンドウに含まれる全てのJavascriptのグローバルオブジェクトになる。このオブジェクトが生成する関数を使って、ブラウザを操作することが出来る。では、jQueryは如何なるwindowの関数を使っているのか、といえば、答えは次の三行に書かれている。

19行目

var document = window.document,
	navigator = window.navigator,
	location = window.location;

上記は全てwindowの下に属する。windowオブジェクトは他にscreen,frames[],self.window.parentなどが属している。それら使用されていないwindowのオブジェクトは改めて調べてもらうとして、document、navigator、locationの役割は一体何か、ということをメモしておく。少なくともjQueryは以下のwindowオブジェクトの情報にアクセスする必要があると判断している。ちなみに、windowオブジェクトは省略可能なのだが、恐らく省略と判断出来ないブラウザに対して、documentを本来のwindow.documentと結びつけている、と判断したほうがいいかもしれない。

Document

まず最初に、このDocumentオブジェクトから下位の階層のことを、ドキュメントオブジェクトモデル(DOM)と呼ばれている。現在主流のブラウザでは、Documentオブジェクトは全て実装されているため、事実上の標準となっている。これをDOMレベル0と呼ばれている。Documentオブジェクトにはどんな階層があるかということはここを参考にして欲しい*2

navigator

navigatorは、ユーザーエージェントやOSの情報を修得することが可能になる。Javascriptを用いてブラウザ及びプラットフォームの判別を行う。なぜJavascirptにおいて、OSやあるいはブラウザの判定が重要になるかといえば、各ブラウザで採用されているJavasciptのエンジンが違うし、またレンダリングエンジンも違う。さらにいえば、ヴァージョン違いによる仕様変更などの問題などがある。さらにクロスブラウザ(未知のブラウザでも、Web標準であるなら、最低限の表示と動作を行う)を実装したりすることが重要になってくる。

クロスブラウザなどの諸問題に関してはこれでできる! クロスブラウザJavaScript入門:連載|gihyo.jp … 技術評論社を読むことをお薦めする。

location

locationは、現在のページに関する情報、特にURLに関する情報を持っており、それを設定することが出来る。documentが実際のHTMLを分析するのに対して、locationは、URLやホスト名、ポート番号、プロトコルなどを修得することが出来る。

22行目

var jQuery = (function(){

ここが実質上のjQueryの本体ということになる。

25-27行目

var jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    },

jQueryが呼ばれると毎回jQuery.fn.initのインスタンスが帰るようになっている。外側のjQueryは最後にreturn window.jQuery = window.$ = jQuery としている。ここで$とjQueryがひもづけされ、この内側のjQueryが呼び出されることになる。windowがブラウザ上の最上位にあたる。

31-37行目

    _jQuery = window.jQuery,

    _$ = window.$,

    rootjQuery,

ここでjQueryが退避させられていたり、あるいは$が退避させられている。ver1.2.1では、JQueryを二回呼び出したさいに、二つのjQuery関数が衝突しないようにする機能が搭載されていた。

    if ( window.$ )
        var _$ = window.$;

    window.$ = jQuery;

なにかしらのオブジェクトが存在しているか否か、を判定するのには、if文を使うことができる。ここで内側のjQueryで使われていたrootjQueryの正体も明らかになる。rootjQueryは、jQueryに紐づけられている。つまり、rootjQuery = jQueryということになる。

41-66行目

	// A simple way to check for HTML strings or ID strings
	// (both of which we optimize for)
	quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

	// Check if a string has a non-whitespace character in it
	rnotwhite = /\S/,

	// Used for trimming whitespace
	trimLeft = /^\s+/,
	trimRight = /\s+$/,

	// Check for digits
	rdigit = /\d/,

	// Match a standalone tag
	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,

	// JSON RegExp
	rvalidchars = /^[\],:{}\s]*$/,
	rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
	rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,

	// Useragent RegExp
	rwebkit = /(webkit)[ \/]([\w.]+)/,
	ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
	rmsie = /(msie) ([\w.]+)/,
	rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,

ここからずっと正規表現が続く。javascript特有の正規表現に関してメモしておく。

少し細かいことになるが、オブジェクトリテラルという二つのものが存在している。たとえば、文字列に関しても、文字列オブジェクトと文字列リテラルが存在している。たとえば、文字列オブジェクトはnew Stringで宣言されるのに対して、文字列リテラル引用符で囲まれている。両側のオペラントは一緒とは評価されるが、しかしデータ型は一緒だと判断されない。実際に、==(等価)と、===(厳密な等価)で両者を比較すると理解できる。前者はTrueが帰ってくるのに対して、後者はFalseが帰ってくる。

ここで宣言されている正規表現は、RegExpリテラルである。『Javascript クックブック』によれば、RegExpリテラルを使うべきか、それともRegExpオブジェクトを使うべきか、という問題に対してヒントを与えている。RegExpリテラルスクリプトが評価されるときにコンパイルされるため、式が変化しないことがわかっているならば、リテラルのほうを使うべきだろう、と説明している。

正規表現の使い方に関しては次のようになる。正規表現はマッチが存在しない場合はnullを返す。そして、マッチが存在する場合は情報配列が帰ってくる。

javascript正規表現にはキャプチャという概念が存在している。キャプチャとは、パターンにマッチした部分文字列をあとから参照できるように保存しておくことを指す。キャプチャは、パターンの該当する部分を丸カッコ()で括ることで使うことが可能になる。逆に非キャプチャグループというのも存在している。それは(?: )となる。またカッコの後ろに?が付くことによって、省略可能となる。

まず最初の正規表現であるquickExprに関してだが、コメントに書いてあるとおり、HTMLの文字列か、あるいはIDは文字列なのかを判断している。この場合、HTML配列の二番目に格納され、そしてIDタグは三番目に格納されている。

81-86行目

    toString = Object.prototype.toString,
    hasOwn = Object.prototype.hasOwnProperty,
    push = Array.prototype.push,
    slice = Array.prototype.slice,
    trim = String.prototype.trim,
    indexOf = Array.prototype.indexOf,

javascriptプロトタイプ継承に基づいている。プロトタイプ継承とは既存のオブジェクトに対して、新しいインスタンスを作ることによって再利用が可能になる。従って、クラスという概念を考えなくてもいい。また、全てのオブジェクトは単なる関数であったりもする。

Javascriptにおいて、オブジェクトは全てObjectを継承している。同様に、全てのメソッドと他のプロパティprototypeオブジェクトを通じて継承されている。とするならば、toStringに紐付けられているのは、全てのオブジェクトが持つ、全てのメソッドが所持しているだろうtoStringを包括し、同様にhasOwnも同様である。また、push、sliceも同様に配列が持つメソッドに属しているものに紐づけていると考えるのが適切だろう。

87-?

jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    init: function( selector, context, rootjQuery ) {
        var match, elem, ret, doc;

prototypeの意味は先ほど説明したとおりだ。ここでjQuery.fnjQuery.prototypeに紐づけられ、同時に機能が追加されている。

さて、ここでいきなり「:」が出てくる。これはオブジェクトリテラルを使用したさいに、プロパティと値のペアを作るために使用される。例えばConstructorならば、jQueryに紐付けされている。これはprototypeにならったものだと考えられる。

さて、ここにて先ほど見たであろうinitが出てくる。こいつがjQuery.fn.initに当たる。

97-99行目

    if( !serector ){
        return this;
    }

さて、同時にselectorがない、あるいは未定義の場合がある。そのときは自分自身を返す。コメントに書いてあるとおり、これは$(``)や$(null)や$(undefined)といったような記述が当てはまる。

102-106行目

        if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;
        }

nodeTypeを知るためには、javascriptDOM構造について知っておく必要がある。DOM構造はツリー構造になる。例えば、タグが開いたり閉じたり出来るHTMLエディタを使ったことがある人ならばピンと来ることもあるだろう。あるタグの始まりから終わりの中に、他のタグが始まり……といったような構造になっている。

当然、最上位に存在しているのがDocumentであり、その次に存在するのが<html>であり、さらにその次に始まるのが<head>と<body>……といったような構造を取る。これらをノードと言う。全てのノードオブジェクトはnodeTypeを持っている。つまり、selectorがノードオブジェクトか否かを調べるのには、nodeTyoeが存在しているか否かを調べたほうがいいのだろう。

109-115行目

		if ( selector === "body" && !context && document.body ) {
			this.context = document;
			this[0] = document.body;
			this.selector = selector;
			this.length = 1;
			return this;
		}

ではselectorがbodyであり、かつコンテキストが存在せず、さらに言うならdocument.bodyが存在しているとした場合、contextをbodyに設定し、jQueryの最初にdocumentを挿入し、returnを返す。

 |