jQuery1.2.6源码分析


jQuery 源码分析 版权所有 Jquery 源码分析 1、概述 jQuery 是一个非常优秀的 JS 库,与 Prototype,YUI,Mootools 等众多的 Js 类 库相比,它剑走偏锋,从 web 开发的实用角度出发,抛除了其它 Lib 中一些中看 但不实用的东西,为开发者提供了优美短小而精悍的类库。其使用简单,文档丰 富,而且性能高效,能极大地提高 web 系统的开发效率。因此可以说是 web 应 用开发中最佳的 Js 辅助类库之一。大部分开发者正在抛弃 Prototype,而选择 Jquery 做为他们进行 web 开发的 JS 库。 如是开发人员仅仅只知道文档中的简单的使用方法,却不明白 Jquery 的运 行原理和内部机制,在使用 jquery 时,肯定会碰到许多的问题。这些问题有一部 分是 Jquery 的 Bug。大部分是自身的使用不当而造成的。而文档的简单的使用说 明很难解决问题。在调试基于 jQuery 的 web 应用时,很多时候都要跟踪进入 jQuery 对象分析其运行状态以了解出错的原因。 如果对于 web 的应用的页面运行性能和效率有所要求的话,那么我们更应 该去明白其运行机理和核心源码。但是 jQuery 源码不像其它的类库那样,它有 点晦涩,难懂。这就是本源码分析的原因,让所有使用 jQuery 的读者,能快速 上手 jQuery 的源码,并在开发中得心应用。 Jquery 的网络资源丰富,但 Baidu 了很久,很难找到那种完全深入地分析 Jquery 源码的文稿。倒是 Jquery 的开发者,John Resi 的《Pro Javascript Techniques》 涉及到 Jquery 的源码的分析,但是其主指还是在于 JavaScript 的使用。那本书并 不能使我们完全细致地了解 Jquery 的源码。 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 第一篇 Query(查询) 2、构建 jQuery 对象 在本节中,我们会就 Jquery 的运行机制和设计理念进行分析及说明。本节 主要从 jQuery 的设计理念 e 及其构建的源码进行剖析。 2.1、jQuery 的设计理念 在使用 jQuery 之前,我们也许会问 jQuery 是什么?其实它的名字就很能反 映其主旨的。J 是的 JS,Query 是指查询。如果把 jQuery 看作是一个查询的 JS 类库。它和 prototype,mootools 等类库一样,为 Web 的 Js 开发提供辅助功能。 那为什么要选用 jQuery 呢?在 jQuery 出现之前,Prototype,YUI 都已经是 成熟的 Js 的框架,而且是各有各的特点。并且市场的书和使用文档都很详尽。 为什么开发员会抛弃它们,而使用后起之秀的 jQuery,它有什么优秀的特性吸引 开发人员呢? 回答这个问题,我们得明白 jQuery 的设计理念。回忆或想象一下,我们在 web 开发中是如何使用 JS?绝大多数时间都是进行如下五个方面的事情: 1、采用 getElementById 在 Dom 文档中找到 Dom 元素,然后取值或设值。 2、对元素采用 innerHTML 取其内容或设定其内容。 3、对元素进行事件的监听(如 click)。 4、通过改变元素的 CSS 样式如 height,达到视觉上的效果。 5、通过 Ajax 从服务器取值,往指定元素里添充内容。 从上面可以看,在使用 Js 开发时候就是在对 Dom 元素在进行操作。这个 Dom 元素可能是单个或是集合的形式。对元素元素操作,对于 document,window 是可以直接引用,但是对于其它的 Dom 元素,我们得从 Dom 文档树查找得到吧。 这样的话就可以把 JS 的操作分析两部分任务, 一、查找 Dom 元素, 二、对 Dom 元素进行操作。 对于使用 JS 熟练的开发者而言,也许手写 document. getElementById 或 elment.getElementsByTagName 这样冗长的直接查找 Dom 元素觉得不是什么问 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 题,许会对 element 的 event,attribute,style 等操作也不含糊,但是对于 IE、mozilla 等几大主流的浏览器的兼容足够让每一个 JS 高手头疼。 这是使用 JS 类库的主要原因。JS 类库只要用得恰当的话,也不一定比直接 采用 JavaScript 的原始函数和对象的运行效率低。但是其却能极大地提高开发的 效率。 自从 Prototype 采用$符号做为 document. getElementById 的缩写,$符号似乎 成了查找元素的代理符号。但是这种简单的查找并不能满足 web 应用的需要。 很多时间我们需要像 CSS selector 那样查找 Dom 元素。 jQuery 从这里出发,采用$符号做为查找元素的代理。它不再是那种简单的 getElementById,而是功能强大的 CSS selector 的选择器。这也就是 query 的本意。 解决了查找的元素的任务,之后就是对元素的操作。 jQuery 抛弃了 prototype 中那么对 Array,Object,Function,Event 等 JS 原生对 象的扩展。把所有的心思都放在解决实际问题的 Dom 元素的操作上。它不仅简 化 Dom 元素原生的冗长名字的函数名和众多难记的方法,而且在简化这些方法 的同时提供了更为便捷且兼容浏览器的功能。同时那些实用的功能一个都没少, 如 Ajax,Event,Fx,CSS 的操作应有仅有。 Prototype 中 Event,Ajax 等众多的对象不但让人觉得烦琐难记,而且让人感 觉有点畏惧感。jQuery 在设计时就考虑到这一点。它提供了统一的入口,就是一 个对象:jQuery 对象($)。所有的操作,变化都是针对这个对象。 现在可能给 jQuery 一个明确的解释:jQuery 实质就是一个查询器。在查询 器的基础还提供对查找到的元素进行操作的功能。这样说来 jQuery 就是查询和 操作的统一。查询是入口,操作是结果。 jQuery 对象在代码分成两大部分,一部分是 jQuery 的静态方法,也可以称 作实用方法或工具方法,通过 jQuery.xxx()的 jQuery 命名空间直接引用。第二部 分是 jQuery 的实例方法,通过 jQuery(xx)或$(xx)来生成 jQuery 实例,然后通过 这个实例来引用的方法。这部分的方法大多数是从采用静态方法代理来完成功 能。真正的功能性的操作都在 jQuery 的静态方法中实现。 这些功能细分起来,可以分成以下几个部分: 1、Selector 查找元素。这个查找不但包含基于 CSS1~CSS3 的 CSS Selector 功能,还包含其对直接引用或间接引用 Dom 元素而扩展的一些功能。 2、Dom 元素的属性操作。Dom 元素可以看作 html 的标签,对于属性的操 作就是对于标签的属性进行操作。这个属性操作包含增加,修改,删除, 取值等。 3、Dom 元素的 CSS 操作。CSS 是控制页面的显示的效果。对 CSS 的操作 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 那就得包含高度,宽度,display 等这些常用的 CSS 的功能。 4、Ajax 的操作。Ajax 的功能就是异步从服务器取数据然后进行相关操作。 5、Event 的操作。对 Event 的兼容做了统一的处理。 6、动画(Fx)的操作。可以看作是 CSS 样式上的扩展。 2.2、jQuery 对象的构建 上一节分析了从整体上分析了 jQuery 的原理,从其原理可以看出,其统一 的入口就是 jQuery 对象。那么这个对象是如何生成的呢?上一节还提到了 jQuery 的实质是 Query,那么生成 jQuery 对象就可能看作是构建并运行一个查询器。 既然是查询,肯定会有查找到的结果(Dom 元素),那么这些结果又存放在 哪里呢?最好的地方当然是 jQuery 对象内面。查询的结果可能是单个元素,也 可能是集合如 NodeSet。 也就是说 jQuery 对象内面应该有一个集合。且这个集合是用来存放查询到 Dom 元素。但 jQuery 对象是所有操作的统一入口,那么它的构建就不应只局限 于从 Dom 文档树中查询到 Dom 元素,有可能是从别的集合中转移过来的 Dom 元素,或是 html 的片断生成的 Dom 元素。 Jquery 文档中提供了四种构建方式:jQuery(expression,[context]) , jQuery(html),jQuery(elements),jQuery(callback)。其中 jQuery 可以用$代替。这 四种方式是经常用到。其实 Jquery 的参数可以是任何的元素。也就是说任何的 参数都可以构建 jquery 对象。举几个例子: 1、$($(“P”))可以看出其参数可以是 jQuery 对象或 ArrayLike 的集合。 2、$()是$(document)的简写。 3、$(3)会把 3 放到 jQuery 对象中集合中。 对于如$(3)这样的其中元素(如 ArrayLike 集合的元素)不是 Dom 元素,最 好不要构建 jQuery 对象,jQuery 对象的方法设计的目的都是针对于 dom 对象的 而进行的操作。如果不清楚其使用的话,很有可能会导致错误。 上面讲了这么多大道理,现在从源码的角度细细分析: 通过 jQuery(xxx)的调用实现没有生成对象,它的 this 是指向 window 对象的。 那么 jQuery 的那些实例方法是怎样继承过来的呢?看一下: var jQuery = window.jQuery = window.$ = function(selector, context) { return new jQuery.fn.init(selector, context); ① }; 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 这是 jquery 的总入口,jQuery 对象不是通过 new jQuery 来继承其 prototype 中的方法,而是 jQuery.fn.init 函数生成的对象。 这里我们可以看出对于 jQuery.prototype 添加一些函数集的对象的意义不大。 new jQuery()还是可以的,但是生成的 jQuery 对象在 return 时会被抛弃。故不要 用 new jQuery()来构建 jQuery 对象。 jQuery 对象其实就是 jQuery.fn.init 对象。那么 jQuery.fn.init.prototype 上就是 挂着 jQuery 对象的操作方法。如 jQuery.fn.init.prototype = jQuery.fn; 有时间可能会担心在 589 行就实现了把 jQuery.fn 中的函数放到 jQuery.fn.init.prototype 上去,那么之后的通过 jquery.fn.extend 的方法怎么办呢? 这里是对 jQuery.fn 的引用。在扩展 jQuery 的时候,只要把相关的函数 extend 到 jQuery.fn 就可以了。 现在我们看一下 jQuery.fn.init 是怎么完成工作的: init : function(selector, context) { selector = selector || document;// 确定selector存在 // 第一种情况 Handle $(DOMElement)单个Dom 元素,忽略上下文 if (selector.nodeType) { ② this[0] = selector; this.length = 1; return this; } if (typeof selector == "string") {//selector为string ③ var match = quickExpr.exec(selector); if (match && (match[1] || !context)) { if (match[1])// 第二种情况处理$(html) -> $(array) ④ selector = jQuery.clean([match[1]], context); else {// 第三种情况:HANDLE: $("#id")//处理$("#id") var elem = document.getElementById(match[3]); if (elem) { // IE会返回name=id的元素 ,如果是这样,就document.find(s) if (elem.id != match[3]) ⑤ return jQuery().find(selector); // 构建一个新的jQuery(elem) return jQuery(elem); ⑥ } selector = []; } } else // 第四种情况:处理$(expr, [context])==$(content).find(expr) return jQuery(context).find(selector); ⑦ } else if (jQuery.isFunction(selector)) ⑧ 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 // 第五种情况:处理$(function)七Shortcut for document ready return jQuery(document)[jQuery.fn.ready ? "ready" : "load"](selector); // 第六种情况:处理$(elements) return this.setArray(jQuery.makeArray(selector)); ⑨ }, jQuery.fn.init 负责对传进来的参数进行分析然后生成 jQuery 对象。它的第一 个参数一般来说是必须的(为空的话,就是默认的 document)。从源码的角度第 一个参数有着如下四种类型: 类型 说明 Dom Element 第一个参数为 Dom 元素,第二个参数不用。直接把 Dom 元素 存在新生成的 jQuery 对象的集合中。返回这个 jQuery 对象。构建 jQuery 对象完成。 String 第一个参数为 string 有三种情况: 1、html 的标签字符串,$(html) -> $(array),第二个参数可选。 执行 selector = jQuery.clean([match[1]], context);。该语句是把 hteml 的字符串转换成 dom 对象的数组。接着执行 Array 类 型的返回。 2、字符串为#id 时$(id) 首先通过 var elem = document.getElementById(match[3]);取 得 elem,如没有取到 selector = [];转到执行 Array 类型的返回 空集合 jquery 对象。 如找到 elem,通过 return jQuery(elem);再次生成 jquery 对象, 这次是 Dom Element 类型的 jquery 对象的返回。 3、兼容 css1-3 语法的 selector 字符串,第二个参数是可选的。 执行 return jQuery(context).find(selector);。该语句先执行 jQuery(context)。可以看出 context 第二参数可以是任意的值, 可以是集合形式。之后就通过 find(selector) 找到 jQuery(context)中所有 dom 元素都满足 selector 表达式的 dom 元素的集合,构建新的 jquery 对象,并返回。 #id其实和这种方式是统一的,单独出来是为了提高性能。 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 Fn 第一参数是函数。第二个参数不用。是$(document).ready(fn)的 简写,其 return jQuery(document)[jQuery.fn.ready ? "ready" : "load"](selector) 是其执行的代码。这个语句首先执行 jQuery(document),它再一次 newjQuery.fn.init 函数,生成 jQuery 对 象(元素为 document)。再调用这个对象的 ready(fn)方法。Ready(fn) 返回当前对象。而上面的语句又是返回这个 Ready(fn)的返回对象。 可见这个$(fn)返回是$(document)的对象。抛弃了第一次生成的 $(fn)对象。 Array 第一参数是除上面提到 Dom 元素,函数,string 所有其它的类 型。可以为空如$()。第二个参数不用。 语句:return this.setArray(jQuery.makeArray(selector)); 它首先是把第一个参数转换数组。Selector 可以是 Array-like 的 集合,如 jQuery 对象,如 getElementsByTag 返回的 Dom 元素集合 等,可能支持$(this)。Selector 还可能是单个任意的对象。 转换成标准的数组之后,执行 this.setArray 把这个数组中的元素 全部存到当前 jquery 对象的集合中。之后返回当前的 jquery 对象。 其实 Dom Element 完全可能综合在这里面,单独拿起来为了提 高性能。 从上面的代码和上表中,我们也可以看出构建 jquery 对象就是往 jquery 对象 的集合中添加元素(一般都应该是 dom 元素)。添加的元素有两种形式: 一是单个元素,可能通过直接的 dom 元素的传参形式,还可以通过#id 从 dom 文档中找元素。 二是集合,如 jquery 对象,还有数组,还有通过 CSS Selector 找到的 Dom 集合等 Array-Like。 上表仅仅是分析传入的参数的类型,它是怎么做呢?在⑤处它实现 CSS1~CSS3 的兼容的 Selector 的查寻器的功能。通过 jQuery().find(selector); 来进行分析 String 并查找到符合传入的 Selector 语法的 Dom 文档树中的元素集 合。 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 在④处,它实现了把 html 的字符串转换成 Dom 元素节点的集合。这个是通 过 jQuery.clean([match[1]], context);来实现的。 在⑧处,它实现 DomReady 的 jQuery 对象的统一入口,我们可以通过$(fn) 要注册 domReady 的监听函数。所有的调用 jQuery 实现的功能代码都应该在 domReady 之后才运行。$(fn)是所有的应用开发中的功能代码的入口。它支持任 意多的$(fn)注册。其是通过 return jQuery(document)[jQuery.fn.ready ? "ready" : "load"](selector);来完成的。 找到元素之后就是构建集合了,在⑨处就是通过 this.setArray(jQuery.makeArray(selector));来构建 jquery 对象内部的集合。 3、构建 Jquery 的 Dom 元素 在 jQuery.fn.init 函数中,最终的结果是把 Dom 元素存放到 jQuery 对象中的 集合内面。根据上一节,我们可以传入单个 Dom 元素或集合直接将其存入 jQuery 对象的集合。 如果其第一个参数是 string 类型的话如#id,那么就得到 Dom 文档树去查找。 对于 html 片断的 string 类型就得生成 Dom 元素。如果再进一步分析,那些传入 Dom 元素(集)的参数从哪里来呢?它们可以通过 Dom 元素的直接或间接引用 方式得到。 这一部分首先分析如何从 html 的片断就得生成 Dom 元素,然后分析 jQuery 是如何通过直接或间接的方式在在 Dom 树中找到 dom 元素,第三就是分析基于 CSS1~CSS3 的 CSS selector。 3.1 生成 Dom 元素 jQuery.fn.init 函数中通过 jQuery.clean([match[1]], context);来实现把 html 片断 转换成 Dom 元素,这是一个静态方法: // 把html转换成Dom元素,elems多个html string 的数组 clean : function(elems, context) { var ret = []; context = context || document;//默认的上下文是document //在IE中!context.createElement行不通,因为它返回对象类型 if (typeof context.createElement == 'undefined') //这里支持context为jQuery对象,取第一个元素。 context = context.ownerDocument || context[0] 作者:prk(彭仁夔) QQ:546711211 Email:sjkjs155@126.com Blog: http://jljlpch.javaeye.com jQuery 源码分析 版权所有 && context[0].ownerDocument || document; jQuery.each(elems, function(i, elem) { // 把int 转换成string的最高效的方法 if (typeof elem == 'number')elem += ''; if (!elem) return;// 为'',undefined,false等时返回 if (typeof elem == "string") {// 转换html为Dom元素 // 修正 "XHTML"-style 标签,对于如
的形式修改为
//但是对于(abbr|br|col|img|input|link|meta|param|hr|area|embed) //不修改 。front=(<(\w+)[^>]*?) elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag) { return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area |embed)$/i)? all: front + ">";} ); // 去空格,否则indexof可能会出不能正常工作 var tags = jQuery.trim(elem).toLowerCase(), div = context.createElement("div");//在上下文中创建了一个元素
// 有些标签必须是有一些约束的,比如