jQuery源码阅读9:css操作

css操作是一大块, 核心操作里有关兼容性的问题太多太杂
先记下来以后一个一个看吧

1.外部api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* 博文作者:ruantao1989@gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
addClass: function( value ) {
var classNames, i, l, elem,
setClass, c, cl;
//如果传入function,需要迭代
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
//直接jQuery( this )创建对象, 然后fn.addClass
jQuery( this ).addClass( value.call(this, j, this.className) );
});
}
if ( value && typeof value === "string" ) {
//预处理字串,按空格切分
classNames = value.split( core_rspace );
//遍历选择器拿到的数组
for ( i = 0, l = this.length; i < l; i++ ) {
elem = this[ i ];
if ( elem.nodeType === 1 ) {
//仅有一个,直接写
if ( !elem.className && classNames.length === 1 ) {
elem.className = value;
} else {
//取出原有class值两边加空格
setClass = " " + elem.className + " ";
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
//原来没有的,写入
if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
setClass += classNames[ c ] + " ";
}
}
//去掉class两边空格
elem.className = jQuery.trim( setClass );
}
}
}
}
//链式操作, 把this返回回去
return this;
},
removeClass: function( value ) {
var removes, className, elem, c, cl, i, l;
//如果传入function,需要迭代
if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
jQuery( this ).removeClass( value.call(this, j, this.className) );
});
}
//传入string或没传值进来
if ( (value && typeof value === "string") || value === undefined ) {
//预处理, 有值就拿\s+切分
removes = ( value || "" ).split( core_rspace );
for ( i = 0, l = this.length; i < l; i++ ) {
//选择器中的一个元素
elem = this[ i ];
//普通dom元素 且有class值
if ( elem.nodeType === 1 && elem.className ) {
///[\t\r\n]/g,换成空格, 两边补空格
className = (" " + elem.className + " ").replace( rclass, " " );
//遍历传入值
for ( c = 0, cl = removes.length; c < cl; c++ ) {
//包含此值的话 ,把此值和两边空格删掉
while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
className = className.replace( " " + removes[ c ] + " " , " " );
}
}
//写入最终值
elem.className = value ? jQuery.trim( className ) : "";
}
}
}
//链式操作
return this;
},
//有此类就删除, 没有就添加
toggleClass: function( value, stateVal ) {
var type = typeof value,
isBool = typeof stateVal === "boolean";
//传入函数的话需要遍历
if ( jQuery.isFunction( value ) ) {
return this.each(function( i ) {
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
});
}
return this.each(function() {
if ( type === "string" ) {
//切换一个类名
var className,
i = 0,
self = jQuery( this ),
state = stateVal,
classNames = value.split( core_rspace );//拿空格切分
while ( (className = classNames[ i++ ]) ) {
//调用hasClass看是否有
state = isBool ? state : !self.hasClass( className );
//jQuery对象[add/removeClass](类名)
self[ state ? "addClass" : "removeClass" ]( className );
}
} else if ( type === "undefined" || type === "boolean" ) {
//没传值,或传布尔
if ( this.className ) {
//jQuery内部缓存起来(就是调用jQuery.data)
jQuery._data( this, "__className__", this.className );
}
//全部切换:
//本来没值还没值, 传什么也都不重要
//传false就赋"", 否则去拿缓存的值(缓存没有就赋"",就是恢复模式)
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}
});
},
hasClass: function( selector ) {
//预处理,两边加空格(之前都叫value,这怎么叫selector了呢...)
var className = " " + selector + " ",
i = 0,
l = this.length;
for ( ; i < l; i++ ) {
//普通dom且包含此class
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
return true;
}
}
//不包含
return false;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//==>源码6639行
jQuery.fn.extend({
css: function( name, value ) {
//调用access执行, 主要还是jQuery.style和jQuery.css
return jQuery.access( this, function( elem, name, value ) {
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
},
show: function() {
//强制显示
return showHide( this, true );
},
hide: function() {
return showHide( this );
},
toggle: function( state, fn2 ) {
var bool = typeof state === "boolean";
//传入函数时调用fn.toggle,这个和事件那块有关
if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
return eventsToggle.apply( this, arguments );
}
return this.each(function() {
//切换显隐
if ( bool ? state : isHidden( this ) ) {
jQuery( this ).show();
} else {
jQuery( this ).hide();
}
});
}
});

## 2.内部实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//==>源码6563行
function vendorPropName( style, name ) {
//驼峰命名,是不是在元素的style中, 在就不用管了
if ( name in style ) {
return name;
}
var capName = name.charAt(0).toUpperCase() + name.slice(1),//转驼峰
origName = name,//原名
i = cssPrefixes.length;//cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
while ( i-- ) {
name = cssPrefixes[ i ] + capName;
if ( name in style ) {
return name;//指定厂商的名+驼峰
}
}
return origName;
}
function isHidden( elem, el ) {
elem = el || elem;
//contains()是元素是否有包含关系
return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
}
function showHide( elements, show ) {
var elem, display,
values = [],
index = 0,
length = elements.length;
for ( ; index < length; index++ ) {
elem = elements[ index ];
//没有style就不用管了
if ( !elem.style ) {
continue;
}
//尝试去取缓存数据"olddisplay"
values[ index ] = jQuery._data( elem, "olddisplay" );
if ( show ) {
//显示模式,第二个参数传入true
if ( !values[ index ] && elem.style.display === "none" ) {
//display设置成""
elem.style.display = "";
}
//要是设成了"", 就缓存起来
if ( elem.style.display === "" && isHidden( elem ) ) {
values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
}
} else {
//第二个参数不传值
display = curCSS( elem, "display" );
if ( !values[ index ] && display !== "none" ) {
//缓存display, 叫"olddisplay"
jQuery._data( elem, "olddisplay", display );
}
}
}
//第二轮循环
for ( index = 0; index < length; index++ ) {
elem = elements[ index ];
//没值不用操作
if ( !elem.style ) {
continue;
}
if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
//切换"" 和 "none"
elem.style.display = show ? values[ index ] || "" : "none";
}
}
//链式
return elements;
}
//==>源码6670行
jQuery.extend({
//特殊处理opacity的get
cssHooks: {
opacity: {
get: function( elem, computed ) {
if ( computed ) {//需要数字
var ret = curCSS( elem, "opacity" );
//没值就是透明度1
return ret === "" ? "1" : ret;
}
}
}
},
//不再末尾加px的列表
cssNumber: {
"fillOpacity": true,
"fontWeight": true,
"lineHeight": true,
"opacity": true,
"orphans": true,
"widows": true,
"zIndex": true,
"zoom": true
},
//cssFloat是标准的, ie是styleFloat
cssProps: {
"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
},
//读写dom的style
style: function( elem, name, value, extra ) {
//元素是过滤Text或Comment直接返回
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
var ret, type, hooks,
origName = jQuery.camelCase( name ),//驼峰命名
style = elem.style;
//float需要修正,加指定厂商名前缀的需要修正
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
//如果用钩子
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
//写入value
if ( value !== undefined ) {
type = typeof value;
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
//支持(+= or -=)
//rrelNum匹配以+-开头,后边跟数字, 第一个子正则是(-+),第二个子正则是数字
//举例来说rrelNum.exec( "+=5") ==> ["+=5", "+", "5"]
//转成++1 * 5,
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
// Fixes bug #9237
type = "number";
}
//undefined,null 或 NaN不设置
if ( value == null || type === "number" && isNaN( value ) ) {
return;
}
//是数字,且不在特殊列表里
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
}
//有钩子用钩子set, 没钩子style[ name ] = value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
//处理ie兼容性 bug #5509
try {
style[ name ] = value;
} catch(e) {}
}
} else {
//有钩子用钩子读取
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
}
//没钩子直接读
return style[ name ];
}
},
css: function( elem, name, numeric, extra ) {
var val, num, hooks,
origName = jQuery.camelCase( name );
//float需要修正,加指定厂商名前缀的需要修正
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
//有可能用钩子
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
//有钩子就用钩子的get
if ( hooks && "get" in hooks ) {
val = hooks.get( elem, true, extra );
}
//调用核心函数curCSS
if ( val === undefined ) {
val = curCSS( elem, name );
}
//还原参数时,查一下还原列表, 就俩:letterSpacing: 0,fontWeight: 400
if ( val === "normal" && name in cssNormalTransform ) {
val = cssNormalTransform[ name ];
}
//加工val的值, 改写成数字形式
if ( numeric || extra !== undefined ) {
num = parseFloat( val );
return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
}
return val;
},

