读Zepto源码之Selector模块

Selector 模块是对 Zepto 选择器的扩展,使得 Zepto 选择器也可以支持部分 CSS3 选择器和 eqZepto 定义的选择器。

在阅读本篇文章之前,最好先阅读《读Zepto源码之神奇的$》。

读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto

源码版本

本文阅读的源码为 zepto1.2.0

GitBook

reading-zepto

辅助方法

visible

function visible(elem){
  elem = $(elem)
  return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
}

判断元素是否可见。

可见的标准是元素有宽或者高,并且 display 值不为 none

filters

var filters = $.expr[':'] = {
  visible:  function(){ if (visible(this)) return this },
  hidden:   function(){ if (!visible(this)) return this },
  selected: function(){ if (this.selected) return this },
  checked:  function(){ if (this.checked) return this },
  parent:   function(){ return this.parentNode },
  first:    function(idx){ if (idx === 0) return this },
  last:     function(idx, nodes){ if (idx === nodes.length - 1) return this },
  eq:       function(idx, _, value){ if (idx === value) return this },
  contains: function(idx, _, text){ if ($(this).text().indexOf(text) > -1) return this },
  has:      function(idx, _, sel){ if (zepto.qsa(this, sel).length) return this }
}

定义了一系列的过滤函数,返回符合条件的元素。这些过滤函数会将集合中符合条件的元素过滤出来,是实现相关选择器的核心。

  • visible: 过滤可见元素,匹配 el:visible 选择器
  • hidden: 过滤不可见元素, 匹配 el:hidden 选择器
  • selected: 过滤选中的元素,匹配 el:selected 选择器
  • checked: 过滤勾选中的元素,匹配 el:checked 选择器
  • parent: 返回至少包含一个子元素的元素,匹配 el:parent 选择器
  • first: 返回第一个元素,匹配 el:first 选择器
  • last: 返回最后一个元素,匹配 el:last 选择器
  • eq: 返回指定索引的元素,匹配 el:eq(index) 选择器
  • contains: 返回包含指定文本的元素,匹配 el:contains(text)
  • has: 返回匹配指定选择器的元素,匹配 el:has(sel)

process

var filterRe = new RegExp('(.*):(\\w+)(?:\\(([^)]+)\\))?$\\s*'),
function process(sel, fn) {
  sel = sel.replace(/=#\]/g, '="#"]')
  var filter, arg, match = filterRe.exec(sel)
  if (match && match[2] in filters) {
    filter = filters[match[2]], arg = match[3]
    sel = match[1]
    if (arg) {
      var num = Number(arg)
      if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
      else arg = num
    }
  }
  return fn(sel, filter, arg)
}

process 方法是根据参数 sel,分解出选择器、伪类名和伪类参数(如 eqhas 的参数),根据伪类来选择对应的 filter ,传递给回调函数 fn

分解参数最主要靠的是 filterRe 这条正则,正则太过复杂,很难解释,不过用正则可视化网站 regexper.com,可以很清晰地看到,正则分成三大组,第一组匹配的是 : 前面的选择器,第二组匹配的是伪类名,第三组匹配的是伪类参数。

sel = sel.replace(/=#\]/g, '="#"]')

这段是处理 a[href^=#] 的情况,其实就是将 # 包在 "" 里面,以符合标准的属性选择器,这是 Zepto 的容错能力。 这个选择器不会匹配到 filters 上的过滤函数,最后调用的是 querySelectorAll 方法,具体见《读Zepto源码之神奇的$》对 qsa 函数的分析。

if (match && match[2] in filters) {
  filter = filters[match[2]], arg = match[3]
  sel = match[1]
  ...
}

match[2] 也即第二组匹配的是伪类名,也是对应 filters 中的 key 值,伪类名存在于 filters 中时,则将选择器,伪类名和伪类参数存入对应的变量。

if (arg) {
  var num = Number(arg)
  if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
  else arg = num
}

如果伪类的参数不可以用 Number 转换,则参数为字符串,用正则将字符串前后的 "' 去掉,再赋值给 arg.

return fn(sel, filter, arg)

最后执行回调,将解释出来的参数传入回调函数中,将执行结果返回。

重写的方法

qsa

var zepto = $.zepto, oldQsa = zepto.qsa, oldMatches = zepto.matches,
    childRe  = /^\s*>/,
    classTag = 'Zepto' + (+new Date())

zepto.qsa = function(node, selector) {
  return process(selector, function(sel, filter, arg){
    try {
      var taggedParent
      if (!sel && filter) sel = '*'
      else if (childRe.test(sel))
        taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel

      var nodes = oldQsa(node, sel)
      } catch(e) {
        console.error('error performing selector: %o', selector)
        throw e
      } finally {
        if (taggedParent) taggedParent.removeClass(classTag)
      }
    return !filter ? nodes :
    zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) }))
  })
}

