02 | 2009/03 |  04

  1. 無料サーバー

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

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


jQuery の挙動を解読する(33):DOM Manipulate メソッド解読──jQuery解読(50)

Contents
  • domManip() メソッド解読
  • 各種 DOM Manipulate メソッドの性質と分類
  • 各種 DOM Manipulate メソッドの例を試す
  • 各種 DOM Manipulate メソッドを解読する───親ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───子ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───兄弟ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───置換系

このエントリイでは DOM 要素操作 に係るインスタンスメソッドに焦点を当て、それらのメソッドを解読します。

対象とするメソッドは、domManip()、wrapAll()、wrapInner()、wrap()、append()、prepend()、before()、after() 、appendTo()、prependTo()、insertBefore()、insertAfter()、replaceWith()、replaceAll()、text()、html() です。

domManip()

まず、DOM 走査の基本的な役割を担う domManip() メソッドから解読します。

このメソッドは args 内の html 文字列によってインスタンスにノードを追加する際に、(1) clean メソッドを適用して html 文字列の記述ミスを正し、(2) またブラウザバグを補正すると共に、(3) DocumentFragment を使用することにより script タグをインスタンスに追加する、以上のことを行います。

■ domManip() インスタンスメソッド解読
  // 引数は順に、1:走査対象となるノードを要素とする配列、2:table 有無の指定、
  // 及び 3:適用するメソッドで、返値は jQuery インスタンスである。
514:domManip: function( args, table, callback ) {
515: if ( this[0] ) { // インスタンスに最初の要素があれば
    // 3 つの変数を定義する。まず文書断片( DocumentFragment )を作る。ここに、
    // this[0] が document ノードの時には owwnerDocument 値が null となる
    // ため < || this[0] > が必要となる。いずれにしても document ノードに
    // 文書断片を作成し、それへの参照を fragment 変数に代入する。
516:  var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
     // 引数 args を clean メソッドでチェックし return 値を変数 scripts に代入
     // する。ここに clean メソッド #961 から返される値は配列であり、その要素は
     // args 内に存在していた script タグである。
517:   scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
     // 上の clean メソッドにより args 配列要素の script タグ以外のタグ内にあった
     // script タグが削除された後に fragmentノードの chileNodes となっている。
     // ( #957 )。その中の第一子ノードを 変数 first に参照させる。
518:   first = fragment.firstChild;
519:  // このブロックは args 内に記述されていた script タグを、インスタンスの各要素
    // ノードから呼び出される実行される callback 関数の引数にするためのもの。
520:  if ( first ) // 第一子ノードが取得できれば
     // インスタンスに格納されている要素ノードの数だけ巡回処理する。
521:   for ( var i = 0, l = this.length; i < l; i++ )
      // root 関数で table ノードの場合の処理を行い、call の第 1 引数とし、
      // this に格納されている要素ノード数が 2 以上か イテレート変数が
      // 1 以上ならば fragment ノードの cloneNode をイベント付きで作成して
522:    callback.call( root(this[i], first), this.length > 1 || i > 0 ?
        // 第 2 引数とし、要素ノード数が 1 以下か i = 0 の時には
        // fragment ノード自体を第 2 引数とする。
523:      fragment.cloneNode(true) : fragment );
524: 
525:  if ( scripts ) // scripts が取得できたらば
     // その 1 つ 1 つを evalScript メソッドで評価する。
526:   jQuery.each( scripts, evalScript );
527: }
528: // 以上の処理を終えた this インスタンスを呼び出し元に返す。
529: return this; // また this[0] がない場合には this をそのまま返す。
530: 
531: function root( elem, cur ) {
    // 引数 table が true で、要素ノード=this[i] が table ノードで
    // cur が tr ノードならば
532:  return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
     // 取得できた最初の tbody ノードを返すか、取得できなければ tbody 要素ノードを
     // elem ノードに追加して return する。
533:   (elem.getElementsByTagName("tbody")[0] ||
534:   elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
     // その他の場合には elem 自身=this[0] を return する。
535:   elem;
536: }
537:}

▲ToTop

各種 DOM Manipulate メソッドの性質と分類

DOM 操作を行う jQuery インスタンスメソッドは次のように沢山あります。─── $(s,c).wrapAll(html)、$(s,c).wrapInner(html)、$(s,c).wrap(html)、$(s,c).append(elem)、$(s,c).prepend(elem)、$(s,c).before(elem)、$(s,c).after(elem)、$(s,c).appendTo(elem)、$(s,c).prependTo(elem)、$(s,c).insertBefore(elem)、$(s,c).insertAfter(elem)、$(s,c).replaceWith(elem)、$(s,c).replaceAll(elem)、$(s,c).text(str)、$(s,c).html(html) 等々。

これらの 15 個のメソッドの共通点は、jQuery インスタンス内に参照が取得されている要素ノードと、各のメソッドの引数である html や elem 等によって定まるノードとの、二者の組み合わせ方を定めることです。これらのインスタンスメソッドは、2 つの(あるいは 2 つの群の) DOM 要素間の前後関係(つまり兄弟関係)、包含/被包含関係(親子関係)、あるいは置換/被置換関係を定義づけます。

まず、組み合わせの対象となる 2 つ( 2 群 )の位置関係を整理しておくべきでしょう。分かりやすく表示するために、インスタンス内で参照付けられているノード群をA、引数によって参照付けられるノードをBとします。

親ノード追加系
  • $(s,c).wrapAll(html)……A[0]~A[last] までの全体を B で包含する。A[i] = B.childNodes[i]
  • $(s,c).wrapInner(html)……A の子ノード C を B で囲む。B.childNodes =A[i].childNodesとなる。
  • $(s,c).wrap(html)……B を個々の A の親ノードとする。A[i].parentNode = B
子ノード追加系
  • $(s,c).append(elem)……B を個々の Aの最後の子ノードとする。A[i].lastChild = B
  • $(s,c).prepend(elem)……B を個々の A の最初の子ノードとする。A[i].firstChild = B
  • $(s,c).appendTo(elem)……A を B の最後の子ノードとする。B.lastChild = A
  • $(s,c).prependTo(elem)……A を B の最初の子ノードとする。B.firstChild = A
兄弟ノード追加系
  • $(s,c).before(elem)……B を個々の A の直前の兄弟ノードとする。A[i].previousSibling = B
  • $(s,c).after(elem)……B を個々の A の直後の兄弟ノードとする。A[i].nextSibling = B
  • $(s,c).insertBefore(elem)……A を B の直前の兄弟ノードとする。A = B.previousSibling
  • $(s,c).insertAfter(elem)……A を B の直後の兄弟ノードとする。A = B.lastSibling
置換系
  • $(s,c).replaceWith(elem)……A を B で置換する。A[i] → B
  • $(s,c).replaceAll(elem)……B を A で置換する。A[i] ← B
  • $(s,c).text(str)……A の 子要素を B で置換する。A[i].childNodes = B
  • $(s,c).html(html)……A の 子要素を B で置換する。A[i].childNodes = B

▲ToTop

各種 DOM Manipulate メソッドの例を試す

解読の前に各種 DOM Manipulate メソッドの適用例をこのページ上で見てみます。それぞれのメソッドの働き方や差異を見るにはそれが手っ取り早いからです。

適用例の説明
  • 下のボタンは各種 DOM Manipulate メソッドを実行するためのもの。15 のいずれかをクリックすると、ボタンの下にある薄緑色のボックス内で各種メソッドの効果を表示します。
  • メソッドの適用効果を消去するにはもう一度同じボタンをクリックします。
  • メソッドの適用対象は、薄緑色のボックス内に配置した 3 つの p タグです。これをボタン上では $(this) と表示しました。3 つの pタグはピンクの枠で示していますが、それらを対象として各種 DOM Manipulate メソッドを適用します。
  • 他方、働き方を逆転させるメソッドの場合には、メソッド起動要素がサイト内に存在していなければ成りませんので、そのための要素として DIV タグ( class="targetDiv" )を用意しました。

メソッドの引数として次の html 文字列を準備しました。

  • "<div style='background-color:maroon;border:4px dotted gray;margin:2px;padding:2px;'></div>"
  • "<p style='background-color:maroon;border:4px dotted gray;margin:2px;padding:2px;'>これはボタンをクリックしたことにより追加されたノードです。</p>"
  
   
   
   

(第1 pタグ)これは各種 DOM Manipulate メソッドの働き方を視覚的に見やすくしたサンプルです。

(第2 pタグ)このブロックを包含する div タグがあり、その中に複数の 3 つの p タグと 2 つの div タグを配置し、3 つめのパラグラフには画像を配置しました。

(第1 divタグ)3 つの p タグを操作対象として各種のメソッドを適用します。

(第3 pタグ) これは画像を含むパラグラフです。

(第2 divタグ)
逆転メソッドで使用する div タグ

各種 DOM Manipulate メソッドを解読する───親ノード追加系

$(s,c).wrapAll(html) メソッド解読

このメソッドは、jQueryインスタンスが参照するノード全体を、 html 文字列によってサイトに追加されるノードの子ノードにします。

■ $(s,c).wrapAll(html) メソッド
219: wrapAll: function( html ) {
220:  if ( this[0] ) {
221:   // The elements to wrap the target around
     // html 文字列で指定されたノード群を document 内で検索し、マッチ
     // したノードの clone ノードを作り、変数 wrap に代入する。
222:   var wrap = jQuery( html, this[0].ownerDocument ).clone();
223:   // インスタンスの最初のノードに親ノードがあれば、
224:   if ( this[0].parentNode )
      // clone ノード ( wrap ) をインスタンスの、最初の配列もどきプロ
      // パティが参照するノード ( this[0] ) の前に配置する。
225:    wrap.insertBefore( this[0] );
226:
227:   wrap.map(function(){
      // この this は mapメソッド内にあり、map メソッドが wrap から
228:    var elem = this; // 呼び出されているので wrap を参照する。
229:
230:    while ( elem.firstChild ) // 最も深い階層にある最初の
231:     elem = elem.firstChild; // 子ノードを elem に代入し、
232:
233:    return elem; // その最後の子ノードを返す。
     // 返された最深層の最初の子ノードの中に、this を最後の子ノードとして追加する。
     // なおここの this はインスタンスから呼び出される wrapAll メソッド内にある
234:   }).append(this); // ので、インスタンスを参照する。
235:  }
236:
237:  return this; // インスタンスを返す。
238: },
$(s,c).wrapInner(html) 解読

このメソッドはインスタンスの各々の子ノードを html 文字列による追加ノードで包含します。

■ $(s,c).wrapInner(html) メソッド
240: wrapInner: function( html ) {
241:  return this.each(function(){ // インスタンスの各々のノードに対して
     // 当該ノードの子ノード jQuery( this ).contents() を html により
     // 挿入されるノードで包含する。なお、contents() は #1186 で定義
     // されているインスタンスメソッドである。
242:   jQuery( this ).contents().wrapAll( html );
243:  });
244: },
$(s,c).wrap(html) 解読

このメソッドはインスタンスが参照する各々のノードを html 文字列による追加ノードで包含します。

■ $(s,c).wrap(html) メソッド
246: wrap: function( html ) {
    // インスタンスの各々のノードを巡回処理し結果を返す。
247:  return this.each(function(){
     // 各々のノードを html 文字列による追加ノードの子ノードとする。
248:   jQuery( this ).wrapAll( html );
249:  });
250: },

▲ToTop

各種の DOM Manipulate メソッドを解読する───子ノード追加系

子ノードを追加する 最初の 2 つのメソッドでは共通して domManip メソッドが使われています。また、appendTo メソッドと prependTo メソッドはそれぞれ append と prepend メソッドを利用して対象を逆転させているだけですから、結局 4 つのメソッドで domManip メソッドが使われていることになります。

これは、誤記や table に係る IE のバグ対策を施さないと追加により、インスタンスが指し示すノードが文法的におかしくなってしまうことがあるためです。

■ 子ノード追加系 メソッド
▼ append() メソッド
252: append: function() {
253:  return this.domManip(arguments, true, function(elem){
254:   if (this.nodeType == 1)
      // この this は domManip メソッドの定義から、インスタンス内で指し
      // 示されている個々のノードを指す。( #522 )その個々のノードの
      // 最後の子ノードとして elem により定義されるノードを挿入する。
255:    this.appendChild( elem );
256:  });
257: },
258:

▼ prepend() メソッド
259: prepend: function() {
260:  return this.domManip(arguments, true, function(elem){
261:   if (this.nodeType == 1)
      // この this は domManip メソッドの定義から、インスタンス内で指し
      // 示されている個々のノードを指す。( #522 )その個々のノードの
      // 最初の子ノードの前に、elem により定義されるノードを挿入する。
262:    this.insertBefore( elem, this.firstChild );
263:  });
264: },

▼insertBefore() 及び insertAfter() メソッド
// 以下のブロックの解読は別途こちらのエントリイ ( jQuery の挙動を解読する(31):
jQuery.each({ nameN:fnN(){}},function(){}) によるメソッド登録──jQuery解読(48) )
で行いました。
1198:jQuery.each({
1199: appendTo: "append",
1200: prependTo: "prepend",
1201: insertBefore: "before",
1202: insertAfter: "after",
1203: replaceAll: "replaceWith"
1204:}, function(name, original){
1205: jQuery.fn[ name ] = function( selector ) {
1206:  var ret = [], insert = jQuery( selector );
1207:
1208:  for ( var i = 0, l = insert.length; i < l; i++ ) {
1209:   var elems = (i > 0 ? this.clone(true) : this).get();
1210:   jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
1211:   ret = ret.concat( elems );
1212:  }
1213:
1214:  return this.pushStack( ret, name, selector );
1215: };
1216:});

