學(xué)習(xí)目標(biāo)
- 理解變量、循環(huán)、循環(huán)條件和循環(huán)體的概念
- 了解控制臺(tái)和日志的概念
- 畫出線和方塊
學(xué)習(xí)用時(shí):60分鐘
通過(guò)上節(jié)課的學(xué)習(xí),我們已經(jīng)掌握了如何通過(guò)畫一個(gè)個(gè)的點(diǎn)來(lái)組成圖案。很多同學(xué)在作業(yè)中都畫出了漂亮的圖案,深刻地領(lǐng)悟到了像素的威力。
相信大家都發(fā)現(xiàn)了一個(gè)問(wèn)題:有一片區(qū)域的點(diǎn)顏色是完全一樣的,但由于上節(jié)課我特別強(qiáng)調(diào)過(guò),不讓大家用fillRect來(lái)畫線和長(zhǎng)方形,于是只能一個(gè)點(diǎn)、一個(gè)點(diǎn)地畫出來(lái),復(fù)制一大堆語(yǔ)句,然后改成一連串的坐標(biāo)……
其實(shí)我們?cè)谄聊簧峡吹降乃袞|西,也都是電腦一個(gè)點(diǎn)接一個(gè)點(diǎn)畫出來(lái)的,只不過(guò)它畫得非??欤覀儾煊X(jué)不到它畫的過(guò)程而已。
由于我們使用的畫布尺寸很?。s放比例為10時(shí),尺寸為40x40),大多數(shù)同學(xué)的圖案也都在15x15的范圍以內(nèi),所以重復(fù)的代碼量還是可以接受的。同學(xué)們咬咬牙,也就做出來(lái)了。但是在今天動(dòng)轍720p、1080p甚至4K的分辨率下,一張畫面中的像素?cái)?shù)量幾乎是個(gè)天文數(shù)字,想要一個(gè)點(diǎn)一個(gè)點(diǎn)地畫,就幾乎是不可能完成的任務(wù)了。
這就像我們拿著一支筆在紙上畫畫,卻被要求只能一個(gè)點(diǎn)接一個(gè)點(diǎn)地把圖案“戳”出來(lái)一樣,很蛋疼是不是?有同學(xué)問(wèn)了:為什么我們不使用現(xiàn)成的fillRect工具,把筆拿起來(lái)“畫”呢?
我們只有在失去一件東西時(shí),才會(huì)真正領(lǐng)悟到它存在的意義。正如沒(méi)有自己親手洗過(guò)衣服的人,無(wú)法認(rèn)識(shí)到洗衣機(jī)的重要性一樣。要是你沒(méi)有親身體驗(yàn)過(guò)這種機(jī)械重復(fù)的“痛苦”,你就無(wú)法真正認(rèn)識(shí)到我們今天所學(xué)內(nèi)容的價(jià)值。
重復(fù)重復(fù)再重復(fù)

編程的主要意義之一,就是消除重復(fù)。對(duì)此我們有一條非常著名的編程原則:
Don’t Repeat Yourself:不要重復(fù)你自己
我們可以運(yùn)用編程思維,對(duì)一件需要重復(fù)進(jìn)行的工作進(jìn)行分析和提煉,然后精簡(jiǎn)成用有限語(yǔ)句構(gòu)成的表達(dá),來(lái)讓機(jī)器執(zhí)行。接下來(lái),我就來(lái)教大家怎樣畫出一條線,進(jìn)而畫出一個(gè)方塊。
我們先來(lái)看看,在日常生活中有哪些不斷重復(fù)的事情?
- 吃飯:一口接一口,吃飽為止……(明確的目標(biāo))
- 做俯臥撐:20次一組,1、2、3……(明確的數(shù)量目標(biāo))
- 洗草莓:一顆一顆地洗,洗完為止……(遍歷一個(gè)集合)
- ……
以上這些事情,雖然表面上的形式完全不同,但其中的模式都是相同的:
- 通過(guò)無(wú)數(shù)次重復(fù)的動(dòng)作完成
- 每次執(zhí)行的動(dòng)作大同小異
- 有明確的目標(biāo):吃飽 / 做夠數(shù)量的俯臥撐 / 洗完所有的草莓
- ……
當(dāng)我們做這些事情時(shí),其實(shí)是在進(jìn)行這樣的流程:

- 判斷目標(biāo)是否已經(jīng)達(dá)成:吃飽了沒(méi)?/ 做夠了沒(méi)?/ 都洗完了嗎?
- 已達(dá)成——結(jié)束流程:不吃了 / 不做了 / 不洗了
- 未達(dá)成——執(zhí)行動(dòng)作:吃一口飯 / 做一個(gè)俯臥撐 / 洗一顆草莓
這樣的重復(fù)流程,在編程中叫做循環(huán)(Loop)。
循環(huán)(Loop):一段在程序中只出現(xiàn)一次,但可能會(huì)連續(xù)運(yùn)行多次的代碼。
事實(shí)上,任何一個(gè)需要持續(xù)進(jìn)行的流程都是循環(huán):
- 走路:走到目的地了嗎?沒(méi)走到,那繼續(xù)走,走到為止……
- 看書:看完了沒(méi)有?沒(méi)看完,那繼續(xù)看,看完為止……
- 睡覺(jué):睡夠了沒(méi)有?沒(méi)睡夠,那繼續(xù)睡,睡夠?yàn)橹埂?/li>
- ……
循環(huán)什么時(shí)候停止?

循環(huán)的本質(zhì)是滿足條件則重復(fù)。比如就吃飯來(lái)說(shuō),當(dāng)我們已經(jīng)吃飽的時(shí)候,就沒(méi)有什么動(dòng)力再繼續(xù)吃了。也就是說(shuō),我們繼續(xù)吃下一口飯的前提是還沒(méi)吃飽,我們把這種前提稱為循環(huán)條件(Condition)。
循環(huán)條件(Condition):讓循環(huán)繼續(xù)重復(fù)執(zhí)行的條件
一般來(lái)說(shuō),循環(huán)條件也可以表現(xiàn)為一個(gè)只能用“是”或“否”回答的問(wèn)題。比如在吃飯的例子中,循環(huán)條件也可以表現(xiàn)為:“是不是沒(méi)吃飽?”
在生活中,如果我們?cè)趫?zhí)行重復(fù)流程前發(fā)現(xiàn)目標(biāo)設(shè)立的不合理(比如洗一整卡車的草莓),那我們可能一開(kāi)始就會(huì)拒絕執(zhí)行。如果目標(biāo)一開(kāi)始貌似可行,但在執(zhí)行的過(guò)程中發(fā)現(xiàn)是不可能的(比如吃完一只烤羊腿),或者遇到了意外情況(吃到蒼蠅 / 發(fā)生車禍 / 咖啡倒書上了……),我們也可能會(huì)讓循環(huán)提前中止。
即便是頭驢子,在發(fā)現(xiàn)無(wú)論如何都吃不到吊在面前的胡蘿卜時(shí),它也會(huì)放棄。但是電腦就沒(méi)有這么聰明了,程序中的循環(huán)條件再怎么不合理它也會(huì)乖乖地執(zhí)行,并會(huì)不知疲倦地一直運(yùn)行下去。如果循環(huán)條件永遠(yuǎn)成立,循環(huán)就不會(huì)終止,就會(huì)形成死循環(huán)(Infinite Loop)。
死循環(huán)(Infinite Loop):因循環(huán)條件永遠(yuǎn)成立而不會(huì)停止運(yùn)行的循環(huán)
“死循環(huán)”聽(tīng)上去是個(gè)可怕的概念。事實(shí)上,由于程序設(shè)計(jì)漏洞意外造成的死循環(huán),可能會(huì)讓電腦卡頓甚至死機(jī)。好在現(xiàn)在大多數(shù)操作系統(tǒng)都能處理這種情況,在這種情況下會(huì)友好地提示我們終止程序:

循環(huán)執(zhí)行時(shí)都做些什么?

吃飯時(shí)我們重復(fù)的動(dòng)作是什么呢?吃一口菜,吃一口飯,嚼一嚼,咽下去……這是在吃飯過(guò)程中要重復(fù)進(jìn)行的流程,我們稱之為循環(huán)體(Statement)。
循環(huán)體(Statement):每次循環(huán)時(shí)所執(zhí)行的流程
循環(huán)體可以是空的。比如在睡覺(jué)的時(shí)候,我們什么也不做。又比如在燒開(kāi)水的過(guò)程中,我們僅僅是在等待而已。
對(duì)一開(kāi)始就給定次數(shù)的循環(huán)(比如做俯臥撐),我們需要在每次循環(huán)時(shí)更新計(jì)數(shù),這樣才能知道什么時(shí)候結(jié)束循環(huán)。
對(duì)遍歷一個(gè)集合的循環(huán)(比如洗一盤子草莓),我們可能需要不斷減少集合里的成員(比如把洗好的草莓放到另一個(gè)盤子里),又或者給處理過(guò)的成員做標(biāo)記以區(qū)分(比如把洗好的草莓葉子都摘掉)。
在一次循環(huán)中,循環(huán)體到底重復(fù)執(zhí)行多少次,我們可能知道(比如做俯臥撐),也可能不知道(比如吃飯),也可能要做完了才知道(比如洗草莓)。
怎么畫一個(gè)方塊?

現(xiàn)在回到我們的主題:我們要畫一個(gè)方塊。但由于不能使用fillRect的后兩個(gè)參數(shù),所以我們現(xiàn)在只能畫出一個(gè)點(diǎn),除了寫一大堆畫點(diǎn)語(yǔ)句之外,目前我們對(duì)用其他方法來(lái)完成這個(gè)任務(wù)還沒(méi)有任何頭緒。
是時(shí)候祭出殺手锏了,讓我們來(lái)對(duì)這個(gè)任務(wù)進(jìn)行拆分。

我們知道,在屏幕上的一個(gè)方塊是由一大堆點(diǎn)構(gòu)成的。這些點(diǎn)可以視為緊緊排在一起的一堆線,可以是一堆垂直方向的線,也可以是一堆水平方向的線。所以我們可以把任務(wù)拆分為畫出一堆線。具體的流程是:
- 畫出一條線
- 知道下一條線畫在哪里
- 重復(fù)以上兩步,在畫夠數(shù)量之后停下來(lái)

線則是由同一方向的點(diǎn)構(gòu)成的,所以我們可以繼續(xù)拆分為畫出一堆點(diǎn)。具體的流程是:
- 畫一個(gè)點(diǎn)
- 知道下一個(gè)點(diǎn)畫在哪里
- 重復(fù)以上兩步,在畫夠數(shù)量之后停下來(lái)
畫一個(gè)點(diǎn)我們已經(jīng)會(huì)了;下一個(gè)點(diǎn)的坐標(biāo)位置我們也可以通過(guò)簡(jiǎn)單的加減計(jì)算得出;需要畫的數(shù)量就是方塊的尺寸。于是我們只要完成“重復(fù)以上兩步”的功能,就可以畫出一條線,進(jìn)而畫出方塊了。
那這個(gè)“重復(fù)以上兩步”怎么完成?這就需要用到我們剛學(xué)過(guò)的循環(huán)了。
初見(jiàn)循環(huán)
首先請(qǐng)?jiān)陔娔X上的Chrome瀏覽器中打開(kāi) http://codepen.io/zhangshenjia/pen/JWeWON,你會(huì)看到這樣的界面:

我們可以看到,畫布上有兩條水平線。再看看代碼:調(diào)縮放比例、調(diào)顏色、畫點(diǎn)……這些都是我們上節(jié)課玩過(guò)的東西。在最后面有三行代碼是我們沒(méi)見(jiàn)過(guò)的:

這是JS里的一個(gè)for循環(huán)。
除了for循環(huán)外,在JS語(yǔ)言中還有while、do/while、for in循環(huán),感興趣的朋友可以查看相關(guān)的文檔:http://www.w3school.com.cn/js/js_loop_for.asp
在 { 和 } 之前的代碼,就是這個(gè)循環(huán)的循環(huán)體。細(xì)心的同學(xué)可能發(fā)現(xiàn)了,這行代碼前面多加了兩個(gè)空格,這是為了表現(xiàn)代碼層次關(guān)系而添加的縮進(jìn)。行首的空格對(duì)程序的運(yùn)行來(lái)說(shuō)沒(méi)有任何實(shí)際影響,但能讓人更容易理解代碼的結(jié)構(gòu)。
有的程序員喜歡在行首增加兩個(gè)空格,有的程序員喜歡增加四個(gè)空格,還有的程序員喜歡在行首添加TAB制表符……孰優(yōu)孰劣,難有定論。但可以確定的是,在一份代碼里應(yīng)該始終使用相同的縮進(jìn)風(fēng)格。
等下,這行語(yǔ)句好像很眼熟……這不就是我們用過(guò)的畫點(diǎn)語(yǔ)句嗎?沒(méi)錯(cuò),就是它。但是我們仔細(xì)看看,就會(huì)發(fā)現(xiàn)有點(diǎn)區(qū)別:第一個(gè)數(shù)字變成字母 i 了。
可能有的同學(xué)在上節(jié)課改代碼時(shí)曾經(jīng)試過(guò)(什么,你沒(méi)試過(guò)?現(xiàn)在試試看?。?/em>,這里必須寫數(shù)字,如果改成字母,整個(gè)程序就會(huì)出問(wèn)題的。比如我們把第一行畫點(diǎn)語(yǔ)句里第一個(gè)數(shù)字改成字母 a 看看:

我的乖乖,整個(gè)畫布都空了,這說(shuō)明程序出問(wèn)題了,趕緊改回來(lái)吧。那循環(huán)里的畫點(diǎn)語(yǔ)句為什么就能這么寫呢?我們可以把它改成數(shù)字 0 看看:

這下可好,第二條線變成一個(gè)點(diǎn)了,為啥呢?想想就會(huì)知道,每次執(zhí)行循環(huán)的時(shí)候,都在0, 10這個(gè)固定的坐標(biāo)位置上畫點(diǎn),結(jié)果可不就重到一起了嘛!我們可以看看前面的那堆畫點(diǎn)語(yǔ)句(它們畫出了畫布最上方的那條線),可以看到第一個(gè)數(shù)字一直在發(fā)生變化:

把我們剛才的修改還原一下,第二條線就又出現(xiàn)了。事實(shí)上,每次循環(huán)時(shí),這個(gè) i 都在發(fā)生變化,它是一個(gè)變量(Variable)。
變量(Variable):可以用來(lái)保存和訪問(wèn)數(shù)據(jù)的具名地址
變量可以理解為我們做菜時(shí),用來(lái)盛裝食材的盤子。一開(kāi)始所有的盤子都是空的,在準(zhǔn)備炒菜的過(guò)程中,我們會(huì)把食材、調(diào)料等盛到不同的盤子里,然后按需取用。同一個(gè)盤子可能一會(huì)用來(lái)存放切好的蒜末,一會(huì)又用來(lái)盛拌好的涼菜……
做一頓飯可能會(huì)用到很多盤子,我們需要記住其中某些盤子的作用,比如這個(gè)盤子曾經(jīng)裝過(guò)生肉,那就不能用來(lái)裝熟食。在多人協(xié)作的廚房里,盤子可能超級(jí)多,單純靠個(gè)人的記憶就不太好使了,這時(shí)候就需要給盤子夾上標(biāo)簽來(lái)做記號(hào)(吃過(guò)麻辣香鍋或羊肉泡饃的同學(xué)應(yīng)該都見(jiàn)過(guò))。
同樣,程序里也可以有很多變量,所以我們也必須給變量進(jìn)行命名,這樣才能通過(guò)名字來(lái)使用它,也能通過(guò)名字得知變量的用途。這個(gè)循環(huán)變量的變量名就是 i。
為什么循環(huán)里的變量的名字要叫作 i 呢?這是個(gè)有趣的問(wèn)題,你可以在課后搜索研究一下。
我們把每次需要畫點(diǎn)的水平坐標(biāo)存放在變量 i 里,需要畫點(diǎn)的時(shí)候再把水平坐標(biāo)從變量 i 里讀取出來(lái)使用。在每次循環(huán)時(shí),我們都改變它的值,這樣就可以畫出一條線來(lái)了。
改改改!
接下來(lái),讓我們通過(guò)試探性地修改,來(lái)了解for循環(huán)中各部分的作用。首先,把for循環(huán)中出現(xiàn)的第二個(gè)數(shù)字 5 改成 10,看看會(huì)發(fā)生什么:

可以看到第二條線延長(zhǎng)了一倍。本來(lái)想要實(shí)現(xiàn)這樣的效果,我們是需要多寫5條畫點(diǎn)語(yǔ)句的。而現(xiàn)在只需要改一個(gè)數(shù)字就行了,有沒(méi)有很爽?看來(lái)這個(gè)數(shù)字決定了線條的長(zhǎng)度,也就是循環(huán)執(zhí)行多少次。
事實(shí)上,i < 10 就是循環(huán)的循環(huán)條件。在每次執(zhí)行循環(huán)體前,都會(huì)對(duì)它進(jìn)行判斷,只有在循環(huán)條件成立的情況下,循環(huán)體才會(huì)被執(zhí)行;如果不成立,循環(huán)就會(huì)結(jié)束。
要是把循環(huán)條件刪掉,或者改成 i > 0 會(huì)怎么樣?如果沒(méi)有循環(huán)條件,或者循環(huán)條件一直都滿足,就會(huì)死循環(huán)噢!你可以試試看(試完了記得把代碼改回來(lái))……放心,Chrome瀏覽器會(huì)檢查出死循環(huán)并終止它,不會(huì)死機(jī)的。
我們?cè)侔训谝粋€(gè)數(shù)字 0 改成 5 試試看:

