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的属性,从而 css()等方法可以利用 for...in循环处理 $() 集合中DOM元素。火狐下控制台截图如下:
下面分步骤分析代码:
①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, ... };