jQuery分析(3) - jQuery.fn.init

jQuery分析(3) - jQuery.fn.init

 

1.前言

上一篇jQuery分析(2)中瞭解了jQuery庫的骨架實現原理,這就比如搖滾音樂,搖滾音樂不是某種音樂他就像一個音樂盒子,裏面包含了各類不一樣的搖滾風格(山地、朋克、鄉村、流行、硬搖、金屬、迷幻等)。那麼上一篇只是大體瞭解了jQuery的基本形狀,從這篇文章開始會深刻jQuery庫的各類函數,深刻詳細的去了解他,那將值得慢慢探索,發現新的神奇好玩的東西。javascript

2.輔助函數

在jQuery.fn.init方法裏面使用到了一些jQuery的靜態函數,在這裏提早統一的介紹html

  • jQuery.merge 合併兩個數組,將第二個參數數組合併到第一個參數數組中。
  • jQuery.parseHTML 解析html字符串,第一個參數html字符串,第二個參數是產生fragment的context,第三個參數是否忽略scripts默認忽略
  • isPlainObject 判斷一個參數是否爲javascript對象即{}
  • jQuery.isFunction 判斷一個參數是否爲函數
  • jQuery.makeArray 合併數組(內部使用)

3.jQuery.fn.init 函數歸納👻

下面圖是jQeury的構造函數參數即$()調用的參數種類集合圖
jQuery.fn.init構造參數圖java

下面的代碼是可能處理各類參數的方式,多餘的代碼我已刪除掉,下面代碼清晰看到selector無非就是三種類型:一、字符串 二、DOMElement 三、函數。下面將會就這三種類型進行詳細深刻的分析他們的實現原理,這將須要一步一步來理解,首先腦子裏要清晰每一步作了什麼爲何這樣作,這樣一步一步下來才能更好的去理解jQuery的寫法。node

// 匹配html標籤寫法和id選擇器 // 第一個分組是 <div> 中的div,第二個分組是#id中的id rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, jQuery.fn.init = function(selector, context, root) { var match, elem; // 沒有selector將會返回當前this,以便建立空的jQuery對象在後面會用到。 if (!selector) { return this; } // 得到初始化文檔的jQuery對象 root = root || rootjQuery; // 處理參數爲字符串參數 if (typeof selector === "string") { // 處理參數爲DOM節點 } else if (selector.nodeType) { // 處理參數爲function } else if (jQuery.isFunction(selector)) { } // 處理參數爲NodeLists return jQuery.makeArray(selector, this); }; // init函數繼承jQuery init.prototype = jQuery.fn; // 初始化document爲jQuery對象 rootjQuery = jQuery( document );

4.參數爲字符串類型分解🐢

由於這個jQuery.fn.init函數代碼不少因此單獨的參數類型會把他的代碼單獨提出來分解,下面看提出來的參數爲字符串的代碼。jquery

對於字符串參數的幾種調用方法參見上面腦圖ios

