jQuery原始碼解析(jQuery物件的例項屬性和方法)
1、記錄版本號 以及 修正constructor指向
jquery: core_version,
constructor: jQuery,
因為jQuery.prototype={ ... } 這種寫法將自動生成的jQuery.prototype.constructor屬性覆蓋刪除,所以需要重新修正(指向其建構函式 jQuery)。
2、init() 初始化方法:
init()方法最終返回的是this -jQuery(其實是jQuery.prototype.init)方法的例項物件。
例項物件繼承了jQuery的例項屬性和方法,且具有下標為0、1、2...、length的屬性,
下面分步驟分析程式碼:
①if:傳入無效的引數selector,直接返回this。
init: function( selector, context, rootjQuery ) {
var match, elem;
if ( !selector ) {
return this;
};
...
}
②if :傳入的引數selector是字串, rquickExpr是前面宣告的變數:rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/;
else if :傳入的是DOM元素;
else if: 傳入的是函式,呼叫$("document").ready(function(){ ... });
if :傳入的是jQuery物件(存在屬性selector);
init: function( selector, context, rootjQuery ) {
...
if ( typeof selector === "string" ) {
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
};
...
}else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
};
上面程式碼中,if :情況包括類似 $("<div></div>")、$("<div>aaa</div><div>aaa</div>") 兩中情況;
else :包括其餘情況,例如:$(“</div>111”)、$("#id")、$(".class")、$("div")、$("#id .class div")等情況。requickExpr匹配的是$(“</div>111”)、$("#id")兩種情況。如果傳入的是$(“</div>111”),則match=["</div>111","</div>",null];如果傳入的是$("#id"),則match=["#id",null,“id”];
③ 接下來是if esle 判斷
if :match不為null 且 match[1]不為null(傳入的是標籤形式),或match不為null且content為null(傳入的是id形式,無需制定上下文);
else if: 如果context沒有傳入或者傳入是$()物件形式,則呼叫$().find ;
else:如果context傳入是原生jsDom物件形式,則呼叫$().find
if ( match && (match[1] || !context) ) {
}else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
}else {
return this.constructor( context ).find( selector );
}
④match不為null時,又分標籤形式 和 id形式
涉及到的函式:$.parseHTML 、$.merge、$.isPlainObject()
$.parseHTML :將字串轉換為儲存DOM節點的陣列 。第一個引數為傳入的字串,第二個為指定的根節點,第三個是boolean值 (“<script></script>”是否可以轉化為<script>標籤),預設為false,不轉換。
$.merge:合併兩個陣列,在jQuery內部不僅可以合併陣列,也可以合併類陣列。例如:
var a={0:"aaa",1:"bbb",length:2} ,
b=["ccc","ddd"];
$.merge(a,b); //{0:"aaa",1:"bbb",2:"ccc",3:"ddd",length:4};
jQuery原始碼中先利用$.parseHTML 將傳入的標籤字串轉化為DOM節點陣列,然後利用$.merge將DOM陣列擴充到jQuery物件。
$.isPlainObject():判斷傳入的引數是否是由 {}或new Object 建立的物件。
程式碼分析:
if:標籤形式,(建立DOM並擴充到jQuery物件)
if ( match[1] ) {
//傳入的第二個引數如果是jQuery物件,則轉化為原生DOM
context = context instanceof jQuery ? context[0] : context;
//利用merge對jQuery例項物件進行擴充
jQuery.merge( this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
...
} else {
...
}
if:傳入的第一個引數 是單標籤字串 且 第二個引數是純物件 ,例如 $("<div>",{"html":"aaa","title":"aaa"});
對第二個物件進行for in 迴圈,如果jQuery[match]是函式,呼叫jQuery[match](context[match]); 否則呼叫jQuery.attr(match,context[match])
if ( match[1] ) {
...
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
} else {
...
}
else:傳入的是#id 形式
if ( match[1] ) {
...
} else {
elem = document.getElementById( match[2] );
if ( elem && elem.parentNode ) {
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
相關jQuery原始碼:
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: core_version,
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
}
// Handle HTML strings
if ( typeof selector === "string" ) {
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
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 ) {
// 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 ) {
// Inject the element directly into the jQuery object
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 rootjQuery.ready( selector );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
},
// Start with an empty selector
selector: "",
// The default length of a jQuery object is 0
length: 0,
...
};