1. 程式人生 > >JavaScript高階教程- 第五課

JavaScript高階教程- 第五課

第一頁:JavaScript高階教程- 第5天

哇!- 今天是最後一天!真是好極了!作為對您這些天辛勤工作的獎勵,我把今天的課程安排得非常放鬆 - 沒有本質程式碼縈繞在你腦中,也沒有多如牛毛的家庭作業,只是編碼理論和指向資源的連線。

今天討論以下話題:

  • 幫你寫JavaScript的工具
  • 除錯技術
  • 使你的JavaScript程式碼快速執行的技巧

可得到的工具

說實話,就算稱我為老學究,我也只用字處理器來寫JavaScript。 雖然有大量的可用來寫JavaScript的工具,但是它們產生的程式碼非常笨拙而且很難更改。如果你真想使用一種工具來建立JavaScript程式碼,我向你推薦

Dreamweaver,它能在很短時間內產生一些令人驚異的JavaScript程式碼。但是,如果你打算隨後更改這些程式碼,就要做好面對一些非常難看的程式碼的準備。

你也能找到幫你除錯JavaScript的工具。我也不用這些工具。網景有一個,微軟也有一個,但我從未見過很成功地使用這些偵錯程式的人。你可以試一試,或許可以證明我是錯的。但是在有人能使我確信某種產品值得買(暗示:送我一套那種免費軟體)之前,我寧願用老方法除錯我的程式碼。

除錯技術

隨著用JavaScript程式設計的深入,你會開始理解那些JavaScript給出的不透明錯誤資訊。一旦你理解了你常犯的一般性錯誤,你就會很快知道怎樣避免它們,這樣你寫的程式碼中的錯誤將越來越少。程式設計實際上是一種能隨著時間不斷飛快進步的技術。但是不管變得多麼熟練,你仍然要花一些時間除錯你的程式碼。

如果你做過家庭作業,或有過JacaScript程式設計經驗,你會知道相當多的時間是花在除錯上。這很正常 - 這只是程式設計者必須做的事之一。實際上,按照大量的研究,程式設計師平均百分之五十的時間花在解決程式碼中的錯誤。

關鍵是學會怎樣有效地除錯你的程式。我有一些技巧可以幫助你解決程式為什麼沒有象應該的那樣執行,或者幫你首先避免寫有很多錯誤的程式碼:

  • 用不同方式打印出變數
  • 注意一般性錯誤
  • 編碼之前先想好

讓我們以打印出那些變數開始。

第二頁:列印變數

一旦你發現一個錯誤,就可以清除它。不幸的是,發現它們的出處並不總是很容易 - 你的大部分除錯時間只是花在指出錯誤的位置。

最可靠的方法之一是在你的程式碼中加入一些簡單的語句打印出正在發生什麼。假如你在下面的兩段程式中發現一個問題:

function getName()
{
    var first_name = prompt("what's your first name?","");
    var last_name = prompt("what's your last name?","");
    var the_name = first_name + " " + last_name;

}

function theGreeting()
{
    var the_name = "";
    the_name = getName();

    if (the_name == "Dave Thau")
    {
alert("Hello, oh greatest one!");
    } else {
alert("Ahoy palloi!");
    }
}

執行這段程式,看看你是否能發現出了什麼問題(Netscape 3.x的使用者可能會遇到一些錯誤檢查的問題,這是由於Netscape 3.x本身的原因,以下的Javascript例子與此相同)。如果你在警告對話方塊中隨意輸入一些名字,你會得到問候:“Ahoy palloi!”。但是,如果你在第一個提示對話方塊中輸入“Dave”,在第二個中輸入“Thau”,你應該得到“Hello,oh greatest one!”這條資訊。然而,你還是隻得到“Ahoy palloi!”這條資訊。很明顯,
函式中出了錯誤。在這個簡單的例子程式中,你或許只是檢視JavaScript程式碼就能發現錯誤。然而,當你的程式碼變得越來越複雜時,只靠目測來發現錯誤會變得愈加困難。

