这一节重点讲jQuery对样式的处理,虽然IE同时拥有style,currentStyle与runtimeStyle,但没有一个能获取used value,这是原罪。直接导致的结果是处理样式,就是处理IE的非精确值问题,有时能否获得值也是个大问题。jQuery与其他类库一样,在这方面下了很大工夫,最终在这方面打败其他类库。
001.
//这里的代码写得很垃圾啊,不过这样写肯定有它的道理,既然版本号已经发展1.32,那当然是那么兼容以前的代码设计的
002.
className: {
003.
//顺便一提className与arguments一样是个类数组
004.
add:
function
( elem, classNames ) {
005.
jQuery.each((classNames ||
""
).split(/\s+/),
function
(i, className){
006.
if
( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
007.
elem.className += (elem.className ?
" "
:
""
) + className;
008.
});
009.
},
010.
// internal only, use removeClass("class")
011.
remove:
function
( elem, classNames ) {
012.
//觉得什么都用自定义函数解决效率太低了,更何况jQuery.grep的逻辑如此复杂
013.
if
(elem.nodeType == 1)
014.
elem.className = classNames !== undefined ?
015.
jQuery.grep(elem.className.split(/\s+/),
function
(className){
016.
return
!jQuery.className.has( classNames, className );
017.
}).join(
" "
) :
018.
""
;
019.
},
020.
// internal only, use hasClass("class")
021.
has:
function
( elem, className ) {
022.
return
elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
023.
}
024.
},
025.
//这是一个非常重要的内部函数,用于精确获取样式值
026.
// A method for quickly swapping in/out CSS properties to get correct calculations
027.
swap:
function
( elem, options, callback ) {
028.
var
old = {};
//备份用
029.
// Remember the old values, and insert the new ones
030.
for
(
var
name
in
options ) {
031.
old[ name ] = elem.style[ name ];
032.
elem.style[ name ] = options[ name ];
033.
}
034.
//交换之后调用测试函数
035.
callback.call( elem );
036.
//测试完后还原
037.
// Revert the old values
038.
for
(
var
name
in
options )
039.
elem.style[ name ] = old[ name ];
040.
},
041.
//jQuery对象也有一个与它同名的方法,但这不是简单的代理
042.
//不过实际路线图为原型的css→原型的attr→静态的attr→静态的css
043.
//最后是curCSS,这才是真身
044.
css:
function
( elem, name, force, extra ) {
045.
//处理宽与高,因为IE不能正确返回以px为单位的精确值
046.
if
( name ==
"width"
|| name ==
"height"
) {
047.
//props用于swap,一个聪明的手段,值得学习
048.
var
val, props = { position:
"absolute"
, visibility:
"hidden"
, display:
"block"
}, which = name ==
"width"
? [
"Left"
,
"Right"
] : [
"Top"
,
"Bottom"
];
049.
function
getWH() {
050.
//Ext与Prototypet等类库也是这样实现
051.
//在标准模式中,offsetWidth是包含padding,borderWidth与width
052.
//在怪癖模式下,offsetWidth等于width,而width是包含padding与borderWidth
053.
//offsetHeight同理
054.
val = name ==
"width"
? elem.offsetWidth : elem.offsetHeight;
055.
if
( extra ===
"border"
)
056.
return
;
057.
jQuery.each( which,
function
() {
058.
if
( !extra )
059.
//求出paddingLeft与paddingRight之和,或paddingTop与paddingBottom之和,
060.
//然后作为减数,去减offsetWidth或offsetHeight
061.
val -= parseFloat(jQuery.curCSS( elem,
"padding"
+
this
,
true
)) || 0;
062.
if
( extra ===
"margin"
)
063.
val += parseFloat(jQuery.curCSS( elem,
"margin"
+
this
,
true
)) || 0;
064.
else
065.
val -= parseFloat(jQuery.curCSS( elem,
"border"
+
this
+
"Width"
,
true
)) || 0;
066.
});
067.
}
068.
if
( elem.offsetWidth !== 0 )
069.
getWH();
070.
else
071.
//如果display:none就求不出offsetWidht与offsetHeight,swap一下
072.
jQuery.swap( elem, props, getWH );
073.
return
Math.max(0, Math.round(val));
074.
}
075.
//再调用jQuery.curCSS进行深加工
076.
return
jQuery.curCSS( elem, name, force );
077.
},
078.
curCSS:
function
( elem, name, force ) {
079.
var
ret, style = elem.style;
080.
// We need to handle opacity special in IE
081.
if
( name ==
"opacity"
&& !jQuery.support.opacity ) {
082.
ret = jQuery.attr( style,
"opacity"
);
083.
return
ret ==
""
?
084.
"1"
:
085.
ret;
086.
}
087.
// Make sure we're using the right name for getting the float value
088.
if
( name.match( /float/i ) )
089.
name = styleFloat;
090.
if
( !force && style && style[ name ] )
091.
ret = style[ name ];
//缓存结果
092.
else
if
( defaultView.getComputedStyle ) {
093.
//标准浏览器
094.
// Only "float" is needed here
095.
if
( name.match( /float/i ) )
096.
name =
"float"
;
//把cssFloat转换为float
097.
//把驼峰风格转换为连字符风格
098.
name = name.replace( /([A-Z])/g,
"-$1"
).toLowerCase();
099.
var
computedStyle = defaultView.getComputedStyle( elem,
null
);
100.
if
( computedStyle )
101.
ret = computedStyle.getPropertyValue( name );
102.
// We should always get a number back from opacity
103.
if
( name ==
"opacity"
&& ret ==
""
)
104.
ret =
"1"
;
//把opacity设置成1
105.
}
else
if
( elem.currentStyle ) {
106.
//IE浏览器部分
107.
var
camelCase = name.replace(/\-(\w)/g,
function
(all, letter){
108.
return
letter.toUpperCase();
109.
});
110.
//把连字符风格转换为驼峰风格
111.
ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
112.
// From the awesome hack by Dean Edwards
113.
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
114.
// If we're not dealing with a regular pixel number
115.
// but a number that has a weird ending, we need to convert it to pixels
116.
//将不是以px为单位的计算值全部转换为以px为单位,用到 Dean Edwards(Base2类库的作者)的hack
117.
//网上有文章讲解这hach,这里不重复
118.
if
( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
119.
// Remember the original values
120.
var
left = style.left, rsLeft = elem.runtimeStyle.left;
121.
// Put in the new values to get a computed value out
122.
elem.runtimeStyle.left = elem.currentStyle.left;
123.
style.left = ret || 0;
124.
ret = style.pixelLeft +
"px"
;
125.
// Revert the changed values
126.
style.left = left;
127.
elem.runtimeStyle.left = rsLeft;
128.
}
129.
}
130.
return
ret;
131.
},
132.
attr:
function
( elem, name, value ) {
133.
// 文本,注释节点不处理
134.
if
(!elem || elem.nodeType == 3 || elem.nodeType == 8)
135.
return
undefined;
136.
//不处理xml文档的
137.
var
notxml = !jQuery.isXMLDoc( elem ),
138.
//是读方法还是写方法
139.
set = value !== undefined;
140.
// Try to normalize/fix the name
141.
//兼容处理,
142.
//jQuery.props = {
143.
//"for": "htmlFor",
144.
//"class": "className",
145.
//"float": styleFloat,
146.
//cssFloat: styleFloat,
147.
//styleFloat: styleFloat,
148.
//readonly: "readOnly",
149.
//maxlength: "maxLength",
150.
//cellspacing: "cellSpacing",
151.
//rowspan: "rowSpan",
152.
//tabindex: "tabIndex"
153.
//};
154.
name = notxml && jQuery.props[ name ] || name;
155.
// Only do all the following if this is a node (faster for style)
156.
// IE elem.getAttribute passes even for style
157.
if
( elem.tagName ) {
158.
// These attributes require special treatment
159.
var
special = /href|src|style/.test( name );
160.
// Safari mis-reports the default selected property of a hidden option
161.
// Accessing the parent's selectedIndex property fixes it
162.
//修正无法取得selected正确值的bug
163.
if
( name ==
"selected"
&& elem.parentNode )
164.
elem.parentNode.selectedIndex;
165.
// If applicable, access the attribute via the DOM 0 way
166.
if
( name
in
elem && notxml && !special ) {
167.
if
( set ){
168.
//不允许改写type的值
169.
// We can't allow the type property to be changed (since it causes problems in IE)
170.
if
( name ==
"type"
&& jQuery.nodeName( elem,
"input"
) && elem.parentNode )
171.
throw
"type property can't be changed"
;
172.
elem[ name ] = value;
173.
}
174.
// browsers index elements by id/name on forms, give priority to attributes.
175.
if
( jQuery.nodeName( elem,
"form"
) && elem.getAttributeNode(name) )
176.
//getAttributeNode() 方法的作用是:通过指定的名称获取当前元素中的属性节点。
177.
return
elem.getAttributeNode( name ).nodeValue;
178.
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
180.
//IE只能tabIndex
181.
//标准浏览器用tabindex
182.
if
( name ==
"tabIndex"
) {
183.
var
attributeNode = elem.getAttributeNode(
"tabIndex"
);
184.
return
attributeNode && attributeNode.specified
185.
? attributeNode.value
186.
: elem.nodeName.match(/(button|input|object|select|textarea)/i)
187.
? 0
188.
: elem.nodeName.match(/^(a|area)$/i) && elem.href
189.
? 0
190.
: undefined;
191.
}
192.
return
elem[ name ];
193.
}
194.
if
( !jQuery.support.style && notxml && name ==
"style"
)
195.
return
jQuery.attr( elem.style,
"cssText"
, value );
196.
if
( set )
197.
// convert the value to a string (all browsers do this but IE) see #1070
198.
elem.setAttribute( name,
""
+ value );
199.
//IE的getAttribute支持第二个参数,可以为 0,1,2
200.
//0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值。
201.
//IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。
202.
var
attr = !jQuery.support.hrefNormalized && notxml && special
203.
// Some attributes require a special call on IE
204.
? elem.getAttribute( name, 2 )
205.
: elem.getAttribute( name );
206.
// Non-existent attributes return null, we normalize to undefined
207.
return
attr ===
null
? undefined : attr;
208.
}
209.
// elem is actually elem.style ... set the style
210.
// IE uses filters for opacity
211.
if
( !jQuery.support.opacity && name ==
"opacity"
) {
212.
if
( set ) {
213.
// IE has trouble with opacity if it does not have layout
214.
// Force it by setting the zoom level
215.
//IE7中滤镜(filter)必须获得hasLayout才能生效,我们用zoom这个IE私有属性让其获得hasLayout
216.
elem.zoom = 1;
217.
// Set the alpha filter to set the opacity
218.
elem.filter = (elem.filter ||
""
).replace( /alpha\([^)]*\)/,
""
) +
219.
(parseInt( value ) +
''
==
"NaN"
?
""
:
"alpha(opacity="
+ value * 100 +
")"
);
220.
}
221.
return
elem.filter && elem.filter.indexOf(
"opacity="
) >= 0 ?
222.
(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) +
''
:
223.
""
;
224.
}
225.
//获得其他属性,直接用DOM 0方法读写
226.
name = name.replace(/-([a-z])/ig,
function
(all, letter){
227.
return
letter.toUpperCase();
228.
});
229.
if
( set )
230.
elem[ name ] = value;
231.
return
elem[ name ];
232.
},
其实在curCss与attr方法中还夹着一个clean方法,总觉得clean职责太多,里面分支繁缛,看得我头晕眼花……太凌乱,这方法应该分割成几个方法条理更清晰,效率更高。
001.
//把字符串转换为DOM元素的纯数组
002.
//这里的elems为字符串数组,将用文档碎片做转换
003.
clean:
function
( elems, context, fragment ) {
004.
context = context || document;
005.
// !context.createElement fails in IE with an error but returns typeof 'object'
006.
if
(
typeof
context.createElement ===
"undefined"
)
007.
context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
008.
// If a single string is passed in and it's a single tag
009.
// just do a createElement and skip the rest
010.
if
( !fragment && elems.length === 1 &&
typeof
elems[0] ===
"string"
) {
011.
var
match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
012.
if
( match )
013.
return
[ context.createElement( match[1] ) ];
014.
}
015.
//div是用于把字符串转换为DOM的
016.
var
ret = [], scripts = [], div = context.createElement(
"div"
);
017.
jQuery.each(elems,
function
(i, elem){
018.
if
(
typeof
elem ===
"number"
)
019.
elem +=
''
;
//转换为字符串
020.
if
( !elem )
021.
return
;
022.
// Convert html string into DOM nodes
023.
if
(
typeof
elem ===
"string"
) {
024.
// Fix "XHTML"-style tags in all browsers
025.
//生成闭合的标签对,亦即把在XHTML中不合法的写法强制转换过来
026.
elem = elem.replace(/(<(\w+)[^>]*?)\/>/g,
function
(all, front, tag){
027.
//但对于abbr|br|col|img|input|link|meta|param|hr|area|embed等元素不修改
028.
return
tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
029.
all :
030.
front +
"></"
+ tag +
">"
;
031.
});
032.
// Trim whitespace, otherwise indexOf won't work as expected
033.
//将“ <div> ”去掉两边的空白“<div>”,用于下面的indexOf
034.
var
tags = elem.replace(/^\s+/,
""
).substring(0, 10).toLowerCase();
035.
var
wrap =
036.
// option or optgroup
037.
//option与optgroup的直接父元素一定是select
038.
!tags.indexOf(
"<opt"
) &&
039.
[ 1,
"<select multiple='multiple'>"
,
"</select>"
] ||
040.
//legend的直接父元素一定是fieldset
041.
!tags.indexOf(
"<leg"
) &&
042.
[ 1,
"<fieldset>"
,
"</fieldset>"
] ||
043.
//thead,tbody,tfoot,colgroup,caption的直接父元素一定是table
044.
tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
045.
[ 1,
"<table>"
,
"</table>"
] ||
046.
//tr的直接父元素一定是tbody,
047.
!tags.indexOf(
"<tr"
) &&
048.
[ 2,
"<table><tbody>"
,
"</tbody></table>"
] ||
049.
//<thead> matched above
050.
//td与th的直接父元素一定是tr
051.
(!tags.indexOf(
"<td"
) || !tags.indexOf(
"<th"
)) &&
052.
[ 3,
"<table><tbody><tr>"
,
"</tr></tbody></table>"
] ||
053.
//col一定是colgroup
054.
!tags.indexOf(
"<col"
) &&
055.
[ 2,
"<table><tbody></tbody><colgroup>"
,
"</colgroup></table>"
] ||
056.
// IE can't serialize <link> and <script> tags normally
057.
!jQuery.support.htmlSerialize &&
058.
[ 1,
"div<div>"
,
"</div>"
] ||
059.
[ 0,
""
,
""
];
060.
// Go to html and back, then peel off extra wrappers
061.
div.innerHTML = wrap[1] + elem + wrap[2];
062.
// Move to the right depth
063.
while
( wrap[0]-- )
064.
div = div.lastChild;
065.
//IE会自动添加tbody,要特殊处理
066.
// Remove IE's autoinserted <tbody> from table fragments
067.
if
( !jQuery.support.tbody ) {
068.
// String was a <table>, *may* have spurious <tbody>
069.
var
hasBody = /<tbody/i.test(elem),
070.
tbody = !tags.indexOf(
"<table"
) && !hasBody ?
071.
div.firstChild && div.firstChild.childNodes :
072.
// String was a bare <thead> or <tfoot>
073.
wrap[1] ==
"<table>"
&& !hasBody ?
074.
div.childNodes :
075.
[];
076.
for
(
var
j = tbody.length - 1; j >= 0 ; --j )
077.
if
( jQuery.nodeName( tbody[ j ],
"tbody"
) && !tbody[ j ].childNodes.length )
078.
tbody[ j ].parentNode.removeChild( tbody[ j ] );
079.
}
080.
// IE completely kills leading whitespace when innerHTML is used
081.
if
( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
082.
div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
083.
//div中的所有节点都转换为数组
084.
elem = jQuery.makeArray( div.childNodes );
085.
}
086.
if
( elem.nodeType )
087.
//过滤非元素节点的节点
088.
ret.push( elem );
089.
else
090.
//把符合要求的节点加入ret中
091.
ret = jQuery.merge( ret, elem );
092.
});
093.
if
( fragment ) {
094.
for
(
var
i = 0; ret[i]; i++ ) {
095.
//处理script元素
096.
if
( jQuery.nodeName( ret[i],
"script"
) && (!ret[i].type || ret[i].type.toLowerCase() ===
"text/javascript"
) ) {
097.
scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
098.
}
else
{
099.
if
( ret[i].nodeType === 1 )
100.
ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName(
"script"
))) );
101.
fragment.appendChild( ret[i] );
102.
}
103.
}
104.
return
scripts;
105.
}
106.
return
ret;
107.
},