FC2ブログ

09 | 2019/10 |  11

  1. 無料サーバー

User forum-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

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


jQueryに学ぶJavascriptの基礎(2) this 活用──jQuery解読(17)

jQuery解読作業を進めるにつれ、Javascriptの基礎が如何に分かっていないか、ほぼ毎日のように痛感させられます。その意味ではjQuery解読は無謀なチャレンジであった訳ですが、それでも誤謬を犯すマイナスを埋め合わせて遙かに余りあるプラスがあることも毎日自覚されるので、恥を忍んで引き続きjQuery解読を進めるつもりでいます。

しかし、基礎の基礎が余りに分かってない自分に嫌気が指してきてしまい、匙を投げ出すような醜態は演じたくありません。そこで、自戒を込めて敢えて誤解していたこと、理解していなかったこと、不十分な理解に留まっていたことなどについて、つらつらと記述していきたい、と思います。

thisとは何か?

「this」──Javascriptを始めたばかりの素人は、直ぐに「thisって何?」と躓きます。しかも度々、あちこちで登場するので、どうも役割がいくつかあるらしいと気がつくと共に、混乱は深まります。

半可通のままでは駄目なことを自覚させられ、やむを得ず学習を深めると

  1. タグ属性に this と記述して(記述しなくても)そこから起動した関数内で this と記述すると当該タグを指し示すこと
  2. メソッドの場合に当該関数内で当該関数の起動元オブジェクトを指し示すこと
  3. new 演算子とコンストラクタ関数によって生成された所謂インスタンスの場合、当該コンストラクタ関数内(入れ子関数含む)や、プロトタイププロパティで設定するインスタンス関数内において、this が当該インスタンスを指し示すこと

などが次第に分かってきます。

▲ToTop

jQuery.jsで多用される this

この「this」が jQuery.js では多用されています。「これでもか!」というくらいあちこちに登場します。従って this をきちんと理解しないと、jQuery は殆ど理解出来ません。しかしそれは決して難解なことではありません。this の定義さえしっかり踏まえれば容易なことです。

jQuery.js コード内において、第一に、インスタンスメソッド内の、つまりプロトタイプオブジェクトのプロパティ内における this は、上の3番目の定義からインスタンスを指し示すことになります。第二に、クラスメソッド内の this は上の 2番目の定義からクラスを指し示します。

jQuery.jsにおけるクラスとは、そう機能するように定義された グローバル変数 jQuery(及びそのコピーである 変数 $ ) に他なりません。

第三に、そして興味深いことは、ユーザーによる jQuery() 実行指示時においても、this を効果的に使用することが出来ることです。例えば、jQuery for JavaScript programmers から引用すれば、

//引用ブロックのテキストコンテンツを順に alert する
jQuery('blockquote').each(function(el) { alert(jQuery(this).text()) }); 

//マウスオーバー時とマウスアウト時の背景色をそれぞれ変更する
jQuery('a').hover(
  function() {
    jQuery(this).css('background-color', 'orange');
  },
  function() {
    jQuery(this).css('background-color', 'white');
  }
);
のように利用出来ます。

そしてどの様な場合においても、「this とはそれが指し示すオブジェクトへの参照である」ことをきちんと踏まえていれば、コードを解読する場合や記述する場合においては、this をそれが指し示すところのオブジェクトそのものであると「思いこむ」ことが手っ取り早い、現実的なthisの利用方法であると思います。

それにしても上のコードにおける jQuery(this) ってどういうこと?───と一瞬思いました。これについてはこのエントリイの末尾で触れたいと思います。

▲ToTop

jQuery()インスタンスオブジェクトを「視覚化」する

それにしても、ユーザーが入力した jQuery(・・・) に返されるインスタンスオブジェクトは、具体的にはどんな内容のものなのでしょうか? それを知ることは決して意味のないことではありません。

なお、FireBugを使用すれば使いながら(on the fly)インスタンスオブジェクトの内容を見ることが出来ますから、敢えて表示することもない───と言うことも出来ます。しかし、ここでは敢えて学習のためにも、何はともあれインスタンスオブジェクトを表示してみたいと思い、それを行ってみました。

その方法は例えば、強引ではありますが、jQuery.js がインクルードされているサイト(勿論このエントリイでも構わない)を開いてから、FireBug を起動し、そのコンソールに次のコードを入力すれば、当該サイトの末尾に当該サイトのリンクタグ一覧を取得したばかりの、jQueryインスタンスオブジェクトのプロパティ一覧が追加されます。

また下のボタンをクリックすると下の関数を実行し、この頁の末尾に一覧を追加します。

(function($){
  var obj =$("a"), tmp="";
 for (var i in obj)
    tmp += "<div>" +i +" : "+ obj[i]+"</div>";
  $(document.body).append(tmp); //bodyに tmp 要素を追加する
})(jQuery);

指定した要素(上の例ではリンク要素)を取得したインスタンスオブジェクトのプロパティ一覧を見れば、当該要素が確かに「1つの配列のように」そのプロパティとして格納されていることが分かります。上の例で言えば、それは「 0 : http://・0・・改行、1 : http://・1・・改行、・・・・」のように表示されますから、 インスタンスオブジェクトに、それぞれプロパティとして格納されていることが分かります。勿論その他に 60 を越える数の、プロトタイプを利用して設定されたインスタンスメソッドやインスタンスプロパティが存在していることが、改めて分かります。

jQuery()インスタンスオブジェクトから指定した要素を取り出す

インスタンスオブジェクトの内容が分かったところで、次にそこから指定した要素ノードを抽出する方法を考えてみます。

それはたやすいことです。指定した要素オブジェクトは、単純な配列のように格納されているのですから、上の例で言えば $("a")[0]、あるいは $('a')['2'] とすれば目的を達します。実際 FireBug のコンソールに $("a")[0] を入力して実行すると、当該サイトの最初のリンク要素が <a href="・・・"> と表示されます。

序でに触れれば、プロトタイプを利用して作成されたインスタンスメソッドも連想配列を利用して自在に表示出来ます。

$("a")["index"]、$()["init"]、・・・・

この件については、jQueryに学ぶJavascriptの基礎(4) インスタンスオブジェクトからの値の取得について──jQuery解読(20) で再考しました。よろしければご覧ください。

▲ToTop

jQuery(・・).meth(・・・) の meth(・・・)内で this を利用する

次に、jQuery()のインスタンスメソッドである meth()において this をどのように使うのか、その方法を考えてみます。

メソッド内の this はその定義から、そのメソッドが実行されているオブジェクト、つまり jQuery(・・) を指し示すことになります。ところで、jQuery(・・) は init() メソッドによって、「 指定した要素ノードを要素とする配列を、そのプロパティの一部に内包しているインスタンスオブジェクト 」を返値として受け取っています。つまり meth()内の this はこの jQuery インスタンスオブジェクトを指し示すことになります。

jQuery(InsObj) を実行すると InsObj が返される

いきなりですが、jQuery(InsObj) を指示すると、init()メソッドによる初期化を経て、当該の InsObj が返値として返されます。init()を読めばそうなることは直ぐに分かりますが、次のコードを実行すればそれが視覚化されます。

※ InsObj はインスタントオブジェクトを意味します。

(function($){
 var InsObj = $(); //インスタンスオブジェクトを取得、引数はなくても問題なし
 InsObj = $(InsObj); //それを引数として $() を実行し結果を InsObj に代入
 for (var i in InsObj) 
  document.write ( i +" : "+ InsObj[i] + "<br />");
 document.close();
})(jQuery);

具体的な meth() 内の this 利用

改めて jQuery for JavaScript programmers から引用したコードを見てみます。(このエントリイのずっと上に表示)

jQuery('blockquote') または jQuery('a') 関数 が実行を終了した時点では、このオブジェクトは返値であるインスタンスオブジェクトを受け取っています。

そして直上の jQuery(insObj) の解明を踏まえれば、ずっと上の例におけるそれぞれの jQuery(this) は jQuery('blockquote') または jQuery('a') が受け取っているインスタンスオブジェクトをその返値として受け取っています。だからこそ jQuery のインスタンスメソッドである each() や hover() メソッド内において jQuery(this) を利用してjQueryインスタンスメソッドである text() や css() が使用出来る訳です。

jQuery() の挙動を解読する(9) jQuery.map()メソッド解読 upon ver1.3.2──jQuery解読(13)