可以看到第二條線變短且向右移動(dòng)了。這個(gè)數(shù)字決定了我們第一個(gè)點(diǎn)從哪里開(kāi)始畫。如果你把這個(gè)數(shù)字改成一個(gè)很大的數(shù),比如說(shuō) 100,你會(huì)發(fā)現(xiàn)一個(gè)點(diǎn)都沒(méi)有畫出來(lái),因?yàn)檠h(huán)條件 i < 10 得不到滿足,循環(huán)一次都不會(huì)被執(zhí)行。
事實(shí)上,var i = 5是循環(huán)的初始化語(yǔ)句,它只在循環(huán)一開(kāi)始執(zhí)行一次,聲明了一個(gè)變量 i 并給它賦一個(gè)初始值。這就相當(dāng)于我們從櫥柜拿出來(lái)一個(gè)盤子洗干凈,裝了點(diǎn)東西。
要是把初始化語(yǔ)句刪掉,會(huì)怎么樣呢?由于變量 i 沒(méi)有聲明,在判斷循環(huán)條件時(shí)程序就會(huì)報(bào)錯(cuò),循環(huán)根本就不會(huì)被執(zhí)行。
讓我們來(lái)研究下括號(hào)里的最后一部分,先把最后的數(shù)字 1 改成 2 看看:

