記jQuery.fn.show的一次踩坑和問題排查


最近很少已經很少用jQuery,因為主攻移動端,常用Zepto,其實很多細節和jQuery並不一樣。 最近又無意中接觸到了PC的需求和IE6, 使用了jQuery,剛好踩坑了,特意記錄一下。

> 本文內容如下: > - 問題 > - 解決 > - jQuery.fn.show()和jQuery.fn.show(0)到底發生了什么 > - 結語 > - 參考和引用

JavaScript - 前端開發交流群:377786580

問題

最近很少用jQuery,因為主攻移動端,常用Zepto,其實很多細節和jQuery並不一樣。以前讀過Zepto的源碼,所以完全知道zepto.fn.show/zepto.fn.hide到底做了什么。
一直只記得jQuery.fn.show/jQuery.fn.hide方法在后來被改寫過,也沒怎么去關注過這里,最近又無意中接觸到了PC的需求和IE6, 使用了jQuery,剛好踩坑了,特意記錄一下。
問題就是有一個<a>標簽默認是隱藏的(display:none),做了一些業務邏輯的處理之后呢把這個<a>顯示出來(通過jQuery.fn.show),但是這個<a>標簽卻被加上了新的樣式:

    <a href="javascript:;" style="display:inline-block;"></a>

查了一下當時調用jQuery.fn.show的代碼:

    $('#id').show(0); //給a加上了display:inline-block;

解決

觸發這個問題就是因為自己錯誤的以為jQuery.fn.show/jQuery.fn.hide會有默認動畫時間,於是顯示隱藏元素的時候喜歡這樣處理:

	$('#id').show(0);
	$('#id').hide(0);

其實是自己對jQuery.fn.show/jQuery.fn.hide記錯了,以前學jQuery的時候就只記得了個jQuery動畫有個默認的時間,是300ms。但沒記API,其實是:

jQuery的動畫系列函數,slideDown、slideUp、slideToggle、fadeIn、fadeOut、fadeToggle、fadeTo、toggle都是默認400ms動畫時間的,而show/hide默認是沒有動畫時間的。

參見jQuery 1.11.0源碼:

	jQuery.each({
		slideDown: genFx("show"),
		slideUp: genFx("hide"),
		slideToggle: genFx("toggle"),
		fadeIn: { opacity: "show" },
		fadeOut: { opacity: "hide" },
		fadeToggle: { opacity: "toggle" }
	}, function( name, props ) {
		jQuery.fn[ name ] = function( speed, easing, callback ) {
			return this.animate( props, speed, easing, callback );
		};
	});

合並動畫配置(設置動畫默認時間)的代碼是jQuery.speed方法:

	jQuery.speed = function( speed, easing, fn ) {
		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
			complete: fn || !fn && easing ||
				jQuery.isFunction( speed ) && speed,
			duration: speed,
			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
		};
		
		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default /*默認動畫時間(ms)*/;
		if ( opt.queue == null || opt.queue === true ) {
			opt.queue = "fx";
		}
		opt.old = opt.complete;
		opt.complete = function() {
			if ( jQuery.isFunction( opt.old ) ) {
				opt.old.call( this );
			}
	
			if ( opt.queue ) {
				jQuery.dequeue( this, opt.queue );
			}
		};
		return opt;
	};
	
	//...
	
	jQuery.fx.speeds = {
		slow: 600,
		fast: 200,
		// Default speed
		_default: 400
	};

后來查到問題是給方法傳遞了一個參數0導致的問題,把參數去掉即可,改成下面的代碼即可:

    <script>
        $('#id').show();
    </script>
    <a href="javascript:;" style=""></a>

然后被妹子追問為什么,回答不上來,只記得這倆API后來調整過。

jQuery.fn.show()和jQuery.fn.show(0)到底發生了什么

之后我就覺得有必要再去深入了解下jQuery.fn.show/jQuery.fn.hideAPI了,至少要了解下這倆API現在到底做了什么。

剛開始我仍然天真的以為jQuery.fn.show在不傳遞參數的情況下會有默認動畫時間,直到翻閱源碼版本至jQuery 1.4.0發現都對沒有參數的情況進行了display的處理,反而是傳遞了speed之后會走動畫處理。

