jquery.jsを読み解く

第5回jQueryライブラリ (1094行目~1359行目)

第5回は、内部的に利用するメソッドやjQueryオブジェクト直下に定義されたプロパティ、TraversingやManipultation用のメソッドについての部分になります。

trim()

1094:   trim: function( text ) {
1095:     return (text || "").replace( /^\s+|\s+$/g, "" );
1096:   },
1097: 

trimメソッドは、文字列の先頭および末尾にある空白を取り除いて返します。

makeArray()

1098:   makeArray: function( array ) {
1099:     var ret = [];
1100: 
1101:     // Need to use typeof to fight Safari childNodes crashes
1102:     if ( typeof array != "array" )
1103:       for ( var i = 0, length = array.length; i < length; i++ )
1104:         ret.push( array[ i ] );
1105:     else
1106:       ret = array.slice( 0 );
1107: 
1108:     return ret;
1109:   },
1110: 

makeArrayメソッドは、クリーンなarrayオブジェクトに変換します。引数がarrayオブジェクトでなければ、forループで配列にpushしていきます。arrayオブジェクトであれば、sliceした結果を返します。

inArray()

1111:   inArray: function( elem, array ) {
1112:     for ( var i = 0, length = array.length; i < length; i++ )
1113:       if ( array[ i ] == elem )
1114:         return i;
1115: 
1116:     return -1;
1117:   },
1118: 

inArrayメソッドは、引数arrayの中に引数elemが見つかればその位置を返します。みつからなければ-1を返します。

merge()

1119:   merge: function( first, second ) {
1120:     // We have to loop this way because IE & Opera overwrite the length
1121:     // expando of getElementsByTagName
1122: 
1123:     // Also, we need to make sure that the correct elements are being returned
1124:     // (IE returns comment nodes in a '*' query)
1125:     if ( jQuery.browser.msie ) {
1126:       for ( var i = 0; second[ i ]; i++ )
1127:         if ( second[ i ].nodeType != 8 )
1128:           first.push( second[ i ] );
1129: 
1130:     } else
1131:       for ( var i = 0; second[ i ]; i++ )
1132:         first.push( second[ i ] );
1133: 
1134:     return first;
1135:   },
1136: 

mergeメソッドは、引数で指定されたfirst配列にsecond配列を追加して返します。Internet Explorerの場合は、コメントノード(nodeType=8)も返してしまうため、それを除外しています。

また、わざわざforループでpushしているのは、後で値を変更したときに実態を上書きしてしまうのを防ぐためです。

unique()

1137:   unique: function( array ) {
1138:     var ret = [], done = {};
1139: 
1140:     try {
1141: 
1142:       for ( var i = 0, length = array.length; i < length; i++ ) {
1143:         var id = jQuery.data( array[ i ] );
1144: 
1145:         if ( !done[ id ] ) {
1146:           done[ id ] = true;
1147:           ret.push( array[ i ] );
1148:         }
1149:       }
1150: 
1151:     } catch( e ) {
1152:       ret = array;
1153:     }
1154: 
1155:     return ret;
1156:   },
1157: 

uniqueメソッドは、配列から重複した要素を取り除いて返します。1143行目のjQuery.data()は、642行目で説明した要素のユニークなIDを返すメソッドで、要素の重複チェックに利用されます。done配列のキーにそのIDがみつからなければ、ret配列にその値をプッシュします。

もし、何かしらのエラーが発生したら、引数arrayをそのまま返します。

grep()

1158:   grep: function( elems, callback, inv ) {
1159:     // If a string is passed in for the function, make a function
1160:     // for it (a handy shortcut)
1161:     if ( typeof callback == "string" )
1162:       callback = eval("false||function(a,i){return " + callback + "}");
1163: 
1164:     var ret = [];
1165: 
1166:     // Go through the array, only saving the items
1167:     // that pass the validator function
1168:     for ( var i = 0, length = elems.length; i < length; i++ )
1169:       if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
1170:         ret.push( elems[ i ] );
1171: 
1172:     return ret;
1173:   },
1174: 

grepメソッドは、引数elemsに対してcallback関数を実行して、フィルタリングした結果を返します。1161行目では、もしcallback関数にstring型が渡された場合は、その名前の関数を実行するようにcallbackを定義し直しています。

1168行目からは、各要素に対してcallback関数を適用し、trueが返ってきた要素のみをret配列にpushして返します。ただし、第3引数のinvがtrueの場合には判定条件が逆になり、falseが返ってきた要素で構成された配列を返すことになります。

map()

