前言在使用Zepto的时候,我们经常要操作一些DOM属性,或者是元素本身固有的或者自定义的属性。比如常见的有attr()、removeAttr()、prop()、removeProp()、data()等,接下来我们就一一了解它们是如何实现的……点击zepto模块源码注释查看本文的相应分析。链接原源码仓库attr()读取或设置dom的属性。如果没有给出值参数,则读取对象集合中第一个元素的属性值。当给出值参数时。然后为对象集合中的所有元素设置此属性的值。当value参数为null时,该属性将被移除(类似removeAttr),可以通过对象键值对设置多个属性。zeptojs_api/#attrexample//获取name属性attr(name)//设置name属性attr(name,value)//设置name属性,区别在于使用回调函数的形式attr(name,function(index,oldValue){...})//设置多个属性值attr({name:value,name2:value2,...})已经知道attr方法的使用方法,开始分析源码之前attr实现代码,让我们了解这几个功能。setAttributefunctionsetAttribute(node,name,value){value==null?node.removeAttribute(name):node.setAttribute(name,value)}主要作用是设置或删除node节点的属性。当值为null或undefined时,调用removeAttribute方法移除name属性,否则调用setAttribute方法设置name属性。funcArgfunctionfuncArg(context,arg,idx,payload){returnisFunction(arg)?arg.call(context,idx,payload):arg}funcArg函数用在很多地方,主要是attr,prop,val等方法中的第二个参数,可以是函数,也可以是其他类型,提供可能性和方便。如果传入的arg参数是函数类型,则使用context作为arg函数的执行上下文,使用idx和payload作为参数执行。否则直接返回arg参数。好了,我们开始看attr的源码,实现attr:function(name,value){varresultreturn(typeofname=='string'&&!(1inarguments))?//获取属性(0inthis&&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(keyinname)setAttribute(this,key,name[key])//SetanattributevalueelsesetAttribute(this,name,funcArg(this,value,idx,this.getAttribute(name)))})}代码分为两部分,获取和设置属性。先看获取部分typeofname=='string'&&!(1inarguments))?//获取属性(0inthis&&this[0].nodeType==1&&(result=this[0].getAttribute(name))!=null?result:undefined):'设置代码逻辑代码块'当name参数是string类型,没有传入value参数,表示读取的是属性。接下来检查当前选中的元素集中的第一个元素是否存在,节点类型是否为元素类型。如果是,则调用getAttribute获取名称属性。如果结果不为null或undefined,则直接返回,否则统一返回undefined。设置部分this.each(function(idx){if(this.nodeType!==1)return//设置多个属性值if(isObject(name))for(keyinname)setAttribute(this,key,name[key])//设置一个属性值elsesetAttribute(this,name,funcArg(this,value,idx,this.getAttribute(name)))})调用各个方法遍历当前元素集,在遍历过程中,如果当前元素不是元素类型,直接返回。否则根据name参数的输入是否为对象,进行两个分支的操作??。如果name是一个对象,则遍历该对象,然后逐个调用setAttribute方法进行属性设置操作。如果它不是对象,则下一行代码允许第二个参数以普通字符串或回调函数的形式传递。看一个实际例子$('.box').attr('name','qianlongo')$('.box').attr('name',function(idx,oldVal){returnoldVal+'qianlongo'})可以看到如果传入回调函数,回调函数可以接收到元素的索引和要设置的属性的前一个值。removeAttr()移除当前对象集合中所有元素的指定属性。理论上attr也可以实现removeAttr的功能。只需将要删除的name属性设置为null或undefined即可。removeAttr:function(name){returnthis.each(function(){//通过拆分name,然后遍历需要删除的属性删除this.nodeType===1&&name.split('').forEach(function(attribute){setAttribute(this,attribute)},this)})}代码本身非常简单。遍历当前选中的元素集合,然后将name参数用空格分割(这样传入的name像'namesexage'可以批量删除),最后调用setAttribute方法进行属性删除操作.prop()读取或设置dom元素的属性值,简写或小写的名称,如for、class、readonly等类似的属性,会映射到实际的属性,如htmlFor、className、readOnly等。直接看源码实现prop:function(name,value){name=propMap[name]||名称返回(参数中有1个)?this.each(function(idx){this[name]=funcArg(this,value,idx,this[name])}):(this[0]&&this[0][name])}在参数中使用1作为一个用于设置和获取元素属性的判断标志,如果传值,则当前选择的元素集将被遍历操作,同样使用funcArg函数,这样可以传值给函数或者其他值。不同于attr方法,因为是设置和获取元素的固有属性,所以直接给元素设置和读取值就可以了。需要注意的是,传入class、for等属性时,需要映射到className、htmlFor等,下面是映射列表varpropMap={'tabindex':'tabIndex','readonly':'readOnly','for':'htmlFor','class':'className','maxlength':'maxLength','cellspacing':'cellSpacing','cellpadding':'cellPadding','rowspan':'rowSpan','colspan':'colSpan','usemap':'useMap','frameborder':'frameBorder','contenteditable':'contentEditable'}removeProp()从集合中的每个DOM节点移除一个属性removeProp:函数(名称){名称=propMap[名称]||namereturnthis.each(function(){deletethis[name]})}直接通过delete删除,但是如果你尝试删除一些DOM的内置属性,比如className或者maxLength,则不会有任何效果,因为浏览器禁止删除这些属性。html()获取或设置对象集合中元素的HTML内容。当没有给出内容参数时,返回对象集合中第一个元素的innerHtml。当给定一个内容参数时,用它替换对象集合中每个元素的内容。content可以是appendzeptojs_api/#html源码分析中描述的所有类型html:function(html){return0inarguments?this.each(function(idx){varoriginHtml=this.innerHTML$(this).empty().append(funcArg(this,html,idx,originHtml))}):(0inthis?this[0].innerHTML:null)}如果传入html,则通过append函数遍历设置html,如果没有传入,则获取(即返回当前集合第一个元素的innerHTML)注:这里的html参数可以是一个函数,接收的参数是当前元素的index和html。text()获取或设置对象集合中所有元素的文本内容。当不给出content参数时,返回当前对象集合中第一个元素的文本内容(包括子节点中的文本内容)。当给出一个内容参数时,它用于替换对象集合中所有元素的文本内容。有点像html,不同的是不能用来获取或设置(文本){在参数中返回0?this.each(function(idx){varnewText=funcArg(this,text,idx,this.textContent)this.textContent=newText==null?'':''+newText}):(0inthis?this.pluck('textContent').join(""):null)}也包括设置和获取,判断的边界是第一个参数是否传入。先看获取部分。获取text(0inthis?this.pluck('textContent').join(""):null)0inthis元素当前是否被选中,如果没有直接返回null,否则传this.pluck('textContent').join(""),看看pluck做了什么})},pluck也是挂在原型上的方法之一。使用map方法遍历当前元素集合,返回的结果是一个数组,数组的每一项都是元素的property属性。于是上面又通过join方法转成了字符串。另外需要注意的是,文本方法设置或获取是操作元素的textContent属性,那么它和innerText、innerHTML有什么区别呢?您可以检查MDN设置textthis.each(function(idx){varnewText=funcArg(this,text,idx,this.textContent)this.textContent=newText==null?'':''+newText})设置和html设置部分类似。它既支持直接输入普通字符串,也支持回调函数。如果获取到的newText为null或undefined,则统一转为空字符串再设置。val获取或设置匹配元素的值。当没有给出值参数时,返回第一个元素的值。如果是