このエントリイの改訂履歴
  • 初稿:2007/11/14
  • ver1.3.2 対応改訂:2009/3/18

全く知りませんでしたが、「map」とはプログラミング界では一般的な専門用語のようです。ある関数の引数として別の関数を受け取る関数を「高階関数(higher-order function)」と呼ぶらしい。その結果、ある配列の各要素を対象として順番にそれを操作した結果を返す関数を作ることが出来る、ということらしい。

C言語やPerlなどでは一般的なようですが、Javascriptの場合実装されてないため、each()メソッドと同様に、フレームワークで色々と工夫されている模様です。

cf. こちらに数少ないJavascriptの高階関数の説明があります。
第2回 JavaScriptの関数をマスターしよう - @IT

$.map()の例

まずjQuery.js 1.1.4の解説にあった例題を掲載します。

  • $.map( [0,1,2], function(i){ return i + 4; }); //result [4, 5, 6]
  • $.map( [0,1,2], function(i){ return i > 0 ? i + 1 : null; }); // result [2, 3]
  • $.map( [0,1,2], function(i){ return [ i, i + 1 ]; }); //result [0, 1, 1, 2, 2, 3]

これらの例によって、$.map()を使って、単純に配列要素に加算したり、条件づけて配列要素数さえ増減させたり、自在に加工出来ることがよく分かります。

そこで$.map()の定義を踏まえて独自のサンプルを作ってみます。

$.mapの定義から、引数となる関数には2つの引数(元の配列の各要素とインクリメント値)を渡していますので、jQuery.js1.1.4のサンプルではなかった2つの引数を取る場合を作成してみました。

▲ToTop

$.map() メソッド解読

このエントリイのjQuery.js 1.3.2における対象箇所
1146: map: function( elems, callback ) {
1147:  var ret = [];
1148:
1149:  // Go through the array, translating each of the items to their
1150:  // new value (or values).
1151:  for ( var i = 0, length = elems.length; i < length; i++ ) {
1152:   var value = callback( elems[ i ], i );
1153:
1154:   if ( value != null )
1155:    ret[ ret.length ] = value;
1156:  }
1157:
1158:  return ret.concat.apply( [], ret );
1159: }
1160:

map() メソッドの要所は 1152 行の var value = callback( elems[ i ], i ); にあります。map メソッドの第二引数である関数 callback の第一引数に、mapメソッドの第一引数である配列の各要素を渡し、その演算結果を value に代入し、この value を新たな配列 ret の要素として取込んでいます。これを元の配列要素数だけ繰り返した上で、作成された配列 ret を map メソッドの返値として返しています。

map() にはもう 1 つ注目すべき箇所があります。それは 1158 行の return 値です。

単に return ret ではなく何故 return ret.concat.apply( [], ret ) とするのか、その理由を以下で考え解明します。

実は ver 1.2.6 では該当箇所は次のようになっていました。

▼jquery.js ver 1.2.6
1187:    ret = ret.concat( value );
1188:  }
1189:
1190:  return ret;

concat() を使うことにより、value が配列の時には要素に分解してから ret に取込み最後に ret を返しています。

一方、1.3.2 では まず ret に単純に value を取込んでから、要素の取り込みが終わった配列 ret に対して concat 処理を施して、配列内配列をシリアライズしています。

▼jquery.js ver 1.3.2
1155:    ret[ ret.length ] = value;
1156:  }
1157:
1158:  return ret.concat.apply( [], ret );

どうして jquery.js ver 1.3.2 で #1155-1158 が変更されたのか、最初は疑問に思いました。そこで原点に返って concat メソッド自体を復習することにしました。

▲ToTop

concat() メソッドについて

少し脱線しますが、concat() メソッドの基本を振り返ります。

concat は或る配列に新たな要素を追加するメソッドですが、追加される要素自身が配列である場合には、その配列を要素に分解してから、元の配列の要素として追加します。いわば線形化するわけです。

例題を作ってそれを通じて学習を確実にしたいと思います。

■ 例 1
 var ary =[1,[5,8],4,9];
 ary.concat(10,[15,80]) //return: [1, [5, 8], 4, 9, 10, 15, 80]
 /* 組み込み元の配列 ary の要素配列 [5,8] は concat 後もそのまま
   ですが、引数の配列 [15,80] は、シリアライズされ、15,18 と分解されて
   配列 ary に取込まれています。
 */

しかし、次のように引数の配列内に更に配列が内包されている場合には、それはシリアライズされません。

■ 例 2
 var ary =[1,[5,8],4,9];
 ary.concat( 10, [15, [ "test", "sample" ] ] );
   //return: [ 1, [5, 8], 4, 9, 10, 15, ["test", "sample"] ]
 /* 配列 ary の要素である配列 [5,8] は concat 後もそのままですが、
   第 2 引数の配列 [15,["test","sample"]] は、外側だけシリアライズされ、
   15, ["test", "sample"] となりますが、["test", "sample"] は分解されません。
 */

▲ToTop

ary2.concat.apply(ary1,ary2) メソッドについて

map() メソッドの 1158 行 return ret.concat.apply( [], ret ) を分析するために、幾つかの例をチェックしてみます。なお、チェックには Firefox 3.0.7、Firebug 1.3.3 を使いました。

以下の 4 つの微妙に異なる concat メソッドの結果を比べると幾つかのことが分かります。

■ 例 3
 var ary =[1,[5,8],4,9];
 var ret =["昨日",["今日",500]];
 ary.concat.apply(ret,ary)    //["昨日", ["今日", 500], 1, 5, 8, 4, 9]
 ret.concat.apply(ret,ary)    //["昨日", ["今日", 500], 1, 5, 8, 4, 9]
 ary.concat.apply(ary,ret)    //[1, [5, 8], 4, 9, "昨日", "今日", 500]
 ret.concat.apply(ary,ret)    //[1, [5, 8], 4, 9, "昨日", "今日", 500]
  • concat.apply(a,b) メソッドは、第 2 引数配列だけを要素に分解する。
  • 結果配列の要素順は、apply メソッドの引数順となる。
  • 結果配列の要素順は、concatを a,b どちらの配列のメソッドとして作用させるかに無関係である。

▲ToTop

改めて map() メソッドの #1155-1158 について

以上を踏まえて map() メソッドの #1155-1158 を解明します。

▼jquery.js ver 1.3.2
1155:    ret[ ret.length ] = value; // ここでは単なる代入だけを行う。
1156:  }
1157:  // ここで一回だけconcat()を使用し、ret 配列内の配列を分解し ret
     // を更新する。ここに [].concat.apply( [], ret ) でも全く同様の
     // 結果が取得できる。しかし、concat.apply( [], ret ) ではエラーとなる。
1158:  return ret.concat.apply( [], ret );

▼jquery.js ver 1.2.6
       // イテレートの度に concat() メソッドを起動している。
1187:    ret = ret.concat( value );
1188:  }
1189:
1190:  return ret;

要点は以下の点です。単なる代入よりも時間を要する cancat() メソッドをイテレートする回数だけ起動する( ver 1.2.6 )よりも、最後に 1 回だけ concat() を起動する( ver 1.3.2 )方が全体の処理時間が短くなる。

Firebug を使って本当に処理時間が変わるのかを調査し、確認出来た

以下のサンプル結果により、concat() 適用回数を減らすことにより明らかに処理時間が短いことが分かります。

このサンプルは5000回loopさせながら、同一の 2 つの配列を作るものです。変数定義時点からタイマーをスタートさせ、コンソール出力させてからタイマーを止め、その間の所用時間を計測しました。

▼毎回 concat() 適用の場合
console.time("Timers")
var i = 0, ary = [], ret = [];
while ( i < 5000 ){
    ary.push( i );
    ret = ret.concat( ary[i] );
    i++;
}
console.log(ret);
console.timeEnd("Timers"); // 計測結果 Timers: 1035ms

▼最後に一回だけ concat() 適用の場合
console.time("Timers2")
var i=0,ary=[],ret=[];
while ( i < 5000 ){
    ary.push( i );
    ret[i] = ary[i];
    i++;
}
    ret.concat.apply([], ret)
console.log(ret);
console.timeEnd("Timers2"); // 計測結果 Timers2: 669ms 半分以下で完了!