1175:   map: function( elems, callback ) {
1176:     var ret = [];
1177: 
1178:     // Go through the array, translating each of the items to their
1179:     // new value (or values).
1180:     for ( var i = 0, length = elems.length; i < length; i++ ) {
1181:       var value = callback( elems[ i ], i );
1182: 
1183:       if ( value !== null && value != undefined ) {
1184:         if ( value.constructor != Array )
1185:           value = [ value ];
1186: 
1187:         ret = ret.concat( value );
1188:       }
1189:     }
1190: 
1191:     return ret;
1192:   }
1193: });
1194: 

mapメソッドは、引数elemの各要素にcallback関数を適用した配列を返します。1181行目で関数を適用し、結果がnullまたはundefinedでなければ、配列を結合して返します。また、1187行目にあるようにarray.concat()を用いているので、callback関数の結果が配列であっても配列オブジェクトではなく、それぞれの値が戻り値の配列に追加されます。

jQuery.browser

1195: var userAgent = navigator.userAgent.toLowerCase();
1196: 
1197: // Figure out what browser is being used
1198: jQuery.browser = {
1199:   version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
1200:   safari: /webkit/.test( userAgent ),
1201:   opera: /opera/.test( userAgent ),
1202:   msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
1203:   mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
1204: };
1205: 
1206: var styleFloat = jQuery.browser.msie ?
1207:   "styleFloat" :
1208:   "cssFloat";
1209:   

jQuery.browserには、ブラウザーの種類およびバージョンが設定されます。navigator.userAgent文字列に"webkit"が含まれていれば「Safari⁠⁠、"opera"が含まれていれば「Opera⁠⁠、"msie"が含まれていれば「Internet Explorer⁠⁠、"mozilla"が含まれていて"compatible","webkit"という文字列がなければ「mozilla」になります。また同時にバージョンの値もjQuery.browser.versionに設定されます。

1206行目は、float値へのアクセスがIEで異なるための対応です。

jQuery.boxModel, jQuery.props

1210: jQuery.extend({
1211:   // Check to see if the W3C box model is being used
1212:   boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
1213:   
1214:   props: {
1215:     "for": "htmlFor",
1216:     "class": "className",
1217:     "float": styleFloat,
1218:     cssFloat: styleFloat,
1219:     styleFloat: styleFloat,
1220:     innerHTML: "innerHTML",
1221:     className: "className",
1222:     value: "value",
1223:     disabled: "disabled",
1224:     checked: "checked",
1225:     readonly: "readOnly",
1226:     selected: "selected",
1227:     maxlength: "maxLength",
1228:     selectedIndex: "selectedIndex",
1229:     defaultValue: "defaultValue",
1230:     tagName: "tagName",
1231:     nodeName: "nodeName"
1232:   }
1233: });
1234: 

1212行目のjQuery.boxModelには、現在のページがW3CのCSSボックスモデルを使用してレンダリングされているかどうかが設定されます。IE以外かもしくは標準モードの場合にTrueになります。

1214行目からのpropsは、HTML属性/CSSプロパティにJavaScriptからDOMでアクセスをするための設定です。

Traversing用メソッド

1235: jQuery.each({
1236:   parent: "elem.parentNode",
1237:   parents: "jQuery.dir(elem,'parentNode')",
1238:   next: "jQuery.nth(elem,2,'nextSibling')",
1239:   prev: "jQuery.nth(elem,2,'previousSibling')",
1240:   nextAll: "jQuery.dir(elem,'nextSibling')",
1241:   prevAll: "jQuery.dir(elem,'previousSibling')",
1242:   siblings: "jQuery.sibling(elem.parentNode.firstChild,elem)",
1243:   children: "jQuery.sibling(elem.firstChild)",
1244:   contents: "jQuery.nodeName(elem,'iframe')?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes)"
1245: }, function(name, fn){
1246:   fn = eval("false||function(elem){return " + fn + "}");
1247: 
1248:   jQuery.fn[ name ] = function( selector ) {
1249:     var ret = jQuery.map( this, fn );
1250: 
1251:     if ( selector && typeof selector == "string" )
1252:       ret = jQuery.multiFilter( selector, ret );
1253: 
1254:     return this.pushStack( jQuery.unique( ret ) );
1255:   };
1256: });
1257: 

1235行目からは、Traversingのためのメソッドを定義しています。使い勝手を向上するためにTraversing用メソッドのバリエーションを最小限の手間で増やしています。jQuery.each()の第1引数として、新メソッド名と実際に行われる関数のペアを定義して、それぞれの要素に対してjQuery.map()を使って内部的に別の関数を実行しています。

Manipulation用メソッド

1258: jQuery.each({
1259:   appendTo: "append",
1260:   prependTo: "prepend",
1261:   insertBefore: "before",
1262:   insertAfter: "after",
1263:   replaceAll: "replaceWith"
1264: }, function(name, original){
1265:   jQuery.fn[ name ] = function() {
1266:     var args = arguments;
1267: 
1268:     return this.each(function(){
1269:       for ( var i = 0, length = args.length; i < length; i++ )
1270:         jQuery( args[ i ] )[ original ]( this );
1271:     });
1272:   };
1273: });
1274: 

