这篇依然是跟 dom
相干的要领,侧重点是操纵 dom
的要领。
读Zepto源码系列文章已放到了github上,迎接star: reading-zepto
源码版本
本文浏览的源码为 zepto1.2.0
.remove()
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
会离别挪用前面天生的几个要领。
辅佐要领traverseNode
function traverseNode(node, fun) {
fun(node)
for (var i = 0, len = node.childNodes.length; i < len; i++)
traverseNode(node.childNodes[i], fun)
}
这个要领递归遍历 node
的子节点,将节点交由回调函数 fun
处置惩罚。这个辅佐要领在背面会用到。
中心源码
adjacencyOperators.forEach(function(operator, operatorIndex) {
var inside = operatorIndex % 2 //=> prepend, append
$.fn[operator] = function() {
// arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
var argType, nodes = $.map(arguments, function(arg) {
var arr = []
argType = type(arg)
if (argType == "array") {
arg.forEach(function(el) {
if (el.nodeType !== undefined) return arr.push(el)
else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
arr = arr.concat(zepto.fragment(el))
})
return arr
}
return argType == "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" operation
target = operatorIndex == 0 ? target.nextSibling :
operatorIndex == 1 ? target.firstChild :
operatorIndex == 2 ? target :
null
var parentInDocument = $.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) {
var target = 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
对象设置对应的属性值(要领名)。
var argType, nodes = $.map(arguments, function(arg) {
var arr = []
argType = type(arg)
if (argType == "array") {
arg.forEach(function(el) {
if (el.nodeType !== undefined) return arr.push(el)
else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
arr = arr.concat(zepto.fragment(el))
})
return arr
}
return argType == "object" || arg == null ?
arg : zepto.fragment(arg)
}),
变量 argType
用来保存变量变量的范例,也即 content
的范例。nodes
是依据 content
转换后的 node
节点数组。
这里用了 $.map
arguments
的体式格局来猎取参数 content
,这里只要一个参数,这什么不必 arguments[0]
来猎取呢?这是由于 $.map
能够将数组举行展平,详细的完成看这里《读zepto源码之东西函数》。
起首用内部函数 type
来猎取参数的范例,关于 type
的完成,在《读Zepto源码之内部要领》 已作过剖析。
假如参数 content
,也即 arg
的范例为数组时,遍历 arg
,假如数组中的元素存在 nodeType
属性,则断定为 node
节点,就将其 push
进容器 arr
中;假如数组中的元素为 zepto
对象(用 $.zepto.isZ
推断,该要领已在《读Zepto源码之奇异的$》有过剖析),不传参挪用 get
要领,返回的是一个数组,然后挪用数组的 concat
要领兼并数组,get
要领在《读Zepto源码之鸠合操纵》有过剖析;不然,为 html
字符串,挪用 zepto.fragment
处置惩罚,并将返回的数组兼并,`zepto.fragment
在《读Zepto源码之奇异的$》中有过剖析。
假如参数范例为 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" operation
target = operatorIndex == 0 ? target.nextSibling :
operatorIndex == 1 ? target.firstChild :
operatorIndex == 2 ? target :
null
var parentInDocument = $.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
插进去到子节点的末端,恰好与 append
对应。详细见文档:Node.insertBefore()
var parentInDocument = $.contains(document.documentElement, parent)
挪用 $.contains
要领,检测父节点 parent
是不是在 document
中。$.contains
要领在《读zepto源码之东西函数》中已有过剖析。
将 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) {
var target = 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
或许为空时,才会根据 javascript
来处置惩罚。见MDN文档<script>
!el.src
而且不存在外部剧本。
var target = 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()
empty: function() {
return this.each(function() { this.innerHTML = '' })
},
empty
的作用是将一切鸠合元素的内容清空,挪用的是 node
的 innerHTML
属性设置为空。
.replaceWith()
replaceWith: function(newContent) {
return this.before(newContent).remove()
},
将一切鸠合元素替代为指定的内容 newContent
, newContent
的范例跟 before
的参数范例一样。
replaceWidth
起首挪用 before
将 newContent
插进去到对应元素的前面,再将元素删除,如许就达到了替代的上的。
.wrapAll()
wrapAll: function(structure) {
if (this[0]) {
$(this[0]).before(structure = $(structure))
var children
// 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()
wrap: function(structure) {
var func = isFunction(structure)
if (this[0] && !func)
var dom = $(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
节点,还可认为回调函数,回调函数吸收当前元素和当前元素在鸠合中的索引两个参数,返回相符前提的包裹构造。
var func = isFunction(structure)
推断 structure
是不是为函数
if (this[0] && !func)
var dom = $(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()
wrapInner: function(structure) {
var func = isFunction(structure)
return this.each(function(index) {
var self = $(this),
contents = self.contents(),
dom = func ? structure.call(this, index) : structure
contents.length ? contents.wrapAll(dom) : self.append(dom)
})
},
将鸠合中每一个元素的内容都用指定的构造 structure
包裹。 structure
的参数范例跟 wrap
一样。
对鸠合举行遍历,挪用 contents
要领,猎取元素的内容,contents
要领在《读Zepto源码之鸠合元素查找》有过剖析。
假如 structure
为函数,则将函数返回的效果赋值给 dom
,不然将直接将 structure
赋值给 dom
。
假如 contents.length
存在,即元素不为空元素,挪用 wrapAll
要领,将元素的内容包裹在 dom
中;假如为空元素,则直接将 dom
插进去到元素的末端,也完成了将 dom
包裹在元素的内部了。
.unwrap()
unwrap: function() {
this.parent().each(function() {
$(this).replaceWith($(this).children())
})
return this
},
当鸠合中的一切元素的包裹层去掉,也行将父元素去掉,然则保存父元素的子元素。
完成的要领也很简朴,就是遍历当前元素的父元素,将父元素替代为父元素的子元素。
.clone()
clone: function() {
return this.map(function() { return this.cloneNode(true) })
},
每鸠合中每一个元素都建立一个副本,并将副本鸠合返回。
遍历元素鸠合,挪用 node
的原生要领 cloneNode
建立副本。要注重,cloneNode
不会将元素本来的数据和事宜处置惩罚顺序复制到副本中。
系列文章
参考
License
末了,一切文章都邑同步发送到微信民众号上,迎接关注,迎接提意见:
作者:对角另一面