jQuery() の挙動を解読する(8) jQuery.find() クラスメソッド解読──jQuery解読(12)

  • 初稿:2007/11/14
  • 改訂:2007/11/21……jQuery.js の挙動を FureBug により追跡し、表にまとめた。
  • 改訂:2008/02/03……抜本改訂(上の表は削除した)

jQuery.find() クラスメソッドは何をするのか?

直前のエントリイで jQuery( args ).find( selector, context ) について概観しました。$().find() メソッドは僅か5行しか記述がありませんが、その僅かの記述の中に、 $.map()、$.find()、$().pushStack()、$.unique() 及び $.data()メソッドと、4つの jQuery クラスメソッドと1つのインスタンスメソッドを呼び出しています。ここで最重要なメソッドは jQuery.find( selector,elem ) クラスメソッドで、find の具体的な検索処理はこのクラスメソッドが全部請け負います。

そこでこのエントリイでは、この長大な jQuery.find() クラスメソッドの解読を行います。

jQuery.find() の構造と各種サブルーチンメソッドの役割

jQuery.find() クラスメソッドは、1445-1635 行までの約 200 行もある長大なコードです。そこでまずその構造と各種サブルーチンの役割を概観しておきます。

下の表は$().find()から呼び出される各種のサブルーチンメソッドを登場順に一覧したもので、……より右の文章はそれぞれのサブルーチンメソッドの役割を簡潔に記したものです。

├init()
│ |
│ ├$(context).find(selector)
│ │ ├$.map(this,fn(elem){})……this 配列(=$(context)のインスタンス)の
│ │ │           各要素 elem を fn 関数で指定した内容に変換する
│ │ │└$.find(selector,elem)……elem の中から selector にマッチする要素を抜き出す
│ │ │  ├$.trim(selector)……selector の最初と最後にある空白を削除
│ │ │  ├$.data(elem)……elem に 固有 id 番号(uuid) を振る
│ │ │  ├$.trim()
│ │ │  ├$.merge(a,b)……a, b2つの配列を合体する
│ │ │  ├$.isXMLDoc(elem)……elem が XML要素かどうかを判定する
│ │ │  ├$.merge()
│ │ │  ├$.classFilter(r,m)……r 配列の中から m というclass名のelemを抽出/除外する
│ │ │  ├$.filter(t,r)……r 配列の各要素に t 文字列によるフィルタを適用する
│ │ │  │ ├$.parse……各種のフィルタを検索するための正規表現文字列の配列
│ │ │  │ ├$.filter()……再帰呼び出し
│ │ │  │ ├$(elem).not(sel)……sel に該当する要素を elem 配列から除外する
│ │ │  │ ├$.classFilter()
│ │ │  │ ├$.props……各種属性値の名称統一化のためのオブジェクト
│ │ │  │ ├$.attr……各種属性値を処理する
│ │ │  │ ├$.data
│ │ │  │ ├$.expr……":"に続く文字列などによるフィルタリングを行うためのオブジェクト
│ │ │  │ └$.grep(arr,fn(){})……関数で指定した条件に合う要素をarr配列から抽出する
│ │ │  ├$.trim()
│ │ │  └$.merge()
│ │ └$(args).pushStack(elem)……新たにjQuery(elem)のインスタンスを作り、そのプロ
│ │   │パティ値に今のインスタンスを格納してから新インスタンスを$(args)関数に返す
│ │   └$.unique(arr)……arr配列の要素で重複があれば重複をなくしその結果を受け取る。
│ │     └$.data()

上に見られるように、$.find() の中では、沢山の jQuery クラスメソッド等がサブルーチンとして処理を請け負い、役割分担して、context から selector に該当するエレメント等を検索することになります。

jQuery.find() メソッド全行解読

長大なコードなので内容に応じて適宜分節しながら解読を進めます。

