通過閱讀源碼我們發現show,hide,toggle調用了showHide和isHidden這2個方法,所以我們要搞明白原理必須先看一下這2個方法。
jQuery.fn.extend({ ................. show: function() { return showHide( this, true ); }, hide: function() { return showHide( this ); }, toggle: function( state, fn2 ) { var bool = typeof state === "boolean"; 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(); } }); } });
isHidden比較簡單,接受2個參數,調用了jq的工具方法css來判斷當前的display是否為none,為none返回真,否則走后面的contains方法
contains用於判斷元素是否包含在元素所在的文檔中,elem.ownerDocument其實就是document, elem是元素
由此可以看出,如果元素不包含在當前的文檔中,jq也認為這個元素是隱藏的,比如document.createElement創建出來的元素。
function isHidden( elem, el ) { elem = el || elem; return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); }
showHide方法代碼有點長,我們發現jQuery._data(),css_defaultDisplay(),curCSS(),這3個東西又是什么呢,稍后再分析。
showHide接受2個參數,elements : 元素集合 、 show : 布爾值(true表示顯示 、false表示隱藏)
function showHide( elements, show ) { var elem, display, values = [], index = 0, length = elements.length; for ( ; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } values[ index ] = jQuery._data( elem, "olddisplay" ); if ( show ) { // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not if ( !values[ index ] && elem.style.display === "none" ) { elem.style.display = ""; } // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element 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" ) { jQuery._data( elem, "olddisplay", display ); } } } // Set the display of most of the elements in a second loop // to avoid the constant reflow for ( index = 0; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } if ( !show || elem.style.display === "none" || elem.style.display === "" ) { elem.style.display = show ? values[ index ] || "" : "none"; } } return elements; }
我們先看代碼最下面,它遍歷了元素集合,如果元素沒有樣式則跳過,哪些元素沒有樣式呢,比如文本節點、注釋節點等等。。。
我們特別注意一下這句代碼:elem.style.display = show ? values[ index ] || "" : "none";
當為show時,它並沒有直接寫"block"而是一個變量賦值的操作,當它沒有時才會空。
這里主要是為了區分元素的樣式屬性,比如span是行內元素如果給他賦blcok就變成塊元素了,所以values[ index ]這個變量是在上面的代碼處理過得到的。
for ( index = 0; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } if ( !show || elem.style.display === "none" || elem.style.display === "" ) { elem.style.display = show ? values[ index ] || "" : "none"; } }
現在回過頭看代碼的開始部分就容易理解了,先獲取元素默認的display值,一般第一次獲取肯定是空的,因為這個值需要在隱藏時保存,我們看到下面就明白了,這里先不解釋。
values[ index ] = jQuery._data( elem, "olddisplay" );
接着往下看,if(show)里面做了2個判斷,第一個判斷沒什么說的,重點看第二個。
當行內樣式為空,且是一個隱藏的元素(css樣式表中display:none),則執行了一個比較有意思的操作。
利用css_defaultDisplay方法能夠正確的獲取到元素的樣式屬性,什么意思呢?
我們可以這樣想,當一個元素初始的時候給一個display為none,我們肯定獲取不到該元素顯示時所對應的值,也就是說我們不知道是block,或是inline,換句話說,我們不知道該元素是塊元素還是行內元素。
可是css_defaultDisplay方法能夠做到,那么它是怎么實現的呢?
這里就不貼css_defaultDisplay的源碼了,簡單說一下它的原理,其實很簡單
首先獲取元素的nodeName(標簽名) -> createElement動態的創建 -> 添加到document.body中 -> 獲取該元素的display(元素默認的display都是顯示,如div是block,span
是inline) -> 刪除該元素
if ( show ) { if ( !values[ index ] && elem.style.display === "none" ) { elem.style.display = ""; } if ( elem.style.display === "" && isHidden( elem ) ) { values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); } } else { ...................... }
好了,if(show)里面的代碼分析完了,我們再看分析一下else里面的代碼
先獲取display的值,再是一個判斷,如果values[ index ]沒有值並且display不是none,則把元素當前的display值保存起來,方便在if(show)中使用,就是上面提到的,不記得的話再去回顧一下。
if ( show ) { ..................... } else { display = curCSS( elem, "display" ); if ( !values[ index ] && display !== "none" ) { jQuery._data( elem, "olddisplay", display ); } }
最后分析一下toggle方法,其實也蠻簡單的,遍歷元素集合,判斷bool是否為一個布爾值,是的話則判斷state,state為真則調show方法,為假則調hide方法
如果bool不是布爾值,則判斷該元素是否隱藏,隱藏的話則調show方法,顯示的話則調hide方法
toggle: function( state, fn2 ) { var bool = typeof state === "boolean"; ........................... return this.each(function() { if ( bool ? state : isHidden( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); } }); }
總結:
這3個方法最難的是show方法,先要獲取values[ index ]保存的display默認值,如果它是空的,則需要調用css_defaultDisplay方法來變向的獲取。
hide方法只是簡單的dispaly=none,toggle只是根據state或者isHidden來調show方法或hide方法。