恩,怎么變成虛線了?這個(gè)數(shù)字決定了下一個(gè)點(diǎn)畫在哪里。你可以通過(guò)修改這個(gè)數(shù)字來(lái)調(diào)整兩個(gè)點(diǎn)之間的距離(還可以是小數(shù)噢)。
事實(shí)上,* i = i + 1* 是循環(huán)的遞增語(yǔ)句,它在每次循環(huán)體執(zhí)行結(jié)束后都會(huì)被執(zhí)行。一般情況下,遞增語(yǔ)句主要的任務(wù)就是對(duì)循環(huán)變量進(jìn)行更新,確保循環(huán)不會(huì)變成死循環(huán)。
i = i + 1 還可以寫成 i += 1 和 **i++ **,感興趣的朋友可以看看JS運(yùn)算符的文檔:http://www.w3school.com.cn/js/js_operators.asp
for循環(huán)是按照什么流程執(zhí)行的?

- 先執(zhí)行初始化語(yǔ)句;
- 判斷循環(huán)條件,如果條件不滿足,則退出循環(huán);
- 如果條件滿足,則執(zhí)行循環(huán)體;
- 執(zhí)行遞增語(yǔ)句,然后跳到第二步。
我們可以在腦中模擬執(zhí)行一下我們的代碼,看看循環(huán)是怎么工作的:
循環(huán)剛開(kāi)始時(shí) i = 5,這當(dāng)然符合 i < 10 的循環(huán)條件,于是畫點(diǎn)語(yǔ)句被執(zhí)行,在坐標(biāo) 5, 10 的位置畫了一個(gè)點(diǎn)。隨后遞增語(yǔ)句 i = i + 1 執(zhí)行,i 的值變?yōu)?strong>6,繼續(xù)循環(huán)。
第二次循環(huán)時(shí) i = 6,循環(huán)條件 i < 10 依然成立,于是又在坐標(biāo) 6,10 的位置畫了一個(gè)點(diǎn),隨后 i 的值變?yōu)?strong>7。
……
第六次循環(huán)時(shí) i = 10,此時(shí)循環(huán)條件 i < 10 已經(jīng)不成立了,于是循環(huán)結(jié)束。
用嵌套循環(huán)來(lái)畫方塊
好了,現(xiàn)在我們已經(jīng)能用for循環(huán)來(lái)畫出N個(gè)點(diǎn)來(lái)組成一條水平線了,那怎么更進(jìn)一步,畫出N條水平線來(lái)組成一個(gè)方塊呢?
想要把畫線的代碼重復(fù)執(zhí)行N次,還是需要使用循環(huán)。不過(guò)這次我們的循環(huán)體不再是畫點(diǎn)的代碼了,而是畫線代碼——即我們剛才研究過(guò)的循環(huán)。
啥,循環(huán)體還可以是個(gè)循環(huán)?有點(diǎn)暈了,讓我喘口氣先……沒(méi)事,使勁喘,喘完了我們?cè)倮^續(xù)。
一個(gè)盒子里的空間可以用來(lái)裝尺寸合適的任何東西,當(dāng)然也可以用來(lái)裝另一個(gè)盒子。同樣,循環(huán)體是由代碼組成的,而整個(gè)循環(huán)本來(lái)就是一段代碼,所以我們當(dāng)然也可以在循環(huán)體里使用循環(huán),這叫做嵌套(Nesting)。
嵌套(Nesting):在一個(gè)結(jié)構(gòu)體內(nèi)部包含另一個(gè)結(jié)構(gòu)體

