jquery.jsを読み解く

第2回jQueryライブラリ(172行目~469行目)

今回はjQueryオブジェクトに定義されたメソッドの説明が中心となります。前回のようなjQueryの特徴的な部分と違ってアクロバティックな感じではないので、あまり面白くないかもしれません。しかし、jQueryの挙動を深く理解するためには必要な部分ですので淡々と進めて行きたいと思います。

また、説明に入る前に少々捕捉ですが、前回から今回までの間にバージョン1.2.3がリリースされました。Adobe AIRに対応したり、namespace周りの挙動が若干変わったりしていますが、根本的に大きくは変わってはいないようです。本連載では、引き続き1.2.2を前提に説明を進めていきます。

attr()

0172:  attr: function( name, value, type ) {
0173:    var options = name;
0174:
0175:    // Look for the case where we're accessing a style value
0176:    if ( name.constructor == String )
0177:      if ( value == undefined )
0178:        return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
0179:
0180:      else {
0181:        options = {};
0182:        options[ name ] = value;
0183:      }
0184:
0185:    // Check to see if we're setting style values
0186:    return this.each(function(i){
0187:      // Set all the styles
0188:      for ( name in options )
0189:        jQuery.attr(
0190:          type ?
0191:            this.style :
0192:            this,
0193:          name, jQuery.prop( this, options[ name ], type, i, name )
0194:        );
0195:    });
0196:  },
0197:

attrメソッドは、要素の属性を操作します。jQueryのメソッドは、パラメータによって動作が変わるものが多いのですが、このattrメソッドもその一つです。

