为了获取最佳的性能与兼容性,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篇博文!