1. 初期処理
1445: find: function( t, context ) {
1446: // Quickly handle non-string expressions
1447: if ( typeof t != "string" ) // tが文字列でない場合には
1448:  return [ t ];       // 配列[t]をリターンして処理を終える。
1449:
1450: // check to make sure context is a DOM element or a document
   // contextが要素ノードでもdocumentでもない場合には空配列を返して処理を終える。
1451: if ( context && context.nodeType != 1 && context.nodeType != 9)
1452:  return [ ];
1453:
1454: // Set the correct context (if none is provided)
1455: context = context || document; //context がなければ document を代入する
1456:
1457: // Initialize the search 検索処理用変数定義
1458: var ret = [context], done = [], last, nodeName;
1459:
2. 検索開始・前処理:(1460-1469)
1460: // Continue while a selector expression exists, and while
1461: // we're no longer looping upon ourselves
 /* 1462 行の while は 1620 行まで 159 行も続く長いフレーズである。
  * この中で文字列 t を様々に分解しながら切り取り、様々なフィルターに掛けて
  * それにヒットするかどうかを検証し、対象を抽出する。
  * そのための初期処理が 1463-1468 行である。
  */
1462:  while ( t && last != t ) {
1463:   var r = []; // 検索結果の要素ノードを格納する配列を準備
 // while 終了準備処置 これ以降の行で t の値が変更されれば last != t となるから 
 // while 処理が継続され、つまり以下の検索が反復される。
1464:   last = t;
1465:   // tの先頭と末尾にある空白文字を削除
1466:   t = jQuery.trim(t);
1467:   // 検索結果有無を示すフラグを「ナシ」とする
1468:   var foundToken = false;
1469:
3-1. 検索ケースA: 子要素 "> chars" 検索(1470-1487)
 /* "> 任意の文字列"形式による検索を行う箇所である。
  * つまり子要素指定が有る場合の抽出作業である。
  * 但し、検索対象文字列 t の先頭文字が > の場合を探しているので、selector
  * 文字列において、既に別の検索が処理された後の二度目以降の検索処理を行う箇所となる。
  */
1470:   // An attempt at speeding up child selectors that
1471:   // point to a specific element tag
     // 先頭文字が > でこれに何らかの文字列が続く場合の正規表現文字列
     //(1356行で定義済み)を re に代入
1472:   var re = quickChild;
1473:   var m = re.exec(t); // 検索対象 t に re が含まれるかチェック
1474:
1475:   if ( m ) { // 検索対象 t に re が含まれれば...
      //m[1] は quickChild の定義から chars=何らかの文字列 を指す
1476:    nodeName = m[1].toUpperCase();
1477:
 /* 検索対象 ret 変数の各対象毎に巡回し、
  * 更に ret[i]の第一子要素を初期値とし、その兄弟要素を巡回する。
  * ノードタイプが要素エレメントで、それが何らかの要素名か、あるいは
  * quickChild 検索で抽出済みのノードネームに等しければ
  * その子要素を r 配列に格納する。
  */
1478:    // Perform our own iteration and filter
1479:    for ( var i = 0; ret[i]; i++ )
1480:     for ( var c = ret[i].firstChild; c; c = c.nextSibling )
1481:      if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
1482:       r.push( c );
1483:
 /* 取得した検索結果配列を ret に代入する。これにより検索対象配列 ret が
  * 検索結果配列に置換される。
  * 一方、検索文字列 t から 今検索した文字列=「>何らかの文字列」を削除する。
  * 直前検索文字列削除後の t に空白が含まれれば検索を続ける。
  */
1484:    ret = r;
1485:    t = t.replace( re, "" );
1486:    if ( t.indexOf(" ") == 0 ) continue;
1487:    foundToken = true; // 検索結果有無フラグをtrue、つまり検索結果ありとする。
3-2. 検索ケースB: 子要素 "> [0-9A-z_]" 検索(1488-1521)

ケースBでは、(1)先頭に>があり、(2)その後に空白文字が 0 個以上あって、かつそれに(3)数字かアルファベットかアンダースコアが続くような検索文字列であるかどうかをチェックする。

ケースAでは、数字/アルファベット/アンダースコアではない文字、例えば:、,、#、$などが含まれていても構わなかったが、ケースBではこれらの文字は対象とされない。

 /* 正規表現文字列 /^([>+~])\s*(\w*)/iは次のことを意味する。
  * 先頭文字が>、+ あるいは ~ のいずれか(第一部分文字列)であって
  * その後に空白文字が 0 個以上あり、
  * 数字/アルファベット/アンダースコアが0個以上続く文字列を
  * 大文字小文字の区別なく探す
  */
1488:   } else {
1489:    re = /^([>+~])\s*(\w*)/i;
1490:    //t に対する re 正規表現文字列の検索結果を m に代入し、それが空でなければ
1491:    if ( (m = re.exec(t)) != null ) {
1492:     r = []; //空配列 r を準備(検索結果受け取り用)
1493:
1494:     var merge = {}; //空オブジェクト merge を準備(id照合用)
1495:     nodeName = m[2].toUpperCase(); // \w* に該当する文字列を大文字にして
1496:     m = m[1]; // m に第一部分文字列(>、+ あるいは ~ のいずれか)を代入
1497:
       // 検索対象ノードを巡回する。
1498:     for ( var j = 0, rl = ret.length; j < rl; j++ ) {
 /* 第一部分文字が ~ か + ならば(つまり兄弟要素を探す場合には)
  * ret[j].nextSiblingを、また第一部分文字列が > の場合には
  * ret[j].firstChild(第一子要素)を、それぞれ n に代入する。
  */
1499:      var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
         // その n 要素の兄弟要素を順に巡回し
          //そのノードタイプが要素ノードならば、それに uuid 番号を振って id に代入。
1500:       for ( ; n; n = n.nextSibling )
1501:        if ( n.nodeType == 1 ) {
1502:         var id = jQuery.data(n);
1503:
 /* 第一部分文字が ~ で(つまり兄弟要素を探していて)既に id 番号が振られた
  * 要素がある場合(正確に言えば既に 1507行で merge[id}==true が与えられて
  * いる場合)には、兄弟要素を検索するループから抜け出す。
  */
1504:         if ( m == "~" && merge[id] ) break;
1505:
 /* nodeName がないか、n の nodeName が先に求めた nodeName と等しい時に
  * 第一部分文字が ~ ならば、merge[id] に true を代入してから
  * 当該の n 要素を r 配列に格納する。
  * またもし 第一部分文字が + だった場合には(直近の兄弟要素を見つけるのだから)
  * 1つ見つかればもう探す必要はないので、兄弟要素を探すループから抜け出す。
  */
1506:         if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
1507:          if ( m == "~" ) merge[id] = true;
1508:          r.push( n );
1509:         }
1510:
1511:         if ( m == "+" ) break;
1512:        }
1513:     }
1514:
 /* 見つかった要素が入った配列 r を、検索対象要素が代入されている配列 ret に代入
  * し、検索文字列のtから re に代入された部分検索用文字列部分を消去し、かつその
  * 後の t 文字列の先頭と末尾にある空白文字を削除してから、t を更新する。
  * この節の最後に、検索対象有無を示すフラグを true とする。
  */
1515:     ret = r;
1516:
1517:     // And remove the token
1518:     t = jQuery.trim( t.replace( re, "" ) );
1519:     foundToken = true;
1520:    }
1521:   } //END OF 1488 else
1522:
4. 検索C:子要素検索ではない検索

以上の検索に引っかからない場合(つまり子要素を探すのではない場合)、次の検索処理が 1525 行から 1611 行までで行われる。この節での検索は selectors による複数条件指定、.className 指定、#idName 指定の場合が処理される。

1523:   // See if there's still an expression, and that we haven't already
1524:   // matched a token
1525:   if ( t && !foundToken ) {
 :     ・・・・・
1611:   }
4-1. 先頭文字がカンマである場合の検索前処理(1527-1539)

まず t が存在していて、検索結果有無フラグが false の時、1526-1539行でカンマを t から削除する処理が行われる。なおここでは検索そのものは行わない。

1526:    // Handle multiple expressions
1527:    if ( !t.indexOf(",") ) { // t の先頭文字が "," の場合
 /* そもそも、検索文字列にカンマが入るのは 
  * jQuery(context).find( selector1,selector2,・・・・,selectorN ) が履行されている時
  * である。
  * さて、ret 配列には 対象要素エレメントの集合である context が代入されている。
  * もし、ret 配列(それは [context] に等しい)の最初の要素が context そのものに
  * 等しいならば、元々 context に1つの要素しかなかったことになる。
  * つまり1つの要素ノードを対象として複数のパターンのセレクターを指示していること
  * になる。この時にはdoneに2つの空要素を代入することによって、jQuery.find() から
  * の返値を「なし」としている。
  */
1528:     // Clean the result set
       // context が1つの要素しかない場合、retを空白配列とする
1529:     if ( context == ret[0] ) ret.shift();
1530:
1531:     // Merge the result sets
       // 空配列を第一要素とし、それにret(これも空配列)が続く配列を done に代入する。
       // つまり done == [ [], [] ] とする。
1532:     done = jQuery.merge( done, ret );
1533:
1534:     // Reset the context
       // r と ret に [context]配列を代入
1535:     r = ret = [context];
1536:
1537:     // Touch up the selector string
       // 検索文字列 t の先頭文字(これはカンマである)を削除し、替わりに
       // 半角スペースに置き換える。これにより別の節での検索対象としている。
1538:     t = " " + t.substr(1,t.length);
1539:

次の検索処理は、いくつかの前処理が行われてから行われる。

4-2-1. "nodeName#idName" 検索の場合の検索前処理
1540:    } else {
1541:     // Optimize for the case nodeName#idName
       // 文字通り「ノードネーム#idネーム」の文字列を探す
1542:     var re2 = quickID; // 1357行で定義されている正規表現文字列
1543:     var m = re2.exec(t); //検索結果を m に代入
1544:
1545:     // Re-organize the results, so that they're consistent
       // m があればその要素を次のように並び替える。
       // [ 0, "#", idName, nodeName ]
1546:     if ( m ) {
1547:      m = [ 0, m[2], m[3], m[1] ];
1548:
4-2-2. 先頭文字が "." か "#" の場合の検索前処理
1549:     } else {
1550:      // Otherwise, do a traditional filter check for
1551:      // ID, class, and element selectors
1552:      re2 = quickClass; // 先頭文字に "." または "#"が1個以下ある場合の正規表現文字列
1553:      m = re2.exec(t); // t を re2 正規表現文字列でチェックしその結果を m に代入
1554:     }
1555:
4-2-3. 4-2-1及び4-2-2の場合の更なる検索前処理
1556:     m[2] = m[2].replace(/\\/g, ""); //chars文字列内から \ を削除
1557:
1558:     var elem = ret[ret.length-1]; //elem に検索対象要素配列の最後の要素を代入
1559:
4-3. やっと検索開始
 /* 検索文字配列 m の2番目の値が "#" の場合の検索を行う。
  * 変数 oid にgetElementByIdメソッドの結果を代入する。
  */
1560:     // Try to do a global search by ID, where we can
1561:     if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
1562:      // Optimization for HTML document case
1563:      var oid = elem.getElementById(m[2]);
1564:
 /* IE と Opera において発生するバグ( form要素内のname属性の値をid値と誤って
  * 認識するバグ)に対する対策を施す。
  * XPathにおいて「@id は id という名前のアトリビュートを選択する」ことを指すから
  * 1569 行によって、elem 内から id 属性値として m[2] を持つ要素が正しく選別される。
  */ 
1565:      // Do a quick check for the existence of the actual ID attribute
1566:      // to avoid selecting by the name attribute in IE
1567:      // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
1568:      if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
1569:       oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
1570:
 /* oid が見つかった場合に、m[3]がない、つまりセレクタ文字列が nodeName#idName 
  * 型ではなかった時か、あるいは nodeName#idName 型の時には oid のノードネームが
  * m[3] つまり nodeName に一致すれば [oid]を、そうでなければ空配列を、
  * それぞれ ret と r に返す。ここに空配列とはこの検索ルーチンでは目的の要素が
  * 見つからなかったことを意味する。
  */
1571:      // Do a quick check for node name (where applicable) so
1572:      // that div#foo searches will be really fast
1573:      ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
4-4. 全ての子孫要素を対象とした検索
 // 全ての子孫要素 (all descendant elements) を対象とした巡回検索を行う。
1574:     } else {
1575:      // We need to find all descendant elements
1576:      for ( var i = 0; ret[i]; i++ ) {
 /* 最初の文字が "#" で nodeName が有れば、その nodeName 名を tag に代入し、
  * そうでない場合、"." があるか m[0] が空白ならば(つまり quickID にも quickClass
  * にもヒットしなかった場合)"*"を tag に代入し、以上のいずれでもない場合には chars
  * 文字列を tag に代入する。
  */
1577:       // Grab the tag name being searched for
1578:       var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
1579:
 // tag が "*" で、ノードネームが object だったら tag に "param" を代入する。
1580:       // Handle IE7 being really dumb about <object>s
1581:       if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
1582:        tag = "param";
1583:
 // これまでの r 配列に対して、検索対象内のタグ名が「tag」である要素を追加する。
1584:       r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
1585:      }
1586:
4-5. class 名による検索
1587:      // It's faster to filter by class and be done with it
1588:      if ( m[1] == "." )
 // classFilterメソッドを利用して r 要素内から m[2] の条件に該当する要素を抽出する
1589:       r = jQuery.classFilter( r, m[2] );
1590:
4-6. ID名による検索
1591:       // Same with ID filtering
1592:       if ( m[1] == "#" ) {
1593:        var tmp = [];
1594:
1595:        // Try to find the element with the ID
1596:        for ( var i = 0; r[i]; i++ )
           // id 属性値が m[2] と等しければ
1597:         if ( r[i].getAttribute("id") == m[2] ) {
1598:          tmp = [ r[i] ]; //tmpにその要素ノードを要素とする配列を代入
1599:          break; //1596行の for ループを中断
1600:         }
1601:
1602:        r = tmp; // r にヒットした要素ノードが入った配列を代入
1603:       }
1604:
4-7. この分節での検索終了措置
1605:       ret = r; //検索対象ノード配列に検索結果を上書き代入
1606:     } //END OF 1574行のelse
1607:     // re2(quickID 又は quickClass正規表現文字列)に該当する文字列を
       // 検索文字列 t から削除する。
1608:     t = t.replace( re2, "" );
1609:    } //END OF 1540行の else
1610:
1611:   } //END OF 1526行の if
1612:
5. 最後の検索処理