如果JavaScript沒能捕獲你的錯誤,你也沒有通過檢視程式碼發現錯誤,有時打印出變數會對你有所幫助。最簡單的方法是象下面這樣使用一個alert():

// theGreeting gets a name using getName, then presents
// one or two alert boxes depending on what the name is
//function getName()
{
    var first_name = prompt("what's your first name?","");
    var last_name = prompt("what's your last name?","");
    var the_name = first_name + " " + last_name;
    alert("in getName, the_name is: " + the_name);

}

// theGreeting gets a name using getName, then presents
// one of two alert boxes depending on what the name is
// function theGreeting()
{
   var the_name = "";
   the_name = getName();
   alert("after getName, the_name = " + the_name);
   if (the_name == "Dave Thau")
   {
   alert("hello, oh greatest one!");
   }else{
   alert("ahoy palloi!");
   }
}

請注意我們已經在所有重要的地方加入警告語句。現在試著執行這段程式。如果你輸入名稱“Dave”和“Thau”,你會注意到第一個警告顯示“in getName, the_name is: Dave Thau,”,但是第二個警告顯示“after getName, the_name = undefined,”,這就告訴你在getName()的最後一行事情變得糟糕起來。不知何故,the_name只在函式存在前正確,但是theGreeting沒有給變數the_name正確賦值。當你寫的函式能正確執行,但返回值出現
問題時,你最先要做的就是檢查你是否的確讓其返回了一個值。很明顯,問題就出在這兒。getName()函式指出了名稱,但沒有返回它。所以我們應把語句“return the_name;”加到函式末尾。

把一些警告對話方塊加入你的程式碼中是很有幫助的。不幸的是,每隔一行就按一次“OK”也是一種痛苦。

不用警告對話方塊也能除錯程式碼。一種選擇是把除錯資訊寫到窗體的一個文字區內。另一種可能是把除錯資訊寫在另一個視窗上。這兒有一個把除錯資訊寫在下面文字區的除錯程式碼的例子。

使你的除錯經歷更舒適的第三個訣竅是這樣的:建立不同的除錯等級,然後設定“除錯”變數。下面就是在此頁上執行的JavaScript程式碼:

// debug can be either none, alert, or textarea
//  depending on the kind of debugging I want to do
// var debug = "none";
// function getName gets a first and last name,
// concatenates them with a space in between,
// and returns the name function getName()
{
    var first_name = prompt("what's your first name?","");
    var last_name = prompt("what's your last name?","");
    var the_name = first_name + " " + last_name;
    var error_message = "in getName, the_name is: " + the_name;
    doError("in getName, the_name is: " + the_name);
}

// theGreeting gets a name using getName, then presents
// one of two alert boxes depending on what the name is
// function theGreeting()
{
   var the_name = "";
   the_name = getName();
   doError("after getName, the_name = " + the_name);
   if (the_name == "Dave Thau")
   {
    alert("hello, oh greatest one!");
   } else {
    alert("ahoy palloi!");
   }
}

// doError is the error handling routine
// depending on the type of debug message
// it presents either no debug message, an alert
// or puts the message in a textarea
//
function doError(the_message)
{
    if (debug == "alert")
    {
    alert(the_message);
    } else if (debug == "textarea")
    {
    window.document.the_form.the_text.value +=    the_message + "<br>/n";
    }
}

    請注意我已經定義了一個叫“debug”的變數,它可以是“none”,“alert”或“textarea”。於是當我想產生一個錯誤資訊時,我把它送給函式doError(),此函式可能什麼也不做,或者顯示一個訊息對話方塊,或者把訊息貼上到一個文字區中,這取決於我怎樣設定除錯變數。當你想同時看到多條錯誤資訊時,你可以設定除錯變數為“textarea”。當你準備把你的程式碼顯示給全世界時,你只需要把除錯變數設為“none”,於是錯誤資訊將不再出現,這樣可以省去發現和清除所有除錯語句的麻煩。

