We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
使用Zepto的时候,我们经常会要去操作一些DOM的属性,或元素本身的固有属性或自定义属性等。比如常见的有attr(),removeAttr(),prop(),removeProp(),data()等。接下来我们挨个整明白他们是如何实现的...点击zepto模块源码注释查看这篇文章对应的解析。
attr()
removeAttr()
prop()
removeProp()
data()
原文链接
源码仓库
读取或设置dom的属性。 如果没有给定value参数,则读取对象集合中第一个元素的属性值。 当给定了value参数。则设置对象集合中所有元素的该属性的值。当value参数为null,那么这个属性将被移除(类似removeAttr),多个属性可以通过对象键值对的方式进行设置。zeptojs_api/#attr
示例
// 获取name属性 attr(name) // 设置name属性 attr(name, value) // 设置name属性,不同的是使用回调函数的形式 attr(name, function(index, oldValue){ ... }) // 设置多个属性值 attr({ name: value, name2: value2, ... })
已经知道了如何使用attr方法,在开始分析attr实现源码之前,我们先了解一下这几个函数。
attr
setAttribute
function setAttribute(node, name, value) { value == null ? node.removeAttribute(name) : node.setAttribute(name, value) }
它的主要作用就是设置或者删除node节点的属性。当value为null或者undefined的时候,调用removeAttribute方法移除name属性,否则调用setAttribute方法设置name属性。
funcArg
function funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg }
funcArg函数在多个地方都有使用到,主要为类似attr,prop,val等方法中第二个参数可以是函数或其他类型提供可能和便捷。
如果传入的arg参数是函数类型,那么用context作为arg函数的执行上下文,以及将idx和payload作为参数去执行。否则直接返回arg参数。
好啦接下来开始看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。
getAttribute
设置部分
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参数传入的是否是对象进行两个分支的操作。
each
看个实际使用的例子
$('.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方法不同的是,因为是设置和获取元素的固有属性,所以直接向元素设置和读取值就可以了。
1 in arguments
需要注意的是当你传入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,将不会有任何效果,因为浏览器禁止删除这些属性。
delete
获取或设置对象集合中元素的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做了些什么
0 in this
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方法再次转成了字符串。
property
还有一点需要注意的是text方法设置或者获取都是在操作元素的textContent属性,那它和innerText,innerHTML的区别在哪呢?可以查看MDN
textContent
设置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
<select multiple>
以上是基本用法
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方法对待取值和设置值的套路基本都是一样的,判断有没有传入第一个参数,有则认为是设置,没有就是读取。
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](也就是元素集合中第一个元素存在)
this[0].multiple ? '获取多选下拉列表的value' : '普通表单元素value'
针对第一种情况首先会通过find函数取查找子元素option集合,然后再过this.selected过滤出已经选中的option元素数组,最后还是通过调用pluck函数返回该option元素集合中的value数组。
this.selected
第二种情况则是直接读取元素的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
data-
zeptoAnalysis
data-zepto-analysis
第二部分调用attr方法去设置后者获取元素的属性
第三部分挺有意思的,读取属性值时,会有下列转换:
来看一下这些转转操作是如何通过deserializeValue方法完成的。
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)解析。欢迎指正其中的问题。
读Zepto源码之属性操作
textContent mdn
multiple
zepto.js 源码解析
ie模块
data模块
form模块
zepto模块
event模块
ajax模块
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
原文链接
源码仓库
attr()
示例
已经知道了如何使用
attr
方法,在开始分析attr实现源码之前,我们先了解一下这几个函数。setAttribute
它的主要作用就是设置或者删除node节点的属性。当value为null或者undefined的时候,调用removeAttribute方法移除name属性,否则调用setAttribute方法设置name属性。
funcArg
funcArg函数在多个地方都有使用到,主要为类似attr,prop,val等方法中第二个参数可以是函数或其他类型提供可能和便捷。
如果传入的arg参数是函数类型,那么用context作为arg函数的执行上下文,以及将idx和payload作为参数去执行。否则直接返回arg参数。
好啦接下来开始看
attr
的源码实现了代码分为两部分,获取与设置属性。先看
获取部分
当name参数是string类型,并且没有传入value参数时候,意味着是读取属性的情况。紧接着再看当前选中的元素集合中第一个元素是否存在并且节点类型是否为element类型,如果是,再调用
getAttribute
获取name属性,结果不为null或者undefined的话直接返回,否则统一返回undefined。设置部分
调用
each
方法,对当前的元素集合进行遍历操作,遍历过程中,如果当前的元素不是element类型,直接return掉。否则根据name参数传入的是否是对象进行两个分支的操作。看个实际使用的例子
可以看到如果传入的是回调函数,那回调函数可以接收到元素的索引,以及要设置的属性的之前的值。
removeAttr()
代码本身很简单,对当前选中的元素集合进行遍历操作,然后对name参数进行空格分割(这样对于name传入类似'name sex age'就可以批量删除了),最后还是调用的setAttribute方法进行属性删除操作。
prop()
直接看源码实现
通过
1 in arguments
作为设置与获取元素属性的判断标志,value传了,则对当前选中的元素集合进行遍历操作,同样用到了funcArg
函数,让value既可以传入函数,也可以传入其他值。与attr方法不同的是,因为是设置和获取元素的固有属性,所以直接向元素设置和读取值就可以了。需要注意的是当你传入class,for等属性的时候需要被映射到className,htmlFor等,下面是映射列表
removeProp()
直接通过
delete
去删除,但是如果尝试删除DOM的一些内置属性,如className或maxLength,将不会有任何效果,因为浏览器禁止删除这些属性。html()
源码分析
如果html传了,就遍历通过append函数设置html,没传就是获取(即返回当前集合的第一个元素的innerHTML)注意:这里的html参数可以是个函数,接收的参数是当前元素的索引和html。
text()
源码分析
同样包括设置和获取两部分,判断的边界则是是否传入了第一个参数。先看获取部分。
获取text
0 in this
当前是否选中了元素,没有直接返回null,有则通过this.pluck('textContent').join("")
获取,我们先来看一下pluck
做了些什么plunck
pluck也是挂在原型上的方法之一,通过使用map方法遍历当前的元素集合,返回结果是一个数组,数组的每一项则是元素的
property
属性。所以上面才通过join方法再次转成了字符串。还有一点需要注意的是text方法设置或者获取都是在操作元素的
textContent
属性,那它和innerText,innerHTML的区别在哪呢?可以查看MDN设置text
设置与html的设置部分比较类似,既支持直接传入普通的字符串也支持传入回调函数。如果得到的newText为null或者undefined,会统一转成空字符串再进行设置。
val
以上是基本用法
源码分析
html
,text
和val
方法对待取值和设置值的套路基本都是一样的,判断有没有传入第一个参数,有则认为是设置,没有就是读取。先看读取部分
假设
this[0](也就是元素集合中第一个元素存在)
我们把它拆成两个部分来学习针对第一种情况首先会通过find函数取查找子元素option集合,然后再过
this.selected
过滤出已经选中的option元素数组,最后还是通过调用pluck
函数返回该option元素集合中的value数组。第二种情况则是直接读取元素的value属性即可。
接下来我们回去继续看设置部分
与html,text等方法类似,通过调用funcArg方法使得既支持普通字符串设置,也支持传入回调函数返回值设置值。
data
注意:data方法本质上也是借用attr方法去实现的,不同之处在于data设置或者读取的属性为data-打头。
源码分析
data方法源码分为三个部分
我们分别一一解释一下这几个部分
将小驼峰书写形式转换成以
data-
开头的连字符形式�,例如zeptoAnalysis
=>data-zepto-analysis
第二部分调用attr方法去设置后者获取元素的属性
第三部分挺有意思的,读取属性值时,会有下列转换:
来看一下这些转转操作是如何通过
deserializeValue
方法完成的。这个函数用的三元表达式比较复杂,一步步解析如下
最后还有一个问题,不知道大家有没有注意到zepto模块中的data方法和data模块中的data方法都是挂载到原型下面的,那他们之间到底有什么关系呢?可以查看之前写的一篇文章Zepto中数据缓存原理与实现 ,应该可以找到答案
结尾
参考
读Zepto源码之属性操作
textContent mdn
multiple
zepto.js 源码解析
文章记录
ie模块
data模块
form模块
zepto模块
event模块
ajax模块
The text was updated successfully, but these errors were encountered: