原文地址:https://github.com/getify/You-Dont-Know-JS/blob/master/up%20&%20going/ch1.md
第一章:進入編程
歡迎來到You Don't Know JS (YDKJS) 系列
Up & Going 是對一些編程基本概念的介紹--當然我們只是特指關于JavaScript(經常縮寫為JS)--還有怎樣接近并理解本系列書的其他幾個主題。特別是如果你只是剛剛接觸編程和JavaScript,這本書會幫助你起步。這本書從在一個很高層次上解釋編程的基本法則開始。它的目標人群是剛開始學YDKJS,沒有什么編程經驗的人。這本書將會使你通過學習JavaScript走上一條漫長的理解編程的征程。
第一章會概覽一些你進入編程所需要的一些東西。還有一些非常棒的編程資料讓你可以深入這些主題,我希望你你可以多閱讀一些東西作為本章的補充。
一旦你熟悉了基本的編程規則,第二章將會幫助你嗅到JavaScript的味道。這一章介紹JavaScript,但是并不全面--那是本系列書籍的的其他幾本所要講的!
如果你已經對于JavaScript十分熟悉,通過第三章將會對YDKJS系列有所了解,接下來就直接進入主題吧!
代碼
我們從最開始的地方開始。
一個程序,常常被稱為『源代碼』或者『代碼』,是告訴電腦運行什么任務的一些特殊指令的集合。雖然我們待會兒會講JavaScript可以直接輸入在一個瀏覽器的控制臺里,但是代碼常常存在一個文本文件里。
格式校驗和指令組合規則被稱為計算機語言,有時被稱為語法,非常像英語告訴你怎樣拼寫單詞,怎樣用單詞和標點符號去創造一個合乎語法的句子。
語句
在一個計算機語言中,語句是指可以做一個特定任務的一組單詞,數字和操作符的組合。在JavaScript中,一條語句大概像這樣:
a = b * 2;
字符a和b被稱為變量,變量就像一些你可以裝東西的盒子。在程序中,變量裝著程序要用到的一些值(比如數值42),可以把他們看做這些值的符號占位符。
相反的,2只是代表數值本身,由于它沒有被存儲成一個變量,所以被稱為字面量值。
=和*字符是操作符--它們的表現是讓變量和數值做賦值和乘法操作。
大多數JavaScript的語句在最后有一個分號(;)。
語句 a = b * 2;就是告訴計算機做下面一些操作:把b中存儲的數值乘以2,然后把結果存儲到另一個變量a中。
程序只是很多語句的集合,這些語句組合在一起,描述了你的程序執行的步驟。
表達式
語句由一個或多個表達式構成。一個表達式是一個變量或者值的引用,或者由操作符連接的變量和值的組合。
比如:
a = b * 2;
這條語句中有四條表達式:
-
2
是一個字面值表達式 -
b
是一個變量表達式,表示取它存儲的當前值 -
b * 2
是一個算數表達式,表示做乘法后的值 -
a = b * 2
是一個賦值表達式,表示將b * 2
表達式的結果賦值給變量a
(稍后介紹賦值)
一個單獨的表達式也被稱為表達式語句,比如:
b * 2;
這種形式的表達式語句并不十分常見,也沒什么用。它通常對于程序沒有任何意義--這個表達式取出b的值,然后乘以2,但是沒有對結果做任何事情。
一個更常見的表達式語句是調用表達式語句(參見『函數』),整個語句本身是函數調用表達式:
alert( a );
執行一段程序
這些編程語句是怎樣告訴計算機怎么工作的?程序需要被執行,也稱為把程序跑起來。
像a = b * 2
這種語句對于開發者來說是非常便于閱讀和書寫的,但并不是一種能被計算機直接理解的形式。因此要有一種特殊工具(解釋器或者編譯器)把你寫的指令翻譯成計算機可以讀懂的形式。
對于一些計算機語言來說,這種指令的翻譯是在程序執行時,從上到下,一行接一行的,這種形式一般叫做解釋代碼。對于其他一些計算機語言,這種翻譯發生在程序執行之前,叫做編譯代碼,在程序執行時,實際上跑的是之前已經編譯好的計算機指令。
人們一貫認為JavaScript是解釋執行的,因為你的JavaScript代碼是在它運行時才會被處理。但是這種說法并不完全準確。JavaScript引擎會在程序被執行前的一瞬間飛快地編譯一下代碼。
說明:更多關于JavaScript編譯的信息,請參看本系列書籍的作用域 & 閉包的頭兩個章節。
自己試一試
本節將會通過一些代碼片段介紹一些編程概念,這些片段是用JavaScript寫的(顯然!)
這些非常重要:當你進入這一節--你需要花些時間反復練習--你應該親自打出這些代碼去聯系這些概念。要練習的話,最簡單的辦法是打開你手頭的瀏覽器(Firefox, Chrome, IE等)的控制臺。
提示:一般的,你可以用鍵盤快捷鍵或者從菜單項打開開發者控制臺。更多關于在你最喜歡的瀏覽器中打開和使用控制臺的信息請參看『Mastering The Developer Tools Console』(http://blog.teamtreehouse.com/mastering-developer-tools-console)。如果你要一次鍵入多行代碼,可以使用<shift>
+ <enter>
,當你同時敲出這兩個鍵,會另起一個新行,一旦你單獨鍵入<enter>
,控制臺會立即執行所有你鍵入的命令。
咱們一起熟悉下在控制臺執行代碼的過程。首先,我建議你在瀏覽器中打開一個新的選項卡。我更喜歡通過在地址欄鍵入about:blank
來做到這一點。然后,我們剛才提到的你的開發者控制臺是打開的。
現在,鍵入以下代碼,看看是怎么運行的:
a = 21;
b = a * 2;
console.log( b );
繼續,嘗試一下。學習編程最好的方法是是開始寫代碼!
輸出
在之前代碼片段中,我們使用了console.log(..)
。我們簡單看下這行代碼是什么意思。
你可能已經猜到,這行代碼正是我們在開發者控制臺打印文本(又稱輸出給用戶)的方法。有兩點我們需要解釋。
首先,log( b )
代表了一個函數調用(參見『函數』)。我們把變量b
傳給了函數,這個函數把b
的值打印到了控制臺。
其次,console.
代表了函數log(..)
所位于的對象。在第二章,我們會講關于對象和其屬性的更多細節。
另外一個輸出的方式是運行alert(..)
語句,比如:
alert( b );
如果你運行這條語句,你會發現它彈出一個帶有信息的『確定』彈窗,信息是變量b
的值,而不是將輸出打印在控制臺。然而,由于可以一次輸出很多值而不影響瀏覽器界面,使用console.log(..)
會讓你學習編程和運行程序更加容易一些。
在本書中,我們將會使用console.log(..)
。
輸入
但我們談論輸出時,你可能自然而然地想到輸入(換句話說,接受用戶的信息)。
在HTML中的輸入最常見的方式是,給用戶展示表單元素(比如輸入框),用戶可以輸入一些東西,然后我們可以使用JS讀取這些值到你的程序變量中。
如果只是簡單的學習和示范操作,有一種更簡單方法,使用prompt(..)
函數:
age = prompt( "Please tell me your age:" );
console.log( age );
你可能已經猜到,你傳給prompt(..)
的信息--在這里是"Please tell me your age:"
會打印在彈出框中。
一旦你點擊『OK』按鈕,你會發現,你輸入的值會存儲在變量age
中,然后我們使用'console.log(..)'輸出這個變量:
為了簡單起見,我們是學習基本的編程概念,本書中的示例將不會使用輸入。但是你可以試著挑戰一下,在你自己的代碼中使用prompt(..)
作為輸入方式。
操作符
操作符代表了我們對變量和值做什么樣的操作。我們已經見過了兩個JavaScript操作符,=
和*
。
*
代表了數學上的乘法操作,非常簡單,不是嗎?
=
被用于賦值--我們首先計算右邊的值(源值),然后將這個值放入左邊(目標值)的變量中。
警告:賦值語句可能看起來看起來比較奇怪,有些人可能習慣將源值放在左邊,目標值放在右邊,比如42 -> a
(這在JavaScript中并不合法)。不幸的是,a = 42
這種形式在現代編程語言中非常常見。如果你覺得不是很自然,那就花些時間適應一下吧。
考慮:
a = 2;
b = a + 1;
在這里,我們將2
賦值給a
,然后我們取出a
的值(仍是2
),并加1
,得到結果3
存儲在b
變量中。
你需要關鍵詞var
,雖然并不是一個嚴格意義上的操作符,但它是你聲明(又稱創建)變量的主要方法。
你應該在你使用變量前,用名稱聲明一下。在同一個作用域中,你只能聲明一次;在聲明后,你就按照所需多次使用。比如:
var a = 20;
a = a + 1;
a = a * 2;
console.log( a ); // 42
這里有一些常見的JavaScript操作符:
- 賦值:
a = 2
中的等號=
- 數學:
+
(加法),-
(減法),*
(乘法),/
(除法),例如a * 3
- 混合賦值:
+=
,-=
,*=
和/=
是混合操作符,他們將數學計算和賦值組合在一起,例如a += 2
(等同于a = a + 2
) - 自增/自減:
++
(增加),--
(減少),比如a++
(等同于a = a + 1
) - 對象屬性訪問:
.
,比如console.log()
中的.
對象可以包含很多屬性,這些屬性存儲其他的一些值。obj.a
表示一個叫做obj
的對象值,它內部含有一個叫做a
的屬性。屬性也可以用這種形式訪問obj["a"]
相等:==
(非嚴格相等),===
(嚴格相等),!=
(非嚴格不相等),!==
(嚴格不相等),例如a == b
- 比較:
<
(小于),>
(大于),<=
(小于等于),>=
(大于等于),例如a <= b
- 邏輯:
&&
(邏輯與),||
(邏輯或),例如a || b
,它表示選擇a或b
這些操作符經常被用來組合判斷條件,比如判斷a或b中任有一個為真
說明: 在這里并沒介紹全部操作符,要想進一步了解,請參看Mozilla Developer Network(MDN)的『表達式和操作符』 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators).
值和類型
如果你問手機店里的伙計一部手機多少錢,他們會告訴你:『99塊9毛9』($99.99)。他們實際上說出了一個數值,代表你要買一部手機要花多少錢(加上稅)。如果你要買兩部,你可以簡單的將一部的錢99.99乘以2得到199.98,這就是你要付的錢。
如果伙計拿起另一部手機說這個手機是免費的(胡謅的),他們并沒有說一個數值,但是用了等價于數值0.00花費的說法--單詞『免費』。
當你隨后問這個手機帶不帶充電器,伙計只會回答你『是』或『否』。
通常地,當你在程序中表示一些值時,你要根據你計劃用他們做什么來選擇不同的類型來表示。
這些不同的值的表現形式在編程術語中被稱為類型。JavaScript有一些內置的類型,也就是原始類型:
- 當你需要數學計算,你要用
number
類型 - 當你要在屏幕上打印一個值,你需要
string
類型(一個或多個字符,單詞,句子) - 當你要在程序中做判斷,你需要
boolean
類型(真或假)
在源代碼中直接包含的值,被稱為字面量。字符串字面量被雙引號"..."
或者單引號'...'
包裹--兩者之間只是個人風格喜好的差別。number
字面量和boolean
字面量表現為他們本身(42
,true
,false
)。
考慮:
"I am a string";
'I am also a string';
42;
true;
false;
在stirng
/number
/boolean
類型之外,編程語言通常提供arrays,objects,functions等類型。我們在本章以及之后會講更多關于值和類型的知識。
類型轉換
如果你有一個number
類型,但是你想把它打印在屏幕上,你需要把它轉換成string
類型。在JavaScript中這種轉換是"強制的"。同樣的,如果有人在電子商務頁面輸入一串數字字符,它實際上是一個字符串,如果要做數學運算,需要被『強制』轉換成number
類型,
JavaScript提供了一些不同的方法來做強制類型轉換。例如:
var a = "42";
var b = Number( a );
console.log( a ); // "42"
console.log( b ); // 42
使用Number(..)
(一個內置的函數)是一個顯式的強制類型轉換,把其他類型轉換為number
類型。這個非常的明確簡單。
但是有一個爭議的話題,當你試圖比較兩個不同類型的值時,將會進行隱式地強制轉換。
當比較字符串"99.99"
和數字99.99
時,大多數人認為它們是相同的。但是實際上是不同的。它們是同樣一個值,但是是兩種不同的類型。所以你可以說他們是『非嚴格的相等』,難道不是嗎?
為了幫助你處理這些相同的情況,JavaScript有時會介入并隱式地進行強制類型成匹配的類型
因此如果你使用==
非嚴格相等操作符來進行比較"99.99" == 99.99
,JavaScript會把左邊的值"99.99"
轉換成數字99.99
,這樣比較就變成了99.99 == 99.99
,這顯然會返回true
。
如果你沒有花些時間去學習隱式的類型轉換的規則,那么這種設計可能會給你制造混亂。大多數JS開發者都是這樣的,他們共同的體會是,隱式的類型轉換令人迷惑并且會制造意想不到的bugs,因此需要避免使用。隱式的類型轉換有時甚至會稱為這門語言設計的瑕疵。
然而,對于認真對待JavaScript編程的的人來說,隱式的類型轉換是一種『可以學會』甚至『應該學會』的機制。不僅僅是因為一旦你學習了它的轉換規則就不會迷惑,并且這種機制實質上會讓你的程序變得更好!你的努力是值得的。
注意: 更多關于強制類型轉換的信息,請參看本書的第二章以及『類型與語法』的第四章。
代碼注釋
手機店里伙計可能寫了一些筆記,這些筆記可能是一款新手機的功能,或者他們公司新的計劃。這些筆記只是給手機店雇員看的--并不是給消費者看。盡管如此,這些筆記可以提醒手機店伙計應該告訴消費者哪些信息以及說話方式,讓伙計更好的完成工作。
你應該學習到的很重要的一點就是你寫的代碼并不是只是給電腦看。如果代碼只是一些字母,那么它之于開發者就像編譯器一樣。你的電腦只關心機器碼,一系列的零和一,這些機器碼從匯編而來。你可以編寫出的零和一的組合幾乎是無限的。你寫代碼時做出的選擇--不僅影響你自己,而且會影響你的同事甚至未來某一天的你。
你應該堅持寫程序不僅要做到能夠正確執行,還要在被檢查時程序顯得有道理。你要在選擇變量和函數名稱上花些功夫,那是一條漫長的路。
但是另外非常重要的一塊就是代碼注釋。這是一段單純的插入在程序中的文本,只是為了向人們解釋一些東西。解釋/編譯器會忽略這些注釋。
有很多能讓代碼注釋寫好的建議;我們不能真正定義絕對通用的規則。但是一些觀點和指導是非常有用的:
- 沒有注釋的代碼是不合適的
- 太多的注釋(比如一行一個)可能說明代碼寫的非常的脆弱
- 注釋應該解釋為什么,而不是是什么。如果在特別混亂的情況下,注釋可能會解釋怎么辦
在JavaScript中,有兩種注釋:單行注釋和多行注釋。
考慮:
// This is a single-line comment
/* But this is a multiline comment. */
//
單行注釋適用于將注釋寫在單個語句上面,或者寫在一行的結尾。//
符號后的任何東西直到這一行結束被視為注釋(因此編譯器會忽略)。單行注釋中的內容并沒有嚴格限定。
考慮:
var a = 42; // 42 is the meaning of life
/* .. */
多行注釋適用于你需要用好幾行語言才能解釋清楚你的意思的情況。
這里有一個常見的多行注釋情況:
/* The following value is used because
it has been shown that it answers
every question in the universe. */
var a = 42;
多行注釋也可以出現在一行代碼中,由于可以用*/
結束,它甚至可以出現在在一行代碼的中間,例如:
var a = /* arbitrary value */ 42;
console.log( a ); // 42
在多行注釋中唯一不能出現的符號是*/
,因為它會被識別為多行注釋的結束。
你一定要在開始學習編程時,養成注釋代碼的習慣。在本章,你會看到我用注釋解釋我的代碼,你在練習時也要這樣。相信我,每一個讀你代碼的人都會感謝你的!
變量
很多可用的程序需要跟蹤一個值,因為它會不斷變化。你的程序可以根據任務需要對它做不同的操作。
最簡單的辦法就是把值分配在一個符號化的容器中,稱為變量--之所以叫變量是因為容器中的值可以隨時根據需要變化。
在一些編程語言中,你聲明一個變量(容器)來裝載一個特殊類型的值,例如number
或者string
。靜態類型,也稱為強制類型通常被使用,因為它有利于程序的正確性,可以防止非預期的值轉換。
其他一些語言則香調值的類型而不是變量的類型。弱類型,也稱為動態類型可以讓一個變量在任何時間下裝載任何類型的值。它通常被使用是因為它有利于程序的靈活性,可以讓單個變量呈現一個值,無論這個值在程序的邏輯里任何時間下的任何類型。
JavaScript uses the latter approach, dynamic typing, meaning
JavaScript使用了后一種,動態類型,意味著變量可以裝載任何類型的值,而不用類型強制轉換。
之前我們提到,我們可以使用var
語句來聲明一個變量--注意這里在聲明時并沒有類型信息。考慮這個簡單的程序:
var amount = 99.99;
amount = amount * 2;
console.log( amount ); // 199.98
// convert `amount` to a string, and
// add "$" on the beginning
amount = "$" + String( amount );
console.log( amount ); // "$199.98"
amount
變量在一開始裝載了數字99.99
,然后裝載了amount * 2
的結果199.98
,也是一個數字。
第一個console.log(..)
語句必須將數值強制轉換為字符串才能將其打印出來。
然后語句amount = "$" + String(amount)
顯式地將199.98
轉換為字符串,并在一開始加了一個"$"
字符。此時,amount
變量就裝載了字符串"$199.98"
。因此第二個console.log(..)
就不用強制類型轉換就能將其打印出來了。
JavaScript會注意到,amount
變量可以裝載99.99
,199.98
,"$199.98"
中任何一個,這有極大的靈活性。而靜態類型的擁護者會用另外一個變量,比如amountStr
來裝載"$199.98"
,因為它是另外一種類型。
不管怎樣,你都會注意到,amount
變量裝載了一個在程序的運行過程中變化的值,這體現了變量的首要目的:管理程序的狀態。
換句話說,你的程序運行的狀態是根據變量的變化而變化的。
變量的另外一種常見用法是將值的設置集中在一起。如果你在程序中聲明了一個變量,而這個變量所代表的值在程序中是不變的,此時一貫地,我們稱之為常量。
通常在一段程序的頂部聲明這些常量,這樣方便你需要它們的時候使用它們。傳統上,JavaScript常量常常使用大寫字母表示,并且用_
來分隔多個單詞。
這有一個小例子:
var TAX_RATE = 0.08; // 8% sales tax
var amount = 99.99;
amount = amount * 2;
amount = amount + (amount * TAX_RATE);
console.log( amount ); // 215.9784
console.log( amount.toFixed( 2 ) ); // "215.98"
注意:函數log(..)
是變量console
的一個對象屬性,因此使用console.log(..)
就可以訪問到。與之相同,函數toFixed(..)
是number
類型上的一個屬性。JavaScript中的數字不會自動格式化為美元形式 -- js引擎并不知道你的意圖,并且也沒有專門的貨幣類型。toFixed(..)
可以讓我們將number
根據指定小數位數進行四舍五入,同時,它會輸出string
類型。
TAX_RATE
是一個按照慣例來命名的常量 -- 在程序中,我們并沒有辦法阻止它發生改變。但是如果城市將銷售稅提高到9%,我們只需要更新TAX_RATE
的值為0.09
,而不需要在程序中到處找0.08
來更改。
在現在最新版本的JavaScript(通常稱為『ES6』)中,有一種新辦法來聲明常量,使用const
來代替var
:
// as of ES6:
const TAX_RATE = 0.08;
var amount = 99.99;
// ..
常量是非常有用的,一旦被初始化后,就不能被改變了。如果你給TAX_RATE
在第一次定義后設置一個其他的值,你的程序是拒絕的。(在嚴格模式下,會報錯 -- 見第二章的『嚴格模式』)。
同時,這種『保護』可以減少錯誤的產生,就像強制靜態類型檢查,所以你可以看到為什么在其他語言中,靜態類型是吸引人的。
注意: 更多關于變量在你的程序中的不同應用的內容,見于本系列的『類型和語法』。
代碼塊
當你要買你的新手機時,手機店伙計必須經過一系列的步驟才能完成結賬。
同樣的,在代碼中,我們經常需要將一系列的語句進行分組,我們通常稱之為塊。在JavaScript中,一個塊會被定義為,一條或多條代碼,被大括號{ .. }
所包裹。
考慮:
var amount = 99.99;
// a general block
{
amount = amount * 2;
console.log( amount ); // 199.98
}
這種獨立的{ .. }
是合法的,但是這種用法在JS并不常見。典型地,塊附著于一些其他的控制語句,比如if
語句(見『條件』)或者循環語句(見『循環』),例如:
var amount = 99.99;
// is amount big enough?
if (amount > 10) { // <-- block attached to `if`
amount = amount * 2;
console.log( amount ); // 199.98
}
我們將會在下一節解釋if
語句。你可以看到,{ .. }
語句快和它的兩條語句依附于if (amount > 10)
語句;塊中的語句只有在條件通過時才能執行。
注意:不同于其他語句比如console.log(amount)
,塊語句不需要用分號(;)
來結束。
條件
『你需要花9.99美元買一個屏幕保護殼嗎?』手機店的伙計讓你做一個決定。然后要回答這個問題,你可能需要首先衡量你的錢包或者銀行賬戶的現在的狀態。但是,明顯地,這只是一個『是或者否』的問題。在我們的程序中,我們有一些方法來表達條件(也稱為決定)。
最常用的方法是if
語句。大體上,你會說,『如果這個條件為真,做以下這些事……』。例如:
var bank_balance = 302.13;
var amount = 99.99;
if (amount < bank_balance) {
console.log( "I want to buy this phone!" );
}
if
語句需要在()
中寫一個表達式,它可以被當做真
或者假
。在這段程序中,我們提供了一個表達式amount < bank_balance
,根據amount
以及bank_balance
變量值的大小的不同,表達式指示了真
或者假
。
你可以在if
語句判斷為非真時,提供一個可選的分支,稱為else
:
const ACCESSORY_PRICE = 9.99;var bank_balance = 302.13;
var amount = 99.99;
amount = amount * 2;
// can we afford the extra purchase?
if ( amount < bank_balance ) {
console.log( "I'll take the accessory!" );
amount = amount + ACCESSORY_PRICE;
}
// otherwise:
else {
console.log( "No, thanks." );
}
在這里,如果amount < bank_balance
為真,增加9.99
到amount
變量。否則,在else
分支里,我們只會禮貌地回答『不用,謝謝!』,保持amount
不變。
正如我們之前討論過『值與類型』,值如果不是預期的類型,會被強制類型轉換。if
語句期望一個boolean
類型,如果你傳入的不是一個boolean
類型,此時強制類型轉換就會發生。
JavaScript定義了一系列類似為『假』的值,因為當強制類型轉換發生時,他們會被轉換為『假』--包括0
和""
。反之有一些值會被轉換為『真』,例如99.99
和"free"
。更多信息請看第二章『真與假』。
除if
之外,條件也會出現在其他地方。比如,switch
語句可以看作是一系列的if..else
語句。循環使用條件來決定循換是繼續進行還是停止。
注意:更多在條件
表達式中關于隱式類型轉換的深層次信息請看本系列書的第四部分『類型與語法』。
循環
在忙的時候,有需求的顧客們會排起長隊,等待手機店的伙計。只要還有顧客在排隊,伙計就需要一直服務下去。重復一系列動作直到某個條件失敗--換句話說,只要條件滿足,就要一直重復--程序的循環也是這樣;循環有很多不同的形式,但是他們全部滿足這種基本行為。
一個循環包括測試條件和一個代碼塊(典型的比如{ .. }
)。每一次代碼塊中的代碼執行稱為一次迭代。例如while
循環和do..while
循環表示重復執行代碼塊中代碼,直到某個條件不在為『真』:
while (numOfCustomers > 0) {
console.log( "How may I help you?" );
// help the customer...
numOfCustomers = numOfCustomers - 1;
}
// versus:
do {
console.log( "How may I help you?" );
// help the customer...
numOfCustomers = numOfCustomers - 1;
}
while (numOfCustomers > 0);
實際上,這兩個循環的區別只是第一次循換是在條件測試之前(do..while
)或者之后(while
)。
它們的共同點是如果條件測試為『假』,接下來的一次循環是不會執行的。這意味著如果條件測試在第一次就為『假』,while
循環中的語句一次都不會執行。但是do..while
會運行一次。
有時你循環的目的是計算一個確切的數字集合,例如從0
到9
(十個數字)。你可以設置一個循環變量例如i
為0
,然后每次循環增加1
。
注意:因為一些歷史原因,編程語言通常從0開始計數,而不是1。如果你對這種思維方式不熟悉,可能在開始的時候很迷惑。花些時間練習下從0開始計數,然后你就會適應它!
條件會在每次循環時都被測試,就向有一個隱式的if
語句在循環中。同時,我們可以看到創建一個死循環是非常容易的。
我們來看下:
var i = 0;
// a `while..true` loop would run forever, right?
while (true) {
// stop the loop?
if ((i <= 9) === false) {
break;
}
console.log( i );
i = i + 1;
}
// 0 1 2 3 4 5 6 7 8 9
注意:這并不是是一個實際的例子。只是為了說明問題而已。
一個while
(或者do..while
)循環可以手工完成這項任務,還有另外一種循環的語法形式for
循環可以自然地完成這項任務:
for (var i = 0; i <= 9; i = i + 1) {
console.log( i );
}
// 0 1 2 3 4 5 6 7 8 9
你可以看到,在10次循環(i
從0
增加到9
)中,i <= 9
為真。但是一旦i
的值為10,表達式就變為假。
for
循環有三塊語句:初始化語句(var i=0
),條件測試語句(i <= 9
),更新語句(i = i + 1
)。因此如果你想在循環中計數,使用for
循環會使代碼更加緊湊,并且更容易理解和編寫。
還有另外一些特殊的循環形式,例如循環遍歷一個對象的所有屬性(見于第二章),此時條件測試僅僅是所有的屬性都被遍歷完。無論那種形式的循環,『循環至某個條件測試失敗』總是有效的。
函數
手機店的伙計可能并不會隨身攜帶一個計算器來計算需要交多少稅以及最終要交多少錢。這是一種她可能需要定義一次然后重復使用的任務。可能的是,公司會有一個結算終端(計算機,平板電腦等),里面內嵌了這些『函數』。
同樣的,你的程序一定會被將代碼的任務切分成可以復用的片段,而不是反復多次地重復自己(雙關語!)。可以通過定義一個函數來達到這個目的。
一個函數大概就是擁有一個名稱的代碼片段,這個名字可以被用來『調用』,一到函數被調用,其內部的代碼會被執行。考慮:
function printAmount() {
console.log( amount.toFixed( 2 ) );
}
var amount = 99.99;
printAmount(); // "99.99"
amount = amount * 2;
printAmount(); // "199.98"
函數可以有選擇性的傳入參數--你傳入的值。同時可以有選擇性的返回一個值。
function printAmount(amt) {
console.log( amt.toFixed( 2 ) );
}
function formatAmount() {
return "$" + amount.toFixed( 2 );
}
var amount = 99.99;
printAmount( amount * 2 ); // "199.98"
amount = formatAmount();
console.log( amount ); // "$99.99"
函數printAmount(..)
要傳入被稱為amt
的參數。函數formatAmount()
返回一個值。當然你可以把這兩項技術應用在同一個函數中。
函數通常被用在一些代碼被多次調用的情況下,但是在將一段代碼組織在一個有名稱的集合里也是有用的,即使在你只想調用他們一次。
考慮:
const TAX_RATE = 0.08;
function calculateFinalPurchaseAmount(amt) {
// calculate the new amount with the tax
amt = amt + (amt * TAX_RATE);
// return the new amount
return amt;
}
var amount = 99.99;
amount = calculateFinalPurchaseAmount( amount );
console.log( amount.toFixed( 2 ) ); // "107.99"
雖然calculateFinalPurchaseAmount(..)
只被調用一次,組織它的行為到一個命名的函數中,這會讓調用的地方(the amount = calculateFinal...
語句)的邏輯顯得十分清晰。如果函數有更多的邏輯在其中,這種優勢會更加明顯。
作用域
如果你問手機店的伙計買一個她們店里沒有的手機,那么她講無法賣給你。她只能買給你她們貨品清單上的手機。你需要去另一家手機看看是否有你想要的手機。
程序也有類似的概念:作用域(嚴格來講叫做詞法作用域)。在JavaScript中每一個函數擁有它自己的作用域。作用域基本上是一些變量的集合,同時提供了這些變量通過名稱訪問的方法。只有函數中的代碼可以訪問函數作用域中的變量。在同一個作用域中的單個變量的名稱是唯一的--一個變量名a
不能指代兩個變量。但是相同的變量名a
可以出現在不同的作用域中。
function one() {
// this `a` only belongs to the `one()` function
var a = 1;
console.log( a );
}
function two() {
// this `a` only belongs to the `two()` function
var a = 2;
console.log( a );
}
one(); // 1
two(); // 2
同時,一個作用域可以嵌套另一個作用域,就像一個小丑在生日聚會上吹了一個氣球里面套著另一個氣球。如果一個作用域嵌套于另一個作用域中,那么它就可以訪問到它的父級作用域中的變量。
function outer() {
var a = 1;
function inner() {
var b = 2;
// we can access both `a` and `b` here
console.log( a + b ); // 3
}
inner();
// we can only access `a` here
console.log( a ); // 1
}
outer();
詞法作用域規定,在一個作用域中的代碼可以訪問到該作用域中的變量以及任何它的外層作用域中的變量。
因此,inner()
中的代碼同時可以訪問到變量a
和b
,但是在outer()
中的代碼只能訪問到變量a
--它訪問不到b
,以為這個變量只存在于inner()
中。
再來看下之前的一段代碼:
const TAX_RATE = 0.08;
function calculateFinalPurchaseAmount(amt) {
// calculate the new amount with the tax
amt = amt + (amt * TAX_RATE);
// return the new amount
return amt;
}
由于詞法作用域的存在,TAX_RATE
常量可以在函數calculateFinalPurchaseAmount(..)
中訪問到,即使我們沒有將它傳進函數。
注意:更多關于詞法作用域的信息,請看作用域與閉包的的前三章。
練習
在學習編程中,絕對沒有別的辦法可以代替練習。在我看來只有足夠數量的寫作表達才能讓你真正成為一個編程者。
有了這樣的共識,我們一起來練習一下這一章中我們學到的一些概念。我會給出『要求』,你先自己試一試,然后我會給出一些參考代碼來展示我是怎樣實現的。
- 寫一段代碼來計算你在手機店總共花費。你將會一直購買手機(提示:循環)直到你的銀行賬戶里的錢都花完。同時你會為每一個手機購買配件直到你的銀行賬戶不足以支持你的購買欲望。
- 在計算玩你的購買花費后,你要加上稅收,然后打印出計算后的花費,要有正確的格式。
- 最后,要檢查下總的花費和你的銀行賬戶,來看下你是否能支付得起。
- 你應該為"tax rate(稅率)", "phone price(手機價格)","accessory price(配件價格)",和"spending threshold(花費閾值)"設置一些常量,同時要為"bank account balance(銀行賬戶余額)"設置一個變量
- 你應該定義一些函數來計算稅收和格式化輸出價格,包括加"$"前綴和四舍五入至兩位小數。
-
額外挑戰:試著將輸入引入到程序中,可能要使用在『輸入』中講到的
prompt(..)
。例如,你可能要提示用戶輸入他們的銀行賬戶余額。享受樂趣,發揮創造力!
好的,繼續。大家可以嘗試自己編碼,請先不要看我的提示代碼。
注意:由于這是一本關于JavaScript的書,顯然,我將使用JavaScript來解決實踐問題。當然,如果能感到更加順手,你可以使用其他編程語言來完成。
下面是我的JavaScript解決方法:
const SPENDING_THRESHOLD = 200;
const TAX_RATE = 0.08;
const PHONE_PRICE = 99.99;
const ACCESSORY_PRICE = 9.99;
var bank_balance = 303.91;
var amount = 0;
function calculateTax(amount) {
return amount * TAX_RATE;
}
function formatAmount(amount) {
return "$" + amount.toFixed( 2 );
}
// keep buying phones while you still have money
while (amount < bank_balance) {
// buy a new phone!
amount = amount + PHONE_PRICE;
// can we afford the accessory?
if (amount < SPENDING_THRESHOLD) {
amount = amount + ACCESSORY_PRICE;
}
}
// don't forget to pay the government, too
amount = amount + calculateTax( amount );
console.log( "Your purchase: " + formatAmount( amount ));
// Your purchase: $334.76
// can you actually afford this purchase?
if (amount > bank_balance) {
console.log( "You can't afford this purchase. :(" );
}
// You can't afford this purchase. :(
注意:運行JavaScritpt程序最簡單的方法是,在你的瀏覽器中的開發者控制臺輸入你的程序。
你做的怎么樣?看了我的代碼后你可以再試一試。并在改變一些常量,以了解程序在不同的值中如何運行。
回顧
學習編程并不是一個復雜而難以忍受的過程。這里有幾條基本概念你需要掌握。
這些過程就像制作一些磚頭。要建造一個高塔,你需要從一塊磚頭摞在另一塊磚頭開始。同理在編程中。這里有一些基本的編程制作磚頭的方法:
- 你需要操作符來描述值的操作。
- 你需要值和類型來展示不同的操作,例如
number
的數學操作以及string
類型的輸出 - 你需要變量在程序執行時來存儲數據(又稱狀態)
- 你需要條件判斷比如
if
語句來做出一個決定 - 你需要循環來重復任務知道某個條件不在為真
- 你需要函數來組織你的代碼到具有邏輯性和復用性的代碼塊中
代碼注釋是一種編寫可讀代碼非常有效的的方法,這樣會讓你的代碼更容易理解,維護以及在之后有問題時容易修復。
最后,不要輕視練習的能量。學習寫代碼的最好方法是練習寫代碼。
我非常高興你現在在編程的道路上已經上道了!保持下去。不要忘了閱讀一些新手編程資源(書籍,博客,在線練習等)。這一章和這本書是一個偉大的開始,但是它們僅僅是一個簡要介紹。
下一章,我們將會復習本章所述的概念,從JavaScript特有的角度,重點介紹更多主題。這些主題會在本系列的其他書中介紹更多深入的細節。