第1引数nameのみが渡されてそれが文字列の場合(178行目⁠⁠、別のjQueryメソッドを実行します。どのメソッドを実行するかというと引数typeが指定されていればそのメソッドを実行し、指定されていなければ1020行目のjQuery.attrメソッドを実行します。name,valueのペアで第2引数も指定されていれば、属性値をセットするという動作になります。182行目がその処理でローカル変数optionsに値を格納します。また、attrメソッドにはハッシュを渡すことも可能で、その場合は173行目において格納しておいたoptionsが使用されます。

実際に属性をセットするのは186行目からで、各要素に対してjQuery.attrメソッドを実行します。jQuery.attrメソッドは1020行目で定義されており、詳しくは次回以降に説明します。193行目のjQuer.prop()は、数値が渡された時に単位pxを付加する処理です。

また、引数typeは内部的に使用するもので、CSSの属性値を直接扱うメソッド等で利用されます。

css()

0198:  css: function( key, value ) {
0199:    // ignore negative width and height values
0200:     if ( (key == 'width' || key == 'height') && parseFloat(value) 0201:      value = undefined;
0202:    return this.attr( key, value, "curCSS" );
0203:  },
0204:

CSSのプロパティを設定するメソッドです。 内部的には先ほどのattrメソッドを呼び出しています。widthまたはheightで負の値を設定しようとした場合は、valueをundefinedとし無視します。

また、202行目にあるように、3番目の引数"curCSS"を指定することによって、css属性を扱う処理になります。

text()

0205:  text: function( text ) {
0206:     if ( typeof text != "object" && text != null )
0207:      return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
0208:
0209:    var ret = "";
0210:
0211:    jQuery.each( text || this, function(){
0212:      jQuery.each( this.childNodes, function(){
0213:         if ( this.nodeType != 8 )
0214:          ret += this.nodeType != 1 ?
0215:            this.nodeValue :
0216:            jQuery.fn.text( [ this ] );
0217:      });
0218:    });
0219:
0220:    return ret;
0221:  },
0222:

206行目がパラメータが渡された時の処理で、いったん中身を空にしてからテキストノードを設定して返します。

211行目からはパラメータがない場合の処理で、すべての要素からテキストノードを結合して返します。nodeTypeが1(つまりElement)ならば、再帰的にtextメソッドを呼び出してテキストノードを取得します。また、コメント(nodeType=8)は取り除かれます。

wrapAll()

0223:  wrapAll: function( html ) {
0224:     if ( this[0] )
0225:      // The elements to wrap the target around
0226:      jQuery( html, this[0].ownerDocument )
0227:        .clone()
0228:        .insertBefore( this[0] )
0229:        .map(function(){
0230:          var elem = this;
0231:
0232:          while ( elem.firstChild )
0233:            elem = elem.firstChild;
0234:
0235:          return elem;
0236:        })
0237:        .append(this);
0238:
0239    return this;
0240:  },
0241:

wrapAllメソッドは、1.2から追加された機能で、選択された要素を一纏めに囲って返します。

このメソッドを実行するにはすくなくとも1つ以上の要素が必要なので、224行目にてチェックします。次に囲う方のDOM要素を生成し、これを選択されている要素の直前に挿入し、その一番子要素を探してそこに追加(移動)します。

ここは少し分かりにくいかもしれませんが、何がthisで、jQueryオブジェクトは何を表しているのかが分かれば理解できるかと思います。

wrapInner()

0242:  wrapInner: function( html ) {
0243:    return this.each(function(){
0244:      jQuery( this ).contents().wrapAll( html );
0245:    });
0246:  },
0247:

wrapInnerメソッドは、選択された各要素が持つ各子要素をパラメータで指定された要素で囲みます。243行目のeachによって、各々の子要素に対して先ほど説明したwrapAllメソッドを実行します。

wrap()

0248:  wrap: function( html ) {
0249:    return this.each(function(){
0250:      jQuery( this ).wrapAll( html );
0251:    });
0252:  },
0253:

wrapメソッドは、選択された各要素を引数の要素で囲って返します。249行目のeachメソッドによって、各要素に対して先ほど紹介したwrapAllメソッドを実行しています。

append()

0254:  append: function() {
0255:    return this.domManip(arguments, true, false, function(elem){
0256:       if (this.nodeType == 1)
0257:        this.appendChild( elem );
0258:    });
0259:  },
0260:

appendメソッドは、選択された各要素の内部にコンテンツを追加します。ここでは、domManipという484行目で定義されているメソッドを使っています。domManipについては後ほど説明しますが、table要素を扱い易くしたり、定義されているscriptを退避したりするための内部処理用の関数です。

257行目の要素のnodeTypeがElementであれば、appendChildメソッドを適用しています。

prepend()

0261:  prepend: function() {
0262:    return this.domManip(arguments, true, true, function(elem){
0263:       if (this.nodeType == 1)
0264:        this.insertBefore( elem, this.firstChild );
0265:    });
0266:  },
0267:  

prependメソッドは、選択された各要素の先頭にコンテンツを挿入します。

appendメソッドと同様にdomManipメソッドを使っています。違いは、264行目のinsertBeforeメソッドで、argumentsを最初の子要素の前に挿入します。また、argumentsは複数指定することが可能なので、domManipの第3引数をtrueとすることで、逆順に追加していきます。

before()

0268:  before: function() {
0269:    return this.domManip(arguments, false, false, function(elem){
0270:      this.parentNode.insertBefore( elem, this );
0271:    });
0272:  },
0273:

beforeメソッドは、選択された要素の前にコンテンツを追加します。

こちらもappendメソッドやprependメソッドと同様です。違いは270行目で、parentNodeに対してinsertBeforeメソッドを適用します。

after()

0274:  after: function() {
0275:    return this.domManip(arguments, false, true, function(elem){
0276:      this.parentNode.insertBefore( elem, this.nextSibling );
0277:    });
0278:  },
0279:

afterメソッドは、選択された要素の後にコンテンツを追加します。

こちらもappendメソッドやprependメソッドと同様で、違いは276行目のnextSibling(次の兄弟ノード)の前に挿入する点です。

end()

0280:  end: function() {
0281:    return this.prevObject || jQuery( [] );
0282:  },
0283:

endメソッドは、第1回で説明したようにスタックに格納しておいたprevObjectを返します。なければ空のjQueryオブジェクトを返します。

find()

0284:  find: function( selector ) {
0285:    var elems = jQuery.map(this, function(elem){
0286:      return jQuery.find( selector, elem );
0287:    });
0288:
0289:    return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
0290:      jQuery.unique( elems ) :
0291:      elems );
0292:  },
0293:

findメソッドは、選択された要素から条件に合う要素を選択するメソッドです。

実際の処理は、1445行目で定義されているjQuery.findメソッドで行い、スタックに格納しておきます。289行目の条件式は、一部のセレクタ式を指定した場合に重複を取り除くための処理です。

clone()

0294:  clone: function( events ) {
0295:    // Do the clone
0296:    var ret = this.map(function(){
0297:       if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
0298:        // IE copies events bound via attachEvent when
0299:        // using cloneNode. Calling detachEvent on the
0300:        // clone will also remove the events from the orignal
0301:        // In order to get around this, we use innerHTML.
0302:        // Unfortunately, this means some modifications to 
0303:        // attributes in IE that are actually only stored 
0304:        // as properties will not be copied (such as the
0305:        // the name attribute on an input).
0306:        var clone = this.cloneNode(true),
0307:          container = document.createElement("div"),
0308:          container2 = document.createElement("div");
0309:        container.appendChild(clone);
0310:        container2.innerHTML = container.innerHTML;
0311:        return container2.firstChild;
0312:      } else
0313:        return this.cloneNode(true);
0314:    });
0315:
0316:    // Need to set the expando to null on the cloned set if it exists
0317:    // removeData doesn't work here, IE removes it from the original as well
0318:    // this is primarily for IE but the data expando shouldn't be copied over in any browser
0319:    var clone = ret.find("*").andSelf().each(function(){
0320:       if ( this[ expando ] != undefined )
0321:        this[ expando ] = null;
0322:    });
0323:    
0324:    // Copy the events from the original to the clone
0325:     if ( events === true )
0326:      this.find("*").andSelf().each(function(i){
0327:         if (this.nodeType == 3)
0328:          return;
0329:        var events = jQuery.data( this, "events" );
0330:
0331:        for ( var type in events )
0332:          for ( var handler in events[ type ] )
0333:            jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
0334:      });
0335:
0336:    // Return the cloned set
0337:    return ret;
0338:  },
0339:

cloneメソッドは、その名の通り、オブジェクトを複製するメソッドです。

298行目からのコメントにある通り、Internet Explorer(以下、IE)では複製した要素に関連付けられたイベントの扱いが異なります。具体的には、クローン側でイベントの解除を行うと、オリジナル側でもイベントが解除されてしまいます。このため、IEの場合は、containerおよびcontainer2のローカル変数を介してinnerHTMLによってコピーします。

また、319行目ではexpandoの値をNullにセットします。expandoはユニークなIDして使われるため、重複しては問題があるためです。

325行目からは、引数eventがtrueだった場合にイベントを複製するための処理です。nodeTypeが3、つまりTextノードだった場合には何もしません。それ以外の場合に、329行にあるjQuery.dataメソッドによって割り当てられているイベントを取得し、複製したオブジェクトに対してひとつずつイベントを割り当て直します(333行目⁠⁠。

filter()

0340:  filter: function( selector ) {
0341:    return this.pushStack(
0342:      jQuery.isfunction( selector ) &&
0343:      jQuery.grep(this, function(elem, i){
0344:        return selector.call( elem, i );
0345:      }) ||
0346:
0347:      jQuery.multiFilter( selector, this ) );
0348:  },
0349:

filterメソッドは選択要素から、条件にマッチしない要素を削除して返すものです。実際の処理は、他で定義されているgrep(1158行目)とmultiFilter(1432行目)が行っていますので、ここでは動作のみを説明します。

まず、341行目でpushStackメソッドを呼び出して、後で再利用できるようにスタックに格納しています。そして、引数に関数が渡された場合はgrepメソッドによって要素を評価し、trueとなったものだけが返されます。引数にセレクタ式が渡された場合は、multiFilterメソッドを実行した結果を返します。

not()

0350:  not: function( selector ) {
0351:     if ( selector.constructor == String )
0352:      // test special case where just one selector is passed in
0353:       if ( isSimple.test( selector ) )
0354:        return this.pushStack( jQuery.multiFilter( selector, this, true ) );
0355:      else
0356:        selector = jQuery.multiFilter( selector, this );
0357:
0358:    var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
0359:    return this.filter(function() {
0360:      return isArrayLike ? jQuery.inArray( this, selector ) this != selector;
0361:    });
0362:  },
0363:

notメソッドは、要素集合から条件にマッチする要素を取り除きます。

353行目の条件式により、isSimpleつまり単純なセレクタ式(34行目参照)の場合は、自身をスタックに格納してmultiFilterメソッドの結果を返します。

358行目の式により、DOM要素でないArrayとして扱えるオブジェクトの場合には、自身がそのArray内に存在するかどうか、それ以外の場合にはセレクタと一致するかどうかによって削除対象を判定します。

add()

0364:  add: function( selector ) {
0365:    return !selector ? this : this.pushStack( jQuery.merge( 
0366:      this.get(),
0367:      selector.constructor == String ? 
0368:        jQuery( selector ).get() :
0369:        selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
0370:          selector : [selector] ) );
0371:  },
0372:

addメソッドは、選択されている要素集合にさらに条件に合う要素集合を追加します。

365行目で、セレクタ式が指定されていなければ、自分自身をそのまま返します。そうでなければ、this.get()した自分自身と新たなセレクタ式をマージして返します。367行目で引数selectorが文字列かどうかを判定しているのは、内部処理用でオブジェクトがそのまま渡される可能性があるためです。

is()

0373:  is: function( selector ) {
0374:    return selector ?
0375:      jQuery.multiFilter( selector, this ).length > 0 :
0376:      false;
0377:  },
0378:

375行目でmultiFilterメソッドを呼び出し、セレクタ式に一致する要素が1つ以上返ってくればtrueとなります。それ以外の場合は、falseを返します。

hasClass()

0379:  hasClass: function( selector ) {
0380:    return this.is( "." + selector );
0381:  },
0382:  

hasClassメソッドは、選択要素に指定したクラスがひとつでもあればtrueとなります。380行目を見ると分かる通り、is("."+class)と同義です。

val()

0383:  val: function( value ) {
0384:     if ( value == undefined ) {
0385:
0386:       if ( this.length ) {
0387:        var elem = this[0];
0388:
0389:        // We need to handle select boxes special
0390:         if ( jQuery.nodeName( elem, "select" ) ) {
0391:          var index = elem.selectedIndex,
0392:            values = [],
0393:            options = elem.options,
0394:            one = elem.type == "select-one";
0395:          
0396:          // Nothing was selected
0397:           if ( index 0398:            return null;
0399:
0400:          // Loop through all the selected options
0401:          for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i 0402:            var option = options[ i ];
0403:
0404:             if ( option.selected ) {
0405:              // Get the specifc value for the option
0406:              value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
0407:              
0408:              // We don't need an array for one selects
0409:               if ( one )
0410:                return value;
0411:              
0412:              // Multi-Selects return an array
0413:              values.push( value );
0414:            }
0415:          }
0416:          
0417:          return values;
0418:          
0419:        // Everything else, we just grab the value
0420:        } else
0421:          return (this[0].value || "").replace(/\r/g, "");
0422:
0423:      }
0424:
0425:      return undefined;
0426:    }
0427:
0428:    return this.each(function(){
0429:       if ( this.nodeType != 1 )
0430:        return;
0431:
0432:       if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
0433:        this.checked = (jQuery.inArray(this.value, value) >= 0 ||
0434:          jQuery.inArray(this.name, value) >= 0);
0435:
0436:      else  if ( jQuery.nodeName( this, "select" ) ) {
0437:        var values = value.constructor == Array ?
0438:          value :
0439:          [ value ];
0440:
0441:        jQuery( "option", this ).each(function(){
0442:          this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
0443:            jQuery.inArray( this.text, values ) >= 0);
0444:        });
0445:
0446:         if ( !values.length )
0447:          this.selectedIndex = -1;
0448:
0449:      } else
0450:        this.value = value;
0451:    });
0452:  },
0453:  

