原文地址: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)。如果你要一次键入多行代码,可以使用
+
,当你同时敲出这两个键,会另起一个新行,一旦你单独键入
,控制台会立即执行所有你键入的命令。
咱们一起熟悉下在控制台执行代码的过程。首先,我建议你在浏览器中打开一个新的选项卡。我更喜欢通过在地址栏键入about:blank
来做到这一点。然后,我们刚才提到的你的开发者控制台是打开的。
现在,键入以下代码,看看是怎么运行的:
a = 21;
b = a * 2;
console.log( b );
在Chrome的控制台中键入这段代码,会产生以下效果:
继续,尝试一下。学习编程最好的方法是是开始写代码!
输出
在之前代码片段中,我们使用了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特有的角度,重点介绍更多主题。这些主题会在本系列的其他书中介绍更多深入的细节。