- jQuery(selector, context)构造器
前面介绍了jQuery内部全局变量和构造函数,从正则表达式和核心函数的定义中可以看出,jQuery在框架封装上追求的是简洁、实用。在构造jQuery对象中,同样可以看到jQuery的风格。首先,看下jQuery是如何构造对象?jQuery.fn.init(selector, context, rootjQuery)这个函数具有三个参数,那么jQuery的使用者只能传递两个参数,因为jQuery(seletcor, context)构造器只提供了selector、context这两个形参。从selector、context字面上理解为选择器、上下文。 - jQuery选择器
jQuery的api中选择器提供了五中结构:#id、element、.class、*和selector1,selector2,…,selectorN联合形式。先来看下jQuery.init初始化中源码
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;
var aaa = jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
)
// scripts is true for back-compat
jQuery.merge(this, aaa);
// 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);
},
... ...
jQuery.prototype.init函数体可以拆分为几个if结构:!selector、typeof selector === “string”和selector.selector !== undefined、selector.nodeType和jQuery.isFunction( selector )。
- !selector
// HANDLE: $(""), $(null), $(undefined), $(false)
if (!selector) {
return this;
}
内容一目了然,就是传入:”“、null、undefined和false形式的选择器时,返回一个空的jQuery对象。
- selector.selector !== undefined
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
}
//将selector中的属性按数组下标形式组装到this对象属性中,如this[0]、this[1]、this[2]...
return jQuery.makeArray(selector, this);
如果选择器是一个对象,并且该selector对象包含属性selector,则将该对象包装成jQuery对象,同时由jQuery.makeArray工具方法返回selector和this属性的[]。
jQuery.extend({
... ...
// results is for internal usage only
makeArray: function(arr, results) {
var ret = results || [];
if (arr != null) {
//如果Object(arr)为类数组对象,那么将arr合并到ret返回数组中
if (isArraylike(Object(arr))) {
jQuery.merge(ret,
typeof arr === "string" ? [arr] : arr);
} else {
//否则,[].push(arr)直接存入数组中
core_push.call(ret, arr);
}
}
return ret;
},
/*
* 将数组second合并到first数组中,并返回first,也就是first结构会改变。
*/
merge: function(first, second) {
var l = second.length,
i = first.length,
j = 0;
if (typeof l === "number") {
for (; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
}
first.length = i;
return first;
},
... ...
});
/**
- 判断obj是否为数组的依据是length属性和类型,处理window对象外都可以组装成数组。
- 对于每一个页面都只能有一个window对象。
*/
function isArraylike(obj) {
var length = obj.length,
type = jQuery.type(obj);
if (jQuery.isWindow(obj)) {
return false;
}
if (obj.nodeType === 1 && length) {
return true;
}
return type === "array" || type !== "function" &&
(length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj);
}
- selector.nodeType
如果选择器为HTMLElement元素,则将该元素包装为jQuery对象,并返回。此时,this[0]属性和this.context都设置为selector,并且this.length为1,也就是说jQuery对象是一个isArraylike检查为true的类数组对象。 - jQuery.isFunction( selector )
如果选择器为函数,则将该函数作为ready事件的绑定函数,也就是document.onload的加载处理器。 - typeof selector === “string”
字符类型的选择器可以分为#id、<*>类型的选择器,同时context有无对元素定位也是不同。所以将该类型的选择器切分为几个分支结构: - 单标签的html(
<input>、 <img>和<link>
等等)
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 ];
// 如果selector是aa<....>这种形式的标签, 则html字符检查正则表达式/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/
} else {
// 这个正则表达式可以检查<...>html字符,和#...这种Id字符,并且match匹配只会有两种 [<aa>dd,<aa>,undefined]或[#dd,undefined,dd]
match = rquickExpr.exec( selector ); }
<*>*</*>
、#id选择器
// Match html or make sure no context is specified for #id,如果match有匹配结果,则进行处理
if ( match && (match[1] || !context) ) {
// HANDLE: $(html) -> $(array),如果匹配的是html元素选择器,如<div>、aa<div>、......
if ( match[1] ) {
//判断当前Element上下文是否jQuery上下文,如果是则去[0]的元素
context = context instanceof jQuery ? context[0] : context;
//返回Element元素数组
var aaa = jQuery.parseHTML(match[1],
context && context.nodeType ? context.ownerDocument || context : document, true)
//合并aaa到this上下文中
// scripts is true for back-compat
jQuery.merge( this, aaa);
// 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),如果选择器是按id来进行,那么调用document原生getElementById来实现
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963,
//因为在黑莓4.6版本中存在Element节点没有parentNode属性情况,所以这里单独处理
if ( elem && elem.parentNode ) {
// Inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
//如果元素没有parentNode,那么this对象中就没有元素,而只是一个空的jQuery对象
this.context = document;
this.selector = selector;
return this;
}
// Match html or make sure no context is specified for #id,如果match有匹配结果,则进行处理
if ( match && (match[1] || !context) ) {
// HANDLE: $(html) -> $(array),如果匹配的是html元素选择器,如<div>、aa<div>、......
if ( match[1] ) {
//判断当前Element上下文是否jQuery上下文,如果是则去[0]的元素
context = context instanceof jQuery ? context[0] : context;
//返回Element元素数组
var aaa = jQuery.parseHTML(match[1],
context && context.nodeType ? context.ownerDocument || context : document, true)
//合并aaa到this上下文中
// scripts is true for back-compat
jQuery.merge( this, aaa);
// 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),如果选择器是按id来进行,那么调用document原生getElementById来实现
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963,
//因为在黑莓4.6版本中存在Element节点没有parentNode属性情况,所以这里单独处理
if ( elem && elem.parentNode ) {
// Inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
//如果元素没有parentNode,那么this对象中就没有元素,而只是一个空的jQuery对象
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...)),如果为selector指定了context(jQuery对象),那么直接调用find方法筛选对象
}
这一块包含的内容可以从context有没有进行区分,因为从match = rquickExpr.exec( selector )开始,就像match数组的match[1]有没有值进行判断<div>
和#id选择器。首先!context为真、match[1]为真时document.createElement(*)创建HTMLElement元素,也就是$(<div>
)创建元素。其次!context为真、match[1]为假时document.getElementById(match[2])先获取元素,然后包装成jQuery对象。然后,当!context为真、match为false,也即形如div、.class、parent>child和selector1…这种选择器,由( context || rootjQuery ).find( selector )筛选出符合条件的元素。最后,构造this.constructor( context ).find( selector )jQuery对象,同时查找符合selector条件的元素。
从源码中看到jQuery.prototype原型中修正了constructor构造器,这样做的目的是因为jQuery.prototype原型重新赋值了Object字面量{},那么jQuery的constructor就不在指向本身,而是指向了Object,所以通过人为的给{}添加constructor属性,并让它指向jQuery来修正constructor。
- 结论
从上面看出,jQuery初始化函数init中实现了jQuery选择器selector,对于简单的element、#id和<\w\W+>
形式的元素选择,可以直接包装jQuery对象返回,此外jQuery对象本身是一个类数组的对象,它方招数组下标访问形式this[0]、this[1]… … 和this.length属性,可以for(var)下标遍历,而不只是传统对象{}的foreach遍历。