這篇也沒什麼好說的,初始化jQuery對象的函數要處理多種狀況,已經被寒冬吐槽爛了。關聯節點獲取函數主要基於兩個工具函數dir和sibling,前者基於指定的方向遍歷,後者則遍歷兄弟節點(真的不能合併?)。後面的一些API則主要調用這兩個函數。大幾百行代碼,不過邏輯很簡單javascript
// Initialize a jQuery object // A central reference to the root jQuery(document) var rootjQuery, // Use the correct document accordingly with window argument (sandbox) document = window.document, // A simple way to check for HTML strings // Prioritize #id over to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) //一個檢查字符串是否包含HTML的簡單正則。 //須要避免如<script>alert(1)</script>之類的XSS rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, //接下來就是被寒冬黑得不亦樂乎的真正的產生jQuery對象的函數了 init = jQuery.fn.init = function( selector, context ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { //??????爲何這裏return 的是一個空數組? //這只是顯示問題 console.log(this.length); return this; } // Handle HTML strings //處理HTML字符串 //當傳入的是字符串時 if ( typeof selector === "string" ) { //當傳入的字符串相似於"<div >" if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; // scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present //把DOM元素加到this裏 jQuery.merge( this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) //什麼狀況下context是PlainObject? //處理$(html,props)這種狀況。。因此第二個參數是{attrName:attrValue},context此時爲PlainObject if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible //爲何這裏要調用函數? if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { //先嚐試使用原生藉口 elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object //這裏不能用push,是由於this是一個僞數組,但不能用本身寫的push? this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) //這裏就是轉換調用一下,這種思路能夠學習 } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return typeof rootjQuery.ready !== "undefined" ? rootjQuery.ready( selector ) : // Execute immediately if ready is not present //不然馬上執行 selector( jQuery ); } //若是傳進來的是jQuery對象 if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }; // Give the init function the jQuery prototype for later instantiation //將init這個函數的原型設置爲jQuery.fn,這樣每一個jQuery對象均可以共享jQuery.fn上的函數 init.prototype = jQuery.fn; // Initialize central reference //文檔節點的jQuery對象 rootjQuery = jQuery( document ); //後面關聯節點時用來判斷函數名是否帶有Until或All的正則 var rparentsprev = /^(?:parents|prev(?:Until|All))/, // methods guaranteed to produce a unique set when starting from a unique set //標記某些方法是否須要確保返回的集合裏每個元素都惟一 guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.extend({ //按某一方向查找,返回匹配元素數組,注意這裏有until dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, //按兄弟元素方向查找,返回匹配元素數組 sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); jQuery.fn.extend({ //這個函數應該和以前的is待在一塊 has: function( target ) { var i, targets = jQuery( target, this ), len = targets.length; return this.filter(function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, //匹配jQuery對象中每個DOM元素最接近的父元素,最後會去重 closest: function( selectors, context ) { var cur, i = 0, l = this.length, matched = [], pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; for ( ; i < l; i++ ) { for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { // Always skip document fragments //跳過文檔碎片節點 if ( cur.nodeType < 11 && (pos ? //pos此時是一個jQuery對象 pos.index(cur) > -1 : // Don't pass non-elements to Sizzle cur.nodeType === 1 && //這裏是檢查cur是否和selectors匹配 jQuery.find.matchesSelector(cur, selectors)) ) { //由於只匹配最近的一個節點 matched.push( cur ); break; } } } return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); }, // Determine the position of an element within // the matched set of elements //其實本質上仍是調用工具函數inArray,不過inArray設計得不錯,錯誤返回-1,正確返回索引 index: function( elem ) { // No argument, return index in parent //這個得到索引的方法很巧妙,檢查本身前面有多少元素,就是本身的索引 if ( !elem ) { return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; } // index in selector //處理傳進來的參數是elem的狀況 if ( typeof elem === "string" ) { return jQuery.inArray( this[0], jQuery( elem ) ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, //這個添加方式頗爲奇葩。。添加完還要unique一下。。添加完原有的selector也會沒了。。 add: function( selector, context ) { return this.pushStack( jQuery.unique( jQuery.merge( this.get(), jQuery( selector, context ) ) ) ); }, //把壓棧以前的jQuery對象加過來,估計是後面有用 addBack: function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter(selector) ); } }); //之因此須要這麼個工具函數,是由於在遍歷的時候須要考慮無用節點。。不過其實也沒用幾回。。 function sibling( cur, dir ) { do { cur = cur[ dir ]; } while ( cur && cur.nodeType !== 1 ); return cur; } //這裏爲何是each而不是extend? //這裏使用each的形式對下面的每個函數進行處理 //下面這些函數就是調用前面的API,說明前面抽象得比較好 jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { //這裏要判斷元素是不是iframe return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.merge( [], elem.childNodes ); } //處理函數,進行一層封裝 }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); if ( name.slice( -5 ) !== "Until" ) { //當name不爲xxxUntil時,說明第一個參數不是用來until的,而是選擇符。 selector = until; } //含有選擇符的話要過濾一下 if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } if ( this.length > 1 ) { // Remove duplicates //當須要去重的時候去重 if ( !guaranteedUnique[ name ] ) { ret = jQuery.unique( ret ); } // Reverse order for parents* and prev-derivatives if ( rparentsprev.test( name ) ) { ret = ret.reverse(); } } return this.pushStack( ret ); }; });