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