10 | 2007/11 |  12

  1. 無料サーバー

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

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


jQuery() の挙動を解読する(3) jQuery.extend()及びjQuery.prototype.extend()──jQuery解読(7)

このエントリイの改訂履歴
  • 初稿:2007/11/6
  • 改訂:2008/1/4……多くのミスがあったため全面更新しました。
このエントリイのjQuery.js 1.2.1における対象箇所
427:jQuery.extend = jQuery.fn.extend = function() {
    ・・・・
465:},

extend メソッド解読の必要性

話が前後してしまいますが、先に進む前に extend メソッドを解読しておく必要があります。

まだ init() メソッドの解読途中ですが、先に進むためには init メソッド内の随所に鏤められている、extend メソッド実行によって拡張された、jQuery() メソッドや jQuery メソッドを解明しなければならず、そのためにはまず extend() による拡張がどの様に行われるのか、押えておく必要があるからです。

以下に jQuery.js の行を辿って解読しますが空白行は省略します。

※ extend 定義コードはver1.1.4以降において、それ以前よりも大幅に拡張されました。deep copy 対応や未定義値への対応が盛り込まれたためです。こうしてより一般化されたということでしょう。

変数定義

427:jQuery.extend = jQuery.fn.extend = function() {
428:  // copy reference to target object
429:  var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false;

直前のエントリイで触れたとおり 427行で、jQuery オブジェクトの extend メソッドと jQuery.prototype オブジェクトの extend メソッドを同時に定義しています。(jQuery.fn は34行で定義済み)

これにより、ユーザーが何らかのオリジナルな jQuery.extend(・・・) を定義した場合、・・・は同時に prototype オブジェクトのプロパティにもなり、こうしてそれが適用されたサイトを開いた場合には、自動的にオリジナルの jQuery プロパティは定義済みとなります。

続いて 429 行で変数を4つ定義しています。extend() メソッドの第1引数への(それがが与えられていなければ空オブジュクトへの)参照を target に、インクリメント変数となる a に初期値1を、引数の数を al に、deepには偽値を、各々代入しています。

deep copy ── オブジェクトの merging を行う

deep copy situation とは何か、色々調べましたが既製の解説は見あたりませんでした。そして仕方なくコードを解読して解明しました。それは「オブジェクトの merge 」を指すようです。

まず、原典のコードを辿ってみます。

431:  // Handle a deep copy situation
432:  if ( target.constructor == Boolean ) {
433:   deep = target; // = arguments[0] = true || false
434:   target = arguments[1] || {}; //第二引数がない場合には空オブジェクト
435:  }

第一引数が Boolean 型だった(例えば true )場合には、 429 行で第一引数を代入した target をここで deep に代入します。この結果 deep 値は true か false となります。次にtarget 値に第二引数(なければ空オブジェクト)を代入します。

▲ToTop

引数が唯一の場合の処理───これによりjQuery オブジェクトが拡張される

437:  // extend jQuery itself if only one argument is passed
438:  if ( al == 1 ) {
439:   target = this;
440:   a = 0;
441:  }

たった1つの引数しか与えられない場合の処理が記されています。this つまり、jQuery オブジェクトを変数 target に代入し、インクリメント変数の初期値をゼロとしています。この a = 0 は445行以降で意味を持ちます。

ここにおいて、this(つまり jQuery オブジェクト)を target に代入することは、jQuery オブジェクト自体を上書きするための措置となります。

引数チェック

443:  var prop;
445:  for ( ; a < al; a++ )
446:   // Only deal with non-null/undefined values
447:   if ( (prop = arguments[a]) != null )
        ・
        ・
461:    }

ここで注目すべきことは、445行のforループに初期値がなく「 ; 」しかないことです。「 ; 」が2つなければ for 文として成立しませんから「 ; 」は不可欠ですが、初期値が記述されていないのです。

これにより初期値aは、既に定義されているとおり引数が1つの場合のみ0となり、その他は最初に定義された1となります。forループのこのような標記には初めてお目に掛かりましたが、初期値を随意に変更するこの手法は勉強になりました。

更にこのfor文には { がありませんから、for文は次の1行にしか係らず、さらにその当該行はif文でありこの行にも{ がありませんので、結局、445行のforループは、auguments[a] が空でない場合(447行)にのみ機能して、448行以下が働くことになります。

▲ToTop

extend メソッドの要所!

448-461行は extend 関数の引数が空でなかった場合に(447行)、そのオブジェクトのプロパティを取り出すコードであり、オブジェクトの拡張を実現する部分です。

extend() メソッドの引数は jQuery.js では JSON オブジェクトとして記述され、その中で様々な名称と値が定義されています。449-461行の for loop によって、1つ以上の引数オブジェクトから順番に、そのプロパティ名毎の値を取り出してそれを target オブジェクトに格納し、最後にこのオブジェクトを return することにより、1以上の個数の引数オブジェクトのプロパティを jQuery 又は任意のオブジェクトのプロパティ又はメソッドに複写します。

448: // Extend the base object
449: for ( var i in prop ) {
450:  // Prevent never-ending loop
     // target が i という名称の prop 値に等しい場合何もせずに進む。
451:  if ( target == prop[i] )
452:   continue;
453: 
454:  // Recurse if we're merging object values
     // jQuery.extend() メソッドの再帰呼び出し
455:  if ( deep && typeof prop[i] == 'object' && target[i] )
456:   jQuery.extend( target[i], prop[i] );
457:
458:  // Don't bring in undefined values
459:  else if ( prop[i] != undefined )
460:   target[i] = prop[i];
461: }
 

上のコードを解読します。

第一に、451-452 行ですが、これは target オブジェクトが、i という名称の prop 値に等しい場合の処置です。

こうした事態が起きる例を作ってこのケースを考えてみました。

 ※ target == prop[i] の例
  var target = { validate: false, test: "good" };
  var prop = { validate: true, test: target };
  var tmp =jQuery.extend(target,prop);

以上のコードを実行すると tmp は {validate:true, test:"good"} となります。つまり target.validate プロパティは上書き複写されますが、target.test プロパティは変化せずにスルーされます。これは 452 行の continue が働いたからです。

では、continue がないとどうなるのか、本当に無限 loop が発生するのかどうか、target[i] に target 自身を代入する式が無限 loop をもたらすのかどうかはまだ確かめてはいません。

第二は、454-456行です。この部分が extend() コードで最も難解な箇所です。

そもそも455 行と456 行は deep == true の時にしか働きません。そしてその場合には、434行によりtarget = arguments[1] となり、447 行の prop も loop の一回目では arguments[1] となります。つまり、456 行の extend() メソッド再帰呼び出しは、まず自分自身(arguments[1]) を自分自身に複写することになります。再帰呼び出された extend() メソッドでは全ての if を通過して一気に 459-460行 が実行され、自己複写が行われるからです。

つまり、この部分は無駄があります。本来ならば次のようにすべきです。

431:  // Handle a deep copy situation
432:  if ( target.constructor == Boolean ) {
433:   deep = target; // = arguments[0] = true || false
434:   target = arguments[1] || {}; //第二引数がない場合には空オブジェクト
追加行: a = 2;
435:  }

次に、第三以降の引数が有る場合を考えてみます。この場合それがいくつあろうとも、それらは target すなわち第二引数に複写させたいはずです。その当たりをどの様に実現しているのかを見てみます。

455 行は、deep == true で prop[i] がオブジェクトでかつ target[i] があれば、との条件ですが、loop 一回目では prop == target なのですから、第三引数がない場合には target[i] は意味がありません。そもそも target[i] がなければ typeof prop[i] つまり typeof target[i] の型は 'object' たり得ないからです。

しかし、第三以上の引数が有る場合にこそ、この条件が活きてきます。この場合には prop オブジェクト( arguments[n] (n≧2) )は target オブジェクトと等しくないのが一般的でしょう。わざわざ同じオブジェクトを引数に指定することはないからです。

この場合 typeof prop[i] == 'object' && target[i] は次のような意味を持ちます。

prop オブジェクトの i プロパティはオブジェクト型でなければならず、かつ当該プロパティ i が target オブジェクトに存在しなければ次行を遂行しません。もちろん 459 行は else if ですから 460 行も遂行されません。

こうして第三引数以降のオブジェクトについては、そのプロパティ値がオブジェクトであって、かつそのプロパティ名が第二引数のオブジェクトに存在する場合にのみ、extend() の再帰呼び出しが行われてプロパティの複写が実行されます。裏返せば、第二引数に存在しないプロパティは複写されず、また第三引数の、プロパティ値が、未定義、真偽値、文字列、数値又は関数であるプロパティも第二引数には複写されないはずです。

かくして、jQuery.js のコメント行 454 行に「 merging object values 」と記されていた意味がやっと解明できた訳です。

さて、「はず」と書いたのはコードに疑義があるからです。その点は次の段で述べます。

merging object values コード(455-456行)の疑義について

しかし、上のコードには疑義があります。455 行の if 文を見る限り、オブジェクト型だけのプロパティを、また target に存在するプロパティ名だけの、コピーを行おうとしていますが、この if 文に該当しない場合には else if 文が実行されますので、prop[i] がオブジェクト型ではなくても複写されますし、target[i] プロパティが存在しなくても prop[i] はコピーされます。

つまり原典のままでは、折角の455行が全く意味がなくなってしまうのです。

455行の意図を反映させる copy を行わせるには、次のようにすべきだと考えます。

▼原典
  451:  if ( target == prop[i] )
  452:   continue;
▲改正
  451': if ( target == prop[i] || 
       ( deep && (typeof prop[i] != 'object' || !target[i]) ) )
  452:  continue;

451行の if 文に deep == true でかつ、prop[i]がオブジェクト型ではないか、またはtarget[i]が存在しない場合、を追加し、この条件に合致した場合には何もしないで loopを先に進めるようにするのです。

こうすれば、target[i] が存在しない場合には複写元から複写されませんし、prop[i] がオブジェクト型でない場合も同様に複写されません。

例 示

▼原典のまま

	var target = {a:{test:3}, b:{hoge:4}, c:5}
	var prop   = {a:{test:9}, b:"text", d:{foo:8}}
	var tmp = $.extend(true,target,prop);

これを実行すると
   tmp.a = {test:9}, tmp.b = "text", tmp.c = 5, tmp.d:{foo:8}
となります。

▲修正した場合

一方、451行を451'行のように修正した jQuery.extend2()メソッドを作って
   var tmp = $.extend2(true,target,prop);
を実行すると
   tmp.a = {test:9}, tmp.b = {hoge:4}, tmp.c = 5, tmp.d = undefined
となります。

typeof prop[b] != 'object' ですからこれは複写されませんし、target[d] は存在しませんから、prop[d] も複写されません。deep copy とはこのような結果を狙っているのではないでしょうか?

第三に、459-460行です。target[i] = prop[i] によって、prop オブジェクトから、そのプロパティが順次 target が複写されます。

この場合においては、target に i プロパティが存在しなくても、prop にある i プロパティは複写されますし、i プロパティの型は問題視されませんから、その値が基本データ型(数値、文字列、真偽値)でも複写されます。

例えば、target={};で jQuery.extend( target, {name1:func1(){}, name2:"str",name3:number,・・・} );が実行されると、target["name1"] = func1(){}, target["name2"] = "str", target["name3"] = number,・・・と複写元のプロパティ順に代入されていきます。

なお、 445 行の for loop 内なのですから、第三以降の引数があれば、第二のケースと同様にそれらも target オブジェクトに複写されていきます。

▲ToTop

return───ここが命!

463:  // Return the modified object
464:  return target;
465:};
  • extend() の引数が1つの場合

    return のシーンで416行が活きてきます。引数が1つしかない場合には変数 target に this が代入されますが、この this は jQuery を指しています。

    437行の target[i] = prop[i] によりプロパティ名とその値が全て収集されますが、それは jQuery[i] = prop[i] となります。こうして 拡張されたjQueryオブジェクト が呼び出し元jQueryに return され、上書きされます。

  • 引数が2つ以上ある場合(但し第一引数が真偽値ではない場合)

    この場合には第一引数オブジェクトに、第二以降の引数オブジェクトのプロパティが複写されていきます。

  • 引数が2つ以上ある場合で第一引数が真偽値の場合

    この場合には第二引数オブジェクトに、第三以降の引数オブジェクトのプロパティが複写されていきます。但し、既に見たように「第三以降のオブジェクトのプロパティ値がオブジェクトではないプロパティは複写されず、また第二引数オブジェクトに存在しないプロパティは、第三以降のオブジェクトから複写されない」仕様を狙っているはずです。

    しかし原典のままではそれは実現されません。上に述べたような一部コードの修正が必要です。

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