使用Zepto的时候,咱们常常会要去操做一些DOM的属性,或元素自己的固有属性或自定义属性等。好比常见的有
attr()
,removeAttr()
,prop()
,removeProp()
,data()
等。接下来咱们挨个整明白他们是如何实现的...点击zepto模块源码注释查看这篇文章对应的解析。javascript
原文连接css
源码仓库html
- 读取或设置dom的属性。
- 若是没有给定value参数,则读取对象集合中第一个元素的属性值。
- 当给定了value参数。则设置对象集合中全部元素的该属性的值。当value参数为null,那么这个属性将被移除(相似removeAttr),多个属性能够经过对象键值对的方式进行设置。zeptojs_api/#attr
示例java
// 获取name属性 attr(name) // 设置name属性 attr(name, value) // 设置name属性,不一样的是使用回调函数的形式 attr(name, function(index, oldValue){ ... }) // 设置多个属性值 attr({ name: value, name2: value2, ... })
已经知道了如何使用attr
方法,在开始分析attr实现源码以前,咱们先了解一下这几个函数。node
setAttributegit
function setAttribute(node, name, value) { value == null ? node.removeAttribute(name) : node.setAttribute(name, value) }
它的主要做用就是设置或者删除node节点的属性。当value为null或者undefined的时候,调用removeAttribute方法移除name属性,不然调用setAttribute方法设置name属性。github
funcArgajax
function funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg }
funcArg函数在多个地方都有使用到,主要为相似attr,prop,val等方法中第二个参数能够是函数或其余类型提供可能和便捷。json
若是传入的arg参数是函数类型,那么用context做为arg函数的执行上下文,以及将idx和payload做为参数去执行。不然直接返回arg参数。api
好啦接下来开始看attr
的源码实现了
attr: function (name, value) { var result return (typeof name == 'string' && !(1 in arguments)) ? // 获取属性 (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) : // 设置属性 this.each(function (idx) { if (this.nodeType !== 1) return // 设置多个属性值 if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) // 设置一个属性值 else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) }) }
代码分为两部分,获取与设置属性。先看
获取部分
typeof name == 'string' && !(1 in arguments)) ? // 获取属性 (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) : '设置代码逻辑代码块'
当name参数是string类型,而且没有传入value参数时候,意味着是读取属性的状况。紧接着再看当前选中的元素集合中第一个元素是否存在而且节点类型是否为element类型,若是是,再调用getAttribute
获取name属性,结果不为null或者undefined的话直接返回,不然统一返回undefined。
设置部分
this.each(function (idx) { if (this.nodeType !== 1) return // 设置多个属性值 if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) // 设置一个属性值 else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) })
调用each
方法,对当前的元素集合进行遍历操做,遍历过程当中,若是当前的元素不是element类型,直接return掉。不然根据name参数传入的是不是对象进行两个分支的操做。
看个实际使用的例子
$('.box').attr('name', 'qianlongo') $('.box').attr('name', function (idx, oldVal) { return oldVal + 'qianlongo' })
能够看到若是传入的是回调函数,那回调函数能够接收到元素的索引,以及要设置的属性的以前的值。
移除当前对象集合中全部元素的指定属性,理论上讲attr也能够作到removeAttr的功能。只要将要移除的name属性设置为null或者undefined便可。
removeAttr: function (name) { return this.each(function () { // 经过将name分割,再将须要移除的属性进行遍历删除 this.nodeType === 1 && name.split(' ').forEach(function (attribute) { setAttribute(this, attribute) }, this) }) }
代码自己很简单,对当前选中的元素集合进行遍历操做,而后对name参数进行空格分割(这样对于name传入相似'name sex age'就能够批量删除了),最后仍是调用的setAttribute方法进行属性删除操做。
读取或设置dom元素的属性值,简写或小写名称,好比for, class, readonly及相似的属性,将被映射到实际的属性上,好比htmlFor, className, readOnly, 等等。
直接看源码实现
prop: function (name, value) { name = propMap[name] || name return (1 in arguments) ? this.each(function (idx) { this[name] = funcArg(this, value, idx, this[name]) }) : (this[0] && this[0][name]) }
经过1 in arguments
做为设置与获取元素属性的判断标志,value传了,则对当前选中的元素集合进行遍历操做,一样用到了funcArg
函数,让value既能够传入函数,也能够传入其余值。与attr方法不一样的是,由于是设置和获取元素的固有属性,因此直接向元素设置和读取值就能够了。
须要注意的是当你传入class,for等属性的时候须要被映射到className,htmlFor等,下面是映射列表
var propMap = { 'tabindex': 'tabIndex', 'readonly': 'readOnly', 'for': 'htmlFor', 'class': 'className', 'maxlength': 'maxLength', 'cellspacing': 'cellSpacing', 'cellpadding': 'cellPadding', 'rowspan': 'rowSpan', 'colspan': 'colSpan', 'usemap': 'useMap', 'frameborder': 'frameBorder', 'contenteditable': 'contentEditable' }
从集合的每一个DOM节点中删除一个属性
removeProp: function (name) { name = propMap[name] || name return this.each(function () { delete this[name] }) }
直接经过delete
去删除,可是若是尝试删除DOM的一些内置属性,如className或maxLength,将不会有任何效果,由于浏览器禁止删除这些属性。
获取或设置对象集合中元素的HTML内容。当没有给定content参数时,返回对象集合中第一个元素的innerHtml。当给定content参数时,用其替换对象集合中每一个元素的内容。content能够是append中描述的全部类型zeptojs_api/#html
源码分析
html: function (html) { return 0 in arguments ? this.each(function (idx) { var originHtml = this.innerHTML $(this).empty().append(funcArg(this, html, idx, originHtml)) }) : (0 in this ? this[0].innerHTML : null) }
若是html传了,就遍历经过append函数设置html,没传就是获取(即返回当前集合的第一个元素的innerHTML)注意:这里的html参数能够是个函数,接收的参数是当前元素的索引和html。
获取或者设置全部对象集合中元素的文本内容。
当没有给定content参数时,返回当前对象集合中第一个元素的文本内容(包含子节点中的文本内容)。
当给定content参数时,使用它替换对象集合中全部元素的文本内容。它有待点似 html,与它不一样的是它不能用来获取或设置 HTMLtext
源码分析
text: function (text) { return 0 in arguments ? this.each(function (idx) { var newText = funcArg(this, text, idx, this.textContent) this.textContent = newText == null ? '' : '' + newText }) : (0 in this ? this.pluck('textContent').join("") : null) }
一样包括设置和获取两部分,判断的边界则是是否传入了第一个参数。先看获取部分。
获取text
(0 in this ? this.pluck('textContent').join("") : null)
0 in this
当前是否选中了元素,没有直接返回null,有则经过this.pluck('textContent').join("")
获取,咱们先来看一下pluck
作了些什么
plunck
// `pluck` is borrowed from Prototype.js pluck: function (property) { return $.map(this, function (el) { return el[property] }) },
pluck也是挂在原型上的方法之一,经过使用map方法遍历当前的元素集合,返回结果是一个数组,数组的每一项则是元素的property
属性。因此上面才经过join方法再次转成了字符串。
还有一点须要注意的是text方法设置或者获取都是在操做元素的textContent
属性,那它和innerText,innerHTML的区别在哪呢?能够查看MDN
设置text
this.each(function (idx) { var newText = funcArg(this, text, idx, this.textContent) this.textContent = newText == null ? '' : '' + newText })
设置与html的设置部分比较相似,既支持直接传入普通的字符串也支持传入回调函数。若是获得的newText为null或者undefined,会统一转成空字符串再进行设置。
获取或设置匹配元素的值。当没有给定value参数,返回第一个元素的值。若是是
<select multiple>
标签,则返回一个数组。当给定value参数,那么将设置全部元素的值。val
以上是基本用法
源码分析
val: function (value) { if (0 in arguments) { if (value == null) value = "" return this.each(function (idx) { this.value = funcArg(this, value, idx, this.value) }) } else { return this[0] && (this[0].multiple ? $(this[0]).find('option').filter(function () { return this.selected }).pluck('value') : this[0].value) } }
html
,text
和val
方法对待取值和设置值的套路基本都是同样的,判断有没有传入第一个参数,有则认为是设置,没有就是读取。
先看读取部分
return this[0] && (this[0].multiple ? $(this[0]).find('option').filter(function () { return this.selected }).pluck('value') : this[0].value)
假设this[0](也就是元素集合中第一个元素存在)
咱们把它拆成两个部分来学习
this[0].multiple ? '获取多选下拉列表的value' : '普通表单元素value'
针对第一种状况首先会经过find函数取查找子元素option集合,而后再过this.selected
过滤出已经选中的option元素数组,最后仍是经过调用pluck
函数返回该option元素集合中的value数组。
第二种状况则是直接读取元素的value属性便可。
接下来咱们回去继续看设置部分
if (value == null) value = "" return this.each(function (idx) { this.value = funcArg(this, value, idx, this.value) })
与html,text等方法相似,经过调用funcArg方法使得既支持普通字符串设置,也支持传入回调函数返回值设置值。
读取或写入dom的 data-* 属性。行为有点像 attr ,可是属性名称前面加上 data-。#data
注意:data方法本质上也是借用attr方法去实现的,不一样之处在于data设置或者读取的属性为data-打头。
源码分析
data: function (name, value) { var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase() var data = (1 in arguments) ? this.attr(attrName, value) : this.attr(attrName) return data !== null ? deserializeValue(data) : undefined },
data方法源码分为三个部分
咱们分别一一解释一下这几个部分
var capitalRE = /([A-Z])/g var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()
将小驼峰书写形式转换成以data-
开头的连字符形式,例如zeptoAnalysis
=> data-zepto-analysis
第二部分调用attr方法去设置后者获取元素的属性
第三部分挺有意思的,读取属性值时,会有下列转换:
来看一下这些转转操做是如何经过deserializeValue
方法完成的。
function deserializeValue(value) { try { return value ? value == "true" || (value == "false" ? false : value == "null" ? null : +value + "" == value ? +value : /^[\[\{]/.test(value) ? $.parseJSON(value) : value) : value } catch (e) { return value } }
这个函数用的三元表达式比较复杂,一步步解析以下
最后还有一个问题,不知道你们有没有注意到zepto模块中的data方法和data模块中的data方法都是挂载到原型下面的,那他们之间到底有什么关系呢?能够查看以前写的一篇文章Zepto中数据缓存原理与实现 ,应该能够找到答案
以上是Zepto中常见的操做元素属性的方法(attr、removeAttr、prop、removeProp、html、text、val、data)解析。欢迎指正其中的问题。
ie模块
data模块
form模块
zepto模块
event模块
ajax模块