// 匹配html標籤寫法和id選擇器 // 第一個分組是 <div> 中的div,第二個分組是#id中的id rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, // 參數爲字符串處理方式 if (typeof selector === "string") { if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { // 字符串是單標籤的DOM格式直接建立正則匹配的格式以跳過正則匹配以節約性能 match = [null, selector, null]; } else { // 匹配到DOM字符串 或者ID選擇器 match = rquickExpr.exec(selector); } // 若是selector是一個html字符串或者是一個ID選擇器 if (match && (match[1] || !context)) { // html字符串解析 if (match[1]) { context = context instanceof jQuery ? context[0] : context; // 解析html(單標籤或多標籤) jQuery.merge(this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true )); // 構建html元素時傳遞了第二個參數爲一個對象,那麼會對對象的key和value進行解析 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; // 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 this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // 其餘選擇器(class、element、attr)等 } else if (!context || context.jquery) { return (context || root).find(selector); // 其餘選擇器(class、element、attr)等且有context(一個DOMElement) } else { return this.constructor(context).find(selector); } }
  • 4-1.字符串處理
    • 4-1-1.selector爲單標籤的html會跳過正則表達式匹配,直接構造出一個正則表達結果結果放置match變量中。代碼
    • 4-1-2.匹配selector是否爲爲多標籤或者ID選擇器結果放置match變量中代碼
    • 4-1-3.selector是一個html字符串會對他們進行解析(單標籤/多標籤)代碼,若是還傳遞了額外的props屬性也會在解析和建立完DOM節點後附加到這個DOM上代碼
    • 4-1-4.selector爲ID選擇器則直接調用document.getElementById進行ID選擇元素,把選擇到的元素放入this[0]中,隨後修正length、context、selector便可代碼
    • 4-1-5.selector爲其餘選擇器(class、element、attr等)而且沒有給定第二個參數(context)或者第二個參數(context)是一個jquery對象那麼會調用(context || root).find(selector)進行查找元素代碼
    • 4-1-6.selector爲其餘選擇器(class、element、attr等)而且第二個參數(context)爲一個DOMElement會先構建context爲一個jQuery對象再利用這個對象進行.find(selector)查找代碼

5.字符串解析流程中涉及的函數分析

4-1把全部的字符串解析流程羅列了出來,在這個流程中所涉及了一些關鍵的函數在這裏給拆分解析一下。git

  • 5-1.jQuery.parseHTML解析html字符串爲DOM節點
// data: string of html // context (optional): If specified, the fragment will be created in this context, // defaults to document // keepScripts (optional): If true, will include scripts passed in the html string jQuery.parseHTML = function( data, context, keepScripts ) { if ( !data || typeof data !== "string" ) { return null; } // 修正參數,只有2個參數狀況下忽略context if ( typeof context === "boolean" ) { keepScripts = context; context = false; } // 修正context默認爲document context = context || document; var parsed = rsingleTag.exec( data ), scripts = !keepScripts && []; // Single tag if ( parsed ) { return [ context.createElement( parsed[ 1 ] ) ]; } parsed = buildFragment( [ data ], context, scripts ); // 移除已經執行過的腳本 if ( scripts && scripts.length ) { jQuery( scripts ).remove(); } // 合併並返回標dom集合的數組 return jQuery.merge( [], parsed.childNodes ); };

這個函數一共有三個參數,參數一是一個html字符串,參數二是建立fragment的context,參數三是表示是否保留scripts腳本默認爲falsegithub

// 單標籤 var parsed = rsingleTag.exec( data ), scripts = !keepScripts && []; // Single tag if ( parsed ) { return [ context.createElement( parsed[ 1 ] ) ]; }

