读 Zepto 源码之集合元素查找
这篇依然是跟 dom
相关的方法,侧重点是跟集合元素查找相关的方法。
读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto
源码版本
本文阅读的源码为 zepto1.2.0
内部方法
之前有一章《读Zepto源码之内部方法》是专门解读 zepto
中没有提供给外部使用的内部方法的,但是有几个涉及到 dom
的方法没有解读,这里先将本章用到的方法解读一下。
matches
zepto.matches = function(element, selector) {
if (!selector || !element || element.nodeType !== 1) return false
var matchesSelector = element.matches || element.webkitMatchesSelector ||
element.mozMatchesSelector || element.oMatchesSelector ||
element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
// fall back to performing a selector:
var match, parent = element.parentNode,
temp = !parent
if (temp)(parent = tempParent).appendChild(element)
match = ~zepto.qsa(parent, selector).indexOf(element)
temp && tempParent.removeChild(element)
return match
}
matches
方法用于检测元素( element
)是否匹配特定的选择器( selector
)。
浏览器也有原生的 matches
方法,但是要到IE9之后才支持。具体见文档:Element.matches()
if (!selector || !element || element.nodeType !== 1) return false
这段是确保 selector
和 element
两个参数都有传递,并且 element
参数的 nodeType
为 ELEMENT_NODE
,如何条件不符合,返回 false
var matchesSelector = element.matches || element.webkitMatchesSelector ||
element.mozMatchesSelector || element.oMatchesSelector ||
element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
这段是检测浏览器是否原生支持 matches
方法,或者支持带私有前缀的 matches
方法,如果支持,调用原生的 matches
,并将结果返回。
var match, parent = element.parentNode,
temp = !parent
if (temp)(parent = tempParent).appendChild(element)
match = ~zepto.qsa(parent, selector).indexOf(element)
temp && tempParent.removeChild(element)
return match
如果原生的方法不支持,则回退到用选择器的方法来检测。
这里定义了三个变量,其中 parent
用来存放 element
的父节点, temp
用来判断 element
是否有父元素。值为 temp = !parent
,如果 element
存在父元素,则 temp
的值为 false
。
首先判断是否存在父元素,如果父元素不存在,则 parent = tempParent
,tempParent
已经由一个全局变量来定义,为 tempParent = document.createElement('div')
,其实就是一个 div
空节点。然后将 element
插入到空节点中。
然后,查找 parent
中所有符合选择器 selector
的元素集合,再找出当前元素 element
在集合中的索引。
zepto.qsa(parent, selector).indexOf(element)
再对索引进行取反操作,这样索引值为 0
的值就变成了 -1
,是 -1
的返回的是 0
,这样就确保了跟 matches
的表现一致。
其实我有点不太懂的是,为什么不跟原生一样,返回 boolean
类型的值呢?明明通过 zepto.qsa(parent, selector).indexOf(element) > -1
就可以做到了,接口表现一致不是更好吗?
最后还有一步清理操作:
temp && tempParent.removeChild(element)
将空接点的子元素清理点,避免污染。
children
function children(element) {
return 'children' in element ?
slice.call(element.children) :
$.map(element.childNodes, function(node) { if (node.nodeType == 1) return node })
}
children
方法返回的是 element
的子元素集合。
浏览器也有原生支持元素 children
属性,也要到IE9以上才支持,见文档ParentNode.children
如果检测到浏览器不支持,则降级用 $.map
方法,获取 element
的 childNodes
中 nodeType
为 ELEMENT_NODE
的节点。因为 children
返回的只是元素节点,但是 childNodes
返回的除元素节点外,还包含文本节点、属性等。
这里用到的 $.map
跟数组的原生方法 map
表现有区别,关于 $.map
的具体实现,已经在《读zepto源码之工具函数》解读过了。
filtered
function filtered(nodes, selector) {
return selector == null ? $(nodes) : $(nodes).filter(selector)
}
将匹配指定选择器的元素从集合中过滤出来。
如果没有指定 selector
,则将集合包裹成 zepto
对象全部返回,否则调用 filter
方法,过滤出符合条件的元素返回。filter
方法下面马上讲到。
元素方法
这里的方法都是 $.fn
中提供的方法。
.filter()
filter: function(selector) {
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element) {
return zepto.matches(element, selector)
}))
}
filter
是查找符合条件的元素集合。
参数 selector
可以为 Function
或者选择器,当为 Function
时,调用的其实调用了两次 not
方法,负负得正。关于 not
方法,下面马上会看到。
当为一般的选择器时,调用的是filter
方法,filter
的回调函数调用了 matches
,将符合 selector
的元素返回,并包装成 zepto
对象返回。
.not()
not: function(selector) {
var nodes = []
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx) {
if (!selector.call(this, idx)) nodes.push(this)
})
else {
var excludes = typeof selector == 'string' ? this.filter(selector) :
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
this.forEach(function(el) {
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
return $(nodes)
}
not
方法是将集合中不符合条件的元素查找出来。
not
方法的方法有三种调用方式:
not(selector) ⇒ collection
not(collection) ⇒ collection
not(function(index){ ... }) ⇒ collection
当 selector
为 Function
,并且有 call
方法时(isFunction(selector) && selector.call !== undefined
),相关的代码如下:
this.each(function(idx) {
if (!selector.call(this, idx)) nodes.push(this)
})
调用 each
方法,并且在 selector
函数中,可以访问到当前的元素和元素的索引。如果 selector
函数的值取反后为 true
,则将相应的元素放入 nodes
数组中。
当 selector
不为 Function
时, 定义了一个变量 excludes
,这个变量来用接收需要排除的元素集合。接下来又是一串三元表达式(zepto的特色啊)
typeof selector == 'string' ? this.filter(selector)
当 selector
为 string
时,调用 filter
,找出所有需要排除的元素
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
这段我刚开始看时,有点困惑,主要是不明白 isFunction(selector.item)
这个判断条件,后来查了MDN文档HTMLCollection.item,才明白 item
是 HTMLCollection
的一个方法,这个三元表达式的意思是,如果是 HTMLCollection
,则调用 slice.call
得到一个纯数组,否则返回 zepto
对象。
this.forEach(function(el) {
if (excludes.indexOf(el) < 0) nodes.push(el)
})
遍历集合,如果元素不在需要排除的元素集合中,将该元素 push
进 nodes
中。
not
方法最终返回的也是 zepto
对象。
.is()
is: function(selector) {
return this.length > 0 && zepto.matches(this[0], selector)
}
判断集合中的第一个元素是否匹配指定的选择器。
代码也比较简单了,选判断集合不为空,再调用 matches
看第一个元素是否匹配。
.find()
find: function(selector) {
var result, $this = this
if (!selector) result = $()
else if (typeof selector == 'object')
result = $(selector).filter(function() {
var node = this
return emptyArray.some.call($this, function(parent) {
return $.contains(parent, node)
})
})
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
else result = this.map(function() { return zepto.qsa(this, selector) })
return result
}
find
是查找集合中符合选择器的所有后代元素,如果给定的是 zepto
对象或者 dom
元素,则只有他们在当前的集合中时,才返回。
fid
有三种调用方式,如下:
find(selector) ⇒ collection
find(collection) ⇒ collection
find(element) ⇒ collection
if (!selector) result = $()
如果不传参时,返回的是空的 zepto
对象。
else if (typeof selector == 'object')
result = $(selector).filter(function() {
var node = this
return emptyArray.some.call($this, function(parent) {
return $.contains(parent, node)
})
})
如果传参为 object
时,也就是 zepto
对象collection
和dom
节点 element
时,先将 selector
包裹成 zepto
对象,然后对这个对象过滤,返回当前集合子节点中所包含的元素($.contains(parent, node)
)。
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
如果当前的集合只有一个元素时,直接调用 zepto.qsa
方法,取出集合的第一个元素 this[0]
作为 qsa
的第一个参数。关于 qsa
方法,已经在《读Zepto源码之神奇的$》分析过了。其实就是获取第一个元素的所有后代元素。
else result = this.map(function() { return zepto.qsa(this, selector) })
否则,调用 map
方法,对集合中每个元素都调用 qsa
方法,获取所有元素的后代元素。这个条件其实可以与上一个条件合并的,分开应该是为了性能的考量。
.has()
has: function(selector) {
return this.filter(function() {
return isObject(selector) ?
$.contains(this, selector) :
$(this).find(selector).size()
})
},
判断集合中是否有包含指定条件的子元素,将符合条件的元素返回。
有两种调用方式
has(selector) ⇒ collection
has(node) ⇒ collection
参数可以为选择器或者节点。
has
其实调用的是 filter
方法,这个方法上面已经解读过了。filter
的回调函数中根据参数的不同情况,调用了不同的方法。
isObject(selector)
用来判断 selector
是否为 node
节点,如果为 node
节点,则调用 $.contains
方法,该方法已经在《读Zepto源码之工具函数》说过了。
如果为选择器,则调用 find
方法,然后再调用 size
方法,size
方法返回的是集合中元素的个数。这个在《读Zepto源码之集合操作》有讲过,如果集合个数大于零,则表示满足条件。
.eq()
eq: function(idx) {
return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1)
},
获取集合中指定的元素。
这里调用了 slice
方法,这个方法在上一篇《读Zepto源码之集合操作》已经说过了。如果 idx
为 -1
时,直接调用 this.slice(idx)
,即取出最后一个元素,否则取 idx
至 idx + 1
之间的元素,也就是每次只取一个元素。+idx+1
前面的 +
号其实是类型转换,确保 idx
在做加法的时候为 Number
类型。
.first()
first: function() {
var el = this[0]
return el && !isObject(el) ? el : $(el)
},
first
是取集合中第一个元素,这个方法很简单,用索引 0
就可以取出来了,也就是 this[0]
。
el && !isObject(el)
用来判断是否为 zepto
对象,如果不是,用 $(el)
包裹,确保返回的是 zepto
对象。
.last()
last: function() {
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
last
是取集合中最后一个元素,这个的原理跟 first
一样,只不过变成了取索引值为 this.length - 1
,也就是最后的元素。
.closest()
closest: function(selector, context) {
var nodes = [],
collection = typeof selector == 'object' && $(selector)
this.each(function(_, node) {
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
node = node !== context && !isDocument(node) && node.parentNode
if (node && nodes.indexOf(node) < 0) nodes.push(node)
})
return $(nodes)
},
从元素本身向上查找,返回最先符合条件的元素。
这个方法也有三种调用方式
closest(selector, [context]) ⇒ collection
closest(collection) ⇒ collection
closest(element) ⇒ collection
如果指定了 zepto
集合或者 element
,则只返回匹配给定集合或 element
的元素。
collection = typeof selector == 'object' && $(selector)
这段是判断 selector
是否为 collection
或 element
,如果是,则统一转化为 zepto
集合。
然后对集合遍历,在 each
遍历里针对集合中每个 node
节点,都用 while
语句,向上查找符合条件的元素。
node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))
这段是 while
语句的终止条件。 node
节点必须存在,如果 selector
为 zepto
集合或者 element
,也即 collection
存在, 则要找到存在于 collection
中的节点(collection.indexOf(node) >= 0
), 否则,节点要匹配指定的选择器(zepto.matches(node, selector)
)
在 while
循环中,是向上逐级查找节点的过程:
node = node !== context && !isDocument(node) && node.parentNode
当前 node
不为指定的上下文 context
并且不为 document
节点时,向上查找(node.parentNode
)
if (node && nodes.indexOf(node) < 0) nodes.push(node)
while
循环完毕后,如果 node
节点存在,并且 nodes
中还不存在 node
,则将 node
push 进 nodes
中。
最后返回 zepto
集合。
.pluck()
pluck: function(property) {
return $.map(this, function(el) { return el[property] })
},
返回集合中所有元素指定的属性值。
这个方法很简单,就是对当前集合遍历,然后取元素指定的 property
值。
.parents()
parents: function(selector) {
var ancestors = [],
nodes = this
while (nodes.length > 0)
nodes = $.map(nodes, function(node) {
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node)
return node
}
})
return filtered(ancestors, selector)
},
返回集合中所有元素的所有祖先元素。
nodes
的初始值为当前集合,while
循环的条件为集合不为空。
使用 map
遍历 nodes
,将 node
重新赋值为自身的父级元素,如果父级元素存在,并且不是 document
元素,而且还不存在于 ancestors
中时,将 node
存入保存祖先元素的 ancestors
中,并且 map
回调的返回值是 node
,组成新的集合赋值给 nodes
,直到所有的祖先元素遍历完毕,就可以退出 while
循环。
最后,调用上面说到的 filtered
方法,找到符合 selector
的祖先元素。
.parent()
parent: function(selector) {
return filtered(uniq(this.pluck('parentNode')), selector)
},
返回集合中所有元素的父级元素。
parents
返回的是所有祖先元素,而 parent
返回只是父级元素。
首先调用的是 this.pluck('parentNode')
,获取所有元素的祖先元素,然后调用 uniq
对集合去重,最后调用 filtered
,返回匹配 selector
的元素集合。
.children()
children: function(selector) {
return filtered(this.map(function() { return children(this) }), selector)
},
返回集合中所有元素的子元素。
首先对当前集合遍历,调用内部方法 children
获取当前元素的子元素组成新的数组,再调用 filtered
方法返回匹配 selector
的元素集合。
.contents()
contents: function() {
return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
},
这个方法类似于 children
,不过 children
对 childNodes
进行了过滤,只返回元素节点。contents
还返回文本节点和注释节点。也返回 iframe
的 contentDocument
.siblings()
siblings: function(selector) {
return filtered(this.map(function(i, el) {
return filter.call(children(el.parentNode), function(child) { return child !== el })
}), selector)
},
获取所有集合中所有元素的兄弟节点。
获取兄弟节点的思路也很简单,对当前集合遍历,找到当前元素的父元素el.parentNode
,调用 children
方法,找出父元素的子元素,将子元素中与当前元素不相等的元素过滤出来即是其兄弟元素了。
最后调用 filtered
来过滤出匹配 selector
的兄弟元素。
.prev()
prev: function(selector) { return $(this.pluck('previousElementSibling')).filter(selector || '*') },
获取集合中每个元素的前一个兄弟节点。
这个方法也很简单,调用 pluck
方法,获取元素的 previousElementSibling
属性,即为元素的前一个兄弟节点。再调用 filter
返回匹配 selector
的元素,最后包裹成 zepto
对象返回
.next()
next: function(selector) { return $(this.pluck('nextElementSibling')).filter(selector || '*') },
next
方法跟 prev
方法类似,只不过取的是 nextElementSibling
属性,获取的是每个元素的下一个兄弟节点。
.index()
index: function(element) {
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},
返回指定元素在当前集合中的位置(this.indexOf($(element)[0])
),如果没有给出 element
,则返回当前鲜红在兄弟元素中的位置。this.parent().children()
查找的是兄弟元素。
系列文章
参考
License
作者:对角另一面
读 Zepto 源码之集合元素查找的更多相关文章
- 读Zepto源码之集合操作
接下来几个篇章,都会解读 zepto 中的跟 dom 相关的方法,也即源码 $.fn 对象中的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码 ...
- 读Zepto源码之操作DOM
这篇依然是跟 dom 相关的方法,侧重点是操作 dom 的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1 ...
- 读Zepto源码之样式操作
这篇依然是跟 dom 相关的方法,侧重点是操作样式的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1.2. ...
- 读Zepto源码之属性操作
这篇依然是跟 dom 相关的方法,侧重点是操作属性的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1.2. ...
- 读Zepto源码之Event模块
Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...
- 读Zepto源码之Callbacks模块
Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的 promise 风格提供支持,接下来很快就会分析到 ...
- 读Zepto源码之Deferred模块
Deferred 模块也不是必备的模块,但是 ajax 模块中,要用到 promise 风格,必需引入 Deferred 模块.Deferred 也用到了上一篇文章<读Zepto源码之Callb ...
- 读Zepto源码之Ajax模块
Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...
- 读Zepto源码之Selector模块
Selector 模块是对 Zepto 选择器的扩展,使得 Zepto 选择器也可以支持部分 CSS3 选择器和 eq 等 Zepto 定义的选择器. 在阅读本篇文章之前,最好先阅读<读Zept ...
随机推荐
- dd命令的使用简介
dd命令: convert and copy a file 用法: dd if=/PATH/FROM/SRC of=/PATH/TO/DEST bs=#: block size, 复制单元大小 ...
- 跟着刚哥梳理java知识点——IO(十五)
凡是与输入.输出相关的类.接口都定义在java.io包下 java.io.File类 1.File是一个类,可以有构造器创建其对象.此对象对应着一个文件或者一个目录. 2.File中的类,仅涉及到如何 ...
- mysql加密解密方式用法
如果你使用的正是mysql数据库,那么你把密码或者其他敏感重要信息保存在应用程序里的机会就很大.保护这些数据免受黑客或者窥探者的获取是一个令人关注的重要问题,因为你既不能让未经授权的人员使用或者破坏应 ...
- html 选择器之属性选择器
属性选择器的主要作用个人的理解就是对带有指定属性的元素设置css样式. 使用css3的属性选择器,可以指定元素的某个属性,也可以指定某个属性和这个属性所对应的值. css3的属性选择器主要包括下面几种 ...
- 笔记整理:计算CPU使用率 ----linux 环境编程 从应用到内核
linux 提供time命令统计进程在用户态和内核态消耗的CPU时间: [root@localhost ~]# time sleep real 0m2.001s user 0m0.001s sys 0 ...
- java类集框架(ArrayList,LinkedList,Vector区别)
主要分两个接口:collection和Map 主要分三类:集合(set).列表(List).映射(Map)1.集合:没有重复对象,没有特定排序方式2.列表:对象按索引位置排序,可以有重复对象3.映射: ...
- Java数据结构和算法
首先,本人自学java,但是只学习了java的基础知识,所以想接下来学习一下数据结构和算法,但是找了很多教材,大部分写的好的都是用c语言实现的,虽然知道数据结构和算法,跟什么语言实现的没有关系,但是我 ...
- Andriod中自定义Dialog样式的Activity点击空白处隐藏软件盘(Dialog不消失)
一.需求触发场景: 项目中需要出发带有EditText的Dialog显示,要求在编辑完EditText时,点击Dilog的空白处隐藏软键盘.但是Dialog不会消失.示例如下: 二.实现方法: 发布需 ...
- 生产环境-jvm内存溢出-jprofile问题排查
首先线上开启了dump的参数 dump的内容有2G,先进行压缩打包,传输至本地(scp) tar -czvf dump.tar java_pid4824.hprof 使用Jprofile打开dump ...
- http接口加密《一》:移动应用中,通过在客户端对访问的url进行加密处理来保护服务器上的数据
来源:http://meiyitianabc.blog.163.com/blog/static/10502212720131056273619/ 我认为,保护服务器端的数据,有这么几个关键点: 不能对 ...