歡迎來個人專欄查看系列文章。javascript
前面一講整體架構已經介紹了 jQuery 的基本狀況,這一章主要來介紹 jQuery 的入口函數 jQuery.fn.init
。css
因爲這個函數直接和 jQuery() 的參數有關,先來講下能接受什麼樣的參數。源碼中接受 3 個參數:html
init: function (selector, context, root) { ... }
jQuery()
,空參數,這個會直接返回一個空的 jQuery 對象,return this
。java
jQuery( selector [, context ] )
,這是一個標準且經常使用法,selector 表示一個 css 選擇器,這個選擇器一般是一個字符串,#id 或者 .class 等,context 表示選擇範圍,即限定做用,可爲 DOM,jQuery 對象。node
jQuery( element|elements )
,用於將一個 DOM 對象或 DOM 數組封裝成 jQuery 對象。jquery
jQuery( jQuery object|object )
,會把普通的對象或 jQuery 對象包裝在 jQuery 對象中。git
jQuery( html [, ownerDocument ] )
,這個方法用於將 html 字符串先轉成 DOM 對象後在生成 jQuery 對象。github
jQuery( html, attributes )
,和上一個方法同樣,不過會將 attributes 中的方法和屬性綁定到生成的 html DOM 中,好比 class 等。正則表達式
jQuery( callback )
,此方法接受一個回掉函數,至關於 window.onload 方法,只是相對於。segmentfault
介紹完入口,就開始來看源碼。
init: function (selector, context, root) { var match, elem; // 處理: $(""), $(null), $(undefined), $(false) if (!selector) { return this; } // rootjQuery = jQuery( document ); root = root || rootjQuery; // 處理 HTML 字符串狀況,包括 $("<div>")、$("#id")、$(".class") if (typeof selector === "string") { //此部分拆分,留在後面講 // HANDLE: $(DOMElement) } else if (selector.nodeType) { this[0] = selector; this.length = 1; return this; // HANDLE: $(function) } else if (jQuery.isFunction(selector)) { return root.ready !== undefined ? root.ready(selector) : // Execute immediately if ready is not present selector(jQuery); } return jQuery.makeArray(selector, this); }
上面有幾點須要注意,root = root || rootjQuery;
,這個參數在前面介紹用法的時候,就沒有說起,這個表示 document,默認的話是 rootjQuery,而 rootjQuery = jQuery( document )。
能夠看出,對於處理 $(DOMElement)
,直接是把 jQuery 看成一個數組,this[0] = DOMElement。其實,這要從 jQuery 的基本構造講起,咱們完成一個 $('div.span')
以後,而後一個 jQuery 對象(this),其中會獲得一組(一個)DOM 對象,jQuery 會把這組 DOM 對象看成數組元素添加過來,並給一個 length。後面就像一些鏈式函數操做的時候,若只能對一個 DOM 操做,好比 width、height,就只對第一個元素操做,若能夠對多個 DOM 操做,則會對全部 DOM 進行操做,好比 css()。
jQuery 大題思路以下,這是一個很是簡單點實現:
jQuery.prototype = { // 簡單點,假設此時 selector 用 querySelectorAll init: function(selector){ var ele = document.querySelectorAll(selector); // 把 this 看成數組,每一項都是 DOM 對象 for(var i = 0; i < ele.length; i++){ this[i] = ele[i]; } this.length = ele.length; return this; }, //css 若只有一個對象,則取其第一個 DOM 對象 //若 css 有兩個參數,則對每個 DOM 對象都設置 css css : function(attr,val){ for(var i = 0; i < this.length; i++){ if(val == undefined){ if(typeof attr === 'object'){ for(var key in attr){ this.css(key, attr[key]); } }else if(typeof attr === 'string'){ return getComputedStyle(this[i])[attr]; } }else{ this[i].style[attr] = val; } } }, }
因此對於 DOMElement 的處理,直接將 DOM 賦值給數組後,return this。
jQuery.makeArray
是一個綁定 數組的函數,和上面的原理同樣,後面會談到。
在介紹下面的內容以前,先來介紹一個 jQuery 中一個識別 Html 字符串的正則表達式,
var rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/; rquickExpr.exec('<div>') //["<div>", "<div>", undefined] rquickExpr.exec('<div></div>') //["<div></div>", "<div></div>", undefined] rquickExpr.exec('#id') //["#id", undefined, "id"] rquickExpr.exec('.class') //null
上面這一系列的正則表達式 exec,只是爲了說明 rquickExpr
這個正則表達式執行後的結果,首先,若是匹配到,結果數組的長度是 3,若是匹配到 <div>
這種 html,數組的第三個元素是 underfined,若是匹配到 #id,數組的第二個元素是 underfined,若是匹配不到,則爲 null。
另外還有一個正則表達式:
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); rsingleTag.test('<div></div>') //true rsingleTag.test('<div ></div>') //true rsingleTag.test('<div class="cl"></div>') //false rsingleTag.test('<div></ddiv>') //false
這個正則表達式主要是對 html 的字符串進行驗證,達到不出差錯的效果。在這裏很少介紹 exec 和正則表達式了。
下面來看下重點的處理 HTMl 字符串的狀況:
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) { // 這個實際上是強行構造了匹配 html 的狀況的數組 match = [null, selector, null]; } else { match = rquickExpr.exec(selector); } // macth[1] 限定了 html,!context 對 #id 處理 if (match && (match[1] || !context)) { // HANDLE: $(html) -> $(array) if (match[1]) { //排除 context 是 jQuery 對象狀況 context = context instanceof jQuery ? context[0] : context; // jQuery.merge 是專門針對 jQuery 合併數組的方法 // jQuery.parseHTML 是針對 html 字符串轉換成 DOM 對象 jQuery.merge(this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true)); // HANDLE: $(html, props) if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) { for (match in context) { // 此時的 match 非彼時的 match if (jQuery.isFunction(this[match])) { this[match](context[match]); // ...and otherwise set as attributes } else { this.attr(match, context[match]); } } } return this; // 處理 match(1) 爲 underfined 但 !context 的狀況 } else { elem = document.getElementById(match[2]); if (elem) { // this[0] 返回一個標準的 jQuery 對象 this[0] = elem; this.length = 1; } return this; } // 處理通常的狀況,find 實際上上 Sizzle,jQuery 已經將其包括進來,下章詳細介紹 // jQuery.find() 爲 jQuery 的選擇器,性能良好 } else if (!context || context.jquery) { return (context || root).find(selector); // 處理 !context 狀況 } else { // 這裏 constructor 實際上是 指向 jQuery 的 return this.constructor(context).find(selector); }
關於 nodeType,這是 DOM 的一個屬性,詳情 Node.nodeType MDN。nodeType 的值通常是一個數字,好比 1 表示 DOM,3 表示文字等,也能夠用這個值是否存在來判斷 DOM 元素,好比 context.nodeType。
整個 init 函數等構造邏輯,很是清晰,好比 (selector, context, root)
三個參數,分別表示選擇的內容,可能存在的的限制對象或 Object,而 root 則默認的 jQuery(document)。依舊採用 jQuery 經常使用的方式,對每個變量的處理都很是的謹慎。
若是仔細看上面兩部分源代碼,我本身也加了註釋,應該能夠把整個過程給弄懂。
find 函數其實是 Sizzle,已經單獨出來一個項目,被在 jQuery 中直接使用,將在下章介紹 jQuery 中的 Sizzle 選擇器。經過源碼,能夠發現:
jQuery.find = function Sizzle(){...} jQuery.fn.find = function(selector){ ... //引用 jQuery.find jQuery.find() ... }
init 函數仍然調用了很多 jQuery 或 jQuery.fn 的函數,下面來逐個分析。
這個函數經過名字,就知道它是用來幹什麼的,合併。
jQuery.merge = function (first, second) { var len = +second.length, j = 0, i = first.length; for (; j < len; j++) { first[i++] = second[j]; } first.length = i; return first; }
這樣子就能夠對相似於數組且有 length 參數的類型進行合併,我感受主要仍是爲了方便對 jQuery 對象的合併,由於 jQuery 對象就是有 length 的。
這個函數也很是有意思,就是將一串 HTML 字符串轉成 DOM 對象。
首先函數接受三個參數,第一個參數 data 即爲 html 字符串,第二個參數是 document 對象,但要考慮到瀏覽器的兼容性,第三個參數 keepScripts 是爲了刪除節點裏全部的 script tags,但在 parseHTML 裏面沒有體現,主要仍是給 buildFragment 看成參數。總之返回的對象,是一個 DOM 數組或空數組。
jQuery.parseHTML = function (data, context, keepScripts) { if (typeof data !== "string") { return []; } // 平移參數 if (typeof context === "boolean") { keepScripts = context; context = false; } var base, parsed, scripts; if (!context) { // 下面這段話的意思就是在 context 缺失的狀況下,創建一個 document 對象 if (support.createHTMLDocument) { context = document.implementation.createHTMLDocument(""); base = context.createElement("base"); base.href = document.location.href; context.head.appendChild(base); } else { context = document; } } // 用來解析 parsed,好比對 "<div></div>" 的處理結果 parsed:["<div></div>", "div"] // parsed[1] = "div" 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(); } return jQuery.merge([], parsed.childNodes); }
buildFragment
函數主要是用來創建一個包含子節點的 fragment 對象,用於頻發操做的添加刪除節點。parsed = buildFragment([data], context, scripts);
創建好一個 fragment 對象,用 parsed.childNodes 來獲取這些 data 對應的 HTML。
jQuery 裏面的函數調用,真的是一層接一層,雖然有時候光靠函數名,就能知道這函數的做用,但其中思考之邏輯仍是挺參考意義的。
jQuery.makeArray = function (arr, results) { var ret = results || []; if (arr != null) { if (isArrayLike(Object(arr))) { jQuery.merge(ret, typeof arr === "string" ? [arr] : arr); } else { push.call(ret, arr); } } return ret; }
makeArray 把左邊的數組或字符串併入到右邊的數組或一個新數組,其中又間接的引用 jQuery.merge 函數。
接下來是着 isArrayLike 函數,可能須要考慮多方面的因素,好比兼容瀏覽器等,就有了下面這一長串:
function isArrayLike(obj) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = jQuery.type(obj); if (type === "function" || jQuery.isWindow(obj)) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj; }
這篇算是承上啓下吧,介紹了 jQuery 中比較重要的入口函數,而後估計下章將會講解 Sizzle,jQuery 中的選擇器。
jQuery 2.0.3 源碼分析core - 選擇器
Node.nodeType
jQuery 3.0的buildFragment
本文在 github 上的源碼地址,歡迎來 star。
歡迎來個人博客交流。