通常,程式設計師可以建立不同的除錯等級,如“none”,“brief”和“extreme”。“brief”將列印一些除錯資訊,“extreme”將列印大量除錯資訊,“none”當然不會列印任何資訊。

如果你按此方法建立你的除錯系統,你可以在編碼時把除錯等級設為“brief”,在準備公佈你的JavaScript時把除錯變數設為“none”。如果有不可思議的事情發生,並且你不知道去哪兒發現問題,你可以把除錯等級設為“extreme”,然後流覽所有的除錯資訊直到發現可疑的地方。

好了,除錯系統就討論到這兒。現在讓我們看看JavaScript編碼
器產生的一般性錯誤。

第三頁:一般性程式錯誤

多數錯誤只是無聊的語法錯誤。記住關閉那些引號,大括號和
小括號會花費很長時間,不過幸運的是JavaScript自動錯誤檢
測器能捕獲大部分此類錯誤。雖然JavaScript錯誤檢測器隨著
日漸複雜的流覽器而不斷完善,但是一些錯誤仍會溜走。下面
是一些需要留意的常見錯誤:

混淆變數名或函式名

大寫和複數變數和函式名產生的錯誤令人煩惱地經
常出現,有時JavaScript錯誤檢測器不能捕獲它們。
通過建立和堅持使用一種對變數和函式的命名協定,
會大大減少這些麻煩的數量。例如,我全部用小寫
字母定義變數,並用下劃線代替空格 (my_variable,
the_data, an_example_variable),用內建符號表
示函式 (addThreeNumbers(), writeError()等)。
我避免使用任何複數,因為我總是忘記那些變數是
不是複數。

偶然地使用了保留字

一些字不能作為變數名,因為它們已經被JavaScript
使用。例如,不能定義一個叫“if”的變數,因為
它實際上是JavaScript的一部分 - 如果使用“if”,
你會遇到各種麻煩。當你因為使用命名為“if”的
變數而變得瘋狂時,一個叫做“document”的變數
是很誘人的。不幸的是,“document”是一個
JavaScript物件。另一個經常遇到的問題是把變數
命名為“name”(窗體元素有“names”屬性)。把
變數命名為“name”不會總出問題,只是有時 -
這會更使人迷惑 - 這就是避免使用“name”變數的
原因。

不幸的是,不同的流覽器有不同的保留字,所以沒
有辦法知道該回避哪些字。最安全的辦法是避免使
用已經成為JavaScript一部分的字和HTML使用的字。
如果你因為變數遇到問題,並且不能發現哪兒錯了,
試著把變數改個名字。如果成功了,你或許就避開
了保留字。

記住在邏輯判斷時應該用兩個等號

一些流覽器能捕獲這種錯誤,有些卻不能。這是一
種非常常見的錯誤,但是如果流覽器不能替你指
出來,你就很難發現。下面是一個這種錯誤的例子:

var the_name = prompt("what's your name?", "");if (the_name = "the monkey"){    alert("hello monkey!");} else {    alert("hello stranger.");}

這段程式碼將產生“hello monkey!”警告對話方塊 -
不管你在提示裡敲的是什麼 - 這不是我們希望的。
原因是在if-then語句中只有一個等號,這句話告
訴JavaScript你想讓一件事等於另一件。假設你在
提示中敲的是“robbie the robot”。最開始,變
量the_name的值是“robbie the robot”,但是隨
後if語句告訴JavaScript你想把the_name設為
“the monkey.”。於是JavaScript很高興地執行你
的命令,送一個“true”訊息給if-then語句,結果
警告對話方塊每次都出現“hello monkey!”。

這種陰險的錯誤會使你發瘋,所以注意使用兩個
等號。

偶然給變數加上了引號,或忘了給字串加引號

我不時遇到這個問題。JavaScript區分變數和字元
串的唯一方法是:字串有引號,變數沒有。下面
有一個明顯的錯誤:

var the_name = 'koko the gorilla';alert("the_name is very happy");

雖然the_name是一個變數,但是程式還會產生一個
提示“the_name is very happy,”的警告對話方塊。
這是因為一旦JavaScript看見引號包圍著某些東西
就不再考慮它,所以當你把the_name放在引號裡,
你就阻止了JavaScript從記憶體中查詢它。

