今天继续分析jQuery.fn.init( selector, context, rootjQuery );的实现。这是我截取的该方法的代码:
init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; 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; doc = ( context && context.nodeType ? context.ownerDocument || context : document ); // scripts is true for back-compat selector = jQuery.parseHTML( match[1], doc, true ); if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { this.attr.call( selector, context, true ); } return jQuery.merge( this, selector ); // 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 ) { // 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; } // 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: $(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 ); }
下面我们来一段一段的分析:
if ( !selector ) { return this; }
这段代码用来处理形如 $("")、$(null)、 $(undefined)、 $(false)的情况,这样可以增加程序的健壮性,避免用户操作不规范时导致整个程序出错和崩溃。平常我们在写代码的时候也应该有这个意识,充分考虑各种情况,从而使测试的时候减少很多不必要的麻烦。另外,在jquery的很多地方,都会返回this,这里的this指向jQuery实例,同时这也是jQuery实现链式调用的方法。
if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; }
上面的这段代码用于处理类似$(DOMElement)的情况,比如:$(document.getElementById("a"))。这里的this[0]是干嘛的呢?其实很简单,0是它的属性名而已。因为 this.0会语法报错,所以就用了this[0]这样一个小技巧。下面给出一个例子,一看就明白:
function Person(name,age) { this[0] = name; this['age'] = age; } var p = new Person("LiLei",22); alert(p[0]); alert(p.age);
至于context和length,更是简单了,他们俩不过是使用jQuery构造器方法生成的jQuery对象的两个属性而已。
注意,下面的才是重头戏:
// 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; doc = ( context && context.nodeType ? context.ownerDocument || context : document ); // scripts is true for back-compat selector = jQuery.parseHTML( match[1], doc, true ); if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { this.attr.call( selector, context, true ); } return jQuery.merge( this, selector ); // 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 ) { // 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; } // 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: $(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 );
上面的这一大段长长的代码是用来处理$('字符串')和$(function(){})这样的情况。先说简单的吧:
if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); }
这段代码用于当selector参数是函数的情况下的操作。因为在JavaScript里,函数既可以是函数,也可以是对象,所以在jQuery里有很多类似于isFunction的方法依附在jQuery变量上。jQuery.isFunction( selector ) 的实现代码:
isFunction: function( obj ) { return jQuery.type(obj) === "function"; }
继续查找,jQuery.type(obj)的实现:
type: function( obj ) { return obj == null ? String( obj ) : class2type[ core_toString.call(obj) ] || "object"; }
这儿的core_toString是什么意思?实际上在jQuery最开始的时候定义的那一大堆变量里面有:core_toString = Object.prototype.toString。在JavaScript中,一切皆为对象。所以 Object.prototype.toStringcall(obj)可以有以下取值:
[object Array]、[object String]、[object Function]、[object Object]、[object RegExp]、[object Number]、[object Undefined]、[object Null]、[object Date]、[object Boolean]
注:以上的全是字符串。但是在IE6中,却会出现以下问题:通过Object.prototype.toString.call获取的 字符串,undefined,null均为Object
至于class2type,我找到了下面这一大段代码:
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); });
上面这一段代码中,jQuery.each的意思就是对于第一个数组参数里面的每一个元素执行第二个参数函数。也就是说:class2type[ "[object Function]" ] = "function"。
所以[object Array]、[object String]、[object Function]、[object Object]、[object RegExp]、[object Number]、[object Date]、[object Boolean]就会全部转化为:"array"、"string"、"function"、"object"、"regexp"、"number"、"date"、"boolean"。
再把以上分析串联起来,得出以下结果:如果selector参数是函数的话,执行return rootjQuery.ready( selector );代码。而rootjQuery = jQuery(document);所以rootjQuery.ready( selector )相当于jQuery(document).ready( selector )。使用过jQuery的人应该知道,在jQuery里,jQuery(document).ready( function(){} )表示当文档的DOM树构建成功后执行函数(这个方法的实现后面再说)。
综合上述,当selector参数是函数时,该函数会在文档的DOM树构建成功后执行。所以现在我们有了两种方法在文档的DOM树构建成功后执行代码:
//方法1: $(document).ready(function() { /*code*/ }); // 这里的$也可以用jQuery //方法2: $(function() { /*code*/ }); // 这里的$也可以用jQuery
不过$(function() {});方法实际上还是在调用$(document).ready(function() {});。
上面讲了这么多,实际上还是没有讲到最难的那一部分,就是处理字符串的部分,这部分由于涉及到很多jQuery里面其他的函数和方法,所以我将在明天专门介绍。