iT邦幫忙

DAY 16
8

Javascript面面觀系列 第 16

Javascript面面觀:網頁篇《jQuery inside - 核心架構》

  • 分享至 

  • xImage
  •  

jQuery是目前最紅的Javascript Library之一,這要歸功於他使用簡單,而且對於CSS Selector的支援很廣泛。研究jQuery核心的重點,首先就是他為何使用簡單,其次就是他怎麼包裝DOM Node及事件。
以下用jquery-1.3.2.js來做觀察

架構
jQuery只暴露給Global兩個變數,一個是jQuery,一個是$,這兩個其實是同一件東西...可以用簡單的程式檢測一下:

<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
	if ($ === jQuery) alert("true");
});
</script>

所以碰到這兩個名字就要留意一下。

接著就來追蹤一下程式。首先會注意到,所有的程式都包在一個匿名函數中,這是大部分library與framework常見的做法。

其實沒幾行就看到了關鍵(24~27行):

 	jQuery = window.jQuery = window.$ = function( selector, context ) {
 		// The jQuery object is actually just the init constructor 'enhanced'
 		return new jQuery.fn.init( selector, context );
 	},

從24行開始,就在window(也就是global)生成了jQuery及$兩個變數,內容都是一個函數。同時也把這個函數指派給匿名函數內的jQuery變數,以加快內部存取。(這樣就不必做context switch,前面var window = this以及undefined的意思也一樣)

透過這個函數可以知道,我們在呼叫$()時,他其實就只是用jQuery.fn.init()這個constructor new 一個instance回傳。接下來看看與這個constructor相關的結構(35~541行):

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) {
		......
	},
	selector: '',
	......
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

init()是jQuery.fn的property,所以使用new jQuery.fn.init()時,這個物件內的this會指向jQuery.fn。在此同時又指派jQuery.fn.init.prototype的值為jQuery.fn,所以透過jQuery()或是$()使用他的property時,就會沿著prototype chain找到jQuery.fn上所有的properties。

當init()收到selector時會依據他是什麼東西來做一些處理。簡單地說,你可以傳入dom node、function或是CSS3 Selector字串,他最後會想辦法找到你指定的所有dom node,如果找不到,他就會使用window.document當作找到的dom node,然後把this做成類似陣列的物件把找到的dom node存起來,而this.length也會改成這個陣列的元素數目。所以只要是掛在jQuery.fn底下的函數,都可以透過this[n]取得找到的dom node。

延伸性
接下來看如何延伸jQuery就知道有多簡單了(562~610行)。

jQuery.extend = jQuery.fn.extend = function() {
	// copy reference to target object
	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) )
		target = {};

	// extend jQuery itself if only one argument is passed
	if ( length == i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ )
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null )
			// Extend the base object
			for ( var name in options ) {
				var src = target[ name ], copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy )
					continue;

				// Recurse if we're merging object values
				if ( deep && copy && typeof copy === "object" && !copy.nodeType )
					target[ name ] = jQuery.extend( deep, 
						// Never move original objects, clone them
						src || ( copy.length != null ? [ ] : { } )
					, copy );

				// Don't bring in undefined values
				else if ( copy !== undefined )
					target[ name ] = copy;

			}

	// Return the modified object
	return target;
};

不管deep copy的話(比較少用,除非你想要extend很複雜的物件中的東西給jQuery或jQuery.fn),他就是把你傳入的物件,依序把property copy給自己(看是透過jQuery.extend()還是jQuery.fn.extend())。透過jQuery.extend()執行時,就會延伸jQuery,透過jQuery.fn.extend()執行時,就會延伸jQuery.fn。由於在jQuery.fn底下就可以存取到$(...)找到的dom node,所以這就是製作plugin的標準方法。而jQuery底下的property與函數,則可以當作靜態的工具來用。

實驗
接下來就試寫一個簡單的plugin,功能只有把jquery找到的dom node的tagName秀出來。(範例使用jsbin.com來做,程式跟html分開,大家有興趣可以上jsbin.com看一下說明)測試網址:http://jsbin.com/ewufu