下面是一個不太明顯的此類錯誤的擴充套件(我們已經
在第三天的課程裡見過):

function wakeMeIn3(){    var the_message = "Wake up!  Hey!  Hey!  WAKE UP!!!!";    setTimeout("alert(the_message);", 3000);}

這裡的問題是你告訴JavaScript三秒後執行alert
(the_message)。但是,三秒後the_message將不再
存在,因為你已經退出了函式。這個問題可以這樣
解決:

function wakeMeIn3(){   var the_message = "Wake up!";   setTimeout("alert('" + the_message+ "');", 3000);}

把the_message放在引號外面,命令“alert('Wake
up!');”由setTimeout預定好,就可以得到你想
要的。

這只是一些可能在你的程式碼中作祟的很難除錯的
錯誤。一旦發現了它們,就有不同的或好或差的方
法來改正錯誤。你很幸運,因為你能從我的經驗和
錯誤中獲益。


第四頁:修正錯誤

找到錯誤,有時侯雖然很難,卻只是第一步。然後你必須清除
錯誤。下面是一些在清除錯誤時應該做的一些事:

首先拷貝你的程式

有些錯誤很難清除。實際上,有時在根除錯誤時,
你會破壞整個程式 - 一個小錯誤使你瘋狂。在開始
除錯前儲存你的程式是確保錯誤不會利用你的最好
方法。

一次修正一個錯誤

如果你知道有好幾個錯誤,應該修正一個,檢驗其
結果,再開始下一個。一次修正許多錯誤而不檢驗
你的工作只會招致更多的錯誤。

警惕迷惑性錯誤

有時你知道存在一個錯誤,但不真正知道為什麼。
假設有一個變數“index”,由於某種原因“index”
總比你期望的小1。你可以做下面兩件事中的一件:
在那兒坐一會兒,解決它為什麼變小了,或只是聳
聳肩;在使用“index”之前加1,然後繼續進行。後
一種方法稱為迷惑程式設計。當你開始思考“究竟是怎
麼了 - 為什麼index是2而不是3呢?好吧...我現在
先讓它正常工作,以後再修改錯誤。”時,你正在
把一塊護創膏布貼到一處潛在的硬傷上。

迷惑程式設計可能在短期內有用,但是你可以看到長期
的厄運 - 如果你沒有完全理解你的程式碼到可以真正
清除錯誤的程度,那個錯誤將會回來困擾你。它或
者以另一種你不能解決的怪異錯誤的方式回來,或
者當下一個可憐的被詛咒的靈魂讀你的程式碼時,他
會發現你的程式碼非常難以理解。

尋找小錯誤

有時侯,對程式設計師來說,剪下和貼上程式碼的能力是
一種很壞的事。通常,你會在一個函式中寫一些
JavaScript程式碼,然後把它們剪下和貼上到另一個
函式中。如果第一個函式有問題,那麼現在兩個函
數都有問題。我並不是說你不應該剪下和貼上程式碼。
但是錯誤會以某種方式繁殖,如果你發現了一個
錯誤,你就應該尋找與其相似的其它錯誤。(或者
在製作它的若干版本之前確切知道會發生什麼。)
變數名拼寫錯誤在一段JavaScript程式碼中會突然多
次出現 - 在一個地方把the_name錯拼成teh_name,
你就有機會在其它地方發現這個錯誤。

如果所有其它的方法都失敗了

如果你正坐在那兒盯著一個錯誤,並且不能指出是
怎麼回事(或者根本沒有發現錯誤,但是因為程式
不能正確執行,你知道存在錯誤),你最好從計算
機前走開。去讀一本書,在角落散散步,或者拿一
杯可口的飲料 - 做些事,任何事,但不要去想程式
或問題。這種技術在某種情況下叫做“醞釀”,效
果非常好。在你稍做休息和放鬆後,再試著找出
錯誤。你會得到一幅比較清晰的景象。“醞釀”起
作用是因為它使你從思維混亂中解脫出來。如果沿
著一條錯路走太遠,你有時會發現無法轉身。這種
情況下最好開闢一條新路。我知道這會令人發火,
但確實有效。真的!

如果上面的方法還不成功...

請求別人的幫助。有時你的思想會形成定式,只有
換一種眼光才能洞察問題之所在。在結構化程式設計環
境中,程式設計師們定期地互相複查別人的程式碼。這可
以適當地叫做“程式碼複查”,不僅可以幫助消除
錯誤,還可以得到更好的程式碼。不要怕把你的
JavaScript程式碼給別人看,它會使你成為更好的
JavaScript程式設計師。

但是消除錯誤的絕對最好的辦法是...

一開始就建立沒有錯誤的程式碼。>>

第五頁:好的程式設計實踐

編好程式的關鍵是程式是寫給人的,不是寫給計算機的。如
果你能明白其他人或許會閱讀你的JavaScript,你就會寫更
清晰的程式碼。程式碼越清晰,你就越不容易犯錯誤。機靈的代
碼是可愛的,但就是這種機靈的程式碼會產生錯誤。最好的經
驗法則是KISS,即Keep It Simple,Sweetie(保持簡單,可愛)。

另一個有幫助的技術是在寫程式碼之前作註釋。這迫使你在動
手之前先想好。一旦寫好了註釋,你就可以在其下面寫程式碼。
下面是一個用這種方法寫函式的例子:

第一步:寫註釋

//function beSassy()
//  beSassy asks for a user's name, chooses a random 
//  insult and returns an alert box with the user's name and the
//  insult.
function beSassy()
{
//  first write a list of insults
//

//  next get the user's name
//

//  then choose a random insult  
//

//  finally, return the personalized sass
//
}

第二步:填充程式碼

//function beSassy()
//  beSassy asks for a user's name, chooses a random 
//  insult and returns an alert box with the user's name and the
//  insult.
function beSassy()
{
//  first write a list of insults
//
var the_insult_list = new Array;
the_insult_list[0] = "your shoe lace is untied";
the_insult_list[1] = "your mama!";
the_insult_list[2] = "it's hard to be insulting";

//  next get the user's name
//
var the_name = prompt("What's your name?", "");

//  then choose a random insult  
//
var the_number =  Math.random() * 5;
var insult_number = parseInt(the_number);
var the_insult = the_insult_list[insult_number];

//  finally, return the personalized sass
//
alert("Hey " + the_name + " " + the_insult);
}

這種先寫註釋的策略不僅迫使你在寫程式碼前思考,而且
使編碼的過程看起來容易些 - 通過把任務分成小的,
易於編碼的各個部分,你的問題看起來就不太象珠穆朗
瑪峰,而象一群令人愉悅的起伏的小山。

最後...

總以分號結束你的每一條語句。

雖然並不是嚴格必需,你應該養成以分號結束每一條語
句的習慣,這樣可以避免這行後面再有程式碼。忘了加
分號,下一行好的程式碼會突然產生錯誤。

把變數初始化為“var”,除非你有更好的理由不這樣做。

用“var”把變數局域化可以減少一個函式與另一個不相
關函式相混淆的機會。

好了,既然你已經知道了如何編碼,下面就讓我們學習怎樣使
你的JavaScript快速執行。


第六頁:按速度優化JavaScript程式碼

一旦你的JavaScript能執行,你就會想到使其執行得更快。
在講解加速程式碼的方法之前,讓我先講講“80/20規則”:
百分之八十的優化是由最初百分之二十的工作所完成的。竭
力實現剩餘百分之二十的速度優化是一種巨大的痛苦,而且
經常導致完全不能讀和難以管理的程式碼。簡言之,如果你的
JavaScript執行得很慢,你可以用很多簡單的方法來加速它,
但是除非你的程式碼確實執行得很慢,我不會對它進行再優化。
下面是一些使你的程式碼輕鬆執行的方法。

限制迴圈內的工作量

程式執行慢的最常見原因是迴圈內的重複工作。如果一
條命令只需要執行一次,就沒有必要把它放在迴圈內。
例如:

var index = 0;
while (index <10)
{
   var the_date = new Date();
   var the_day = the_date.getDay();
   var the_name = prompt("what's the kid's name? " ,"");
   alert("On " + the_day + " " + the_name + " is a very special person.");
    index++;
}

此程式迴圈執行10次。每次得到當天的日期,詢問小孩
的名字,然後打印出“On Monday,so-and-so is a
very special person.”。

但是日期是不會改變的,總是今天。所以沒有必要把前
兩行放在迴圈中。把它們從迴圈中拿出來,讓其只執行
一次而不是10次,這樣會節省時間:
var index = 0;
var the_date = new Date();
var the_day = the_date.getDay();
while (index <10)
{
var the_name = prompt("what's the kid's name? " ,"");
alert("On " + the_day + " " + the_name + " is a very special person.");
index++;
}

定製if-then-else語句,按最可能到最不可能的順序

因為if-then-else語句在遇到條件為真時結束,你可以
通過把最有可能的條件放到最開始來減少需要判斷的語
句的數量。例如:

var pet = prompt("what kind of pet do you have?", "");
if (pet  == "cat") 
{
    doCatStuff();
} else if (pet == "dog") 
{
    doDogStuff();
} else if (pet == "bird")
{
    doBirdStuff();
} else if (pet == "lizard")
{
    doLizardStuff();
}

一般來說,程式中的if子句比從lizard到dog需要執行的
邏輯判斷要少。

最小化重複執行的表示式

如果你發現需要重複計算一個特定的表示式,如
var pi=22/7,只計算一次並把它放在一個全域性變數中或
許是個好主意。例如,不象下面程式這樣:

function theArea(radius)
{
var pi = 22/7;
var area = pi * radius * radius;
return area;
}

function theCircumference(radius)
{
var pi = 22/7;
var circumference = 2 * pi * radius;
return circumference;
}

而是這樣做:

var pi = 22/7;
function theArea(radius)
{
var area = pi * radius * radius;
return area;
}
function theCircumference(radius)
{
var circumference = 2 * pi * radius;
return circumference;
}

我知道我在用一個全域性變數,我也說過這不是一個好主意。
然而,一些數字,如pi,其值在程式中永遠不會改變,是
此規則的特例。通過只計算pi一次,可以省去額外的計算。
或許時間上的一些小的節省,累加起來會很管用。

如果你發現程式碼執行很慢,你只要注意一些事情。這些都
很明顯,但是當你發現你經常忽略象這樣簡單的優化技巧
時,你會很吃驚。

還有,我的朋友,讓我們結束今天的課程,這也是整個
JavaScript高階教程的結束。如果你已經進行到這兒,
並且你至少讀過過去五天課程中的一半,那麼你已經看
過很多JavaScript程式碼了。實際上,如果你能理解跨越
第一部分和第二部分的10課的大部分內容,你就可以很
安全地把自己稱為“JavaScript助手”。通往神祕真知
的路就在你的腳下。

第七頁:下面講什麼?

既然你已經對JavaScript有了實際的理解,你就可以寫一些非
常認真的JavaScript程式了。現在該是把目標轉移到JavaScript
應用上的時候。Taylor的“動態HTML教程”是一個很好的開始。
學會動態HTML,你就可以實現更遠大的夢想。不論如何,總該
有一些夢想吧。

但是首先,你需要練習,練習,再練習。把已有的程式碼拿過來,
使它變得面目全非。或者白手起家寫程式碼。努力寫出無錯誤的
程式碼,成為一個能修正各種無論如何也要溜走的錯誤的專家。
用眼睛盯著有趣的和新的JavaScript應用程式,指出它們是如
何實現的。你的程式碼執行得是否完美並不重要,因為它總能教
你些什麼。你學得越多,你的程式碼就會變得越好,越快,越容
易建立。

現在開始吧,年輕的JavaScript程式設計師,做些讓我驕傲的事!