JavaScript学习指南(第3版)

978-7-115-45632-8
作者: 【美】Ethan Brown(布朗)
译者: 娄佳袁慎建
编辑: 陈冀康

图书目录:

详情

本书系统地介绍了JavaScript的基本语法、基本对象、调试工具与排错技术、事件处理机制、浏览器对象模型/文档对象模型(bom/dom)等方面的知识,帮助读者掌握众多关于JavaScript的基本要点,书中还通过一系列示例引导读者深入探索程序开发的全过程。

图书摘要

版权信息

书名:JavaScript学习指南(第3版)

ISBN:978-7-115-45632-8

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美]Ethan Brown

  译    娄 佳 袁慎建

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright © 2016 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2017. Authorized translation of the English edition, 2017 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


JavaScript是目前Web开发领域非常流行的一种编程语言,得到众多IT从业人员和编程爱好者的关注。现在市面上的JavaScript图书多数基于ES5,而本书重点关注ES6。ES6是语言的新标准,是目前业界超级活跃的计算机语言。

本书共分为22章,在ES6的基础上,本书系统地介绍了JavaScript的基本语法、语言特性、相关工具、基本对象、技术以及推动现代JavaScript开发的范例等方面的知识。其内容由浅及深,从变量、控制流、函数等内容到异步编程、正则表达式等。

本书适合具备一定JavaScript基础知识的读者阅读,也适合从事程序设计工作并想要深入探索JavaScript语言的读者阅读。


虽然这已经是我在JavaScript技术领域的第二本书了,但对于JavaScript专家和布道师这个角色,我仍然觉得有些意外。和很多开发人员一样,我对JavaScript持有强烈的偏见,这种偏见一直持续到2012年。即便现在我的思想已经发生了转变,我的迷惑仍然存在。

我对JavaScript持有偏见的缘由很简单:我认为JavaScript是一种“玩具”语言(我并没有真正地好好学习过它,因此不是完全了解我所评价的东西),它经常被那些危险、粗心、未经培训的业余程序员所使用。这些现象都是有事实依据的。ES6开发速度很快,即使是它的发明人Brendan Eich后来也承认有些东西在一开始并没有考虑周全,而当他意识到的时候,已经有太多人指望他能够对这些问题做出有效的改进(然而,又有哪个语言没有这方面的困扰呢)。第二个原因是,JavaScript确实让编程突然变得更加容易上手。这不仅是因为浏览器的普及,更是因为它的高回报/投入比,人们只要付出一点点努力,就可以收获JavaScript为他们的网站所创造的巨大价值。人们可以通过试错,通过阅读彼此的代码,甚至在很多情况下,通过模仿那些缺乏理解的不良代码来学习这门编程语言。

我很庆幸自己在学习了足够多的JavaScript知识后,意识到它不仅不是一种玩具语言,而且拥有极其雄厚的基础,强大、灵活、具有表现力。我也很庆幸自己能够毫无芥蒂地拥抱JavaScript所带来的简易特性。毫无疑问,我对业余爱好者没有任何敌意:每个人都会找到自己的起点,而编程是一项有益的技能,以软件开发作为职业则会带来更多好处。

对于新手和业余的开发人员,我会说:成为一个业余开发人员并不是一件羞耻的事情。但如果一直停留在业余水平,那么就有点不合适了(如果你决定把编程作为职业)。如果你想练习编程,那么就付诸行动。搜集所有你能找到的资源,学习任何你可以学到的东西。保持谦虚、开放的心态,最重要的是,学会质疑。质疑一切权威,质疑任何一个经验丰富的开发人员,多问几句“为什么?”

多数情况下,我都尽量尝试让本书的内容符合JavaScript的“事实”,但是依然不可避免地会有不同的意见。当我提出不同意见时,按照原有的意思理解它们就行。我非常欢迎不同的看法,我也鼓励读者去寻求其他有经验的开发人员的意见。

如今正是学习JavaScript最好的时代。互联网正在逐渐脱离蹒跚学步(从技术的角度上)的阶段,网站开发领域也不再处于5~10年前那种令人不解的拓荒阶段。一些类似HTML5或ES6之类的标准使得学习网站开发变得越来越简单,开发出高质量的网站应用也不再是一件困难的事情。Node.js使JavaScript的应用范围不再局限于浏览器,现在的JavaScript已经可以用来开发系统脚本、桌面应用程序、网站后端,甚至是嵌入式应用。可以肯定的是,我从20世纪80年代中期开始编程以来,从未觉得编程是一件如此有趣的事。

Brendan Eich于1995年开发了JavaScript,他曾就职于网景通信公司(Netscape Communications Corporation)。最早的JavaScript是在很短时间内开发出来的,很多批判它的人都说它的开发缺乏远见。然而,Brendan Eich并不是一个浅尝辄止的人:他有着非常扎实的计算机科学基础,对JavaScript有着成熟和颇具先见之明的想法。从很多方面来说,这些想法是超越时代的,主流开发者们花了15年的时间才逐渐理解了这门语言的先进性。

JavaScript最早的名字是Mocha,在1995年网景领航员浏览器(Netscape Navigator)的一次发布中被正式命名为JavaScript之前,它曾经短期使用了LiveScript这个名字。“JavaScript”中的单词“Java”并不是巧合,但是却有点费解:撇开通用的语法传统不说,相比Java,JavaScript更类似于Self(一种基于原型的语言,20世纪80年代中期由Xerox PARC所开发)和Scheme(一种深受Lisp和ALGOL影响的语言,20世纪70年代由Guy Steele和Gerald Sussman所开发)。Eich精通Self和Scheme,他在开发JavaScript时应用了这两种语言中的一些具有前瞻性思考的范例。JavaScript这个名字的由来一部分是出于市场需求,当时的Java语言非常流行,所以将它们关联起来[1]

1996年11月,网景通信公司声明他们已经把JavaScript提交到Ecma。Ecma是一家私有的、国际化非营利性标准化组织,它在技术和通信行业有着重大影响力。Ecma国际发布了第一版ECMA-26规格,其本质就是JavaScript。

Ecma规格(一种叫作ECMAScript的语言)和JavaScript的区别更多体现在学术方面。从技术上说,JavaScript是ECMA的一种实现,但在实际应用中,JavaScript和ECMAScript是可以互换的。

ECMAScript的上一个主版本是5.1(通常也称为ES5),发布于2011年6月。市面上现存的老到不支持ECMAScript5.1的“非正规”浏览器已经不足个位数,可以很有把握地说,ECMAScript5.1已经是现今的网络通用语言了。

ECMAScript6(ES6)作为本书的重点,由Ecma国际发布于2015年6月。在正式发布之前,它被叫作“Harmony”,所以你可能会听到有人把ES6称作“Harmony”“ES6 Harmony”“ES6”“ES2015”以及“ECMAScript2015”。在本书中,我们将其统称为ES6。

如果说ES5是目前的网络通用语言的话,有心的读者可能会奇怪为什么本书要重点关注ES6呢。

ES6代表了JavaScript语言的一个重大进步,ES5的一些缺陷也在ES6中得到了改进。你一定会发现,在工作中,ES6是一门更加令人愉快和强大的语言(从ES5开始入门则会很享受)。同时,感谢转换器,你现在可以编写ES6代码,然后把它们转化成“浏览器可识别的”ES5代码。

随着ES6的最终发布,浏览器对它的支持也会越来越稳定,总有一天,转换器将从历史的舞台褪去(我不会预测这件事什么时候会发生,即使是粗略地估计)。

毫无疑问,ES6代表了JavaScript开发的未来,你现在花些时间来学,就是为未来做准备,我们现在使用转换器,可以保留代码的可移植性。

然而,并不是每个开发人员都希望编写ES6代码。很可能你正在一个非常庞大的基于ES5的代码库上工作,将这些代码转换成ES6的成本非常高,一些开发人员仅仅是不想为此花费额外的精力。

除了第1章以外,本书将重点讲解ES6,而非ES5。我会在恰当的时机指出ES6和ES5的不同之处,但是不会出现对照代码的例子,或者在更适合使用ES6的时候出现大量关于“ES5用法”的讨论。如果你恰好是那种无论如何都要坚持使用ES5的开发人员,那么本书可能不适合你(即使这样我还是希望未来的某一天你可以开始使用ES6)。

从编纂的角度来说,选择ES6作为本书的重点这一决定非常谨慎。ES6里程碑式的进步使得维持其清晰的教学框架变得非常困难。简而言之,如果一本书试图同时涵盖关于ES5和ES6的内容,很可能会顾此失彼。

本书主要针对那些有一定编程经验的读者(即使只听过一些介绍编程的课程,或者在线课程)。如果你刚接触编程,本书将会对你非常有帮助,不过你可能需要一些介绍性的文章或者课程作为补充。

那些已经有一些JavaScript经验(尤其是只有ES5的经验)的读者,也会在本书中找到实用且全面的重要语言概念。

从其他编程语言转过来的开发人员对本书的内容应该会有一见如故的感觉。

本书将全面涵盖关于语言特性、相关工具、技术,以及推动现代JavaScript开发的范例等方面的知识。因此,本书中的素材也将相应地从简明易懂(如变量、控制流、函数)到复杂深奥(如异步编程、正则表达式)变化。根据你的经验情况,你可能会发现阅读其中某些章节颇具挑战性。毫无疑问,初学者将需要反复阅读其中某些章节。

本书不是关于JavaScript或其相关类库的索引大全。Mozilla开发者网络(Mozilla Developer Network,MDN)维护了一个非常出色、全面、实时更新,并且免费的在线JavaScript索引(https://developer.mozilla.org/en-US/docs/Web/ JavaScript),本书将会自由地引用以上类库。如果你更喜欢实体书,David Flanagan的《JavaScript权威指南》则更加全面(尽管它在本书编写的时候还未涵盖ES6的内容)。

本书将使用以下印刷相关约定:

斜体

表示新的术语、网址、邮件地址、文件名及文件扩展名。

等宽字体

用于代码列表,包括段落内引用代码元素的地方,比如,变量或者函数名、数据库、数据类型、环境变量、语句及关键字。

等宽加粗

表示命令行或者其他应该由用户输入的文本。

等宽斜体

表示应由用户输入或者由上下文决定的值。

 

此标志表示小窍门或者建议。

 

 

此标志表示一般性的注解。

 

 

此标志表示警告或警示。

 Safari Books Online是一个按需数字图书馆,它提供了来自全球各地技术和商业领域优秀作者的专业书籍和视频。

专业技术人员、软件开发人员、网页设计人员,以及商业和创意专业人士等将Safari Books Online用作其首选的内容数据库,进行搜索、解决问题、学习和认证培训。

Safari Books Online为企业、政府、教育机构和个人提供一系列计划和定价。

会员有权在这个完全可搜索的内容数据库中访问来自O'Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kauf mann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett和Course Technology等上百家出版商的上千本书籍、培训影片,以及正式出版前的手稿。了解更多有关Safari Books Online的信息,请访问我们的官方网站。

请通过以下方式把对本书的评价和问题提交给出版商:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

电话:800-998-9938(美国或者加拿大)

707-829-0515(国际或者本地)

707-829-0104(传真)

关于本书的勘误、示例和其他信息,请访问官方页面。

关于本书的意见,建议或技术问题,请发邮件到:bookquestions@oreilly.com.

了解更多有关我们的图书、课程、会议的信息以及最新动态,请访问我们的官方推特。也可以在Youtube上观看。

能够有机会为O’Reilly写书是一个巨大的荣耀,我欠Simon St.Laurent一个大人情,因为他在我身上看到了这种潜能,并且带我走向写书这条路。Meg Foley是我的编辑,他一直在支持我,鼓励我,对我有前所未有的帮助(我给你寄了一件非常绿的T恤衫,Meg!)。每一本O’Reilly出版的书都是一个团队努力的成果,我的文字编辑Rachel Monaghan,制作编辑Kristen Brown,以及校对员Jasmine Kwityn,他们在跟我合作中都非常迅速,全面,富有见解:谢谢每个人的努力!

我的技术审稿人,Matt Inman、Shelley Powers、Nick Pinkham和Cody Lindley,感谢你们敏锐的反馈,绝妙的想法,以及不遗余力地帮我润色这本书。可以说没有你们我就无法完成这本书。当然,每个人的反馈都非常有帮助,这里我想特别感谢一下Matt:他深厚的教育学背景为我提供了一个很有价值的教学视角,他经常在反馈中使用的Stephen Colbert的图片帮我理清思路!

在此要特别感谢Shelley Powers——本书上一个版本的作者,不仅仅是因为她把这个头衔传递给我,更因为她为本书提供了非常有经验的反馈,从而让本书变得更好(并且引发了一些激烈的讨论)。

我想感谢所有读过我上一本书(《Node与Express开发》)的读者。如果没有你们的购买,以及积极地评论,我可能就没有机会写本书了。在此特别感谢提出反馈和修正建议的读者:从你们的反馈中我学到了很多!

我很荣幸能在Pop Art工作,感谢这里的每一位同事,你们是我坚实的依靠。你们的支持令我自惭形秽,你们的热情激励着我,你们的专业精神和奉献精神是我每天起床的动力。在这里特别感谢Tom Paul:他坚定不移的原则,创新的经营理念,以及卓越的领导力时刻激励着我不仅要做好当下,还要放眼未来,不断进步。感谢Steve Rosenbaum创办Pop Art,并在度过惊涛骇浪后将火炬成功地传递给Tom。在我写作本书的过程中,Colwyn Fritze-Moor 和Eric Buchmann帮助处理了一些我的日常工作,感谢你们!感谢Dylan Hallstrom成为可靠的模范!感谢Liz Tom和Sam Wilskey加入Pop Art的团队!感谢Carole Hardy、Nikki Brovold、Jennifer Erts、Randy Keener、Patrick Wu和Lisa Melogue给我的所有支持。最后,感谢我的前辈Tony Alferez、Paul Inman和Del Olds,从他们那里我学到了很多。

我对这本书以及编程语言的热情,是被Dan Resler博士点燃的,他是弗吉尼亚联邦大学的副教授。我在参加他的编译原理课程的时候缺乏兴趣,但是课程结束后却对形式语言理论产生了浓厚的兴趣。感谢您用自己的热情感染我,用您深邃的思想影响我。

感谢我所有在PSU中兼职MBA的朋友们,很高兴认识大家!特别感谢Cathy、Amanda、Miska、Sahar、Paul S.、Cathy、John R.、Laurie、Joel、Tyler P.、Tyler S.和Jess:是你们让我的人生更加充实!

Pop Art的同事们激励我变得杰出,点亮我的白天;朋友们激励我变得更有深度,照亮我的夜晚。Mark Booth:没有人比你更了解我,也没有人能像你这样获得我最深的信任。你的创造力和天赋让我感到惭愧,千万别把这本书拿到你面前当做炫耀的资本。Katy Roberts像定期来临的潮水一样可靠,美丽的Katy,感谢你深厚持久的善良和友谊。Sarah Lewis: 我喜欢你的样子。Byron和Amber Clayton是我真诚而忠实的朋友,他们总是能带给我欢乐。Lorraine,这么多年过去了,你总是能让我展现出最好的一面。在这里我想对Kate Nahas说:很高兴多年之后我们恢复了联系;我很期待为我们拥有在杜克大学的共同回忆而举杯。Desember:感谢你的信任,温暖和友谊。最后,感谢我的新朋友Chris Onstad和Jessica Rowe:你们在过去两年中给我的生活带来如此多的欢笑,真不知道没有你们我该怎么办。

在这里我想对我的母亲Ann说:感谢您给予我坚定不移的支持、爱和耐心。我的父亲Tom,使我一直保持好奇心、创新和奉献精神,如果没有他我可能只是一个可怜的工程师(或者压根不是工程师)。还有我的姐姐,Meris,她将永远是我生命中那个代表着忠诚和信念的不动点。

[1] 在2014年的一次公开采访中,Eich对“憎恨 JavaScript”的Sun Microsystems公司嗤之以鼻。


通常,实践是最佳的学习方式:所以接下来本书会从一个简单的应用开始。首先要申明的是,本章的重点不是解释接下来将发生的一切:因为肯定会有很多读者不熟悉或者困惑的地方。作者的建议是,尽量放轻松,不要试图去搞懂现在所发生的一切,以免让自己陷入泥团中。那么本章的重点是什么呢?没错,就是为了让读者进入兴奋状态。尽情放松地去享受接下来的一切吧,当你读完本书的时候,所有在本章中产生的困惑,都将烟消云散。

 

假如读者没有太多的编程经验,那么计算机死板的识别编程语言的能力会困扰到你。对于那些混乱的输入,人类大脑可以很容易地处理,而计算机却有点捉襟见肘。打个比方,如果作者犯了一个语法错误,读者可能只是觉得作者写作能力有待提高,却依然能理解我的意思。然而,JavaScript跟其他所有编程语言一样,并不能这么灵活地处理混乱的输入。大小写、拼写、单词顺序,以及标点符号都很关键。如果大家也被这些问题困扰着,那么就要确保复制的信息都是正确的:不要用分号代替冒号或逗号,不要混淆单引号和双引号,所有代码的大小写都正确。一旦有了编程经验,开发人员就会明白什么地方可以“用自己的方式”来写代码,什么地方必须是严格按照语言的规范。但在此时,只要确保能准确地输入示例中的内容就可以避免这些困扰。

在过去,跟编程有关的书都习惯以一个叫作“Hello,World”的例子作为开始,虽然它只是简单地在终端打印“hello world”。有趣的是,这种做法是由Bell实验室的Brian Kernighan在1972开始的。它第一次出现在1978年出版的《C语言程序设计》(《The C Programming Language》)里,此书由Brian Kernighan和Dennis Ritchie所著。直到今天,这本书仍然被广泛地认为是最好、最有影响力的编程语言书籍之一。甚至在编写本书的时候,作者本人也从那部作品中获取了很多灵感。

现代的编程学习者越来越聪明了,“Hello,World”这个例子对于他们来说可能有些过时了。这个简单短语背后隐含的意义在今天看来,依然如同1978年那会儿有生命力:这是编程人员为其注入生命时说出的第一句话!它证明了编程人员可以像神话中的普罗米修斯一样,从太阳神阿波罗那里偷到火种,或者像弗兰肯斯坦博士一样,将生命注入到创作中。正是这种意义上的发明和创造,才将作者带入了编程世界。或许有一天,某些程序员(也可能是你),将会创造出某种形式的人工生命体,而它开口说的第一句话就是“hello world.”。

在本章,会在传统教条(44年前由Brian Kernighan开创)与现代编程者日益增长的学习能力之间做好权衡。“hello world”同样会输出在屏幕上,只是它与1972年出现在荧光屏上的“hello world”已经大相径庭了。

本书涵盖了JavaScript在它当前所涉及的领域(服务端、脚本、桌面、基于浏览器,等等)中的用法,但由于一些历史和现实的原因,本章选择从一个基于浏览器的程序开始。

为什么要选择基于浏览器的例子呢?一方面是因为它更容易访问图形库,从而获得良好的可视化效果。研究表明,人类本质上是视觉动物,将编程理念和视觉元素关联起来是一种很强大的学习方式。在学习过程中,因为要花大量时间来阅读这些文本文字,所以亟需一些视觉上充满趣味性的东西来润滑一下枯燥的文字。另一方面是因为它能有组织地介绍一些重要的概念,例如,事件驱动编程,这些概念会帮助更快地理解后面的章节。

没有一些实用工具的帮助,很难写出好的软件,就好比木匠没有锯子是很难做出一张像样的桌子。幸运的是,在本章,一个浏览器和一个文本编辑器就够用了。告诉大家一个好消息,在编写本书的时候,市面上所有浏览器都可以完成本书中要做的事情。即便是从未入过程序员法眼的IE浏览器也改过自新,向Chrome、Firefox、Safari和Opera这些浏览器看齐了。这里选择了Firefox。本书也会讲解一些Firefox的特性,这些特性会在后续的编程之旅中助你一臂之力。当然,其他浏览器也有这些特性,本书会介绍这些特性在Firefox中的实现。所以说,倘若使用Firefox来完成本章中的例子,将会度过一个更加轻松愉快的下午。

还需要一个文本编辑器去编写代码。文本编辑器的选择是一个容易引发争论(甚至是宗教性的)的主题了。大体上来讲,文本编辑器可以分为文本模式编辑器或窗口化编辑器。目前,两个最主流的文本模式的编辑器是vi/vim和Emacs。它们最大的优点是,不仅能在本地环境使用它们,还可以通过SSH远程连接到别的机器上,然后使用这个已经非常熟悉的编辑器来编辑文件。窗口化边界器显得更时髦一些了,它们添加了一些有用(并且更加为用户所熟悉)的用户接口元素。然而,直到现在,也只需要编辑一些文本。所以说,窗口化编辑器此时并没有发挥出它固有的优势,而文本编辑器显得更加轻量和便捷。目前主流的窗口化本文编辑器有Atom、Sublime Text、Coda、Visual Studio、Notepad++、TextPad,以及Xcode。如果读者已经能很熟练地使用其中一种编辑器,那就没有必要更换了。但是,如果正在Windows系统中使用Notepad,那么,强烈建议升级到一个更加强大的编辑器(Notepad++是一款适用于Windows用户并且免费易用的编辑器)

虽然全面描述编辑器的特性超出了本书的范围,但仍然有一些特性是需要去学习并掌握如何使用的。

语法高亮是使用颜色来区分程序中语法元素。比如,字符串可能是一种颜色,变量又是一种颜色(很快将了解这些术语的含义!),这个特性帮助开发人员更容易在代码中发现问题。大部分现代文本编辑器都默认有语法高亮功能;如果所使用代码不是彩色的,赶快查看编辑器的说明文档,了解如何启用它。

大多数编程语言都使用了大量的括号,它们有花括号、方括号(统称为括号)。有时候,这些括号的内容跨越了多行,有的甚至跨越了整个屏幕。除此之外,还会在括号中嵌套括号,而且通常是不同类型的括号。这个时候,括号匹配和“平衡(左右括号个数一致)”就显得至关重要了。否则,程序很难正确运行。另外,括号匹配在代码的起始位置起到了一个视觉提示的作用,它能够帮助发现括号不匹配的问题。在不同的编辑器中,括号匹配的处理方式也是不同的,有的只是细微的提示,有的却有非常明显的提示。在学习的过程中,括号不匹配通常会给初学者带来一些阻碍,所以,强烈建议读者花些时间学习掌握如何在编辑器中使用括号匹配功能。

另外一个与括号匹配相关的当属代码折叠。代码折叠功能能够临时隐藏不相关的代码,从而更加专注于核心代码。这个词源自于一个理念:将一张纸对折来隐藏不重要的细节。同样,类似于括号匹配功能,不同的编辑器对代码折叠的处理也是不同的。

自动补全(也叫单词补全或者智能提示[1])是一个很便捷的功能,它会试图在完成输入之前猜测想要输入的内容。这么做有两个好处。首先,它能帮助节省输入时间。比如说,不用完整输入encodeURIComponent,只用输入enc,然后就可以从自动补全的提示列表中选择encodeURIComponent。第二个好处是它的探索性(discoverability)。比如,当输入enc时,本意是想使用encodeURIComponent函数,提示列表中还出现了encodeURI函数,这样就可以了解更多相关函数。甚至在某些编辑器中,还可能找到区分这两个选项的说明文档。JavaScript是一门弱类型语言,它有自带的作用域规则(后面会学习它),所以在JavaScript中实现自动补全功能的难度要比其他语言大。如果编码时经常使用自动补全功能,花点时间去找一个拥有该功能的编辑器是很有必要的:很多编辑器提供了非常出色的自动补全功能。还有一些编辑器(比如vim),虽然也提供强大的自动补全功能,但是需要做一些额外的配置。

像很多编程语言一样,JavaScript也有在代码中添加注释的语法。这些注释在执行时会被JavaScript所忽略,所有注释只对开发人员或者和开发人员一起工作的同事有意义。因为它允许使用自然语言解释含义不那么明确的代码。在本书中,会在代码实例中添加注释来解释该段代码。

在JavaScript中,有两种不同的注释:单行注释和多行注释。单行注释由两个连在一起的斜线开始(//),直到本行结束。多行注释由一组斜线和星号开始(/*),由另一组星号和斜线作为结束(*/),可以包含多行文字。以下的例子可以说明这两种注释的不同:

console.log("echo");        // prints "echo" to the console 
/*
 In the previous line, everything up to the double forward slashes
 is JavaScript code, and must be valid syntax. The double
 forward slashes start a comment, and will be ignored by JavaScript.

 This text is in a block comment, and will also be ignored
 by JavaScript. We've chosen to indent the comments of this block
 for readability, but that's not necessary.
*/
/*Look, Ma, no indentation!*/

在本书中,会时不时看到层叠样式表(CSS),CSS中的注释跟JavaScript的多行注释语法一样(CSS不支持单行注释)。HTML(类似CSS)不支持单行注释,它的多行注释也与JavaScript的不一样。HTML中的注释是被<!- -和- - >这种笨重的符号所包围的:

<head>
    <title>HTML and CSS Example</title>
 
    <style>
        body: { color: red; }
 /* this is a CSS comment...
 which can span multiple lines. */
    </style>
    <script>
        console.log("echo"); // back in JavaScript...
 /* ...so both inline and block comments
 are supported. */
    </script>
</head>

本书将从创建三个文件开始:一个HTML文件、一个CSS文件和一个JavaScript源文件。可以在HTML文件中完成所有事情,然后把JavaScript和CSS文件引入到HTML文件中,这样把内容分离到不同的文件中是有很大好处的。如果读者是一个编程新手,强烈建议你跟着这些说明一步一步来,在本章中将会采取逐步探索形式,这种形式会使学习过程更加便利。

就上例而言,好像为了完成简单的事情做了很多工作,这样做是有原因的。其实也可以用更简单的方式来实现这些,但是这样可能会教给你一些不好的编程习惯。在这里看到的多余步骤以后也会在其他地方重复看到,但是通过这样的步骤,至少可以确保自己学习到了正确的编程方法。

最后需要强调的是,本章是本书中唯一使用ES5(而非ES6)标准编写示例代码的一章,这也是本章最后一个重点所在。这样做是为了兼容那些尚且还不支持ES6的浏览器。在后续章节中,将会讲到如何使用ES6标准编写JavaScript代码,以及如何转换词法使其可以在传统浏览器上运行。之后本书的剩余部分都会使用ES6标准。本章的示例代码都非常简单,即使使用ES5标准来编写也不会有任何困难。

 

接下来的这个练习,必须确保创建的所有文件都在同一个目录中。建议给这个例子创建一个新目录或者新文件夹,从而确保它不会跟别的文件混在一起。

从JavaScript文件开始,使用文本编辑器创建一个叫作main.js的文件。现在只需要在文件中写入一行代码:

console.log('main.jsloaded;')

然后创建一个CSS文件,main.css. 目前不用给这个文件中写入任何代码。

/*Style go here.*/

接下来创建一个叫作index.html的文件:


<html>
    <head>
        <link rel="stylesheet" href="main.css">
    </head>
    <body>
        <h1>My first application!</h1>
        <p>Welcome to Learning JavaScript, 3rd Edition</i>.</p> <script src="main.js"></script> </body> </html>

这本书不是一本讲HTML或者网站开发的书,但是很多人学JavaScript都是为了网站开发,所以也会提到一些跟JavaScript相关的HTML知识。一个HTML文档包含两个主要部分:文件头(head)和文件体(body)。文件头包含了一些不会直接显示在浏览器上的信息(但是它会影响那些显示在网页上的信息)。文件体包含了会被浏览器渲染的页面内容。要注意文件头中的内容永远不会显示在浏览器中,而文件体中的内容通常会显示(但是某些类型的元素不会显示的,例如<script>标签、CSS样式这些内容都会隐藏在文件体中),理解这一点非常重要。

在文件头中有这样一行<link rel="stylesheet"href="main.css">;这可以把当前的空CSS文件加载到HTML文档中。文件体的结尾有<script src="main.js"></script>,这个则是为了加载JavaScript文件。大家是不是很好奇为什么把css文件引入放在文件开头而把js文件的引入放在文件末尾。当然也可以把<script>标签放在开头,但是考虑到性能和其他一些较为复杂的原因,选择把它放在文件末尾。

在文件体中有有这样一行<h1>My first application!</h1>,它定义了一级标题(表示页面中字体最大,最重要的文本),随后是<p>(段落)标签,包含了一些文本内容,其中有些内容是斜体(用<i>标签表示)

接下来在浏览器中打开index.html文件。在大多数操作系统中,最简单的做法就是双击这个文件(也可以把文件拖到浏览器窗口中)。这样文件中的内容就能显示在浏览器中了。

 

在本书中有很多代码示例。由于HTML和JavaScript文件通常都很庞大,所以不会每次将整个文件展示出来。不过不必太担心,作者会解释这些示例代码属于文件的哪一部分。这可能会让初学编程的人在理解代码结构时有些困难,但是弄清楚代码如何被整合在一起的是学习编程时非常关键的地方,而这也是不可避免的。

前面已经写过一些JavaScript的代码了,例如:console.log(‘main.js loaded’)。这段代码做了什么呢?控制台是一个纯文本工具,开发人员用它来调试程序。在学习本书的过程中,会大量使用控制台。

打开控制台的方式因浏览器而异。鉴于会频繁用它,建议事先了解打开控制台的快捷键。在Firefox中,Ctrl-Shift-K(Windows或者Linux系统)或Command-Option-K(Mac操作系统)就可以打开控制台。

在加载index.html文件的页面上打开JavaScript控制台,会看到控制台中输出了“main.js loaded”(如未输出,尝试刷新页面)。console.log方法可以在控制台中打印任何内容[2],这对调试和学习都很有帮助。

控制台有很多有用的功能,其中一个是不仅能看到程序的输出,还可以在控制台直接输入JavaScript代码,从而测试输出、学习JavaScript特性,甚至临时修改代码。

将会在页面中引入一个非常流行的客户端脚本库 -- jQuery. 虽然这不是必须的,甚至与手头的任务关系不大,但在开发中,往往会最先将这个无处不在的类库引入到网页代码中。在这个例子中,即便没有引入jQuery,也能很轻易地搞定,但是,越早开始习惯jQuery代码会越好。

在HTML文件body标签的最下面,在引入main.js之前,引入jQuery:

<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> 

<script src="main.js"></script>

这里使用了一个网络链接,意味着如果网络不可用时,页面就不能正常工作了。从一个公共的内容分发网络(CDN:content delivery network)来加载jQuery,这点有利于保证性能。如果在无网络的情况下工作,就必须把文件下载到本地,然后从本地加载文件。现在对main.js文件做些修改,使之用上一个jQuery的特性。

$(document).ready(function() {
    'use strict';
    console.log('main.js loaded');
});

如果没有使用jQuery的经验,上面这段代码看起来可能会有些费解。事实上,这里提到的很多东西在后面的章节中都有详细的讲解。在这里,jQuery确保了所有的HTML文件都在JavaScript执行之前加载完成。(虽然现在JavaScript代码只有一句简单的console.log)。每次使用基于浏览器的JavaScript时,都会将JavaScript代码写在$(document).ready(function(){和});之间,从而帮助建立好的编程习惯。注意到’use strict’了吗,后面的章节里会详细介绍它,它会让JavaScript解释器更严格地对待所写的代码。乍听起来好像不那么友好,事实上它会帮助写出更好的JavaScript代码,也避免一些常见却又难以定位的问题。本书中的JavaScript都将严格遵循语法规范。

HTML5提供了众多的好处,标准的图形化接口就是其一。HTML5 canvas可以画出很多图元,例如,方形、圆形或多边形等基本图形。但是直接使用canvas可能会痛苦,所以会借用一个叫作Paper.js的图形库间接使用canvas。

 

Paper.js不是唯一可用的图形库:KineticJS,Fabric.js和EaselJS都是当下流行且非常易用的替代品。这些类库作者都用过,它们都是很棒的图形库。

在使用Paper.js画图之前,需要一个可以用来画图的HTML canvas元素。在HTML文件body标签中加入以下代码(可以把这些代码放在任何位置,比如在介绍的段落后面):

<canvas id="mainCanvas"></canvas>

注意,这里给canvas设置了id属性:这样就方便在JavaScript和CSS中引用它。此时刷新页面不会有任何变化,这是因为还没有在canvas上绘制任何东西,而且这个跟背景色一样的白色canvas连宽高都不存在,从而导致了它很难被区分。

 

每个HTML元素都可以有一个合法的ID(格式正确),ID必须是唯一的。在上面的代码中创建了一个id为“mainCanvas”的canvas元素,就意味着不能在其他元素上使用这个ID。正因为如此,在编写HTML时不应过多的使用ID。对于初学者来说,一次只考虑一件事情会更简单一些,所以在本例中使用ID,根据定义,一个ID只能引用页面上的一个元素。

继续修改main.css文件,让canvas在页面上显示出来。即使不熟悉CSS也没关系,上述CSS代码只是简单地给元素设定了宽度和高度,并且设置了黑色边框:[3]

#mainCanvas {
   width: 400px;
   height: 400px;
   border: solid 1px black;
}

刷新页面,应该可以看到canvas了。

现在有了画图的载体,接下来要引入Paper.js帮助绘图。在引入jQuery和main.js的语句之间插入一行:

<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.24/
paper-full.min.js"></script>

注意,跟引入jQuery的方式一样,也从CDN上加载Paper.js。

 

大家可能已经意识到,引入js文件的顺序是很重要的。因为需要在main.js中使用jQuery和Paper.js,所以这两个文件必须在main.js之前被加载。而这两个文件互相之间没有依赖,所以谁先谁后并不重要,作者习惯最先引入jQuery,因为在网页开发中的很多东西都依赖jQuery。

代码中引入了Paper.js后,还需要对它做些配置。任何时候当遇到这样的重复代码--使用某个工具类库前的初始化设置--通常称为样板代码。将以下样板代码加入到main.js中“use strict”后面(如果愿意,可以删除console.log)

paper.install(window);
paper.setup(document.getElementById('mainCanvas'));

// TODO

paper.view.draw();

第一行将Paper.js注册为一个全局变量(第七章中会深入讲)。第二行将Paper.js附在canvas上,同时准备绘图。在中间放置了TODO,将从这里开始编写代码来完成那些有趣的事情。最后一行则使用Paper.js在屏幕上绘画。

所有的样板代码都就绪,就可以开始绘图了!下面要在canvas中间画一个绿色的圆。将“TODO”语句替换成以下代码:

var c = Shape.Circle(200, 200, 50);
c.fillColor = 'green';

刷新浏览器,会看到一个绿色的圆形。到这儿已经编写了第一行真实的JavaScript代码。上面两行代码包含了很多信息,不过现在只需要关注重要的信息。第一行代码通过三个参数创建了一个circle对象,参数分别是圆心的xy坐标和半径。回忆一下创建canvas的时候给它指定的长和宽都是400pixels,所以canvas中心的坐标是(200,200)。圆形的半径设为50,恰好是canvas的长和宽的1/8。第二行设置了填充颜色,是和外部截然不同的颜色(这种用法在Paper.js中称为stroke)。可以任意改变这些参数看看结果会有什么不同。

思考一种场景:相比于简单地给画布上添加一个圆,如果想将多个圆以网格布局填充画布,此时要怎么做呢?如果让圆与圆之间相距50个像素并设置略小的尺寸,就可以在画布上绘制出64个圆。当然,可以将同样的代码复制63次,然后手工修改所有的坐标,让它们正确地显示在网格中。这听起来并非易事,还好计算机擅长这种重复的工作。来见证一下如何将64个均匀排列的圆绘制出来的。用下面代码替换掉前面绘制单个圆的代码:

var c;
for(var x=25; x<400; x+=50) {
    for(var y=25; y<400; y+=50) {
        c = Shape.Circle(x, y, 20);
        c.fillColor = 'green'; 
    }
}

刷新浏览器,64个绿色的圆就会如期出现在大家的眼前!如果读者才开始接触编程,刚才那段代码可能会有些费解,但相信也会觉得它比手动复制修改的128行代码要好。

刚才的代码使用了for循环,它是控制流语句的一部分,关于控制流语句在第四章有详细的讲解。在For循环中,可以指定初始条件(25),结束条件(小于400),以及增量值(50)。在循环中嵌套循环是为了同时在x轴和y轴两个方向上绘制圆。

 

针对这个例子,可以有很多不同的写法。上面那种写法是以x, y坐标作为一个重要的参考点:明确指定圆的开始位置,以及它们的间距。还可以从另一个方向去解决这个问题:只需要关注要绘制的圆的个数(64),然后让程序去计算怎样将它们均匀的填充在画布上。现在之所以采用第一种方式是因为它与要做的事情有更多的相似性,比如,将绘制圆的代码复制64次并自己计算出它们的间距。

到目前为止,还没有涉及任何用户输入。用户点击圆不会有任何变化。同样地,拖拽圆也没有任何效果。接下来将设计一些用户交互,比如,让用户来决定从哪里开始画圆。

用户输入是具有异步特征的(时间不受控制),那么如何恰当并顺畅地处理它们显得尤为重要。异步事件有个特点:执行时间完全不受控制。用户点击鼠标就是一个异步事件:猜测不到用户在想什么,也不知道他们什么时候会点击。当然也可以给他们的点击做出提示,但这取决于他们何时点击或者是否真的点击了。用户输入引发的异步事件通常具有直观的表现,但在后面的章节中,所学习到的异步事件就不是那么的直观了。

Paper.js使用一个tool对象来处理用户输入。如果觉得那个名字不是很表意,说明读者确实是一个优秀的程序员:必须承认,作者也不知道Paper.js的开发人员为什么使用这个术语[4]。那么为了更好地理解,不妨在意识里将“tool”转化为“user input tool”。使用下面的代码替换掉绘制网格布局的代码:

var tool = new Tool();

tool.onMouseDown = function(event) {
   var c = Shape.Circle(event.point.x, event.point.y, 20);
   c.fillColor = 'green';
};

这段代码的第一行创建了一个tool对象。有了tool对象之后,可以给它绑定一个事件处理器。这里给它绑定了一个叫onMouseDown的事件处理器。只要用户点击了鼠标,onMouseDown事件处理器就会被调用,这是需要弄明白的一个关键点。之前编写的代码会立即执行:只要刷新页面,绿色的圆就会自动出现。但此时已经发生了变化:即便圆会显示出来,也是在单击画布后,而且每单击一次屏幕上只会出现一个绿色的圆。这是因为function(event)紧跟的花括号中的代码有一个执行条件:用户在画布上单击了鼠标。

事件处理器做了两件事情:在用户点击鼠标的时候执行代码,以及告诉鼠标点击的位置。鼠标点击的位置信息存储在参数event的point属性中,event.point有两个属性:xy,它们能确定鼠标点击的位置。

其实还可以少写一些代码,直接将参数event的point属性传给圆(代替单独传入x, y坐标值):

var c = Shape.Circle(event.point, 20);

上述代码突出了JavaScript一个非常重要特征:它能自动识别传入的变量。在之前的例子中,如果参数是三个数字,JavaScirpt会把它们分别当做xy坐标值和半径来处理。如果只有两个参数,JavaScript就认为它们分别是point对象和半径。关于这点我们在第6章和第8章有更多的讲解。

最后用一个例子来结束本章,这个例子跟Brian Kernighan在1972年编写的例子具有相同的表现力。此时已经完成了所有的核心工作,剩下来要做的是添加文本。在onMouseDown事件处理器之前插入下面的代码:

var c = Shape.Circle(200, 200, 80);
c.fillColor = 'black';
var text = new PointText(200, 200);
text.justification = 'center';
text.fillColor = 'white';
text.fontSize = 20;
text.content = 'hello world';

这段代码非常直观:创建另一个圆作为文本的背景,并且创建了一个真实的文本对象(PointText)。然后指定它的位置,以及一些额外的属性(对齐方式,颜色和字体尺寸)。最后,给它设置了真实的文字内容(“hello world”)。

注意:这里不是我们第一次使用JavaScript来展示文字了。第一次是在本章开始的console.log中打印文字。当然也可以将打印的文字改成“hello world.”,在许多方面,这更类似于在1972年可能会经历的事情(假如见证了Brian Kernighan在1972年编写了第一个hello world)。但是就这个例子,文字本身及如何渲染它不是重点,重点是创造的东西已经产生了显著的效果。

刷新浏览器,此刻犹如在参加一个庄严神圣而传统的“Hello, World”盛宴。如果读者是第一次编写“Hello, World”,欢迎加入“Hello, World”俱乐部。如果不是,希望通过这个例子,读者能对JavaScript有一个初步的认识。

[1] 微软的术语。

[2] 在第9章中会详细介绍function和method的不同之处。

[3] 如果你想学习更多关于CSS和HTML的内容,我推荐学习Codecademy上的免费HTML和CSS课程。

[4] 技术评论家Matt Inman提到,Page.js开发者可能是Photoshop用户,他们熟悉“手工具”“直接选择工具”等。


虽然编写JavaScript代码只需要一个编辑器和一个浏览器(从上一章可以得知),但JavaScript开发人员往往需要借助一些实用开发工具来辅助开发。此外,由于本书后面的示例代码都将使用ES6的规范编写,所以还需要一个工具将ES6代码转换成常用的ES5代码。本章中介绍的工具都是些很常见的工具,而且它们经常被用在一些开源项目或者软件开发团队中。它们是:

本章介绍了一些JavaScript开发中非常重要的工具和技术,这些工具看似与JavaScript关系不大,实则它们都是非常有用的。

首先要公布两个消息,一好一坏。好消息是ES6(aka Harmony,aka Java-Script 2015)的出现实属JavaScript历史上一个激动人心的变革。坏消息是大家还没有为它的到来做好充分的准备。但是这并不是说现在不能用它,只是它会给使用它的开发人员带来一些额外的负担,因为必须将ES6代码转换成“安全”的ES5代码,才能保证这些代码可以运行在任何浏览器上。

一些有经验的开发人员可能会觉得“这不是问题,看看我一天的工作,没有一个编程语言是不需要编译的!”作者本人已经从事软件开发工作很久了,所以对那些时刻需要编译的日子记忆犹新,但并不怀念那些时光。相反,很享受像JavaScript1这样的解释型语言所带来的轻便。[1]

JavaScript的优点之一是它几乎无处不在:它几乎在一夜之间变成了浏览器的标准脚本语言,而随着Node的出现,JavaScript的应用场景也不再局限于浏览器。所以意识到后面这一点会有点令人不快,因为可能要等到几年之后,才能随心所欲的编写ES6代码而不用担心浏览器是否支持。如果读者是一名Node开发者,情况会乐观一些,因为只是需要关注一个JavaScript引擎,而且还可以跟踪Node对ES6的支持状况。

 

本书中的ES6代码可以在Firefox,以及一些类似ES6 Fiddle的网页中运行。不过对于那些“真实项目中的代码”,依旧要使用本章中介绍的工具和技术将ES6代码转成ES5。

比较有意思的是JavaScript从ES5升级到ES6的过程是循序渐进的,这点与过去的编程语言版本升级不太一样。也就是说,目前使用的浏览器可能只支持一部分ES6特性。这种循序渐进的方式一方面由JavaScript原生的动态特性所决定,另一方面由浏览器本身升级所导致。大家可能听过常青树这个用来描述浏览器的术语,浏览器制造商也在逐渐取消浏览器不同版本之间必须通过升级来保持同步的理念。他们主张浏览器应该时刻保持最新版本,因为它们一直处于联网状态(至少如果它们希望获得最新版本的特性)。如今浏览器依然存在不同的版本,不过现在更有理由假设用户使用的都是最新的—因为常青树浏览器一定会自动替用户升级。

即使是常青树浏览器,也需要一些时间才能支持ES6的所有特性,所以目前我们还需要借助转换编译器(也叫转换器)。

ES6有太多新的特性,即便是即将提到的转换器在现阶段都不能全部支持。为了解决这个问题,纽约的开发者kangax维护了一个非常全面的兼容性表格,表格列出了ES6(和ES7)的所有特性。直到2015年8月,最完整的实现(Babel)也只覆盖了72%的特性。这听起来可能有些沮丧,不过没关系,因为最重要的特性都已经优先被实现了,另外本书中提到的所有ES6特性都可以在Babel中使用。

在开始转换之前,还需要一些准备工作。首先要确保已经有了必要的工具,并且学会如何在一个新工程使用这些工具。大家会发现,尝试几次之后,这个过程就变成一个自然而然的过程。同时,在开始新项目的之前,可能需要回顾一下本章的内容。

如果还没有安装Git,可以在Git官网的首页上找到符合操作系统的相关下载和介绍。

本章中所有的练习都离不开终端(也叫命令行或快捷命令)。终端是一个基于文本的工具,它可以和计算机进行交互,开发人员基本都会用到它。即便不使用终端,也可以成为一个高效的程序员,但作者认为熟悉终端使用对于开发人员来说是一项很重要的技能:很多教程和书籍都会假设在使用它,并且很多工具也都是针对终端的使用而设计的。

目前业界最常用的终端工具叫作bash,它被内嵌在shell(终端的图形用户界面)中,是Linux或者OS X操作系统的默认终端。Windows有自带的命令行,但这里还是推荐大家使用Git(下一步就会安装它)提供的bash命令行工具。本书中将使用bash。

Linux和OS X,在系统应用中就可以找到终端。而在Windows中,首先要安装Git,然后在系统应用中找Git Bash。

打开终端后,会看到一个提示符,这里就是输入命令的地方。默认提示符一般会包含电脑当前的用户名或者所在的文件目录,末尾一般会有一个美元符号($)。因此,本章中的示例代码都会以一个美元符号作为提示符。在提示符后面的就是需要敲入到终端的命令了。比如,列出当前目录下的所有文件,在提示符后敲入ls:

$ ls

在Unix系统的bash中,文件路径名由紧跟其后的斜线来划分(/)。

而在Windows中,目录通常使用反斜杠(\)。当使用Git Bash时,会把反斜杠转换成斜杠。Bash通常使用波浪线(~)作为当前用户目录(一般用来存储文件)的快捷键。

最基础的命令行是改变当前目录(cd)和添加新目录(mkdir)。比如,进用户目录:

$ cd ~

pwd(打印当前目录)命令可以打印当前所在的目录:

$ pwd

创建一个叫作test的子目录:

$ mkdir test

切换到新创建的目录:

$ cd test

两个点是“父目录”的快捷键(如果正在使用电脑练习前面的命令,那么以下命令会回到当前用户目录)

$ cd ..

关于终端,需要学习的知识还有很多,不过对于本章中的示例代码来说,掌握这些基础命令就足够了。如果读者还意犹未尽,推荐你学习Treehouse上的Console Foundations课程。

实际开发中,通常会给每个项目都创建一个目录。称这个目录为项目根目录。比如,如果正在练习本书中的例子,可能会创建一个名为lj的目录作为项目根目录。而对于书中任何有关命令行的例子,都假设是在项目根目录中执行那些命令。所以如果在练习的时候发现结果不对,首先检查当前工作目录是否是项目根目录。另外,创建文件的路径都是相对于根目录的。例如,如果项目根目录是/home/joe/work/lj,新创建一个文件public/js/test.js,那么该文件的全路径应该是/home/joe/work/lj/public/js/test.js.

本书不会详细讲述版本控制工具,因为它是作为开发人员的必备技能。如果对Git不熟悉,那么本书和书中的例子就可以作为学习Git的良好材料。

首先,在项目的根目录下,初始化一个Git仓库:

$ git init

这条命令创建了一个项目仓库(在项目的根目录下会有一个隐藏起来的.git文件)。

当然,一定会有一些文件是不想使用Git进行追踪的:构建包,临时文件等。这些文件可以在.gitignore文件中被显示地排除掉。创建一个.gitignore文件,添加以下内容:

# npm debugging logs
npm-debug.log*

# project dependencies
node_modules

# OSX folder attributes
.DS_Store

# temporary files
*.tmp
*~

如果还有其他不需要被追踪的“垃圾”文件,尽管把它们添加进来(比如:有些编辑器会创建.bak的文件,通过*.bak可以将这些文件放进来)。

git status是一个非常常用的命令,它可以显示仓库的当前状态。试着输入这个命令,看看结果是不是与下面的一样:

$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

       .gitignore

nothing added to commit but untracked files present (use "git add" to   
track)

Git会告诉程序员在当前目录下有一个新文件(.gitignore),但是它处于未被追踪的状态,这意味着Git并不会对它进行版本管理。这条信息很重要。

Git的基本工作单元是提交。当前仓库还没有任何提交(因为这个仓库刚刚被初始化,虽然添加了一个文件,但是这个文件不被Git管理)。如果不特殊声明,Git就不知道有哪些文件需要被追踪,所以必须把.gitignore文件添加到仓库中:

$ git add .gitignore

现在还有没有进行任何提交;只是简单地把.gitignore放入下一个提交中。运行git status,可以看到:

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   .gitignore

现在.gitignore的状态是待提交。到目前为止仍然没有提交,一旦提交,.gitignore就会包含在里面。也可以添加更多文件,先来做一次提交:

$ git commit -m "Initial commit: added .gitignore."

这条命令中,-m后面的是提交信息:简单描述本次提交中包含的修改。它可以保存项目的提交历史,方便后期回顾。

可以把提交想象成项目快照,它表示项目在某一时刻的状态。这次提交好比做了一次项目快照(只把.gitignore放进来),在日后的任意时刻,都可以回顾这次提交。这时候运行git status看到的结果应该是:

On branch master
nothing to commit, working directory clean

接下来,多做一些修改。在目前的.gitignore文件中,已经忽略了所有名为npm-debug.log的文件,这次试着忽略所有以 .log结尾的文件(这是一个比较好的实践)。编辑.gitignore文件,将npm-debug.log的那行修改为*.log。再添加一个叫作README.md的文件,这是一个用Marddown格式编写的文件,它是介绍项目的标准文件:

= Learning JavaScript, 3rd Edition
== Chapter 2: JavaScript Development Tools

In this chapter we're learning about Git and other
development tools

再试试git status:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working   
directory)

           modified:   .gitignore
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        README.md

现在已经有两处修改:一个是被git追踪的文件(.gitignore),另一个是新文件(README.md)。如果重复之前添加文件的步骤,那么应该运行的命令是:

$ git add .gitignore
$ git add README.md

不过这次尝试用快捷键一次性添加所有修改,然后做一次提交:

$ git add -A
$ git commit -m "Ignored all .log files and added README.md."

以上这两个步骤在后面的例子中会经常重复(添加修改的文件和提交这些修改)。在提交时,试着把它们做的小且逻辑性强一些:就好像这些提交是在讲述一个关于项目进度的故事。任何时候,只要在项目仓库中做修改,都要遵循这个步骤:添加修改的文件,然后提交:

$ git add -A
$ git commit -m "<brief description of the changes you just made>"

 

初学者经常会对git add有些疑惑;顾名思义,add像是在给项目仓库中添加文件。有时候修改的内容确实是个新文件,不过这些新的修改早已经在代码库中完成了。换句话说,git add的时候是在添加新的修改,而非文件(增加文件只是一种特殊类型的修改)。

以上内容展示了Git中最简单的工作流;如果想了解更多关于Git的知识,推荐GitHub上的Git教程,以及由Jon Loeliger和Matthew McCullough编写的《Git版本控制》第二版。

npm对于JavaScript开发不是必须的,不过现在越来越多的前端开发人员都会选择npm作为项目的包管理工具。而对于Node开发,掌握npm是必不可少的。其实不管是Node开发还是基于浏览器的开发,npm都可以让开发工作变得更简单。出于这个原因,本书中将会使用npm来安装开发所需的构建工具和转换器。

npm一般会结合Node一起使用,如果还没安装Node,访问Node官网(https://nodejs.org/),点击大绿色“INSTALL”按钮即可。

安装Node之后,需要验证npm和Node是否生效。在命令行输入以下命令:

$ node -v
v4.2.2
$ npm -v
2.14.7

机器上的版本号可能随着Node和npm的更新而变化。大体上讲,npm是用来管理安装包的。这里说的包可以是一个功能齐全的应用程序中的任何东西,从示例代码,到一个功能模块,或者一个库,都可以使用npm来管理。

npm支持两种级别的安装包:全局和本地。全局安装的包通常是一些用于开发中过程中的命令行工具,本地安装的包则是用于具体项目上的包。使用npm install命令就可以安装包。下面通过安装一个很常用的包Underscore来熟悉这个过程。切换到项目的根目录,运行下面命令:

$ npm install underscore
underscore@1.8.3 node_modules\underscore

这些信息表明npm已经安装了最新版本的Underscore(1.8.3是作者运行命令时的最新版本;大家在运行时版本号可能会有更新)。Underscore这个功能模块没有其他依赖,所以npm的输出信息较简单;当安装一些复杂的功能模块时,npm可能输出多达好几页的信息!如果想安装指定版本的Underscore,可以显式指定版本号:

$ npm install underscore@1.8.0
underscore@1.8.0 node_modules\underscore

那么这个模块安装到哪里去了呢?安装结束后,在项目的根目录里会看到一个名为node_modules的目录,所有本地安装的模块都会放在这个目录里。可以删掉node_modules目录,稍后会重新创建它。

安装模块之后,就需要管理这些模块;这些已安装的模块就是项目的依赖。在项目日趋成熟的时候,需要以简明的方式获得当前项目的依赖信息,npm通过一个名为package.json的文件来帮管理它们。这个文件不需要自己创建:在命令行里运行npm init,然后回答几个关于配置的问题(最简单的办法是一路回车,使用默认的配置;之后可以随时修改文件内容)。运行npm init命令,看看生成的package.json文件有哪些信息。

依赖分为常规依赖和开发依赖。开发依赖是指那些只在项目构建时需要的依赖(稍后会有例子),应用程序运行时不需要它们。从现在开始,每安装一个本地依赖时,都需要在命令行后面添加--save或者--saveDev的标签;否则这些包虽然会被安装,但是不会出现在package.json文件里。接下来使用--save标记重新安装Underscore

$ npm install --save underscore
npm WARN package.json lj@1.0.0 No description
npm WARN package.json lj@1.0.0 No repository field.
underscore@1.8.3 node_modules\underscore

这些警告是什么意思呢?这表示在准备安装的包里有一些组件找不到。由于本书不是专门讨论npm的书,所以可以暂时忽略这些警告。只有在使用npm公开自己的包的时候,才需要担心这些警告,但本书不会涉及这些内容。

此时package.json文件已经把Underscore加入到依赖列表中。依赖管理是因为那些被列在package.json里的有特定版本的依赖包需要被快速重建(下载和安装)。试着再删除node_modules目录,然后运行npm install(这一次不用输入任何包名)。npm就会下载所有在package.json文件里列出的包。看看新生成的node_modules目录,就知道下载的对不对了。

大多数开发人员都会使用构建工具,可以在开发过程中自动化地运行一些重复任务。当下最火的两款的JavaScript构建工具分别是Grunt和Gulp。它们都可以胜任系统构建的工作。Grunt在几年前就已经为人们所熟知了,它比Gulp早一些出现,所以其社区也大一些,不过Gulp已经迎头赶上了。因为对于新的JavaScript开发人员来说,选择Gulp作为构建工具的比例正在迅速上升,本书中也会使用Gulp。但这并不意味着我觉得Gulp比Grunt更高级(或者Grunt比Gulp更高级)。

首先,用以下命令全局安装Gulp:

$ npm install -g gulp

 

如果读者在使用Liunx或者OS X,可能需要在运行-g(全局)的时候切换到高一级的权限,使用:sudo npm install -g gulp。在输入密码后就会获得超级用户权限(只针对这一行命令)。如果在使用被其他人管理的系统,那就需要让管理员把你添加到sudoers的文件里。

对于一个操作系统,只需全局安装一次Gulp即可。而每个项目都需要本地的Gulp,此时需要切换项目根目录下,运行npm install --save-dev gulp(Gulp只是开发依赖的一个例子。程序的运行并不依赖它,但是在开发过程中却需要它的帮助)。现在Gulp已经安装好了,接下来创建一个Gulpfilegulpfile.js):

const gulp = require('gulp');
// <em>Gulp dependencies go here</em>
gulp.task('default', function() {
    // <em>Gulp tasks go here</em>
});

到目前为止,并没有给gulp配置任何任务,不过现在可以验证gulp是否能够正常运行:

$ gulp
[16:16:28] Using gulpfile /home/joe/work/lj/gulpfile.js
[16:16:28] Starting 'default'...
[16:16:28] Finished 'default' after 68 μs

 

如果你是一个Windows用户,可能会看到这个错误“The build tools for Visual Studio 2010 (Platform Toolset = v100) cannot be found。”这是因为在Windows上很多npm的包都依赖于Visual Studio构建工具。可以从它的产品下载页面(https://www.visualstudio.com/en-us/visual-studio- homepage-vs.aspx)下载免费版的Visual Studio。安装之后,在program文件下找到“开发人员命令提示符(Developer Command Prompt)”。在命令行快捷方式里,切换到项目的根目录,然后尝试再安装Gulp,这一次应该会顺利很多。接下来并不需要一直使用Visual Studio的开发人员命令提示符,只是在安装对Visual Studio有依赖的npm的包时需要使用它,从而简化安装过程。

使用Gulp和Babel将ES6代码转化成ES5之前,需要考虑代码应该放在项目中的什么位置。在JavaScript开发中,并没有一个世界通用的项目结构标准。相反,多样性在这个生态系统中体现得淋漓尽致。通常,项目的源代码会放在src或者js目录下。这里将把源代码放在es6这个目录下,体现出在使用ES6编写JavaScript代码。

因为大多数项目包含了服务器端(Node)代码和客户端(浏览器)代码,下面会在例子中把它们区分开来。服务器端代码放在根目录的es6目录下,而在浏览器端可以看到的代码放在public/es6目录下(默认情况下,所有被浏览器加载的代码都是public的,这是一个很常见的约定)。

在下一节,会把ES6代码转换成ES5代码,所以还需要一个地方来存放这些代码(不想把它们与ES6代码混在一起)。一个通用的约定是把它们放在一个叫作dist的目录下(是单词分布“distribution”的缩写)。

把所有代码放置好后,项目的根目录应该看起来是下面的结构:

.git                  # Git
.gitignore

package.json        # npm
node_modules

es6                   # Node source
dist

public/              # browser source
    es6/
    dist/

在编写此书时,两款最流行的转换器是Babel和Traceur。这两个作者都用过,它们都可以满足需求并且简单易用。不过最近本人的学习方向渐渐转向了Babel,本书中也将会使用Babel作为转换器。下面就开始吧!

Babel最早是用于ES5到ES6的转换,随着时间的推移,Babel逐渐成为一个可以转换多种格式的通用转换器,这其中包括ES6,React,甚至是ES7。从Babel 6开始,它就不再包含转换器了。为了完成ES5到ES6的转换,需要安装并配置Babel的ES6转换器。这些设置都会在本地项目中进行,由此可知会在当前项目中使用ES6,而在别的项目中使用React或者ES7(或者其他形式的JavaScript)。首先,安装ES6的前置插件(aka ES2015):

$ npm install --save-dev babel-preset-es2015

接下来在项目的根目录创建一个叫作.babelrc(这个文件默认会被隐藏起来)的文件。文件的内容是:

{ "presets": ["es2015"] }

有了这个文件,Babel就可以识别出项目中所有使用ES6的地方了。

现在可以使用Gulp做一些有意思的事情:把ES6代码转换成对应的ES5代码。接下来会将所有在es6文件夹或者public/es6文件夹下的代码转换成ES5代码,生成的代码分别放在distpublic/dist目录下。由于会使用一个叫作gulp-babel的包,所以首先要运行npm install --save-dev来安装它,接着在gulpfile.js里加入下面的内容:

const gulp = require('gulp');
const babel = require('gulp-babel');

gulp.task('default', function() {
  // Node source
gulp.src("es6/**/*.js")
  .pipe(babel())
  .pipe(gulp.dest("dist"));
// browser source
gulp.src("public/es6/**/*.js")
  .pipe(babel())
  .pipe(gulp.dest("public/dist"));
});

在这里Gulp使用了管道的概念。首先需要告诉Gulp要处理哪些文件:src(“es6/**/*.js”)。大家可能会问**是什么意思,它是一个通配符,表示“任何目录,包含子目录。”所以这里的数据源过滤器会解析es6文件夹下所有后缀为.js的文件,包括所有子目录里的文件,而且不管文件的目录层次有多深都能找到。接下来,把这些文件传送给Babel。最后一步是把已经转换的ES5代码输出到它的目标文件夹,也就是dist目录。Gulp会保存源文件的文件名和目录结构。比如,es6/a.js这个文件经过转换后会输出到dist/a.js,而es6/a/b/c.js则会输出到dist/a/b/c.js。同理,对于public/es6目录也会重复相同的过程。

到目前为止还没有真正地学习ES6,不过可以先试着创建一个ES6文件来验证Gulp配置的正确性。创建es6/test.js文件,并写入以下可以展现ES6特性的代码。(如果读者还不理解这些代码,不用担心,看完这本书后就会明白了!)

'use strict';
// es6 特性: 基于块作用域的"let" 声明
const sentences = [
    { subject: 'JavaScript', verb: 'is', object: 'great' },
    { subject: 'Elephants', verb: 'are', object: 'large' },
];
// es6 特性: 对象解构
function say({ subject, verb, object }) {
    // es6 特性: 模板字符串
    console.log('${subject} ${verb} ${object}');
}
// es6 特性: for..of
for(let s of sentences) {
    say(s); 
}

接下来把这个文件复制到public/es6文件夹下(可以试着改变sentances数组中的内容,以验证使用了不同文件)。然后在命令行窗口敲入gulp命令。执行完毕后,查看distpublic/dist目录。会发现里面各有一个test.js目录。如果仔细看那个文件就会发现它跟原始的ES6文件不一样。

下面试着直接运行ES6代码:

$ node es6/test.js
/home/ethan/lje3/es6/test.js:8
function say({ subject, verb, object }) {
             ^ 

SyntaxError: Unexpected token {
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:374:25)
    at Object.Module._extensions..js (module.js:417:10)
    at Module.load (module.js:344:32)
    at Function.Module._load (module.js:301:12)
    at Function.Module.runMain (module.js:442:10)
    at startup (node.js:136:18)
    at node.js:966:3

这个错误提示是Node输出的,大家得到的错误提示可能跟书中的不一样,这是因为Node还未完全实现ES6的特性(如果在足够远的未来读这本书,Node可能已经完全实现了ES6的特性!)接下来试试运行ES5吧:

$ node dist\test.js
JavaScript is great
Elephants are large

至此已经成功将ES6代码转换成更轻量的ES5代码,这样它就能在任何地方运行了。最后将distpublic/dist添加到.gitignore文件中:因为想跟踪的是ES6源码,而不是生成的ES5代码。

在参加很时尚的朋友聚会或者重要的面试之前,大家会用除毛滚筒来去除衣服上多余的毛球吗?大部分人会这样做,因为都希望将自己最好的一面展现出来。同理,也可以整理代码,让它(也就让用户)更好看些。整理器会严格审查用户的代码,然后告诉用户其中有哪些常见的错误。即使像作者这样拥有25年开发经验的人,整理器还是能在代码里找到错误。对于初学者来说,使用这个工具带来的好处是无与伦比的,它可以避免开发人员纠结于常见的代码错误。

目前有不少常见的JavaScript整理器,不过作者更喜欢由Nicholas Zaka’s开发的ESLint。安装ESLint:

npm install -g eslint

在使用它之前,需要在项目中创建一个叫作.eslintrc的配置文件。不同的项目会使用风格迥异的技术栈和编程规范,而.eslintrc文件则可以在各种环境下都妥善处理代码。

创建.eslintrc文件最简单的方法是运行eslint --init,执行这个命令时需要回答一些问题,之后程序会自动创建一个默认文件。

在项目的根目录,运行eslint --init。需要回答的问题如下。

所有问题回答完毕后,系统会生成一个.eslintrc文件,接下来就可以使用ESLint了。ESLint的运行方式有很多种。可以直接运行它(比如,运行eslint es6/ test.js),还可以把它集成到编辑器里,或者把它加到Gulpfile里。最好的方式莫过于跟编辑器集成,但是不同的编辑器和操作系统所需的操作是不一样的。如果你想这样做,可以谷歌一下将“eslint”添加到编辑器的具体操作。

不管是否会将ESlint集成到编辑器中,作者都建议把ESLint加到Gulpfile里。毕竟,在构建项目时会运行Gulp,而此时是一个检查代码的绝佳机会。首先运行:

npm install --save-dev gulp-eslint

然后修改gulpfile.js:

const gulp = require('gulp');
const babel = require('gulp-babel');
const eslint = require('gulp-eslint');

gulp.task('default', function() {
  // 运行 ESLint
  gulp.src(["es6/**/*.js", "public/es6/**/*.js"])
    .pipe(eslint())
    .pipe(eslint.format());
  // Node的资源
  gulp.src("es6/**/*.js")
    .pipe(babel())
    .pipe(gulp.dest("dist"));
  // 浏览器的资源
  gulp.src("public/es6/**/*.js")
    .pipe(babel())
    .pipe(gulp.dest("public/dist"));
});

现在来看看ESLint帮助找出了哪些错误。由于把ESLint加到了Gulpfile的默认任务里,所以可以直接运行Gulp:

$ gulp
[15:04:16] Using gulpfile ~/git/gulpfile.js
[15:04:16] Starting 'default'...
[15:04:16] Finished 'default' after 84 ms
[15:04:16]
/home/ethan/lj/es6/test.js
  4:59  error  Unexpected trailing comma     comma-dangle
  9:5   error  Unexpected console statement  no-console

✖ 2 problems (2 errors, 0 warnings)

很显然,Nicholas Zakas和作者一样都不喜欢行末的逗号。好在ESLint会让自己选择哪些是错误,哪些不是。默认行尾永远不能出现逗号,不过可以选择把这个功能整个关掉,或者改成“允许多个”(本人一般喜欢这样做)。编辑.eslintrc文件来修改一下设置(不过如果同意Nicholas的观点,可以继续使用默认的规则)。在.eslintrc中的每一条规则都用一个数组表示。数组的第一行是数字,0表示关闭规则,1表示警告,2表示错误:

<pre class="代码无行号"><code>{
   "<strong>rules</strong>": { 
      /* changed comma-dangle default...ironically,
          we can't use a dangling comma here because
          this is a JSON file. */
      "<strong>comma-dangle</strong>": [
          2,
          "always-multiline"
      ],
      "<strong>indent</strong>": [
         2,
         4 
      ],
      /* ... */ </code></pre>

再次运行gulp,逗号问题就不再是错误了。事实是,如果现在把逗号删掉,就会导致错误!

第二个错误与console.log的使用有关,如果该错误出现在产品代码上,通常会被当成“粗心大意”(如果使用传统浏览器甚至会埋下隐患)。然而出于学习的目的,可以禁用这个规则,因为本书中很多地方会使用到console.log。同时,可能会想关闭那个“括号”的规则。这个就留给读者去练习。

ESLint有很多配置选项;详情请查看ESlint官方网站。

现在可以编写ES6代码,并将其转换成ES5代码,然后对代码进行审查和优化,至此已经完成了ES6之旅的准备工作!

通过本章的学习,我们已经了解到并不是所有浏览器都支持ES6,不过这并不能阻止我们从ES6中获益,因为可以将使用ES6规范编写的代码转换成对应的ES5代码。

当开发人员配置一台新的开发机器时,需要以下这些工具:

每当开始一个新项目时(不管用来运行本书中示例代码的学习项目,还是一个真实的项目),都需要以下这些组件:

一旦准备好了所有东西,就可以开始了,基本的工作流程如下:

(1)编写代码,并修改相关逻辑。

(2)运行Gulp来测试程序,同时使用ESLint来规范代码。

(3)重复上述两个步骤直到改变生效,并且没有语法错误。

(4)使用git status检查当前代码状态,确保不会提交所有代码。如果有些不需要被Git管理的文件,记得把它们放到.gitignore文件里。

(5)把所有的修改添加到Git里(使用git add -A;如果不准备添加所有文件,那就对每个需要添加的文件使用git add)。

(6)提交你的修改(使用git commit -m“<本次修改的描述>”)。

除上述步骤外还可能有其他步骤,这取决于实际项目。比如,有些项目需要运行测试(这通常会是一个Gulp task),或者提交代码到一个用于分享的远端代码库(使用git push),例如,GitHub或者Bitbucket。不管怎样,这里列出的步骤可以用来构建一个很好的项目骨架。

本书的剩下部分将不再赘述上述步骤,只会展示源代码。除非特殊说明会浏览器运行,示例代码都会使用Node运行。打个比方,如果有一个叫作example.js的示例代码,需要把它放在es6文件夹下,然后运行:

$ gulp
$ node dist/example.js

也可以跳过运行Gulp的步骤,直接运行babel-node(不过这并不会缩短运行时间,因为在运行bable-node时仍然需要编译):

$ babel-node es6/example.js

接下来正式开始学习JavaScript!

[1] 有一些JavaScript引擎(比如Node)确实会编译JavaScript,但这个过程对开发者是透明的。


相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
JavaScript核心原理:规范、逻辑与设计
JavaScript核心原理:规范、逻辑与设计
JavaScript入门经典(第7版)
JavaScript入门经典(第7版)
JavaScript函数式编程指南
JavaScript函数式编程指南
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)
JavaScript数据可视化编程
JavaScript数据可视化编程

相关文章

相关课程