若是data參數爲一個單標籤html字符串("正則表達式

","
")將直接調用context.createElement建立DOM元素並返回

 

// 多標籤 parsed = buildFragment( [ data ], context, scripts );

若是data參數爲一個多標籤html字符串("數組

abc
")會調用buildFragment函數進行解析建立。

 

最後返回一個數組元素,裏面是全部解析好的DOM元素

其實在整個selector爲字符串參數的代碼處理中,buildFragment應該仍是算代碼比較多的了,其餘那些class、element、attr等都是Sizzle選擇器引擎搞定了,因此buildFragment仍是一個比較有看頭的函數,其中文檔碎片技術和必需要外包裹標籤的建立方法其實咱們平時編碼時也會常常使用,能夠借鑑一二。

/* buildFragment 重要的參數是前面3個 elems 一個待轉換的html字符串 context 轉換上下文 scripts 是否忽略script標籤 */ function buildFragment(elems, context, scripts, selection, ignored) { var j, elem, contains, tmp, tag, tbody, wrap, l = elems.length, // Ensure a safe fragment // 建立文檔碎片 safe = createSafeFragment(context), nodes = [], i = 0; for (; i < l; i++) { elem = elems[i]; if (elem || elem === 0) { // Add nodes directly // 若是在elems中的某個數組元素是對象直接添加到nodes if (jQuery.type(elem) === "object") { jQuery.merge(nodes, elem.nodeType ? [elem] : elem); // Convert non-html into a text node // 轉換非html的字符串爲文本節點 } else if (!rhtml.test(elem)) { nodes.push(context.createTextNode(elem)); // Convert html into DOM nodes } else { //給文檔碎片建立一個元素,用來裝接下來咱們須要的html字符串 tmp = tmp || safe.appendChild(context.createElement("div")); // Deserialize a standard representation // 取得標籤名稱,並轉爲小寫 tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase(); // 須要其餘元素包裹的標籤 wrap = wrapMap[tag] || wrapMap._default; //把咱們的html字符串放入剛剛建立文檔碎片的div中造成dom元素 // 若是須要包裹元素則把html字符串進行包裹 <td>123</td> => <table><tbody><tr><td>abc</td></tr></tbody></table> tmp.innerHTML = wrap[1] + jQuery.htmlPrefilter(elem) + wrap[2]; // Descend through wrappers to the right content // 取得剛剛建立的元素 j = wrap[0]; while (j--) { tmp = tmp.lastChild; } // Manually add leading whitespace removed by IE if (!support.leadingWhitespace && rleadingWhitespace.test(elem)) { nodes.push(context.createTextNode(rleadingWhitespace.exec(elem)[0])); } // Remove IE's autoinserted <tbody> from table fragments if (!support.tbody) { // String was a <table>, *may* have spurious <tbody> elem = tag === "table" && !rtbody.test(elem) ? tmp.firstChild : // String was a bare <thead> or <tfoot> wrap[1] === "<table>" && !rtbody.test(elem) ? tmp : 0; j = elem && elem.childNodes.length; while (j--) { if (jQuery.nodeName((tbody = elem.childNodes[j]), "tbody") && !tbody.childNodes.length) { elem.removeChild(tbody); } } } // 把建立好的dom節點也就是tmp的子節點合併到nodes中 jQuery.merge(nodes, tmp.childNodes); // Fix #12392 for WebKit and IE > 9 tmp.textContent = ""; // Fix #12392 for oldIE while (tmp.firstChild) { tmp.removeChild(tmp.firstChild); } // Remember the top-level container for proper cleanup tmp = safe.lastChild; } } } // Fix #11356: Clear elements from fragment // 清除文檔碎片中的元素 if (tmp) { safe.removeChild(tmp); } // Reset defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) if (!support.appendChecked) { jQuery.grep(getAll(nodes, "input"), fixDefaultChecked); } i = 0; while ((elem = nodes[i++])) { // Skip elements already in the context collection (trac-4087) if (selection && jQuery.inArray(elem, selection) > -1) { if (ignored) { ignored.push(elem); } continue; } // 元素是否已經包含在document中 contains = jQuery.contains(elem.ownerDocument, elem); // Append to fragment // 將建立好的元素再次添加到文檔碎片中,並取得scirpt標籤 tmp = getAll(safe.appendChild(elem), "script"); // Preserve script evaluation history if (contains) { setGlobalEval(tmp); } // Capture executables // 收集要執行的腳本 if (scripts) { j = 0; while ((elem = tmp[j++])) { if (rscriptType.test(elem.type || "")) { scripts.push(elem); } } } } tmp = null; // 返回建立好的文檔碎片 return safe; }

關於buildFragment函數裏面還有一些兼容性的解決方案還沒分析到,後面再作吧。至此關於jQuery.fn.init函數的構造流程也就分析完畢。

若有疏忽、遺漏、錯誤請狠狠批評謝謝。

 

轉載:http://www.cnblogs.com/monsterooo/p/5524985.html

相關文章
相關標籤/搜索