前言
本篇文章较长,其中包含了对jQuery源码的分析,如果不感兴趣的同学可以直接查看参数类型部分,感兴趣的同学可以继续阅读下面源码解析,如果内容有误,还望指出。
背景
最近的工作需要处理IE8的兼容性问题,但是对于大量的工作任务,使用原生js实现功能,并且兼容IE8是很费时费力的事情,因此为了提高工作效率,JQuery框架便成为了不二选择,于是我便开始学习能提高开发效率的jQuery框架,该专题主要记录学习过程中的问题和总结所需要掌握的知识点,以及对于jQuery源码的分析与理解。文章参考了《jQuery技术内幕》以及网络上其他大牛的博客,旨在分享与记录,如有错误还望指出。
初识jQuery(构造函数)
在使用jQuery的过程中,我们首先就要构造jQuery对象,否则无法调用jQuery的方法,但在构造jQuery对象的过程中,我们发现同一个$()方法却可以传入多种不同的参数,于是以下便对参数进行总结以及分析:
参数类型:
1.$(element)
传入参数是一个DOM对象的情况下,则将DOM对象封装成一个jQuery对象并返回。
2.$(selector[,context])
selector为jQuery的css选择器,context是个可选参数,其表示查询的上下文及查询范围,如果不指定则表示全局查询。
3.$(html[,props])
html为html代码,jQuery会解析html并且使用其创建DOM节点,其中html可分为单独标签以及复杂代码片段,如<p/>或者<p></p>就是单独标签,<p>123</p>则是复杂片段,两种格式的DOM节点创建方式不同。前者是使用document.createElement()方式创建,后者使用innerHTML的方式创建。props则是设置创建的DOM节点的属性。
4.$(object)
object为普通javascript对象,则返回一个封装到jQuery中的对象,返回对象可以使用jQuery的方法。
5.$(callback)
callback为函数,当传入参数为函数时,会将传入函数绑定到ready时间上,其提前于onload事件,在DOM文本解析完成时便触发,貌似绑定与DOMContentLoaded事件上(猜测,暂时不知道,待后续研究)。
6.$(jQuery object)
如果传入一个jQuery对象,则创建该jQuery对象的一个副本并返回,副本与传入的jQuery对象引用完全相同的DOM元素。
7.$()
传入空对象,则返回一个空的jQuery对象。
源码分析:
通过上述参数分类,我们知道了jQuery的构造函数的强大,接下来我们就通过源码来分析上述不同参数类型在jQuery内部的处理方式,以便与我们进一步加深对于jQuery的理解。
首先我们从jQuery源码知道jQuery的所有代码都封装在一个自执行函数中,函数代码如下:
(function(window,undefined){
...
})(window);
上述代码只传入window对象,而内部存在一个undefined参数是为了确保undefined未被重赋值,保证jQuery内部代码的undefined的正确性。(undefined的重赋值问题见此文)
然后jQuery内部使用一种特别的方式避免了外部使用new方法构造jQuery对象,代码如下:
jQuery = function( selector, context ) {
//jQuery.fn实则是jQuery.prototype,init则是jQuery原型上的方法,用于构造jQuery对象
return new jQuery.fn.init( selector, context, rootjQuery );
}
那么以上方法就存在一个问题使用init方法构造jQuery对象会导致jQuery的原型方法实例无法使用,因为实例的原型是init方法的原型而init方法上并没有任何方法。于是jQuery采取了一种极其机智的方法处理上述问题,代码如下:
jQuery.fn.init.prototype = jQuery.fn;
以上代码将jQuery的prototype赋值给init的prototype(注意此时jQuery.fn=jQuery.prototype),于是init方法的实例自然可以使用jQuery对象上的原型方法了。
--等等,说了这么多压根没提jQuery.fn.init方法的内部机制呀💢,别急,理解了上面的知识更有利于理解init内部机制,接下来就让我们看看jQuery.fn.init的源码吧。
代码:(解释均在代码注释上)
function( selector, context, rootjQuery ) {
//selector可以是DOM对象,undefined,字符串,函数,JQuery对象,普通javaScript对象-有效,其他参数无效
//context可传入,可不传入,DOM对象,JQuery对象,普通javaScript对象
//rootJQuery是包含了document对象的JQuery对象,用于document.getElementById()查找失败,selector选择器未指定context或者selector是函数的情况
var match, elem, ret, doc;
//处理$(''),$(null),$(undefined),$(false)
if ( !selector ) {
return this;
}
//处理传入参数是DOMElement的情况。
if ( selector.nodeType ) {//$(this)等DOM对象在此转换成JQuery对象
this.context = this[0] = selector;
this.length = 1;
return this;
}
// 处理传入是HTML代码的情况
if ( typeof selector === "string" ) {
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
//由于IE6~IE8与标准浏览器对charAt方法的支持是一样的。
//而使用String[]的方法取值在IE6~IE8下会返回undefined(注意必须是String对象而非字面量)
//处理HTML代码开始是<,结束是>的情况
match = [ null, selector, null ];
} else {
//rquickExpr=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if ( match && (match[1] || !context) ) {
//如果match存在,且match[1],或者match[2]且context上下文范围为undefined的情况下执行下列代码
// 处理: $(html) -> $(array)
if ( match[1] ) {
//如果match[1]表示是创建dom节点的话,先修正context对象
context = context instanceof jQuery ? context[0] : context;
doc = ( context && context.nodeType ? context.ownerDocument || context : document );
// scripts is true for back-compat
//parseHTML对单一标签内容通过createElement方法创建对象,并返回一个包含该对象的数组。
//jQuery将对HTML代码的复杂对象与非复杂对象处理都放在了parseHTML函数中,感兴趣的同学可下去继续研究,我也会在以后对该函数进行分析。
selector = jQuery.parseHTML( match[1], doc, true );
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
//在context是props属性时,将属性赋值到新生成的DOM元素上
this.attr.call( selector, context, true );
}
return jQuery.merge( this, selector );
// 处理: $(#id)
} else {
//查找id对应元素
elem = document.getElementById( match[2] );
//检测parentNode属性,因为黑莓4.6会返回已经在文档中不存在的节点
if ( elem && elem.parentNode ) {
//在IE7和IE6中有可能按照name查找了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;
}
// 处理: $(selector, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
// 处理: $(selector, context)
// 相当于$(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
// 处理: $(function)
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );//如果是一个函数则执行ready
}
if ( selector.selector !== undefined ) {
//如果传入对象是JQuery对象
this.selector = selector.selector;
this.context = selector.context;
}
//在传入参数是非上述类型数据,而是如同Array,Number,JS原生对象的情况下,将直接置入当前jQuery实例中返回,以使上述数据可使用jQuery方法
return jQuery.makeArray( selector, this );
}
上述代码段便是jQuery的init方法内部机制,其中我都加上了解释与自己的理解,希望读者能够阅读并且在浏览器环境下通过开发者工具执行jQuery代码并加入断点,观测代码执行情况以进一步测试代码运行。
总结:
jQuery是一个非常棒的函数库,它帮助开发者减轻开发负担,解决兼容问题,提高开发效率,但是作为一个想要在JS方面有所成就的开发者,仅会使用是不够的,我们还要花费时间去学习该框架的内部机制,理解其代码风格,当质疑自己代码写得不好的时候,那就是因为你看的代码不够多,因此学习jQuery源码,可以让我们真正了解到自己与大师之间的差距。以后我也会进一步对jQuery代码进行研究,其中很多个大的模块如AJAX,选择器,DOM操作,事件代理等均是jQuery的经典之处,希望有兴趣的同学也可以自己研究深入。
行文仓促,如有错误,还望指出。🙏