在javascript中,表達式是短語,那麼語句(statement)就是整句或命令。正如英文語句以句號結尾,javascript以分號結尾。
表達式計算出一個值,但語句使某件事發生。
“使某件事發生”的一個方法是計算帶有副作用的表達式。諸如賦值和函數調用這些有副作用的表達式,是可以作為單獨的語句的。這種把表達式當做語句的用法也稱做表達式語句(expression statement)。類似的語句還有聲明語句(declaration statement),聲明語句用來聲明新變量或者定義新函數。
javascript程序就是一系列的可執行語句的集合,默認情況下,javascript解釋器依照編寫順序依次執行。另一種“使某件事情”發生的方法就是改變語句的默認執行順序:
1.條件語句(conditional)語句:javascript解釋器可以根據一個表達式的值來判斷;來執行還是跳過這些語句,例如if和switch語句。
2.循環語句(loop)語句:可以重復執行的語句,例如while和for語句
3.跳轉(jump)語句:可以讓解釋器跳轉至程序的其它部分繼續執行、例如break、return和throw語句
接下來本文將介紹javascript中各式各樣的語句和其語法。本章最後對這些語句做了總結。一個javascript程序無非是以分隔分割的語句集合,所以一旦掌握了javascript語句,就可以編寫javascript程序了。
1.表達式語句
賦值語句是一種比較重要的表達式語句,它的作用就是改變一個變量的值,就像執行一條賦值語句一樣:例如
復制代碼 代碼如下:
greet = "hello" + name;
i *= 3;
遞增運算符(++)和遞減運算符(--)和賦值語句有關。它們的作用是改變一個變量的值,就像執行一條賦值語句一樣。
復制代碼 代碼如下:
counter++;
delete運算符的重要作用就是刪除一個對象的屬性(或數組的元素),所有它一般作為語句使用,而不是作為復雜表達式的一部分。
復制代碼 代碼如下:
delete o.x;
函數調用是表達式語句的另外一個大類,例如
復制代碼 代碼如下:
alert(greet);
window.close();
雖然這些客戶端函數都是表達式,但它們對web浏覽器造成了一定的影響。所以我們認為也是語句,調用一個沒有副作用的函數是沒有意義的,除非它是復雜的表達式或賦值語句的一部分,例如。不可能隨便把一個余弦值丟棄;
Math.cos(x);
相反,得出余弦值就得把它賦值給一個變量,以便將來使用這個值:
var cx = Math.cos(x);
再次提醒各位,每行代碼就是以分號結束的。
2.復合語句和空語句
可以用逗號運算符將幾個表達式連接在一起,形成一個表達式。同樣,javascript還可以講多條語句聯合在一起,形成一個復合語句(compound statement)。只需花括號將多條語句括起來即可。因此,下面幾行代碼可以當成一條單獨的語句,使用在javascript任何希望使用一條語句的地方。
復制代碼 代碼如下:
{
x = Math.PI;
cx = Math.cos(x);
console.log("cos(π)=" + cx);
}
關於語句塊有幾點需要注意:第一,語句塊不需要分號。塊中的元素語句必須以分號結尾,但語句塊不需要。
第二,語句塊中的行都有縮進,這不是必須的,但整齊的縮進能使代碼可讀性更強,更容易理解。
第三,javascript沒有塊級作用域,在語句塊中聲明的變量並不是語句塊所私有的。(參考3章10節第一小節)
將很多條語句合並成一個大語句塊的做法在javascript編程中非常常見。類似的表達式通常包含子表達式一樣,很多javascript包含其它子語句,從形式來講,javascript通常允許一個語句塊包含一條子語句。例如:while循環的循環體就可以只包含一條語句。使用語句塊,可以將任意數量的語句放到這個塊中,這個語句塊可以當做一條語句來使用。
在javascript中,當希望多條語句被當做一條語句使用時,使用符合語句來替代。空語句(empty statement)則恰好相反,它允許包含0條語句。空語句如下所示:
;//分號
javascript解釋器在執行空語句時顯然不執行任何動作,但實踐證明:當創建一個具有空循環體的循環時,空語句有時是很有用的,例如下面的for循環
復制代碼 代碼如下:
//初始化一個數組a
for (i = 0; i < a.length; a[i++] = 0);
在這個循環中,所有的操作都在表達式a[i++]=0中完成,這裡並不需要任何循環體。然而javascript需要循環體中至少包含一條語句,因此這裡只使用了一個單獨的分號來表示一條空語句。
注意,在for循環、while循環或if語句的右邊園括號的分號很不起眼,這很可能造成 一些致命的bug,而這些bug很難定位到。例如下面的代碼的執行結果很可能就是作者不想要的效果:
復制代碼 代碼如下:
if((a==0)||(b==0)); //這一行代碼什麼也沒做....
o = null; //這一行代碼總會執行
如果有特殊目的使用空語句,最好在代碼中添加注釋,這樣能更清楚的說明這條空語句是有用的
復制代碼 代碼如下:
for (i = 0; i < a.length; a[i++] = 0) /*empty*/;
3.聲明語句
var和function都是聲明語句,它們聲明或定義變量或函數。這些語句定義標識符(變量名和函數名)並給其賦值,這些標識符可以在程序任意地方使用。聲明語句本身什麼也不做,但它有一個重要意義:通過創建變量和函數,可以更好的組織代碼的語義。
接下幾節將講述var語句和function語句,但並不包含變量和函數的全部內容。
i.var
var語句用來聲明一個或者多個變量,它的語法如下:
var name_1[ = value_1][, ..., name_n[ = value_n]]
關鍵字var之後跟隨的是要聲明的變量列表,列表中的每一個變量都可以帶有初始化表達式,可用於指定它的初始值。例如:
復制代碼 代碼如下:
var i; //一個簡單的變量
var j = 0; //一個帶有初始值的變量
var p, q; //兩個變量
var greet = "hello" + name; //更復雜的初始化表達式
var x = 2.34,y = Math.cos(0.75),r, theta; //很多變量
var x = 2,y = x * x; //第二個變量使用了第一個變量
var x = 2,
f = function(x) {return x * x}, //每個變量都獨占一行
y = f(x)
如果var語句出現在函數體內,那麼定義的是一個局部變量,其作用域就是這個函數。如果在頂層代碼中使用var語句,那麼它聲明的是全局變量,在整個javascript中,都是可見的。在第三章10節提到:全局變量是全局對象的屬性,然後和其它全局對象屬性不同的是,var聲明的變量是無法通過delete刪除的。
如果var語句中的變量沒有指定初始化表達式,那麼這個變量的值初始為undefined。所以,在聲明語句之前的變量值就是undefined。
需要注意的是,var語句同樣可以作為for循環或者for/in循環的組成部分。(在循環之前聲明的變量聲明一樣,這裡聲明變量也會"提前"),例如:
復制代碼 代碼如下:
for (var i = 0; i < 10; i++) console.log(i);
for (var i = 0, j = 10; i < 10; i++, j--) console.log(i * j);
for (var i in o)console.log(i);
注意,多次聲明同一變量是無所謂的。
ii.function
關鍵字function用來聲明函數的,我們已經學過函數表達式(4.3).函數定義可以寫成語句的形式。例如:下面示例代碼中的兩種定義寫法:
復制代碼 代碼如下:
var f = function f(x) {return x + 1;} //將表達式賦值給一個變量
function f(x){return x + 1;} //含有變量名的語句
函數聲明的語法如下:
復制代碼 代碼如下:
function funcname([arg1[, arg2[..., argn]]]) {
statements
}
funcname是要聲明的函數的名稱標識符。函數名之後是參數列表,參數之間使用逗號隔開。當調用函數的時候,這些標識符則指代傳入函數的實參。
函數體是由javascript語句組成的,語句數量不限,且用花括號括起來。在定義函數時,並不執行函數體內的語句,它和調用函數時待執行的新函數對象相關聯。注意,function函數語句裡的花括號是必須的,這和while循環和其它一些語句鎖使用的語句塊是不同的,即使函數體只有一條語句,仍然需要花括號將其括起來。
復制代碼 代碼如下:
function hyteus(x, y) {
return Math.sqrt(x * x + y * y);
}
hyteus(1, 2) //=>2.23606797749979
function facial(n) { //一個遞歸函數
if (n <= 1) return 1;
return n * facial(n - 1);
}
facial(11) //=>39916800
函數的聲明通常出現在javascript代碼的最頂部,也可以嵌套在其他函數體內。但在嵌套時,函數聲明只能出現在所嵌套的函數頂部。也就是說:函數定義不能出現在if、while、或其他語句中。
和var語句一樣,函數聲明語句創建的變量也是不可刪除的。但是這些變量不是只讀的,變量值可以重寫。
4.條件語句
條件語句是通過判斷指定的表達式的值是否來執行或跳過某些語句。這些語句是代碼的”決策點“,有時稱為”分支“。如果javascript解釋器是按照代碼的”路徑“執行的。條件語句就是這條路上的分叉點。程序到達這裡必須選擇一條路徑來繼續執行。
i.if語句
if語句是基本的控制語句,准確的說,它讓程序有條件的執行,這種語句有兩種形式:第一種是
復制代碼 代碼如下:
if (expression)
statement
這種形式中,判斷expression 的值,如果是真,執行statement語句,如果是假值,就不執行statement.例如
復制代碼 代碼如下:
if (username == null) //如果username是null或undefined
username = "jack wong"; //對其進行定義
需要注意的是,if語句括住expression的園括號是必須的。
javascript語法規定,if關鍵字和帶園括號的表達式之後必須跟隨一條語句。但可以使用語句塊將多條語句合成一條。因此,if語句的形式如下所示:
復制代碼 代碼如下:
if (!address) {
address = "";
message = "please mailing address"
}
if語句的第二種形式引入了else從句,當expression的值是false值時執行else 邏輯
復制代碼 代碼如下:
if (expression)
statement1
else
statement2
例如以下代碼
復制代碼 代碼如下:
if (n == 1)
console.log("1 new message");
else
console.log("you have" + n + "new message");
當if/else語句中,嵌套使用if語句時,必須注意確保else語句匹配正確的if語句。考慮如下代碼:
復制代碼 代碼如下:
i = j = 1;
k = 2;
if (i == j)
if (j == k)
console.log("i equs k");
else
console.log("i dosent equal j"); //錯誤!!
這個實例中,內層的if語句構成了外層if語句所需要的子句。但是,if和else的匹配關系不清晰(只有縮進給了一點暗示)而且在這個例子裡,縮進給出的暗示是錯誤的,因為javascript解釋器是這麼理解的。
復制代碼 代碼如下:
if (i == j) {
if (j == k)
console.log("i equs k");
else
console.log("i dosent equal j");
}
和大多編程語言一樣,javascript中的if、else匹配規則是,else總是和就近的if語句匹配,為了讓個例子的可讀性更強,更容易理解,更方便維護和調試,應當使用花括號
復制代碼 代碼如下:
if (i == j) {
if (j == k) {
console.log("i equs k");
} else { //花括號使代碼的結果更清晰
console.log("i dosent equal j");
}
}
許多程序員都將有if和else語句主體用花括號括起來的習慣(就像類似while循環這樣的符合語句中一樣),即使每條分支只有一條語句,但這樣做能避免剛才的程序歧義問題。
ii.else if
if/else語句通過判斷一個表達式的計算結果來選擇兩條分支中的一條。當代碼中有許多條分支的時候應該怎麼辦呢?一種解決的辦法是使用else if語句。else if並不是真正的javascript語句,它只不過是多條if / else語句連接在一起的寫法。
復制代碼 代碼如下:
if (n == 1) {
//執行代碼塊 1
} else if (n == 2) {
//執行代碼塊2
} else if (n == 3) {
//執行代碼塊3
} else {
//之前的條件都為false,則執行代碼塊4
}
這種代碼沒有什麼特別之處,它由多條if語句組成,每條if語句的else的從句又包含另外一條if語句。可以用if語句的嵌套形式來完成語法上的等價代碼,但與此相比,顯然else if的寫法更加清晰也更可取。
iii.switch
if語句在程序執行的過程中,創建一支分支,並且可以使用else if來處理多條分支。然後,當所有的分支都依賴同一個表達式的值時,else if並不是最佳的解決方案。在這種情況下,重復計算多條if語句中的表達式是非常浪費的做法。
switch語句適合處理這種情況。關鍵字switch之後緊跟著園括號括起來的一個表達式。隨後是花括號括起來的代碼塊。
復制代碼 代碼如下:
switch (expression) {
statements
}
然而switch語句完整的語法要比這更復雜一些。case之後是一個表達式和冒號,case和標記語很類似,只是這個標記語並沒有名字。
它只和他後面的表達式關聯在一起。當執行執行這條switch語句時,它首先計算expression的值,然後查找case子句的表達式是否和expression的值相同。(這裡的相同是按照“===”運算符進行比較的),如果匹配case,它將會執行對應的代碼。如果找不到匹配的case,它將會執行"default:"標簽中的代碼塊。如果沒有“default:”標簽,switch將跳過所有的代碼塊。
switch語句是非常容易混淆的,用例子介紹會比較清晰一點,下面的switch語句和方才的if/else語句是等價的
復制代碼 代碼如下:
switch (n) {
case 1: //如果n ===1從這裡開始
//執行代碼塊1
break;
case 2:
//執行代碼塊2
break;
case 3:
//執行代碼塊3
break;
default:
//執行代碼塊4
break;
}
需要注意的是,每個case語句的結尾處都使用了關鍵字break。我們將後面介紹break語句,break語句可以使解釋器跳出switch語句或循環語句。在switch中,case只是指明了要執行的代碼起點,但沒有指明終點。如果沒有break語句,那麼switch語句就從expression的值的匹配的case標簽處代碼開始執行,依次執行後續的語句,一直到整個switch代碼塊結束。當然,如果在函數中使用switch語句,可以使用return來替換break,return和break都用於終止switch語句,也會防止一個case語句執行完繼續執行下一個case語句塊。
下面的語句貼近實戰,它根據值的類型將該值轉換為字符串。
復制代碼 代碼如下:
function convert(x) {
switch (typeof x) {
case 'number': //將數字轉換為16進制
return x.toString(16);
case 'string':
return '"' + x + '"'; //返回兩段帶雙引號的字符串。
default: //使用普通方法轉換其它類型
return String(x);
}
}
console.log(convert(100255114)) //=>5f9c58a
注意,在上面的兩個例子中,case關鍵字後跟隨的是數字和字符串直接量,在實際中這是switch最常見的用法,但是ECMAScript標准允許每個關鍵字跟隨任意的表達式。
switch語句首先計算switch 關鍵字後的表達式,然後按照從上到下的順序計算每個case後的表達式,知道執行到case的表達式的值和switch的表達式的值相等時為止。由於對每個case的匹配操作實際上是“===”恆等運算符比較,而不是“==”,因此表達式和case的匹配並不會做任何類型轉換。
每次執行switch語句的時候,並不是所有的case表達式都能執行到,因此,應當避免帶有副作用的case表達式,比如函數調用的表達式和賦值表達式。最安全的做法就是在case表達式中使用常量表達式。
前面提到過,switch表達式與所有的case表達式都不匹配,則執行標記為“default:”的語句塊,如果沒有"default:"標簽,則switch整個語句都跳過。在之前的例子中,“default:”標簽都出現在switch末尾,位於所有case標簽之後,當然這是最合理也是最常用的寫法。實際上,“default:”標簽可以放在switch語句內任何地方。
5.循環。
為了理解條件語句,可以將javascript中的代碼想成一條條分支路徑。循環語句(looping statement)就是程序路徑的一個回路,可以讓一部分代碼重復執行。javascript中有四種循環語句:while、do/while、for、for/in下面幾節會一次講解他們。其中最常用的循環就是數組元素的遍歷,(7.6會詳細討論這種循環和使用數組類定義的特殊循環方法。)
i.while
if語句是一種基本的控制語句,用來選擇執行程序的分支語句。和if一樣,while語句也是一個基本的循環語句,它的語法如下:
復制代碼 代碼如下:
while (expression)
statement
在執行while語句之前,javascript解釋器首先計算expression的值,如果它的值是假值,那麼程序將跳過循環體中的邏輯statement轉而執行程序中的下一條語句。如果它的值是真值,則執行循環體statement內的邏輯,然後再計算表達式expression的值,種循環會一直持續下去,知道expression的值為假值為止。換一種說法 就是表達式為expression是真值的時候則循環執行statement,注意,使用while(true)則會創建一個死循環。
通常來說,我們不想讓javascript反復執行同一操作。在幾乎每一次循環中,都會有一個或多個變量隨著循環而迭代改變。正是由於改變了變量這些變量,因此每次循環執行的statement的操作也不盡相同,而且,如果改變變量在expression中用到,那麼每次循環表達式的值也不同。這一點非常重要,負責初始值為真值的表達式永遠是真值,循環也不會結束,下面的這個示例所示while循環輸出0-9值。
復制代碼 代碼如下:
var count = 0;
while (count < 10) {
console.log(count);
count++;
}
可以發現,在這個例子中,變量count的初始值為0,在循環的過程中,它的值每次都遞增1,當循環執行了十次。表達式的值就編程了false,這時while就會結束,javascript解釋器將執行程序下一條語句。大多循環都有一個像count這樣的計數器變量。盡管計數器常用i j k這樣的變量名,但如果想讓代碼的可讀性更強,就應當使用更具體的語法名。
ii.do/while
do/while循環和while循環非常相似,只不過它是在循環的尾部而不是頂部檢測循環表達式,這就意味這循環體至少執行一次。do/while循環的語法如下:
復制代碼 代碼如下:
do
statement
while(expression);
do/while循環並不像while循環那麼常用。這是因為在實踐中想要循環至少執行一次的情況並不常見。下面是一個do/while循環的例子
復制代碼 代碼如下:
function printArray(a) {
var len = a.length,
i = 0;
if (len == 0)
console.log("空數組");
else
do {
console.log(a[i]);
} while (++i < len);
}
printArray([1,5,2,6])
在do/while循環和普通while循環之間有兩點語法方面的不同之處。首先,do循環要求必須使用關鍵字do來標識循環的開始,用while變標識循環的結尾並進入循環條件判斷;其次,和while循環不同,do循環使用分號結尾的。如果while的循環體使用花括號括起來,則while循環也不使用分號結尾。
iii.for
for語句提供了一種比while更方便的循環語句控制結構。for語句對常用的循環模式做了一些簡化。大部分的循環都具有特定的計數器變量。在循環開始之前要初始化這個變量,然後在每次循環之前檢查下它的值。最後,計數器變量做自增操作,否則就在循環結束後、下一次判斷前做修改。在這類循環中,計數器的三個關鍵操作是初始化、檢測和更新。for語句就將這三部操作明確聲明為循環語法的一部分,各自使用一個表達式來表示。for語句的語法如下:
復制代碼 代碼如下:
for (initialize; test; increment)
statement
intialize、test、increment三個表達式之間使用分號分隔,他們負責初始化操作、循環條件判斷和計數器變量的更新。將它們放在循環的第一行會更容易理解for循環正在做什麼,而且也可防止忘記初始化或者遞增計數器變量。
要解釋for循環是怎麼樣工作的,最簡單方法就是列出一個與之等價的while循環
復制代碼 代碼如下:
initialize
while (test) {
statement
increment;
}
換句話說,initialize表達式只在循環 開始之前執行一次。初始化表達式應當具有副作用(通常是一條賦值語句)。javascript同樣允許初始化表達式中帶有var變量聲明語句,這樣的話就可以聲明並初始化一個變量。每次循環之前會執行test表達式,並判斷表達式的結果來決定是否執行循環體。每次循環之前會執行test表達式,並判斷其結果是否來執行循環體,如果test結果為真值,則執行循環體中的statement。最後,執行increment表達式。同樣為了有用起見,這裡的increment表達式也必須有副作用。通常來講,它不是一個賦值表達式就是一個由“++”、“--”運算符構成的表達式。
上文的while循環可以使用for循環來從寫
復制代碼 代碼如下:
for (var count = 0; count < 10; count++)
console.log(count)
當然,有些循環更加復雜,而且循環中一次迭代多個變量。在javascript,這種情況必須用到逗號運算符,它將初始化表達式和自增表達式合並入一個表達式中以用於for循環。
復制代碼 代碼如下:
var i, j;
for (i = 0, j = 10; i < 10; i++, j--)
console.log(i * j);
到目前為止,在示例代碼中的循環變量都是數字。當然是數字是最常用的,但不是必須的。下面這段代碼就使用for循環來遍歷表數據結果,並返回鏈表中最後一個對象(也就是第一個不包含next屬性的對象)
復制代碼 代碼如下:
function tail(o) { //返回鏈表的最後一個節點對象
for (; o.next; o = o.next) /*empty*/ //根據判斷o.next是不是真值來執行遍歷
return o;
}
需要注意的是,這段代碼不包含initialize表達式,for循環中的那三個表達式中的人和一個都可以忽略,但兩個分號必不可少。如果省略test表達式,那麼將是一個死循環。同樣和while(ture)類型,死循環的令一種寫法是for(;;)。
iiii.for/in
for/in語句使用for關鍵字,但它和常規的for循環是不同的一類循環。for/in循環的語法如下
復制代碼 代碼如下:
for (variable in object)
statement
variable通常是一個變量名,也可以是一個可以產生左值的表達式或者一個通過var語句聲明的變量。總之是一個適用於賦值表達式左側的值。object是一個表達式,這個表達式的計算結果是一個對象。同樣,statement是一個語句或語句塊,它構成了循環的主體。
使用for循環來遍歷數組元素是非常簡單的
復制代碼 代碼如下:
var a = [1, 3, 5, "44"];
for (var i = 0; i < a.length; i++) //i代表了數組元素的索引
console.log(a[i]) //輸出每個數組的元素
而for/in循環則是用來方便的遍歷對象成員屬性
復制代碼 代碼如下:
for (var p in o) //將屬性的名字賦值給變量p
console.log(o[p]); //輸出每一個屬性的值
在執行 for/in語句的過程中,javascript解釋器首先計算object表達式。如果表達式為null或undefined,javascript解釋器將跳過循環並執行後續的代碼。如果表達式等於一個原始值,這個原始值將會轉換為與之對於的包裝對象(wapper object)(3.6節)。否則,expression本身已經是對象了。javascript會依次枚舉對象的屬性來執行循環。然而在每次循環之前,javascript都會計算variable表達式的值,並將屬性名(一個字符串)賦值給它。
需要注意的是,只要for/in循環中,varibale的值可以當做賦值表達式的左值,它可以是任意表達式。每次循環都會計算這個表達式,也就是說每次循環它計算的值可能不同。例如,可以使用下面的這段代碼將所有對象屬性復制到一個數組中:
復制代碼 代碼如下:
var o = {x: 1,y: 2,z: 3};
var a = [],i = 0;
for (a[i++] in o) /*empty*/;
document.write(a)//=> x,y,z
javascript數組只不過是一種特殊的對象,因此,for/in循環可以像枚舉對象屬性一樣枚舉數據索引。例如在上面的代碼之後添加這段代碼,就可以枚舉數據索引0,1,2:
復制代碼 代碼如下:
var o = {x: 1,y: 2,z: 3};
var a = [],i = 0;
for (a[i++] in o) /*empty*/;
document.write(a)//=> x,y,z將對象屬性復制到一個數組中
for(i in a)
document.write(i) //=>枚舉數據索引 0 1 2
其實,for/in循環並不會遍歷對象的所有屬性,只有“可枚舉”(enumerable)的屬性才會遍歷到(參照6.7)。由於javascript語言核心所定義的內置方法就不是“可枚舉的”。比如,所有的對象都有toString(),但for/in循環並不枚舉toString()這個屬性。除了內置的方法之外,還有很多內置對象的屬性是不可枚舉的(nonenumberable)。而代碼中定義的所有屬性和方法都是可枚舉的(6.7節會講到,但ECMAScript5中有特殊手段可以使屬性變為不可枚舉)。
對象可以繼承其它對象的屬性,那行繼承自定義屬性(6.2.ii)也可以使用for/in枚舉出來。
如果for/in的循環體刪除了還未枚舉的屬性,那麼這個屬性將不會再枚舉。如果循環體定義了對象的 新屬性,這些屬性通常也不會枚舉到(不過。javascript有些實現可以枚舉那麼些在循環體中增加的屬性)。
屬性枚舉的順序
ECMAScript規范並沒有指定for/in循環按照何種順序來枚舉對象的屬性。但實際上,主流的浏覽器廠商javascript實現是按照屬性定義的先後順序來枚舉簡單對象的屬性,先定義的屬性先枚舉。如果使用對象直接量的形式創建對象,則將按照直接量中屬性的出現順序枚舉。(有一些網和javascript庫是依賴這種枚舉順序的,而浏覽器廠商大多不修改這個順序),在下面的情況下,枚舉順序取決於具體的實現(並非交互)
1.對象繼承了可枚舉屬性
2.對象具有整數數組索引的屬性
3.使用delete刪除了對象已有的屬性
4.使用Object.defineProperty()或者類似的方法改變了對象屬性
6.跳轉
javascript中令一類語句是跳轉語句(jump statement)。從語句理解,它可以使javascript執行從一個位置跳轉到令一個位置。
break語句是跳轉到循環或其他的語句結束。continue語句是終止本次循環的執行並開始下一次循環的執行。javascript中的語句可以命名或帶有標簽,break和continue可以標識目標循環或者其它語句標簽。
return語句可以讓解釋器跳出函數體的執行。並提供本次調用的返回值。throw語句觸發或者拋出一個異常,它是與try/catch/finally語句一同使用的,這些語句指定了處理異常代碼邏輯。這是一種復雜的跳轉語句,當拋出一個異常的時候,程序將跳至最近的閉合異常辰星,這個異常程序可以是在同一個函數中或者更高層的調用棧中。
接下來,描述每一種跳轉語句
i.標簽語句
語句是可以添加標簽的,標簽是由語句前的標識符和冒號組成:
identifier:statement
通過給語句定義標簽,就可以在程序中任何地方通過標簽名來引用這條語句。可以對多條語句定義標簽,盡管只有給語句塊定義標簽時它才有更有用,比如循環語句或條件判斷語句。通過給循環定義一個標簽名,可以在循環體內部使用break和continue來退出循環或者直接挑戰到下一個循環開始。break和continue是javascript中唯一可使用語句標簽的語句(本章接下來會講述)。下面的例子,其中while循環定義了一個標簽,continue語句使用了這個標簽:
復制代碼 代碼如下:
mainloop: while (token != null) {
//忽略這裡代碼...
continue mainloop; //跳轉到下一次循環
//忽略這裡的代碼...
}
這裡做標簽的indentifier必須是一個合法的javascript標識符,而不能是一個保留字。標簽的命名空間和變量或函數的命名空間是不同的,因此可以使用同一個標識符作為語句標簽和作為變量名或函數名。語句標簽只在它所起作用的語句(當然可以在它的子句)內是有定義的。一個語句標簽不能和它內部的語句標簽重名,但在兩個代碼不相互嵌套的情況下是可以出現同名語句標簽的。帶有標簽的語句還可以帶有標簽,也就是說,任何語句可以有很多個標簽。
ii.break
單獨使用break語句的作用是立即退出最內存的循環或switch語句。它的語法如下:
break;
由於它能夠使循環和switch語句退出,因此這種形式的break只能出現在這類語句中才是合法的。
我們在switch語句的例子中已經見到果break語句。在循環中,無論出於什麼原因,只要不想繼續執行整個循環,就可以用break提前退出。當循環終止條件非常復雜時,要函數體內使用break語句實現這樣些條件判斷的做法要比直接在循環表達式中寫出這個復雜的終止條件做法簡單的多。
下面的例子中循環遍歷整個數組元素來查找某個特定的值,當整個數組遍歷完成後正常退出循環,如果找到 了需要查找的數組元素,則使用break語句退出循環:
復制代碼 代碼如下:
for (var i = 0; i < a.length; i++) {
if (a[i] == target) break;
}
javascript中同樣允許break關鍵字後跟隨一個語句標簽,(只有標識符,沒有冒號)
break labelname;
當break和標簽一塊使用時,程序將跳轉到這個標簽所識別的語句塊的結束,或者直接終止這個閉合語句塊的執行。當沒有任何閉合語句塊指定break所用的標簽,這時會產生一個語法錯誤。當使用這種形式的break語句時,帶標簽的語句不應該是循環或者switch語句,因為break語句可以“跳出”任何閉合的語句塊。這裡的語句可以是由花括號組起來的一組語句,使用同一個標簽來識別一組語句。
break關鍵字和labelname之間不能換行。因為javascript可以給語句自動補全省略掉的分號,如果break關鍵字和標簽之間有換行,javascript解釋器會認為你在使用break不帶標簽的最簡形式,因此會在break後補充分號.
當你希望通過break來跳出非就近的循環體或者switch語句時,就會用到帶標簽的break語句。下面是示例代碼:
復制代碼 代碼如下:
var matrix = getData(); //從某處獲得一個二維數組
//將矩陣中所有元素進行求和
var sum = 0,
success = false;
//從簽名處開始,以便在報錯時推出程序。
compure_sum: if (matrix) {
for (var x = 0; x < matrix.length; x++) {
var row = matrix[x];
if (!row) break compure_sum;
for (var y = 0; y < row.length; y++) {
var cell = row[y];
if (isNaN(cell)) break compure_sum;
sum += cell;
}
}
success = true;
}
//break語句跳轉至此
//如果success =false條件到達這裡,說明我們給出的矩陣中有錯誤
//否則對矩陣中所有的元素進行求和
最後,需要注意的是,不管break語句帶不帶標簽,它的控制權都無法越過函數的邊界。比如:對於一條帶標簽的函數定義語句來說,不能通過函數內部通過這個標簽來跳轉到函數外部.
iii.continue語句
continue語句和break語句非常類似,但它不退出循環,而是轉而執行下一次循環。continue語句的語法和break的語句語法一樣簡單
continue;
continue語句會也會帶有標簽
continue lebname;
不管continue語句帶不帶標簽,它只能在循環體使用,在其它地方使用將會 報語法錯誤。
當執行到continue語句的時候,當前的循環邏輯就終止了,隨即執行下一次循環,在不同類型的循環中,continue的行為也有區別
1. 在while循環中,在循環開始處指定expression會重復檢測,如果檢測結果為true,循環體會從頭執行。
2. 在do/while循環中,程序的執行至今跳轉到循環的結尾處,這時會重新判斷循環條件,之後才會繼續下一次循環。
3. 在for循環中,首先會計算自增表達式,然後再檢測test表達式,用以判斷是否執行循環體。
4. 在for/in循環中,循環開始遍歷下一個屬性名,這個屬性名賦給了指定的變量。
需要注意continue語句在while和for循環中的區別,while循環直接進入下一輪的循環條件判斷,但for循環首先計算器increment表達式,然後判斷循環條件。之前的章節討論了和while循環“等價”的for循環行為。但由於continue在這兩種循環中行為表現不同,因此使用while循環不可能完美的模擬等價的for循環。
下面這段代碼展示了不帶標簽的continue語句,產生一個錯誤的時候跳過當前循環的後續邏輯
復制代碼 代碼如下:
for (i = 0; i < data.length; i++) {
if (!data[i]) continue; //不能處理undefined數據
total += data[i];
}
和break語句類似,帶標簽的continue語句可以用在嵌套的循環中,用以跳出層次嵌套的循環體邏輯。同樣和break語句類似,在continue語句和labname之間不能有換行。
iiii.return
回想一下,函數調用的一種表達式,而且所有的表達式都有值。函數中的return語句即是指函數調用後的返回值。這裡是return語句的語法:
return expression;
return語句只能在函數體內出現,如果不是的話會報語法錯誤。當執行到return語句的時候,函數終止執行,並返回expression的值給調用程序。例如:
復制代碼 代碼如下:
function square(x) {return x * x} //一個包含return的語句函數
square(4) //執行為16
如果沒有return語句,則函數調用僅依次執行函數體內的每一條語句直到函數結束,最後返回調用程序。這種情況下,調用表達式的結果是undefined。return語句經常作為函數內最後的一條語句出現,但並不是說一定一定要放在函數的最後,即使在執行return語句的時候還有很多代碼沒有執行到,這時候函數也還返回調用程序。
return語句可以單獨使用而不必帶有expression,這樣的話函數也會想調用程序返回undefined.例如:
復制代碼 代碼如下:
//如果參數是null或者undefined則立即返回
if (!o) return;
//其它邏輯
由於javascript可以自動插入分號,因此,return關鍵字和它後面的表達式之間不能有換行。
iiiii.throw語句
所謂異常(excepion)是當發生了某種異常情況或錯誤時產生的一個信號。拋出異常,就是用信號通知發生了錯誤或異常狀況。捕獲異常是指處理這個信號,拋出異常,就是用信號通知發生了錯誤或異常狀況。捕獲異常是指處理這個信號,即采取必要的手段從異常中匯豐。在javascript中,當產生運行時錯誤或者程序使用throw語句時就會顯式的拋出異常。使用try/catch/finally語句可以捕獲異常,下一節會對它作詳細介紹。
throw語句的語法如下:
throw expression
expression的值可以是任意類型的。可以拋出一個代表錯誤碼的數組,或者包含可錯誤消息的字符串。當javascript解釋器拋出異常的時候,通常采用Eeeor類型或其子類型,當然也可以使用它們。一個error對象有一個那麼熟悉表示錯誤類型,一個message屬性用來傳遞構造函數的字符串(參照第三部分的Error類),在下面的例子中,當使用非法參數調用函數時就拋出一個Error對象:
復制代碼 代碼如下:
function fa(x) {
//如果輸入的參數是非法的,則拋出一個異常
if (x < 0) throw new Error("x不能是負數。");
//否則計算出一個值,正常地返回它
for (var f = 1; x > 1; f *= x, x--) /*empty*/;
return f;
}
當拋出異常時,javascript解釋器會立即停止當前正在執行的邏輯,並跳轉至就近的異常處理程序。異常處理程序用try/catch/finally語句的catch從句編寫的。如果拋出的異常沒有一條關聯catch從句 ,解釋器會檢測更高層的閉合代碼塊,看它是否關聯相關的異常處理程序。以此類推 ,直到扎到一個異常處理的程序為止。
如果拋出的異常函數沒有處理它的try/catch/finally語句,異常將向上傳播到調用該函數的代碼。這樣的話,異常就會沿著javascript方法的詞法結構和調用棧向上傳播。如果沒有找到任何異常處理的程序,javascript將吧異常當成程序錯誤來處理,並報告給用戶。
iiiiii.try/catch/finally語句
try/catch/finally語句是javascript的異常處理機制。其中try從句定義了需要處理的異常所在代碼塊。catch語句跟隨在try從句之後,當try塊從某處發送了異常時,調用了catch內的代碼邏輯。catch從句跟隨finnlly塊,後者防置了清理代碼,不管try塊中是否產生了異常,finnally塊內的邏輯總會執行。盡管catch和finally都是可選的,但try從句只殺二者之一與組成完整的語句。try、catch和finally語句塊都需要花括號括起來,這裡的花括號是必須的。即使從句中只有一條語句也不能省略花括號。
下面的代碼說明了try/catch/finlly的語法和使用目的:
復制代碼 代碼如下:
try{
//通常來講,這裡的代碼會從頭執行到尾而不會產生任何問題,
//但有時會拋出一個異常,要麼是由throw語句直接拋出異常
//要麼通過調用一個方法間接拋出異常
}
catch(e){
//當且僅當try拋出了異常,才會執行這裡的代碼
//這裡可以通過局部變量e來獲得對Error對象或者拋出的其它值的引用
//這裡的代碼可以基於某種原因處理這個異常 ,也可以忽略這個異常。
//還可以通過throw語句重新拋出異常
}
finally{
//不管try語句塊是否拋出看異常,這裡的邏輯總會執行,終止try的語句塊方式有:
//1)正常終止,執行完語句塊的最後一條語句
//2)通過break,continue或return語句終止
//3)拋出一個異常,異常被catch從句捕獲
//4)拋出一個異常,異常未被捕獲,繼續向上傳播
}
我們注意到,關鍵字catch後跟隨了一對圓括號,圓括號內是一個標識符。這個標識符和函數參很像。當捕獲一個異常時,把這個異常相關的值(比如Error對象)賦值給這個參數。和普通的變量不同,這條catch子句中的標識符具有塊級作用域,它只在catch語句塊 內有定義。
這裡有一個關於try/catch語句更實際的例子,這裡使用了前面章節中提到factorial()方法,並使用客戶端javascript方法prompt()和alert()來輸入和輸出
復制代碼 代碼如下:
try {
//要求用戶輸入一個數字
var n = Number(prompt("請輸入一個正整數", ""));
//假設輸入是合法的,計算這個階乘
var f = factorial(n);
//顯示結果
alert(n + "!=" + f);
} catch (ex) {
//如果輸入不合法,將執行這裡的邏輯
document.write(ex); //告訴用戶發送了什麼。
}
這裡的try/catch語句並不包含finally從句。盡管finally不像catch那樣經常使用,但有時候它還是非常有用。然而,我們需要更詳盡的解釋它的行為。不管try語句塊中的代碼執行完成了多少,只要try語句中有一部分代碼執行了,finally從句就會執行。它通常在try從句的代碼後用於清理工作。
關注下面這個例子
復制代碼 代碼如下:
try {
print("Outer try running..");
try {
print("Nested try running...");
throw "an error";
} catch (e) {
print("Nested catch caught " + e);
throw e + " re-thrown";
} finally {
print("Nested finally is running...");
}
} catch (e) {
print("Outer catch caught " + e);
} finally {
print("Outer finally running");
}
// Windows Script Host 作出該修改從而得出 WScript.Echo(s)
function print(s) {
document.write(s);
}
輸出:
復制代碼 代碼如下:
Outer try running..
Nested try running...
Nested catch caught an error
Nested finally is running...
Outer catch caught an error re-thrown
Outer finally running
7.其它語句類型。
本節討論剩余的三種javascript語句:width,debugger和use strict
i.with語句
3.10討論了作用域鏈(scope chain),一個可以按序檢索的對象列表,通過它可以進行變量名的解析。width語句可以用來臨時擴展作用域鏈:它具體有如下語法:
with (object)
statement
這條語句將object添加到作用域鏈頭部,然後執行statement,最後把作用域鏈恢復到原始狀態。
在嚴格模式下(5.7.iii)是禁止使用width的,在非嚴格模式下也是不推薦使用width語句的,盡可能的避免使用width語句。那些使用width語句的javascript非常難優化,而且比沒有使用width的語句,它運行速度更慢。
在對象嵌套層次很深的時候,常會使用with語句來簡化代碼的編寫。例如客戶端javascript中,可能使用下面的這種表達式來訪問表單的一個html元素
document.forms[0].address.value
如果這段代碼多次出現,則可以使用with將form對象添加至作用域鏈的頂層。
復制代碼 代碼如下:
with(document.forms[0]){
//直接訪問表單元素
name.value="";
address.value="";
email.value ="";
}
這種方法簡化了大量的輸入,不用再為每個變量添加document.forms[0]前綴。這個臨時對象掛載在作用域鏈上,當javascript需要解析諸如address標識符時,就會在這個對象中查找。當然,不使用with的語句代碼可以寫成這樣。
復制代碼 代碼如下:
var f = document.forms[0];
f.name.value = "";
f.adress.value = "";
f.email.value = "";
不要忘記,只有在查找標識符的時候才能用到作用域鏈,創建新的變量時候不使用它,看一下下面的代碼:
復制代碼 代碼如下:
with(o) x = 1;
如果對象o有一個屬性x,那麼這行代碼給這個屬性賦值1。如果o沒有定義屬性x,這段代碼和不使用with的代碼x=1是一模一樣的。它給一個局部變量或者全局變量x賦值,或者創建全局對象的一個新屬性。with語句提供了一種讀取o屬性的快捷方法,但並不會創建o的屬性。
ii.debugger語句
debugger語句通常什麼也不做。然而,在調試程序可用並運行的時候,javascript解釋器將會(非必須)以調試模式運行。實際上,這條語句產生一個斷點(breakpoint),javascript代碼執行會停止在斷點的位置,這時可用使用調速器輸出變量的值,檢查調用棧等。
例如加上調用函數f()的時候使用了未定義的參數,因此f()拋出一個異常,但無法定位到到底哪裡出了異常。為了有助於調試這個問題,需要修改f():
復制代碼 代碼如下:
function f(o){
if (o === undefined) debugger; //這段代碼用來臨時調試
console.log(1) //函數的其它部分
}
f();
這時候,當調用f()沒有傳入參數,程序將停止執行,這時候通過調用調速器檢測調用棧並找出錯誤的原因。
在ECMAScirpt5中,debugger語句已經正式加入到專門語言裡,但在很長的一段時間裡,主浏覽器的廠商已經將其實現了。注意,可用的調速器是遠遠不夠的,debugger語句不會啟動調試器。但如果調試器已經在運行,這條語句才會正在產生斷點。例如,使用Firefox插件firebug,首先啟動firebug,這樣debugger語句才能工作。
iii.“use strict”
“use strict”是ECMASCript5引入的一條指令。指令不是語句(但非常接近於語句),“use strict”和普通語句之前有兩個重要區別:
1.它不包含任何語言的關鍵字,指令僅僅是一個包含一個特殊字符串直接量的表達式(可以是使用單引號也可以是雙引號)。
2.它只能出現在腳本代碼的開始或者函數體的開始、任何實體語句之前。但它不必一定出現在腳本的首行或者函數體內的首行。因為“use strict”指令之前之後或之前都可能有其它字符串直接量的表達式語句,並且javascript的具體實現可能將它們解析為解釋器自有的指令。在腳本或者函數體內第一條常規語句之後,字符串直接量表達式語句只當做普通的表達式語句對待,它們不做指令解析,它們也沒有任何副作用。
使用“use strict”指令的目的是說明(腳本或函數中)後續代碼解析為嚴格代碼(strict code)。如果頂層(不在任何函數內)代碼使用了“use strict”指令,那麼它們就是嚴格代碼。如果函數體定義處的代碼是嚴格代碼或者函數體使用了“use strict”指令,那麼函數體的代碼也是嚴格代碼。如果eval()調用所處的代碼是嚴格代碼或者eval()要執行的字符串使用了“scrict code”指令,則eval()內的代碼是嚴格代碼。
嚴格代碼以嚴格模式執行。ECMAScript5中的嚴格模式是該語言的一個受限的子集。它修正了語言的重要缺陷,並提供健壯的差錯功能和增強安全機制。嚴格模式和非嚴格模式區別如下(前三條尤其重要)
•嚴格模式中禁止使用with語句
•嚴格模式中,所有的變量要先聲明,如果給一個未聲明的變量、函數、函數參數、catch從句參數或全局的對象的屬性賦值。就會拋出一個引用錯誤異常(在非嚴格模式中,這種隱式聲明全局變量的方法是給全局變量新添加一個新屬性)
•嚴格模式中,調用的函數(不是方法)中的一個this值是undefined。(在非嚴格模式中,調用的函數中的this值總是全局變量)。可以利用這種特性來判斷javascript實現是否支持嚴格模式。
復制代碼 代碼如下:
var hasStrictMode = (function() {
"use strict";
return this === undefined
}());
•同樣,在嚴格模式中,當通過call()和apply()來調用函數時,其中的this值就是通過call()或apply()傳第一個參數(在非嚴格模式中,null和undefined值被全局對象轉換為對象的非對象值鎖代替)
•在嚴格模式中,給只讀屬性賦值和給不可擴展的對象創建成員都將拋出一個類型錯誤異常(在非嚴格模式中,這些操作只是簡單的操作失敗,不會報錯)。
•在嚴格模式中,傳入eval()代碼不能再調用辰星所在的上下文中聲明變量或定義函數,在非嚴格模式中是可以這樣做的。相反,變量和函數的定義是在eval()創建的作用域中,這個作用域在eval()返回時就棄用了。
•在嚴格模式中,函數裡的arguments對象擁有傳入函數值的靜態副本。在非嚴格模式中,agreements對象具有“魔術般”的行為,arguments裡的數組元素和函數都指向同一個值的引用。
•在嚴格模式中,當delete運算符後面跟隨非法的標識符(比如變量、函數、函數參數時)將會拋出一個語法錯誤,(在非嚴格模式下,這種delete什麼也沒做,並返回false)
•在嚴格模式中,在一對象直接量中定義兩個或多個同名屬性將產生一個語法錯誤(非嚴格模式下不會報錯)
•在嚴格模式下,不允許八進制整數直接量。(以0為前綴,而不是0x為前綴)在非嚴格模式中是允許直接八進制直接量的
•在嚴格模式下,標識符eval和arguments當做關鍵字,他們的值是不能更改的。不能給這些標識符賦值,也不能把它們聲望為變量,用做函數名,用做函數參數或用做catch塊的標識符。
•在嚴格模式中限制了對調用棧的檢測能力,在嚴格的模式的函數中,arguments,caller和arguments.callee都會拋出一個類型錯誤異常。嚴格模式的函數同樣具有caller和arguments屬性,當訪問這兩個屬性時拋出類型錯誤異常。
8.javascript語句小結:
javascript語句語法:
語句
語法
用途
break
break[label];
退出最內側循環或者退出switch語句,又或退出label指定的語句
case
case expression:
在switch語句標記一條語句
continue
continue [label];
重新開始最內層的循環或從新開始label指定的循環
debugger
debugger;
斷點器調試
default
default;
在switch標記默認語句
do/while
do statement while(expression);
while循環的一種替代形式
empty
;
什麼都不做
for
for(init;test;incr)statement
一種簡寫的循環
for/in
for(var in object)statement
遍歷一個對象屬性
function
function name([param[],...]){body}
聲明一個函數
if/else
if (expr)statement1[else statement2]
執行statement1或者statement2
label
label:statement
給statement指定一個名字:label
return
return [expression];
從函數返回一個值
switch
switch(expression){statements}
用case或者“default:”語句標記多個分支語句
throw
throw expression
拋出異常
try
try {statements}
[catch {hander satements}]
[finally {cleanup satements}]
捕獲異常
use strict
"use strict"
對腳本和函數使用嚴格模式
var
avr name=[=expr][,...]
聲明並初始化一個或多個變量
while
while (expression) statement
基本的循環結構
with
with(object) statement
擴展作用域鏈(不贊成使用)