子要素でもid名でもclass名でもない検索の場合、filter クラスメソッドを活用して検索を行う。

この filter クラスメソッドもまた長いコードなので、これについては別のエントリイで詳述する。

 // これまでの検索を終えてもなお、t に文字列が残っている場合
1613:   // If a selector string still exists
1614:   if ( t ) {
1615:    // Attempt to filter it
      // 検索対象要素 r に t 文字列によるフィルタを適用し結果を val 配列に代入。
1616:    var val = jQuery.filter(t,r);
1617:    ret = r = val.r; // 配列 valから要素ノードを抽出して ret と r に代入
1618:    t = jQuery.trim(val.t); // 配列 val から検索文字列を取りだして t に代入
1619:   }
1620:  }
1621:
6. find() クラスメソッドの終了処理
 // ここまで来ても t が残っていたらエラーが起こったはずなので、空配列を ret に代入
1622:  // An error occurred with the selector;
1623:  // just return an empty set instead
1624:  if ( t )
1625:   ret = [];
1626:
 // ret がありかつ context が ret の第一項値に等しければ、retを空配列にする
1627:  // Remove the root context
1628:  if ( ret && context == ret[0] )
1629:   ret.shift();
1630:
 // done に ret を併合し、その結果を return する。
1631:  // And combine the results
1632:  done = jQuery.merge( done, ret );
1633:
1634:  return done;
1635: },
return 連鎖がもたらす階層性

上述の第二のケースの場合、65行により新たなjQuery(context)インスタンスが生成され、上述の一連の流れを経てinit()メソッドが終了します。この流れではjQuery()インスタンスが二度作成されるため複雑な動きをします。

そこでインタープリターがどの様に各種メソッドを呼び出しているのかを Firebug を使って追跡・解読してみました。解読結果を以下に図示します。

なお、ここで行った追跡は、次のようなセレクタとメソッドを指定して、ローカルパソコン上のHTMLファイル上で行いました。追跡に使ったセレクタとメソッドは jQuery("p > a").size()です。

  1. jQuery.js がインクルードされるとグローバル変数 jQuery 及び $ 並びにこれら の prototype オブジェクトが生成され、定義されている各種 prototype プロパティが設定される。こうしてinit()メソッドの返値が配列であることも設定される。
  2. ユーザーが jQuery("p > a") の実行をインタープリタに指示する。ここに第一引数の expr は「<・・・>付きの html タグ名でもなく、# 付きの id 名称だけでもない文字列」となる。
  3. 指示されたインタープリタは jQuery()関数の定義に従い、(1)new 演算子を起動して空オブジェクトを生成し、それへの参照をキーワード this に与え、jQuery.prototype.init("p > a").prototypeを生成するする。
  4. ここに 既に 526 行(jQuery.prototype.init.prototype = jQuery.prototype;)により init.prptotype は jQuery.prototype に等しくなっているから、jQuery.prototype.init("p > a").prototype は jQuery.prototype.init("p > a") の実行となる。
  5. init("p > a")は jQuery("p > a") は init("p > a") の返値を受け取り、かつ 1. により init() の返値は配列とされているから jQuery("p > a") のインスタンス(=this)の型も配列となる。
  6. 指示を受けたインタープリタは、 jQuery("p > a") メソッドを実行し、jQuery.js に記述されているコードに従って、return new jQuery( context ).find("p > a"); を実行しようとする。
    しかし、context は与えられていないから、新たに init() コンストラクタが呼び出され、新たなインスタンスの生成が始まる。この時 this の参照先はそれまでの参照先を捨て init( context )コンストラクタによるインスタンスとなる。
  7. init()コンストラクタの引数は39行から documentとなり、42-45行により init()には[document]が返される。
  8. this.setArray(context) が実行されると、jQuery(context) インスタンスは context を唯一の要素とする配列 [context] を生成し、return this.setArray(context) によって当該配列が init(expr,context) メソッドの返値となる。
  9. ところで、この init(expr,context) は return this.init(expr,context) 行より呼び出されたのであるから、当該init()メソッドの返値は、jQuery( context ).find( expr ) コンストラクタに返される。
  10. かくして、やっと .find(expr) メソッドが開始される。

jQuery() の挙動を解読する(2) インスタンスプロパティ・メソッドとクラスプロパティ・メソッド──jQuery解読(6)

  • 初稿:2007/11/03
  • 改訂:2007/11/21……用語を正確に書き換えた
  • 再改訂:2007/11/27……ケアレスミスを修正した。
  • 再改訂:2009/3/20 jquery.js ver1.3.2 対応に

jQuery() と jQuery のそれぞれのプロパティとメソッド

init() 内では、随所に jQuery(s,c).prop や jQuery(s,c).method() と jQuery.prop や jQuery.methop() が多用されています。また init() だけではなく jQuery 全体でこのような表記が沢山出てきます。ここではこれらの差異に着目し、これらが互いに全く別のものであることを確認し、またそれぞれの役割を解明したいと思います.

まず jQuery.prop や jQuery(s,c).method() は jQuery(s,c) インスタンスのプロパティ又はメソッドです。これらは new 演算子とinit() コンストラクタ関数によって生成されるインスタンスオブジェクトのプロパティ又はメソッドであり、このインスタンスは this キーワードで参照出来ます。そしてそれは決して関数オブジェクトである jQuery 自体のプロパティやメソッドではありません

一方、jQuery.prop や jQuery.method() は関数オブジェクト jQuery のメソッドやプロパティであって、決して、new 演算子とコンストラクタ関数によって生成されるインスタンスオブジェクトのそれらではありません。

端的に言えば、前者はインスタンスオブジェクトのメソッドやプロパティであり、後者は jQuery クラスのプロパティでありメソッドです。

ここに、jQuery のプロパティやメソッドは jQuery.extend() メソッドの実行(jquery.js ver1.3.2 の 612 行以降など)により定義されますますが、その extend() メソッドは 562~610 行で定義されています。そして実に巧みだと思うのは、その extend() メソッドは、同時にインスタンスオブジェクトのメソッドとして、定義されていることです( 562 行)。こうして extend() メソッドは2つの機能を担っている訳で、jQuery クラスのメソッド及び jQuery() インスタンスオブジェクトのメソッドを拡張する手段として機能するようになっています。

そして、この拡張方法こそがプラグインを自在に作成することを容易に可能としていることも強調すべき jQuery の利点であり、同時にこの複層的構造が jQuery.js を難解にしている一因ともなっているのではないでしょうか?。

▲ToTop

jQuery() とjQuery.extend()による拡張箇所リスト(該当行数リスト)

extend() メソッドの実行によって jQuery クラスオブジェクトと jQuery()インスタンスオブジェクトの、それぞれのプロパティとメソッドが様々に拡張される訳ですが、それらは次のように多用されています。( 以下は jQuery.js ver1.2.1 非圧縮版 における行数)

▼ jQuery インスタンスオブジェクトの拡張
  • 1855-1941(Event 関係)
  • 2040-2126(Ajax 関係)
  • 2507-2665(Animation 関係)
  • 2904-2991(Offset 関係)
▼ jQuery クラスオブジェクトの拡張
  • 469-1008(基本)
  • 1023-1044(BoxModelチェック)
  • 1139-1565(DOM 関係)
  • 1943-1976(DOM Ready 関係)
  • 2137-2506(Ajax 関係)
  • 2693-2738(Animation 関係)

インスタンスオブジェクトの拡張に 241 行が、そして jQuery クラスの拡張に 1438 行も充てられています。合計すると 1679 行となり、これらに extend() の定義の為の行数 39 行(404-442行)を加えた1718行は、全行数 2965 行の約 58 %を占めることになります。extend 関数はjquery.js のまさにキーとなる関数と言えます。

▲ToTop

jQuery.jsインクルード時に定義されるグローバル変数 jQuery のプロパティとメソッド

インクルード時に定義されるjQueryオブジェクトのプロパティとメソッド図。クリックすると拡大図を表示します。

以上を踏まえて、jQuery.js インクルード時に定義されるオブジェクトやそのプロパティを一覧してみます。例えば firebug の DOM インスペクターでそれを確認することが出来ます。(左図参照)

左図はグローバルオブジェクトとして $ と jQuery が定義されていること、そして jQuery クラスの 64 項目のプロパティ及びメソッドがあることを示すものです。なお、uiプロパティは jqueryui.js をインクルードしているために発生しているもので、jQuery.js だけインクルードした場合には生じません。

▲ToTop

JSON形式データ取得テスト (2)──jQuery1.2を使ってJSONデータをクロスドメイン取得

関連エントリイリスト in this Blog

クロスドメイン対応となったjQuery1.2にて、Fc2ブログデータサーバーからJSONデータを取得した

jQuery1.1.xはクロスドメイン対応となっていなかったので、ブログと異なるドメインにあるJSONデータはマイブログからjQueryを使っては取得出来ませんでした。しかし1.2になってクロスドメイン対応となったので、既に書いたコード(JSON形式データ取得テスト)をより簡単にすることが出来ます。

そこで早速それを試してみました。以下の文章は「JSON形式データ取得テスト」で使用したものと全く同様のもので、ここでFc2ブログのマイファイルサーバーからJSON形式データを取得します。

ここをクリックすると下の文字が変わります。

ここがサーバーからの文字列に入れ替わります。

スクリプト

jQuery1.2適用により、一層簡単になったコードは以下の通りです。ここに打ち消し線部分は、クロスドメイン非対応のjQueryを使わずに、以前に独自に書いたコード部分を示します。jQueryを使うことによってコードはかなり短くなりました。

  //JSONデータを取り出すcallback関数定義
function callback(){
  var x =0, txt ="", obj, obj2;
    //jQueryのObject取得方法を参考に上記のJSON形式の
    //オブジェクトからプロパティを取得した。
  while ( (obj=arguments[x++]) !=null ) {
    for (var key in obj){
      for (var key2 in obj[key])
      txt +="<div>"+key+" : "+key2+" = "+obj[key][key2] + "</div>";
    }
  }
  //以上は全く変わらないが、以下は不要となる。
  $("#result").html(txt);  //折角なのでこの部分もjQueryを利用した
  document.getElementById("result").innerHTML = txt;
  var removeElem = document.getElementById("appendedScript");
  if (removeElem) document.body.removeChild(removeElem);
}

  //jQuery1.2利用
function insertTest(){
  var s = document.createElement('script');
  s.type = 'text/javascript';
  s.src = url;
  s.id = "appendedScript";
  document.body.appendChild(s);
   //↓がクロスドメイン対応jQuery1.2を利用した箇所
   //2番目の引数である関数は特に必要としないのでnull値とした
  $.getScript(fileServerURL,null); 
}

赤いボックスをドラグ&ドロップするとリサイズ出来ます。

続きを読む

jQueryはどのようにしてクロスドメイン対応を行っているのか?

それを知るには $.getScript() の挙動を知らなければなりません。

そこでFirebugを利用して挙動を追跡し、流れを理解しました。それはおよそ次のようになっていました。全てはjQuery独特のextend関数によって様々な関数が利用されていました。

  1. $.getScript()を実行すると、直ぐにget()関数が呼び出される。
  2. get()実行の結果、$.ajax()がたった1つの引数を伴って返される。
  3. ajax()関数によってどのようなリクエストが行われたのかチェックする。
  4. 対象URIが異なるドメインで、かつ目的がスクリプト取得だった場合、ヘッダーにスクリプトタグが追加され、それを使用後には削除する。

以上のように結局の所スクリプトタグをHTMLヘッダー部に挿入し、使用した後にそれを削除している訳で、こうした挙動は以前作成したコード(JSON形式データ取得テスト)におけるクロスドメイン対応方法と全く変わりません。

Sleipnirではajaxを使って取得したデータをうまく挿入出来ない

理由が全く分からないのですが、Sleipnir2ではクリックしてもファイルダーバーからのJSONデータを所定の場所に挿入することが出来ません。一方、Firefox、Opera、IE7では問題なくファイルサーバーからのJSONデータが所定の場所に挿入されます。

Sleipnir2でJSONデータをどうしてうまく読み込めないのか全く原因が分からないので困惑しています。

Firebug Liteをサーバーに置いて提供しているサイトがあった

Fc2ブログのMyファイルサーバーに置くことはやめたのです

何故ならばそのようにしてこのブログ以外からアクセスして利用することになった場合、Fc2の規約に違反する虞があるためです。

そこであれこれと無料サイトでファイル置き場として使えるものはないだろうか、と探したのですが、適当なものが見つからないため、Firebug Liteのブックマークレット作成は諦めていたのです。

ところが、あるサイトでそれが得られることが分かりましたので、早速使ってみようかと思います。

そのサイトとはFirebug Lite をブックマークレット化しました - WebOS Goodiesです。

こちらのサイトを開くとブックマークレットが掲載されていますので、それを利用者のブラウザのリンクバーに置けば、簡単にFirebug LiteをどのサイトでもIEや、Operaで使えるようになる訳ですから、大変便利です。

感謝!WebOS Goodiesさん!

現行最新版のjQuery でもクロスドメイン対応ではない

改めてjQuery本家サイトのDocumentを読んでみました。するとver1.2以降では異なるドメインからもスクリプトを取得できると書いてあります。現在のバージョンは1.1.4ですから現行バージョンでは、クロスドメイン対応してない、と言うことになります。

実際に試してみましたが、矢張り駄目でした。

test.jsをサーバーにアップロードして、そのURIをgetScripptの引数としてみました。ここに簡単にするためにtest.jsの中身は単純にfunction(){alert('HogeHoge')}としました。

その上で当エントリイ内にスクリプトタグを挿入し次のコードを書いてXMLHttpequestオブジェクトを利用してみました。

$(function(){
	$.getScript("http://blog1.fc2.com/x/Username/file/test.js", function(){
		alert("Script loaded and executed.");
	});
});

確かに何も変化しません。firebugで追跡してみたらエラーはドメインが見つからないことを示唆していました。

Dojoフレームワークでは既に異なるドメイン間のデータ受け渡しが可能となっているようですが、残念ながらjQueryの場合、9月に登場する予定の1.2を待たなければ駄目なようです。

と思っていたら・・・pluginがありました

上の記事を書いてから何気なく「クロスドメイン jQuery」でグーグったら何とクロスドメイン対応のプラグインがありました!!

その名もずばり jQuery JSONP plugin (iandeth.) ですから昨日から探していたものに、やっと到達した事になります。

今晩中にFc2ブログへのFc2ファイルサーバーからのAjaxな読み込みが可能となれば嬉しいのですが・・・

エントリイの前後が表示されない問題

