1.选择器结构


jQuery的选择器根据源码可以分为几块

          init: function( selector, context, rootjQuery ) {
            ...
// HANDLE: $(""), $(null), $(undefined), $(false)
...
// Handle HTML strings
if ( typeof selector === "string" ) {
  ...
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
  ...
// HANDLE: $(function)
} else if ( jQuery.isFunction( selector ) ) {
  ...
} //HANDLE: $($...)
if ( selector.selector !== undefined ) {
  ...
} return jQuery.makeArray( selector, this );
}

可以看到,jQuery接受的参数方式也就这么几个(""/null/undefined/false)、(string, context, rootjQuery)、(DOMElement)、(function)、($...)。除了第一个参数是string的情况比较复杂以外,其他情况都比较简单。

$(""/null/undefined/false)直接返回即可

  // HANDLE: $(""), $(null), $(undefined), $(false)
  if ( !selector ) {
    return this;
  }

$(DOMElement)将DOM对象转化为伪数组返回即可  

  // HANDLE: $(DOMElement)
  else if ( selector.nodeType ) {
    this.context = this[0] = selector;
    this.length = 1;
    return this;
  }

$(function)等同于jQuery.ready(function)

  // HANDLE: $(function)
  } else if ( jQuery.isFunction( selector ) ) {
    return rootjQuery.ready( selector );
  }

$($...)将参数执行结果(也是一个jQuery实例)包装到this上返回

  //HANDLE: $($...)
  if ( selector.selector !== undefined ) {
    this.selector = selector.selector;
    this.context = selector.context;
  }   return jQuery.makeArray( selector, this );

接下来重点:当jQuery传递的第一个参数是字符串的时候

2. jQuery选择器核心—选择器是字符串$(String[,xxx])


首先必须要必备的几个正则知识:


test方法:

  RegExpObject.test(string)用于检测一个字符串是否匹配某个模式。

  如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。

match方法:

  stringObject.match(searchvalue | regexp)可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。

  match方法将检索字符串 stringObject,以找到一个或多个与 regexp 匹配的文本。这个方法的行为在很大程度上有赖于 regexp 是否具有标志 g。

  如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。

  如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。

  注意:在全局检索模式下,match() 即不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用 RegExp.exec()。

exec方法:

  RegExpObject.exec(string) 返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。

  此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。input 属性则存放的是被检索的字符串 string。我们可以看得出,在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的。

  但是,当 RegExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。

  重要事项:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0。

  提示:请注意,无论 RegExpObject 是否是全局模式,exec() 都会把完整的细节添加到它返回的数组中。这就是 exec() 与 String.match() 的不同之处,后者在全局模式下返回的信息要少得多。因此我们可以这么说,在循环中反复地调用 exec() 方法是唯一一种获得全局模式的完整模式匹配信息的方法。

  eg:

  var str = "Visit W3School sfsffs test W3School W3School";
  var patt = new RegExp(/(W3)(School)/);
  var result = patt.exec(str);// ["W3School", "W3", "School"]
  result.index;//
  result.input;// "Visit W3School sfsffs test W3School W3School"   patt = new RegExp(/(W3)(School)/g);
  result = patt.exec(str);// ["W3School", "W3", "School"]
  result.index;//
  patt.lastIndex;//
  result = patt.exec(str);// ["W3School", "W3", "School"]
  result.index;//
  patt.lastIndex;//

jQuery选择器第一个参数为字符串的源码分析


注:stringObject.charAt(index)方法可返回指定位置的字符

先整体浏览源码在细细分析

  var match, elem;
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
    // 匹配html标签
    match = [ null, selector, null ];
  } else {
    //rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,
    //匹配html标签+字符串(可以是空字符)或#+字符串(可以是空字符)
    match = rquickExpr.exec( selector );
  }   //匹配的html或确保没有为选择器为#id的情况下指定上下文
  //因为当选择器为#id的情况下,match = ["#id", undefined, "id"],match[1]是undefined
  if ( match && (match[1] || !context) ) {
    // HANDLE: $(html) -> $(array);选择器是html字符串,将字符串解析成DOM元素数组返回
    if ( match[1] ) {
      ...
    }
    // HANDLE: $(#id);选择器是ID
    else {
      ...
    }   // HANDLE: $(expr, $(...));选择器的context没有或是jQuery对象的情况,比如$("p")、$("p",$(".test")):查找$(".test")下的p标签元素
  } else if ( !context || context.jquery ) {
    return ( context || rootjQuery ).find( selector );
  // HANDLE: $(expr, context);选择器的context为真实的上下文环境,比如$("p",".test"):查找class .test下的p标签元素,等价于$(context).find(expr)
  } else {
    return this.constructor( context ).find( selector );
  }

其中使用到find函数我们会在后面去解析他,现在我们只需要知道他是用来查找下级节点即可。接下来主要分析选择器为id和html字符串的情况

a.选择器为html字符串


