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)

作者:对角另一面

读Zepto源码之Selector模块的更多相关文章

  1. 读Zepto源码之Touch模块

    大家都知道,因为历史原因,移动端上的点击事件会有 300ms 左右的延迟,Zepto 的 touch 模块解决的就是移动端点击延迟的问题,同时也提供了滑动的 swipe 事件. 读 Zepto 源码系 ...

  2. 读Zepto源码之Gesture模块

    Gesture 模块基于 IOS 上的 Gesture 事件的封装,利用 scale 属性,封装出 pinch 系列事件. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  3. 读Zepto源码之IOS3模块

    IOS3 模块是针对 IOS 的兼容模块,实现了两个常用方法的兼容,这两个方法分别是 trim 和 reduce . 读 Zepto 源码系列文章已经放到了github上,欢迎star: readin ...

  4. 读Zepto源码之Fx模块

    fx 模块为利用 CSS3 的过渡和动画的属性为 Zepto 提供了动画的功能,在 fx 模块中,只做了事件和样式浏览器前缀的补全,没有做太多的兼容.对于不支持 CSS3 过渡和动画的, Zepto ...

  5. 读Zepto源码之fx_methods模块

    fx 模块提供了 animate 动画方法,fx_methods 利用 animate 方法,提供一些常用的动画方法.所以 fx_methods 模块依赖于 fx 模块,在引入 fx_methods ...

  6. 读Zepto源码之Stack模块

    Stack 模块为 Zepto 添加了 addSelf 和 end 方法. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 ...

  7. 读Zepto源码之Form模块

    Form 模块处理的是表单提交.表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  8. 读Zepto源码之Data模块

    Zepto 的 Data 模块用来获取 DOM 节点中的 data-* 属性的数据,和储存跟 DOM 相关的数据. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading ...

  9. 读Zepto源码之Ajax模块

    Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

随机推荐

  1. 使用IntelliJ IDEA 开发Java Web项目

  2. 自定义类似于Jquery UI Selectable 的Vue指令v-selectable

    话不多说,先看效果. 其实就是一个可以按住鼠标进行一个区域内条目选择的功能,相信用过Jquery UI 的都知道这是selectable的功能,然而我们如果用Vue开发的话没有类似的插件,当然你仍然可 ...

  3. 【可视化】div背景半透明

    css实现元素半透明使用 opacity:0.x 实现背景色半透明:rgba(a,b,c,x); x为透明度0,到1

  4. Win7系统如何复制CMD命令提示符框中的内容

    Win7系统如何复制CMD命令提示符框中的内容.. Win7系统如何复制CMD命令提示符框中的内容右键命令提示符窗口的标题栏,选择属性. 选择“编辑选项”里的“快速编辑模式”,并确定: 鼠标左键按下选 ...

  5. linux source命令与export命令的区别

    shell与export命令 用户登录到Linux系统后,系统将启动一个用户shell.在这个shell中,可以使用shell命令或声明变量,也可以创建并运行shell脚本程序.运行shell脚本程序 ...

  6. [2017-08-28]Abp系列——业务异常与错误码设计及提示语的本地化

    本系列目录:Abp介绍和经验分享-目录 前言 ABP中有个异常UserFriendlyException经常被使用,但是它所在的命名空间是Abp.UI,总觉得和展现层联系过于紧密,在AppServic ...

  7. 基于dijkstra算法求地铁站最短路径以及打印出所有的路径

    拓展dijkstra算法,实现利用vector存储多条路径: #include <iostream> #include <vector> #include <stack& ...

  8. github+hexo搭建自己的博客网站(六)进阶配置(搜索引擎收录,优化你的url)

    详细的可以查看hexo博客的演示:https://saucxs.github.io/ 绑定了域名: http://www.chengxinsong.cn hexo+github博客网站源码(可以clo ...

  9. Redis集群的相关概念

    1.1 redis-cluster架构图 架构细节: (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽. (2)节点的fail是通过集群中超过半数的节 ...

  10. hdu 6194 沈阳网络赛--string string string(后缀数组)

    题目链接 Problem Description Uncle Mao is a wonderful ACMER. One day he met an easy problem, but Uncle M ...