それはFirebug Liteをアップロードしてから始まった。

任意のサイトをIEで閲覧している時に、jQueryfyブックマークレットを使って当該サイト上でjQuery.jsを使えるようにし、かつFirebug Liteをもインクルード出来ないか───と思い、Firebug Lite.zipを解凍したファイル全てを、このブログサーバーにアップロードしました。

その後このブログ上で、ではなく自分のローカルPC上のHTMLファイルにFc2ブログサーバーからFirebug Liteをインクルードしようとしたのです。

すると、数回繰り返してからこのブログの表示がおかしくなり始め、その後このブログ内でページを遷ろうとすると必ず表示できないエラーが発生するようになりました。

そして気がついたのです。この行為は規約違反であるということを!

いわゆる「ファイル置き場」として利用してしまった

無意識に、Fc2ブログサーバーをローカルPCから呼び出すファイルサーバーにしてしまっていたのです。

早速Fc2にメールを出し、違反を詫びると共にこうした過ちを繰り返さないことを約束したのです。

すると数日後にFc2からメールが来ました。そこには、支障なく見えていること及びファイル置き場として利用してしまったファイルを削除すべきこと、が書いてありました。

この後者の指示を受けて早速当該ファイルを削除したところ、やっと問題が解決したのです。

▲ToTop

お詫び

ここ一週間ほどの間に閲覧された方には、閲覧障害というご迷惑をお掛けして場合もあったかと思います。その理由は以上の通りですので、この場を借りてお詫びしたいと思います。

オンラインファイル置き場はGmailでも無理でした。

関連エントリイリスト in this Blog

firebug.js のファイル置き場を探して・・・

直前のエントリイで書きましたが、オンラインでファイルにアクセスできる無料のファイル置き場が欲しくなりました。有料のファイル置き場としては、Just System の インターネットディスク等が有名ですが、2893 MB もの巨大容量(GMail)が無料で提供されている今日、敢えて有料サイトを選択する必要はありません。(セキュリティ上の問題から有料サイトを選択する道はあるかもしれませんが。)

そこでGmailの巨大容量をファイル置き場としても利用する方法を色々調べてみました。

しかし結論から言えば、この方法も無理なようです。

Gmail Shell Extension や Gmail File Space (gspace) など

これらが有名なようですが、どちらもマイコンピュータ上に仮想ドライブを構成してExplorerから利用するのですから、Homepage上のリンク先としてのファイル置き場にはなり得ないと思います。

またこんなファイル置き場(ファイルバンク【FILEBANK】-無料でファイル保管・送信・シェアができる-)もありますが、7日間無料保管、つまり8日以降は有料になってしまうため、リンク先ファイルの置き場としては使えません。敢えて有料でも良しとする置き場を欲しない限り・・・。

GmailにfirebugLiteの全ファイルをメールで送ってみた

次に、Gmailにmail送信したFirebugLiteファイルのuriに対して、IEとOperaにおいて下記のコードを試してみましたが、これも成功しませんでした。

試してみた自作のブックマークレットは以下の通りです(可読性を高めるために適当に改行やインデントを挿入しています)。

javascript:(function(){
  var%20d=document.getElementsByTagName('body')[0];
  var%20s=document.createElement('script');
  s.setAttribute('src','http://jqueryjs.googlecode.com/files/jquery-1.1.4.pack.js');
  d.appendChild(s);s=document.createElement('script');
  s.setAttribute('src','http://mail.google.com/mail/xxx');   d.appendChild(s);alert('using jQuery & Firebug Lite!');
})();

ここに「http://mail.google.com/mail/xxx」は、 firebug.js をメール添付ファイルとして自分のGmailアドレスに送信し、その後に取得したその添付ファイルのurlです。

結局複数の一連のfirebugLiteに係るファイルが連動しているのに、その中の特定のファイルであるfirebug.jsにだけリンクを貼っても動かないということでしょう。やはりサーバー内のフォルダを管理出来る状態のURIが記述できない限り、無理なのでしょう。

解決策は・・・

ジャストシステム(インターネットディスク)やsony(Webpocket)の有料レンタルサーバーも調べましたが、単なるファイル置き場としての活用方法は沢山説明されているものの、Homepageのリンク先であるファイル置き場の説明はほとんどなく、そのような使い方が出来るのかどうか分かりませんでした。

自分のWebサーバーを持てば解決しますが、リンク先ファイル置き場のために敢えてWebサーバーを立ち上げるほどの時間も気力もありません。

というよりも、結局jQuery.jsをfirebugを使って色々試したい場合には、Firefoxを利用する、Firefoxだけを利用する、と言う選択肢が賢明なのでしょう。このケースではIEやOperaに拘るのは愚かな選択肢というべきでしょう。

jQuerify.jsブックマークレットを利用したい

jQuerifyブックマークレットを改訂

jQueryを勉強する上で、Firebugは欠かせないツールです。そしてFirebugを利用して、任意のサイト上でjQueryを試すことの出来るjQuerify.jsブックマークレット(Learning jQuery :jQuerify Bookmarklet)は大変重宝する優れものです。

元々FirebugはFirefoxのアドオンとして開発されましたが、IEやOperaでも一部の機能を利用できるFirebug Liteが開発されている(Firebug Lite)ので、jQuerify.jsブックマークレットもこれらのBrowserでも利用できます。

但し、オリジナルのjQuerify.jsブックマークレットは名前空間を汚染する可能性があり、またリンク先のjQueryのバージョンは1.1.3.1であり、既に1.1.4が公表されているため、次のように無名関数を利用し、リンク先を最新版へのものへと改訂し、メッセージもちょっと変えてて利用することにしました。

jQuerify.js ブックマークレット改訂版(無名関数利用)のコードは以下の通り

javascript:(function(){var%20s=document.createElement('script'); s.setAttribute('src',%20'http://jqueryjs.googlecode.com/files/jquery-1.1.4.pack.js'); document.getElementsByTagName('body')[0].appendChild(s);alert('using jQuery1.1.4!');})();

jQueryブックマークレットの利用

jQueryブックマークレットを利用する前提として、Firebug(firefoxの場合)又はFirebug Lite(IE、Operaの場合)を組み込む必要があります。前者の場合にはFirefoxのアドオンとしてBrowser自身に組み込めばお仕舞いで、簡単にjQueryブックマークレットを利用することが出来ます。

しかし、後者のFirebug Liteの場合には、jQueryブックマークレットを適用するサイトに、Firebug Liteをインクルードしなければなりません。しかしそれを、一々追加するのでは余りに面倒です。

そこで、Firebug LiteとjQuerifyを同時にインクルードするブックマークレットを作成し、容易にインクルード出来るようにすれば便利なはずです。

(for IE & Opera)Firebug LiteとjQueryを同時にインクルードするためのブックマークレットはFc2サーバーには置けない

それは先のjQuery.jsに、firebug.jsをインクルードするコードを追加するだけで簡単に出来るはずです。しかしここに大きな問題が1つあります。jQuery.jsのように、サイト上に圧縮しないそのままjsファイルがおいてあれば良いのですが、Firebug Lite の場合、当該サイトにはzip圧縮されたファイルしかありません。そのためどこか別の場所にfirebug.jsを置いておいて、そこからインクルードするようにしなければなりません。

Fc2ブログはファイル置き場を認めていません。従って、そこにファイルを置いて自分のPC上のファイルからアクセスしたり、他の方が利用する場合にはFc2ブログ利用上のルール違反となってしまいます。firebug.js をmyFc2サーバー上に置き、それをmyFc2ブログ以外から利用することは出来ません。

従って、firebug.js はファイル置き場として利用できるWebサーバーに置くか、自分のローカルPC上に置くしかありません。しかし、後者の方法を試してみましたがそれは駄目でした。ローカルPCにリンクするようなブックマークレットはうまく作動しませんでした。一方、ファイル置き場として使えるWebサーバーは知りませんし、また自分のサーバーは持っていません。

従って、firebug.js をネット上からインクルードするブックレットの作成は諦めました。

もしかしたら、巨大なGmailサーバーをオンラインファイル置き場として利用できるかもしれませんが、まだその方法は試していません。

jQueryの挙動を辿る

jQueryがどの様に動くのかを辿ってみたい

jQuery.jsもprototype.js共に、Javascriptを深く良く知らないと、そのコードの意味は理解できない代物です。これまでの借り物をコピー利用するような姿勢から脱却しなければ、それらの解読は望めません。

