jQuery原始碼解析(4)—— css樣式、定位屬性
閒話
原計劃是沒有這篇博文的,研究animation原始碼的時候遇到了css樣式這個攔路虎。比如jQuery支援“+=10”、“+=10px”定義一個屬性的增量,但是有的屬性設定時可以支援數字,有的必須有單位;在對屬性當前值讀取時,不同的瀏覽器可能返回不同的單位值,無法簡單的相加處理;在能否讀取高寬等位置資訊上,還會受到display狀態的影響;不同瀏覽器,相同功能對應的屬性名不同,可能帶有私有字首等等。
眾多的疑問,讓我決定先破拆掉jQuery的樣式機制。
(本文采用 1.12.0 版本進行講解,用 #number 來標註行號)
css
jQuery實現了一套簡單統一的樣式讀取與設定的機制。$
/* 讀取 */
$('#div1').css('lineHeight')
/* 寫入 */
$('#div1').css('lineHeight', '30px')
// 對映(這種寫法其實容易產生bug,不如下面一種,後文會講到)
$('#div1').css('lineHeight', function(index, value) {
return (+value || 0 ) + '30px';
})
// 增量(只支援+、-,能夠自動進行單位換算,正確累加)
$('#div1').css('lineHeight', '+=30px')
// 物件寫法
$('#div1').css({
'lineHeight': '+=30px' ,
'fontSize': '24px'
})
如何統一一個具有眾多相容問題的系統呢?jQuery的思路是抽象一個標準化的流程,然後對每一個可能存在例外的地方安放鉤子,對於需要例外的情形,只需外部定義對應的鉤子即可調整執行過程,即標準化流程 + 鉤子。
下面我們來逐個擊破!
1、access
jQuery.fn.css( name, value )
jQuery.css( elem, name, extra, styles )
、jQuery.style( elem, name, value, extra )
。
jq中鏈式呼叫、物件寫法、對映、無value則查詢這些特點套用在了很多API上,分成兩類。比如第一類:jQuery.fn.css(name, value)、第二類:jQuery.fn.html(value),第二類不支援物件引數寫法。jq抽離了不變的邏輯,抽象成了access( elems, fn, key, value, chainable, emptyGet, raw )
入口。
難點(怪異的第二類)
第一類(有key,bulk = false)
普通(raw):對elems(一個jq物件)每一項->fn(elems[i], key, value)
對映(!raw):對elems每一項elems[i],求得key屬性值val=fn(elems[i], key),執行map(即value)函式value.call( elems[ i ], i, val )得到返回值re,執行fn(elems[i], key, re)
取值(!value):僅取第一項fn( elems[ 0 ], key )
第二類(無key,bulk = true)
普通(raw):直接fn.call(elems, value)
對映(!raw):對elems每一項elems[i],求得值val=fn.call( jQuery( elems[i] )),執行map(即value)函式value.call( jQuery(elems[ i ]), val )得到返回值re,執行fn.call( jQuery( elems[i] ), re)
取值(!value):取fn.call( elems )
正是這兩類的不同造成了access內部邏輯的難懂,下面程式碼中第二類進行fn封裝,就是為了bulk->!raw->map能夠與第一類使用同樣的邏輯。兩類的使用方法,包括對映引數寫法都是不同的。有value均為鏈式呼叫chainable=true
// #4376
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
length = elems.length,
// 確定哪一類,false為第一類
bulk = key == null;
// 第一類物件寫法,為設定,開啟鏈式
if ( jQuery.type( key ) === "object" ) {
chainable = true;
// 拆分成key-value式寫法,靜待鏈式返回
for ( i in key ) {
access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// value有值,為設定,開啟鏈式
} else if ( value !== undefined ) {
chainable = true;
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// bulk->raw 第二類普通賦值,靜待鏈式返回
if ( raw ) {
fn.call( elems, value );
fn = null;
// bulk->!raw 第二類map賦值,封裝,以便能使用第一類的式子
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < length; i++ ) {
// 第一類raw普通,!raw對映。封裝後的第二類共用對映方法
fn(
elems[ i ],
key,
raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
}
return chainable ?
// 賦值,鏈式
elems :
// 取值
bulk ?
// 第二類
fn.call( elems ) :
// 第一類
length ? fn( elems[ 0 ], key ) : emptyGet;
};
讀取依賴window上的getComputedStyle方法,IE6-8依賴元素的currentStyle方法。樣式的寫入依賴elem.style。
2、jQuery.fn.css
jQuery.fn.css( name, value )為什麼會有兩個核心方法呢?因為樣式的讀取和寫入不是同一個方式,而寫入的方式有時候也會用來讀取。
讀:依賴window上的getComputedStyle方法,IE6-8依賴元素的currentStyle方法。內聯外嵌的樣式都可查到
寫:依賴elem.style的方式。而elem.style方式也可以用來查詢的,但是隻能查到內聯的樣式
因此封裝了兩個方法jQuery.css( elem, name, extra, styles )
、jQuery.style( elem, name, value, extra )
,前者只讀,後者可讀可寫,但是後者的讀比較雞肋,返回值可能出現各種單位,而且還無法查到外嵌樣式,因此jQuery.fn.css方法中使用前者的讀,後者的寫
// #7339
jQuery.fn.css = function( name, value ) {
// access第一類用法,fn為核心函式的封裝,直接看返回值
return access( this, function( elem, name, value ) {
var styles, len,
map = {},
i = 0;
// 增加一種個性化的 取值 方式。屬性陣列,返回key-value物件
// 第一類取值,只取elems[0]對應fn執行的返回值
if ( jQuery.isArray( name ) ) {
styles = getStyles( elem );
len = name.length;
for ( ; i < len; i++ ) {
// false引數則只返回未經處理的樣式值,給定了styles則從styles物件取樣式
// 下面會單獨講jQuery.css
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
}
return map;
}
return value !== undefined ?
// 賦值
jQuery.style( elem, name, value ) :
// 取值
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
};
3、屬性名相容
第一步:jq支援駝峰和’-‘串聯兩種寫法,會在核心方法中統一轉化為小駝峰形式
第二步:不同瀏覽器的不同屬性名相容,如float為保留字,標準屬性是cssFloat,IE中使用styleFloat(當前版本IE已拋棄)。檢視是否在例外目錄中
第三步:css3屬性支援程度不一,有的需要加上私有字首才可使用。若加上私有字首才能用,新增到例外目錄中方便下次拿取
// #83,#356,第一步,小駝峰
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
};
// #356
camelCase = function( string ) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},
// #7083,第二步
cssProps: {
// normalize float css property
"float": support.cssFloat ? "cssFloat" : "styleFloat"
}
// #6854,第三步
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
emptyStyle = document.createElement( "div" ).style;
// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( name ) {
// 查詢無字首駝峰名是否支援
if ( name in emptyStyle ) {
return name;
}
// 首字母變為大寫
var capName = name.charAt( 0 ).toUpperCase() + name.slice( 1 ),
i = cssPrefixes.length;
// 查詢是否有支援的私有字首屬性
while ( i-- ) {
name = cssPrefixes[ i ] + capName;
if ( name in emptyStyle ) {
return name;
}
}
}
4、jQuery.css、jQuery.style
jq的樣式機制之所有複雜,因為在核心方法功能的設計上,考慮了非必要的“易用”、“相容”、“擴充套件”。使得設定更靈活、輸出更一致、支援累加換算、支援拓展的功能。由於對下面特性的支援,因此對樣式的取值抽象出了核心邏輯curCSS( elem, name, computed )
,一來是邏輯劃分更清晰,內部更可根據需要酌情選擇使用這兩者
易用
1、為了提高易用性,jQuery.style()可以自動為設定值加上預設單位’px’,由於有些屬性值可以為數字,因此定義了cssNumber
的列表,列表中的專案不會加上預設單位。
2、允許增量’+=20px’式寫法,由於採用jQuery.css獲取的初始值單位有可能不同,因此封裝了一個自動單位換算並輸出增量後最終結果的函式adjustCSS()
相容
並不是每個屬性都能返回預期的值。
1、比如opacity在IE低版本是filter,用jQuery.style方式取值時需要匹配其中數字,結果跟opacity有100倍差距,而且設定的時候alpha(opacity=num)的形式也太獨特。
2、比如定位資訊會因為元素display為none等狀態無法正確獲取到getBoundingClientRect()、offsetLeft、offsetWidth等位置資訊及大小,而且對於自適應寬度無法取得寬高資訊。
3、比如一些瀏覽器相容問題,導致某些元素返回百分比等非預期值。
jQuery.cssHooks
是樣式機制的鉤子系統。可以對需要hack的屬性,新增鉤子,在jQuery.css、jQuery.style讀取寫入之前,都會先看是否存在鉤子並呼叫,然後決定是否繼續下一步還是直接返回。通過在set
、get
屬性中定義函式,使得行為正確一致。
擴充套件
1、返回值:jQuery.css對樣式值的讀取,可以指定對於帶單位字串和”auto”等字串如何返回,新增了extra
引數。為”“(不強制)和true(強制)返回去單位值,false不做特殊處理直接返回。
2、功能擴充套件:jq允許直接通過innerWidth()/innerHeight()、outerWidth()/outerHeight()讀取,也支援賦值,直接調整到正確的寬高。這是通過extra指定padding、border、margin等字串做到的
3、cssHooks.expand:對於margin、padding、borderWidth等符合屬性,通過擴充套件expand介面,可以得到含有4個分屬性值的物件。
bug
使用字串’30’和數字30的效果有區別。對於不能設為數字的,數字30自動加上px,字串的卻不會。
下面adjustCSS換算函式中也提到一個bug,下面有描述。
建議:adjustCSS函式本身就可以處理增量和直接量兩種情況,type===’string’判斷的地方不要ret[ 1 ],以解決第一個問題。adjustCSS返回一個數組,第一個為值,第二個為單位,這樣就防止第二個bug。
// #4297,pnum匹配數字,rcssNum -> [匹配項,加/減,數字,單位]
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
var rcssNum = new RegExp( "^(?:([+-])=)(" + pnum + ")([a-z%]*)$", "i" );
// #6851
cssNormalTransform = {
letterSpacing: "0",
fontWeight: "400"
}
// #7090,核心方法
jQuery.extend( {
// 支援數字引數的屬性列表,不會智慧新增單位
cssNumber: {
"animationIterationCount": true,
"columnCount": true,
"fillOpacity": true,
"flexGrow": true,
"flexShrink": true,
"fontWeight": true,
"lineHeight": true,
"opacity": true,
"order": true,
"orphans": true,
"widows": true,
"zIndex": true,
"zoom": true
},
// elem.style方式讀寫
style: function( elem, name, value, extra ) {
// elem為文字和註釋節點直接返回
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
/**
* ---- 1、name修正,屬性相容 ----
*/
var ret, type, hooks,
// 小駝峰
origName = jQuery.camelCase( name ),
style = elem.style;
// 例外目錄、私有字首
name = jQuery.cssProps[ origName ] ||
( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
// 鉤子
// 先name、後origName使鉤子更靈活,既可統一,又可單獨
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
/**
* ---- 2、elem.style方式 - 賦值 ----
*/
if ( value !== undefined ) {
type = typeof value;
// '+='、'-='增量運算
// Convert "+=" or "-=" to relative numbers (#7345)
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
// adjustCSS對初始值和value進行單位換算,相加/減得到最終值(數值)
value = adjustCSS( elem, name, ret );
// 數值需要在下面加上合適的單位
type = "number";
}
// Make sure that null and NaN values aren't set. See: #7116
if ( value == null || value !== value ) {
return;
}
// 數值和'+=xx'轉換的數值,都需要加上單位。cssNumber記錄了可以是數字的屬性,否則預設px
// ret[3]為'+=xx'原本匹配的單位
if ( type === "number" ) {
value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
}
// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
// but it would mean to define eight
// (for every problematic property) identical functions
if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
style[ name ] = "inherit";
}
// 有鉤子先使用鉤子,看返回值是否為undefined決定是否style[ name ]賦值,否則直接賦值
if ( !hooks || !( "set" in hooks ) ||
( value = hooks.set( elem, value, extra ) ) !== undefined ) {
// Support: IE
// Swallow errors from 'invalid' CSS values (#5509)
try {
style[ name ] = value;
} catch ( e ) {}
}
/**
* ---- 3、elem.style方式 - 取值 ----
*/
} else {
// 有鉤子先使用鉤子,看返回值是否為undefined決定是否style[ name ]取值,否則直接取值
if ( hooks && "get" in hooks &&
( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
return ret;
}
return style[ name ];
}
},
// 預設computedStyle/currentStyle方式只讀,也可styles指定讀取物件
css: function( elem, name, extra, styles ) {
/**
* ---- 1、name修正,屬性相容(同style) ----
*/
var num, val, hooks,
origName = jQuery.camelCase( name );
name = jQuery.cssProps[ origName ] ||
( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
// 鉤子
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
// 若有鉤子,通過鉤子讀取
if ( hooks && "get" in hooks ) {
val = hooks.get( elem, true, extra );
}
// 沒有鉤子,通過封裝的curCSS讀取
if ( val === undefined ) {
val = curCSS( elem, name, styles );
}
// 屬性值為"normal",若為cssNormalTransform內的屬性,把對應值輸出
if ( val === "normal" && name in cssNormalTransform ) {
val = cssNormalTransform[ name ];
}
// extra === "" 去單位處理,若為"normal"、"auto"等字串,原樣返回
// extra === true 強制去單位,若為parseFloat後為NaN的字串,返回0
// extra === false/undefined 不特殊處理
if ( extra === "" || extra ) {
num = parseFloat( val );
return extra === true || isFinite( num ) ? num || 0 : val;
}
return val;
}
} );
5、adjsutCSS換算
adjustCSS( elem, prop, valueParts, tween )
用於呼叫jQuery.style對增量計算的換算,並得到最終值。在jq內部,除了css樣式會換算,動畫處理也支援換算。這裡也可以把動畫的tween物件的初始值和增量進行累加換算,得到最終值賦給tween物件
難點:
這裡需要知道jQuery.css( elem, prop, "" )
通過computedStyle/currentStyle求得的值單位不變,並且被extra=”“去掉了單位。比如初始值是30px,增量為’+=1rem’,先使用增量的單位30rem,然後呼叫jQuery.css查詢跟修改前的初始值比較,比如變成了scale=15倍,則30rem/15=2rem求得原值換算後為2rem,然後再累加返回3rem。
maxIterations設為20有兩個原因:1、js浮點誤差可能導致兩邊總是不相等;2、對於首次調整單位變成了很小的倍數趨近於0無法計算,則通過重置為0.5每次乘2直到可以計算,慢慢的調整差距
bug(建議見第4點):
cssNumber列表中屬性的值使用無單位增量如’+=10’,而初始值單位為px,將按照初始值單位’+=10px’處理後返回。但返回到外部,由於在cssNumber列表中,並不會再次加上單位,按照倍數被設定了。
比如lineHeight初始值20px,使用’+=4’,變成了賦值24倍
// valueParts為增量匹配結果集,兩種形式
// 動畫 adjustCSS(tween.elem, prop, rcssNum.exec( value ), tween)
// css adjustCSS(elem, prop, rcssNum.exec( value ))
function adjustCSS( elem, prop, valueParts, tween ) {
var adjusted,
// 預設比例
scale = 1,
// 最大修正次數
maxIterations = 20,
currentValue = tween ?
// 動畫物件當前屬性值計算
function() { return tween.cur(); } :
function() { return jQuery.css( elem, prop, "" ); },
// 當前用作累加基數的初始值
initial = currentValue(),
// 匹配單位,若不在cssNumber目錄,並且沒帶單位,則當做px
unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
// 由於初始值若匹配到單位,都會是px,不是的在執行css過程中jq也有鉤子修正,所以有可能需要換算的只有cssNumber列表中專案,或者unit不為px且initial有非0數值的(0無需換算)。初始值為字串如"auto",則會在下面按照0處理
initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
rcssNum.exec( jQuery.css( elem, prop ) );
// 單位不同時換算
if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
// 預設使用增量值單位
// 若為cssNumber中屬性,且增量無單位,則使用初始值單位,後面也無需換算了
// 小bug:cssNumber列表中屬性'+=10'若無unit,按照初始值單位'+=10px'處理返回。但返回到外部,由於在cssNumber列表中,並不會再次加上單位,按照倍數被設定了。比如lineHeight初始值20px,使用'+=4',變成了賦值24倍
unit = unit || initialInUnit[ 3 ];
// Make sure we update the tween properties later on
valueParts = valueParts || [];
// Iteratively approximate from a nonzero starting point
// 此處個人覺得沒有 || 1 的寫法沒有必要性,若為0,則無需換算了
initialInUnit = +initial || 1;
// 換算,見難點解釋
do {
// If previous iteration zeroed out, double until we get *something*.
// Use string for doubling so we don't accidentally see scale as unchanged below
scale = scale || ".5";
// Adjust and apply
initialInUnit = initialInUnit / scale;
jQuery.style( elem, prop, initialInUnit + unit );
// Update scale, tolerating zero or NaN from tween.cur()
// Break the loop if scale is unchanged or perfect, or if we've just had enough.
} while (
scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
);
}
if ( valueParts ) {
// 初始值為字串,也將按照0處理,需要注意咯
initialInUnit = +initialInUnit || +initial || 0;
// 根據是否為增量運算判斷直接賦值還是換算後的初始值與增量相加,css運算中只允許增量運算使用該函式
adjusted = valueParts[ 1 ] ?
initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+valueParts[ 2 ];
// 對動畫物件賦初值和末值
if ( tween ) {
tween.unit = unit;
tween.start = initialInUnit;
tween.end = adjusted;
}
}
return adjusted;
}
6、curCSS、getStyles
curCSS( elem, name, computed )
是對getStyles( elem )
的封裝,可以通過computed指定樣式物件替代內部的getStyle。對高版本瀏覽器和低版本IE getStyle分別使用的getComputedStyle、currentStyle,前者是全域性物件下的屬性,所以原始碼中使用了ownerDocument.defaultView指代。
不同的瀏覽器,對屬性的返回可能出現百分比等非px返回值,jq通過鉤子處理個體,curCSS內部也處理了一些情況,比如Chrome、Safari的margin相關屬性值返回百分比,低版本IE的非top等位置屬性返回百分比等。
// #6489,下面會用到的正則
var rmargin = ( /^margin/ );
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
// #6692
var getStyles, curCSS,
rposition = /^(top|right|bottom|left)$/;
// 高版本瀏覽器
if ( window.getComputedStyle ) {
getStyles = function( elem ) {
// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
// IE throws on elements created in popups
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
var view = elem.ownerDocument.defaultView;
// opener指的是開啟該頁面的源頁面的window
if ( !view || !view.opener ) {
view = window;
}
return view.getComputedStyle( elem );
};
// 預設使用getStyles,也可通過computed引數指定樣式物件。內部還有對文件片段和margin類屬性值的特殊處理
curCSS = function( elem, name, computed ) {
var width, minWidth, maxWidth, ret,
style = elem.style;
computed = computed || getStyles( elem );
// getPropertyValue is only needed for .css('filter') in IE9, see #12537
// getComputedStyle(elem).getPropertyValue(name)其實也可以用來獲取屬性,但是不支援駝峰,必須-連線書寫,否則返回""
ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
// 文件片段document fragments的元素通過getComputedStyle取樣式是""或undefined,需要退回到style方式取
// 文件片段中的元素elem.ownerDocument不為文件片段,為docuemnt
if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
ret = jQuery.style( elem, name );
}
if ( computed ) {
// 為了相容有的瀏覽器margin相關方法返回百分比等非px值的情況,由於width輸出是px,並且margin的百分比是按照width計算的,因此可以直接賦值width。設定minWidth/maxWidth是為了保證設定的width不會因為超出限制失效
if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
// 記憶
width = style.width;
minWidth = style.minWidth;
maxWidth = style.maxWidth;
// 把margin的值設定到width,並獲取對應width值作為結果
style.minWidth = style.maxWidth = style.width = ret;
ret = computed.width;
// 還原
style.width = width;
style.minWidth = minWidth;
style.maxWidth = maxWidth;
}
}
// Support: IE
// IE returns zIndex value as an integer.都以字串返回
return ret === undefined ?
ret :
ret + "";
};
// IE 6-8
} else if ( documentElement.currentStyle ) {
getStyles = function( elem ) {
return elem.currentStyle;
};
curCSS = function( elem, name, computed ) {
var left, rs, rsLeft, ret,
style = elem.style;
computed = computed || getStyles( elem );
ret = computed ? computed[ name ] : undefined;
// 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
// 對非位置top|left|right|bottom返回的,先把left屬性儲存,然後把屬性設定到left上,然後取出
if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
// 記憶
left = style.left;
// runtimeStyle是低版本IE中表示執行中樣式,可讀可寫,優先順序大於style設定的
rs = elem.runtimeStyle;
rsLeft = rs && rs.left;
// Put in the new values to get a computed value out
if ( rsLeft ) {
rs.left = elem.currentStyle.left;
}
// 對於百分比,是以父元素寬度為基準。而對於fontSize,設定到left的1rem大小則是固定的
style.left = name === "fontSize" ? "1em" : ret;
ret = style.pixelLeft + "px";
// 還原
style.left = left;
if ( rsLeft ) {
rs.left = rsLeft;
}
}
// Support: IE
// IE returns zIndex value as an integer.數字以字串形式返回
return ret === undefined ?
ret :
ret + "" || "auto";
};
}
7、cssHooks鉤子
正如第4點提到的,cssHooks存在的主要目的是應對存取出現的不一致行為。jq用support物件放置測試後的相容性資訊,原始碼#6519 - #6690行有很多support物件的樣式方面相容測試,內部通過addGetHookIf( conditionFn, hookFn )
來繫結鉤子。鉤子有個computed引數,用於標記是jQuery.css/style哪個方法的讀操作觸發的,對應true/false
存:存的不一致只有一個
1、opacity透明度對於IE內部使用filter,需要設定為alpha(opacity=100*value)的形式
*、對於padding、borderWidth、height、width的存只是做了負數變為0的特殊處理。
取:取的不一致比較多
1、opacity的”“按”1”處理,對於需要使用filter的IE低版本也要hook
2、height、width的獲取需要display不為none和帶有table的任意值(除了table、table-cell、table-caption三樣),因此提供了swap( elem, options, callback, args )
用於以指定屬性狀態呼叫函式取值,之後還原狀態
3、marginLeft、marginRight對於不支援返回可靠值的瀏覽器做處理,marginRight在display為”inline-block”下取值,marginLeft通過getBoundingClientRect比對與marginLeft設定為0後的位置差得到
4、top、left中有可能返回百分比的瀏覽器,先取值,若不為px單位,則呼叫內部position方法計算top、left(但是此方法是相對有定位父集或html的,對於position為relative的是有bug的,個人建議原始碼中可以對relative的使用getBoundingClientRect比對處理)
擴充套件: innerWidth()/innerHeight()/outerWidth()/outerHeight()
盒模型預設是寬高不包括padding、border、margin。css3裡有boxSizing屬性,content-box|border-box|inherit分別代表 “不包括padding、border、margin” | “包含border和padding” | “繼承”。
jq通過innerWidth()/innerHeight()可以直接查詢/設定content-box區域的長寬;通過outerWidth()/outerHeight()可查詢/設定為border-box區域的長寬,增加一個引數true,如([value, ]true),可查詢/設定為border-box區域加上margin區域的總長寬。
jq仍然是設定height、width,不過它會進行換算。通過augmentWidthOrHeight( elem, name, extra, isBorderBox, styles )
計算增量(數值),通過getWidthOrHeight( elem, name, extra )
得到最終值(帶px字串)。通過extra
來指明按照content、padding、border、margin中哪一個級別。
注意:
cssHooks內若要得到自身屬性的樣式,不呼叫jQuery.css,而是直接呼叫curCSS,包括getWidthOrHeight內,因為curCSS是純粹的取值,不會呼叫鉤子造成死迴圈
/* #1307 contains
* 節點包含。後面經常用來驗證是否為文件片段中的元素
---------------------------------------------------------------------- */
// /^[^{]+\{\s*\[native \w/ -> 匹配內部方法 'funtion xxx() { [native code] }'
hasCompare = rnative.test( docElem.compareDocumentPosition );
// 返回布林值,true表示 b節點在a節點內/a文件的根節點內(節點相等為false)
// ie9+及其他瀏覽器支援compareDocumentPosition,ie6-8支援contains,比較老的safari都不支援使用下面的函式
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
// a.compareDocumentPosition( bup ) = 16表示 a包含b
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
// 若不等於父節點,繼續冒泡知道根節點
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};
/* #6494 swap
* elem的style在options狀態下呼叫callback.apply(elem, args),然後改回原屬性。返回查到的值
---------------------------------------------------------------------- */
var swap = function( elem, options, callback, args ) {
var ret, name,
old = {};
// 記錄原有值,設定上新值
for ( name in options ) {
old[ name ] = elem.style[ name ];
elem.style[ name ] = options[ name ];
}
ret = callback.apply( elem, args || [] );
// 還原舊值
for ( name in options ) {
elem.style[ name ] = old[ name ];
}
return ret;
};
/* #6816 addGetHookIf
* conditionFn()執行後返回true,說明支援,不會繫結hookFn鉤子
---------------------------------------------------------------------- */
function addGetHookIf( conditionFn, hookFn ) {
// Define the hook, we'll check on the first run if it's really needed.
// 預執行和懶載入的方式均可,原始碼選擇了懶載入。第一次當做鉤子執行呼叫時繫結真實鉤子或刪除
return {
get: function() {
if ( conditionFn() ) {
// 支援,無需鉤子
delete this.get;
return;
}
// 需要鉤子,定義為hookFn。即使是第一次也要執行一次
return ( this.get = hookFn ).apply( this, arguments );
}
};
}
/* #6935 setPositiveNumber
* 保證非負值,保留單位,subtract可以指定需要減去的值
---------------------------------------------------------------------- */
function setPositiveNumber( elem, value, subtract ) {
var matches = rnumsplit.exec( value );
return matches ?
// Guard against undefined "subtract", e.g., when used as in cssHooks
Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
value;
}
/* #6944 augmentWidthOrHeight
* 根據extra型別計算增量(相對於height/width取值),返回純數值
* 注意:讀取為增量,寫入為減量
---------------------------------------------------------------------- */
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
// border-box -> border, content-box -> content。無需修正為4
var i = extra === ( isBorderBox ? "border" : "content" ) ?
// If we already have the right measurement, avoid augmentation
4 :
// height: 0(top) 2(bottom) width: 1(right) 3(left)
// cssExpand = [ "Top", "Right", "Bottom", "Left"];
name === "width" ? 1 : 0,
val = 0;
for ( ; i < 4; i += 2 ) {
// border-box content-box 想變為margin級別都需要 + margin值
if ( extra === "margin" ) {
val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
}
if ( isBorderBox ) {
// border-box = content級別 + "padding" + "border"(下面那個)
if ( extra === "content" ) {
// true 表示強制去單位
val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
}
// border-box = padding級別 + "border"
if ( extra !== "margin" ) {
val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
} else {
// 邏輯能走到這裡,說明一定不是content級別,否則 i = 4
// content-box 變為任意級別都要 + padding 。 true 表示強制去單位
val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
// 變為border級別、margin級別要 + border
if ( extra !== "padding" ) {
val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
}
}
return val;
}
/* #6988 getWidthOrHeight
* 用於讀取,會根據extra加上augmentWidthOrHeight增量
---------------------------------------------------------------------- */
function getWidthOrHeight( elem, name, extra ) {
// getWidthOrHeight = contentBox級別值 + augmentWidthOrHeight增量
// 這裡直接用offsetWidth/offsetHeight返回的borderbox級別值作為基礎值,因此下面需要調整,valueIsBorderBox預設值為true,表示為border-box
var valueIsBorderBox = true,
val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
styles = getStyles( elem ),
// 只有支援boxSizing屬性,且為border-box,isBorderBox才為true,否則要調整val
isBorderBox = support.boxSizing &&
jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
// Support: IE11 only,全屏瀏覽下bug,不瞭解(逃
// In IE 11 fullscreen elements inside of an iframe have
// 100x too small dimensions (gh-1764).
if ( document.msFullscreenElement && window.top !== window ) {
// Support: IE11 only
// Running getBoundingClientRect on a disconnected node
// in IE throws an error.
if ( elem.getClientRects().length ) {
val = Math.round( elem.getBoundingClientRect()[ name ] * 100 );
}
}
// some non-html elements return undefined for offsetWidth, so check for null/undefined
// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
// svg 和 MathML 可能會返回 undefined,需要重新求值
if ( val <= 0 || val == null ) {
// 直接獲取 width/height 作為基礎值,若之後呼叫elem.style,說明support.boxSizingReliable()一定為false
val = curCSS( elem, name, styles );
if ( val < 0 || val == null ) {
val = elem.style[ name ];
}
// 匹配到非px且帶單位的值,則直接退出
if ( rnumnonpx.test( val ) ) {
return val;
}
// valueIsBorderBox意思是得到的value是borderbox級別的,由於調整為了curCSS取值,因此,必須要isBorderBox為true,不可靠值當做content級別處理(因為border、padding容易獲取到準確值,val === elem.style[ name ]除外)
valueIsBorderBox = isBorderBox &&
( support.boxSizingReliable() || val === elem.style[ name ] );
// Normalize "", auto, and prepare for extra
// 強制去單位,"auto"等字串變為0
val = parseFloat( val ) || 0;
}
// use the active box-sizing model to add/subtract irrelevant styles
return ( val +
augmentWidthOrHeight(
elem,
name,
// 若沒指定,預設值跟盒模型一致
extra || ( isBorderBox ? "border" : "content" ),
// 表示基數val是否為borderBox,extra和它一致說明無需累加
valueIsBorderBox,
styles
)
) + "px";
}
/* #7201 cssHooks[ "height", "width" ]
* 防止設定負數。支援根據extra指定級別修正設定/獲取值
---------------------------------------------------------------------- */
jQuery.each( [ "height", "width" ], function( i, name ) {
jQuery.cssHooks[ name ] = {
get: function( elem, computed, extra ) {
// innerWidth等API內部只調用jQuery.css,style方式不用鉤子,所以false則退出
if ( computed ) {
// rdisplayswap = /^(none|table(?!-c[ea]).+)/
// cssShow = { position: "absolute", visibility: "hidden", display: "block" }
// display影響了定位資訊的獲取,比如offsetWidth為0。先設定cssShow屬性獲取到值,然後改回屬性
return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
elem.offsetWidth === 0 ?
swap( elem, cssShow, function() {
return getWidthOrHeight( elem, name, extra );
} ) :
getWidthOrHeight( elem, name, extra );
}
},
set: function( elem, value, extra ) {
var styles = extra && getStyles( elem );
// 設定非負值,在設定時增量即為減量,第三個引數對於substract引數
return setPositiveNumber( elem, value, extra ?
augmentWidthOrHeight(
elem,
name,
extra,
support.boxSizing &&
jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
styles
) : 0
);
}
};
} );
/* #7053 #7233 cssHooks[ "opacity" ]
* 根據是否支援opacity,判斷內部使用opacity還是filter
---------------------------------------------------------------------- */
cssHooks: {
opacity: {
get: function( elem, computed ) {
// elem.style['opacity']呼叫無需鉤子,所以false則不處理
if ( computed ) {
// We should always get a number back from opacity
var ret = curCSS( elem, "opacity" );
return ret === "" ? "1" : ret;
}
}
}
},
// #7233 filter部分
if ( !support.opacity ) {
jQuery.cssHooks.opacity = {
// computed -> css(true)、style(false) 均hook
get: function( elem, computed ) {
// IE uses filters for opacity
// ropacity = /opacity\s*=\s*([^)]*)/i,
return ropacity.test( ( computed && elem.currentStyle ?
elem.currentStyle.filter :
elem.style.filter ) || "" ) ?
( 0.01 * parseFloat( RegExp.$
相關推薦
jQuery原始碼解析(4)—— css樣式、定位屬性
閒話
原計劃是沒有這篇博文的,研究animation原始碼的時候遇到了css樣式這個攔路虎。比如jQuery支援“+=10”、“+=10px”定義一個屬性的增量,但是有的屬性設定時可以支援數字,有的必須有單位;在對屬性當前值讀取時,不同的瀏覽器可能返回不同的單
jQuery原始碼解析(1)—— jq基礎、data快取系統
閒話
jquery 的原始碼已經到了1.12.0 版本,據官網說1版本和2版本若無意外將不再更新,3版本將做一個架構上大的調整。但估計能相容IE6-8的,也許這已經是最後的樣子了。
我學習jq的時間很短,應該在1月,那時的版本還是1.11.3,通過看妙味課堂
jQuery原始碼解析(2)—— Callback、Deferred非同步程式設計
閒話
這篇文章,一個月前就該出爐了。跳票的原因,是因為好奇標準的promise/A+規範,於是學習了es6的promise,由於興趣,又完整的學習了《ECMAScript 6入門》。
本文目的在於解析jQuery對的promise實現(即Deferred,是
Spring原始碼解析(4):IOC過程下
上文說到populateBean方法中,對被@Autowired註解的屬性方法進行注入。在這之後,BeanFactory執行applyPropertyValues方法,這個方法中,一個是把之前解析出來的屬性值設定到bean中去;一個是繼續解析出BeanDefinition中定
死磕 java同步系列之ReentrantLock原始碼解析(一)——公平鎖、非公平鎖
問題
(1)重入鎖是什麼?
(2)ReentrantLock如何實現重入鎖?
(3)ReentrantLock為什麼預設是非公平模式?
(4)ReentrantLock除了可重入還有哪些特性?
簡介
Reentrant = Re + entrant,Re是重複、又、再的意思,entrant是enter的名詞或
Spring原始碼解析(四)——元件註冊4
/**
* 給容器中註冊元件;
* 1)、包掃描+元件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的類]
* 2)、@Bean[匯入的第三方包裡面的元件]
* 3)、@Import[快速給容器中匯入一個
jQuery原始碼解析(jQuery物件的例項屬性和方法)
1、記錄版本號 以及 修正constructor指向
jquery: core_version,
constructor: jQuery,
因為jQuery.prototype={ ... } 這種寫法將自動生成的jQuery.prototype.constructor
jquery 1.7.2原始碼解析(二)構造jquery物件
構造jquery物件
jQuery物件是一個類陣列物件。
一)建構函式jQuery()
建構函式的7種用法:
1.jQuery(selector [, context ])
傳入字串引數:檢查該字串是選擇器表示式還是HTML程式碼。如果是選擇器表示式,則遍歷文件查詢匹配的DOM元
jQuery深入之原始碼解析(一)
總體架構
可以看出來jQuery主要有三個模組:
入口模組、功能模組、底層支援模組。
- 入口模組
在構造jQuery物件模組中,如果在呼叫建構函式建立jQuery物件時,會呼叫選擇器
jQuery原始碼解析(架構與依賴模組)
jQuery有3種針對文件載入的方法$(document).ready(function() {
// ...程式碼...
})
//document ready 簡寫
$(function() {
// ...程式碼...
})
$(document).load(function() {
HLS學習(五)HLSDownloader原始碼分析(4)解析Master PlayList
解析Master PlayList
PlayList就是m3u8檔案或者索引檔案,Master PlayList也叫一級索引檔案。
解析Master PlayList的過程如下:
1、
jQuery源代碼解析(1)—— jq基礎、data緩存系統
代碼解析 post 方法 step 作用域鏈 垃圾清理 版本 get initial
閑話
jquery 的源代碼已經到了1.12.0 版本號。據官網說1版本號和2版本號若無意外將不再更新,3版本號將做一個架構上大的調整。但預計能兼容IE6-8的。或許
jQuery源代碼解析(3)—— ready載入、queue隊列
else ng- settime eve ref promise ont 出隊 function
ready、queue放在一塊寫,沒有特殊的意思,僅僅是相對來說它倆可能源代碼是最簡單的了。ready是在dom載入完畢後。以最高速度觸發,非常實用。que
Mybatis原始碼分析(4)—— Mapper的建立和獲取
Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用?
就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的
HashMap原始碼解析(JDK8)
前言
這段時間有空,專門填補了下基礎,把常用的ArrayList、LinkedList、HashMap、LinkedHashMap、LruCache原始碼看了一遍,List相對比較簡單就不單獨介紹了,Map準備用兩篇的篇幅,分別介紹HashMap和(LruCache+LinkedHa
Spring原始碼解析(十三)——AOP原理——AnnotationAwareAspectJAutoProxyCreator註冊
* 2、 AnnotationAwareAspectJAutoProxyCreator: * AnnotationAwareAspectJAutoProxyCreator &nbs
Spring原始碼解析(八)——生命週期——BeanPostProcessor在spring底層的使用
一、ApplicationContextAwareProcessor
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import or
Spring原始碼解析(七)——生命週期——BeanPostProcessor
https://blog.csdn.net/u011734144/article/details/72600932
http://www.cnblogs.com/lucas2/p/9430169.html
BeanPostProcessor:bean的後置處理器。在bean
Spring原始碼解析(三)——元件註冊3
@Scope設定元件作用域
import com.ken.domain.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Config
Spring原始碼解析(二)——元件註冊2
import com.ken.service.BookService;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.