1258行目からも、先ほどのTraversing用メソッドの定義と同様にManipulationのためのメソッドを定義しています。これは、jQueryの特徴のひとつでもありますが、appendとappendToのように対象となる要素が異なるだけで内部処理的には変わらないメソッドを再定義しています。ライブラリを使う側からすると、処理が短いコード記述できるようになるため使い勝手が向上します。

appendToを例にとって説明すると、引数argsのそれぞれの要素に対してoriginalのappendメソッドを自身を引数として実行するようなメソッドを定義しています。

Attributes用メソッド

1275: jQuery.each({
1276:   removeAttr: function( name ) {
1277:     jQuery.attr( this, name, "" );
1278:     if (this.nodeType == 1) 
1279:       this.removeAttribute( name );
1280:   },
1281: 
1282:   addClass: function( classNames ) {
1283:     jQuery.className.add( this, classNames );
1284:   },
1285: 
1286:   removeClass: function( classNames ) {
1287:     jQuery.className.remove( this, classNames );
1288:   },
1289: 
1290:   toggleClass: function( classNames ) {
1291:     jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
1292:   },
1293: 
1294:   remove: function( selector ) {
1295:     if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
1296:       // Prevent memory leaks
1297:       jQuery( "*", this ).add(this).each(function(){
1298:         jQuery.event.remove(this);
1299:         jQuery.removeData(this);
1300:       });
1301:       if (this.parentNode)
1302:         this.parentNode.removeChild( this );
1303:     }
1304:   },
1305: 
1306:   empty: function() {
1307:     // Remove element nodes and prevent memory leaks
1308:     jQuery( ">*", this ).remove();
1309:     
1310:     // Remove any remaining nodes
1311:     while ( this.firstChild )
1312:       this.removeChild( this.firstChild );
1313:   }
1314: }, function(name, fn){
1315:   jQuery.fn[ name ] = function(){
1316:     return this.each( fn, arguments );
1317:   };
1318: });
1319: 

1275行目からは、HTML/CSS属性を操作するためのメソッドです。こちらもjQuery.fnに対してremoveAttrやaddClassといったメソッドを定義しています。

height(), width()

1320: jQuery.each([ "Height", "Width" ], function(i, name){
1321:   var type = name.toLowerCase();
1322:   
1323:   jQuery.fn[ type ] = function( size ) {
1324:     // Get window width or height
1325:     return this[0] == window ?
1326:       // Opera reports document.body.client[Width/Height] properly in both quirks and standards
1327:       jQuery.browser.opera && document.body[ "client" + name ] || 
1328:       
1329:       // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
1330:       jQuery.browser.safari && window[ "inner" + name ] ||
1331:       
1332:       // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
1333:       document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
1334:     
1335:       // Get document width or height
1336:       this[0] == document ?
1337:         // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
1338:         Math.max( 
1339:           Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), 
1340:           Math.max(document.body["offset" + name], document.documentElement["offset" + name]) 
1341:         ) :
1342: 
1343:         // Get or set width or height on the element
1344:         size == undefined ?
1345:           // Get width or height on the element
1346:           (this.length ? jQuery.css( this[0], type ) : null) :
1347: 
1348:           // Set the width or height on the element (default to pixels if value is unitless)
1349:           this.css( type, size.constructor == String ? size : size + "px" );
1350:   };
1351: });
1352: 

1320行目からは、選択された要素のWidthおよびHeightを取得または設定するためのクラスです。こちらも["Height", "Width"]の配列に対してjQuery.each()を使って同様の関数定義を行うことで、同じような関数を二度定義する無駄をなくしています。

1326~1341行目は、対象がwindowオブジェクトの場合の処理で、windowの高さまたは幅を取得します。ブラウザの種類や標準モードか互換モードかによって返される値が異なるので、その違いを吸収しています。

1344行目からは、対象がwindowオブジェクト以外の場合の処理で、引数sizeが指定されていればcss()を使って大きさを設定します。

セレクタ定義

1353: var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) > 417 ?
1354:     "(?:[\\w*_-]|\\\\.)" :
1355:     "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
1356:   quickChild = new RegExp("^>\\s*(" + chars + "+)"),
1357:   quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
1358:   quickClass = new RegExp("^([#.]?)(" + chars + "*)");
1359: 

1353行目から1355行目は、Safariの少し古いバージョン(1.3)を使っていると、Safariが異常終了する問題があって、その対策を行っています。

1356行目からは、セレクタ式の正規表現です。後で利用するために"> foo"と"foo#id"と"#id"または".class"形式の正規表現オブジェクトを生成しておきます。

おすすめ記事

記事・ニュース一覧