▲ToTop

各種の DOM Manipulate メソッドを解読する───兄弟ノード追加系

兄弟ノードを追加する最初の 2 つのメソッドでも共通して domManip メソッドが使われ、その中では clean メソッドが呼び出されます。また、insertBefore メソッドと insertAfter メソッドはそれぞれ before と after メソッドを利用して対象を逆転させているだけですから、結局 4 つのメソッドで domManip メソッドが使われていることになります。

このような回り道をするのは、clean メソッドを使って誤記や table に係る IE のバグ対策を施さないと、ノード追加によって、インスタンスが指し示すノードが文法的におかしくなってしまうことがあるためです。

■ 兄弟ノード追加系 メソッド
▼ before() メソッド
266: before: function() {
267:  return this.domManip(arguments, false, function(elem){
     // この this は domManip メソッドの定義から、インスタンス内で指し
     // 示されている個々のノードを指す。( #522 )その個々のノードの
     // 前に、elem により定義されるノードを兄弟要素として挿入する。
268:   this.parentNode.insertBefore( elem, this );
269:  });
270: },
271:

▼ after() メソッド
272: after: function() {
273:  return this.domManip(arguments, false, function(elem){
     // この this は domManip メソッドの定義から、インスタンス内で指し
     // 示されている個々のノードを指す。( #522 )その個々のノードの
     // 次の兄弟ノードの前に、elem により定義されるノードを挿入する。
     // つまり個々のノードの後ろに elem を兄弟ノードとして挿入する。
274:   this.parentNode.insertBefore( elem, this.nextSibling );
275:  });
276: },
// insertBefore と insertAfter の 2 つのメソッドに関しては、直上の
// 「子ノード追加系」でリストアップした #1198-1216 を参照のこと。

▲ToTop

各種の DOM Manipulate メソッドを解読する───置換系

replaceWith(elem) メソッドは極めて簡単なコードで定義されています。インスタンスが指し示す個々の要素ノードの直後に兄弟要素 elem を追加してから、当該要素ノードをを削除します。

■ 置換系 メソッド
▼ replaceWith() メソッド
491: replaceWith: function( value ) {
492:  return this.after( value ).remove();
493: },

▼ replaceAll() メソッド
// replaceAll メソッドに関しては、こちらを参照のこと。

▼ text() メソッド
201: text: function( text ) {
    // 引数がオブジェクトでもなく空でもなければ
202:  if ( typeof text !== "object" && text != null )
     // インスタンスが指し示す個々の要素の子ノードを削除してから、
     // 0 プロパティが存在したら document ノードにテキストノードを作り、
     // それを個々の要素の子ノードとして追加する。
203:   return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
204:  // 引数がオブジェクトか、空文字のとき
205:  var ret = ""; // 空文字を変数 ret に代入
206:
207:  jQuery.each( text || this, function(){ // 
208:   jQuery.each( this.childNodes, function(){ // 子ノードを巡回処理
209:    if ( this.nodeType != 8 ) // 子ノードがコメントノードでない場合
210:     ret += this.nodeType != 1 ? // 子ノードが要素ノードでなければ
211:      this.nodeValue : // その値、つまりテキスト文字列を ret に代入
        // 子ノードが要素ノードの場合 this を配列の要素に入れて
212:      jQuery.fn.text( [ this ] ); //text メソッドを再帰呼び出しする。
213:   });
214:  });
215:
216:  return ret; // 文字列 ret を返す。
217: },

▼ html() メソッド
483: html: function( value ) {
484:  return value === undefined ? // value が未定義ならば
485:   (this[0] ? // 最初の配列もどきプロパティ 0 が存在すれば
      // プロパティ 0 の innerHTML 文字列内に変数 expando による
      // 「半角スペース + "jQuery" + 数字 = 数字」文字列があれば
      // それを削除してから、innerHTML 文字列を返値とする。
      // プロパティ 0 が存在しなければ null を返す。
486:    this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
487:    null) :
     // value が存在すれば個々の要素の子ノードを空にしてから、
     // value を子ノードとして追加する。
488:   this.empty().append( value );
489: },



・・・・・

jQuery の挙動を解読する(32):jQuery.clean() メソッド解読──jQuery解読(49)

jQuery.clean() メソッドを徹底解明!

以前にこちらのエントリイ ( jQuery()の挙動を解読する(6) jQuery.clean()メソッド解読──jQuery解読(10) )で clean メソッドを解読しました。

しかし、jquery.js ver1.3.2 でこのメソッドは一部仕様が変わり拡張されました。

そこで改めてこのメソッドに正面から取り組み、ほぼ全容を解明しました。

特にver 1.3.x 系から追加された Document Fragment を利用して DOM にスクリプトを追加することなく実行する機能はなかなか興味深いものがあります。

そこで、jQuery()の挙動を解読する(6) jQuery.clean()メソッド解読──jQuery解読(10) を抜本改定しましたので、よろしければご覧下さい。

上のエントリイでは、ver 1.3.x 系の新たな仕様であるスクリプト実行機能のサンプルも用意しました。

非常に興味深いのでこのエントリイでも抜粋しておきます。

▲ToTop

cleanメソッドが 第 3 引数 fragment を取る場合の興味深い例示

clean() メソッドの第 3 引数 「 fragment 」 は ver 1.3.x で初めて登場しました。1.2.6 まではこのメソッドは 2 つの引数しか取りませんでした。この fragment 引数は一体何をするために設けられたのか?───それがなかなか解明できませんでした。今でも完全に解明しきったとは思えません。まが霧が完全には晴れないからです。

それでも、コードを睨み続け、サンプルを幾つか試してみて分かったことがあります。

スクリプトを使って Web ページに新たにノードを追加する場合において、追加する html 文字列内にスクリプトを埋め込んで( いわば「 二重スクリプト 」になります。 )、fragment 引数つきで clean() メソッドを実行すると、大変興味深いことが起こるのです。

くだくだ説明するよりも、実例を示すのが手っ取り早いでしょう。

下のボタンをクリックするとこの文字列付近で 2 つの変化が起こります。或る文章がボタンの前に挿入され(文字列を内包する p タグです)、同時に画面中央に alert 表示が出ます。

ここで行ったことは以下の通りです。

上のボタンがクリックされると、このページに埋め込まれた或る Javascript コードが起動します。そのコードでは jQuery().before() メソッドを使って、このボタンの前に兄弟要素 p タグとスクリプトタグを挿入します。

このとき、jQuery().before() メソッドはその定義により、domManip メソッドを呼び出し、その中では clean メソッドが fragment 引き数付きで起動されます。この結果、挿入する html 文字列に記述されているスクリプトタグは、ページ内に挿入された直後に削除され、かつ実行されるのです。こうして alert 表示が実行されます。

