创建jQuery对象的整体架构
源码架构分解如下:
// 整个架构就是闭包自执行,参数factory是一个函数,闭包最终其实也就是执行factory
(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
var
version = "1.11.1",
// jQuery对象构造函数
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
// jQuery对象原型
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
selector: "",
length: 0,
// 省略在原型定义的公用函数
};
// line 2747
<span style="white-space:pre"> </span>// 定义jQuery.fn.init
init = jQuery.fn.init = function( selector, context ) {
// 省略
};
line 2853
// 原型指向jQuery原型
init.prototype = jQuery.fn;
// line 10299
// 执行完毕向window导出jQuery和$
if ( typeof noGlobal === strundefined ) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
}));
从上架构简易分解可以看出,当使用$(selector)或者jQuery(selector)等方式创建jQuery对象时,执行的是如下函数:
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
即实际调用的是jQuery.fn.init( selector, context )去得到最终对象。
仔细分析发现我们在是创建jQuery对象的时候并没有使用关键字new,但创建出来的对象实例依然跟jQuery.prototype关联起来了,拥有其所有属性,这是因为执行jQuery()时,返回结果是new jQuery.fn.init(selector, context),也就是实际jQuery对象是通过init函数构造出来的,最后通过init.prototype = jQuery.fn使得其与jQuery函数的原型关联起来。
jQuery.fn.init详细分析
从源码2733行开始,详细分析如下:
// jQuery(document)
var rootjQuery,
document = window.document,
// 校验HTML格式,优先匹配id防止XSS通过location.hash注入
// (?:exp)表示不捕获匹配文本不给分组记号,因此本正则第一个分组(<[\w\W]+>)匹配html标签,之后[^>]*匹配右非尖括号的任意字符,第二个分组([\w-]*)匹配id
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
// context是限定上下文(即确定范围),不传则默认是document,即在document下创建JQ对象
init = jQuery.fn.init = function( selector, context ) {
var match, elem;
// 若selector为各种空则返回空对象
if ( !selector ) {
return this;
}
// 对selector值不同情况进行分类处理
// 处理selector是字符串的场景
if ( typeof selector === "string" ) {
// 寻找html标签,则将其放在match[1]位置
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// 原注释意思是有rquickExpr匹配不到html标签的场景(我暂时想不出哪种场景会这样),因此需要这个if判断
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
// 有html的情况,以及单独id没有context的情况
if ( match && (match[1] || !context) ) {
// 传入html的场景,HANDLE: $(html) -> $(array)
if ( match[1] ) {
// 获取原生上下文,防止传入的是JQ对象
context = context instanceof jQuery ? context[0] : context;
// jQuery.parseHTML()将传入html转成dom数组与this合并
jQuery.merge( this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// 传入html及其属性的场景,HANDLE: $(html, props)
// rsingleTag保证match[1]必须是单标签
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
// 遍历context属性
for ( match in context ) {
// 如果属性名是jQuery内部方法,则执行且属性值作为参数传入
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// 否则调用attr设置进标签中,前面jQuery.parseHTML()已经根据html生成dom对象了
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
// 调用document.getElementById()找match[2]匹配的id对应的dom对象
} else {
elem = document.getElementById( match[2] );
// 规避Blackberry 4.6会找到已经不存在的节点的bug,因此要判断parentNode是否存在(bug id #6963)
if ( elem && elem.parentNode ) {
// 防止IE跟Opera的document.getElementById()返回的是以name获取的dom对象
if ( elem.id !== match[2] ) {
// 调用find即调用Sizzle去查找节点
return rootjQuery.find( selector );
}
// 将得到的dom对象放进JQ对象(类数组结构)
this.length = 1;
this[0] = elem;
}
// 最后定义JQ对象的context跟selector属性,然后返回
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...)),处理context不存在或者是一个JQ对象(通过context.jquery这个版本属性来判断)的场景,均是直接调用find()
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
// HANDLE: $(expr, context),处理context不是JQ对象的场景
// 使用this.constructor( context )生成JQ对象,然后调用find()
} else {
return this.constructor( context ).find( selector );
}
// 处理selector是dom对象的场景
// 直接将其引用传入JQ对象以及this.context中,设置一下length,然后返回(备注:window对象没有nodeType)
} else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
// 处理selector是function的场景
// 调用ready的简便写法:$(function(){})
} else if ( jQuery.isFunction( selector ) ) {
return typeof rootjQuery.ready !== "undefined" ?
rootjQuery.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
// 以上全部不满足则执行以下代码,传进来的selector如果是一个JQ对象,则会有selector跟context属性,就会进入以下if分支
// 最后jQuery.makeArray()相当于复制selector这个JQ对象返回(makeArray会把selector里的属性全部浅拷贝进this然后返回this)
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
};
// init函数的原型指向jQuery.fn即指向jQuery函原型
init.prototype = jQuery.fn;
// 初始化rootjQuery
rootjQuery = jQuery( document );
总结,创建jQuery对象传入参数的情况如下:
selector | context | return |
空 | / | 空JQ对象 |
单独标签如<li>,<li></li>,不含text 即$(html, props) | 纯粹对象 | 创建dom,执行context,包装返回 |
html字符串,不满足单独标签 | / | 创建dom包装返回 |
#id | 空/上下文对象 | context为空直接调用document.getElementById(id), 存在上下文对象则用find() |
复杂选择器 | / | find() |
dom节点 | / | 包装返回 |
function | / | 当做参数传入执行ready |
JQ对象 | / | 赋值selector,contexnt,调用makeArray返回 |
其他 | / | 调用makeArray返回 |