jquery构造器的实现
jQuery的$符号非常神奇,它可以接受一个字符,也可以接受一个文档对象或window对象,亦可以传个函数进行变为domReady加载器。显然,能做到这一步,其实现是相当的复杂,这个实现就是它的init方法,jQuery的真实构造器。它功能也随着版本的升级而升级,越来越长。
2009-01-13发布的1.3版
init: function ( selector, context ) { |
// Make sure that a selection was provided |
selector = selector || document; |
|
// 处理节点参数,直接添加属性到新实例上 |
if ( selector.nodeType ) { |
this [0] = selector; |
this .length = 1; |
this .context = selector; |
return this ; |
} |
// 处理字符串参数 |
if ( typeof selector === "string" ) { |
// 判定是否为HTML片断还是ID |
var match = quickExpr.exec( selector ); |
|
if ( match && (match[1] || !context) ) { |
|
// 如果是HTML片断,转换一个由节点构造的数组 |
if ( match[1] ) |
selector = jQuery.clean( [ match[1] ], context ); |
|
// 如果是ID,则查找此元素,如果找到放进空数组中 |
else { |
var elem = document.getElementById( match[3] ); |
|
// Make sure an element was located |
if ( elem ){ |
// 处理 IE and Opera 混淆ID与NAME的bug |
if ( elem.id != match[3] ) |
return jQuery().find( selector ); |
var ret = jQuery( elem ); |
ret.context = document; |
ret.selector = selector; |
return ret; |
} |
selector = []; |
} |
} else |
//使用Sizzle处理其他CSS表达式,生成实例并返回 |
return jQuery( context ).find( selector ); |
// 处理函数参数,直接domReady |
} else if ( jQuery.isFunction( selector ) ) |
return jQuery( document ).ready( selector ); |
|
//处理jQuery对象参数,简单地将其两个属性赋给新实例 |
if ( selector.selector && selector.context ) { |
this .selector = selector.selector; |
this .context = selector.context; |
} |
//将上面得到节点数组,用setArray方法把它们变成实例的元素 |
return this .setArray(jQuery.makeArray(selector)); |
}, |
2009-02-19发布的1.32版
init: function ( selector, context ) { |
// Make sure that a selection was provided |
selector = selector || document; |
// 处理节点参数,直接添加属性到新实例上 |
if ( selector.nodeType ) { |
this [0] = selector; |
this .length = 1; |
this .context = selector; |
return this ; |
} |
//处理字符串参数 |
if ( typeof selector === "string" ) { |
//判定是否为HTML片断还是ID |
var match = quickExpr.exec( selector ); |
if ( match && (match[1] || !context) ) { |
// 如果是HTML片断,转换一个由节点构造的数组 |
if ( match[1] ) |
selector = jQuery.clean( [ match[1] ], context ); |
else { |
var elem = document.getElementById( match[3] ); |
|
// 如果是ID,则查找此元素,如果找到放进空数组中 |
if ( elem && elem.id != match[3] ) |
return jQuery().find( selector ); |
|
//这里对1.3版做了些优化,更简洁 |
var ret = jQuery( elem || [] ); |
ret.context = document; |
ret.selector = selector; |
return ret; |
} |
} else |
//使用Sizzle处理其他CSS表达式,生成实例并返回 |
return jQuery( context ).find( selector ); |
|
// 处理函数参数,进行domReady操作 |
} else if ( jQuery.isFunction( selector ) ) |
return jQuery( document ).ready( selector ); |
|
//处理jQuery对象参数,简单地将其两个属性赋给新实例 |
if ( selector.selector && selector.context ) { |
this .selector = selector.selector; |
this .context = selector.context; |
} |
//这里对1.3版做了些扩展,允许传珍上元素集合(HTMLCollection)与节点集合(NodeList), |
//元素数组可能是我们用字符串转换过来的,也可以是用户直接传进来的 |
return this .setArray(jQuery.isArray( selector ) ? selector : jQuery.makeArray(selector)); |
}, |
2010-01-13发布的1.4版
init: function ( selector, context ) { |
var match, elem, ret, doc; |
|
//处理空白字符串,null,undefined参数(新增),返回一个非常纯净的实例 |
if ( !selector ) { |
return this ; |
} |
|
// 处理节点参数,直接添加属性到新实例上 |
if ( selector.nodeType ) { |
this .context = this [0] = selector; //写法上优化 |
this .length = 1; |
return this ; |
} |
|
//处理字符串参数 |
if ( typeof selector === "string" ) { |
// 判定是否为HTML片断还是ID |
match = quickExpr.exec( selector ); |
if ( match && (match[1] || !context) ) { |
|
//如果是HTML片断 |
if ( match[1] ) { |
//取得文档对象 |
doc = (context ? context.ownerDocument || context : document); |
|
// 如果是单个标签,直接使用 document.createElement创建此节点并放入数组中 |
ret = rsingleTag.exec( selector ); |
|
if ( ret ) { |
//如果后面跟着一个纯净的JS对象,则为此节点添加相应的属性或样式 |
if ( jQuery.isPlainObject( context ) ) { |
selector = [ document.createElement( ret[1] ) ]; |
jQuery.fn.attr.call( selector, context, true ); |
} else { |
selector = [ doc.createElement( ret[1] ) ]; |
} |
|
} else { |
//改由buildFragment来生成节点集合(NodeList) |
ret = buildFragment( [ match[1] ], [ doc ] ); |
selector = (ret.cacheable ? ret.fragment.cloneNode( true ) : ret.fragment).childNodes; |
} |
} else { |
// 如果是ID,则查找此元素,如果找到放进空数组中 |
elem = document.getElementById( match[2] ); |
|
if ( elem ) { |
// 处理 IE and Opera 混淆ID与NAME的bug |
if ( elem.id !== match[2] ) { |
return rootjQuery.find( selector ); |
} |
//这里也做了一些优化,原来是很傻地再生成一个jQuery实例 |
this .length = 1; |
this [0] = elem; |
} |
this .context = document; |
this .selector = selector; |
return this ; |
} |
|
// 如果字符是很简单的标签选择器,那基本没有必要走Sizzle路线,直接getElementsByTagName,很好的优化 |
} else if ( !context && /^\w+$/.test( selector ) ) { |
this .selector = selector; |
this .context = document; |
selector = document.getElementsByTagName( selector ); |
|
// 如果第二个参数不存在或者是jQuery对象,那么用它或rootjQuery调用find查找目标节点(走Sizzle路线) |
} else if ( !context || context.jquery ) { |
return (context || rootjQuery).find( selector ); |
|
// HANDLE: $(expr, context) |
// (which is just equivalent to: $(context).find(expr) |
} else { |
//如果第二个参数已指定为某元素节点,转为jQuery对象,走Sizzle路线 |
return jQuery( context ).find( selector ); |
} |
|
// 处理函数参数,直接domReady |
|
} else if ( jQuery.isFunction( selector ) ) { |
return rootjQuery.ready( selector ); |
} |
//处理jQuery对象参数,简单地将其两个属性赋给新实例 |
if (selector.selector !== undefined) { |
this .selector = selector.selector; |
this .context = selector.context; |
} |
//这里又做了些许修改,缘于makeArray可以接受第二个参数(可以是数组或类数组,这时相当合并操作) |
return jQuery.isArray( selector ) ? |
this .setArray( selector ) : //内部用push方法,迅速将一个普通对象变成类数组对象 |
jQuery.makeArray( selector, this ); |
}, |
接着是广受欢迎的2010-02-13发布的1.42版
init: function ( selector, context ) { |
var match, elem, ret, doc; |
|
// 处理空白字符串,null,undefined参数 |
if ( !selector ) { |
return this ; |
} |
// 处理节点参数 |
if ( selector.nodeType ) { |
this .context = this [0] = selector; |
this .length = 1; |
return this ; |
} |
// 处理body参数(新增) |
if ( selector === "body" && !context ) { |
this .context = document; |
this [0] = document.body; |
this .selector = "body" ; |
this .length = 1; |
return this ; |
} |
|
// 处理字符串参数,分七种情形: |
//①单个标签,带对象属性包 ---> jQuery.merge |
//②单个标签,不带对象属性包 ---> attr + jQuery.merge |
//③复杂的HTML片断 ---> buildFragment + jQuery.merge |
//④ID选择器,与找到的元素的ID不同 ---> getElementById + Sizzle + pushStack |
//⑤ID选择器,与找到的元素的ID相同 ---> getElementById + 简单属性添加 |
//⑥标签选择器 ---> getElementsByTagName + jQuery.merge |
//⑦其他CSS表达式 ---> Sizzle + pushStack |
if ( typeof selector === "string" ) { |
match = quickExpr.exec( selector ); |
if ( match && (match[1] || !context) ) { |
if ( match[1] ) { |
doc = (context ? context.ownerDocument || context : document); |
ret = rsingleTag.exec( selector ); |
if ( ret ) { |
if ( jQuery.isPlainObject( context ) ) { |
selector = [ document.createElement( ret[1] ) ]; |
jQuery.fn.attr.call( selector, context, true ); |
|
} else { |
selector = [ doc.createElement( ret[1] ) ]; |
} |
} else { |
ret = buildFragment( [ match[1] ], [ doc ] ); |
selector = (ret.cacheable ? ret.fragment.cloneNode( true ) : ret.fragment).childNodes; |
} |
|
return jQuery.merge( this , selector ); |
} else { |
elem = document.getElementById( match[2] ); |
|
if ( elem ) { |
if ( elem.id !== match[2] ) { |
return rootjQuery.find( selector ); |
} |
this .length = 1; |
this [0] = elem; |
} |
|
this .context = document; |
this .selector = selector; |
return this ; |
} |
} else if ( !context && /^\w+$/.test( selector ) ) { |
this .selector = selector; |
this .context = document; |
selector = document.getElementsByTagName( selector ); |
return jQuery.merge( this , selector ); |
|
} else if ( !context || context.jquery ) { |
return (context || rootjQuery).find( selector ); |
} else { |
return jQuery( context ).find( selector ); |
} |
// 处理函数参数,直接domReady |
} else if ( jQuery.isFunction( selector ) ) { |
return rootjQuery.ready( selector ); |
} |
//处理jQuery对象参数 |
if (selector.selector !== undefined) { |
this .selector = selector.selector; |
this .context = selector.context; |
} |
//无论是数组还是类数组(如NodeList),统统使用jQuery.makeArray来为实例添加新的元素 |
return jQuery.makeArray( selector, this ); |
}, |
另附上makeArray方法与merge方法,merge方法好神奇啊,
makeArray: function ( array, results ) { |
var ret = results || []; |
if ( array != null ) { |
// The window, strings (and functions) also have 'length' |
// The extra typeof function check is to prevent crashes |
// in Safari 2 (See: #3039) |
if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || ( typeof array !== "function" && array.setInterval) ) { |
push.call( ret, array ); |
} else { |
jQuery.merge( ret, array ); |
} |
} |
return ret; |
}, |
merge: function ( first, second ) { |
var i = first.length, j = 0; |
if ( typeof second.length === "number" ) { |
for ( var l = second.length; j < l; j++ ) { |
first[ i++ ] = second[ j ]; |
} |
|
} else { |
while ( second[j] !== undefined ) { |
first[ i++ ] = second[ j++ ]; |
} |
} |
first.length = i; |
return first; |
}, |
2011-01-23发布的1.5版,其init方法与1.42的变化不大:只有两处做了改动:
//1.42 |
- ret = buildFragment( [ match[1] ], [ doc ] ); |
- selector = (ret.cacheable ? ret.fragment.cloneNode( true ) : ret.fragment).childNodes; |
//1.5 |
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); |
+ selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; |
|
//1.42 |
- return jQuery( context ).find( selector ); |
//1.5 |
+ return this .constructor( context ).find( selector ); //目的就是为了不再生成新实例 |
2011-05-02发布的jquery1.6,变化不大,只是对HTML片断进行了更严密的判定:
// Are we dealing with HTML string or an ID? |
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 = quickExpr.exec( selector ); |
} |
总体来说,jQuery的构造器已经做得非常之完美,基本上达到“改无可改”的地步了。但是要保证其高效运作,我们还需要一点选择器的知识与了解buildFragment方法的运作,因为这两个实在太常用了,但也是最耗性能的。