<html><head><title>Javascript Debugging</title><script> function populateDiv(){ var divElement = document.getElementById('messageLabel'); divElement.innerHTML = "Lorem ipsum dollor"; }</script></head><body> <div id="messageLabel"></div> <input type="button" value="Click Me!" onclick="populateDiv();" /></body></html>
在本章裡,我們將討論Firebug提供的,以支援JavaScript的開發、調試、概覽、以及測試的各種工具。在這裡我們將採用典型的
JavaScript用例的方式,並解釋如何使用Firebug來實現這些用例。
在本章裡,我們將討論以下主題:
- 命令列API及其功能
- 控制台API
- 單步JavaScript調試
- 插入條件或無條件斷點
命令列API
在第2章“Firebug視窗概覽”中,我們已經看到了如何使用命令列,這裡,我們將討論的是命令列API提供的一些方法(methods)。這些方法將協助我們調試JavaScript。下面是具體的描述與用法:
$(id)
這個方法類似於JavaScript中的document.getElementById()。它返回指定ID的單個元素。
我們將使用下面的HTML代碼來解釋$(id)方法。將這些代碼寫在一個HTML檔案中,並用Firefox將其開啟。
<html> <body> <input name="myText" id="test_id" type="text"> </body></html>
現在,當我們在Firebug的命令列中執行如下代碼,我們將看見如下的輸出視窗:
$("test_id")
$$(選取器)
這個方法返回由指定CSS選取器匹配元素所組成的數組。
Tip
關於CSS選取器更多的資訊,可以參考這個連結:http://www.w3.org/TR/css3-selectors
下面的HTML程式碼片段顯示在其DOM樹上有四個<input>HTML元素。我們將使用$$(選取器)的方法來選擇全部四個元素:
<html> <body> <input name="myText1" type="text" class="test_class" > <input name="myText2" type="text" class="test_class" > <input name="myText3" type="text" class="test_class" > <input name="myText4" type="text" class="test_class" > </body></html>
下面的顯示了在Firebug的命令列中執行 $$('input') 之後的輸出結果:
Tip
為了在Firebug的單一命令列執行代碼,你需要去掉“控制台”標籤下拉式功能表中的“更大的命令列”選項選擇。
$x(xpath)
此方法返回與給定的XPath運算式相匹配的元素的數組。
Tip
關於XPath更多的資訊請參考:http://www.w3schools.com/xpath
為瞭解釋此方法,我們將使用前面的HTML檔案來講解。現在,當我們在Firebug命令列執行如下代碼,我們將在Firebug的控制台標籤頁看到輸出結果:
var objs = $x('html/body/input') console.log(objs[0].name) console.log(objs[1].name) console.log(objs[3].name) console.log(objs[3].name)
Note
多行命令列:為了方便,Firebug提供了一個多行命令列編輯器。這是一個迷你編輯器,我們可以在其中輸入多行JavaScript命令,甚至輸入完整的JavaScript程式,並且我們可以即時執行這些代碼。我們可以通過點擊控制台標籤頁內右下角的紅色表徵圖來開啟這個多行編輯器。
dir(對象)
這個方法列印出對象的所有屬性的互動清單。這個結果與我們在DOM標籤頁內看到的是一致的。
讓我們來看一下與我們在$$(選取器)方法中使用過的HTML代碼。如果我們在Firebug的命令列中執行下面的代碼,我們將會得到如下的輸出:
var objs = $x('html/body/input') dir(objs)
dirxml(節點)
這個方法將列印一個HTML或XML元素的XML源碼樹。這個結果與我們在HTML標籤頁內看到的結果是一致的。我們可以在HTML標籤頁內點擊任意節點來觀察它。
使用相同的HTML檔案,在Firebug的程式碼中執行如下代碼將得到其XML源碼樹。下面的顯示了在控制台標籤頁內產生的輸出結果:
var obj = $$('body')[0] dirxml(obj)
我們可以用$(id)選擇一個節點後,再將其傳遞給此方法,或者採用與此類似的其他擷取一個節點的方法。
預設情況下,命令列運算式是與頁面的頂層視窗相關的。cd()方法將使我們可以使用頁面內的架構視窗。
clear()
這個方法將對控制台清屏。此方法提供的功能也可以通過點擊“清除”按鈕實現,這個按鈕在控制台標籤頁的左上方。
inspect(object[, tabName])
這個方法讓我們可以用最適合的標籤來檢查一個對象,或者使用與選擇性參數tabName相一致的標籤。
可以使用的標籤名字有HTML、CSS、SCRIPT,以及DOM。
現在,對我們已經開啟的HTML文檔,我們在Firebug的命令列中輸入下面代碼。輸出結果將顯示在HTML標籤頁內,如下:
inspect($$('input')[0], 'html')
keys(object)
這個方法返回一個包含此對象所有屬性的名字的數組。
還是使用我們前面使用的HTML檔案為例,執行下面的代碼將顯示第一個input標籤的全部屬性、實體、函數和常量:
keys($$('input')[0])
values(object)
這個方法將返回一個包含該對象所有屬性值的數組。
執行下面的代碼將顯示DOM樹中第一個input標籤的所有屬性值。
values($$('input')[0])
debug(fn)和undebug(fn)
這兩個方法將在函數的第一行增加或移除斷點。
我們將在後面第8章“AJAX開發”中學習這些方法的細節內容。
monitor(函數名)和unmonitor(函數名)
這些方法被用來開啟或關閉對一個函數所有調用的日誌記錄。
通常,為了查看JavaScript中某個函數是否被調用,我們在其內部設定alert()或console.log()方法。這是非常繁瑣的事情。首先,我們不得不在一個大的JavaScript檔案中找到這個方法,然後我們需要添加alert或log方法。接下來,當我們看到每樣事情都對了,我們需要從代碼中移除所有的這些日誌聲明。
Firebug用巧妙的方式來處理這種監視工作。為了判斷某個函數是否被調用,我們僅僅需要知道函數的名字就可以了。通過使用monitor()方法,我們可以跟蹤發現該函數被調用了多少次。我們將在控制台上看到提示,告訴我們正在被監視的函數是否被調用了。並且,它還會給我們一個指向函數指令碼的連結。
我們以下面的代碼為例來進行討論,建立一個HTML檔案,輸入以下代碼,並且用Firefox瀏覽器開啟這個檔案:
<html> <script> function function1(){ return true; //some statement } function function2(){ return true; //some statement } </script> <body> This is the body <input id="button1" type="button" value="Invoke function1()" onclick="function1();" /> <input id="button2" type="button" value="Invoke function2()" onclick="function2();" /> <input id="button3" type="button" value="Invoke function3()" onclick="function3();" /> </body> </html>;
現在,在命令列輸入下面的代碼並且執行:
monitor(function1)
點擊本文檔中的按鈕“Invoke function1()”。我們將看到,無論何時只要調用了一次function1()函數,Firebug就會在控制檯面板上顯示其日誌記錄。如果我們在控制檯面板上點擊function1()的連結,將跳轉到function1()函數所在的行。
下面的代碼將取消對function1()函數的監控:
unmonitor(function1)
monitorEvents(object[, types])
此方法將開啟對發送到一個對象的所有事件的日誌記錄。可選的參數類型指定只記錄特定的訊息族。類型中最常使用的值是mouse和key。
全部可用類型包括:composition, context menu, drag, focus, form, key, load, mouse, mutation, paint, scroll, text, ui, 以及 xul。
unmonitorEvents(object[, types])
此方法關掉發送到某對象的所有事件的日誌記錄。
監視和取消監視事件與日誌事件是一樣的,關於日誌事件我們已經在前一章討論過。
讓我們考察全面使用的HTML檔案。在命令列執行下面的代碼並且點擊第一個按鈕:
monitorEvents($("button1"))
下面的顯示了事件監控情況:
profile([title])和profileEnd()
這兩個方法用於開啟和關閉JavaScript概況器。選擇性參數title將作為概況報告的抬頭列印在文本中。
下面是在Firebug中啟動JavaScript概況器的三個方法:
- 在控制台標籤頁內點擊“概況”按鈕
- 從JavaScript代碼中使用console.profile("Profiler Title")
- 從命令列中使用profile("Profiler Title")
為查看概況器產生的統計資訊,輸入如下HTML代碼,並儲存為HTML檔案,然後在瀏覽器中開啟此檔案。按F12開啟Firebug並且點擊“Start”按鈕。
<html> <head> <title>Firebug</title> <script> function bar(){ console.profile('Measuring time'); foo(); console.profileEnd(); } function foo(){ loop(1000); loop(100000); loop(10000); } function loop(count){ for(var i=0; i<count; i++){} } </script> </head> <body> Click this button to profile JavaScript <input type="button" value="Start" onclick="bar();" /> </body></html>
下面顯示了概況器產生的統計資訊:
概況器的欄目與描述
- 函數(Function):此欄顯示每個函數的名字。
- 調用(Call):此欄顯示該函數被調用的次數。(在本例中loop()函數被調用了3次。)
- 百分比(Percent):此欄顯示每個函數消耗的時間百分比。
- 佔用時間(Own Time):此欄顯示在某個函數中代碼所佔用的時間。例如,foo()沒有自己的代碼。相反,它只是調用其他函數。因此,它的執行時間佔用大約是~0毫秒。如果你看本欄的某些值,我們在此函數內添加一些迴圈。
- 時間(Time):此欄顯示函數從開始的地方到結束的地方執行所花時間。例如,foo()沒有代碼。所以,它的執行時間大約是~0毫秒,但是在這個函數裡要調用其他函數。所以,其他函數執行的全部時間是4.491毫秒。於是,這裡顯示了4.54毫秒,這是三個loop()函數執行的時間加上自身foo()函數執行的時間之和。
- 平均時間(Avg):此欄顯示某個函數的平均執行時間。如果我們僅僅調用一次這個函數,我們將看不到差別。但如果此函數被調用了多次,我們將看出差別。計算公式如下:
平均時間=自身代碼執行時間 / 調用次數
- 最小時間和最大時間(Min and Max columns):這裡顯示的是函數的最小執行時間。在我們的例子中,我們調用loop()三次。當我們傳遞1000作為參數時,大約只消耗了很短時間(0.045秒),當我們傳遞100000給此函數時,它消耗更長的時間(4.036秒)。所以,在本例中,最小時間將顯示0.045秒,而最大時間顯示為4.036秒。
- 檔案(File):此欄顯示函數所在的檔案的檔案名稱,並顯示所在行數。
控制台API
Firebug對所有載入的頁面增加了一個全域變數,命名為“console”。這個對象包括許多方法,這些方法使得我們可以向控制台寫入資訊,以揭示出指令碼運行中的資訊。
console.log(object[, objcet, ...])
這個方法向控制台寫入一個訊息。我們可以傳遞任意多的參數,這些參數在一個空格分隔行內被連結起來。
log的第一個參數也許是包含類似printf字串替換模式的字串。例如:
console.log("The %s jumped over %d tall buildings", animal, count);
上面的例子可以用非字串替換的方式來重寫,效果一樣:
console.log("The", animal, "jumped over", count, "tall buildings");
這兩個技術可以聯合起來用。如果我們使用字串替換模式,但是給出的參數多過替換模式使用的參數,其餘的參數將附加在空格分隔行內,如下面的代碼所示:
console.log("I am %s and I have:", myName, thing1, thing2, thing3);
如果對對象進行了日誌記錄,這些對象顯示出來的就不止是靜態文本,還有互動超連結,這些連結分別指向Firebug中與該對象相關的HTML、CSS、指令碼或DOM標籤頁。我們也可以使用%o的模式在字串中替換超連結。
下面是我們可以使用的字串替代的全部模式:
Table�1.1.�
字串
替換模式
%s
字串
%d, %i
整數(還不支援格式化的數值)
%f
浮點數(還不支援格式化的數值)
%o
對象連結
console.debug(object[, object, ...])
此方法把一個訊息寫到控制台,這個訊息包括了指向此方法被調用的行的超連結。
console.info(object[, object, ...])
此方法向控制台寫入一個訊息,這個訊息帶有可視化的資訊表徵圖(info icon)、作色代碼、以及指向此方法被調用的行的超連結。
console.warn(object[, object, ...])
此方法向控制台寫入一個訊息,這個訊息帶有可視化的警告圖示、作色代碼、以及指向此方法被調用的行的超連結。
console.error(object[, object, ...])
此方法向控制台寫入一個訊息,這個訊息帶有可視化的錯誤表徵圖(error icon)、作色代碼、以及指向此方法被調用的行的超連結。
console.assert(expression[, object, ...])
此方法測試一個運算式是否為真,如果不是,它將向控制台寫入一個訊息,並拋出異常。
console.dir(object)
此方法列印出對象的所有屬性的互動式列表。看起來與我們在DOM標籤頁中看到的視圖是一致的。
console.dirxml(node)
此方法列印HTML或XML元素的XML原始碼樹。這與我們在HTML標籤頁中看到的是一致的。我們可以通過點擊節點在HTML標籤頁內檢查此節點。
console.trace()
此方法輸出Javascript代碼在被調用時所執行互動式棧的跟蹤。
這個棧跟蹤列出了棧中的函數細節,以及每一個傳遞給函數的參數值。通過點擊函數,將轉到指令碼標籤頁內相應代碼的位置。並且,點擊參數值將可以查看其在DOM或HTML標籤頁中的情況。
console.group(object[, object, ...])
此方法想控制台寫入一個訊息,並且以後向此控制台寫入的訊息都被縮排的嵌入一個塊中。調用console.groupEnd()可以關閉這個塊。
console.groupCollapsed(object[, object, ...])
這個方法類似於console.group(),只不過這個塊最初是摺疊的。
console.groupEnd()
此方法關閉被console.group()或console.groupEnd()最近開啟的塊。
console.time(name)
此方法用給定的名字建立一個新的計時器。用相同的名字調用console.timeEnd(名字)將停止計時器並且列印出所耗用的時間。
console.timeEnd(name)
此方法終止console.time(name)所建立的定時器,並列印出消耗的時間。
console.profile([title])
此方法啟用JavaScript概寫器。作為可選項的參數將會出現在概寫報告的頭部。
console.profileEnd()
此方法將關閉JavaScript概寫器並列印出報告。
console.count([title])
此方法將返回count被調用的程式碼的執行時間。作為選擇性參數的標題,將會在count所在行增加一個訊息輸出。
Note
控制台是一個附加到Web頁面的視窗對象上的對象。在Firebug中,這個對象僅僅在控制台被啟用時附加。在Firebug Lite,如果Lite在此頁中已經安裝,則控制台就被附加上。
JavaScript調試
本節介紹使用Firebug如果在內部和外部調試JavaScript。在開始之前,先回憶一下前面章節所介紹的內容:
使用Mozilla的Firefox和Firebug來調試JavaScript是一個非常直觀的過程。如果我們是Visual Studio的開發人員,我們將不會感到使用Firebug來調試JavaScript有什麼不同,除了調試器是作為瀏覽器的一部分之外。
Firebug中單步調試JavaScript
在一個編輯器中輸入下列代碼,並儲存為.html檔案,然後使用Firefox開啟它:
<html><head><title>Javascript Debugging</title><script> function populateDiv(){ var divElement = document.getElementById('messageLabel'); divElement.innerHTML = "Lorem ipsum dollor"; }</script></head><body> <div id="messageLabel"></div> <input type="button" value="Click Me!" onclick="populateDiv();" /></body></html>
現在在瀏覽器中按F12來開啟並啟用Firebug。點擊“指令碼”標籤頁,並在第6行插入斷點,如下面的所示:
Note
為了核實我們已經插入斷點,我們可在指令碼標籤頁的右邊“斷點”面板中查看斷點列表。
在第6行顯示一個大的紅點,表明這裡插入了一個斷點。現在,點擊“Click Me!”按鈕來執行JavaScript代碼。
一旦我們點擊,JavaScript將停在第6行,斷點所在的地方。
現在我們可以單步調試JavaScript,通過點擊在指令碼標籤頁下的這些按鈕來操作:繼續、單步進入、單步跳過、單步退出。
- 繼續(F8):只要指令碼的執行被另一斷點所終止,這個按鈕就使得我們可以繼續指令碼執行。
- 單步進入(F11):這個按鈕可以使我們單步進入另一個函數的內部。
- 單步跳過(F10):這個按鈕使我們可以單步跳過函數調用。
- 單步退出:這個按鈕使我們可以繼續指令碼的執行並停在下一個斷點。
現在,我們點擊“單步跳過”並按F10執行第6行並移動到第7行。注意右邊的“監控”面板中的divElement的值。在執行第6行之前該值是undefined,在執行第6行之後,它被賦予一個HTML的div元素作為值。讓我們來看下面的:
如果我們想要看看調用及執行流的棧的情況,只需要點擊在指令碼標籤頁的右邊點擊“堆棧”標籤。