jQuery.fn.extend({
	//$('id').show();
	show: function( speed, callback ) {
		if ( speed != null ) {
			return this.animate( genFx("show", 3), speed, callback);
		} else {
			//speed===undefined
			for ( var i = 0, l = this.length; i < l; i++ ) {
				//操作元素的display
				var old = jQuery.data(this[i], "olddisplay");
				this[i].style.display = old || "";
				if ( jQuery.css(this[i], "display") === "none" ) {
					var nodeName = this[i].nodeName, display;
					if ( elemdisplay[ nodeName ] ) {
						display = elemdisplay[ nodeName ];
					} else {
						var elem = jQuery("<" + nodeName + " />").appendTo("body");
						display = elem.css("display");
						if ( display === "none" ) {
							display = "block";
						}
						elem.remove();
						elemdisplay[ nodeName ] = display;
					}
					jQuery.data(this[i], "olddisplay", display);
				}
			}
			for ( var j = 0, k = this.length; j < k; j++ ) {
				this[j].style.display = jQuery.data(this[j], "olddisplay") || "";
			}
			return this;
		}
	},

	hide: function( speed, callback ) {
		if ( speed != null ) {
			return this.animate( genFx("hide", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ) {
				var old = jQuery.data(this[i], "olddisplay");
				if ( !old && old !== "none" ) {
					jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
				}
			}
			for ( var j = 0, k = this.length; j < k; j++ ) {
				this[j].style.display = "none";
			}
			return this;
		}
	}
});

jQuery 1.11.0代碼整理后閱讀起來好了很多:

	jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
		var cssFn = jQuery.fn[ name ];
		jQuery.fn[ name ] = function( speed, easing, callback ) { //$('#id').show();
			//speed===undefined
			return speed == null || typeof speed === "boolean" ?
				cssFn.apply( this, arguments ) :
				this.animate( genFx( name, true ), speed, easing, callback );
		};
	});

可以看見當在jQuery 1.11.0中,當speed參數為null、undefined、boolean的時候會進入css直接處理分支,而不會走動畫

所以下面的代碼完全走的是不同的分支:

	$('#id').show(); //走css處理分支
	$('id').show(0); //走animate處理分支

  

那么<a>上的display:inline-block從何而來呢?於是搜索了一下inline-block,發現了這一段代碼:

function defaultPrefilter( elem, props, opts ) {
	/* jshint validthis: true */
	var prop, value, toggle, tween, hooks, oldfire, display, dDisplay,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHidden( elem ),
		dataShow = jQuery._data( elem, "fxshow" );
	
	// ...

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
		display = jQuery.css( elem, "display" ); //獲取元素樣式中的display
		dDisplay = defaultDisplay( elem.nodeName ); //獲取元素默認的display
		if ( display === "none" ) {
			display = dDisplay;
		}
		if ( display === "inline" && jQuery.css( elem, "float" ) === "none" ) {
			if ( !support.inlineBlockNeedsLayout || dDisplay === "inline" ) {
				//現代瀏覽器下,當元素默認的display為inline,則調整為inline-block
				style.display = "inline-block";
			} else {
				style.zoom = 1;
			}
		}
	}

	// ...
}

defaultPrefilter方法在jQuery內置的動畫對象Animation中調用,用於修正執行動畫前的一些細節。
到了這里再想想動畫的一些操作因素,也就可以理解為什么會這樣了:

jQuery中的動畫操作的無非是位置、透明度、寬高幾個點。
有些動畫涉及到寬高的改變(例如:jQuery.fn.show/jQuery.fn.hide改變的就是元素的width+heightjQuery.fn.slideDown改變的是height),那么就需要把這些元素設置成可以改變寬高的(行)塊級元素。因為行級元素是不能通過css改變寬高的。
而<a>標簽默認是inline的,當對它操作width/height(即jQuery.fn.show)的時候,需要把它改成inline-block,讓動畫對它設置的寬高生效。

結語

至此,問題追查完畢,我也可以好好的跟妹子吹吹牛逼啦~~~~~~~當然除了這個,更多的是自己的反思,想起了之前做的代碼里很多時候使用的都是:

	$('#id').show(0);

加這一個參數和不加參數光看看源碼就知道是天壤之別了,更別提什么性能,IE678套餐可能造成的影響了。
其實問題的根源仍然在於我對代碼的了解程度不夠,對於當前代碼的認知自己竟然覺得滿足了。想想剛學js的我可是充滿了好奇心各種探索造輪子查資料讀源碼,隨着時間的推移,技術實力和視野逐漸的提升,讓自己變得越來越懶惰,越來越容易滿足,這樣的狀態並不好。
任何時候,都應該清楚的知道自己擼的代碼到底發生了什么,學無止境,共勉。

JavaScript - 前端開發交流群:377786580

作者:linkFly
聲明:嘿!你都拷走上面那么一大段了,我覺得你應該也不介意順便拷走這一小段,希望你能夠在每一次的引用中都保留這一段聲明,尊重作者的辛勤勞動成果,本文與博客園共享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM