jQuery對象是什么,舉個例子,$('#id')?返回的就是jQuery對象,這個東西是整個jQuery的核心所在,所以我先來分析它。
var?jQuery = function( selector, context ) {
????// The jQuery object is actually just the init constructor 'enhanced'
????return?new?jQuery.fn.init( selector, context, rootjQuery );
};
......
jQuery.fn = jQuery.prototype = {
????constructor: jQuery,
????init: function( selector, context, rootjQuery ),
????selector: "",
????jquery: "1.7.2",
????length: 0,
????size: function(),
????toArray: function(),
????get: function( num ),
????pushStack: function( elems, name, selector ),
????each: function( callback, args ),
????ready: function( fn ),
????eq: function( i ),
????first: function(),
????last: function(),
????slice: function(),
????map: function( callback ),
????end: function(),
????push: push,
????sort: [].sort,
????splice: [].splice
};
jQuery.fn.init.prototype = jQuery.fn;
?
?
我敢說,第一次看到這段代碼的人都會被它的結構搞暈,因為這種寫法實在是太罕見了。關于john的想法,我斗膽一猜:
1. jQ對象的構造函數為啥是protptype.init?
答:構造函數中的邏輯和prototype的其他?屬性/方法?有聯系,比如?selector?和?length,寫在一起比較易讀。
調用init?方法時,如果參數為空,jQ對象可以等同于?prototype,這樣看起來結構很清晰;
如果參數不為空,會覆蓋一些?prototype?的屬性(如?selector?和?length,也就說prototype?上的屬性只是為了給一個默認值而已),還會創建一些?prototype?上沒有列出的屬性(如context),這些屬性本該寫在構造函數中的。但同樣的,為了讓邏輯保持緊湊,就一并寫在?init?方法中。
?
2. jQ到底想創建一個怎樣的對象?
答:這個對象有點像數組,比如下面這段代碼:
?
function?FuckYou(who) { ????this[0] = who; } var?fy = new?FuckYou('john'); |
于是?fy[0] === 'john',是不是有點小暈了?千萬搞清楚,這不是數組,別被你的眼睛蒙蔽了,這是一個對象,0是它的屬性名而已。因為this.0 = who?會語法報錯,所以就用了一個小技巧。
再看prototype?中的其他方法,如each, slice, map, push, sort, splice,無一不是在模擬數組。
?
3.?解釋一下pushStack唄?
答:顧名思義,就是一個壓棧操作,主要是用于undo
?
?
pushStack: function( elems, name, selector ) {
????//?創建一個全新的jQ對象,API和prototype一模一樣
????var?ret = this.constructor();
?
????// elems?是數組,直接push
????if?( jQuery.isArray( elems ) ) {
????????push.apply( ret, elems );
?
????// elems?是類數組,調用merge(),這存在兩種情況:
????// 1. elems是childNodes這樣的類數組
????// 2. elems是this[0], this[1]這樣的模擬數組,上面舉過例子的
????} else?{
????????jQuery.merge( ret, elems );
????}
?
????//?如?$("p").find("span")
????//?第一次是匹配p,第二次是在上次的結果中匹配span
????//?類似這樣破壞上一個鏈的行為,jQ都會把上一個鏈存起來,以便回退(調用end())
????ret.prevObject = this;
?
????ret.context = this.context;
?
????//?這小段很有意思,從這里你幾乎可以看出整個jQ的設計思想
????// name?表示一個方法名
????// selector?表示 選擇器
????//?你懂的,jQ里面有很多DOM相關的方法,比如after, find之類的,它們的參數就是一個選擇器
????if?( name === "find"?) {
????????// find?相當于后代選擇器,如?"div span"?這樣的
????????ret.selector = this.selector + ( this.selector ? " "?: ""?) + selector;
????} else?if?( name ) {
????????//?這個分支比較復雜,光看這里的代碼是看不懂的
????????//?我就一直為什么要加?.?因為除了匹配class,是用不到?.?的
????????//?我覺得吧,這個應該涉及到?Sizzle?的匹配模式問題,我不關心這種細節問題,囧
????????ret.selector = this.selector + "."?+ name + "("?+ selector + ")";
????}
?
????return?ret;
}
?
4.?再解釋一下?init?唄?
答:?必須解釋啊,這個方法真是重中之重,jQ的構造函數不解釋還能解釋啥。
init: function( selector, context, rootjQuery ) {
????var?match, elem, ret, doc;
?
????//?處理?$(""), $(null), or $(undefined)
????if?( !selector ) {
????????return?this;
????}
?
????//?處理?$(DOMElement)
????if?( selector.nodeType ) {
????????this.context = this[0] = selector;
????????this.length = 1;
????????return?this;
????}
?
????//?因為?body?元素只有一個,所以可以優化一下
????if?( selector === "body"?&& !context && document.body ) {
????????this.context = document;
????????this[0] = document.body;
????????this.selector = selector;
????????this.length = 1;
????????return?this;
????}
?
????//?處理?HTML?字符串
????if?( typeof?selector === "string"?) {
????????//?如果是標簽,如
,可省略第二個分支的正則匹配
????????if?( selector.charAt(0) === "<"?&& selector.charAt( selector.length - 1 ) === ">"?&& selector.length >= 3 ) {
????????????match = [ null, selector, null?];
?
????????} else?{
????????????// quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/
????????????//?再來看早期的一個版本
????????????// quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/
????????????//
????????????//?大同小異,但前者肯定是做了更多的考慮,
????????????//?比如防止通過location.hash?進行XSS攻擊
????????????//
????????????//?這個正則有點復雜,它分為兩個分支
????????????// 1. ^[^#<]*(<[\w\W]+>)[^>]*$
????????????// 2. ^#([\w\-]*)$
????????????//?第二個不用說,是匹配ID的
????????????//?第一個用來匹配HTML代碼段:
????????????//???[^#<]*?表示開頭不能包含#和<</span>
????????????//???(<[\w\W]+>)?表示匹配完整的標簽,如
????????????//???[^>]*$?表示結尾不能包含>
????????????match = quickExpr.exec( selector );
????????}
?
????????//?匹配成功,并且匹配上ID時沒有指定context
????????//?為什么?ID?和context?不能同時指定?拜托,ID全局唯一的好不
????????if?( match && (match[1] || !context) ) {
?
????????????//?處理?$(html) -> $(array)
????????????if?( match[1] ) {
????????????????//?確保?context?是一個?HTMLElement
????????????????context = context instanceof?jQuery ? context[0] : context;
????????????????//?確保?doc?是?document
????????????????doc = ( context ? context.ownerDocument || context : document );
?
????????????????// rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/
????????????????//?就是匹配單個標簽,如?或
????????????????//?如果匹配成功,不用往下走了,直接?createElement_x?就完事
????????????????ret = rsingleTag.exec( selector );
?
????????????????if?( ret ) {
????????????????????//?我凌亂了,context?可能是純對象么?
????????????????????//?按第一個分支的邏輯,context應該是?{id: 'id', title: 'title'}之類的
????????????????????if?( jQuery.isPlainObject( context ) ) {
????????????????????????selector = [ document.createElement_x( ret[1] ) ];
????????????????????????jQuery.fn.attr.call( selector, context, true?);
?
????????????????????} else?{
????????????????????????selector = [ doc.createElement_x( ret[1] ) ];
????????????????????}
?
????????????????} else?{
????????????????????//?不是單個標簽,就創建文檔碎片吧
????????????????????ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
????????????????????selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
????????????????}
?
????????????????return?jQuery.merge( this, selector );
?
????????????//?處理$("#id")
????????????} else?{
????????????????elem = document.getElementByIdx_x( match[2] );
?
????????????????// Check parentNode to catch when Blackberry 4.6 returns
????????????????// nodes that are no longer in the document #6963
????????????????//?囧,黑莓還有這種問題,我覺得移動開發就該另寫一個庫,不能把所有bugfix都寫在jQ里
????????????????//?就像說,難道移動開發還要考慮IE678??肯定不要啊,基本都支持HTML5了
????????????????//?既然這樣,那又何必在這fix手機瀏覽器呢?john?您蛋疼了么
????????????????if?( elem && elem.parentNode ) {
????????????????????// IE?和?Opera?調用?getElementById?會依據name返回,而不是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;
????????????}
?
????????//?處理?$(expr, $(...)),相當于?$(...).find(expr)
????????//?這么傳參數的人純屬蛋疼,jQ就是這樣被搞大的
????????//?反正我覺得接口就該定死,context只能傳?HTMLElement?或 選擇器
????????//?搞得我現在覺得jQ的接口好像萬能一樣,啥都能傳
????????} else?if?( !context || context.jquery ) {
????????????return?( context || rootjQuery ).find( selector );
?
????????//?處理?$(expr, context),也相當于$(context).find(expr)
????????//?同樣蛋疼的寫法
????????} else?{
????????????return?this.constructor( context ).find( selector );
????????}
?
????//?處理?$(function)
????//?就是文檔加載完成時調用的函數
????} else?if?( jQuery.isFunction( selector ) ) {
????????return?rootjQuery.ready( selector );
????}
?
????// selector?是一個jQ對象,我真心覺得除了蛋疼沒有別的理由這么寫了
????if?( selector.selector !== undefined ) {
????????this.selector = selector.selector;
????????this.context = selector.context;
????}
?
????//?把?selector?加到?this?的尾部
????//?因為能走到這,this已經是一個類數組了
????return?jQuery.makeArray( selector, this?);
}
?
5.?還有啥想說的不?
答:最后提一點吧:
?
jQuery.fn = jQuery.prototype = {...} jQuery.fn.init.prototype = jQuery.fn; |
為啥要這么寫?比如第一行,為什么不直接賦給一個變量,而是賦給jQuery.fn?
1.?賦給一個變量就只能在jQ這個文件內部用,而jQ是有強大的插件機制的,所以為了便于外部擴展,賦給jQuery.fn。當然不處理這部分也沒事,您就多打幾個字,但是jQ畢竟是一個超級流行的框架,能縮寫的就自己縮了吧,省的別人去做這種無畏的勞動。
?
2.?第二行的寫法更加讓人蛋疼。首先?init?是構造函數,還要我繼續說?好吧,雖然我覺得這都是基礎知識了。
?
1 2 3 4 | function?jQuery() { } jQuery.prototype = { ????constructor: jQuery } |
如果jQ不寫這一行,之后?obj instanceof jQuery?就永遠為?false。
?
技巧:
1.?對象上的?屬性/方法?會覆蓋原型上對應的?屬性/方法(說?override?可能好些);
2.?巧用數組的方法,如下:
?
1 2 3 4 5 6 7 8 9 10 11 | var?push = Array.prototype.push; ? function?ArrayLike() { ????this[0] = 0; ????this[1] = 1; ????this.length = 2; } ? var?al = new?ArrayLike(); push.apply(al, [2, 3]); console.log(al) |
最后的結果是?al.length === 4,現在知道jQ對象為啥需要?length?屬性了吧