改过的 qsa 调用的是 process 方法,在回调函数中处理大部分逻辑。

思路是通过选择器获取到所有节点,然后再调用对应伪类的对应方法来过滤出符合条件的节点。

处理选择器,根据选择器获取节点

var taggedParent
if (!sel && filter) sel = '*'
else if (childRe.test(sel))
  taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel

var nodes = oldQsa(node, sel)

如果选择器和过滤器都不存在,则将 sel 设置 * ,即获取所有元素。

如果 >sel 形式的选择器,查找所有子元素。

这里的做法是,向元素 node 中添加唯一的样式名 classTag,然后用唯一样式名和选择器拼接成子元素选择器。

最后调用原有的 qsa 函数 oldQsa 来获取符合选择器的所有元素。

清理所添加的样式

if (taggedParent) taggedParent.removeClass(classTag)

如果存在 taggedParent ,则将元素上的 classTag 清理掉。

调用对应的过滤器,过滤元素

return !filter ? nodes :
    zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) }))

如果没有过滤器,则将所有元素返回,如果存在过滤器,则遍历集合,调用对应的过滤器获取元素,并将新集合的元素去重。

matches

zepto.matches = function(node, selector){
  return process(selector, function(sel, filter, arg){
    return (!sel || oldMatches(node, sel)) &&
      (!filter || filter.call(node, null, arg) === node)
  })
}

matches 也是调用 process 方法,这里很巧妙地用了 ||&& 的短路操作。

其实要做的事情就是,如果可以用 oldMatches 匹配,则使用 oldMatches 匹配的结果,否则使用过滤器过滤出来的结果。

系列文章

  1. 读Zepto源码之代码结构
  2. 读Zepto源码之内部方法
  3. 读Zepto源码之工具函数
  4. 读Zepto源码之神奇的$
  5. 读Zepto源码之集合操作
  6. 读Zepto源码之集合元素查找
  7. 读Zepto源码之操作DOM
  8. 读Zepto源码之样式操作
  9. 读Zepto源码之属性操作
  10. 读Zepto源码之Event模块
  11. 读Zepto源码之IE模块
  12. 读Zepto源码之Callbacks模块
  13. 读Zepto源码之Deferred模块
  14. 读Zepto源码之Ajax模块
  15. 读Zepto源码之assets模块

参考

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 这篇依然是跟 dom 相关的方法,侧重点是跟集合元素查找相关的方法。 读Zepto源码系列文章已经放到了githu...
    对角另一面阅读 278评论 0 0
  • Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest ...
    对角另一面阅读 603评论 0 1
  • Form 模块处理的是表单提交。表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提...
    对角另一面阅读 532评论 0 0
  • 艺艺说要和我谈谈的时候,我就猜到了她肯定要和我谈军营的事。艺艺是个拥军姑娘,我义务兵的时候认识她,转眼我都第四年了...
    渠六亿阅读 665评论 3 4
  • 每天日子在有条不紊地进行着…学业,是我觉得能体验到我还是个学生的东西,但是,大学绝不仅仅是学习,有社团,有很多其他...
    CYF_BY阅读 166评论 0 0