我們可以通過(guò)嵌套循環(huán)來(lái)解決多維度上的問(wèn)題,每層循環(huán)處理一個(gè)維度上的變化,將問(wèn)題“降維”之后在循環(huán)體內(nèi)進(jìn)一步解決。在我們今天的例子里,我們要在二維平面上畫一個(gè)方塊,就可以通過(guò)兩層嵌套循環(huán)來(lái)實(shí)現(xiàn),內(nèi)層在水平方向循環(huán)(畫點(diǎn)成線),外層在垂直方向循環(huán)(畫線成面)?,F(xiàn)在實(shí)現(xiàn)畫點(diǎn)成線功能的內(nèi)層循環(huán)我們已經(jīng)有了,我們需要在它外面再寫一個(gè)外層循環(huán)。
首先,請(qǐng)刷新一下頁(yè)面,把代碼恢復(fù)成原樣,然后把第8-12行的畫點(diǎn)語(yǔ)句刪掉。這樣屏幕上就只剩下用for循環(huán)畫的那條線了。在for語(yǔ)句前面插入一個(gè)空行,然后照貓畫虎寫一條for語(yǔ)句,然后在循環(huán)后面插入一個(gè)空行,輸入 } :

注意:在程序中出現(xiàn)的所有符號(hào)都是半角符號(hào)(如(;){),推薦在編程時(shí)關(guān)閉中文輸入法,避免不小心輸入全角符號(hào)(如(;){),導(dǎo)致語(yǔ)法錯(cuò)誤。
現(xiàn)在代碼的層次結(jié)構(gòu)并不是很清楚,讓我們選中內(nèi)層循環(huán)的那三行,按一下 TAB 鍵,給它們?cè)黾涌s進(jìn),這樣看上去就容易理解多了:

等等,怎么畫出來(lái)的還是條直線呢?因?yàn)槲覀冄h(huán)里的畫點(diǎn)語(yǔ)句只更改了x坐標(biāo),y坐標(biāo)一直都是10?,F(xiàn)在我們把畫點(diǎn)語(yǔ)句里的第二個(gè)數(shù)字 10 改成 i 看看:

這下我們畫出了一條斜線,還不是方塊,這是為什么呢?想想就能明白,水平和垂直方向坐標(biāo)相同,畫出來(lái)的點(diǎn)就是(1, 1)、(2、2)、……連起來(lái)可不就是一條斜線嘛!我們想讓水平和垂直坐標(biāo)分別進(jìn)行變化,只用一個(gè)變量 i 肯定是不行的,需要再增加一個(gè)變量。讓我們把內(nèi)層循環(huán)里的 i 改成 j,然后再把畫點(diǎn)語(yǔ)句里的第二個(gè) i 也改成 j:

耶,我們的方塊終于畫出來(lái)了!
為什么第二層循環(huán)的變量要取名為 j 呢?這也是一個(gè)慣例,如果有多層循環(huán),會(huì)從 i 開(kāi)始按順序使用字母作為循環(huán)變量。
通過(guò)輸出日志跟蹤程序的運(yùn)行情況

代碼的結(jié)構(gòu)層級(jí)越多,就越難搞明白它是怎么運(yùn)作的。如果只有一層循環(huán),還可以通過(guò)純粹的思考來(lái)模擬,畢竟只有一個(gè)循環(huán)變量在發(fā)生變化。但在多層嵌套循環(huán)里這樣做就難多了,因?yàn)槟阈枰涀∶恳粚友h(huán)的當(dāng)前循環(huán)次數(shù)以及對(duì)應(yīng)循環(huán)變量的當(dāng)前值。
另外,隨著嵌套循環(huán)層級(jí)的增多,最內(nèi)部循環(huán)體的運(yùn)行次數(shù)是呈指數(shù)增長(zhǎng)的。像我們剛才僅僅畫一個(gè)5x5的方塊,內(nèi)部的畫點(diǎn)語(yǔ)句就執(zhí)行了25次。想要跟蹤每一步運(yùn)行的情況不是不可以,而是太繁瑣了。
幸好,我們有其他方法可以用來(lái)跟蹤復(fù)雜程序的運(yùn)行過(guò)程,那就是在控制臺(tái)輸出日志。
控制臺(tái)(Console):一個(gè)交互界面,可以用來(lái)顯示程序運(yùn)行中的錯(cuò)誤信息和調(diào)試打印日志
日志(Log):在程序運(yùn)行過(guò)程中,按照指定的需求和格式留下的數(shù)據(jù)記錄
讓我們先在循環(huán)里的畫點(diǎn)語(yǔ)句后面新增一行,把下面的代碼復(fù)制過(guò)去:
console.log('i=' + i + ', j=' + j);