curCSS核心转换

style只能获取元素的内联样式,内部样式和外部样式使用style是获取不到的。
currentStyle可以弥补style的不足,但是只适用于IE。
getComputedStyle同currentStyle作用相同,但是适用于FF、opera、safari、chrome。
具体兼容问题以后再看吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//事先声明好curCSS
if ( window.getComputedStyle ) {
//标准浏览器
curCSS = function( elem, name ) {
var ret, width, minWidth, maxWidth,
computed = window.getComputedStyle( elem, null ),
style = elem.style;
if ( computed ) {
//针对IE9 的.css('filter') see #12537
ret = computed.getPropertyValue( name ) || computed[ name ];
//dom包含
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
ret = jQuery.style( elem, name );
}
//rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
//rmargin = /^margin/,
if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
width = style.width;
minWidth = style.minWidth;
maxWidth = style.maxWidth;
style.minWidth = style.maxWidth = style.width = ret;
ret = computed.width;
style.width = width;
style.minWidth = minWidth;
style.maxWidth = maxWidth;
}
}
return ret;
};
} else if ( document.documentElement.currentStyle ) {
//IE
curCSS = function( elem, name ) {
var left, rsLeft,
ret = elem.currentStyle && elem.currentStyle[ name ],
style = elem.style;
// Avoid setting ret to empty string here
// so we don't default to auto
if ( ret == null && style && style[ name ] ) {
ret = style[ name ];
}
// From the awesome hack by Dean Edwards
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to pixels
// but not position css attributes, as those are proportional to the parent element instead
// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
// Remember the original values
left = style.left;
rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
// Put in the new values to get a computed value out
if ( rsLeft ) {
elem.runtimeStyle.left = elem.currentStyle.left;
}
style.left = name === "fontSize" ? "1em" : ret;
ret = style.pixelLeft + "px";
// Revert the changed values
style.left = left;
if ( rsLeft ) {
elem.runtimeStyle.left = rsLeft;
}
}
return ret === "" ? "auto" : ret;
};
}