首先将html字符串解析成DOM节点集合,然后将它和当前jQuery实例合并

  jQuery.merge( this, jQuery.parseHTML(
    match[1],
    context && context.nodeType ? context.ownerDocument || context : document,
    true
  ) );

  比如:$("<span><p></p></span><div></div>")合并结果为

  

然后判断html字符串是否是一个单独的标签并且后一个参数是否是对象(props对象)。如果满足条件,将对象的属性(property)添加到标签属性(attribute)上。特别需要注意的是如果props对象的某个属性名称(假设为fncName)是刚才搜集的DOM集合对象(实际上就是jQuery对象)的函数名的情况下,将props对象的fncName属性的值作为参数传递给DOM集合对象中的fncName函数并执行之

  //rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/
  // HANDLE: $(html, props)
  if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
    for ( match in context ) {
      // context的属性是函数的话执行之
      if ( jQuery.isFunction( this[ match ] ) ) {
        this[ match ]( context[ match ] );       //其他情况设置t as attributes
      } else {
        this.attr( match, context[ match ] );
      }
    }
  }

最终返回处理过的DOM集合对象

  return this;

完整源码

  // HANDLE: $(html) -> $(array)

  if ( match[1] ) {
    context = context instanceof jQuery ? context[0] : context;     jQuery.merge( this, jQuery.parseHTML(
      match[1],
      context && context.nodeType ? context.ownerDocument || context : document,
      true
    ) );     // HANDLE: $(html, props)
    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
      for ( match in context ) {
        if ( jQuery.isFunction( this[ match ] ) ) {
          this[ match ]( context[ match ] );         }else {
          this.attr( match, context[ match ] );
        }
      }
    }
    return this;
  }

b.选择器为ID


如果选择器为ID,则直接使用原生的document.getElementById直接获取即可。

唯一需要做的是兼容为难题。Blackberry 4.6返回的节点可能已经不再DOM上了(缓存导致),所以需要判断该节点是否存在并且其父节点也存在;

  if ( elem && elem.parentNode )

IE和Opera有时候会使用name属性代替ID属性,返回的结果可能不是我们期望的,这个时候需要做兼容。使用find查找来替代

  if ( elem.id !== match[2] ) {
    return rootjQuery.find( selector );
  }

将兼容后得到的节点元素添加到this上并返回。

完整源码:

  // HANDLE: $(#id)

  else {
    elem = document.getElementById( match[2] );     // Blackberry 4.6 返回的节点可能已经不再文档中#6963
    if ( elem && elem.parentNode ) {
      // IE 和Opera有时使用name替代ID去查询导致结果不对的处理
      if ( elem.id !== match[2] ) {
        return rootjQuery.find( selector );
      }       this.length = 1;
      this[0] = elem;
    }     this.context = document;
    this.selector = selector;
    return this;
  }

  如果觉得本文不错,请点击右下方【推荐】!

jQuery-1.9.1源码分析系列(二)jQuery选择器的更多相关文章

  1. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  5. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  6. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  7. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  8. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  9. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  10. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

随机推荐

  1. 移动端click时间延迟300

    解决移动端click延迟事件方法,,引入fastclick.js  然后在script标签里面写上FastClick.attach(document.body); <!DOCTYPE html& ...

  2. ASP.NET TextBox 当鼠标点击后清空默认提示文字

    ASP.NET TextBox 当鼠标点击后清空默认提示文字 [ 方法一] 前台代码: <div>    <asp:TextBox ID="txtName" ru ...

  3. Call for Papers IEEE/ACM International Conference on Advances in Social Network Analysis and Mining (ASONAM)

    IEEE/ACM International Conference on Advances in Social Network Analysis and Mining (ASONAM) 2014 In ...

  4. 禁用nested loop join里的spool

    禁用nested loop join里的spool 转载自: https://blogs.msdn.microsoft.com/psssql/2015/12/15/spool-operator-and ...

  5. Change the Target Recovery Time of a Database (SQL Server) 间接-checkpoints flushcache flushcache-message

    Change the Target Recovery Time of a Database (SQL Server) 间接checkpoints   flushcache flushcache-mes ...

  6. CSP的今世与未来

    一.从两个工具说起 最近Google又推出了两款有关CSP利用的小工具,其一为CSP Evaluator,这是一个能够评估你当前输入的CSP能否帮助你有效避免XSS攻击的工具,其用法非常简单,在输入框 ...

  7. 在.NET Core中遭遇循环依赖问题"A circular dependency was detected"

    今天在将一个项目迁移至ASP.NET Core的过程中遭遇一个循环依赖问题,错误信息如下: A circular dependency was detected for the service of ...

  8. 玩转Asp.net MVC 的八个扩展点

    MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去 ...

  9. .NET WEB程序员需要掌握的技能

    本来这个是我给我们公司入职的新人做一个参考,由于 @张善友 老师在他的微信号转了我的这篇文章<<.Net WEB 程序员需要掌握的技能>>,很多人觉得比较有用,说是看了后知道一 ...

  10. Linux RAID卡优化

    200 ? "200px" : this.width)!important;} --> 介绍 我们的生产服务器经常会做raid存储,但是单单做了raid就能保证性能高效和数据 ...