属性在这里只是一个统称,它对应两个术语attribute与property。attribute是指用户通过setAttribute设置的自定义属性,其值只能是字符串,如果没有显式定义,用getAttribute取值为undefined。property是指元素固有属性,像title, className,tabIndex, src, checked等等。这些属性的值可以是五花八门,如className总是字符串,tabIndex总是数字(它只限表单元素与个别能获取焦点的元素),表单元素的form属性总是指向其外围的表单对象(当然如果它没有放在form标签之内是不存在的), checked, disabled , defer等属性,如果此元素支持它,总总是返回布尔值。这些固有属性,如果没有显示定义,基本上是返回null(如果className, value则是字符串)。
document.body.className = function(){} alert(typeof document.body.className )//无法重写,返回"string"
如何处理这些庞杂的属性就是本模块要做的事情。它拥有如下四类方法。
val用于设置获取表单元素的value值,如果是普通元素则内部转交prop方法去处理。
hasClass, addClass, removeClass, toggleClass, replaceClass用于判定,添加, 移除, 切换与替换元素节点的className属性。className是一个特别的属性,它对应由多个类名组成的字符串,因此从prop独立出来区别对待。
attr,removeAttr用于处理元素节点的自定义属性,如果发现对象是window,则转交prop方法处理。
prop,removeProp用于固有属性。在下面给出的JS代码中,给出两份清单,一份是用于属性名映射,如著名的class变成className,for变成htmlFor,rowspan变成rowSpan....另一个是布尔属性,如async,autofocus,checked....它们比jquery源码那份更齐全了。
val, attr, prop 都支配着复数个适配器,确保能取得正确的值。
; (function(global,DOC){ var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')]; dom.define("attr","node", function(){ var rclass = /(^|\s)(\S+)(?=\s(?:\S+\s)*\2(?:\s|$))/g, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rspaces = /\s+/, support = dom.support, nodeAdapter, valOne = { "SELECT":"select", "OPTION":"option", "BUTTON":"button" }, getValType = function(node){ return "form" in node && (valOne[node.tagName] || node.type) } //Breast Expansion - Kate beim Arzt dom.implement({ /** * 为所有匹配的元素节点添加className,添加多个className要用空白隔开 * 如dom("body").addClass("aaa");dom("body").addClass("aaa bbb"); * <a href="http://www.cnblogs.com/rubylouvre/archive/2011/01/27/1946397.html">相关链接</a> */ addClass:function(value){ if ( typeof value == "string") { for ( var i = 0, el; el = this[i++]; ) { if ( el.nodeType === 1 ) { if ( !el.className ) { el.className = value; } else { el.className = (el.className +" "+value).replace(rclass,"") } } } } return this; }, //如果第二个参数为true,则只判定第一个是否存在此类名,否则对所有元素进行操作; hasClass: function( value, every ) { var method = every === true ? "every" : "some" var rclass = new RegExp('(\\s|^)'+value+'(\\s|$)');//判定多个元素,正则比indexOf快点 return dom.slice(this)[method](function(el){ return "classList" in el ? el.classList.contains(value): (el.className || "").match(rclass); }); }, //如果不传入类名,则去掉所有类名,允许传入多个类名 removeClass: function( value ) { if ( (value && typeof value === "string") || value === undefined ) { var classNames = (value || "").split( rspaces ); for ( var i = 0, node; node = this[i++]; ) { if ( node.nodeType === 1 && node.className ) { if ( value ) { var className = (" " + node.className + " ").replace(rspaces, " "); for ( var c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[c] + " ", " "); } node.className = className.trim(); } else { node.className = ""; } } } } return this; }, //如果存在(不存在)就删除(添加)一个类。对所有匹配元素进行操作。 toggleClass:function(value){ var classNames = value.split(rspaces ), i, className; var type = typeof value return this.each(function(el) { i = 0; if(el.nodeType === 1){ var self = dom(el); if(type == "string" ){ while ( (className = classNames[ i++ ]) ) { self[ self.hasClass( className ) ? "removeClass" : "addClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( el.className ) { self._data( "__className__", el.className ); } el.className = el.className || value === false ? "" : self.data( "__className__") || ""; } } }); }, //如果匹配元素存在old类名则将其改应neo类名 replaceClass:function(old, neo){ for ( var i = 0, node; node = this[i++]; ) { if ( node.nodeType === 1 && node.className ) { var arr = node.className.split(rspaces), arr2 = []; for (var j = 0; j<arr.length; j++) { arr2.push(arr[j] != old ? arr[j] : neo); } node.className = arr2.join(" "); } } return this; }, val: function( value ) { var node = this[0], adapter = dom.valAdapter; if ( !arguments.length ) {//读操作 if ( node && node.nodeType == 1 ) { //处理select-multiple, select-one,option,button var ret = (adapter[ getValType(node)+":get" ] || dom.propAdapter[ "_default:get" ])(node, "value"); return ret || "" } return undefined; } //强制将null/undefined转换为"", number变为字符串 if(Array.isArray(value)){ value = value.map(function (value) { return value == null ? "" : value + ""; }); }else if(isFinite(value)){ value += ""; }else{ value = value || "";//强制转换为数组 } return this.each(function(node) {//写操作 if ( node.nodeType == 1 ) { (adapter[ getValType(node)+":set" ] || dom.propAdapter[ "_default:set" ])(node, "value",value); } }); }, removeAttr: function( name ) { name = dom.attrMap[ name ] || name; var isBool = boolOne[name]; return this.each(function(node) { if(node.nodeType === 1){ dom["@remove_attr"]( node, name, isBool ); } }); }, removeProp: function( name ) { name = dom.propMap[ name ] || name; return this.each(function() { // try/catch handles cases where IE balks (such as removing a property on window) try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); } }); dom.extend({ attrMap:{ tabindex: "tabIndex" }, propMap:{ "accept-charset": "acceptCharset", "char": "ch", charoff: "chOff", "class": "className", "for": "htmlFor", "http-equiv": "httpEquiv" }, //内部函数,原则上拒绝用户的调用 "@remove_attr": function( node, name, isBool ) { var propName; name = dom.attrMap[ name ] || name; //如果支持removeAttribute,则使用removeAttribute dom.attr( node, name, "" ); node.removeAttribute( name ); // 确保bool属性的值为bool if ( isBool && (propName = dom.propMap[ name ] || name) in node ) { node[ propName ] = false; } }, attrAdapter: {//最基本的适配器 "_default:get":function(node,name){ var ret = node.getAttribute( name ) ; return ret === null ? undefined : ret; }, "_default:set":function(node, name, value){ //注意,属性名不能为数字,在FF下会报错String contains an invalid character" code: "5 node.setAttribute( name, "" + value ) }, "tabIndex:get":function( node ) { // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = node.getAttributeNode( "tabIndex" ); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test(node.nodeName) || rclickable.test(node.nodeName) && node.href ? 0 : undefined; } }, propAdapter:{ "_default:get":function(node,name){ return node[ name ] }, "_default:set":function(node, name, value){ node[ name ] = value; }, "value:set":function(node, name, value){ return dom.fn.val.call([node],value) ; }, "value:get":function(node){ return dom.fn.val.call([node]) } }, valAdapter: { "option:get": function( elem ) { var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; }, "select:get": function( node ) { var index = node.selectedIndex, values = [],options = node.options, value; var one = !node.multiple if ( index < 0 ) { return null; } for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; if ( option.selected ) { value = option.value || option.text; if ( one ) { return value; } values.push( value ); } } if(one && !value.length && node.length){ return dom(options[index]).val(); } return values; }, "select:set": function( node, name,values ) { dom.lang(node.options).forEach(function(el){ el.selected = values.indexOf(el.value || el.text) >= 0; }); if ( !values.length ) { node.selectedIndex = -1; } return values; } } }); //attr方法只能用于元素节点,只能写入字符串,只能取得字符串或undefined //prop方法只能用于原生的事件发送器,能写入除flase,undefined之外的各种数据,如果是bool属性必返回bool值 //attr是用于模拟set/getAttribute操作,用于设置或取得对象的自定义属性 //prop是用于模拟obj[name]操作,用于设置或取得对象的固有属性 "attr,prop".replace(dom.rword,function(method){ dom[method] = function( node, name, value ) { if(node && (dom["@emitter"] in node || method == "prop")){ if ( !("getAttribute" in node) ) { method = "prop"; } var notxml = !node.nodeType || !dom.isXML(node); //对于HTML元素节点,我们需要对一些属性名进行映射 name = notxml && dom[method+"Map"][ name ] || name; var adapter = dom[method+"Adapter"]; if(method === "attr" && node.tagName){ //比如input的readonly的取值可以为true false readonly if(boolOne[name] && (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase())) { name = dom.propMap[name] || name; adapter = boolAdapter; }else if(nodeAdapter ){ adapter = nodeAdapter; } } if ( value !== void 0 ){ if( method === "attr" && value === null){ //移除属性 return dom["@remove_"+method]( node, name ); }else{ //设置属性 return (notxml && adapter[name+":set"] || adapter["_default:set"])( node, name, value ); } } //获取属性 return (notxml && adapter[name+":get"] || adapter["_default:get"])( node, name ); } }; dom.fn[method] = function( name, value ) { return dom.access( this, name, value, dom[method], dom[method]); } }); //========================propAdapter 的相关补充========================== var prop = "accessKey,allowTransparency,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan,contentEditable,"+ "dateTime,defaultChecked,defaultSelected,defaultValue,frameBorder,isMap,longDesc,maxLength,marginWidth,marginHeight,"+ "noHref,noResize,noShade,readOnly,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"; prop.replace(dom.rword, function(name){ dom.propMap[name.toLowerCase()] = name; }); //safari IE9 IE8 我们必须访问上一级元素时,才能获取这个值 if ( !support.attrSelected ) { dom.propAdapter[ "selected:get" ] = function( parent ) { var node = parent for(var i= -3;i++; parent.selectedIndex, parent = parent.parentNode){}; return node.selected; } } //========================attrAdapter 的相关补充========================== if ( !support.attrHref ) { //IE的getAttribute支持第二个参数,可以为 0,1,2,4 //0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值(注,IE67对动态创建的节点没效)。 //IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。 "href,src,width,height,colSpan,rowSpan".replace(dom.rword,function(method ) { dom.attrAdapter[ method + ":get" ] = function( node,name ) { var ret = node.getAttribute( name, 2); return ret === null ? undefined : ret; } }); } //IE67是没有style特性(特性的值的类型为文本),只有el.style(CSSStyleDeclaration)(bug) if ( !support.attrStyle ) { dom.attrAdapter[ "style:get" ] = function( node ) { return node.style.cssText.toLowerCase(); } dom.attrAdapter[ "style:set" ] = function( node, value ) { return (node.style.cssText = "" + value); } } //=========================valAdapter 的相关补充========================== //checkbox的value默认为on,唯有Chrome 返回空字符串 if ( !support.checkOn ) { "radio,checkbox".replace(dom.rword,function(name) { dom.valAdapter[ name + ":get" ] = function( node ) { return node.getAttribute("value") === null ? "on" : node.value; } }); } //处理单选框,复选框在设值后checked的值 "radio,checkbox".replace(dom.rword,function(name) { dom.valAdapter[ name + ":set" ] = function( node, name, value) { if ( Array.isArray( value ) ) { return node.checked = !!~value.indexOf(node.value ) ; } } }); //=========================nodeAdapter 的相关补充========================= //如果我们不能通过el.getAttribute("class")取得className,必须使用el.getAttribute("className") if(!support.attrProp){ dom.attrMap = dom.propMap; var fixSpecified = dom.oneObject("name,id") //注意formElement[name] 相等于formElement.elements[name],会返回其辖下的表单元素 nodeAdapter = dom.mix({ "_default:get": function( node, name ) { var ret = node.getAttributeNode( name ); return ret && (fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified) ? ret.nodeValue : undefined; }, "_default:set": function( node, name, value ) { var ret = node.getAttributeNode( name ); if ( !ret ) { ret = node.ownerDocument.createAttribute( name ); node.setAttributeNode( ret ); } return (ret.nodeValue = value + ""); } },dom.attrAdapter,false); "width,height".replace(dom.rword,function(attr){ nodeAdapter[attr+":set"] = function(node, name, value){ node.setAttribute( attr, value === "" ? "auto" : value+""); } }); dom.valAdapter["button:get"] = nodeAdapter["_default:get"]; dom.valAdapter["button:set"] = nodeAdapter["_default:set"]; } //=========================boolAdapter 的相关补充========================= var boolAdapter = dom.mix({},dom.attrAdapter,false); var bools = "async,autofocus,checked,declare,disabled,defer,defaultChecked,contentEditable,ismap,loop,multiple,noshade,noresize,readOnly,selected" var boolOne = dom.oneObject( support.attrProp ? bools.toLowerCase() : bools ); bools.replace(dom.rword,function(method) { //bool属性在attr方法中只会返回与属性同名的值或undefined boolAdapter[method+":get"] = function(node,name){ var attrNode; return dom.prop( node, name ) === true || ( attrNode = node.getAttributeNode( name ) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; } boolAdapter[method+":set"] = function(node,name,value){ if ( value === false ) {//如果设置为false,直接移除,如设置input的readOnly为false时,相当于没有只读限制 dom["@remove_attr"]( node, name, true ); } else { if ( name in node ) { node[ name ] = true; } node.setAttribute( name, name.toLowerCase() ); } return name; } }); }); })(this, this.document); /* 2011.8.2 将prop从attr分离出来 添加replaceClass方法 2011.8.5 重构val方法 2011.8.12 重构replaceClass方法 2011.10.11 重构attr prop方法 **/