第二章 jQuery技术解密 (三)

简介: 2.3 破解 jQuery 选择器接口 jQuery 选择器功能强大,但是用法简单,它仅仅提供了一个接口:jQuery(),也可以简写为 $() 。用法如此简单,但又具有如此强大的处理能力,使 jQuery 必然成为众人追捧的对象。

2.3 破解 jQuery 选择器接口

jQuery 选择器功能强大,但是用法简单,它仅仅提供了一个接口:jQuery(),也可以简写为 $() 。用法如此简单,但又具有如此强大的处理能力,使 jQuery 必然成为众人追捧的对象。

在上一节中,我们重点分析了 jQuery 框架的雏形,而对于选择器并没有深入分析,仅仅提供了一个简单的 DOM 元素选择作为演示,目的是方便读者理解该框架的架设思路和过程。本节将重点研究 jQuery 选择器的设计思路、实现过程和工作原理。

2.3.1 简单但很复杂的黑洞

前面说到,jQuery 提供了惟一的接口 (jQuery() 或者 $()) 使选择器与外界进行交流。那么这个对象是如何生成的呢?

jQuery 框架的基础是查询,即查询文档元素对象,因此我们可以认为 jQuery 对象就是一个选择器,并在此基础上构建和运行查询过滤器。

jQuery 查询的结果是获取 DOM 元素,这些查询到的 DOM 元素又是如何存储的呢?

根据前面的介绍,我们初步了解到它把查询的结果存储到 jQuery 对象内。由于查询的结果可能是单个元素,也可能是集合,因此,jQuery 对象内应该定义了一个集合。这个集合专门负责存放查询到的 DOM 元素。这正如 JavaScript 中的 Function 对象一样,其内部也构建了一个集合对象 Arguments ,专门负责存储函数的参数。

但是,Functiono 对象和 Arguments 是两个相互独立的概念,仅通过 arguments 属性联系在一起。也就是说 Arguments 对象并非是 Function 对象的子对象,或者是它的内部组成部分。而 jQuery 对象与查询结果的数据集合就不同了,它是完全作为 jQuery 对象的一部分而存在的。

另外,jQuery 虽然仅提供了一个入口,但是它的构建并不只局限于从 DOM 文档树中查询到 DOM 元素,DOM 元素也有可能从别的集合中转移过来的,或者是从 HTML 片断生成的等。

例如,类似下面的代码在 jQuery 应用中经常会看到。

$("div.red").css("display", "none"); // 将 class 为 red 的 div 元素隐藏显示

var width = $("div .red").width(); // 获取 div 元素下 class 为 red 的元素的宽度

var html = $(document.getElementById("wrap")).html(); // 获取 id 为 wrap 元素的 innerHTML 值

$("#wrap", document.forms[0]).css("color", "red"); // 将在第一个 form 元素下 id 为 wrap 元素的字体颜色设置为红色

$("<div>hello,world</div>").appendTo("#wrap"); // 将 HTML 字符串信息追加到 id 为 wrap 元素的末尾

在 $() 函数中可以包含选择字符串、HTML 字符串、 DOM 对象和数据集合等不同类型的参数。jQuery 是如何分辨这些参数是选择符字符串、HTML字符串、DOM对象或数据集合的呢?

为了方便读者理解这其中的奥妙,我们不妨把 jQuery 框架进行简化,先删除所有方法、函数以及逻辑代码,然后在 init() 构造器中,使用 alert() 方法获取 selector 参数的类型和信息,其代码如下。

[html] view plain copy
  1. <scripttype="text/javascript">
  2. (function(){
  3. varwindow=this;
  4. jQuery=window.jQuery=window.$=function(selector,context){
  5. returnnewjQuery.fn.init(selector,context);
  6. };
  7. jQuery.fn=jQuery.prototype={
  8. init:function(selector,context){
  9. alert(selector);
  10. }
  11. };
  12. })();
  13. window.onload=function(){
  14. $("div.red");//获取"div.red"
  15. $("div.red");//获取"div.red"
  16. $(document.getElementById("wrap"));//获取"[object]"
  17. $("#wrap",document.forms[0]);//获取"#wrap"
  18. $("<div>hello,world</div>");//获取"<div>hello,world</div>"
  19. };
  20. </script>
  21. <divid="wrap"></div>

2.3.2 盘根错节的逻辑关系

根据 jQuery 官网提供的 API 文档可知, jQuery() 提供了以下 4 种构建 jQuery 对象的方式。

  • jQuery(expression, [context])
  • jQuery(html, [ownerDocument])
  • jQuery(elements)
  • jQuery(callback)
