- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
你不懂JS:入门与进阶
展开查看详情
1 . 目 录 致谢 阅前必读 序 第一章:进入编程 代码 亲自尝试 操作符 值与类型 代码注释 变量 块儿 条件 循环 函数 练习 复习 第二章:进入JavaScript 值与类型 变量 条件 Strict模式 函数作为值 this 标识符 原型 旧的与新的 非JavaScript 复习 第三章:进入YDKJS 复习 作用域与闭包 this与对象原型 类型与文法 异步与性能 本文档使用 书栈(BookStack.CN) 构建 - 1 -
2 . ES6与未来 附录A:鸣谢 本文档使用 书栈(BookStack.CN) 构建 - 2 -
3 .致谢 致谢 当前文档 《你不懂JS:入门与进阶(You Dont Know JS)》 由 进击的皇虫 使用 书栈 (BookStack.CN) 进行构建,生成于 2018-02-07。 书栈(BookStack.CN) 仅提供文档编写、整理、归类等功能,以及对文档内容的生成和导出工 具。 文档内容由网友们编写和整理,书栈(BookStack.CN) 难以确认文档内容知识点是否错漏。如 果您在阅读文档获取知识的时候,发现文档内容有不恰当的地方,请向我们反馈,让我们共同携手, 将知识准确、高效且有效地传递给每一个人。 同时,如果您在日常生活、工作和学习中遇到有价值有营养的知识文档,欢迎分享到 书栈 (BookStack.CN) ,为知识的传承献上您的一份力量! 如果当前文档生成时间太久,请到 书栈(BookStack.CN) 获取最新的文档,以跟上知识更新换 代的步伐。 文档地址:http://www.bookstack.cn/books/You-Dont-Know-JS-up-going 书栈官网:http://www.bookstack.cn 书栈开源:https://github.com/TruthHun 分享,让知识传承更久远! 感谢知识的创造者,感谢知识的分享者,也感谢每一位阅读到此处的 读者,因为我们都将成为知识的传承者。 本文档使用 书栈(BookStack.CN) 构建 - 3 -
4 .阅前必读 阅前必读 你不懂JS:入门与进阶 你不懂JS:入门与进阶 从O’Reilly购买数字/印刷版 序(Jenn Lukas) 前言 第一章:进入编程 第二章:进入JavaScript 第三章:进入YDKJS 附录A:鸣谢 本文档使用 书栈(BookStack.CN) 构建 - 4 -
5 .阅前必读 本文档使用 书栈(BookStack.CN) 构建 - 5 -
6 .序 序 你不懂JS:入门与进阶 序 你不懂JS:入门与进阶 序 你学的最后一个新东西是什么? 也许是一门外语,比如意大利语或德语。或者可能是一种图像编辑器,比如 Photoshop。或者是一 种烹饪技术,木工活,日常锻炼。我想让你回忆一下你最终学会它时的感觉:醍醐灌顶的时刻。当事 情从模糊不清变得豁然开朗,正如你掌握了如何使用台锯,或者理解了法语中雄性名词和雌性名词的 区别。那种感觉怎么样?非常美妙,对吧? 现在我想让你再多向前回忆一些,找到你学会新技能之前的那一刻。它 感觉如何?可能有点儿吓人, 也可能有点儿沮丧,是吧?在某一个时刻,我们都还不知道我们现在知道的事情,而这完全没问题; 我们是从某处开始的。学习新的东西是一次激动人心的冒险,特被是当你想高效地学习它时。 我教授过许多面向初学者的编程课程。上我课的学生们经常试着通过阅读博客或者拷贝粘贴代码来自 学HTML或JavaScript这样的东西,但是他们都没能真正掌握能够使他们编写出自己渴望的结果的技 能。而且,因为他们没有真正把握关于编程的特定问题的内在和外在,他们不能编写强大的代码或调 试自己的程序,因为他们没有真正地理解发生的事情。 我总是相信教授我的课程的正确方法,意味着我教授Web标准,语义标记,良好注释的代码,和其他 的最佳实践。我使用一种彻底的方式讲解问题来阐明如何做与为何做,而非通过复制粘贴来倒腾代 码。当你努力理解你的代码时,你就在创造更好的成果,并在编程上变得更加纯熟。代码不再仅仅是 你的 工作,而是你的 作品。这就是为什么我喜爱 入门与进阶。Kyle通过深入讲解语法和术语给我 们带来了一个对JavaScript的全面介绍。这本书不是浅尝辄止,而是让我们真正地理解我们将要编 写的东西。 能够在你的网站中复制JQuery代码段是不够的,就像在Photoshop中仅仅学习如何打开,关闭和保 存一个文档是不够的一样。确实,只要我学会了一些关于编程的基本我就可以制造并分享一些我的设 计。但是没有合理地了解这些工具和它们背后的机制,我又如何定义一个网格,或者建造一个合理的 类型系统,或者为Web优化图像呢?JavaScript也一样。不知道循环如何工作,或者如何定义变 量,或者作用域是什么,我们将不能写出最好的代码。我们不想安于这种次优的状态 —— 这毕竟是我 们的作品。 你对JavaScript探索得越多,它就变得越清晰。闭包,对象,和方法这样的词现在可能看起来与你 还有些距离,但是这本书将会帮你搞清楚这些术语。我希望你在开始阅读这本书时保持学会东西之前 本文档使用 书栈(BookStack.CN) 构建 - 6 -
7 .序 与之后的那两种感觉。它看起来可能有些令人望而却步,但是你已经拿起了这本书,你开启了一个了 不起的旅程来磨练自己的知识。入门与进阶 是我们理解编程之路的开端。享受醍醐灌顶的时刻吧! Jenn Lukas jennlukas.com, @jennlukas 前端顾问 本文档使用 书栈(BookStack.CN) 构建 - 7 -
8 .第一章:进入编程 第一章:进入编程 第一章:进入编程 第一章:进入编程 欢迎来到 你不懂JS(YDKJS)系列。 入门与进阶 是一个对几种编程基本概念的介绍 —— 当然我们是特别倾向于JavaScript(经常略称 为JS)的 —— 以及如何看待与理解本系列的其他书目。特别是如果你刚刚接触编程和/或 JavaScript,这本书将简要地探索你需要什么来 入门与进阶。 这本书从很高的角度来解释编程的基本原则开始。它基本上假定你是在没有或很少的编程经验的情况 下开始阅读 YDKJS 的,而且你期待这些书可以透过JavaScript的镜头帮助你开启一条理解编程的 道路。 第一章应当作为一个快速的概览来阅读,它讲述为了 进入编程 你将想要多加学习和实践的东西。有 许多其他精彩的编程介绍资源可以帮你在这个话题上走得更远,而且我鼓励你学习它们来作为这一章 的补充。 一旦你对一般的编程基础感到适应了,第二章将指引你熟悉JavaScript风格的编程。第二章介绍了 JavaScript是什么,但是同样的,它不是一个全面的指引 —— 那是其他 YDKJS 书目的任务! 如果你已经相当熟悉JavaScript,那么就首先看一下第三章作为 YDKJS 内容的简要一瞥,然后一 头扎进去吧! 代码 亲自尝试 操作符 值与类型 代码注释 变量 块儿 条件 循环 函数 练习 复习 本文档使用 书栈(BookStack.CN) 构建 - 8 -
9 .第一章:进入编程 本文档使用 书栈(BookStack.CN) 构建 - 9 -
10 .代码 代码 语句 表达式 执行一个程序 让我们从头开始。 一个程序,经常被称为 源代码 或者只是 代码,是一组告诉计算机要执行什么任务的特殊指令。代 码通常保存在文本文件中,虽然你也可以使用JavaScript在一个浏览器的开发者控制台中直接键入 代码 —— 我们一会儿就会讲解。 合法的格式与指令的组合规则被称为一种 计算机语言,有时被称作它的 语法,这和英语教你如何拼 写单词,和如何使用单词与标点创建合法的句子差不多是相同的。 语句 在一门计算机语言中,一组单词,数字,和执行一种具体任务的操作符构成了一个 语句。在 JavaScript中,一个语句可能看起来像下面这样: 1. a = b * 2; 字符 a 和 b 被称为 变量(参见“变量”),它们就像简单和盒子,你可以把任何东西存储在其 中。在程序中,变量持有将被程序使用的值(比如数字 42 )。可以认为它们就是值本身的标志占位 符。 相比之下, 2 本身只是一个值,称为一个 字面值,因为它没有被存入一个变量,是独立的。 字符 = 和 * 是 操作符(见“操作符”) —— 它们使用值和变量实施动作,比如赋值和数学乘法。 在JavaScript中大多数语句都以末尾的分号( ; )结束。 语句 a = b * 2; 告诉计算机,大致上,去取得当前存储在变量 b 中的值,将这个值乘以 2 ,然 后将结果存回到另一个我们称为 a 变量里面。 程序只是许多这样的语句的集合,它们一起描述为了执行你的程序的意图所要采取的所有步骤。 表达式 语句是由一个或多个 表达式 组成的。一个表达式是一个引用,指向变量或值,或者一组用操作符组 合的变量和值。 例如: 本文档使用 书栈(BookStack.CN) 构建 - 10 -
11 .代码 1. a = b * 2; 这个语句中有四个表达式: 2 是一个 字面量表达式 b 是一个 变量表达式,它意味着取出它的当前值 b * 2 是一个 算数表达式,它意味着执行乘法 a = b * 2 是一个 赋值表达式,它意味着将表达式 b * 2 的结果赋值给变量 a (稍后有更多关 于赋值的内容) 一个独立的普通表达式也被称为一个 表达式语句,比如下面的: 1. b * 2; 这种风格的表达式语句不是很常见也没什么用,因为一般来说它不会对程序的运行有任何影响 —— 它 将取得 b 的值并乘以 2 ,但是之后不会对结果做任何事情。 一种更常见的表达式语句是 调用表达式 语句(见“函数”),因为整个语句本身是一个函数调用表达 式: 1. alert( a ); 执行一个程序 这些程序语句的集合如何告诉计算机要做什么?这个程序需要被 执行,也称为 运行这个程序。 在开发者们阅读与编写时,像 a = b * 2 这样的语句很有帮助,但是它实际上不是计算机可以直接理 解的形式。所以一个计算机上的特殊工具(不是一个 解释器 就是一个 编译器)被用于将你编写的 代码翻译为计算机可以理解的命令。 对于某些计算机语言,这种命令的翻译经常是在每次程序运行时从上向下,一行接一行完成的,这通 常成为代码的 解释。 对于另一些语言,这种翻译是提前完成的,成为代码的 编译,所以当程序稍后 运行 时,实际上运 行的东西已经是编译好,随时可以运行的计算机指令了。 JavaScript通常被断言为是 解释型 的,因为你的JavaScript源代码在它每次运行时都被处理。 但这并不是完全准确的。JavaScript引擎实际上在即时地 编译 程序然后立即运行编译好的代码。 注意: 更多关于JavaScript编译的信息,参见本系列的 作用域与闭包 的前两章。 本文档使用 书栈(BookStack.CN) 构建 - 11 -
12 .代码 本文档使用 书栈(BookStack.CN) 构建 - 12 -
13 .亲自尝试 亲自尝试 输出 输入 这一章将用简单的代码段来介绍每一个编程概念,它们都是用JavaScript写的(当然!)。 有一件事情怎么强调都不过分:在你通读本章时 —— 而且你可能需要花时间读好几遍 —— 你应当通 过自己编写代码来实践这些概念中的每一个。最简单的方法就是打开你手边的浏览器(Firefox, Chrome,IE,等等)的开发者工具控制台。 提示: 一般来说,你可以使用快捷键或者菜单选项来启动开发者控制台。更多关于启动和使用你最喜 欢的浏览器的控制台的细节,参见“精通开发者工具控制 台”(http://blog.teamtreehouse.com/mastering-developer-tools-console)。要在 控制台中一次键入多行,可以使用` + 来移动到下一行。一旦你敲击 `,控制台将运行你刚刚键入的任何 东西。 让我们熟悉一下在控制台中运行代码的过程。首先,我建议你在浏览器中打开一个新的标签页。我喜 欢在地址栏中键入 about:blank 来这么做。然后,确认你的开发者控制台是打开的,就像我们刚刚提 到的那样。 现在,键入如下代码看看它是怎么运行的: 1. a = 21; 2. 3. b = a * 2; 4. 5. console.log( b ); 在Chrome的控制台中键入前面的代码应该会产生如下的东西: 继续,试试吧。学习编程的最佳方式就是开始编码! 本文档使用 书栈(BookStack.CN) 构建 - 13 -
14 .亲自尝试 输出 在前一个代码段中,我们使用了 console.log(..) 。让我们简单地看看这一行代码在做什么。 你也许已经猜到了,它正是我们如何在开发者控制台中打印文本(也就是向用户 输出)的方法。这个 语句有两个性质,我们应当解释一下。 首先, log( b ) 部分被称为一个函数调用(见“函数”)。这里发生的事情是,我们将变量 b 交给 这个函数,它向变量 b 要来它的值,并在控制台中打印。 第二, console. 部分是一个对象引用,这个对象就是找到 log(..) 函数的地方。我们会在第二章 中详细讲解对象和它们的属性。 另一种创建你可以看到的输出的方式是运行 alert(..) 语句。例如: 1. alert( b ); 如果你运行它,你会注意到它不会打印输出到控制台,而是显示一个内容为变量 b 的“OK”弹出框。 但是,一般来说与使用 alert(..) 相比,使用 console.log(..) 会使学习编码和在控制台运行你的 程序更简单一些,因为你可以一次输出许多值,而不必干扰浏览器的界面。 在这本书中,我们将使用 console.log(..) 来输出。 输入 虽然我们在讨论输出,你也许还想知道 输入(例如,从用户那里获得信息)。 对于HTML网页来说,输入发生的最常见的方式是向用户显示一个他们可以键入的form元素,然后使 用JS将这些值读入你程序的变量中。 但是为了单纯的学习和展示的目的 —— 也就是你在这本书中将通篇看到的 —— 有一个获取输入的更 简单的方法。使用 prompt(..) 函数: 1. age = prompt( "Please tell me your age:" ); 2. 3. console.log( age ); 正如你可能已经猜到的,你传递给 prompt(..) 的消息 —— 在这个例子中, "Please tell me your age:" —— 被打印在弹出框中。 它应当和下面的东西很相似: 本文档使用 书栈(BookStack.CN) 构建 - 14 -
15 .亲自尝试 一旦你点击“OK”提交输入的文本,你将会看到你输入的值被存储在变量 age 中,然后我们使 用 console.log(..) 把它 输出: 为了让我们在学习基本编程概念时使事情保持简单,本书中的例子不要求输入。但是现在你已经看到 了如何使用 prompt(..) ,如果你想挑战一下自己,你可以试着在探索这些例子时使用输入。 本文档使用 书栈(BookStack.CN) 构建 - 15 -
16 .操作符 操作符 操作符是我们如何在变量和值上实施操作的方式。我们已经见到了两种JavaScript操作 符, = 和 * 。 * 操作符实施数学乘法。够简单的,对吧? = 操作符用于 赋值 —— 我们首先计算 = 右手边 的值(源值)然后将它放进我们在 左手边 指定的变量中(目标变量)。 警告: 对于指定赋值,这看起来像是一种奇怪的倒置。与 a = 42 不同,一些人喜欢把顺序反转过 来,于是源值在左而目标变量在右,就像 42 -> a (这不是合法的JavaScript!)。不幸的 是, a = 42 顺序的形式,和与其相似的变种,在现代编程语言中是十分流行的。如果它让你觉得不 自然,那么就花些时间在脑中演练这个顺序并习惯它。 考虑如下代码: 1. a = 2; 2. b = a + 1; 这里,我们将值 2 赋值给变量 a 。然后,我们取得变量 a 的值(还是 2 ),把它加 1 得 到值 3 ,然后将这个值存储到变量 b 中。 虽然在技术上说 var 不是一个操作符,但是你将在每一个程序中都需要这个关键字,因为它是你 声 明(也就是 创建)变量(见“变量”)的主要方式。 你应当总是在使用变量前用名称声明它。但是对于每个 作用域(见“作用域”)你只需要声明变量一 次;它可以根据需要使用任意多次。例如: 1. var a = 20; 2. 3. a = a + 1; 4. a = a * 2; 5. 6. console.log( a ); // 42 这里是一些在JavaScript中最常见的操作符: 赋值:比如 a = 2 中的 = 。 数学: + (加法), - (减法), * (乘法),和 / (除法),比如 a * 3 。 复合赋值: += , -= , *= ,和 /= 都是复合操作符,它们组合了数学操作和赋值,比如 a += 2 (与 a = a + 2 相同)。 本文档使用 书栈(BookStack.CN) 构建 - 16 -
17 .操作符 递增/递减: ++ (递增), -- (递减),比如 a++ (和 a = a + 1 很相似)。 对象属性访问:比如 console.log() 的 . 。 对象是一种值,它可以在被称为属性的,被具体命名的位置上持有其他的值。 obj.a 意味着一 个称为 obj 的对象值有一个名为 a 的属性。属性可以用 obj["a"] 这种替代的方式访问。参 见第二章。 等价性: == (宽松等价), === (严格等价), != (宽松不等价), !== (严格不等 价),比如 a == b 。 参见“值与类型”和第二章。 比较: < (小于), > (大于), <= (小于或宽松等价), >= (大于或宽松等价), 比如 a <= b 。 参见“值与类型”和第二章。 逻辑: && (与), || (或),比如 a || b 它选择 a 或 b 中的一个。 这些操作符用于表达复合的条件(见“条件”),比如如果 a 或者 b 成立。 注意: 更多细节,以及在此没有提到的其他操作符,可以参见Mozilla开发者网络(MDN)的“表达 式与操作符”(https://developer.mozilla.org/en- US/docs/Web/JavaScript/Guide/Expressions_and_Operators)。 本文档使用 书栈(BookStack.CN) 构建 - 17 -
18 .值与类型 值与类型 类型间转换 如果你问一个手机店的店员一种特定手机的价格,而他们说“九十九块九毛九”(即,$99.99),他 们给了你一个实际的美元数字来表示你需要花多少钱才能买到它。如果你想两部这种手机,你可以很 容易地心算这个值的两倍来得到你需要花费的$199.98。 如果同一个店员拿起另一部相似的手机说它是“免费的”(也许在用手比划引号),那么他们就不是在 给你一个数字,而是你的花费($0.00)的另一种表达形式 —— “免费”这个词。 当你稍后问到这个手机是否带充电器时,回答可能仅仅是“是”或者“不”。 以同样的方式,当你在程序中表达一个值时,你根据你打算对这些值做什么来选择不同的表达形式。 在编程术语中值的这些不同的表达形式称为 类型。JavaScript中对这些所谓的 基本类型 值都有 内建的类型: 但你需要做数学计算时,你需要一个 number 。 当你需要在屏幕上打印一个值时,你需要一个 string (一个或多个字符,单词,句子)。 当你需要在你的程序中做决定时,你需要一个 boolean ( true 或 false )。 在源代码中直接包含的值称为 字面量。 string 字面量被双引号 "..." 或单引号( '...' )包围 —— 唯一的区别是风格上的偏好。 number 和 boolean 字面量用它们本身来表示 (即, 42 , true ,等等)。 考虑如下代码: 1. "I am a string"; 2. 'I am also a string'; 3. 4. 42; 5. 6. true; 7. false; 在 string / number / boolean 值的类型以外,编程语言通常会提供 数组,对象,函数 等更多 的类型。我们会在本章和下一章中讲解更多关于值和类型的内容。 类型间转换 如果你有一个 number 但需要将它打印在屏幕上,那么你就需要将这个值转换为一个 string ,在 JavaScript中这种转换称为“强制转换”。类似地,如果某些人在一个电商网页的form中输入一系列 数字,那么它是一个 string ,但是如果你需要使用这个值去做数学运算,那么你就需要将它 强制 本文档使用 书栈(BookStack.CN) 构建 - 18 -
19 .值与类型 转换 为一个 number 。 为了在 类型 之间强制转换,JavaScript提供了几种不同的工具。例如: 1. var a = "42"; 2. var b = Number( a ); 3. 4. console.log( a ); // "42" 5. console.log( b ); // 42 使用上面展示的 Number(..) (一个内建函数)是一种从任意其他类型到 number 类型的 明确的 强 制转换。这应当是相当直白的。 但是一个具有争议的话题是,当你试着比较两个还不是相同类型的值时发生的事情,它需要 隐含的 强制转换。 当比较字符串 "99.99" 和数字 99.99 时,大多数人同意它们是等价的。但是他们不完全相同,不是 吗?它们是相同的值的两种不同表现形式,两个不同的 类型。你可以说它们是“宽松地等价”的,不 是吗? 为了在这些常见情况下帮助你,JavaScript有时会启动 隐含的 强制转换来把值转换为匹配的类 型。 所以如果你使用 == 宽松等价操作符来进行 "99.99" == 99.99 比较,JavaScript会将左手边 的 "99.99" 转换为它的 number 等价物 99.99 。所以比较就变成了 99.99 == 99.99 ,这当然是 成立的。 虽然隐含强制转换是为了帮助你而设计,但是它也可能把你搞糊涂,如果你没有花时间去学习控制它 行为的规则。大多数开发者从没有这么做,所以常见的感觉是隐含的强制转换是令人困惑的,并且会 产生意外的bug危害程序,因此应当避免使用。有时它甚至被称为这种语言中的设计缺陷。 然而,隐含强制转换是一种 可以被学习 的机制,而且是一种 应当 被所有想要认真对待 JavaScript编程的人学习的机制。一旦你学习了这些规则,它不仅是消除了困惑,而且它实际上是 你的程序变得更好!这种努力是值得的。 注意: 关于强制转换的更多信息,参见本书第二章和本系列 类型与文法 的第四章。 本文档使用 书栈(BookStack.CN) 构建 - 19 -
20 .代码注释 代码注释 手机店店员可能会写下一些笔记,记下新出的手机的特性或者他们公司推出的新套餐。这些笔记仅仅 是给店员使用的 —— 他们不是给顾客读的。不管怎样,通过记录下为什么和如何告诉顾客他应当说的 东西,这些笔记帮助店员更好的工作。 关于编写代码你要学的最重要的课程之一,就是它不仅仅是写给计算机的。代码的每一个字节都和写 给编译器一样,也是写给开发者的。 你的计算机只关心机器码,一系列源自 编译 的0和1。你几乎可以写出无限多种可以产生相同0和1序 列的代码。所以你对如何编写程序作出的决定很重要 —— 不仅是对你,也对你的团队中的其他成员, 甚至是你未来的自己。 你不仅应当努力去编写可以正确工作的程序,而且应当努力编写检视起来有道理的程序。你可以通过 给变量(见“变量”)和函数(见“函数”)起一个好名字在这条路上走很远。 但另外一个重要的部分是代码注释。它们纯粹是为了向人类解释一些事情而在你的程序中插入的一点 儿文本。解释器/编译器将总是忽略这些注释。 关于什么是良好注释的代码有许多意见;我们不能真正地定义绝对统一的规则。但是一些意见和指导 是十分有用的: 没有注释的代码是次优的。 过多的注释(比如,每行都有注释)可能是代码编写的很烂的标志。 注释应当解释 为什么,而不是 是什么。它们可以选择性地解释 如何做,如果代码特别令人困 惑的话。 在JavaScript中,有两种可能的注释类型:单行注释和多行注释 考虑如下代码: 1. // 这是一个单行注释 2. 3. /* 而这是 4. 一个多行 5. 注释。 6. */ 如果你想在一个语句的正上方,或者甚至是在行的末尾加一个注释, // 单行注释是很合适的。这一 行上 // 之后的所有东西都将被视为注释(因此被编译器忽略),一直到行的末尾。在单行注释内部 可以出现的内容没有限制。 考虑: 本文档使用 书栈(BookStack.CN) 构建 - 20 -
21 .代码注释 1. var a = 42; // 生命的意义是 42 如果你想在注释中用好几行来解释一些事情, /* .. */ 多行注释就很合适。 这是多行注释的一个常见用法: 1. /* 使用下面的值是因为 2. 它回答了 3. 全宇宙中所有的问题。 */ 4. var a = 42; 它还可以出现在一行中的任意位置,甚至是一行的中间,因为 */ 终结了它。例如: 1. var a = /* 随机值 */ 42; 2. 3. console.log( a ); // 42 在多行注释中唯一不能出现的就是 */ ,因为这将干扰注释的结尾。 你绝对会希望通过养成注释代码的习惯来开始学习编程。在本书剩余的部分中,你将看到我使用注释 来解释事情,请也在你自己的实践中这么做。相信我,所有阅读你的代码的人都会感谢你! 本文档使用 书栈(BookStack.CN) 构建 - 21 -
22 .变量 变量 大多数有用的程序都需要在程序运行整个过程中,追踪由于你的程序所意图的任务被调用的底层不同 的操作而发生的值的变化。 要这样做的最简单的方法是将一个值赋予一个符号容器,称为一个 变量 —— 因为在这个容器中的值 可以根据需要不时 变化 而得名。 在某些编程语言中,你可以声明一个变量(容器)来持有特定类型的值,比如 number 或 string 。 因为防止了意外的类型转换,静态类型,也被称为 类型强制,通常被认为是对程序正确性有好处的。 另一些语言在值上强调类型而非在变量上。弱类型,也被称为 动态类型,允许变量在任意时刻持有任 意类型的值。因为它允许一个变量在程序逻辑流程中代表一个值,而不论这个值在任意给定的时刻是 什么类型,所以它被认为是对程序灵活性有好处的。 JavaScript使用的是后者,动态类型,这意味着变量可以持有任意 类型 的值而没有任何 类型 强 制约束。 正如我们刚才提到的,我们使用 var 语句来声明一个变量 —— 注意在这种声明中没有其他的 类型 信息。考虑这段简单的代码: 1. var amount = 99.99; 2. 3. amount = amount * 2; 4. 5. console.log( amount ); // 199.98 6. 7. // 将 `amount` 转换为一个字符串, 8. // 并在开头加一个 "$" 9. amount = "$" + String( amount ); 10. 11. console.log( amount ); // "$199.98" 变量 amount 开始时持有数字 99.99 ,然后持有 amount * 2 的 number 结果,也就 是 199.98 。 第一个 console.log(..) 命令不得不 隐含地 将这个 number 值强制转换为一个 string 才能够打 印出来。 然后语句 amount = "$" + String(amount) 明确地 将值 199.98 强制转换为一个 string 并且在开 头加入一个 "$" 字符。这时, amount 现在就持有这个 string 值 $199.98 ,所以第二 个 console.log(..) 语句无需强制转换就可以把它打印出来。 本文档使用 书栈(BookStack.CN) 构建 - 22 -
23 .变量 JavaScript开发者将会注意到为值 99.99 , 199.98 ,和 "$199.98" 都使用变量 amount 的灵 活性。静态类型的拥护者们将偏好于使用一个分离的变量,比如 amountStr 来持有这个值最后 的 "$199.98" 表达形式,因为它是一个不同的类型。 不管哪种方式,你将会注意到 amount 持有一个在程序运行过程中不断变化的值,这展示了变量的主 要目地:管理程序 状态。 换句话说,在你程序运行的过程中 状态 追踪着值的改变。 变量的另一种常见用法是将值的设定集中化。当你为一个在程序中通篇不打算改变的值声明了一个变 量时,它更一般地被称为 常量。 你经常会在程序的顶部声明这些 常量,这样提供了一种方便:如果你需要改变一个值时你可以到唯一 的地方去寻找。根据惯例,用做常量的JavaScript变量通常是大写的,在多个单词之间使用下划 线 _ 连接。 这里是一个呆萌的例子: 1. var TAX_RATE = 0.08; // 8% sales tax 2. 3. var amount = 99.99; 4. 5. amount = amount * 2; 6. 7. amount = amount + (amount * TAX_RATE); 8. 9. console.log( amount ); // 215.9784 10. console.log( amount.toFixed( 2 ) ); // "215.98" 注意: console.log(..) 是一个函数 log(..) 作为一个在值 console 上的对象属性被访问,与此 类似,这里的 toFixed(..) 是一个可以在值 number 上被访问的函数。JavaScript number 不会 被自动地格式化为美元 —— 引擎不知道你的意图,而且也没有通货类型。 toFixed(..) 让我们指明 四舍五入到小数点后多少位,而且它如我们需要的那样产生一个 string 。 变量 TAX_RATE 只是因为惯例才是一个 常量 —— 在这个程序中没有什么特殊的东西可以防止它被改 变。但是如果这座城市将它的消费税增至9%,我们仍然可以很容地通过在一个地方将 TAX_RATE 被赋 予的值改为 0.09 来更新我们的程序,而不是在程序通篇中寻找许多值 0.08 出现的地方然后更新 它们全部。 在写作本书时,最新版本的JavaScript(通常称为“ES6”)引入了一个声明常量的新方法, 用 const 代替 var : 1. // 在ES6中: 2. const TAX_RATE = 0.08; 3. 本文档使用 书栈(BookStack.CN) 构建 - 23 -
24 .变量 4. var amount = 99.99; 5. 6. // .. 常量就像带有不变的值的变量一样有用,常量还防止在初始设置之后的某些地方意外地改变它的值。 如果你试着在第一个声明之后给 TAX_RATE 赋予一个不同的值,你的程序将会拒绝这个改变(而且在 Strict模式下,会产生一个错误 —— 见第二章的“Strict模式”)。 顺带一提,这种防止编程错误的“保护”与静态类型的类型强制很类似,所以你可以看到为什么在其他 语言中的静态类型很吸引人。 注意: 更多关于如何在你程序的变量中使用不同的值,参见本系列的 类型与文法。 本文档使用 书栈(BookStack.CN) 构建 - 24 -
25 .块儿 块儿 在你买你的新手机时,手机店店员必须走过一系列步骤才能完成结算。 相似地,在代码中我们经常需要将一系列语句一起分为一组,这就是我们常说的 块儿。在 JavaScript中,一个块儿被定义为包围在一个大括号 { .. } 中的一个或多个语句。考虑如下代 码: 1. var amount = 99.99; 2. 3. // 一个普通的块儿 4. { 5. amount = amount * 2; 6. console.log( amount ); // 199.98 7. } 这种独立的 { .. } 块儿是合法的,但是在JS程序中并不常见。一般来说,块儿是添附在一些其他的 控制语句后面的,比如一个 if 语句(见“条件”)或者一个循环(见“循环”)。例如: 1. var amount = 99.99; 2. 3. // 数值够大吗? 4. if (amount > 10) { // <-- 添附在`if`上的块儿 5. amount = amount * 2; 6. console.log( amount ); // 199.98 7. } 我们将在下一节讲解 if 语句,但是如你所见, { .. } 块儿带着它的两个语句被添附在 if (amount > 10) 后面;块儿中的语句将会仅在条件成立时被处理。 注意: 与其他大多数语句不同(比如 console.log(amount); ),一个块儿语句不需要分号( ; ) 来终结它。 本文档使用 书栈(BookStack.CN) 构建 - 25 -
26 .条件 条件 “你想来一个额外的屏幕贴膜吗?只要$9.99。” 热心的手机店店员请你做个决定。而你也许需要首 先咨询一下钱包或银行帐号的 状态 才能回答这个问题。但很明显,这只是一个简单的“是与否”的问 题。 在我们的程序中有好几种方式可以表达 条件(也就是决定)。 最常见的一个就是 if 语句。实质上,你在说,“如果 这个条件成立,做后面的……”。例如: 1. var bank_balance = 302.13; 2. var amount = 99.99; 3. 4. if (amount < bank_balance) { 5. console.log( "I want to buy this phone!" ); 6. } if 语句在括号 ( ) 之间需要一个表达式,它不是被视作 true 就是被视作 false 。在这个程 序中,我们提供了表达式 amount < bank_balance ,它确实会根据变量 bank_balance 中的值被求值 为 true 或 false 。 如果条件不成立,你甚至可以提供一个另外的选择,称为 else 子句。考虑下面的代码: 1. const ACCESSORY_PRICE = 9.99; 2. 3. var bank_balance = 302.13; 4. var amount = 99.99; 5. 6. amount = amount * 2; 7. 8. // 我们买得起配件吗? 9. if ( amount < bank_balance ) { 10. console.log( "I'll take the accessory!" ); 11. amount = amount + ACCESSORY_PRICE; 12. } 13. // 否则: 14. else { 15. console.log( "No, thanks." ); 16. } 在这里,如果 amount < bank_balance 是 true ,我们将打印出 "I'll take the accessory!" 并在我 们的变量 amount 上加 9.99 。否则, else 子句说我们将礼貌地回应 "No, thanks." ,并保 持 amount 不变。 本文档使用 书栈(BookStack.CN) 构建 - 26 -
27 .条件 正如我们在早先的“值与类型”中讨论的,一个还不是所期望类型的值经常会被强制转换为那种类 型。 if 语句期待一个 boolean ,但如果你传给它某些还不是 boolean 的东西,强制转换就会发 生。 JavaScript定义了一组特定的被认为是“falsy”的值,因为在强制转换为 boolean 时,它们将变 为 false —— 这些值包括 0 和 "" 。任何不再这个 falsy 列表中的值都自动是“truthy” —— 当强制转换为 boolean 时它们变为 true 。truthy值包括 99.99 和 "free" 这样的东西。更多 信息参见第二章的“Truthy与Falsy”。 除了 if 条件 还以其他形式存在。例如, switch 语句可以被用作一系列 if..else 语句的缩写 (见第二章)。循环(见“循环”)使用一个 条件 来决定循环是否应当继续或停止。 注意: 关于在 条件 的测试表达式中可能发生的隐含强制转换的更深层的信息,参见本系列的 类型 与文法 的第四章。 本文档使用 书栈(BookStack.CN) 构建 - 27 -
28 .循环 循环 在繁忙的时候,有一张排队单,上面记载着需要和手机店店员谈话的顾客。虽然排队单上还有许多 人,但是她只需要持续服务下一位顾客就好了。 重复一组动作直到特定的条件失败 —— 换句话说,仅在条件成立时重复 —— 就是程序循环的工作; 循环可以有不同的形式,但是它们都符合这种基本行为。 一个循环包含测试条件和一个块儿(通常是 { .. } )。每次循环块儿执行,都称为一次 迭代。 例如, while 循环和 do..while 循环形式就说明了这种概念 —— 重复一块儿语句直到一个条件不 再求值得 true : 1. while (numOfCustomers > 0) { 2. console.log( "How may I help you?" ); 3. 4. // 服务顾客…… 5. 6. numOfCustomers = numOfCustomers - 1; 7. } 8. 9. // 与 10. 11. do { 12. console.log( "How may I help you?" ); 13. 14. // 服务顾客…… 15. 16. numOfCustomers = numOfCustomers - 1; 17. } while (numOfCustomers > 0); 这些循环之间唯一的实际区别是,条件是在第一次迭代之前( while )还是之后( do..while )被 测试。 在这两种形式中,如果条件测试得 false ,那么下一次迭代就不会运行。这意味着如果条件初始时 就是 false ,那么 while 循环就永远不会运行,但是一个 do..while 循环将仅运行一次。 有时你会为了计数一组特定的数字来进行循环,比如从 0 到 9 (十个数)。你可以通过设定一个 值为 0 的循环迭代变量,比如 i ,并在每次迭代时将它递增 1 。 警告: 由于种种历史原因,编程语言几乎总是用从零开始的方式来计数的,这意味着计数开始 于 0 而不是 1 。如果你不熟悉这种思维模式,一开始它可能十分令人困惑。为了更适应它,花些 时间练习从 0 开始数数吧! 本文档使用 书栈(BookStack.CN) 构建 - 28 -
29 .循环 条件在每次迭代时都会被测试,好像在循环内部有一个隐含的 if 语句一样。 你可以使用JavaScript的 break 语句来停止一个循环。另外,我们可以看到如果没有 break 机 制,就会极其容易地创造一个永远运行的循环。 让我们展示一下: 1. var i = 0; 2. 3. // 一个 `while..true` 循环将会永远运行,对吧? 4. while (true) { 5. // 停止循环? 6. if ((i <= 9) === false) { 7. break; 8. } 9. 10. console.log( i ); 11. i = i + 1; 12. } 13. // 0 1 2 3 4 5 6 7 8 9 警告: 这未必是你想在你的循环中使用的实际形式。它是仅为了说明的目的才出现在这里的。 虽然一个 while (或 do..while )可以手动完成任务,但是为了同样的目的,还有一种称 为 for 循环的语法形式: 1. for (var i = 0; i <= 9; i = i + 1) { 2. console.log( i ); 3. } 4. // 0 1 2 3 4 5 6 7 8 9 如你所见,对于这两种循环形式来说,前10次迭代( i 的值从 0 到 9 )的条件 i <= 9 都 是 true ,而且一旦 i 值为 10 就变为 false 。 for 循环有三个子句:初始化子句( var i=0 ),条件测试子句( i <= 9 ),和更新子句( i = i + 1 )。所以如果你想要使用循环迭代来计数, for 是一个更紧凑而且更易理解和编写的形 式。 还有一些意在迭代特定的值的特殊循环形式,比如迭代一个对象的属性(见第二章),它隐含的测试 条件是所有的属性是否都被处理过了。无论循环是何种形式,“循环直到条件失败”的概念是它们共有 的。 本文档使用 书栈(BookStack.CN) 构建 - 29 -