这篇依然是跟
相关的方法,侧重点是操作
- dom
的方法。
- dom
读 Zepto 源码系列文章已经放到了 github 上,欢迎 star: reading-zepto
本文阅读的源码为 zepto1.2.0
- remove: function(){
- return this.each(function(){
- if(this.parentNode != null)this.parentNode.removeChild(this)})},
删除当前集合中的元素。
如果父节点存在时,则用父节点的
方法来删掉当前的元素。
- removeChild
中
- zepto
、
- after
、
- prepend
、
- before
、
- append
、
- insertAfter
、
- insertBefore
和
- appendTo
都是通过这个相似方法生成器生成的。
- prependTo
- adjacencyOperators=['after', 'prepend', 'before', 'append']
首先,定义了一个相似操作的数组,注意数组里面只有
、
- after
、
- prepend
、
- before
这几个方法名,后面会看到,在生成这几个方法后,
- append
、
- insertAfter
、
- insertBefore
和
- appendTo
会分别调用前面生成的几个方法。
- prependTo
- function traverseNode(node,fun){
- fun(node)for(vari= 0,len= node.childNodes.length;i<len;i++)traverseNode(node.childNodes[i],fun)}
这个方法递归遍历
的子节点,将节点交由回调函数
- node
处理。这个辅助方法在后面会用到。
- fun
- adjacencyOperators.forEach(function(operator,operatorIndex){
- varinside=operatorIndex% 2 //=> prepend, append
- $.fn[operator]= function(){
- // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
- varargType,nodes= $.map(arguments, function(arg){
- vararr=[]
- argType= type(arg)if(argType== "array"){
- arg.forEach(function(el){
- if(el.nodeType !== undefined)return arr.push(el)else if($.zepto.isZ(el))returnarr= arr.concat(el.get())
- arr= arr.concat(zepto.fragment(el))})returnarr}
- returnargType== "object" ||arg== null ?arg :zepto.fragment(arg)}),parent,copyByClone= this.length > 1
- if(nodes.length < 1)return this
- return this.each(function(_,target){parent=inside?target :target.parentNode
- // convert all methods to a "before" operationtarget=operatorIndex== 0 ? target.nextSibling:
- operatorIndex== 1 ? target.firstChild:
- operatorIndex== 2 ?target :null
- varparentInDocument= $.contains(document.documentElement,parent)nodes.forEach(function(node){
- if(copyByClone) node= node.cloneNode(true)else if(!parent)return $(node).remove()parent.insertBefore(node,target)if(parentInDocument)traverseNode(node, function(el){
- if(el.nodeName != null && el.nodeName.toUpperCase()=== 'SCRIPT' &&(!el.type || el.type === 'text/javascript')&& !el.src){
- vartarget= el.ownerDocument ? el.ownerDocument.defaultView: window
- target['eval'].call(target, el.innerHTML)}
- })})})}
在分析之前,先看看这几个方法的用法:
- after(content)prepend(content)before(content)append(content)
参数
可以为
- content
字符串,
- html
节点,或者节点组成的数组。
- dom
是在每个集合元素后插入
- after
,
- content
正好相反,在每个集合元素前插入
- before
,
- content
是在每个集合元素的初始位置插入
- prepend
,
- content
是在每个集合元素的末尾插入
- append
。
- content
和
- before
插入的
- after
在元素的外部,而
- content
和
- prepend
插入的
- append
在元素的内部,这是需要注意的。
- content
转换成
- content
节点数组
- node
- var inside = operatorIndex % 2 //=> prepend, append
遍历
,得到对应的方法名
- adjacencyOperators
和方法名在数组中的索引
- operator
。
- operatorIndex
定义了一个
变量,当
- inside
为偶数时,
- operatorIndex
的值为
- inside
,也就是
- true
的值为
- operator
或
- prepend
时,
- append
的值为
- inside
。这个可以用来区分
- true
是插入到元素内部还是外部的方法。
- content
即为
- $.fn[operator]
对象设置对应的属性值(方法名)。
- $.fn
- varargType,nodes= $.map(arguments, function(arg){
- vararr=[]
- argType= type(arg)if(argType== "array"){
- arg.forEach(function(el){
- if(el.nodeType !== undefined)return arr.push(el)else if($.zepto.isZ(el))returnarr= arr.concat(el.get())
- arr= arr.concat(zepto.fragment(el))})returnarr}
- returnargType== "object" ||arg== null ?arg :zepto.fragment(arg)}),
变量
用来保存变量变量的类型,也即
- argType
的类型。
- content
是根据
- nodes
转换后的
- content
节点数组。
- node
这里用了
- $.map
的方式来获取参数
- arguments
,这里只有一个参数,这什么不用
- content
来获取呢?这是因为
- arguments[0]
可以将数组进行展平,具体的实现看这里《读 zepto 源码之工具函数》。
- $.map
首先用内部函数
来获取参数的类型,关于
- type
的实现,在《读 Zepto 源码之内部方法》 已经作过分析。
- type
如果参数
,也即
- content
的类型为数组时,遍历
- arg
,如果数组中的元素存在
- arg
属性,则断定为
- nodeType
节点,就将其
- node
进容器
- push
中;如果数组中的元素为
- arr
对象(用
- zepto
判断,该方法已经在《读 Zepto 源码之神奇的 $》有过分析),不传参调用
- $.zepto.isZ
方法,返回的是一个数组,然后调用数组的
- get
方法合并数组,
- concat
方法在《读 Zepto 源码之集合操作》有过分析;否则,为
- get
字符串,调用
- html
处理,并将返回的数组合并,`
- zepto.fragment
在《读 Zepto 源码之神奇的 $》中有过分析。
- zepto.fragment
如果参数类型为
(即为
- object
对象)或者
- zepto
,则直接返回。
- null
否则为
字符串,调用
- html
处理。
- zepto.fragment
- parent,
- copyByClone = this.length > 1
- if (nodes.length < 1) return this
这里还定义了
变量,用来保存
- parent
插入的父节点;当集合中元素的数量大于
- content
时,变量
- 1
的值为
- copyByClone
,这个变量的作用后面再说。
- true
如果
的数量比
- nodes
小,也即需要插入的节点为空时,不再作后续的处理,返回
- 1
,以便可以进行链式操作。
- this
来模拟所有操作
- insertBefore
- return this.each(function(_,target){parent=inside?target :target.parentNode
- // convert all methods to a "before" operationtarget=operatorIndex== 0 ? target.nextSibling:
- operatorIndex== 1 ? target.firstChild:
- operatorIndex== 2 ?target :null
- varparentInDocument= $.contains(document.documentElement,parent)
- ...})
对集合进行
遍历
- each
- parent=inside?target :target.parentNode
如果
节点需要插入目标元素
- node
的内部,则
- target
设置为目标元素
- parent
,否则设置为当前元素的父元素。
- target
- target=operatorIndex== 0 ? target.nextSibling:
- operatorIndex== 1 ? target.firstChild:
- operatorIndex== 2 ?target :null
这段是将所有的操作都用
原生方法
- dom
来模拟。 如果
- insertBefore
即为
- operatorIndex == 0
时,
- after
节点应该插入到目标元素
- node
的后面,即
- target
的下一个兄弟元素的前面;当
- target
即为
- operatorIndex == 1
时,
- prepend
节点应该插入到目标元素的开头,即
- node
的第一个子元素的前面;当
- target
即为
- operatorIndex == 2
时,
- before
刚好与之对应,即为元素本身。当
- insertBefore
的第二个参数为
- insertBefore
时,
- null
会将
- insertBefore
插入到子节点的末尾,刚好与
- node
对应。具体见文档:Node.insertBefore()
- append
- varparentInDocument= $.contains(document.documentElement,parent)
调用
方法,检测父节点
- $.contains
是否在
- parent
中。
- document
方法在《读 zepto 源码之工具函数》中已有过分析。
- $.contains
节点数组插入到元素中
- node
- nodes.forEach(function(node){
- if(copyByClone) node= node.cloneNode(true)else if(!parent)return $(node).remove()parent.insertBefore(node,target)
- ...})
如果需要复制节点时(即集合元素的数量大于
时),用
- 1
节点方法
- node
来复制节点,参数
- cloneNode
表示要将节点的子节点和属性等信息也一起复制。为什么集合元素大于
- true
时需要复制节点呢?因为
- 1
插入的是节点的引用,对集合中所有元素的遍历操作,如果不克隆节点,每个元素所插入的引用都是一样的,最后只会将节点插入到最后一个元素中。
- insertBefore
如果父节点不存在,则将
删除,不再进行后续操作。
- node
将节点用
方法插入到元素中。
- insertBefore
标签内的脚本
- script
- if(parentInDocument)traverseNode(node, function(el){
- if(el.nodeName != null && el.nodeName.toUpperCase()=== 'SCRIPT' &&(!el.type || el.type === 'text/javascript')&& !el.src){
- vartarget= el.ownerDocument ? el.ownerDocument.defaultView: window
- target['eval'].call(target, el.innerHTML)}
- })
如果父元素在
内,则调用
- document
来处理
- traverseNode
节点及
- node
节点的所有子节点。主要是检测
- node
节点或其子节点是否为不指向外部脚本的
- node
标签。
- script
- el.nodeName != null && el.nodeName.toUpperCase()=== 'SCRIPT'
这段用来判断是否为
标签,通过
- script
的
- node
属性是否为
- nodeName
来判断。
- script
- !el.type || el.type === 'text/javascript'
不存在
属性,或者
- type
属性为
- type
。这里表示只处理
- 'text/javascript'
,因为
- javascript
属性不一定指定为
- type
,只有指定为
- text/javascript
或者为空时,才会按照
- test/javascript
来处理。见 MDN 文档 script
- javascript
- !el.src
并且不存在外部脚本。
- vartarget= el.ownerDocument ? el.ownerDocument.defaultView: window
是否存在
属性,
- ownerDocument
返回的是元素的根节点,也即
- ownerDocument
对象,
- document
对象的
- document
属性返回的是
- defaultView
对象所关联的
- document
对象,这里主要是处理
- window
里的
- iframe
,因为在
- script
中有独立的
- iframe
对象。如果不存在该属性,则默认使用当前的
- window
对象。
- window
- target['eval'].call(target, el.innerHTML)
最后调用
的
- window
方法,执行
- eval
中的脚本,脚本用
- script
取得。
- el.innerHTML
为什么要对
元素单独进行这样的处理呢?因为出于安全的考虑,脚本通过
- script
的方法插入到
- insertBefore
中时,是不会执行脚本的,所以需要使用
- dom
来进行处理。
- eval
、
- insertAfter
、
- prependTo
和
- insertBefore
方法
- appendTo
先来看看这几个方法的调用方式
- insertAfter(target)insertBefore(target)appendTo(target)prependTo(target)
这几个方法都是将集合中的元素插入到目标元素
中,跟
- target
、
- after
、
- before
和
- append
刚好是相反的操作。
- prepend
他们的对应关系如下:
- after=>insertAfter
- prepend=>prependTo
- before=>insertBefore
- append=>appendTo
因此可以调用相应的方法来生成这些方法。
- $.fn[inside?operator+ 'To':'insert' +(operatorIndex? 'Before':'After')]= function(html){
- $(html)[operator](this)return this
- }
- inside?operator+ 'To':'insert' +(operatorIndex? 'Before':'After')
这段其实是生成方法名,如果是
或
- prepend
,则在后面拼接
- append
,如果是
- To
或
- Before
,则在前面拼接
- After
。
- insert
- $(html)[operator](this)
简单地反向调用对应的方法,就可以了。
到此,这个相似方法生成器生成了
、
- after
、
- prepend
、
- before
、
- append
、
- insertAfter
、
- insertBefore
和
- appendTo
等八个方法,相当高效。
- prependTo
- empty: function() {
- return this.each(function() {
- this.innerHTML = ''
- })
- },
的作用是将所有集合元素的内容清空,调用的是
- empty
的
- node
属性设置为空。
- innerHTML
- replaceWith: function(newContent) {
- return this.before(newContent).remove()
- },
将所有集合元素替换为指定的内容
,
- newContent
的类型跟
- newContent
的参数类型一样。
- before
首先调用
- replaceWidth
将
- before
插入到对应元素的前面,再将元素删除,这样就达到了替换的上的。
- newContent
- wrapAll: function(structure){
- if(this[0]){
- $(this[0]).before(structure= $(structure))varchildren// drill down to the inmost element
- while((children= structure.children()).length) structure= children.first()$(structure).append(this)}
- return this
- },
将集合中所有的元素都包裹进指定的结构
中。
- structure
如果集合元素存在,即
存在,则进行后续操作,否则返回
- this[0]
,以进行链式操作。
- this
调用
方法,将指定结构插入到第一个集合元素的前面,也即所有集合元素的前面
- before
- while((children= structure.children()).length) structure= children.first()
查找
的子元素,如果子元素存在,则将
- structure
赋值为
- structure
的第一个子元素,直找到
- structure
最深层的第一个子元素为止。
- structrue
将集合中所有的元素都插入到
的末尾,如果
- structure
存在子元素,则插入到最深层的第一个子元素的末尾。这样就将集合中的所有元素都包裹到
- structure
内了。
- structure
- wrap: function(structure){
- varfunc= isFunction(structure)if(this[0]&& !func)vardom= $(structure).get(0),clone= dom.parentNode || this.length > 1
- return this.each(function(index){
- $(this).wrapAll(
- func? structure.call(this,index) :
- clone? dom.cloneNode(true) : dom
- )})},
为集合中每个元素都包裹上指定的结构
,
- structure
可以为单独元素或者嵌套元素,也可以为
- structure
元素或者
- html
节点,还可以为回调函数,回调函数接收当前元素和当前元素在集合中的索引两个参数,返回符合条件的包裹结构。
- dom
- varfunc= isFunction(structure)
判断
是否为函数
- structure
- if(this[0]&& !func)vardom= $(structure).get(0),clone= dom.parentNode || this.length > 1
如果集合不为空,并且
不为函数,则将
- structure
转换为
- structure
节点,通过
- node
来转换,并赋给变量
- $(structure).get(0)
。如果
- dom
的
- dom
存在或者集合的数量大于
- parentNode
,则
- 1
的值为
- clone
。
- true
- return this.each(function(index){
- $(this).wrapAll(
- func? structure.call(this,index) :
- clone? dom.cloneNode(true) : dom
- )})
对集合进行遍历,调用
方法,如果
- wrapAll
为函数,则将回调函数返回的结果作为参数传给
- structure
;
- wrapAll
否则,如果
为
- clone
,则将
- true
也即包裹元素的副本传给
- dom
,否则直接将
- wrapAll
传给
- dom
。这里传递副本的的原因跟生成器中的一样,也是避免对
- wrapAll
节点的引用。如果
- dom
的
- dom
存在时,表明
- parentNode
本来就从属于某个节点,如果直接使用
- dom
,会破坏原来的结构。
- dom
- wrapInner: function(structure){
- varfunc= isFunction(structure)return this.each(function(index){
- varself= $(this),contents= self.contents(),dom=func? structure.call(this,index) : structurecontents.length ? contents.wrapAll(dom) :self.append(dom)})},
将集合中每个元素的内容都用指定的结构
包裹。
- structure
的参数类型跟
- structure
一样。
- wrap
对集合进行遍历,调用
方法,获取元素的内容,
- contents
方法在《读 Zepto 源码之集合元素查找》有过分析。
- contents
如果
为函数,则将函数返回的结果赋值给
- structure
,否则将直接将
- dom
赋值给
- structure
。
- dom
如果
存在,即元素不为空元素,调用
- contents.length
方法,将元素的内容包裹在
- wrapAll
中;如果为空元素,则直接将
- dom
插入到元素的末尾,也实现了将
- dom
包裹在元素的内部了。
- dom
- unwrap: function(){
- this.parent().each(function(){
- $(this).replaceWith($(this).children())})return this
- },
当集合中的所有元素的包裹层去掉,也即将父元素去掉,但是保留父元素的子元素。
实现的方法也很简单,就是遍历当前元素的父元素,将父元素替换为父元素的子元素。
- clone: function() {
- return this.map(function() {
- return this.cloneNode(true)
- })
- },
每集合中每个元素都创建一个副本,并将副本集合返回。
遍历元素集合,调用
的原生方法
- node
创建副本。要注意,
- cloneNode
不会将元素原来的数据和事件处理程序复制到副本中。
- cloneNode
来源: http://www.cnblogs.com/hefty/p/6939656.html