這行代碼的作用是,在每次執(zhí)行循環(huán)體時(shí),在控制臺(tái)把循環(huán)變量 i 和 j 的值打印出來(lái)。接下來(lái)我們就要打開(kāi)控制臺(tái),看看輸出的日志:
首先按下 F12(Mac電腦按下Command + Option + I),或者在網(wǎng)頁(yè)的任何位置點(diǎn)擊鼠標(biāo)右鍵,然后選擇最下面的“檢查”項(xiàng),打開(kāi)開(kāi)發(fā)人員工具:

有可能你的開(kāi)發(fā)人員工具出現(xiàn)在瀏覽器下面,或者是一個(gè)獨(dú)立的窗口。你可以在界面的右上角的關(guān)閉按鈕旁邊找到設(shè)置功能,選擇第三個(gè)布局,這樣它就會(huì)固定在瀏覽器的右邊了。

開(kāi)發(fā)人員工具默認(rèn)顯示的是 Elements 選項(xiàng)卡,這是用來(lái)顯示網(wǎng)頁(yè)元素結(jié)構(gòu)的,我們現(xiàn)在用不著。請(qǐng)切換到如圖所示的 Console 選項(xiàng)卡,就能看到一堆我們輸出的日志了:

通過(guò)跟蹤日志輸出,我們就可以知道每一次循環(huán)時(shí),變量 i 和 j 分別的變化情況。有同學(xué)問(wèn):這里面的日志太多了,分不清哪些是之前輸出的,哪些是這次輸出的,該怎么辦呢?

好辦,我們可以點(diǎn)擊左上角的清除按鈕把所有的日志都干掉,再對(duì)程序進(jìn)行一次無(wú)所謂的改動(dòng)(比如在一個(gè)空行里加個(gè)空格)讓它重新運(yùn)行一次,這樣新出現(xiàn)的日志就都是本次運(yùn)行輸出的了。
除了顯示錯(cuò)誤信息和記錄調(diào)試日志外,控制臺(tái)還可以直接輸入JS代碼進(jìn)行執(zhí)行,有興趣的朋友可以自己研究體驗(yàn)一下。
內(nèi)容回顧

本節(jié)課所學(xué)的概念:
循環(huán)(Loop):一段在程序中只出現(xiàn)一次,但可能會(huì)連續(xù)運(yùn)行多次的代碼。
循環(huán)條件(Condition):讓循環(huán)繼續(xù)重復(fù)執(zhí)行的條件
循環(huán)體(Statement):每次循環(huán)時(shí)所執(zhí)行的流程
死循環(huán)(Infinite Loop):因循環(huán)條件永遠(yuǎn)成立而不會(huì)停止運(yùn)行的循環(huán)
變量(Variable):可以用來(lái)保存和訪問(wèn)數(shù)據(jù)的具名地址
嵌套(Nesting):在一個(gè)結(jié)構(gòu)體內(nèi)部包含另一個(gè)結(jié)構(gòu)體
控制臺(tái)(Console):一個(gè)交互界面,可以用來(lái)顯示程序運(yùn)行中的錯(cuò)誤信息和調(diào)試打印日志
日志(Log):在程序運(yùn)行過(guò)程中,按照指定的需求和格式留下的數(shù)據(jù)記錄
本節(jié)課所學(xué)的原則:
Don’t Repeat Yourself:不要重復(fù)你自己
課后作業(yè)

1、找出至少一個(gè)在生活中循環(huán)的案例,并明確循環(huán)條件和循環(huán)體,比如:
循環(huán):吃飯
循環(huán)條件:沒(méi)吃飽
循環(huán)體:吃一口
2、配合使用循環(huán)和畫點(diǎn)語(yǔ)句,畫出更復(fù)雜的圖案。