其中 jQuery 可以使用 $ 简写。上述四种构建 jQuery 对象的方式是经常用到的。从上述参数列表可以看出,其实 jQuery 的参数可以是任意元素。例如:
$("div > p"); // 参数可以是字符串
$( $("div >p") ); // 参数可以是 jQuery 对象或者类数组 (ArrayLike) 的集合
$(document); // 参数可以是 DOM 元素
$(); // $(document) 简写
$(function(){}); // $(document).ready() 的简写
$([]); // 参数可以是数组
$({}); // 参数可以是对象
$(1); // 参数可以是数字,即把 1 存储在 jQuery 对象的数据集合中
虽然说,在上面的示例中最后 4 行代码都可以被解析,但是这些参数数据是被存储到 ArrayLike (类数组) 集合中的,而不是被转换为 DOM 元素。虽然语法不错,解析正常,但是它们无法完成实际应用,所以不建议传入非 DOM 元素的参数。
注意:jQuery 对象的方法都是针对 DOM 元素对象进行的操作,如果不清楚其使用的话,很有可能会导致错误。
下面我们就顺着 jQuery 框架的这个惟一入口,慢慢向里爬进,以窥视其中的秘密。
***** 当我们调用 jQuery() 方法时,它没有被实例化,也就是说 jQuery 类型被抛弃了,我们仅仅把它作为一个普通函数来调用,此时该方法中的 this 关键字指向的是 Window 对象,而不是 jQuery 对象,请读者务必注意。 ******
不过,当调用该方法时,会返回一个 jQuery.fn.init 类型的实例,同时,jQuery 又使用自己的原型对象覆盖了 jQuery.fn.init 类型的原型对象,所以就形成了一种错觉,很多初学者往往在这里栽了跟头。下面是 jQuery 框架中的核心代码 (节选) 。
jQuery = window.jQuery = window.$ = function(selector, context){
return new jQuery.fn.init(selector, context);
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery 对象不是通过 new jQuery 来继承其 prototype 中的方法的,而是通过 jQuery.fn.init 初始化构造器生成的。所以,为 jQuery.prototype 添加函数集也就失去了存在价值。虽然直接使用 new jQuery() 也是允许的,但是由于该函数的返回值覆盖了 new jQuery() 创建的实例对象,所以使用 new jQuery() 来构建 jQuery 对象也是无法存活的。 (---???----)
===== 总之,jQuery 对象其实就是 jQuery.fn.init 构造器创建的对象,而通过 jQuery.fn.init.prototype = jQuery.fn; 途径,再使用 jQuery 的原型对象去覆盖 jQuery.fn 的原型对象,使得 jQuery 对象的原型方法也就被继承过来,从而形成了错综复杂但又井然有序的关系。 =====

2.3.3 jQuery 构造器

jQuery.fn.init() 负责对传入参数进行分析,然后生成并返回 jQuery 对象。jQuery.fn.init() 构造器的第一个参数是必须的,如果为空,则默认为 document 。
从本质上讲,使用 jQuery 选择器 (即 jQuery.fn.init() 构造器) 构建jQuery对象,就是在this 对象上附加 DOM 元素集合。附加的方式包括以下两类。
  • 如果是单个 DOM 元素,可以直接把 DOM 元素作为数组元素传递给 this 对象,还可以通过 ID 从 DOM 文档中查询元素。
  • 如果是多个 DOM 元素,则以集合形式附加,如 jQuery 对象、数组和对象等,此时可以通过 CSS 选择器匹配到所有 DOM 元素,然后过滤,最后构建类数组的数据结构。
而 CSS 选择器,则是通过 jQuery().find(selector) 函数来完成的。通过 jQuery().find(selector) 可以分析选择器字符串,并在 DOM 文档树中查找到符合语法的元素集合。这个函数我们将在下面章节进行分析。该函数能够兼容 CSS1 ~ CSS3 选择器。
下面就从 init() 初始化构造器函数开始,来分析 jQuery 选择器是如何工作的。为了方便解释,我们先结合源代码进行讲解。
[html] view plain copy
  1. <scripttype="text/javascript">
  2. (function(){
  3. var
  4. window=this,
  5. jQuery=window.jQuery=window.$=function(selector,context){
  6. returnnewjQuery.fn.init(selector,context);
  7. },
  8. quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/;
  9. //jQuery原型对象
  10. //构造jQuery对象的入口
  11. //所有jQuery对象方法都通过jQuery原型对象来继承
  12. jQuery.fn=jQuery.prototype={
  13. //jQuery对象初始化构造器,相当于jQuery对象的类型,由该函数负责创建jQuery对象
  14. //参数说明:selector:选择器的符号,可以是任意数据类型。考虑DOM元素操作需要,该参数应该是包含DOM元素的任何数据
  15. //context:上下文,指定在文档DOM中哪个节点下开始进行查询,默认值为document
  16. init:function(selector,context){
  17. selector=selector||document;//确保selector参数存在,默认值为document
  18. //第一种情况,处理选择符为DOM元素,此时将忽略上下文,即忽略第二个参数
  19. //例如,$(document.getElementById("wrap")),jQuery(DOMElement)匹配DOM元素。
  20. //先使用selector.nodeType判断当selector为元素节点,将length设置为1,
  21. //并且赋值给context,实际上context作为init的第二个参数,
  22. //也意味着它的上下文节点就是selector该点,返回它的$(DOMElement)对象
  23. if(selector.nodeType){//存在nodeType属性,说明选择符是一个DOM元素
  24. this[0]=selector;//直接把当前参数的DOM元素存入类数组中
  25. this.length=1;//设置类数组的长度,以方便遍历访问
  26. this.context=selector;//设置上下文属性
  27. returnthis;//返回jQuery对象,即类数组对象
  28. }
  29. //如果选择符参数为字符串,则进行处理
  30. //例如,$("<div>hello,world</div>"),jQuery(html,[ownerDocument])匹配HTML字符串
  31. if(typeofselector=="string"){
  32. //使用quickExpr正则表达式匹配该选择符字符串,决定是处理HTML字符串,还是处理ID字符串
  33. //quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/
  34. //quickExpr匹配包含<>的字符串或#后跟[a-zA-Z0-9_]或-的字符串
  35. varmatch=quickExpr.exec(selector);
  36. //验证匹配的信息,任何情况下都不是#id
  37. if(match&&(match[1]||!context)){
  38. //第二种情况,处理HTML字符串,类似$(html)->$(array)
  39. if(match[1]){
  40. //selector=jQuery.clean([match[1]],context);
  41. }
  42. //第三种情况,处理ID字符串,类似$("#id")
  43. else{
  44. varelem=document.getElementById(match[3]);//获取该元素确保元素存在
  45. //处理在IE和Opera浏览器下根据name,而不是ID返回元素
  46. if(elem&&elem.id!=match[3]){
  47. //returnjQuery().find(selector);//默认调用document.find()方法
  48. }
  49. //否则将把elem作为元素参数直接调用jQuery()函数,返回jQuery对象
  50. varret=jQuery(elem||[]);
  51. ret.context=document;//设置jQuery对象的上下文属性
  52. ret.selector=selector;//设置jQuery对象的选择符属性
  53. returnret;
  54. }
  55. }else{
  56. //第四种情况,处理jQuery(expression,[context])
  57. //例如,$("div.red")的表达式字符串
  58. //returnjQuery(context).find(selector);
  59. }
  60. }//elseif(jQuery.isFunction(selector))
  61. //第五种情况,处理jQuery(callback),即$(document).ready()的简写
  62. //例如,$(function(){alert("hello,world");}),
  63. //或者$(document).ready(function(){alert("hello,world");});
  64. //returnjQuery(document).ready(selector);
  65. //确保旧的选择符能够通过
  66. if(selector.selector&&selector.context){
  67. this.selector=selector.selector;
  68. this.context=selector.context;
  69. }
  70. //第六种情况,处理类似$(elements)
  71. //returnthis.setArray(jQuery.isArray(selector)?selector:jQuery.makeArray(selector));
  72. }
  73. };
  74. })();
  75. </script>
进一步分析 init() 构造器函数的设计思路如下。
(1) 第一步,当第一个参数为 DOM 元素,则废弃第二个参数,直接把 DOM 元素存储到 jQuery 对象的集合中,返回该 jQuery 对象。
(2) 第二步,如果第一个参数是字符串,则可能存在三种情况。
  • 情况一,第一个参数是 HTML 标签字符串,第二个参数可选,则执行 selector = jQuery.clean([match[1]], context); 该语句能够把 HTML 字符串转换成 DOM 对象的数组,然后执行 Array 类型数组并返回 jQuery 对象。
  • 情况二,第一个参数是 #id 字符串,即类似 $(id),则先使用 document.getElementById() 方法获取该元素,如果没有获得元素,则设置 selector = [],转到执行 Array 类型,并返回空集合的 jQuery 对象。如果获得元素,则构建 jQuery 对象并返回。这里把 #id 单独列出,是为了提高性能。
  • 情况三,处理复杂的 CSS 选择符字符串,第二个参数是可选的。通过 return jQuery().find(selector); 语句实现。该语句先执行 jQuery(context) ,可以看出第二个参数 context 可以是任意值,也可以是集合数据。然后调用 find(selector) 找到 jQuery(context) 上下文中所有的 DOM 元素,即这些元素都满足 selector 表达式,最后构建 jQuery 对象并返回。
(3) 第三步,如果第一个参数是函数,则第二个参数可选。它是 $(document).ready(fn) 形式的简写,return jQuery(document)[jQuery.fn.ready? "ready": "load"](selector) 是其执行的代码。该语句先执行 jQuery(document) ,再通过 new jQuery.fn.init() 方式创建 jQuery 对象,此时元素为 document 。再调用这个对象的 ready() 方法,并返回当前的 jQuery 对象。
$(document).ready(fn) 是实现 domReady 的 jQuery 对象的统一入口,可以通过 $(fn) 注册 domReady 的监听函数。所有的调用 jQuery 实现功能的代码都应该在 domReady 之后才能够运行。$(fn) 是所有应用开发中的功能代码的入口,它支持任意多的 $(fn) 注册。
(4) 第四步,如果第一个参数是除 DOM 元素、函数和字符串之外的所有其他类型,也可以为空 (如$()),而第二个参数可选。调用 return this.setArray(jQuery.makeArray(selector)); 进行处理时,它先是把第一个参数转换为数组。当然这个参数可以是类数组结构的集合,如 jQuery 对象、getElementsByTag 返回的 DOM 元素集合等,可支持 $(this) 。selector 还可能是单个任意对象,转换成标准的数组之后,执行 this.setArray 把这个数组中的元素全部存储到当前的 jQuery对象集合中,并返回 jQuery 对象。
目录
相关文章
|
Web App开发 XML JSON
【jQuery入门】为JavaScript而生,简化JavaScript操作的神技术
之前我们学习了这个JSON热门技术,在之后的学习中都会多多少少的牵扯到JSON相关的知识的,好多技术中也会用到JSON,所以如果你还不知道JSON建议先去看一下我的上一篇博客。
【jQuery入门】为JavaScript而生,简化JavaScript操作的神技术
|
开发框架 JavaScript 前端开发
jQuery 已“死”?为清除技术债,我们删掉了前端所有 jQuery 依赖
近期,英国公共部门信息网站 GOV.UK 前端开发主管 Matt Hobbs 宣布该公司删除了 jQuery 作为所有前端应用程序的依赖项,这意味着“在所有 13 个 FE 应用程序中,JS 大小减少了 32 KB(31% ~49% 之间)”。
121 0
jQuery 已“死”?为清除技术债,我们删掉了前端所有 jQuery 依赖
|
设计模式 JavaScript 前端开发
尚能饭否|技术越来越新,我对老朋友jQuery还是一如既往热爱
最近在搭建完善自己的博客,需要用到一些页面样式之类的,就特意问了一下女朋友一个问题,关于Web前端开发,jQuery现在过时了嘛?她毅然决然告诉我,那是我们前端现在的鄙视链。是的,不...
111 0
|
JavaScript 前端开发
JavaScript 技术篇-js代码触发dom元素绑定事件实例演示,jquery触发元素绑定事件方法
JavaScript 技术篇-js代码触发dom元素绑定事件实例演示,jquery触发元素绑定事件方法
454 0
JavaScript 技术篇-js代码触发dom元素绑定事件实例演示,jquery触发元素绑定事件方法
|
JavaScript 前端开发
JavaScript 技术篇-本地js文件里直接集成jQuery的方法,js文件不依赖html外部引用直接使用jquery方法
JavaScript 技术篇-本地js文件里直接集成jQuery的方法,js文件不依赖html外部引用直接使用jquery方法
160 0
JavaScript 技术篇-本地js文件里直接集成jQuery的方法,js文件不依赖html外部引用直接使用jquery方法
|
Web App开发 JavaScript 前端开发
Jquery 图片延迟加载技术
参考网址:http://code.ciaoca.com/jquery/lazyload/ 延迟加载能大大增加你网站的加载速度! 需要引入以下文件: $('img').lazyload(); 这些JQ文件的下载地址:http://code.ciaoca.com/jquery/lazyload/ 官方网址:http://appelsiini.net/projects/lazyload/ 建议将这些JQ文件引用放在HTML 文档的最下面,这句话很重要。
1492 0
|
前端开发 JavaScript .NET
一起谈.NET技术,跟ASP.NET MVC一起使用jQuery
  藉由ASP.NET MVC内置的扩展性,开发人员便可以使用第三方库,例如jQuery。在使用ASP.NET Webforms的时候,如果使用jQuery而不是ASP.NET AJAX,难度会比较大。
1078 0
|
JSON JavaScript 前端开发
前后台交互经常使用的技术汇总(后台:Java技术,前台:Js或者Jquery)
1:由于针对特定的前后台交互用到的知识总结,所以不大量贴代码,主要给出思路,方便自己以后脑补和技术总结,当然也希望可以帮助到别人。 后台Json和其他格式转化,之前总结过Json和对象,集合,字符串的转化或者互相转化,这里我想网上有很多demo。
2009 0