そこで、改めて各種書籍を読み直すと共に、それらframework全文を印刷して、ここ一ヶ月間日々睨み続けて何とか理解しようとしてきました。

しかし、その方法だけではとても解読出来ませんでした。

そこで、今度はそれらのコードの実行過程を辿ってみることにしました。まず、解読する対象は実際に使ってみたい jQuery としました。そして、随所にalert()関数を挿入して、引数の挙動を調べてみました。しかし、それでも「動き」は見えてきません。alert関数を設置した箇所の動きはピンポイントで見えますが、コード全体がどのように動いているのかは分からないのです。

コードの挙動を辿る方法は?

何とか進行過程を辿れないか?、と悶々としている中で、次のことを思い出しました。Firebugはコードのステップ進行を辿れるはずです。しかし、その使い方が分からないのでこれまで全くステップ進行を使ったことはありませんでした。

昨晩になって初めて、「ならばネット検索で」と思い立ち(何で今までこのことを思いつかなったのか不思議ですが・・・)、検索してみたら直ぐに結論をゲットすることが出来ました。

FireBugでコードのステップ進行をどの様な方法で行うのか、全く分からなかったのですが、このサイト(FireBugのDebuggerの使い方 - JavaScriptist)を見て、直ぐに合点がいきました。

そこで、早速jQuery.jsコード内にちょっとした加工を施し、更に進行過程を垣間見るHTML頁を作成して、進行過程を辿ってみました。

辿った結果、多くのことが分かりました

こうして、やっとjQueryの挙動を把握する第一歩を切り開いたのでした。

挙動把握の過程を綴ってみようと思いますが、とりあえず今日は「扉を開いた」ことを記しておきたいと思い、このEntryを書いてみました。

▲ToTop





jQuery (ver 1.2.1) の object 拡張方法をやっと解明!

このエントリイの改訂履歴
  • 2007/08/15:初稿
  • 2009/3/6:改めて読んでみて懐かしくなり、部分的に追記/改訂

それは10日夜からの足かけ5日間の苦悩の末に突如開けた!

jQueryの使い方は簡単ですが、そのコードを理解することは私にとっては大変難しいものでした。そうなのです。過去の4日間、「どうしてあのようにしてobjectを拡張できるのだろうか?」───とずっと悶々としてきました。

それが極めて初歩的な方法ですが、jQueryのコードの中で分かりにくい箇所にalert関数を追加し、その進行過程を取りだし分析する、と言う方法で解明することが出来ました。嬉しい!!

ここでは解明できた、jQuery におけるobjectそのものの拡張方法について綴ってみようと思います。

まず苦闘してきたコードの該当箇所を抽出しておきます

それは次の2つのコードです。jQuery オブジェクト自身を拡張している箇所です。最初は、prototypeプロパティにおいて引数を操作し、かつそれをjQuery.extendメソッドに複写しています。

次にこの複写されたjQuery.extendメソッドを、無名オブジェクトつまり{name1:func1,name2:func2,……}をたった一つの引数として定義し、実行しています。

ここで難解だったのは、定義ではなく実行の場合の jQuery.extend() メソッドが、たった 1 つの引数しかとらない場合です。頻繁に登場する jQuery.extend() メソッドにおいて、その引数はたった 1 つのオブジェクトである場合が多く、他方、そのオブジェクトには、多数のキーと値をもつプロパティが定義されています。

さて、そのたった1つの引数を取る場合、while が効果的に働き、しかも jQuery.extend() メソッドのたった 1 つの引数であるオブジェクトの多数のプロパティが、そのキー名と一緒に target 配列に順番に代入されていることでした。( i がキー名であり prop[i] がプロパティの値 )

この時点ではまだ x++ と ++X の差異について極めて初歩的なことであるにも拘わらず認識していませんでした。その結果次の段のような疑問を持ってしまったのです。以下打ち消し線部分はx++の動作について誤った認識をしていた時点で書いてもので、明らかに内容が間違っていることを示しています。

引数オブジェクトの中のプロパティのキー名と値を arguments オブジェクトを利用して取りだしているわけで、どうしてこんなことが出来るのだろうか、ずっと頭を悩ましていました。

しかし、いくらコードを睨み付けて思案してみても、解明できるほどの知識はありません。

そこで alert を jquery.js のあちこちに挿入し、動く過程をチェックしてみました。

▲ToTop

そして分かったのです。arguments オブジェクトの独特の利用法が!

引数オブジェクトの中に定義されているプロパティを、次の部分のコードによって巧みに取りだしているということ、及びその取り出し方が理解できたのです。

それはこれまで遭遇したことのない arguments オブジェクトの使い方であり、極めて独創的に思えました。Javascript1.5 なり 1.6 なりで新たに定義され拡張されたargumentsの仕様なのかどうか、皆目知りませんが、こうした方法で引数オブジェクト内のプロパティを抽出できることは大変な驚きでした。

2009/3/6追記:上記の記述は今から振り返れば、いかにも拙い内容となっています。この時点ではこの程度しか知識がなかったのだなぁ・・・と感慨に耽ってしまいました。

for in ループによってオブジェクト内のプロパティを順番に取り出すこの方法は、prototype.js にも使われているありきたりのコードでした。

早速例題を作ってみました。

それは以下のような短いコードです。

上のコードを firebug でテストしてみた所、最初に arguments[0] がオブジェクトとして alert され、続いて obj の 5 つのプロパティのキー名と値、並びに x の値( 5回とも 1 でした )が順番に alert されました。

こうして存在しない 2 番目の引数( arguments[1] )を利用して、arguments[0] オブジェクト内のプロパティを取りだしていることが理解できました。

どうしてこのような動作が可能となっているのかはargumentsオブジェクトの仕様上の問題かな、と思いつつ釈然としないままに、一応jQueryのオブジェクト拡張方法を何とか理解したのでした。

コメントによる指摘を戴くまでは、頭からa++をインクリメントしてから代入すると決めてかかっていました。ポストインクリメントというらしいのですが(++aはプリインクリメントと呼ぶらしい)、これまでさんざん forループ内でi++を使ってきたにも拘わらず、プリインクリメントと勘違いして頭を悩ましてしまったのでした。 (☆-◎;)ガーン !

Fc2ブログ投稿ブックマークレット別窓作業版

jQueryfyブックマークレットを改訂

jQueryを勉強する上で、Firebugは欠かせないツールです。そしてFirebugを利用して、任意のサイト上でjQueryを試すことの出来るjQueryfy.jsブックマークレット(Learning jQuery :jQuerify Bookmarklet)は大変重宝する優れものです。

元々FirebugはFirefoxのアドオンとして開発されましたが、IEやOperaでも一部の機能を利用できるFirebug Liteが開発されている(Firebug Lite)ので、jQueryfy.jsブックマークレットもこれらのBrowserでも利用できます。

但し、オリジナルのjQueryfy.jsブックマークレットは名前空間を汚染する可能性があるので次のように無名関数を利用するものへと改訂して利用することにしました。

jQueryfy.js ブックマークレット改訂版(無名関数利用)のコードは以下の通り

javascript:(function(){var%20s=document.createElement('script');s.setAttribute('src',%20'http://code.jquery.com/jquery-latest.pack.js');document.getElementsByTagName('body')[0].appendChild(s);alert('using jQuery!');})();

jQueryブックマークレットの利用

jQueryブックマークレットを利用する前提として、Firebug(firefoxの場合)又はFirebug Lite(IE、Operaの場合)を組み込む必要があります。前者の場合にはFirefoxのアドオンとしてBrowser自身に組み込めばお仕舞いで、簡単にjQueryブックマークレットを利用することが出来ます。

しかし後者のFirebug Liteの場合には、jQueryブックマークレットを適用するサイトに対して、その都度Firebug Liteをインクルードする必要があります。

しかし、サイト毎に一々Firebug Liteをインクルードするのは面倒です。そこで、firebug liteをインクルードするブックマークレットを作成し、容易にインクルード出来るようにしたいと思っています。

時間切れにて<・・・続く・・・>

 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が開きます。

200711300058
200711141758
200711141601
200711031900
200709170059
200709102123
200709041808
200708310708
200708261210
200708251245
200708200148
200708150056
200511261217
FC2 Management