網頁部份:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> 
<title>Sandbox</title> 
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 
<style type="text/css" media="screen"> 
body { background-color: #000; font: 16px Helvetica, Arial; color: #fff; } 
</style> 
 
 
<div id="panel">target panel</div> 
<div id="test1" class="cls">test1.cls</div> 
<div id="test2" class="cls">test2.cls</div> 
<div id="test3" class="cls">test3.cls</div> 
 

程式部份:

$.fn.extend({showTagName: function(){ 
  var i=0; 
  for(;i<this.length;i++) { 
    alert(this[i].tagName); 
  } 
  return this; 
}}); 
$('.cls').showTagName();

結束。

小結
jQuery的核心,使用一個非常精簡有容易使用的架構,讓開發者可以很容易擴充,而使用者也很容易使用。而jQuery的核心研發人員,只需要加速CSS Selector的速度,並且維護與各家瀏覽器的相容性,然後大家用起來就很高興了。核心的程式加上註解也只有四千多行,除了最核心的架構與功能,最大的部份就是他的selector引擎,另外一個比較大的部分則是處理event的功能。這些細節就留待後話了。


上一篇
Javascript面面觀:網頁篇《瀏覽器之爭與網頁大問題》
下一篇
Javascript面面觀:網頁篇《jQuery inside - selector》
系列文
Javascript面面觀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
jamesjan
iT邦高手 1 級 ‧ 2009-10-28 08:20:25

trace javascript 程式,一直是很大的困擾,有沒有什麼好方法呢?
雖然現在的開發工具已經支援 javascript 的 code insight,但是要 trace javascript 程式好像還是蠻困難的一件事情。

fillano iT邦超人 1 級 ‧ 2009-10-28 08:53:04 檢舉

firefox、chrome跟IE都有debuger可以做trace,可以設中斷點,也可以在debuger中顯示值。

chrome的debuger還有附一個profiler,可以快速找到程式的瓶頸(他會顯示所有資源在瀏覽器中載入所花費的時間)。

IE的debuger需要另外下載,據說IE8的做的不錯,但是我沒用過。

0
fillano
iT邦超人 1 級 ‧ 2009-10-28 11:54:46

阿,還沒確認就碰到submit按鈕...

我大概列一下我比較常用的工具,我之後的文章也會提到一些。

  1. 做單元測試的話,最常用的是jsunit
  2. 開發工具的話,目前還是Firefox比較多,我會用Firebug或WebDeveloper這兩個工具。做trace的話,大概就用firebug了,他也可以加單元測試外掛(fireunit)。設中斷點後,他會預設顯示一些相關scope的變數值,你也可以自己加expression來監看。(firebug依賴另外一個plugin叫做javascript debugger,這個工具功能更完整,可以做單步trace,不過自己很少需要做到這個程度)。另外有一些外掛可以監看header...例如httpfox,或是像yslow可以提供一些效能建議。webdeveloper的介面在做dom inspection比fireunit好用,所以需要時我也會用。
  3. 做整合測試或是回歸測試,有一個工具叫做Selenium,可以透過他的firefox plugin寫測試腳本,然後再來運行。他還有一套工具叫做Selenium RC,可以用來帶出瀏覽器做測試,對自動化測試很有幫助。

很多人稱讚IE8的developer tools,不過我沒用過,也許有時間可以來試試看。

我之後會寫文章介紹jsunit、selenium、fireunit還有John Resig最近開發的一套分散測試工具叫做testswarm。

jamesjan iT邦高手 1 級 ‧ 2009-10-28 12:01:57 檢舉

^^b

0
sean666
iT邦新手 5 級 ‧ 2020-02-20 09:37:32

鑑往知來研究甚麼是 ES6 ,2019年 六月已經到了 ES10,發現版主這個說明詳細的 ES5,串聯了瀏覽器、JavaScript 與 jQuery,十分詳細。

附上程式碼檢測,jQuery只暴露給Global兩個變數,一個是jQuery,一個是$,這兩個其實是同一件東西...

  • 更改為 CDN連結
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<script type="text/javascript">
	$(document).ready(function(){
		if ($ === jQuery) alert("true");
	});
	</script>
</head>
<body>
</body>
</html>
  • 實驗的部分使用 span 來區分
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Sandbox</title>
	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<style type="text/css" media="screen"> 
	body { background-color: #000; font: 16px Helvetica, Arial; color: #fff; } 
	</style>
</head>
<body>
	<div id="panel">target panel</div> 
	<span id="test1" class="cls">test1.cls</span>
	<div id="test2" class="cls">test2.cls</div> 
	<span id="test3" class="cls">test3.cls</span> 
	<script>
		$.fn.extend({showTagName: function(){ 
  		var i=0; 
  		for(;i<this.length;i++) { 
    	alert(this[i].tagName); 
  		} 
  		return this; 
	}}); 
	$('.cls').showTagName();
	</script>
</body>
</html>
fillano iT邦超人 1 級 ‧ 2020-02-20 10:05:21 檢舉

感謝,我都沒有再更新了XD

sean666 iT邦新手 5 級 ‧ 2020-02-20 10:08:23 檢舉

fillano 出現了,大師早安!

fillano iT邦超人 1 級 ‧ 2020-02-20 10:22:49 檢舉

早安XD

我要留言

立即登入留言