W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
我們經常需要重復執(zhí)行一些操作。
例如,我們需要將列表中的商品逐個輸出,或者運行相同的代碼將數字 1 到 10 逐個輸出。
循環(huán) 是一種重復運行同一代碼的方法。
for…of 和 for…in 循環(huán)
給進階讀者的一個小提示。
本文僅涵蓋了基礎的循環(huán):
while
,do..while
和for(..; ..; ..)
。
如果你閱讀本文是為了尋找其他類型的循環(huán),那么:
否則,請繼續(xù)閱讀。
while
循環(huán)的語法如下:
while (condition) {
// 代碼
// 所謂的“循環(huán)體”
}
當 condition
為真時,執(zhí)行循環(huán)體的 code
。
例如,以下將循環(huán)輸出當 i < 3
時的 i
值:
let i = 0;
while (i < 3) { // 依次顯示 0、1 和 2
alert( i );
i++;
}
循環(huán)體的單次執(zhí)行叫作 一次迭代。上面示例中的循環(huán)進行了三次迭代。
如果上述示例中沒有 i++
,那么循環(huán)(理論上)會永遠重復執(zhí)行下去。實際上,瀏覽器提供了阻止這種循環(huán)的方法,我們可以通過終止進程,來停掉服務器端的 JavaScript。
任何表達式或變量都可以是循環(huán)條件,而不僅僅是比較。在 while
中的循環(huán)條件會被計算,計算結果會被轉化為布爾值。
例如,while (i != 0)
可簡寫為 while (i)
:
let i = 3;
while (i) { // 當 i 變成 0 時,條件為假,循環(huán)終止
alert( i );
i--;
}
單行循環(huán)體不需要大括號
如果循環(huán)體只有一條語句,則可以省略大括號
{…}
:
let i = 3; while (i) alert(i--);
使用 do..while
語法可以將條件檢查移至循環(huán)體 下面:
do {
// 循環(huán)體
} while (condition);
循環(huán)首先執(zhí)行循環(huán)體,然后檢查條件,當條件為真時,重復執(zhí)行循環(huán)體。
例如:
let i = 0;
do {
alert( i );
i++;
} while (i < 3);
這種形式的語法很少使用,除非你希望不管條件是否為真,循環(huán)體 至少執(zhí)行一次。通常我們更傾向于使用另一個形式:while(…) {…}
。
for
循環(huán)更加復雜,但它是最常使用的循環(huán)形式。
for
循環(huán)看起來就像這樣:
for (begin; condition; step) {
// ……循環(huán)體……
}
我們通過示例來了解一下這三個部分的含義。下述循環(huán)從 i
等于 0
到 3
(但不包括 3
)運行 alert(i)
:
for (let i = 0; i < 3; i++) { // 結果為 0、1、2
alert(i);
}
我們逐個部分分析 for
循環(huán):
語句段 | ||
---|---|---|
begin | let i = 0
|
進入循環(huán)時執(zhí)行一次。 |
condition | i < 3
|
在每次循環(huán)迭代之前檢查,如果為 false,停止循環(huán)。 |
body(循環(huán)體) | alert(i)
|
條件為真時,重復運行。 |
step | i++
|
在每次循環(huán)體迭代后執(zhí)行。 |
一般循環(huán)算法的工作原理如下:
開始運行
→ (如果 condition 成立 → 運行 body 然后運行 step)
→ (如果 condition 成立 → 運行 body 然后運行 step)
→ (如果 condition 成立 → 運行 body 然后運行 step)
→ ...
所以,begin
執(zhí)行一次,然后進行迭代:每次檢查 condition
后,執(zhí)行 body
和 step
。
如果你這是第一次接觸循環(huán),那么回到這個例子,在一張紙上重現它逐步運行的過程,可能會對你有所幫助。
以下是在這個示例中發(fā)生的事:
// for (let i = 0; i < 3; i++) alert(i)
// 開始
let i = 0
// 如果條件為真,運行下一步
if (i < 3) { alert(i); i++ }
// 如果條件為真,運行下一步
if (i < 3) { alert(i); i++ }
// 如果條件為真,運行下一步
if (i < 3) { alert(i); i++ }
// ……結束,因為現在 i == 3
內聯變量聲明
這里“計數”變量
i
是在循環(huán)中聲明的。這叫做“內聯”變量聲明。這樣的變量只在循環(huán)中可見。
for (let i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } alert(i); // 錯誤,沒有這個變量。
除了定義一個變量,我們也可以使用現有的變量:
let i = 0; for (i = 0; i < 3; i++) { // 使用現有的變量 alert(i); // 0, 1, 2 } alert(i); //3,可見,因為是在循環(huán)之外聲明的
for
循環(huán)的任何語句段都可以被省略。
例如,如果我們在循環(huán)開始時不需要做任何事,我們就可以省略 begin
語句段。
就像這樣:
let i = 0; // 我們已經聲明了 i 并對它進行了賦值
for (; i < 3; i++) { // 不再需要 "begin" 語句段
alert( i ); // 0, 1, 2
}
我們也可以移除 step
語句段:
let i = 0;
for (; i < 3;) {
alert( i++ );
}
該循環(huán)與 while (i < 3)
等價。
實際上我們可以刪除所有內容,從而創(chuàng)建一個無限循環(huán):
for (;;) {
// 無限循環(huán)
}
請注意 for
的兩個 ;
必須存在,否則會出現語法錯誤。
通常條件為假時,循環(huán)會終止。
但我們隨時都可以使用 break
指令強制退出。
例如,下面這個循環(huán)要求用戶輸入一系列數字,在輸入的內容不是數字時“終止”循環(huán)。
let sum = 0;
while (true) {
let value = +prompt("Enter a number", '');
if (!value) break; // (*)
sum += value;
}
alert( 'Sum: ' + sum );
如果用戶輸入空行或取消輸入,在 (*)
行的 break
指令會被激活。它立刻終止循環(huán),將控制權傳遞給循環(huán)后的第一行,即,alert
。
根據需要,"無限循環(huán) + break
" 的組合非常適用于不必在循環(huán)開始/結束時檢查條件,但需要在中間甚至是主體的多個位置進行條件檢查的情況。
continue
指令是 break
的“輕量版”。它不會停掉整個循環(huán)。而是停止當前這一次迭代,并強制啟動新一輪循環(huán)(如果條件允許的話)。
如果我們完成了當前的迭代,并且希望繼續(xù)執(zhí)行下一次迭代,我們就可以使用它。
下面這個循環(huán)使用 continue
來只輸出奇數:
for (let i = 0; i < 10; i++) {
//如果為真,跳過循環(huán)體的剩余部分。
if (i % 2 == 0) continue;
alert(i); // 1,然后 3,5,7,9
}
對于偶數的 i
值,continue
指令會停止本次循環(huán)的繼續(xù)執(zhí)行,將控制權傳遞給下一次 for
循環(huán)的迭代(使用下一個數字)。因此 alert
僅被奇數值調用。
?
continue
?指令利于減少嵌套顯示奇數的循環(huán)可以像下面這樣:
for (let i = 0; i < 10; i++) { if (i % 2) { alert( i ); } }
從技術角度看,它與上一個示例完全相同。當然,我們可以將代碼包裝在
if
塊而不使用continue
。
但在副作用方面,它多創(chuàng)建了一層嵌套(大括號內的
alert
調用)。如果if
中代碼有多行,則可能會降低代碼整體的可讀性。
禁止 ?
break/continue
? 在 ‘?’ 的右邊請注意非表達式的語法結構不能與三元運算符
?
一起使用。特別是break/continue
這樣的指令是不允許這樣使用的。
例如,我們使用如下代碼:
if (i > 5) { alert(i); } else { continue; }
……用問號重寫:
(i > 5) ? alert(i) : continue; // continue 不允許在這個位置
……代碼會停止運行,并顯示有語法錯誤。
這是不(建議)使用問號
?
運算符替代if
語句的另一個原因。
有時候我們需要一次從多層嵌套的循環(huán)中跳出來。
例如,下述代碼中我們的循環(huán)使用了 i
和 j
,從 (0,0)
到 (3,3)
提示坐標 (i, j)
:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果我想從這里退出并直接執(zhí)行 alert('Done!')
}
}
alert('Done!');
我們需要提供一種方法,以在用戶取消輸入時來停止這個過程。
在 input
之后的普通 break
只會打破內部循環(huán)。這還不夠 —— 標簽可以實現這一功能!
標簽 是在循環(huán)之前帶有冒號的標識符:
labelName: for (...) {
...
}
break <labelName>
語句跳出循環(huán)至標簽處:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果是空字符串或被取消,則中斷并跳出這兩個循環(huán)。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
alert('Done!');
上述代碼中,break outer
向上尋找名為 outer
的標簽并跳出當前循環(huán)。
因此,控制權直接從 (*)
轉至 alert('Done!')
。
我們還可以將標簽移至單獨一行:
outer:
for (let i = 0; i < 3; i++) { ... }
continue
指令也可以與標簽一起使用。在這種情況下,執(zhí)行跳轉到標記循環(huán)的下一次迭代。
標簽并不允許“跳到”所有位置
標簽不允許我們跳到代碼的任意位置。
例如,這樣做是不可能的:
break label; // 跳轉至下面的 label 處(無效) label: for (...)
break
指令必須在代碼塊內。從技術上講,任何被標記的代碼塊都有效,例如:
label: { // ... break label; // 有效 // ... }
……盡管 99.9% 的情況下
break
都被用在循環(huán)內,就像在上面那些例子中我們看到的那樣。
continue
只有在循環(huán)內部才可行。
我們學習了三種循環(huán):
while
?—— 每次迭代之前都要檢查條件。do..while
? —— 每次迭代后都要檢查條件。for (;;)
? —— 每次迭代之前都要檢查條件,可以使用其他設置。通常使用 while(true)
來構造“無限”循環(huán)。這樣的循環(huán)和其他循環(huán)一樣,都可以通過 break
指令來終止。
如果我們不想在當前迭代中做任何事,并且想要轉移至下一次迭代,那么可以使用 continue
指令。
break/continue
支持循環(huán)前的標簽。標簽是 break/continue
跳出嵌套循環(huán)以轉到外部的唯一方法。
重要程度: 3
此代碼最后一次 alert 值是多少?為什么?
let i = 3;
while (i) {
alert( i-- );
}
答案是:1
。
let i = 3;
while (i) {
alert( i-- );
}
每次循環(huán)迭代都將 i
減 1
。當檢查到 i = 0
時,while(i)
循環(huán)停止。
因此,此循環(huán)執(zhí)行的步驟如下(“循環(huán)展開”):
let i = 3;
alert(i--); // 顯示 3,i 減至 2
alert(i--) // 顯示 2,i 減至 1
alert(i--) // 顯示 1,i 減至 0
// 完成,while(i) 檢查循環(huán)條件并停止循環(huán)
重要程度: 4
對于每次循環(huán),寫出你認為會顯示的值,然后與答案進行比較。
以下兩個循環(huán)的 alert
值是否相同?
++i
:let i = 0;
while (++i < 5) alert( i );
i++
let i = 0;
while (i++ < 5) alert( i );
這個題目展現了 i++/++i 兩種形式在比較中導致的不同結果。
let i = 0;
while (++i < 5) alert( i );
第一個值是 i = 1
,因為 ++i
首先遞增 i
然后返回新值。因此先比較 1 < 5
然后通過 alert
顯示 1
。
然后按照 2, 3, 4…
—— 數值一個接著一個被顯示出來。在比較中使用的都是遞增后的值,因為 ++
在變量前。
最終,i = 4
時,在 ++i < 5
的比較中,i
值遞增至 5
,所以 while(5 < 5)
不符合循環(huán)條件,循環(huán)停止。所以沒有顯示 5
。
let i = 0;
while (i++ < 5) alert( i );
第一個值也是 i = 1
。后綴形式 i++
遞增 i
然后返回 舊 值,因此比較 i++ < 5
將使用 i = 0
(與 ++i < 5
不同)。
但 alert
調用是獨立的。這是在遞增和比較之后執(zhí)行的另一條語句。因此它得到了當前的 i = 1
。
接下來是 2, 3,4…
我們在 i = 4
時暫停,前綴形式 ++i
會遞增 i
并在比較中使用新值 5
。但我們這里是后綴形式 i++
。因此,它將 i
遞增到 5
,但返回舊值。因此實際比較的是 while(4 < 5)
——
true,程序繼續(xù)執(zhí)行 alert
。
i = 5
是最后一個值,因為下一步比較 while(5 < 5)
為 false。
重要程度: 4
對于每次循環(huán),寫下它將顯示的值。然后與答案進行比較。
兩次循環(huán) alert
值是否相同?
for (let i = 0; i < 5; i++) alert( i );
for (let i = 0; i < 5; ++i) alert( i );
答案:在這兩種情況下都是從 0
到 4
。
for (let i = 0; i < 5; ++i) alert( i );
for (let i = 0; i < 5; i++) alert( i );
這可以很容易地從 for
算法中推導出:
i = 0
?。i < 5
? 條件true
?—— 執(zhí)行循環(huán)體并 ?alert(i)
?,然后進行 ?i++
?遞增 ?i++
? 與檢查條件(2)分開。這只是另一種寫法。
在這沒使用返回的遞增值,因此 ?i++
? 和 ?++i
?之間沒有區(qū)別。
重要程度: 5
使用 for
循環(huán)輸出從 2
到 10
的偶數。
for (let i = 2; i <= 10; i++) {
if (i % 2 == 0) {
alert( i );
}
}
我們使用 “modulo” 運算符 %
來獲取余數,并檢查奇偶性。
重要程度: 5
重寫代碼,在保證不改變其行為的情況下,將 for
循環(huán)更改為 while
(輸出應保持不變)。
for (let i = 0; i < 3; i++) {
alert( `number ${i}!` );
}
let i = 0;
while (i < 3) {
alert( `number ${i}!` );
i++;
}
重要程度: 5
編寫一個提示用戶輸入大于 100
的數字的循環(huán)。如果用戶輸入其他數值 —— 請他重新輸入。
循環(huán)一直在請求一個數字,直到用戶輸入了一個大于 100
的數字、取消輸入或輸入了一個空行為止。
在這我們假設用戶只會輸入數字。在本題目中,不需要對非數值輸入進行特殊處理。
let num;
do {
num = prompt("Enter a number greater than 100?", 0);
} while (num <= 100 && num);
兩個檢查都為真時,繼續(xù)執(zhí)行 ?do..while
? 循環(huán):
num <= 100
? —— 即輸入值仍然不大于 ?100
?。num
?為 ?null
?或空字符串時,?&& num
? 的結果為 false。那么 ?while
?循環(huán)也會停止。P.S. 如果 ?num
?為 ?null
?,那么 ?num <= 100
? 為 ?true
?。因此用戶單擊取消,如果沒有第二次檢查,循環(huán)就不會停止。兩次檢查都是必須的。
重要程度: 3
大于 1
且不能被除了 1
和它本身以外的任何數整除的整數叫做素數。
換句話說,n > 1
且不能被 1
和 n
以外的任何數整除的整數,被稱為素數。
例如,5
是素數,因為它不能被 2
、3
和 4
整除,會產生余數。
寫一個可以輸出 2
到 n
之間的所有素數的代碼。
當 n = 10
,結果輸出 2、3、5、7
。
P.S. 代碼應適用于任何 n
,而不是對任何固定值進行硬性調整。
這個題目有很多解法。
我們使用一個嵌套循環(huán):
對于間隔中的每個 i {
檢查在 1~i 之間,是否有 i 的除數
如果有 => 這個 i 不是素數
如果沒有 => 這個 i 是素數,輸出出來
}
使用標簽的代碼:
let n = 10;
nextPrime:
for (let i = 2; i <= n; i++) { // 對每個自然數 i
for (let j = 2; j < i; j++) { // 尋找一個除數……
if (i % j == 0) continue nextPrime; // 不是素數,則繼續(xù)檢查下一個
}
alert( i ); // 輸出素數
}
這段代碼有很大的優(yōu)化空間。例如,我們可以從 2
到 i
的平方根之間的數中尋找除數。無論怎樣,如果我們想要在很大的數字范圍內實現高效率,我們需要改變實現方法,依賴高等數學和復雜算法,如二次篩選法(Quadratic sieve),普通數域篩選法(General number field sieve)等。
譯注:素數也稱為質數,對本答案的代碼進一步優(yōu)化,其實就是一道 LeetCode 算法題,感興趣的可以點擊鏈接查看如何通過 埃拉托斯特尼篩法篩選素數。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯系方式:
更多建議: