mass Framework attr模块 v3
为了获取最佳的性能与兼容性,attr模块在v3中分割为两大块。attr模块大胆HTML5的classList,Array.isArray等新式API, attr_fix则是专门为IE6789准备的。attr模块存在大量的钩子,为摆平浏览器做了许多工件,但我们没有必须把所有补丁都集成到一个JS文件,其中过半是为IE侍侯的,因此分割出去对标准浏览器的加载量非常有利。
下面是attr.js,它相当于jQuery的attributes.js(github中的划分)。
//================================================== // 属性操作模块 v3 //================================================== define( "attr" ,!!top.getComputedStyle ? [ "$node" ] : [ "$attr_fix" ], function ( $ ){ var rreturn = /\r/g, rtabindex = /^(a|area|button|input|object|select|textarea)$/i, rnospaces = /\S+/g, support = $.support function getValType( el ){ var ret = el.tagName.toLowerCase(); return ret == "input" && /checkbox|radio/.test(el.type) ? el.type : ret; } $.implement({ /** * 为所有匹配的元素节点添加className,添加多个className要用空白隔开 * 如$("body").addClass("aaa");$("body").addClass("aaa bbb"); * <a href="http://www.cnblogs.com/rubylouvre/archive/2011/01/27/1946397.html">相关链接</a> */ addClass: function ( item ){ if ( typeof item == "string" ) { for ( var i = 0, el; el = this [i++]; ) { if ( el.nodeType === 1 ) { item.replace(rnospaces, function (clazz){ el.classList.add(clazz); }) } } } return this ; }, //如果不传入类名,则清空所有类名,允许同时删除多个类名 removeClass: function ( item ) { var removeSome = item && typeof item === "string" ,removeAll = item === void 0 for ( var i = 0, node; node = this [ i++ ]; ) { if ( node.nodeType === 1 ) { if (removeSome && node.className){ item.replace(rnospaces, function (clazz){ node.classList.remove(clazz); }) } else if (removeAll){ node.className = "" ; } } } return this ; }, //如果第二个参数为true,要求所有匹配元素都拥有此类名才返回true hasClass: function ( item, every ) { var method = every === true ? "every" : "some" , rclass = new RegExp( '(\\s|^)' +item+ '(\\s|$)' ); //判定多个元素,正则比indexOf快点 return $.slice( this )[ method ]( function ( el ){ //先转换为数组 return (el.className || "" ).match(rclass); }); }, //如果存在(不存在)就删除(添加)指定的类名。对所有匹配元素进行操作。 toggleClass: function ( value, stateVal ){ var type = typeof value , classNames = type === "string" && value.match( rnospaces ) || [], className, i, isBool = typeof stateVal === "boolean" ; return this .each( function ( el ) { i = 0; if (el.nodeType === 1){ var self = $( el ), state = stateVal; if (type == "string" ){ while ( (className = classNames[ i++ ]) ) { state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( el.className ) { $._data( el, "__className__" , el.className ); } el.className = el.className || value === false ? "" : $._data( el, "__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.match( rnospaces ), arr2 = []; for ( var j = 0; j < arr.length; j++ ) { arr2.push( arr[j] == old ? neo : arr[j]); } node.className = arr2.join( " " ); } } return this ; }, val : function ( item ) { var el = this [0], getter = valHooks[ "option:get" ]; if ( !arguments.length ) { //读操作 if ( el && el.nodeType == 1 ) { var ret = (valHooks[ getValType(el)+ ":get" ] || $.propHooks[ "@default:get" ])( el, "value" , getter ); return typeof ret === "string" ? ret.replace( rreturn, "" ) : ret == null ? "" : ret; } return void 0; } //我们确保传参为字符串数组或字符串,null/undefined强制转换为"", number变为字符串 if ( Array.isArray( item ) ){ item = item.map( function (item) { return item == null ? "" : item + "" ; }); } else if ( isFinite(item) ){ item += "" ; } else { item = item || "" ; } return this .each( function ( el ) { //写操作 if ( el.nodeType == 1 ) { (valHooks[ getValType(el)+ ":set" ] || $.propHooks[ "@default:set" ])( el, "value" , item , getter ); } }); } }); var cacheProp = {} function defaultProp(node, prop){ var name = node.tagName+ ":" +prop; if (name in cacheProp){ return cacheProp[name] } return cacheProp[name] = document.createElement(node.tagName)[prop] } $.mix({ fixDefault: $.noop, propMap:{ //属性名映射 "accept-charset" : "acceptCharset" , "char" : "ch" , "charoff" : "chOff" , "class" : "className" , "for" : "htmlFor" , "http-equiv" : "httpEquiv" }, prop: function (node, name, value){ if ($[ "@bind" ] in node){ if (node.nodeType === 1 && !$.isXML( node )){ name = $.propMap[ name.toLowerCase() ] || name; } var access = value === void 0 ? "get" : "set" return ($.propHooks[name+ ":" +access] || $.propHooks[ "@default:" +access] )(node, name, value) } }, attr: function (node, name, value){ if ($[ "@bind" ] in node){ if ( typeof node.getAttribute === "undefined" ) { return $.prop( node, name, value ); } //这里只剩下元素节点 var noxml = !$.isXML( node ), type = "@w3c" , isBool if ( noxml ){ name = name.toLowerCase(); var prop = $.propMap[ name ] || name if ( !support.attrInnateName ){ type = "@ie" } isBool = typeof node[ prop ] == "boolean" && typeof defaultProp(node,prop) == "boolean" //判定是否为布尔属性 } //移除操作 if (noxml){ if (value === null || value === false && isBool ){ return $.removeAttr(node, name ) } } else if ( value === null ) { return node.removeAttribute(name) } //读写操作 var access = value === void 0 ? "get" : "set" if ( isBool ){ type = "@bool" ; name = prop; } return ( noxml && $.attrHooks[ name+ ":" +access ] || $.attrHooks[ type + ":" +access] )(node, name, value) } }, //只能用于HTML,元素节点的内建不能删除(chrome真的能删除,会引发灾难性后果),使用默认值覆盖 removeProp: function ( node, name ) { if (node.nodeType === 1){ if (!support.attrInnateName){ name = $.propMap[ name.toLowerCase() ] || name; } node[name] = defaultProp(node, name) } else { node[name] = void 0; } }, //只能用于HTML removeAttr: function ( node, name ) { if (name && node.nodeType === 1){ name = name.toLowerCase(); if (!support.attrInnateName){ name = $.propMap[ name ] || name; } //小心contentEditable,会把用户编辑的内容清空 if ( typeof node[ name ] != "boolean" ){ node.setAttribute( name, "" ) } node.removeAttribute( name ); // 确保bool属性的值为bool if ( node[ name ] === true ) { node[ name ] = false ; $.fixDefault(node, name, false ) } } }, propHooks:{ "@default:get" : function ( node, name ){ return node[ name ] }, "@default:set" : function (node, name, value){ node[ name ] = value; }, "tabIndex:get" : function ( node ) { var ret = node.tabIndex; if ( ret === 0 ){ //在标准浏览器下,不显式设置时,表单元素与链接默认为0,普通元素为-1 ret = rtabindex.test(node.nodeName) ? 0 : -1 } return ret; } }, attrHooks: { "@w3c:get" : function ( node, name ){ var ret = node.getAttribute( name ) ; return ret == null ? void 0 : ret; }, "@w3c:set" : function ( node, name, value ){ node.setAttribute( name, "" + value ) }, "@bool:get" : function (node, name){ //布尔属性在IE6-8的标签大部字母大写,没有赋值,并且无法通过其他手段获得用户的原始设值 return node[ name ] ? name.toLowerCase() : void 0 }, "@bool:set" : function (node, name){ //布尔属性在IE6-8的标签大部字母大写,没有赋值,并且无法通过其他手段获得用户的原始设值 node.setAttribute( name, name.toLowerCase() ) node[ name ] = true ; $.fixDefault(node, name, true ) } } }); "Attr,Prop" .replace($.rword, function ( method ){ $.fn[ method.toLowerCase() ] = function ( name, value ) { return $.access( this , name, value, $[ method.toLowerCase() ] ); } $.fn[ "remove" + method] = function (name){ return this .each( function () { $[ "remove" + method]( this , name ); }); } }); //========================propHooks 的相关修正========================== 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($.rword, function (name){ $.propMap[name.toLowerCase()] = name; }); //safari IE9 IE8 我们必须访问上一级元素时,才能获取这个值 if ( !support.optSelected ) { $.propHooks[ "selected:get" ] = function ( node ) { for ( var p = node; typeof p.selectedIndex != "number" ;p = p.parentNode){} return node.selected; } } //========================valHooks 的相关修正========================== var valHooks = { "option:get" : function ( node ) { var val = node.attributes.value; //黑莓手机4.7下val会返回undefined,但我们依然可用node.value取值 return !val || val.specified ? node.value : node.text; }, "select:get" : function ( node ,value, getter) { var option, options = node.options, index = node.selectedIndex, one = node.type === "select-one" || index < 0, values = one ? null : [], max = one ? index + 1 : options.length, i = index < 0 ? max : one ? index : 0; for ( ; i < max; i++ ) { option = options[ i ]; //旧式IE在reset后不会改变selected,需要改用i === index判定 //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况 if ( ( option.selected || i === index ) && (support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) && (!option.parentNode.disabled || !$.type( option.parentNode, "OPTGROUP" )) ) { value = getter( option ); if ( one ) { return value; } //收集所有selected值组成数组返回 values.push( value ); } } return values; }, "select:set" : function ( node, name, values, getter ) { $.slice(node.options).forEach( function ( el ){ el.selected = !!~values.indexOf( getter(el) ); }); if ( !values.length ) { node.selectedIndex = -1; } } } //checkbox的value默认为on,唯有chrome 返回空字符串 if ( !support.checkOn ) { "radio,checkbox" .replace( $.rword, function ( name ) { valHooks[ name + ":get" ] = function ( node ) { return node.getAttribute( "value" ) === null ? "on" : node.value; } }); } //处理单选框,复选框在设值后checked的值 "radio,checkbox" .replace( $.rword, function ( name ) { valHooks[ name + ":set" ] = function ( node, name, value) { if ( Array.isArray( value ) ) { return node.checked = !!~value.indexOf(node.value ) ; } } }); if ( typeof $.fixIEAttr == "function" ){ $.fixIEAttr(valHooks, $.attrHooks); } return $; }); |
attr_fix模块,里面只有一个补丁函数。
define( "attr_fix" , !!top.getComputedStyle, [ "$node" ], function ($){ $.fixIEAttr = function (valHooks, attrHooks){ var rnospaces = /\S+/g, rattrs = /\s+([\w-]+)(?:=( "[^" ]* "|'[^']*'|[^\s>]+))?/g, rquote = /^['" ]/, defaults = { checked: "defaultChecked" , selected: "defaultSelected" } $.fixDefault = function (node, name, value){ var _default = defaults[name]; if (_default){ node[ _default ] = value; } } if (!( "classList" in $.html)){ $.fn.addClass = function ( item ){ if ( typeof item == "string" ) { for ( var i = 0, el; el = this [i++]; ) { if ( el.nodeType === 1 ) { if ( !el.className ) { el.className = item; } else { var a = (el.className+ " " +item).match( rnospaces ); a.sort(); for ( var j = a.length - 1; j > 0; --j) if (a[j] == a[j - 1]) a.splice(j, 1); el.className = a.join( " " ); } } } } return this ; } $.fn.removeClass = function ( item ) { if ( (item && typeof item === "string" ) || item === void 0 ) { var classNames = ( item || "" ).match( rnospaces ), cl = classNames.length; for ( var i = 0, node; node = this [ i++ ]; ) { if ( node.nodeType === 1 && node.className ) { if ( item ) { //rnospaces = /\S+/ var set = " " + node.className.match( rnospaces ).join( " " ) + " " ; for ( var c = 0; c < cl; c++ ) { set = set.replace( " " + classNames[c] + " " , " " ); } node.className = set.slice( 1, set.length - 1 ); } else { node.className = "" ; } } } } return this ; } } attrHooks[ "@ie:get" ] = function ( node, name ){ var str = node.outerHTML.replace(node.innerHTML, "" ), obj = {}, k, v; while (k = rattrs.exec(str)) { //属性值只有双引号与无引号的情况 v = k[2] obj[ k[1].toLowerCase() ] = v ? rquote.test( v ) ? v.slice(1, -1) : v : "" } return obj[ name ]; } attrHooks[ "@ie:set" ] = function ( node, name, value ){ var attr = node.getAttributeNode( name ); if ( !attr ) { //不存在就创建一个同名的特性节点 attr = node.ownerDocument.createAttribute( name ); node.setAttributeNode( attr ); } attr.value = value + "" ; } var support = $.support if ( !support.attrInnateValue ) { //在IE6-8如果一个A标签,它里面包含@字符,并且没任何元素节点,那么它里面的文本会变成链接值 $.propHooks[ "href:set" ] = attrHooks[ "href:set" ] = function ( node, name, value ) { var b if (node.tagName == "A" && node.innerText.indexOf( "@" ) > 0 && !node.children.length){ b = node.ownerDocument.createElement( 'b' ); b.style.display = 'none' ; node.appendChild(b); } node.setAttribute(name, value+ "" ); if (b) { node.removeChild(b); } } } //========================attrHooks 的相关修正========================== if ( !support.attrInnateHref ) { //IE的getAttribute支持第二个参数,可以为 0,1,2,4 //0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值(注,IE67对动态创建的节点没效),4用于取得完整路径 //IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。 "href,src,width,height,colSpan,rowSpan" .replace( $.rword, function ( method ) { attrHooks[ method.toLowerCase() + ":get" ] = function ( node,name ) { var ret = node.getAttribute( name, 2 ); return ret == null ? void 0 : ret; } }); "width,height" .replace( $.rword, function ( attr ){ attrHooks[attr+ ":set" ] = function (node, name, value){ node.setAttribute( attr, value === "" ? "auto" : value+ "" ); } }); $.propHooks[ "href:get" ] = function ( node, name ) { return node.getAttribute( name, 4 ); }; } if (!document.createElement( "form" ).enctype){ //如果不支持enctype, 我们需要用encoding来映射 $.propMap.enctype = "encoding" ; } if ( !support.attrInnateStyle ) { //IE67是没有style特性(特性的值的类型为文本),只有el.style(CSSStyleDeclaration)(bug) attrHooks[ "style:get" ] = function ( node ) { return node.style.cssText.toLowerCase() || undefined ; } attrHooks[ "style:set" ] = function ( node, name, value ) { node.style.cssText = value + "" ; } } //========================valHooks 的相关修正========================== if (!support.attrInnateName){ //IE6-7 button.value错误指向innerText valHooks[ "button:get" ] = attrHooks[ "@ie:get" ] valHooks[ "button:set" ] = attrHooks[ "@ie:set" ] } delete $.fixIEAttr; } return $; }) |
到目前为止,lang, css, event, attr模块都分割完成。它比jQuery分割为jquery1.9与jquery-compat-1.9更细腻,带来的性能提升更好。
顺便庆祝一下,此篇是mass Framework专题博文的第100篇博文!