スクリプトによって或る DOM エレメントを追加する行為において、そのノード内にスクリプトタグを含める必要性が果たしてどれほどあるのか判然とはしません。挿入する html 文字列内に敢えてスクリプトタグを埋め込まなくても、挿入する DOM エレメントとは別にスクリプトから直接同様の効果を得ることも可能でしょう。

それでも、二重スクリプトはなかなかおもしろい効果をWebページに与えるのではないか、と感じています。

なお、以上を実現する Javascript コードでは name エンティティに悩まされました。フゥ~(;´_`;)

■ 上のことを実現している Javascript コード
(function(){
 $("#fragmentTest702").css("background-color","pink")
  .toggle(function(){
   $(this).text("clean(e,c,fragment)メソッドテスト取り消し")
    .css("background-color","aquamarine")
    .before("<p id='before702' class='ta_l accentuate2'>このブロックは下のボタンをク
    リックした結果表示されました。それは jQuery().before('<p id=\"before702\">
    ・・・</p><script type=\"text/javascript\">alert(\"実験成功・・・\")
    </script>) メソッドテストであり、追加する html 文字列内に上に見るようにスク
    リプトコードを含む場合の実験です。</p><script type='text/javascript'>alert('実験
    成功!\\nこの alert 表示がスクリプト実行結果です')<\/script>");
  },function(){
   $("#before702").remove();
   $(this).text("clean(e,c,fragment)メソッドテスト実行")
    .css("background-color","pink");
  });
})();

▲ToTop

jQuery の挙動を解読する(31):jQuery.each({ nameN:fnN(){}},function(){}) によるメソッド登録──jQuery解読(48)

$.each( { nameN,funcN }, fn ) 形式によるメソッドの一括登録

jquery.js #1177-1263 では $.each クラスメソッドを利用して、 fn の内容別に 3 つに分けて各種メソッドの一括登録が行われています。

この方法によるメソッド登録は他にも、2 箇所 ( #2888-2892、 #3954-3964 )ありますが、今回対象とする 3 ブロックのメソッド一括登録は、jQuery prototype オブジェクト、すなわちインスタンスメソッドの登録である点において、他の 2 箇所での使用方法と一線を画しています。

さて、#1177-1263 では obj オブジェクトプロパティとして、複数のメソッド名とメソッドを定義し、それらを巡回処理により順に jQuery インスタンスメソッドとして登録しています。まず #1177-1196 では トラバース系の DOM メソッドを、続く #1198-1216 では 挿入置換系の DOM メソッドを、#1218-1263 では属性処理 DOM メソッドを、それぞれ定義しています。

ここに、これらは共通して $.each( obj,fn ) メソッド実行コードなので、jquery.js 読み込み時に実行され、こうして obj オブジェクト内にリストアップされている各種メソッドは、プロトタイプオブジェクトに登録され jQuery インスタンスメソッドとなります。

以下に、これらの 3 つの $.each({nameN,funcN},fn) 形式によるインスタンスメソッド一括登録コードを解読します。

DOM トラバース系のインスタンスメソッド登録

次のコードは、親子、兄弟姉妹、前後などの要素ノードを扱うインスタンスメソッドを一括してプロトタイプオブジェクトに登録するものです。

   // #1177-1187 は「登録するメソッドリスト」をプロパティとするオブジェクト
1177:jQuery.each({ // $.dir、.nth、.sibling は Sizzle 内で定義されている。
1178: parent: function(elem){return elem.parentNode;},
1179: parents: function(elem){return jQuery.dir(elem,"parentNode");},
1180: next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
1181: prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
1182: nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
1183: prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
1184: siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
1185: children: function(elem){return jQuery.sibling(elem.firstChild);},
1186: contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
1187:}, function(name, fn){// name はリスト内のメソッド名称、fn はリスト内の関数を指す
    // プロトタイプオブジェクトに function name(selector){・・・ } を登録する。 
1188: jQuery.fn[ name ] = function( selector ) { // selector は name メソッドの引数
     // インスタンス this に fn メソッドを map する。
     // 例えばイテレート処理の最初には、parent プロパティが対象となるが、
     // map メソッドの定義から ret は this.length 個の this[i].parentNode 
     // を要素とする配列となる。
1189:  var ret = jQuery.map( this, fn );
1190:  // もし selector が文字型の場合
1191:  if ( selector && typeof selector == "string" )
      // ノードを要素とする配列 ret から selector 条件に
      // 合致するノードを抽出する。
1192:   ret = jQuery.multiFilter( selector, ret );
1193:  // 重複チェックを掛けて jQuery オブジェクトとして return
     // する。このとき this インスタンスは return オブジェクトの
     // prevObject プロパティに保持する。
1194:  return this.pushStack( jQuery.unique( ret ), name, selector );
1195: };
1196:});

以下には、上の each メソッド第 1 引数オブジェクトのプロパティ内で、繰り返し登場する Sizzle ユーティリティ内にある jQuery クラスメソッドを簡単に解読しました。

なお、これらの 3 つのコードは全て jQuery クラスメソッドの登録ですが、興味深いことに extend メソッドを使わずメソッドを直接 jQuery オブジェクトに登録する「 素朴な 」方法を採用しています。

■ jQuery.multiFilter
2383: jQuery.multiFilter = function( expr, elems, not ) {
2384:  if ( not ) {
2385:   expr = ":not(" + expr + ")"; // "not(expr)"文字列を生成して代入
2386:  }
2387:  // 要素ノード elems から expr に合致する要素を抽出し return 値とする。
2388:  return Sizzle.matches(expr, elems);
2389: };
■ jQuery.dir
2391: jQuery.dir = function( elem, dir ){
2392:  var matched = [], cur = elem[dir];
2393:  while ( cur && cur != document ) { // elem の dir プロパティを
2394:   if ( cur.nodeType == 1 ) // 走査し、それが要素ノードであれば
2395:    matched.push( cur );  // matched 配列に格納する。
     // cur の dir プロパティを cur に代入してloopを繰り返す。
     // これにより cur ノードの子ノードがあれば、それが走査対象となる
2396:   cur = cur[dir];
2397:  }
2398:  return matched; // 該当要素ノードを格納した配列を返す。
2399: };
■ jQuery.nth
2401: jQuery.nth = function(cur, result, dir, elem){
2402:  result = result || 1;
2403:  var num = 0;
2404:  // cur がある限りその dir プロパティを走査する
2405:  for ( ; cur; cur = cur[dir] ) // cur が要素ノードで num 値が
2406:   if ( cur.nodeType == 1 && ++num == result ) // result に等しくなるまで
2407:    break; // loopする。
2408: 
2409:  return cur;  // 該当した要素ノードを返す。
2410:
 };

▲ToTop

挿入置換系の DOM インスタンスメソッド

2 つめは、登録済みの jQuery インスタンスメソッドを使って、その機能を逆転させるためのメソッド群を一括登録するコードです。

1198: jQuery.each({
1199:  appendTo: "append",
1200:  prependTo: "prepend",
1201:  insertBefore: "before",
1202:  insertAfter: "after",
1203:  replaceAll: "replaceWith"
    // name はプロパティ名、original はプロパティ値
1204: }, function(name, original){
1205:  jQuery.fn[ name ] = function( selector ) { // selector は name メソッドの引数
      // selector に合致するノードを insert jQuery オブジェクトに代入
1206:   var ret = [], insert = jQuery( selector );
1207:    // 配列のようなプロパティを走査
1208:   for ( var i = 0, l = insert.length; i < l; i++ ) {
       // 要素があれば clone をイベント付きで作ってその、なければ this の
       // 要素を配列として取り出す。
1209:    var elems = (i > 0 ? this.clone(true) : this).get();
       // jQuery(insert[i]).original( elems ) メソッドを起動。
       // これにより insert[i] ノードに elems が追加される。
1210:    jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
1211:    ret = ret.concat( elems );  // elems を ret 配列に線形化して結合する。
1212:   }
1213:    // ret jQuery オブジェクトを返し、その prevObject プロパティに this を保持する。
1214:   return this.pushStack( ret, name, selector );
1215:  };
1216: });

▲ToTop

属性処理系の DOM インスタンスメソッド

最後に、タグ属性を操作するメソッド群の登録です。

1218:jQuery.each({
1219: removeAttr: function( name ) {
1220:  jQuery.attr( this, name, "" );
1221:  if (this.nodeType == 1)
1222:   this.removeAttribute( name );
1223: },
1224:
1225: addClass: function( classNames ) {
1226:  jQuery.className.add( this, classNames );
1227: },
1228:
1229: removeClass: function( classNames ) {
1230:  jQuery.className.remove( this, classNames );
1231: },
1232:
1233: toggleClass: function( classNames, state ) {
1234:  if( typeof state !== "boolean" )
1235:   state = !jQuery.className.has( this, classNames );
1236:  jQuery.className[ state ? "add" : "remove" ]( this, classNames );
1237: },
1238:
1239: remove: function( selector ) {
1240:  if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
1241:   // Prevent memory leaks
1242:   jQuery( "*", this ).add([this]).each(function(){
1243:    jQuery.event.remove(this);
1244:    jQuery.removeData(this);
1245:   });
1246:   if (this.parentNode)
1247:    this.parentNode.removeChild( this );
1248:  }
1249: },
1250:
1251: empty: function() {
1252:  // Remove element nodes and prevent memory leaks
1253:  jQuery(this).children().remove();
1254:
1255:  // Remove any remaining nodes
1256:  while ( this.firstChild )
1257:   this.removeChild( this.firstChild );
1258: }
1259:}, function(name, fn){
1260: jQuery.fn[ name ] = function(){ // prototype オブジェクトに登録する。
     // each メソッドの定義から次のように読み解けます。
     // jQuery インスタンス(this) の各プロパティに fn(arguments) メソッド
     // を登録し、処理後の this インスタンスを返値とする。例えば、
     // name が remove の時には、remove.apply(this,arguments) となる。
1261:  return this.each( fn, arguments );
1262: };
1263:});
1264:

jQueryに学ぶ Javascript の基礎(9) concat() メソッド──jQuery解読(47)

 過去のエントリイで行った jQuery.map() メソッド解読を抜本的に改め、かつ 配列のメソッドである concat を徹底的に解明しました。

 map() メソッドの一部における concat() メソッドの使い方を jquery.js ver 1.2.6 と最新の ver 1.3.2 とで比較し、高速化のために後者においてコードが改訂されていることも確認しました。

 よろしければこちら 【 jQuery() の挙動を解読する(9) jQuery.map()メソッド解読 upon ver1.3.2──jQuery解読(13)】 をご覧下さい。

jQueryに学ぶ Javascript の基礎(8) String.replace() メソッド──jQuery解読(46)

string.replace(/・・・/,function(hit,b1,b2,・・・){}) メソッド

string.replace() は比較馴染みやすい、また分かりやすいメソッドです。何故ならば「置換」は、ワードやエクセル等のメジャーアプリケーションは元より、エディターでも必須機能ですし、言葉の意味からも機能が類推しやすいからです。

事実、多くのサイトで string.replace( beforeString, afterString ) のように引数付きで紹介され、例題の多くも 引数は置換前文字列、置換語文字列の2 つの「定数文字列」として説明されています。

しかし、第 2 引数に関数を取る場合に触れたサイトは限られています。ところが、その機能を知ってみると関数による置換は極めて有用で便利です。どうしてもっと紹介されないのだろうか、と訝しく思います。置換前文字列内の部分文字列を、置換後文字列に部分的に利用することは決して少なくないからです。

jquery.js での使われ方

この関数を第 2 引数とする replace() メソッドは、jQuery.curCSS() クラスメソッドに登場します。#1056-1057 の

 1056: name = name.replace(/-([a-z])/ig, function(all, letter){
 1057:   return letter.toUpperCase();
 1058: });

です。このエントリイでは第 2 引数に関数を取る replace メソッドについて学習します。

参考サイトで調べる

この件で全面的に参考になる Web Pages は MDC(Mozilla Developer Center)にありました。こちら【 replace - MDC 】には、第 2 引数が関数の場合の例題があり、またこのメソッドの詳細な解説も掲載されています。(残念ながら英語版です。)

それでも「例題」というものの神通力はたいしたもので、例題を見て本質は十分理解できます。以下がその例題です。

function replacer(str, p1, p2, offset, s){
  return str + " - " + p1 + " , " + p2;
}
"XXzzzz".replace(/(X*)(z*)/, replacer); // returns:XXzzzz - XX , zzzz
ここに

置換の対象となる文字列:"XXzzzz"

置換対象文字列:/(X*)(z*)/

一致文字列:str

置換後文字列:str + " - " + p1 + " , " + p2(つまり return 値)

改めて jquery.js での使われ方を調査

#1056-1057 の以下のコードを改めて見てみると

 1056: name = name.replace(/-([a-z])/ig, function(all, letter){
 1057:   return letter.toUpperCase();
 1058: });

これは、"abc-def" を "abcDef" に置換する、所謂駱駝文字列作成コードです。

置換対象はハイフンに続く一文字のアルファベットであり、letter は部分文字列である [a-z] (任意の小文字アルファベット)を指しますから、letter.toUpperCase() によって "-" とその直後にある小文字の任意のアルファベット 1 文字、合計 2 文字が 1 文字の大文字に変わります。

ここに、学習のために上のコードを一寸拡張し、name 文字列に複数のハイフンのある文字列を与えてみて挙動をフォローしてみます。

var str ="abc-defghi-jkl-mnop"
str = str.replace(/-([a-z])/ig, function(hit, letter, offset, orStr){
    return " , hit : "+ hit + " , letter.toUpperCase() : " + letter.toUpperCase() +
     " , offset : " + offset + " , orStr : " + orStr + "\n";
});

以上のコードを走らせると置換後の str は以下のようになります。

str == "
abc , hit : -d , letter.toUpperCase() : D , offset : 3 , orStr : abc-defghi-jkl-mn
efghi , hit : -j , letter.toUpperCase() : J , offset : 10 , orStr : abc-defghi-jkl-mn
kl , hit : -m , letter.toUpperCase() : M , offset : 14 , orStr : abc-defghi-jkl-mn
nop"
  • 各行行頭の文字列は置換対象外の文字列です。具体的には abc、efghi、kl 及び nop です。
  • 続く hit : -d 等は正規表現 /-[a-z]/ にヒットした置換対象文字列 -d とその説明文字列です。
  • 次の letter.toUpperCase() : D 等が置換後文字で、コロンの前が説明、後が置換後の文字です。
  • offset : 3 等は対象文字列の先頭からのヒット位置の説明とヒット位置です。最初の「-」は3番目にあります。(最初の文字は 0 )
  • orStr : abc-defghi-jkl-mn は対象文字列全体を意味しています。orStr が説明で、コロンに続く文字列は、置換対象となった文字列全体、すなわち上の場合には置換前の str です。

このように僅かな文字列に対しても、replace メソッドは多くの情報をもたらします。正規表現による部分文字列指定がこうした複雑な情報取得を可能にしています。

自作例題を作ってみる

上の例題を踏まえて以下のような例題を作ってみました。

var str="I love you.";
var ret = str.replace( /^(i)(.+)(you).*$/i, function(all,p1,p2,p3){
 return p3.charAt(0).toUpperCase() + p3.substring(1) +" must" + p2 + "me !"} );
// この結果 ret=="You must love me !" となる。

柔軟に置換結果を得られることが分かりました。

jQuery() の挙動を解読する(30) jQuery を使ってタグ属性( css 値含む)を設定/取得する upon ver1.3.2──jQuery解読(45)

このエントリイでは、HTML タグ要素の属性の設定/取得に係る(その属性の 1 つである CSS スタイル値の設定/取得を含む) jQuery の各種メソッドについて学習を深めたいと思います。

HTML要素の属性とは

そのためにまず、jquery.js 解読の前に、タグ要素属性、或いは 要素属性とは何か、どのような特性があるのか、改めて復習しておくべきでしょう。

HTML文では要素の属性を開始タグの中で指定します。属性には殆どの要素で共通して使用することの出来る次の 6 つの「汎用属性」があります。id、class、lang、title、style 及び dir です。

他方、href、src、xmlns 等々のように要素の機能・性格に応じた固有の属性が沢山あります。

さて、今日ではマイナーとなり、W3C非推奨とされていますが、ブラウザの黎明期から20世紀末頃までは、例えば、color、border、center、align等々のように、表示を左右する属性を要素属性に書き連ねる HTML 文が当たり前でした。

今や「文書の構造は HTML 文で、装飾を CSS と Javascript で」とのルールが支配的になっており、要素の style 属性を使用するインラインスタイル設定さえも推奨されていませんから、構造化したHTML文を書き続けていると、ついタグ要素属性のことは軽視しがちになってしまいます。

しかし、このエントリイで取り上げる attr インスタンスメソッドは、Javascriptからタグ要素属性を操作する訳ですから、改めて要素属性について認識を新たにする必要があります。特にstyle属性が存在する場合にのみ、当該要素に係るstyleオブジェクトが存在していることに注意しなければなりません。

jQuery.attr(elem, name, value) メソッドの機能

このメソッドは、第 1 引数で指定する要素の、第 2 引数で指定する属性を取得するか、または設定するものです。このように目的は単純ですが、ここでもまた IE 対策に多くのコードが費やされています。解読作業を通じて、IE の勝手な仕様に費やされたユーザーの労苦と年月を思い、改めて驚愕してしまいました。

さて、整理のためにもまず IE のタグ属性に係る固有仕様やバグを列挙しておきたいと思います。

Javascript でタグ属性をコントロールする場合に
配慮しなければならないIE 固有の仕様等
class 属性

DOM では class 属性を className 名で扱うが、W3C 準拠ブラウザの場合には getAttribute/setAttribute メソッドにおいては "class" で class 属性にアクセスできる。

しかし、IE では getAttribute/setAttribute メソッドにおいても class ではなく className としなければならない。

getAttribute メソッド
IE では "style" では style 属性値は取得できず、"cssText"を使わなければならない。
IE は href、src、style 属性値を取得する場合第 2 引数を要求する。
setAttribute メソッド
IE では DOM 要素に直接 style 属性を設定できない。替わりに elem.style オブジェクトに対して設定する。
IE は setAttribute メソッドにおいて自動的に value 値を文字列に変換しない。
opacity 属性
IE は alpha filter に固執している。
input タグの type 属性
IE はその属性値を勝手に変更する。
float プロパティ
IE の場合 style.cssFloat ではなく、style.styleFloat

jQuery.attr() クラスメソッド解読

■ jQuery.attr() クラスメソッド■
    // 引数は順に対象要素、属性名称、属性値
 967: attr: function( elem, name, value ) {
 968:  // don't set attributes on text and comment nodes
 969:  if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
 970:   return undefined; // テキスト/コメントノードの場合には未定義値を返す。
 971:
 972:  var notxml = !jQuery.isXMLDoc( elem ), // elem が xml 文書内の要素かどうか
 973:   // Whether we are setting (or getting) // value があれば set = true、
 974:   set = value !== undefined; // なければ set = false。
 975:
 976:  // Try to normalize/fix the name  // xml 文書ではなくかつ、
     // name の値が登録されている標準名称であればそれを使う。
     // jQuery.props は #3212-#3223 で定義されているクラスプロパティ
 977:  name = notxml && jQuery.props[ name ] || name;
 978:
 979:  // Only do all the following if this is a node (faster for style)
 980:  // IE elem.getAttribute passes even for style
 981:  if ( elem.tagName ) { // elem にタグ名称があれば
 982:
 983:   // These attributes require special treatment
 984:   var special = /href|src|style/.test( name ); // 特殊名称かどうかチェック
 985:
 986:   // Safari mis-reports the default selected property of a hidden option
 987:   // Accessing the parent's selectedIndex property fixes it
 988:   if ( name == "selected" && elem.parentNode )
 989:    elem.parentNode.selectedIndex; // safari バグ対策。親ノードの index 値を設定
 990:
 991:   // If applicable, access the attribute via the DOM 0 way
      // elem 内に name があり xml 文書ではなく、特殊文字列でもなければ
 992:   if ( name in elem && notxml && !special ) {
 993:    if ( set ){ // value が与えられていれば(つまり設定の場合)
 994:     // We can't allow the type property to be changed (since it causes problems in IE)
        // 処理対象が input タグで name 値が type で、親要素があれば
 995:     if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
 996:      throw "type property can't be changed";
 997:
 998:     elem[ name ] = value; // name 値に value を「敢えて」代入する。
 999:    }
1000:
1001:    // browsers index elements by id/name on forms, give priority to attributes.
       // 処理対象が form タグ要素で name 属性値が取得できる場合には
1002:    if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
        // そのノードバリュー値を return する。
1003:     return elem.getAttributeNode( name ).nodeValue;
1004:
1005:    // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
1006:    // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
1007:    if ( name == "tabIndex" ) {
1008:     var attributeNode = elem.getAttributeNode( "tabIndex" );
1009:     return attributeNode && attributeNode.specified //★不明な箇所
1010:      ? attributeNode.value
1011:      : elem.nodeName.match(/(button|input|object|select|textarea)/i)
1012:       ? 0
1013:       : elem.nodeName.match(/^(a|area)$/i) && elem.href
1014:        ? 0
1015:        : undefined;
1016:    }
1017:
1018:    return elem[ name ];
1019:   }
1020:   // IE では "style" を getAttribute/setAttribute メソッドに設定できないので、
1021:   if ( !jQuery.support.style && notxml &&  name == "style" )
       // elem として elem.style を、また nameとして cssText を使用して
       // jQuery.attr() メソッドを再帰呼び出しする。
1022:    return jQuery.attr( elem.style, "cssText", value );
1023:   // #1022 による再帰呼び出し時には、#981 の if 条件が成立しなくなるので、
      // #1024-#1035 は適用されない。つまり #1024-#1035 は name=="style" 時には
      // 適用されなくなる。
1024:   if ( set ) // 属性を設定する場合 IE では value 値を文字列に変換しないため
1025:    // convert the value to a string (all browsers do this but IE) see #1070
       // elem の name 属性ノードの値を強制的に文字列に変換する。
1026:    elem.setAttribute( name, "" + value );
1027:
1028:   var attr = !jQuery.support.hrefNormalized && notxml && special
1029:     // Some attributes require a special call on IE
        // 特殊な属性(href, src, style)の場合、取得に際して
        // IE のみ第 2 引数が必要となり、この 2 は「値を返す」の意だそうです。
1030:     ? elem.getAttribute( name, 2 )
1031:     : elem.getAttribute( name );
1032:
1033:   // Non-existent attributes return null, we normalize to undefined
      // #1028 で取得した attr が null 値ならば undefined に変える。
1034:   return attr === null ? undefined : attr;
1035:  } // #981以降の if 文がここで終わる。
1036:
1037:  // elem is actually elem.style ... set the style
1038:  // #1039-#1054 は #1040 行の if 条件により他のブラウザには適用されない。
     // 専ら IE だけに適用され、しかも再帰後の attr メソッドだけで意味のある
     // ブロックである。このブロック内の elem は #1021-#1022 により、既に
     // elem.style となっているが、1度目の attr メソッド実行では、このブロッ
     // クに到達する前に attr メソッドが再帰呼び出しされてしまう。よって
     //  #1039-#1054 ブロックは IEに対する再帰呼び出しの場合にのみ適用される。
1039:  // IE uses filters for opacity // IE の不透明度を処理する
1040:  if ( !jQuery.support.opacity && name == "opacity" ) {
1041:   if ( set ) { // 属性設定の場合
1042:    // IE has trouble with opacity if it does not have layout
1043:    // Force it by setting the zoom level
1044:    elem.zoom = 1;
1045:
1046:    // Set the alpha filter to set the opacity
       // elem.filter プロパティに opacity 属性をセットする。
1047:    elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
        // alpha(・・・)があればそれを消去し、value に整数部がなければ空文字を、
        // 整数部があれば value 値を 100 倍してフィルター値を作成する。
1048:     (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
1049:   }
1050:   // filter プロパティが存在し、"opacity=" 文字列があれば
1051:   return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
       // 2 番目の opacity(・・・) を100で割ってからその値を文字列化して返す。
       // なお何故 2 番目なのかは解明できなかった。
1052:    (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
1053:    ""; // "opacity=" 文字列がない場合空文字を返す。
1054:  } // IE 不透明度対策終わり
1055:  // name 文字列を駱駝文字列化する。置換後文字列に関数が置かれているが、
     // その使用と使い方については、学習のためにも後術する。
1056:  name = name.replace(/-([a-z])/ig, function(all, letter){
1057:   return letter.toUpperCase();
1058:  });
1059:
1060:  if ( set ) // 属性設定の時には elem の name 属性にここまで処理してきた
1061:   elem[ name ] = value; // value を代入する。
1062:
1063:  return elem[ name ]; // elem の name 属性値を返す。
1064: },

▲ToTop

jQuery().attr() インスタンスメソッド解読

attr() インスタンスメソッドは、当該インスタンスに登録されている要素の属性値を取得/設定するためのメソッドで、その内実は attr() クラスメソッドに他なりません。

とっても attr() メソッドは複雑な構造となっているため、引数の状態に応じて具体的なコード進行を探ってみます。

  • まず、引数が文字列型の name だけの場合

    インスタンスに登録されている最初の要素の name 属性値を取得します。このとき $.props() メソッドによりブラウザにより差異のある名称の統一が図られ、また当該 name に相当する属性が存在しない場合には(#1034)未定義値が返されます。(#172-179)

  • 次に、引数が nama と value の 2 つの場合

    当該インスタンスに登録されている各要素に対して、当該 name 属性に value 値を設定します。(#182-191)この場合 type がありませんから、$.attr メソッドの第 1 引数は this となります。

  • 第 3 に、引数が object 型の name だけの場合

    直前の場合と同様に #182-191 の部分が作動して、当該インスタンスに登録されている各要素に対して、nameオブジェクト内の各プロパティ名を属性とし、プロパティ値をその属性値に設定します。

  • 第 4 に、第 3 引数が与えられた場合(と言っても jquery.js においてそれは "curCSS" だけしかケースとしては存在していません。)

    name が文字列型ならば $.curCSS(this[0],name) が起動されて、インスタンスの最初の要素の、 name 属性の算出 CSS スタイル値を取得します。
    ここに算出 CSS スタイル値とは、ブラウザが当該要素を描画する際に算出されるスタイル値のことで、当該スタイル属性の定義有無に関わらず、ブラウザが算出するスタイル値となります。

    なお、後述するようにこのケースはまさに css() インスタンスメソッドそのものに他なりません。

  • 第 5 に、第 3 引数 type == "curCSS" が与えられ、かつ name がオブジェクト型の場合

    インスタンスの各要素の style オブジェクト( this.style )に対して、$.attr(this.style,name,$.prop(this,options[name],"curCSS",name) が起動されます。

    ここに prop クラスメソッドは、単位 "px" が必要なスタイル名にはそれを付け、不要ならば付与しない役割を果たします。

    こうして、各要素のstyle属性に対して value または value + "px" が設定されます。

■ jQuery().attr() インスタンスメソッド
   // 引数は順に属性名、属性値、type は 実際には"curCSS" 
168: attr: function( name, value, type ) {
169:  var options = name;
170:
171:  // Look for the case where we're accessing a style value
172:  if ( typeof name === "string" ) // name が文字型で
173:   if ( value === undefined ) // attr( name, type ) ならば
      // インスタンスの最初のノードが存在すれば、jQuery[type]( this[0], name )
      // 又は jQuery.attr( this[0], name ) の返値を返す。
      // type == "css" | "curCSS" ならば CSS 値を取得することになる。
      // 実際 css() インスタンスメソッドは this.attr(key,value,"curCSS") の
      // 形式で attr() インスタンスメソッドを呼び出す。
174:    return this[0] && jQuery[ type || "attr" ]( this[0], name );
175:
176:   else { // name が文字列型でないならば
177:    options = {}; // 変数 options を空オブジェクトに変えてから、
178:    options[ name ] = value; // { name : value } とする。
179:   }
180:
181:  // Check to see if we're setting style values
    // このブロックは name が文字型ではない場合に作動する。
182:  return this.each(function(i){ // インスタンス内の各要素エレメントに対して
183:   // Set all the styles  // 実際には style 以外の要素属性の設定も行う。
184:   for ( name in options ) // オブジェクト走査を行う。
185:    jQuery.attr( // jQuery.attr() クラスメソッドを起動して
186:     type ?
187:      this.style : // type があれば this.style
188:      this, // type がなければ this
       // attrメソッドの第 3 引数はjQuery.prop()メソッドの返値とする。
       // 返値は value + "px" または value となる。
189:     name, jQuery.prop( this, options[ name ], type, i, name )
190:    );
191:  });
192: },
193:

jQuery().css() インスタンスメソッド解読

このメソッドは css 値をインスタンスに設定したり、インスタンスから読み取るもので、内実は attr() インスタンスメソッドを利用しています。

具体的には、上述の jQuery().attr() インスタンスメソッドの第 4 のケースを参照してください。

■ jQuery().css() インスタンスメソッド
194: css: function( key, value ) {
195:  // ignore negative width and height values
196:  if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
197:   value = undefined;
198:  return this.attr( key, value, "curCSS" );
199: },

▲ToTop

jQueryに学ぶ Javascript の基礎(8) jQuery.support upon ver1.3.2──jQuery解読(44)

ver 1.3.x から登場した jQuery.support クラスプロパティとは?

直前エントリイで触れた curCSS() クラスメソッドの中にも jQuery.support が登場していましたが、別エントリイで触れようと思ったので、この新しい jQuery クラスプロパティには言及しませんでした。

しかし、曖昧さは無知と安逸への下り坂をスタンバイします。この気持ち悪さを解消するには曖昧なままにせず、解明する道しか選択肢がありません。

そこで、意訳も含めて大変うまく訳していると思われるサイト【jQuery 1.3.2 日本語リファレンス】をこっそり(^_^);眺めさせてもらって jQuery.support を学習しました。

極めて雑な言い方ですが、このプロパティはクロスブラウザ対応に係る 1 つの答えです。

それはブラウザの基礎的な事項について W3C 対応か否かを調査する多数のメソッドを内包させたブラウザ調査ツールとも言うべきものです。

これにより、各種ブラウザの W3C 対応状況を今までよりも遙かに分かりやすく知ることが出来るようになりました。

使っているブラウザのW3C対応を知るために

jQuery.support を使えば今見ているブラウザの性能を把握できます。

大変分かりやすいので先に触れたサイトにも出ているサンプルを、ここに引用して理解を深めたいと思います。

因みに、IE8 であっても殆どのプロパティ値が false ( =W3C 標準仕様ではない)となります。相変わらずの Microsoft 社の対応が垣間見られます。

他方、当初は IE もどきとして開発されてきた Opera は途中から路線を変更し、今や W3C 準拠ブラウザとなり、実際 Opera Ver 9.64 でこのページを表示したところ、以下の全ての結果が true となりました。

今見ているブラウザの jQuery.support による W3C 対応チェック

出典:jQuery 1.3.2 日本語リファレンス

 $(function(){$.each($.support, function(key, val){
  $("dl").append("<dt>"+"・"+key+"</dt><dd class='ml_4'>"+val+"");
 });});

▲ToTop

jQuery() の挙動を解読する(29) jQuery で DOM エレメントの CSS 値を取得する upon ver1.3.2──jQuery解読(43)

jquery.js は jQuery.css() や jQuery.curCSS() クラスメソッドで、スタイル宣言されていない CSS スタイル値や未描画要素の CSS スタイル値を取得する

CSS 値はそれが明示的に style 宣言されている場合には elem.style.styleName で容易に所得出来ます。しかし明示的に宣言されていない限り、要素が隠蔽( display : none )されていたり、ブラウザによる描画前の時点では、当該要素の幅や高さは取得できません。

さて、jQuery サンプルで多用される 1 つのメソッドに jQuery(x).css(y) インスタンスメソッドがあります。これは jQuery(x) によって Web サイト内から抽出した DOM Nodes を対象として、y で指定された様々な CSS スタイルを設定したり、宣言されているスタイル値を取得する汎用的なメソッドです。

これに対して jQuery.css( elem, name, ・・・) は elem DOM Node の name スタイル属性値を取得するためのクラスメソッドで、このメソッドの前後に定義されている swap() メソッド(#734-#748)や curCSS() メソッド(#781-#845)と連携して、明示的にスタイルが宣言されていない場合や要素が隠蔽されている場合も含めて、スタイル値を取得します。

このエントリイでは jquery.js がどのように CSS 値の取得を行うのかを解読しようと思います。

なお、以下に登場するボックスモデルに係る名称は下図に拠ります。

ボックスモデルの寸法説明図

呼称は全て日本語化し、マージン辺、ボーダー辺、パディング辺、内容辺とします。

jQuery.css() クラスメソッド解読

■ jQuery.css() クラスメソッド ■
750: css: function( elem, name, force, extra ) {
    // 引数は順に、タグ要素、スタイル名称。force は算出スタイル値を求めか否か、
    // extra は内容辺、ボーダー辺またはマージン辺のどこまでのスタイル値を求めるか
751:  if ( name == "width" || name == "height" ) {
752:   var val, props = { position: "absolute", visibility: "hidden", display:"block" },
       which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
753:
754:   function getWH() {
755:    val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
756:
757:    if ( extra === "border" )
758:     return;
759:
760:    jQuery.each( which, function() {
761:     if ( !extra )
762:      val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
763:     if ( extra === "margin" )
764:      val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
765:     else
766:      val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
767:    });
768:   }
769:
770:   if ( elem.offsetWidth !== 0 )
771:    getWH();
772:   else
773:    jQuery.swap( elem, props, getWH );
774:
775:   return Math.max(0, Math.round(val));
776:  }
777:
778:  return jQuery.curCSS( elem, name, force );
779: },

jQuery.css() メソッドは次のようにして CSS 値を取得します。

  1. ユーザーが値を取得したいスタイル( name で指定)が幅か高さである場合(#751)

    この場合には Microsoft 社が IE において定義し、他のブラウザも追随した便利なメソッド offsetWidth あるいは offsetHeight を使って幅や高さを求めます。

    1. elem.offsetWidth 値があれば(#770)、つまり当該要素がブラウザで描画されていれば、getWH() を起動し、要素の幅又は高さを計測します。勿論、幅 or 高さのいずれを取得するかはユーザーが name で指定します。
    2. elem.offsetWidth 値が取得できない場合、そうなるのは描画されていないからですから jQuery.swap() を起動して、ブラウザ表示に変化が出ないように、要素の CSS スタイル設定を、「絶対配置・非表示かつブロック表示」に一時的に変更してから、getWH() メソッドを呼び出して当該要素のスタイル値を取得します。
    3. getWH() 関数内の処理……ボーダー辺までの幅か高さを求める場合を除き jQuery.curCSS() メソッドを呼び出して計測します。
      1. ボーダー辺までの幅か高さを求める場合には、offset 値はボーダー辺までの値ですから、offset 値をそのまま返します。(#757-758)
      2. マージン辺までの幅か高さを求める場合(#763-764)margin 値を offset 値に加算します。
      3. 内容辺までの幅か高さを求める場合(#761-762及び#765-766)パディング値とボーダー値を offset 値から差し引きます。
      4. パディング辺までの幅か高さを求めることは意味がないと考えたのでしょう、算出しません。
  2. ユーザーが取得したいスタイル名が幅でも高さでもない場合(#772)

    処理を jQuery.curCSS( elem, name, force ); に委ねます。(#778)

▲ToTop

jQuery.css() の肝は中で使用されている this にあり

さて、このメソッドは決して難解な箇所はありません。たった 1 つのことを除いては!

中で 3 箇所使用されている this が一寸見には何を指しているのか、大変わかりにくいのです。

前後の文脈から何を指すべきかは推測できますが、「どうしてこの this がそれを指すのか?」───それが暫く分かりませんでした。

解明の糸口は推測した内容と値から、逆に色々考えていて突如見えてきました。

Javascript の基本中の基本ですが、メソッド内で使用される this は、当該メソッドを呼び出したオブジェクトを参照します。

これを改めて踏まえて、css() クラスメソッド内で使用されている this について解明してみます。

上のコードで注目に値する箇所は getWH() です。その中では each() クラスメソッドが起動され、更にその中で curCSS() クラスメソッドが呼び出されていますが、3 カ所の this は全て getWH() 内の each() 内の curCSS() の引数内にあります。

要点は 3 つの this が全て each() クラスメソッド内にあって、jQuery.curCSS() クラスメソッドの引数となっていることです。

each メソッドは第 1 引数の配列要素毎にそれを呼び出し元にして、第 2 引数の関数を実行します。callback.call( arguments[0][i], arguments[1] ) です。

上のコードにおいて which は [ "Left", "Right" ] か又は [ "Top", "Bottom" ]です(#752)。これを第 1 引数として、第 2 引数である 760 ~ 766 行の関数が call されます。ですから呼び出された関数内で this はまず which を参照します。

しかし、呼び出された関数内部で更に別の関数 jQuery.curCSS() メソッドが起動されますから、この関数が起動した後は this は jQuery 関数オブジェクトを参照します。

しかし、this は jQuery.curCSS() メソッドの引数として登場しています。ここが肝心なところです。

もし this が jQuery.curCSS() メソッドの内部で使用されれば、それは this の仕様から jQueryを参照します。しかし、引数である this は無名関数内から jQuery.curCSS() メソッドに外挿され、投入されます。つまり、所与の値として付与されるのですから、このメソッドはそれを受け取って起動するわけで、受け取る時点では this は jQuery ではなく 巡回処理対象 which が参照するいずれかの要素を参照しているのです。

こうして this は "Left"、 "Right"、"Top"、あるいは "Bottom" に置き換えられることになります。

▲ToTop

jQuery.swap() クラスメソッド解読

このクラスメソッドは elem に対して、options による新たなスタイルを一時的に設定し、elem に設定されている CSS スタイルではなく、optionsによるその新たな CSS スタイルに callback( 実際には getWH ) 関数を適用します。

swap() メソッドは、jquery.js の中では css() メソッドからたった1度呼び出されるだけですが、隠蔽されているために、あるいは描画されていないために CSS スタイル値を取得できない要素のスタイル値を、ブラウザ表示を一切変えることなく取得するためのものです。つまり、要素の幅や高さを描画前に知るために使われます。

display:none が指定されている要素は、ブラウザから見れば存在しないことと同値です。また、ボックス内容領域幅よりも長いテキストノードは、暗黙的か明示的かに関わらず CSS スタイルが設定されていない限り、描画されなければその表示幅や高さは決まりません。

しかし、ブラウザ描画前に要素の高さや幅を知りたいケースは決して少なくありません。例えば mouseover 時のポップアップ表示はその好例です。popup は mouseover した要素の直近位置で、ブラウザ表示領域からはみ出さぬような位置に、適切な大きさの領域を表示させ、その中に解説文字などを示す場合に使用します。そのときには幅を指定し高さを文字数に応じて可変となるようにしますが、その大前提として事前に高さを知る必要があります。そうしないとポップ領域がウィンドウの上又は下にはみ出してしまうからです。

以上のようなケースに対処するための関数が、swap() メソッドです。

■ jQuery.swap() クラスメソッド ■
734: // A method for quickly swapping in/out CSS properties to get correct calculations
735: swap: function( elem, options, callback ) {
    // 引数は順にタグ要素、css() メソッド内で定義した要素の css style オブジェクト
    // callback は swapメソッド呼び出し時の引数定義により getWH() メソッドとなっている。
736:  var old = {}; // elem の本来のスタイルプロパティを記憶させるオブジェクト
737:  // Remember the old values, and insert the new ones
738:  for ( var name in options ) { // elem に設定されている各種スタイル値を
739:   old[ name ] = elem.style[ name ]; // old オブジェクトに複写する。
     // css() メソッド内で次の props が設定済みとなっている。
     // props = { position: "absolute", visibility: "hidden", display:"block" }
     // 第2引数 options に登録されているこの CSS 値を elem のスタイルオブジェクトに複写する。
740:   elem.style[ name ] = options[ name ];
741:  }
742:  // CSS 値計測用プロパティが追加された elem から callback 関数 getWH を呼び出す。
    // この処理の中で必要な CSS 値を取得する。
743:  callback.call( elem ); // css() から呼び出されたときには callback = getWH となっている。
744:
745:  // Revert the old values
746:  for ( var name in options ) //処理が終わったら記憶させてあるプロパティを
747:   elem.style[ name ] = old[ name ]; // elem に戻す。
748: },

▲ToTop

jQuery.curCSS() クラスメソッド解読

これはその名の通り Current CSS 値(算出スタイル値とも呼ぶ)を取得するメソッドで、jquery.js 内では css() メソッドなど 10 箇所から呼び出されています。

IE 以外の W3C 規格に準拠するブラウザの場合と、準拠を頑なに拒絶する IE の場合とで、全く異なる方法で CSS 現在値を取得しなければならないため、つまりクロスブラウザ対応を図る必要があるためコードが長くなっています。

■ jQuery.curCSS() メソッド ■
781: curCSS: function( elem, name, force ) { // force については後述する797行の解説を参照
782:  var ret, style = elem.style;
783:
784:  // We need to handle opacity special in IE
785:  if ( name == "opacity" && !jQuery.support.opacity ) { // IE の場合
786:   ret = jQuery.attr( style, " opacity" ); // 不透明度値を ret に取得する。
787:
788:   return ret == "" ? // 返値を決める。
789:    "1" :
790:    ret;
791:  }
792:
793:  // Make sure we're using the right name for getting the float value
794:  if ( name.match( /float/i ) ) // 正しく float セレクタ名があれば
     // jquery.js では W3C 準拠表現の cssFloat も IE 固有の styleFloat も共に
     // styleFloat で統一して扱っています。( #3210 )
795:   name = styleFloat;
796:
    // force == false で style と style.name があれば style.name 値をそのまま使用する
    // つまりブラウザによる算出スタイル値を使わずに、スタイル属性の設定値を利用する。
    // 裏返せば force=="true" ならば、style 属性の有無に拘わらず、カレントスタイル値を算出する。
797:  if ( !force && style && style[ name ] )
798:   ret = style[ name ];
    // 以下はブラウザによる描画値を取得するためのコード
    // force を指定することによりブラウザの算出描画値を取得する。
799:  // getComputedStyle 関数があれば( つまり IE 以外 )
800:  else if ( defaultView.getComputedStyle ) {
801:
802:   // Only "float" is needed here  // float 文字列が name に含まれる場合、
803:   if ( name.match( /float/i ) )
804:    name = "float"; // 使用する呼称は「 float 」だけでよい。
805:   // 所謂駱駝文字列内の大文字を小文字化してハイフンを前に付ける。
     // Javascript スタイル属性表現を CSS スタイル表現に変える。
806:   name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
807:   // elem 要素の描画値を取得する準備。
808:   var computedStyle = defaultView.getComputedStyle( elem, null );
809:
810:   if ( computedStyle ) // computedStyle があれば値を取得する。
      // その値をretに代入する。これによりブラウザによる描画値が ret に代入される。
811:    ret = computedStyle.getPropertyValue( name );
812:
813:   // We should always get a number back from opacity
814:   if ( name == "opacity" && ret == "" )
815:    ret = "1";
816:   // 以下は #842 迄 IE の場合
817:  } else if ( elem.currentStyle ) {
818:   var camelCase = name.replace(/\-(\w)/g, function(all, letter){
819:    return letter.toUpperCase(); // 駱駝文字列への変換を行う。
820:   });
821:   // 値を取得する。
822:   ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
823:
824:   // From the awesome hack by Dean Edwards
825:   // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
826:
827:   // If we're not dealing with a regular pixel number
828:   // but a number that has a weird ending, we need to convert it to pixels
     // #822 の取得値 ret が数字始まりで "px" が途中にあるか、ない場合には、
829:   if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
830:    // Remember the original values
831:    var left = style.left, rsLeft = elem.runtimeStyle.left;
832:
833:    // Put in the new values to get a computed value out
      // left 値を取得してランタイムスタイルの left 値に代入し、
834:    elem.runtimeStyle.left = elem.currentStyle.left;
835:    style.left = ret || 0; // style.left 値には #822 の値を入れておき、
836:    ret = style.pixelLeft + "px"; // ret 値は正しい値を取得して代入する。
837:
838:    // Revert the changed values // 元の値を戻す。
839:    style.left = left;
840:    elem.runtimeStyle.left = rsLeft;
841:   }
842:  }
843:
844:  return ret; // ret 値を返す。
845: },
846:

jQuery() の挙動を解読する(28) pushStack()インスタンスメソッド再 学習 upon ver1.3.2──jQuery解読(42)

以前書いたエントリイを全面的に刷新し、ver1.3.2対応としましたので、よろしければご覧下さい。

jQuery()の挙動を解読する。(12) pushStack()解読 upon ver1.3.2──jQuery解読(21)

jQuery() の挙動を解読する(27) 事例による jQuery.extend() 学習 upon ver1.3.2──jQuery解読(41)

jQuery.extend() メソッド ──オブジェクトの拡張

このメソッドは jquery.js に頻繁に登場するので、屋上屋を重ねて事例を綴る必要は皆無ですが、敢えてjquery.js に登場しない表現を使って、へそ曲がりな extend メソッドの使い方を試してみました。これは jQuery.extend メソッドの定義コードを検証してみたいからです。

Test 1 ── jQuery.extend ( obj1, obj2, obj3 )

異なるプロパティを持つ 3 つのオブジェクトの統合です。obj1 に obj2 と obj3 が統合されます。

 var obj1 ={a:1}, obj2 ={b:2}, obj3 ={c:3};
$.extend( obj1, obj2, obj3 )  // obj1=={a : 1, b : 2, c : 3}

なお jquery.js において jQuery 自身の拡張ではないオブジェクト拡張は、ajaxSetup メソッドで使われています。#3362 jQuery.etend( jQuery.ajaxSetting, settings ) これ以外は未だ見つけていません。

Test 2 ── jQuery.extend ( obj1, obj2, obj3 )

一部重複するプロパティを持つ 3 つのオブジェクトの統合です。

重複するプロパティは後でコピーされるプロパティ値で上書きされます。

 var obj1 ={a:1}, obj2 ={a:2,b:2}, obj2 ={b:3,c:4,d:5};
$.extend( obj1, obj2, obj3 )  // obj1=={a : 2, b : 3, c : 4, d : 5}
Test 3 ── jQuery.extend ( true, jQuery ,{ temp: function(a,b){return a+b} } )

extend 定義コードによれば、このメソッドを実行すると 第 2 引数である jQuery オブジェクトを拡張し、jQuery.temp = function(a,b){return a+b} となるはずです。

そして確かにそうなりました。敢えて jQuery を引数にしても extend メソッドは成功裏に作動したわけです。

但し、第 2 引数を this にしたところ、window オブジェクトに新たなオブジェクトが作られてしまいました。

Test 4 ──jQuery.extend ( false, jQuery ,{temp: function(a,b){return a+b}} )

今度は第 1 引数を false としてみました。extend 定義コードによればこの結果も Test3 同様に 第 2 引数である jQuery オブジェクトを拡張して、jQuery.temp = function(a,b){return a+b} となるはずです。

ところが、駄目でした。#567 の typeof target === "boolean" が コードを見る限り true となるはずなのに、そうならないのです。

第 1 引数を false とすると #564 で target = false となるから、#567 は typeof false === "boolean" となり、これは true となるはずなのに、false となってしまうのです。この結果 #568~#571 のブロックがスルーされてしまうのです。

どうしてこうなってしまうのか原因を解明できず、この Test は後味の悪い未完のものとなってしまいました。

Firebug 1.3.0~1.3.2 のバグが 1.3.3 でやっと解消

やっとまともに使えるようになったようです。

Firebug はJavascript コードを書くために必須のアイテムですが、ver 1.3.0~1.3.2 までのそれは基本的な問題に関するバグがあり、この間1.2.1を使ってきました。

Firebug 1.3.x のバグについて

それが 1.3.3 になってやっと解消されたようなのです。

これでやっと支障なくコード作成が可能となりました!(^。^;)ホッ!

jQueryに学ぶ Javascript の基礎(7) 組み込みクラス Array の各種メソッドに apply() メソッドを適用する──jQuery解読(40)

組み込みクラス Array の全てのメソッドに apply() メソッドは適用出来るか?

直前のエントリイ において、Array.prototype.push() メソッドへの apply() メソッドの適用について考えてみました。また、その中で関連して使用した jQuey().get() メソッドについては、こちら ( jQuery()の挙動を解読する。(11) 簡単なインスタンスメソッドいくつか──jQuery解読(19) ) で解読しました。

これらのエントリイで学習したことは、組み込みクラス Array の push() メソッドと slice() メソッドに、apply() 又は call() メソッドを適用することでした。言い換えれば、jQuery インスタンスオブジェクトに対して、組み込みクラス Array の push() メソッドあるいは slice() メソッド を apply() または、call() することでした。

さて、組み込みクラス Array の2つのメソッドに appy() / call() メソッドが適用できるということは、Array の他のメソッドに対しても apply() / call() メソッドが適用出来るのではないか、と期待しても不思議ではありません。

そこで早速それを試してみたところ、確かにそれが可能であることが分かりましたので、以下にその検証過程を詳らかにします。

▲ToTop

Array クラスのメソッドリスト

Array クラスのメソッドにはよく知られているように次のようなものがあります。

join()、reverce()、sort()、concat()、slice()、splice()、push()、pop()、unshift()、shift()、toString()、toLocalString()

この他に Firefox1.5 以降に実装されている Javascript1.6 以降で使用可能な indexOf()、lastIndexOf()、forEach()、map()、filter() などがありますが、これらは標準にはなっていないので、ここでは検証対象外とします。

まず、オブジェクトのプロパティにとってその並び順に意味はありませんから、並び順に作用する Array クラスのメソッド ( reverce、sort ) に対する apply() メソッドの適用は、検証から除外します。

残ったメソッドは 10 種類になりますが、検証はメソッドの作用内容から次のグループに分けて行います。

  1. push()、pop()、unshift()、shift()
  2. slice()、concat()、splice()
  3. join()、toString()、toLocalString()

検証の方法

jquery.1.3.2 をインクルードした Local HTML ファイルを FireFox3.x で開き、 Firebug を走らせながら、そのコンソールウィンドウに任意のコードを入力し、結果表示を観察して検証を行いました。また、検証に際しては以下のプロパティ一覧ユーティリティを使用しました。

 // 検証に利用したオブジェクトのプロパティ名とプロパティ値を一覧するための関数
 var getProp = function(obj){
    var ret = [];
    for (var prop in obj) ret.push(prop + " : " +obj[prop]);
     return ret.join();
 };

▲ToTop

push()、unshift()、pop()、shift()

jQuery().setArray() インスタンスメソッドにおいて push() メソッドが利用されていたことから推測できることは、これらのメソッドに apply() / call() メソッドを適用することは、オブジェクトに対して array like な配列を追加又は削除する働きを持つだろう、ということです。

以下にそれらの検証過程と結果を記します。

unshift()
 /*++++++ unshift() +++++*/
 // unshift 関数に apply(obj,[array])メソッドを適用する
 Array.prototype.unshift.apply ( obj,[ "test",100,["test",500] ] );
 // 適用結果を一覧する
 getProp(obj) // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
  c : object, d : [object Object], 0 : test, 1 : 100, 2 : test,500, length : 3"

以上の結果を見るとこれは push() メソッドの場合の
   Array.prototype.push.apply ( obj, [ "test", 100, ["test",500] ] );
と同一の結果になることが分かります。

つまり、2 つの異なるメソッドの効果が同一となるのは、配列と異なってオブジェクトの場合には、プロパティの並び順に意味はありませんから、最後に要素を追加する push() であれ、最初に要素を追加する unshift() であれ、作用効果が同様になると考えられます。

▲ToTop

次に、配列から要素を削除する pop()、shift() メソッドを調べてみます
 /*++++++ pop().apply() or shift().apply() の検証 +++++*/
 // obj 再定義
 var obj = {
  "a":[1,2,3],
  "b":function(x,y){return x+y},
  "c":"object",
  "d":{x:789,y:321}
 };
 // pop 又は shift メソッドに apply(obj) 又は call(obj) メソッドを適用する
 Array.prototype.pop.apply ( obj ); /*又は*/ Array.prototype.shift.call ( obj );
  /*あるいは*/
 Array.prototype.pop.call ( obj , "" ); /*又は*/ Array.prototype.shift.apply ( obj , [] );
 // 適用結果を一覧する
 getProp(obj) // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
 // c : object, d : [object Object], length : 0"

以上から分かることはこの操作では何も変化しないということです。

ところが objに「配列のような」プロパティが存在する場合に pop().apply(obj) やshift().call(obj)を適用すると、事態が変わります。

 /*++++++ array like なプロパティがある obj に対して、
 pop 又は shift メソッドを apply(obj) 又は call(obj) する +++++*/
 // obj 再定義 ( array like プロパティを含ませる )
 var obj = {
  "a":[1,2,3],
  "b":function(x,y){return x+y},
  "c":"object",
  "d":{x:789,y:321},
  0:"test",
  1:100,
  2: ["test",500] 
 };
 // pop() 又は shift() メソッドに apply(obj) か call(obj)メソッドを適用する
 Array.prototype.pop.call ( obj ); /*又は*/ Array.prototype.shift.apply ( obj ); /*など*/。
 // 適用結果を一覧する
 getProp(obj) // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
  c : object, d : [object Object], 0 : test, 1 : 100, length : 2"
  // array like なプロパティの、最後のそれ( 2 : test,500 )が削除されたことが分かる。

 // 上記の後に再度 pop() 又は shift() メソッドに apply(obj) メソッドを適用する
 Array.prototype.pop.apply ( obj ); 又は Array.prototype.shift.apply ( obj );
 // 適用結果を一覧する
 getProp(obj) // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
 // c : object, d : [object Object], 0 : test, length : 1"
  //  array like なプロパティの、最後のそれ( 1 : 100 )が削除されたことが分かる。

以上をまとめると、次のようになります。

  1. obj オブジェクトに array like なプロパティがある場合には、 Array.prototype.pop.apply ( obj ) 又は Array.prototype.shift.apply ( obj ) を実行すると、当該の array like なプロパティの 1 つを削除する。その削除対象は pop でも shift でも同様に、最大整数のプロパティ名を持つプロパティとなる。
  2. 一方、obj オブジェクトに array like なプロパティが存在しない場合には、 Array.prototype.pop.apply ( obj ) 又は Array.prototype.shift.apply ( obj ) を履行しても obj に何の変化も生じない。

▲ToTop

slice()、concat()、splice()

jQuery().get() では Array.prototype.slice.call( this ) によって this に格納されている DOM Elements を取り出しています。

これを踏まえて、上と同様にカスタムオブジェクトに対して Array.prototype.concat.call( ) 及び Array.prototype.splice.call( ) を適用した場合に、意味のある結果が得られるのかどうかを検証してみます。

Array.prototype.slice.call( obj )

最初に jQuery().get() で使用されている slice() メソッドについて改めて記しておきます。

 // obj 定義
 var obj = {
  "a":[1,2,3],
  "b":function(x,y){return x+y},
  "c":"object",
  "d":{x:789,y:321},
  0:"test",
  1:100,
  2:["test",500],
  length:3
 };

 // slice.call(obj) :結果はプロパティ値を要素とする 配列になるので、
 // toString() メソッドで要素を取り出すことにする。
 Array.prototype.slice.call(obj).toString(); // 結果 "test,100,test,500"

3 つの array like なプロパティ値だけをオブジェクトから取り出せることが分かります。

jQuery().get() の場合には jQuery() オブジェクトの array like なプロパティ値は DOM Elements ですから、 jQuery().get() によって当該 DOM Elements が「取り出せる」ことになります。(詳細は こちらに まとめてあります。)

Array.prototype.concat.apply( obj , [array] )
 // obj 定義
 var obj = {
  "a":[1,2,3],
  "b":function(x,y){return x+y},
  "c":"object",
  "d":{x:789,y:321}
 };

 // Array.prototype.concat.apply( obj , [array] ) はオブジェクトを
 // 返すので、concat.apply() メソッドの return 値を ret に代入する。
 var ret = Array.prototype.concat.apply( obj, [ "test",100,["examine",500],{} ] )
 // ret オブジェクトを一覧する
 getProp(ret) // 結果 "0 : [object Object], 1 : test, 2 : 100, 3 : examine, 4 : 500, 5 :  [object Object]"

結果を見ると、生成されたret オブジェクトは obj オブジェクトを最初のプロパティ値とし、続けて apply() メソッド第 2 引数配列の各要素を順に(配列がある場合にはその要素に分解して)プロパティ値とする、全体として array likeなプロパティによって構成されることが分かります。

つまり、concat() メソッドに apply( obj,[array] ) メソッドを適用すると、元 obj オブジェクトと第 2 引数の [array] 配列オブジェクトを、その配列内に配列がある場合にはそれを全て要素に分解してから、全体として array like な配列をプロパティとする、新たなオブジェクトが再構築されるわけです。

なお jquery.js(ver 1.3.2) 内に concat は 4 カ所ありますが、それらがここで触れた方法と目的で使われているのかどうかについては、まだ検証していません。

Array.prototype.splice.apply( obj , [array] )
 // obj 定義
 var obj = {
  "a":[1,2,3],
  "b":function(x,y){return x+y},
  "c":"object",
  "d":{x:789,y:321},
  0:"test",
  1:100,
  2:["test",500],
  length:3
 };
 /*---------(1) 単純に新規プロパティを追加するケース-------------------------*/
 // splice.apply(obj,[array])
 Array.prototype.splice.apply( obj, [ obj.length,0,1000,["today","yesterday"],{} ] )
 // 適用結果を一覧する
 getProp(obj) // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
 // c : object, d : [object Object], 0 : test, 1 : 100, 2 : test,500, 
 // length : 6, 3 : 1000, 4 : today,yesterday, 5 : [object Object]"

 /*-- (2) 一部の既存プロパティを削除し、かつ新規プロパティを追加するケース --*/
 // splice.apply(obj,[array])
 Array.prototype.splice.apply( obj, [ 0,2,1000,["today","yesterday"],{} ] )
 // 適用結果を一覧する
  getProp(obj)  // 結果 "a : 1,2,3, b : function (x, y) { return x + y; },
  // c : object, d : [object Object], 0 : 1000, 1 : today,yesterday,
  // 2 : [object Object], 3 : test,500, length : 4"

splice() メソッドは配列に要素を追加/削除するための、最も汎用的な関数です。上の例により、これをオブジェクトに apply することによって、array like なプロパティを必要なだけ追加出来ることが分かります。

例(1)では、第 2 引数である配列の第 1 要素の obj.length により開始番号を設定し、第 2 要素の 0 により何も削除しないことを指示し、1000 以降の 3 つの配列要素を obj オブジェクトに追加しています。

例(2)では、開始番号を 0 、削除数を 2 と指定することにより、既存の 2 つのプロパティ {0 : test, 1 : 100}を削除し、第 2 引数である配列の、第 3 要素から第 5 要素までの 3 つの要素を、新たなプロパティとして obj オブジェクトに追加し{ 0 : 1000, 1 : today,yesterday, 2 : [object Object] }、残存している既存のプロパティ値 "test,500" の要素番号をずらして {3 : test,500} とし、最後に 2 つの要素を削除し、3 つの要素を追加した結果として length 値を 4 としています。

つまり、splice() メソッドを obj オブジェクトに apply すると、obj オブジェクトに対して pop や unshift よりも自在に( 位置を指定してプロパティを部分的に削除することも可能 )、array like なプロパティを付加することが出来ることが分かります。

因みに jquery.js(ver 1.3.2) 内に splice は 3 カ所ありますが、それらがここで触れた方法と目的で使われているのかどうかについては、まだ検証していません。

▲ToTop

join()、toString()、toLocalString()

toString() 及び toLocalString() については、call(obj) を適用して意味のあるreturnは得られませんでしたが、join() については以下のように意味のある結果を得ることが出来ました。

Array.prototype.join.call( obj )
 // obj 定義は同上とする

 // join.call(obj, arg)
 Array.prototype.join.call( obj, " : " )
  // 結果 "test : 100 : test,500"
 // [ "test", 100, ["test",500] ].join(" : ") と同一の結果となっている。

上を見ると join.call(obj, arg) は obj オブジェクトの中の その他プロパティを無視しつつ、array like なプロパティ値だけを文字列として取りだし、arg 文字(列)で接続する機能を持っていることが分かります。

但し、そのような機能を必要とする状況にはどんなものがあるのか、想像すら出来ません。つまりこのメソッドは余り使い道がないような気がします。

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