jQuery 之所以能让开发者用极简的 API 操作样式,一个关键设计就是它对 CSS 属性值的单位补全。很多人第一次用 jQuery 设置宽度时,会不假思索地写成 $('#box').css('width', 200),然后惊喜地发现元素宽度变成了 200px。如果换成原生 DOM,直接执行 document.getElementById('box').style.width = 200 会被静默忽略,必须写成 200 + 'px'。这背后的差异正是 jQuery 的 style 钩子以及其内部的 cssNumber 映射在起作用。

理解这个机制,不仅能帮你写出更健壮的 jQuery 样式操作代码,也能为阅读 jQuery 源码、实现自定义钩子提供清晰的思路。
jQuery 样式钩子与 CSS 数值属性映射
jQuery 在 1.4.3 版本引入了 jQuery.cssHooks 机制,允许开发者为特定的 CSS 属性定义自定义的 getter 和 setter。在设置样式之前,jQuery 会先检查该属性是否存在于 cssNumber 这个对象中。这个对象包含了一组无需添加单位的 CSS 属性名,例如 zIndex、opacity、fontWeight、lineHeight 等。如果某个属性在这个列表里,jQuery 就不会为数字值自动附加像素单位;否则,所有以数字形式传入的值都会被默认加上 px 后缀。
这样做的好处非常明显:开发者不需要记住哪些属性必须带单位、哪些不能带单位。比如设置 width 时传数字 300,jQuery 会变成 300px;而设置 zIndex 时传数字 10,则保持为 10,不附加任何单位,因为 z-index 在 CSS 规范中就是一个纯数值。
这套映射表并非凭空定义,它对应着 CSS 规范中那些允许无单位数字值的属性。除了上面提到的,常见的还有 columnCount、orphans、widows 以及 flex、flexGrow、flexShrink 等。值得注意的是,lineHeight 虽然可以带单位,但无单位的数字会被浏览器解释为相对当前字体大小的倍数,因此也在 cssNumber 列表中。
源码剖析:setPositiveNumber 与单位补全逻辑
在 jQuery 源码中,处理样式值的核心函数之一是 setPositiveNumber。这个函数负责处理数字或数字字符串,将其转换成最终需要写入 style 属性的值。它的实现大致如下:
function setPositiveNumber( elem, value, subtract ) {
var matches = rnumsplit.exec( value );
return matches ?
Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
value;
}
可以看到,jQuery 先用一个正则表达式 rnumsplit 去匹配传入的值。这个正则通常定义为 /(-?[\d.]+)([a-z%]*)/i,也就是说它会将数字部分和单位部分分离开。如果传入的是纯数字字符串,比如 "200",那么匹配结果中的单位部分是空字符串,此时 matches[2] 为 undefined,最终函数返回 matches[1] 拼接上默认的 "px"。而如果传入的值已经携带了单位,例如 "20%" 或 "1.5em",则单位部分会原样保留。
但 setPositiveNumber 本身并不区分属性是否属于 cssNumber 列表,这个判断发生在更外层的 style 函数中。在 jQuery.style 的实现里,对于设置分支,会先检查 hooks 是否存在且有 set 方法,如果存在则调用钩子的 setter;否则会尝试调用 setPositiveNumber,但有一个前提:只有当属性名不在 cssNumber 中时才调用。对于 cssNumber 中的属性,值会被直接传递给 elem.style[ name ] = value;,不进行任何单位补全。
这里还涉及到一个细节:setPositiveNumber 还处理了减去某个值的情况,这主要用于 width 和 height 的 getter 和 setter 中,当传入的值包含 += 或 -= 这样的相对值时,jQuery 会先读取当前值再计算。当然,大多数开发者通常传入的是绝对数字或带单位的字符串,这个特性并不常用。
读取样式时的钩子与 getComputedStyle 处理
除了设置,style 钩子在读取 CSS 属性时也扮演着重要角色。原生 DOM 的 style.width 只能取到内联样式,而 jQuery 的 css() 方法之所以能拿到最终计算值,是因为它使用了 window.getComputedStyle。
对于没有定义特殊 getter 的属性,jQuery 直接通过 getComputedStyle( elem, null )[ name ] 来获取。但有些属性在不同浏览器下返回的值并不统一,例如在旧版 WebKit 中,getComputedStyle 返回的 width 可能是 "auto" 而不是具体像素值;又或者 opacity 在老 IE 下返回字符串形式的 "0.5",但有些浏览器返回数字。为此 jQuery 定义了一批内置的 cssHooks,例如针对 width、height、opacity、margin、padding 等属性,重写了 getter 和 setter。
以 opacity 的钩子为例,在支持标准 opacity 属性的浏览器中,jQuery 会在 getter 里将返回的计算值转换为浮点数并做四舍五入处理,避免出现类似 0.999999 的意外结果。而在较老的 IE 中,由于没有 opacity,则使用 filter: alpha(opacity=xx),jQuery 通过自定义 getter 和 setter 屏蔽了这些差异。
自定义 style 钩子的扩展也很直观。如果你需要为一个非标准属性添加单位自动补全,可以这样写:
jQuery.cssHooks.columnGap = {
set: function( elem, value, extra ) {
if ( typeof value === "number" ) {
elem.style.columnGap = value + "px";
} else {
elem.style.columnGap = value;
}
},
get: function( elem, computed, extra ) {
return computed.columnGap;
}
};
从上面的例子可以看出,钩子对象可以同时定义 set 和 get,分别负责写入和读取。在 setter 中手动补全 px,就实现了与内置属性一致的单位补全行为。jQuery 在执行 css() 时会优先检查 cssHooks[ name ],如果存在则使用钩子,否则走默认逻辑。
这种设计让 jQuery 的样式系统具备了很高的扩展性。无论是处理厂商前缀、兼容性补丁,还是添加复杂的复合属性支持,都可以在不修改核心代码的情况下完成。
常见误区与扩展建议
一个经常被忽略的误区是,并非所有数字值都应该补全为 px。比如设置 lineHeight 时传数字 1.5,它的含义是相对字体大小的倍数,如果加上了 px 反而会破坏原有的相对语义。所以 cssNumber 列表的存在正是为了区分这类属性。
另一个容易混淆的地方是单位补全只发生在传入数字或数字字符串的时候。如果你传入的是 "200" 这样的字符串,jQuery 同样会将其解析为数字并补全单位;但如果传入 "200px",则原样使用。如果你传入 "" 或 null,jQuery 会直接移除对应的样式属性,而不是报错。
在实际项目中,如果你大量操作样式,建议在自定义插件或模块中封装一层语义化的 API,内部仍然调用 css(),这样既保持了 jQuery 便利性,也避免了魔法字符串散落在业务代码里。同时,别忘了可以利用 style 钩子统一处理浏览器前缀,比如早期版本的 transform 属性就需要为不同浏览器设置不同的属性名。
理解 style 钩子和单位补全机制,能让你在调试样式相关问题时少走很多弯路。当某个属性设置后没有生效,首先应该检查传入的值类型是否正确,以及该属性是否在 cssNumber 中。如果涉及自定义属性,则要检查是否存在对应的 cssHooks 实现遮挡了默认行为。