valメソッドは、選択された要素のvalue属性を操作します。

まず384行目~426行目の引数が指定されていない場合の処理を見ていきます。要素がselectではない場合は、421行目にあるように最初の要素のvalue値を改行を取り除いて返すだけです。一方、selectの場合は少々複雑です。まず何も選択されていない場合(397行目)は、nullを返します。そして、selectボックスのtypeが単一選択可能(select-one)かどうかで処理が変わってきます。401行目にあるようにselect-oneの場合は、無駄なループ処理が実行されないようになっています。そして、選択されている値をArrayで返します。

次に428行目以降の引数が指定された場合は、要素にvalue属性を設定します。nodeTypeが1以外の場合、つまりElement以外の時は何もしません(429行目⁠⁠。また、要素がradio,checkbox,select以外の場合はそのままセットします(450行目⁠⁠。それ以外の場合は、checkedおよびselectedの値を設定します。また、引数のvalueには配列を渡せるようになっていて、複数の値を一度に選択することが可能です。

html()

0454:  html: function( value ) {
0455:    return value == undefined ?
0456:      (this.length ?
0457:        this[0].innerHTML :
0458:        null) :
0459:      this.empty().append( value );
0460:  },
0461:

455行目の条件式により、パラメータが渡されなかった場合はinnerHTMLを返します。渡された場合は459行目にあるように一度内部を空にしてからパラメータを追加して返します。

replaceWith()

0462:  replaceWith: function( value ) {
0463:    return this.after( value ).remove();
0464:  },
0465:

replaceWithメソッドは、選択された要素を引数で置き換えます。463行目で直後にvalueを挿入してから元のオブジェクトを削除しています。

eq()

0466:  eq: function( i ) {
0467:    return this.slice( i, i + 1 );
0468:  },
0469:

eqメソッドは、選択された要素のi番目を取得して返します。467行目で、スタックに格納するために自身のsliceメソッドを呼び出しています。

おすすめ記事

記事・ニュース一覧