10 | 2007/11 |  12

  1. 無料サーバー

search phpbb-phpbb-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

anything from here

CSSやJavascript自習の苦闘史を綴っていきたい。恐縮ですがJavascriptを有効にしてご覧ください。
2005年12月から社会問題も掲載!


jQuery()の挙動を解読する(6) jQuery.clean()メソッド解読(2)──jQuery解読(10)

このエントリイのjQuery.js 1.2.1における対象箇所
33: jQuery.fn = jQuery.prototype = {
34:  init: function(selector, context) {
    ・・・・
39:   if ( typeof selector == "string" ) {
40:     var m = quickExpr.exec(selector);
41:     if ( m && (m[1] || !context) ) {
42:      // HANDLE: $(html) -> $(array)
43:      if ( m[1] )
44:        selector = jQuery.clean( [ m[1] ], context );
      ・・・・・・
82:   },
      ---------------------------------------------
765:   clean: function(a, doc) {
766:     var r = [];
767:     doc = doc || document;
768:
769:     jQuery.each( a, function(i,arg){
      ・・・・・・
854:   },

直前のエントリイにおいて、何とか jQuery.each() メソッドを解明できましたので、次に44行の jQuery.clean([m[1]],context) で呼び出される clean()メソッド内で、eachメソッドが どの様に呼び出されているのかを調べます。

769行の jQuery.each(a,function(i,arg))メソッドの引数は何?

直前のエントリイで明らかにしたjQuery.each()メソッドの定義から、呼び出されたeachメソッドの最初の引数 a は、44行と765-769行によって cleanメソッドの最初の引数、つまり配列 [m[1]]となります。またfunction()の2つ目の引数argは、やはり定義からaの要素、つまり 配列[m[1]]の要素 となります。そしてこの場合のeachメソッド(769行)は引数が2つですので、先に見た4番目のケースとなります。

こうして769行の jQuery.each(a,function(i,arg)) は、具体的には jQuery.each([m[1]],function(i,[m[1]]の個々の要素)) となります。

次に、eachで反復対象となる a、つまり [m[1]] はどのような内容となるのか、改めて確認しておく必要があるでしょう。m[1] は31-40行の定義上から、タグの属性や子要素に任意の文字を含むhtmlタグの羅列となります。例えば"<div><p>Hellow!</p></div>"のような文字列です。従って [m[1]].length=1であり、arg="<div><p>Hellow!</p></div>"であり、これも1つしかありません。つまりeachは一回だけ適用されることになります。

わずか1度しか使わないのにどうしてeachメソッドを使う必要があるのか、この点は不明です。

str.replace(RexExp,function())───(777-780行)

さて、clean()メソッドを先に進むと、775-841行で「Convert html string into DOM nodes」(html文字列のDOMノードへの変換)が行われています。その最初の部分はタグのXHTMLスタイルへの変換ですが、ここで興味深いのは replaceメソッドが引数に関数を取っていることです。

これまではstring.replace(regexp,newString) しか知らなかったのです。関数を指定できるようになったのはJavascript1.3以降だそうですから、決して最近のことではないのですが、関数を使ったreplaceの例はこれまで見たことがなかったのです。

さて書籍等に拠ればこの場合の関数の引数は、順に「ヒットした文字列」、「最初の部分文字列」、「2番目の部分文字列」、・・・となるようです。

正規表現文字列「/(<(\w+)[^>]*?)/>/g」によって arg 内から抽出された文字列が第1引数 m に代入され、allには第1部分文字列である <(\w+)[^>]*? (mから末尾の2文字"/>"を省いた文字列)が、またtagには 第2部分文字列である \w+ (タグ名になる)が、それぞれ順次代入されることになります。

例えば argが"<img src='test' alt='test' />"である場合には m="<img src='test' alt='test' />"、all="<img src='test' alt='test' "、tag="img" と順に代入されます。

それにしても、置換メソッドは何を目的としているのか、最初は分かりませんでした。関数の返値を見るとここでも正規表現によるパターン検索が登場します。tag名に代入されたタグ名を対象として、tag.matchでパターン検索しているのはみな終了タグがないタグ名です。そして、当該パターン検索にヒットした場合には m が、その他の場合にはall+</tag>が返されます。つまり、tag.match検索にヒットした場合には、閉鎖タグがないままの<tag名・・・/>が返値となり、ヒットしなかった場合には<tag名・・・></tag名>が返されるのです。

当たり前のことをやっているだけではないか、と最初は不審に思いました。

そこで以下のテストコードを作成しfirebugでチェックしてみました。

(function(arg){
var result = arg.replace(/(\w+)[^>]*?)\/>/g, function(m, all, tag){
    var tmp =[m,all,tag].join(", ")
    alert(" m, all, tag = " + tmp);
    return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i)? m : all+"></"+tag+">"
}
alert(result);
})("<div src='test' />");

上記のargに色々なタグ文字列を入れてみると、<img・・・/>はそのまま<img・・・/>となって帰ってくるし、/がない<img・・・>はそのまま<img・・・>で帰ってきます。そこで敢えて謝った記述 <div・・・/></div>をreplaceしてみたら、誤りを正して<div></div>が帰ってきました。

つまり、この置換メソッドは不正なXHTML構文を正す役割を負っているようです。ユーザーによって終了タグを記述しない誤記があった場合において、自動的に終了タグを挿入させるのです。しかし同時に終了タグがない要素名であって / が付いていないタグ記述に対して、それを付与する機能は持ってない、ということが分かりました。

▲ToTop

次には空白文字の削除とdivエレメントの生成が続いている───(782-783行)

jQuery.trim()───(911-913行)これは trim の名から推測されるとおり不要な空白文字列を削除するメソッドです。正確に言うとjQuery.trim(arg)メソッドは、arg文字列の先頭または末尾に存在する1個以上の空白文字列を削除し、途中にある空白文字は削除しません。引数tがなかった場合にエラーにならぬよう、(t||"")をreplace対象文字列としています。これによりarg文字列の先頭又は末尾にあるホワイトスペースが削除され、変数 s にその結果が代入されます。

このように空白文字を削除するのは、直後に生成されるdivエレメントと併せて、この後に続く"入力値に対する次なるエラーチェック"を行うためです。

var wrap で誤ったタグ記述を正しく修正する───(785-817行)

「clean」の意味はこのwrapを解明することでかなり見えてきそうです。結論を先取りすれば誤ったタグ記述を正しく修正する為のコードがこのwrapに続く部分です。

この部分では極めてトリッキーなコードが3つあります。

第一はindexOf()メソッドの特異な利用方法です。string.indexOf("str")は、string文字列内にstrが含まれる場合には 0 または自然数を返します。含まれなければ -1 が返ります。さて、否定演算子を前に置いて、!string.indexOf("str")とすると、string.indexOf("str") = 0 の時だけこの値はtrueとなり、その他はfalseになります。0 の否定だけが true だからです。

こうして !string.indexOf("str") は string 文字列の先頭に str が含まれる場合にだけ true となります。このトリッキーな方法を使って s 文字列の先頭に或る文字列が含まれる場合に、或る配列を指定しそれを wrap に代入しているのが785-810行です。

トリッキーなコードの第二は、&& 演算子及び || 演算子の使い方です。var wrap への代入値は、連続する || 演算子によってその前後にあるいずれか一つの値となりますが、そのいずれかを選択するのは && の前に置かれたコードです。&& の前に置かれた式の値が true の時だけ && の右の値が取得されるからです。

因みにここで行われることを馴染みのコードで書けば、極めて冗長ですが次のようになります。

&& と || の2つの演算子を巧みに組み合わせて条件分岐を行っているわけで switch 文を代行していることになります。この記述方法は自作コード内でも極めて重宝すると思います。

第三のトリッキーコードは極めつけです。817行の div=div.lastChild がタグの入力ミスを修正する役割を負っているのです。

どういうことかと言うと、wrapに代入された配列とargの値を組み合わせて、コード内で生成したdivタグ内に wrap[1] + arg + wrap[2] を代入していますが、これは例えば<table><tbody></tbody><colgroup><col></col></colgroup></table>のような文字列になります(argが<colを含んでいた場合)。そして815-817行によって、生成したdivタグ内に<col></col>を代入しています。

これだけ見ると元の値を返して一体何をしているのか?、と疑問に思いますが、実はこの一連のコードは入力ミスを修正してくれる機能を持っているのです。例えばjQuery("<col>abc</tr>").*** のようにタイプミスをしたとします。これがclean()メソッドに掛けられて<col>abc</tr>の部分が<col>abc</col>と修正されるのです。

どうしてこのような修正が行われるかと言えば、lastChild(別にfirstChildでも構わない)プロパティの効能です。一端ラッピングされたタグ文字列から DOM のプロパティを利用することによってエラーを修正しているのです。この方法には驚きました。そんな効能が DOM ツリー検索機能に存在していることなど、全く知らなかったからです。

とにかくこれらのトリッキーなコードを重ねて、ユーザーによるタグ標記のエラー訂正を行っているわけです。(785-817行)。

▲ToTop

IEのバグ対策───(819-838行)

823: if ( !s.indexOf("<table") && s.indexOf("<tbody") < 0 )
824:  tb = div.firstChild && div.firstChild.childNodes;

ここではIEによって自動挿入されてしまうことがある tbody 空要素 を削除しています。つまりバグ対策です。

まず、ユーザーが入力した標記文字列の先頭に <table が含まれ、かつ当該タグ標記文字列内に <tbody が含まれなかったら、tb に div の孫ノード全てを代入し(823-824行)、入力文字列がtableタグを含まず <thead や <tfoot だった場合には tbに div の子ノードを代入しています。(827-828行)つまりtableタグの子要素を抽出しています。

IEバグ対策のこのブロックでも && 論理演算子がトリッキーな使われ方をしています。

824行の tb = div.firstChild && div.firstChild.childNodes; です。これは2つのノードオブジェクトを論理演算子で結合してますが、この式は&& の両側が共に真の場合に真を返すわけではなく、
if (div.firstChild) tb = div.firstChild.childNodes;
と言う意味になります。

次に、tbノードの要素数を頼りにその最後から順に「子ノードを持たない tbody ノード」を抽出し(831行)、当該tbodyノードを削除しています。(832行)

以上によりIEが勝手に挿入するtbodyタグ(それは子要素を持たない)を削除することが出来るわけです。

IE対策の最後は空白文字の扱いです。arg の最初に空白文字がある場合に(835行)、その空白文字列を取りだして、補正されたarg つまりdiv.firstChildの前に挿入しています。(836行)

しかしこの部分の必要性と具体的な適用例は思いつきません。

▲ToTop

jQuery.clean()メソッド解読の最後(840-845行)

arg が文字列型であった場合の処理の最後に、
arg = jQuery.makeArray( div.childNodes ); (840行)があります。

これは、補正されたタグ文字列を arg 配列に入れるコードです。ここに、jQuery.makeArray() は915-926行で定義されていますが、「配列を作る」との名称から推測される内容とは裏腹に、safariのバグフィックスのようです。配列もどきを配列に格納して返値とするメソッドで、対象が配列の場合には当該配列が返されます。

とにかく840行によって補正されたタグ文字列は配列に格納されます。

最後に、arg が文字列型ではない場合の処理が続きます。既に直前の840行でタグ文字列は配列 arg に代入されていますので、元のargが文字列型であってもこの段階では誤記が修正された上で arg 配列に変化しています。

続く843-847行までの5行の解読は未だ出来ません。

849行では取得された配列が r 配列に合併されていますが(jQuery.merge())、元々 r 配列は空配列(766行)ですから、849行によって arg 配列が r 配列に代入されることになります。

そして最後の最後に、この r 配列を return して、長い長い jQuery.clean()メソッドが終了します。

jQuery.clean()メソッド全体の意味

途中でJavascriptの学習を深めながら、延々とinit()メソッド内で呼び出されるjQuery.clean()メソッドの解読を続けてきました。何とかその全容を解き明かすことが出来たものと考えていますが、最後に、init()メソッドから呼び出されたjQuery.clean()メソッドが果たす役割についてまとめておきます。

それは第一に、ユーザーが記述したタグ名(子孫を持つ場合を含む)の整合性をチェックし、もし記述にミスがあればそれを修復します。整合性は(a)終了タグがないタグ名が正しく記述されているかどうか、また(b)開始タグと終了タグがきちんと対になっているかを調べます。

第二に、tableタグにまつわるIEのバグ対策を行います。しかしこれはユーザー入力値とは無縁ですので、init()メソッドで呼び出されたjQuery.clean()メソッドではこの部分は利用されません。まだ分析していませんが、cleanメソッドが別の箇所から呼び出された時に効果を発揮する箇所なのだと推測しています。

第三に、formタグに関わるチェックがありますが、この部分は解読できていません。後日を待つこととします。

以上のように、jQuery.clean()メソッドは3つの清掃作業をおこなうメソッドとして定義されていると理解することが出来ます。

jQuery() の挙動を解読する(5) jQuery.clean()解読のためのjQuery.each()解読──jQuery解読(9)

このエントリイのjQuery.js 1.2.1における対象箇所
33: jQuery.fn = jQuery.prototype = {
34:  init: function(selector, context) {
    ・・・・
39:   if ( typeof selector == "string" ) {
40:     var m = quickExpr.exec(selector);
41:     if ( m && (m[1] || !context) ) {
42:      // HANDLE: $(html) -> $(array)
43:      if ( m[1] )
44:        selector = jQuery.clean( [ m[1] ], context );
      ・・・・・・
82:   },
      ---------------------------------------------
765:   clean: function(a, doc) {
766:     var r = [];
767:     doc = doc || document;
768:
769:     jQuery.each( a, function(i,arg){
      ・・・・・・
854:  },

jQuery.each()はどんなことをするための関数か?

何はともあれjQuery.each()の実例を見て何をするメソッドなのかを理解する必要があるでしょう。

jQuery.each(
 ["white","red","blue"],
 function(i, n){alert(i +"番目は : " + n +"です");}
);

上の例は jQuery.com Web サイトの例題をちょっと改訂したものです。序でに本家の解説も引用しておくと「普遍的な反復処理関数で、オブジェクトと配列の両方で継ぎ目なく反復処理を行う場合に使える。なおこの関数は$().each()とは別物で、何であろうと反復処理する場合に使用出来る。ここでcallback(呼び出)される関数の引数は2つあり、1番目はオブジェクトのキー文字列か配列のインデックスで2番目は値である。」

これを踏まえて対象が配列ではなくオブジェクトの場合の反復関数を作ってみます。

jQuery.each(
 {"a":"white","b":"red","c":"blue"},
 function(i, n){alert(i +" は " + n +" です");}
);

上を実行すると、「a は white です」「b は red です」「c は blue です」と順にalertされます。つまり jQuery.each()は、任意のオブジェクトか配列に対して、その内容を順番に取りだし何らかの処理をさせるための関数です。これは所謂イテレーターと呼ばれる関数であり、Javascriptそのものに実装されていないことから、それぞれのフレームワークであれこれと定義されているようです。prototype.js にも arrayObject.each(arg) なる形式のイテレーターがあった気がします。

さて、jQuery.each()は多くて3つの引数(obj, fn, args)を取り、objが配列であってもなくても、またargsがあってもなくても、fn なる callback 関数でそれぞれの要素又はプロパティを抽出し、その各々に対して何らかの処理をするよう定義されています。

▲ToTop

jQuery.each()の解読──概要編

569-589行で定義されているjQuery.each()は最大3つの引数(obj, fn, args)を取り、第3引数の有無と第1引数が配列であるか否かによって、都合4つのケースに分けて定義されています。

569:each: function( obj, fn, args ) {
570: if ( args ) {   //第3引数argsがある場合
571:  if ( obj.length == undefined ) //Case1:Objが配列でなければ
572:   for ( var i in obj )     //obj走査
573:    fn.apply( obj[i], args );  //obj[i].fn(args);を実行
574:  else              //Case2:Objが配列の場合
575:   for ( var i = 0, ol = obj.length; i < ol; i++ )
    //obj[i].fn(args); をそれが成立する間実行する。
576:    if ( fn.apply( obj[i], args ) === false ) break;
577:
578: // A special, fast, case for the most common use of each
579: } else {     //第3引数がない場合
580:  if ( obj.length == undefined ) //Case3:Objが配列でなければ
581:   for ( var i in obj )
582:    fn.call( obj[i], i, obj[i] ); //obj[i].fn(i,obj[i]);を実行
583:  else              //Case4:Objが配列の場合
    //obj[i].fn(i,obj[i]); をそれが成立する間実行する。
584:   for ( var i = 0, ol = obj.length, val = obj[0];
585:     i < ol && fn.call(val,i,val) !== false; val = obj[++i] ){}
586: }
587:
588: return obj; //objを返す
589:},

▲ToTop

jQuery.each()の解読──学習編

apply()メソッドの活用

jQuery.each()の4つのケースでは全てapply()又はcall()メソッドが活用されています。そしてこれらのメソッドは素人には馴染みが薄く、これまできちんと理解していませんでした。

しかし、それらが理解できなければ jQuery.each は全く分かりませんので、必死に学習した結果、今は何とか分かったような気持ちになっています。

fn.apply(obj,args)はobj.fn(args)であり、fn.call(obj,arg1,arg2,・・・)はobj.fn(arg1,arg2,・・・)であることが何とか理解出来、やっとjQuery.eachの解読を果たすことが出来ました。

なお、applyの第2引数 args は必ず配列でなければならないようですが、それがfnに引数として引き渡される時点では配列そのものとしてではなく、その要素が取り出されて渡されます。いわばargs.join()の結果文字列が引数に渡されると言えます。

そのことを試してみたのが以下のコードです。alertは 1,2,3,4,this,test と表示されます。

function doDisp(n){
 alert(n);
}

function doTest(){
    doDisp.apply(this,arguments) 
}

doTest([1,2,[3,4],["this","test"]])
if(・・・) break;文

576行目の if ( fn.apply( obj[i], args ) === false ) break;
には戸惑いました。falseでなかった場合、つまりbreakしない場合の処理がどこにも見あたらないからです。「if (expression) statement」のstatementがないのです。jQueryの動作結果から判断してfalseでない場合にはそれが for ループ文の出力結果となる、と理解せざるを得ないわけで、「そういうものか」と合点せざるを得ませんでした。

しかしよくよく考えてみれば、 fn.apply( obj[i], args ) の実行結果とfalseを同値演算子で比較しているということは、まさしく fn.apply( obj[i], args ) は実行されているわけですから、if文の statement がないとしても支障がないのだと判断し、自分を納得させました。

また、等値演算子ではなく同値演算子を使う理由についても調べる迄は全く理解できませんでした。もしこのケースで等値演算子を利用するとfalseを 0 に置き換えられてから左辺と比較されるため、この式は常にfalseとなってしまいます。どうしても同値演算子でなければならないわけです。

forループ文の特徴的使い方

584-585行目のforループ文も特異です。

ここでもまた等値演算子ではなく同値演算子が利用されています。そしてこのfor文は空オブジェクト{}を返していますが、fn.call(val,i,val) !== false;である限り、つまり fn.call(val,i,val) の実行結果がある限り fn.call(val,i,val) を返しています。こうして意味のあるコードとなっている訳でやっと理解は出来ましたが、難解でした。

次に、for に続くループ条件部分の()内も不思議の連鎖です。初期値が3項目(var i = 0, ol = obj.length, val = obj[0])あるfor文は初めて見ましたし、そもそも初期値をカンマで羅列出来ることも初めて知りました。

更に 条件が2つあるケースにも初めて遭遇しました。(i < ol && fn.call(val,i,val) !== false)

最後に増分値が単なるインクリメントではなく式であるケースも初体験です。

つまり、584-585行目のforループ文は未知との遭遇でした。

--------------------------------------------------------

次回はここで解明したjQuery.each()メソッドがjQuery.clean()メソッドからどの様に呼び出されるのか、そしてどの様な役割を負っているのかを解読する予定です。

▲ToTop

 90%近いシェアを握っているインターネットエクスプローラの描画エンジンを利用したタブbrowser。沢山のタブbrowserがあるが、多機能、カスタマイズフリー、スクリプト利用等で一日の長がある。Gekkoエンジンへの対応も行われ、IEからの自立独立の方向に向かっている。2005年7月にはIE7が登場する見通しの中で、今後の発展が望まれる。

 多様なCSS作成支援機能を備えた、タグ入力式 HTML&CSS作成支援エディタ。スキンデザインもすっきりしている。テキストエディター上で作成するよりも確実で安全にタグ打ちが出来る。
文字コードを選べないのが欠点。

 StyleNote同様のタグ入力式 HTML&CSS 作成支援エディタ。長年使用してきたが現在StyleNoteに乗り換えつつある。

 クリップボード履歴情報を活用する為のソフト。画像まで履歴を取ってくれるのが嬉しい。このソフトを使わない日は絶対ない程に重宝し、愛用している。

 起動中のウィンドウの「コピーできない」説明文などの文字列を取得し、コピー可能な文字データにするツール。何かと便利。

 ストリーミングデータを保存することが出来るソフト。動画利用には不可欠なソフトだ。

 無料ながらレイヤー機能を有し、スクリプトによる拡張も可能な、sleipnir作者が提供している優れもの画像編集ソフト。

 画面キャプチャソフトと言えばこれに勝るものなし、ではないだろうか? 様々な取得方法を有しており、ブログ作成にもHomepage作成に不可欠だ。Jtrimと並んでWoodyBellsの作品。

 複数ファイルの同時編集は出来ないが、透過pngも作れる画像編集ソフト。
(以下当該サイトから抜粋)初心者にも簡単に操作が出来るフォトレタッチソフトです。多くの加工機能で画像に様々な効果を与えることができます。非常に軽快に動作するため、ストレスなく操作できます。

 Animation Gifファイルを作れる無料ソフト。

 キャプチャソフト。画面内にサイト全体が表示しきれない場合でも、これを使えば全体をキャプチャすることが出来る。

 画像処理。画像のフォーマット変換のみならず、色数やサイズ、圧縮率の変更まで一括処理できてしまう『BatchGOO!』は、大量の画像をまとめて処理したいときに大変便利なソフト。BMP, TIFF, JPEG, PCX, PNG の相互変換をはじめ、色数・サイズ・解像度の統一、JPEG圧縮率の調節など、ホームページ用の画像や携帯電話用の壁紙を揃えるのに抜群の相性を見せる。(Vectorの当該ソフト紹介頁より抜粋引用)

 名前から直ぐに想像が付くように画像のサイズを測るためのソフトだ。Homepage作成には欠かせない。2カラム、3カラムのレイアウトを行う場合に大変重宝する。

 ランチャーソフトは沢山あるが、中でもこれが一押しだ。2年以上使ってきたがその操作性には毎日満足している。これを使い始めてからデスクトップには一切のアイコンを表示することをやめてしまった。

 AdobeReader7によって、起動時間が長すぎるという長年のユーザーの不満はある程度解消した。そのためこの高速化ソフトは存在価値が低下してしまったかもしれない。AdobeReader6迄はこのソフトによる起動高速化で恩恵を受けてきた。

 IE専用が難点だが、様々なサイト内でIDやパスワードを入力するのに重宝するソフト。コンテキストメニューから簡単に起動できるのがGood! sleipnir等のIEの描画エンジンを利用しているブラウザでも使える。

 利用しているパソコンの諸元値を取得するには、このソフトがベストだ。インストール済みソフトの一覧が取得できるのも嬉しい。

 WMPは機能が豊富なだけ重い。RealPlayerも同様だ。そこでMedia Player Classicを使いたい。動作が軽快なだけではなく、対応しているファイル形式もすこぶる多く、これひとつで、wmvもrmも表示できてしまうのだから凄い! 数多あるMedia Playerの王様と言えるだろう。

 自宅でPCを起動しているときには必ず起動しているメディアプレーヤー。何かと過剰なWinampよりも、起動も速くスキンはシンプルだ。

 DivX, Xvid, Mov, Vob, Mpeg, Mpeg4, avi, wmv, dv, などの動画をDVD-Video形式に変換できるフリーソフト。クリックするとDVD関連ソフト紹介サイト=「DVDなToolたち」なるHomepageが開きます。