Web-开发职业顶级规划指南-全-
Web 开发职业顶级规划指南(全)
原文:
zh.annas-archive.org/md5/eeadad2427312dd8406c599551a391d8译者:飞龙
前言
网络开发是一个令人兴奋的领域,快速的技术变化是日常的秩序。这可能会使它成为一个具有挑战性的领域,尤其是在知道从哪里开始时。当我进入这个领域时,我们真正需要的就是一个基本的文本编辑器,比如记事本,以及一些时间来查看网站背后的代码,以了解构建它们所涉及的基本内容。当时自学仍然很困难,但确实更容易,因为要学习的东西没有那么多。
现在事情远非那么简单!确定你的起点是困难的。然后,当你确实弄清楚并学习了技术的基础知识后,你必须面对以专业身份进入该领域的挑战,当你没有太多东西来展示你的能力时,这可能很困难。
一旦你弄清楚如何打开那个难题,挑战就转变为跟上技术的不断变化,继续扩展你的能力,并学习如何融入并做好工作。
是的,虽然这是一个令人兴奋的领域,但它也是一个具有挑战性的领域。但我希望,通过这本书,我能利用近 30 年的专业经验来帮助你稳固地开始这段旅程,建立良好的技术基础,以及你作为一个成功的网络开发者所需的其他许多技能。
本书面向的对象
如果你是一个对问题解决充满热情并对创造数字创新感到兴奋的潜在网络开发者,那么这本书就是为你准备的。无论你是热衷于学习的,技术爱好者,还是对这个领域感兴趣想要开始职业生涯的人,这本书都提供了宝贵的见解。
设计成对没有任何先验技术或软件开发知识的个人友好,这本书提供了对网络开发世界的概述和基础介绍。对于那些想要踏入一个广阔而令人兴奋领域的初学者来说,这是一本完美的入门书籍。
本书涵盖的内容
在第一章,“那么,你想要成为一名网络开发者,对吧?”,你将了解成为一名网络开发者意味着什么,以及这项工作的技术层面和个人层面的要求。
在第二章,“跨越鸿沟——客户端(前端)与服务器(后端)开发”,你将开始学习前端和后端的区别,它们是如何协同工作的,以及两者的技术基础。
在第三章,“扩展基础 – 从前到后构建用户注册页面”,你将继续接触到网络开发的基石,并回顾一个完整——尽管简单——的网站,该网站展示了所有关键组件。
在第四章 管理、安全和与代码一起工作 中,你将开始了解网络开发者使用的工具类型,并开始理解你编写的代码是如何构建、管理和部署的。
在第五章 理解用户体验、部署选项和 DevOps 中,你将更深入地了解如何将你的作品制作成适合用户使用,以及你如何实际上使他们能够使用它。
在第六章 探索现代前端开发 中,你将了解一些目前用于构建网站和 Web 应用的当前最先进框架、库和工具包。
在第七章 从前端到后端——更多关于后端工具和技术 中,你将更深入地了解用于构建和运行服务器端代码的后端技术;这是你的前端代码为了完成工作而向其发出请求的代码。
在第八章 编写更少的代码——CMS、无代码解决方案和生成式 AI 中,你将了解允许网络开发者构建无需编写代码或至少编写较少代码的解决方案的工具。
在第九章 找到你的第一份工作 中,你将开始关注非技术性话题——大部分是——这些话题与让你进入门槛和找到你的第一份网络开发工作有关。
在第十章 作为网络开发者发现高质量工作的关键 中,你将看到优秀的网络开发者用来确保他们每一步都交付高质量工作的技术和习惯。
在第十一章 回顾软技能(它们使你难以拒绝) 中,你将了解一些通常不是技术性的但任何优秀的网络开发者都需要掌握的技能,包括人际关系技巧和沟通技巧等。
在第十二章 发展你的职业生涯 中,你将感受到在技术领域持续学习和成长的重要性,因为技术和需求不断变化,所以你必须同样这样做才能成功!
为了最大限度地利用这本书
技术要求在每个相关章节中都有介绍。然而,作为一个总结,以下是在阅读本书过程中所需的内容,并且在所有情况下,建议使用最新版本,尽管任何相对较新的版本也应该可以正常工作:
| 书中涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| NodeJS | Windows, macOS, 或 Linux |
| Git | Windows, macOS, 或 Linux |
| VirtualBox + Ubutu Linux VirtualBox 镜像 | Windows, macOS, 或 Linux |
| Python | Windows, macOS, 或 Linux |
| Postman | Windows、macOS 或 Linux |
| Java JDK | Windows、macOS 或 Linux |
如前所述,您现在不需要下载和安装所有这些内容,您可以在阅读章节时进行(同时附上下载链接)。在所有情况下,您只需遵循您从同一网站上下载时的标准安装说明。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Web-Development-Career-Master-Plan。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有来自我们丰富的书籍和视频目录中的其他代码包,可在github.com/PacktPublishing/找到。查看它们!
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“HTML 文档本身是通过从<html>标签开始并以其关闭伙伴</html>结束来创建的。”
代码块设置如下:
<h1>Luke Skywalker</h1>
<div id="relatives">Anakin, Padme, Leia</div>
<p class="friends">Obi-Wan, Han, Chewie</p>
当我们希望将您的注意力引到代码块中的特定部分时,相关的行或项目将以粗体显示:
<h1>Luke Skywalker</h1>
<div id="relatives">Anakin, Padme, Leia</div>
<p class="friends">Obi-Wan, Han, Chewie</p>
任何命令行输入或输出都按以下方式编写:
npm init
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“现在,要在 VirtualBox 中设置虚拟机,请点击机器菜单,然后点击新建按钮。”
提示或重要注意事项
看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,我们将非常感激您能提供位置地址或网站名称。请通过 copyright@packtpub.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com
分享您的想法
一旦您阅读了网页开发职业规划大师计划,我们非常乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载此书的免费 PDF 副本
感谢您购买此书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走?
您的电子书购买是否与您选择的设备不兼容?
别担心,现在每购买一本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯和每日免费内容的每日邮箱访问权限。
按照以下简单步骤获取福利:
- 扫描下面的二维码或访问以下链接

packt.link/free-ebook/978-1-80324-708-3
-
提交您的购买证明
-
就这些!我们将直接将您的免费 PDF 和其他福利发送到您的邮箱。
第一部分:介绍网页开发的基础知识
在本部分中,您将开始学习成为一名网页开发者意味着什么。我们将讨论网页开发者的定义,以及一个典型的网页开发者工作日可能是什么样子,以及定义该职业的职位和工作类型。
我们还将开始探索涉及的技术,包括“三大件”HTML、CSS 和 JavaScript。您将了解客户端/服务器划分,前端和后端是什么,以及它们是如何协同工作的。您还将了解网络和网页开发者可能被要求构建的网站和应用类型。
最后,您将获得构建实际网站的第一手经验,以了解所有部件如何组合成最终产品。
本部分包括以下章节:
-
第一章,所以,你想要成为一名网页开发者,对吧?
-
第二章,架起桥梁——客户端(前端)与服务器(后端)开发之间的差异
-
第三章,扩展基础——从前端到后端构建用户注册页面!
第一章:那么,你想要成为一名网页开发者,对吧?
Facebook. Wikipedia. CNN. Reddit. Ars Technica. Amazon. Instagram. ESPN. YouTube. Google. 网络真的吞噬了整个世界,不是吗?
每天有成千上万的人访问成千上万的网站。有些人是为了玩游戏,有些人是为了了解世界正在发生什么,有些人是为了让我们知道他们在忙什么(以及他们的猫在忙什么!),还有些人是为了与世界就各种话题进行交流。银行、医学、农业、社会、育儿、锻炼、食品和文化——几乎每个行业和每个你能想到的兴趣都在这个庞大的网站迷宫中有代表。
但谁创造了我们所有人(虚拟地)现在都聚集的这些数字空间?谁操纵着支撑这一切的技术来创造下一个伟大的网站?好吧,如果你正在阅读这本书,那么猜猜看?你已经迈出了成为答案的第一步——那就是你!
在这本书之外,还有许多更多的步骤,但正如人们所说:千里之行,始于足下。这本书的目标是为你提供一个路线图,详细说明成为网页开发者(无论那是什么——我们很快就会谈到!)需要采取的步骤。同时,它将为你提供在技术上的成功旅程所需理解的技术基础的第一层。
不要误解:这本书不会教你所有你需要知道的技术知识——一本书永远做不到这一点——但当你读到结尾时,你会在脑海中拥有这些知识的起点,并且你会对下一个你应该攀登的山峰有一个清晰的画面。
在本章中,基本目标是让你了解什么是网页开发者,以及成为其中一员需要什么。当然,理解这一点对于你的整个旅程至关重要,因为追求一个你并不真正理解的职业是没有太多意义的。特别是在一个即使是看似简单的事物也可能有广泛定义的领域,拥有基础的知识是非常重要的。
因此,在本章中,我们将涵盖以下主题:
-
究竟什么是网页开发者?
-
成为不同类型的网页开发者需要具备哪些条件?
-
网页开发者可能每天会执行哪些类型的任务?
-
网页开发者典型的一天工作内容是什么?
究竟什么是网页开发者?!
当我们问“什么是网页开发者?”这个问题时,明显的答案是那些开发网站的人。但这个答案过于简单化,并没有真正涵盖所有内容。此外,实际上并没有一个单一、被广泛接受的定义,但关于这个主题有很多变体。
如果你问我——通过阅读这本书,你多少已经做到了!——我的回答会是,网页开发者是程序员、编码者或软件开发者。所有这些术语基本上可以互换,它们仅仅指的是那些创建计算机软件的人,其中网站就是其中一种类型。
无论我们是在谈论网络开发还是其他类型的软件开发,唯一真正的统一原则是,你必须能够进行逻辑思考,这转化为能够编写计算机将遵循的指令(即代码),以便在网页浏览器中生成一个网站。基本上,网络开发是一种软件工程形式,尽管并非所有的软件工程都是网络开发(因为并非所有的软件都在网络上运行)——所以,你实际上是在走向成为一名软件工程师的旅程,而你的软件恰好运行在网络上。
但这不仅仅是编写代码!网络开发者还必须是一名图形艺术家。他们必须是可用性专家。他们必须是业务分析师。他们必须是作家和校对员。他们必须是网络工程师。他们还必须做更多的事情。换句话说,他们必须是全能的。这份工作不仅仅是编写代码;其中还有很多其他的东西,随着我们继续前进,我们将一一探讨。
这引发了一个观点,你有时会从那些不太熟悉网络开发者所涉及内容的人那里听到:他们说网络开发在某些方面比其他类型的软件开发更容易。好吧,让我立刻打消你的这种想法,因为这离真相相差甚远。事实上,虽然这并不总是正确的,但可以有力地论证,网络开发通常比非网络开发更难,仅仅是因为涉及到的所有事情,其中一些是网络开发特有的(或者至少在非网络开发中更为重要)。在这里,我指的是像网络这样的东西,因为网络本质上是在网络上运行的,而并非所有的软件都是这样,以及在网络开发中,作为开发者的你实际上并不真正控制运行环境。例如,用户可以随意调整他们的浏览器窗口大小,或者改变网页的缩放或字体,而你的代码必须适应并处理这些决定(这在桌面开发中也是真实的,但在网络开发中这是一个更大的问题)。
说了这么多,让我来缓解你心中可能升起的任何恐惧:虽然确实有很多东西要学习,但简单的事实是,网络开发是从一些相对基本的概念开始的,并从这里逐步构建。此外,完全没有必要一次性学习所有东西。不,你可以在不知道任何给定时刻“需要”学习的所有内容的情况下,成为一个非常出色的网络开发者。这是一个需要“学会学习”的关键领域,能够边走边学,一点一点地解决问题是至关重要的。事实上,一个人根本不可能知道所有的事情,当你完成这本书的时候,你会非常清楚地理解这一点。换句话说:无论看起来多么令人畏惧,我保证你会到达那里!
让我们更深入地探讨一下成为网页开发者意味着什么,以及从流程角度来看构建网站是什么样的,至少在非常高的层面上。
深入探讨成为网页开发者和构建网站
通常情况下,一切始于 利益相关者 或客户——本质上,是你为谁创建网站的人。当然,那个利益相关者也可能是你自己。无论如何,某个地方总有人有一些关于网站的构想。
这个网站可能是我们所说的 宣传册网站(或“宣传册网站”),这是一种更多或更少只是静态(不变)内容的网站。你有没有检查过你最喜欢的当地比萨店的网站?很可能,它除了提供一些关于餐厅的信息、菜单和一些用于下单的联系方式之外,没有更多内容(当然,有些可能会有在线订购,但让我们假设你的当地地方不够豪华到有这个功能)。这样的网站通常被认为是宣传册网站,因为它们在某种程度上是公司营销宣传册的数字版本。它们大部分不是交互式的;它们的目的是简单地传达信息给你。
我的个人网站位于 zammetti.com,如图 图 1.1 所示,就是一个这样的宣传册网站。它除了传达关于我的信息之外,没有做太多其他的事情,这就是重点:

图 1.1:一个简单的宣传册网站——我的个人网站
然而,客户可能有一个宏伟的想法,下一个 Twitter(或 X,如现今所知),或者可能是 Gmail 或 Bing。这些网站与宣传册网站截然不同,因为它们高度交互式,意味着它们为访客执行功能。它们的目的是完成任务。这类网站通常被称为 网络应用程序。
这是一个微妙的不同之处,它们之间的界限可能会变得非常模糊。但作为一个一般性的指导原则,如果一个网站非常交互式,并且可以在用户的请求下执行功能,那么很可能,它是一个网络应用程序。这样说,重要的是要记住,网络应用程序仍然是一个网站。
无论如何,利益相关者都有那个愿景,而你作为网页开发者的第一份工作就是倾听他们,并理解他们试图实现的目标。这听起来可能很简单,但请相信我,这并不总是那么容易!通常,这个想法会非常模糊,而且/或者他们可能无法清楚地表达出来,他们将依赖你来解释他们的言语。本质上,你需要弄清楚他们头脑中的想法,有时甚至他们自己也不知道!
然后,你需要将那种理解映射到一个技术解决方案上——也就是说,实际上构建一个网站。这个过程通常会是迭代的,这意味着你会构建一些东西,展示给利益相关者,得到反馈,然后进行修改,经历几次这样的循环。这是非常典型的,而且绝对没有问题。我甚至可以说,不这样做将是罕见的例外。
但在那个阶段,你实际上建造了什么?好吧,你可能很惊讶地发现,在大多数情况下,你不会立即编写任何代码或进行任何编程。相反,第一步通常是创建我们所说的“低保真度原型”。或者,更简单地说:草图!
如果你是一个艺术型的人,你可能用手来绘制这些,但如果你像我一样——一个连直线都画不直的人——你会寻找专门为此目的而设计的工具。这里有很多选择,比如 Canva 和 Figma。甚至 Adobe Photoshop——一个图形编辑程序——也经常被用于这个目的。
另一个相当受欢迎的工具,碰巧也是我最喜欢的工具,是 Balsamiq Wireframes。使用它,你可以制作出如图图 1.2所示的插图:

图 1.2:使用 Balsamiq Wireframes 创建的比萨店网站示例
注意在那个原型中——我们称之为这种低保真度图表——事物并没有完全对齐,中间没有实际的文字,只是有些填充物来说明文字将会在那里?在这个阶段,你实际上并不关心诸如颜色、布局、字体选择、详细文字、图形以及其他使网站完整的事物。你试图做的只是确定整体结构,确保你从高层次上理解客户想要什么,并以他们能理解的形式展示出来,这样他们就可以回答“是的,你明白了!”或者“不,回到画板”(尽管你希望得到介于两者之间的东西,比如“好吧,很接近了,我们只需要调整这些一两个相对较小的问题”)。
下一个阶段通常是原型设计,有时也称为概念验证(尽管这通常只适用于交互式网站,因为目的是证明功能)。这通常将涉及实际开发和实际编程,但以非常快速和草率的方式进行。事实上,你在这个阶段可能会丢弃你完成的工作的一部分。
你在这里可能并不一定追求完美,但肯定需要深入了解细节。例如,当用户点击“在此处下单”按钮时,应该看到什么?在草图阶段,你可能会给客户一个第二版草图,上面说的并不多,只是“点击按钮时将出现订单表单。”但是,在原型阶段,你需要在一定程度上构建那个订单表单屏幕。它从风格或功能上讲,仍然不需要 100%完整,但你必须开始制作一个实际的订单表单,并让它开始看起来和看起来像它真正工作时的样子。
在这个阶段,用户交互通常是“模拟”的,这意味着它们是伪造的,但以一种让客户看起来真实的方式。例如,如果你需要能够将披萨和其他食品添加到购物车中,那么有快速且低成本的方法来实现这一点,这些方法可以让客户真正体验订购过程,但无需投入大量时间和精力来真正构建这一功能,这需要更多的时间和精力。
一旦他们开始使用它——你很可能会经历几个迭代周期——这时才是真正开始构建网站的时候。这是你开始编写真正有效、最终形式的代码的时候。你需要开始收集图形,可能从头开始创建它们,并在对齐、颜色、字体以及网页中所有风格方面确保布局稳固。你需要编写使订单表单真正工作的代码,这可能包括也可能不包括在服务器上编写代码(我们稍后会详细讨论)。所有那些只是为了向利益相关者表达想法而以粗略形式放入草图和原型的想法,现在必须巩固成一个真正的产品。
从一个想法,有时是一个非常模糊的想法,到建立一个完整的网站,无论是宣传册网站还是完整的网络应用程序,这真是一项相当冒险的任务。这需要大量的时间,有时还需要极大的耐心,以及逐渐微调事物的能力。本质上,你是在将粘土塑造成一个美丽的花瓶。这并不总是容易的;有时粘土塔甚至可能倒塌,你不得不重新开始,但这就是网络开发者的工作,简而言之!
在这一点上,我想再次重申我之前提到的观点,尽管这一切可能看起来令人望而生畏,但你不必害怕!我们将一步一步地前进,共同建立起你需要的基础。我们在这里所做的只是从 10,000 英尺的高度审视事物,并且没有期望你了解一切,而且这基本上将始终是你在这一领域工作的情形。
话虽如此,我希望你认为这一切听起来都很有趣!但有趣只是其中的一部分——那么,就业市场怎么样?网络开发者真的受欢迎吗?好吧,让我们来看看!
网络开发者有多受欢迎?
如果你现在上网,你会看到一些关于网络开发——以及更广泛的软件开发——是否处于危险中的辩论。那么,我们是否仍然受欢迎?一旦人工智能开始对我们产生影响,我们还会继续受欢迎吗?ChatGPT 及其竞争对手是否会以极短的时间和极低的成本完成我们做的工作?在这方面确实有很多悲观情绪,所以这是一个合理的问题。
我们当然可以整天辩论这个问题,很多人确实在这么做。如果你想知道我的看法,很简单:人工智能不会取代我们的工作。不过,它将会成为一个所有网络开发者都必须使用的不可或缺的工具。但我在这里跑题了,我们会在本书的后面部分讨论 AI 工具。
目前来说,简单的回答是,是的,网络开发者现在绝对很受欢迎,我认为至少在接下来的 10-20 年内这种状况不太可能改变(可能甚至更长)。当然,随着时间的推移,这份工作会有些变化,因为软件开发总是这样(它是一个变化极其迅速的领域),但如果你愿意跟上,我认为你不需要担心自己不受欢迎。
实际上,如果你想看看一些支持这个断言的硬数据,请看看图 1.3,这是一张美国劳工统计局职业展望手册页面讨论网络开发者的截图(网络开发者):

图 1.3:美国劳工统计局职业展望手册页面讨论网络开发者
你在 BLS 网站上会发现,预计到 2032 年,网络开发者的就业机会将增长 16%(截至撰写本文时——这个数字在你阅读本文时可能会改变)。有趣的是,两年前的预测增长率是 13%,这告诉我们需求正在增加,这对任何进入这个领域的人来说都是好消息。事实上,这种预测的增长速度几乎超过了所有其他领域。
所以,是的,说我们很受欢迎可能还是低估了!
这种需求主要是由电子商务网站和移动开发推动的。那些是什么?
好吧,一个电子商务网站,其中“e”代表“电子”,是一个人们买卖商品或进行其他通常被认为是“商业”活动的地方。亚马逊和 eBay 可能是最知名的电子商务网站,但你的银行网站也是一个,甚至你之前看过的那个当地比萨店网站可能也是。电子商务网站通常更侧重于进行商业活动,所以如果你可以从比萨店网站上订购食物,那么这个比萨店的网站就是一个电子商务网站;然而,如果它只是谈论餐厅,那么它可能是一个宣传册网站(有时候这个界限可能有点模糊)。无论如何,由于商业活动都是关于企业的,而企业某种程度上统治着世界(无论这是好事还是坏事),你可以看到为什么需求只会不断增加。
当然,这不仅仅关于电子商务网站。社交媒体是另一个巨大的类别,其中有很多工作可以找到,当然还有许多其他类别的网站。但无论网站的类型如何,它往往都归结于背后的企业或其他类型的组织(非营利组织、政府等)。随着这些实体继续将在线存在优先于非互联网存在(因为人们现在普遍期望在互联网上生活),这种需求趋势线只会持续上升。
移动开发是一个完全不同的类别,但事实是,如今很多移动开发也是网络开发。你知道你在 iPhone 或三星 Galaxy 手机上运行的应用程序吗?其中许多是我们所说的“原生”应用程序,这意味着它们是为那些特定平台编写的。然而,许多应用程序使用与构建网站相同的网络技术,并且这个趋势可能会增长。为移动设备创建网站或网络应用程序会带来独特的挑战,但技能集是相同的,所以那里的增长意味着网络开发的增长。
事实上,对于世界上的许多地方来说,网站更多地是通过移动设备而不是典型的桌面机器(尽管笔记本电脑有点灰色地带,但它们更像是桌面机器而不是移动智能手机)来访问的。网络开发通常以“移动优先”的方式进行,这意味着你让你的网站在智能手机上看起来和功能正确,然后调整它以便在桌面上也能很好地工作。
从这次讨论中,你可以看到网络开发有多种形式,使其成为一个不断扩大的主题,随着时间的推移只会变得更加广泛。作为网络开发者,你需要掌握许多不同的学科,包括云计算、网络安全、数据分析、搜索引擎优化等等。如果你不知道这些是什么意思,不用担心——你会在本书结束时了解!不过,这里的相关点是,由于你需要至少对这一长串事物有所了解,熟练的网络开发者变得越来越有价值。一旦你掌握了这些技能,你将这些技能用于他人的需求只会增长,因为该领域的不断演变意味着你的学习能力——以及能够证明这一点的能力——将是你最宝贵的资产。
给我看看钱!
好的,太好了,我已经说服你,网络开发者需求旺盛,并且将持续保持需求旺盛,我们甚至可能会看到需求的增长。但这听起来有点困难,不是吗?这最好是有价值的,对吧?!
嗯,确实是!根据我之前提到的 BLS 网站,截至 2022 年 5 月,中位收入为 78,580 美元。我可以告诉你,这个数字比 2020 年高,而且在此之前已经持续上涨了一段时间。这不算差!
但还有更好的!随着你不断进步和积累经验,你不会对发现自己的收入潜力上升感到惊讶。我将根据你的工作经验年限给你一个非常粗略的职业发展路径:
-
你可能一开始的薪资在 5 万到 6 万美元之间(但请记住,所有这些数字都受到许多因素的影响,所以你的起薪可能实际上更低或更高)
-
三年后,你很可能期望在 7 万到 8 万美元的范围内
-
五年后,你很可能达到或接近 10 万美元
-
到了第十年,你几乎肯定能超过 10 万美元,可能更多,大约在 13 万到 14 万美元之间,你很可能即将,或者已经,晋升到高级/领导职位
-
15 年后,你可能看到 150,000 美元或更高
之前的数字是非FAANG职位——也就是说,任何不被视为 FAANG 公司的公司。当然,这会引发一个问题:什么是 FAANG 公司?!FAANG 是一个缩写,代表Facebook, Amazon, Apple, Netflix, and Google,我将在第九章中详细介绍,但现在的重点是,我们用这个术语指的是“最大的、薪酬最高的硅谷公司”。如果你在这些类型的公司中获得职位,那么你可以期待比这里提到的数字多得多的收入。但无论你是否在 FAANG 公司工作,在这个领域都有很大的机会实现财务上的成功。
因此,到目前为止,你一定垂涎欲滴,对吧?那些山里有金子,你只需要获得挖掘它的技能。但让我们稍微放慢脚步,提出一个可能很难但至关重要的一个问题:这是否是一个适合你的领域?
但是,网页开发的工作适合你吗?
你有没有偶然听说过“任何人都能编程”的咒语?这是我们喜欢说的话,在基本意义上,确实是如此。对于有学习动力的人来说,在网页开发领域没有什么是根本无法学会的。它不像棒球,你必须能够击中每小时 95 英里的快速球,或者像篮球,你必须能够做出跳投,这两者都需要大量的身体技巧。而且,它也不像宇航员,你必须有完美的视力和不屈的意志去爬上并乘坐一个巨大的、爆炸的蜡烛进入轨道!不,这里没有真正的身体要求,天生的才能或与生俱来的能力是你必须拥有的。只要你有了动力,你就能学会你需要的一切。
让我们来看看一个成功的网页开发者通常会需要的某些关键特征。
你必须是一个解谜大师
话虽如此,网页开发——以及软件开发总体上——在某种程度上是一种持续的解谜练习。你所做的一切都将围绕着解决问题。你是如何让客户的标志在网页上正确显示的?你是如何让他们的联系表单发送电子邮件的?你是如何让网站对残疾人可访问的?这些都是需要解决的问题,需要完成的谜题。
在这个领域茁壮成长的人有一些特定的品质,一些是固有的,但大多数可以通过经验随着时间的推移而发展。
创意最大化
在网页开发中,也涉及相当大的创意。很少有正确或错误的方法去做事情,很少有解决问题的明确方法,也很少有经典正确的答案。因此,你通常需要有一定的创意才能产生令人满意的结果。你必须接受你所被要求做的事情——这可能是相当一般性的,有时可能并不明确定义——并找出如何将其变为现实,通常是通过引入你自己的创意解释。
而且这不仅仅是你看到屏幕上的内容,创意和艺术性的概念在这里显然更适用。不,编写代码中也有创意和艺术性:你是如何构建它的?你需要哪些组件,以及你是如何让它们相互作用的?
但尽管网页开发是一个充满创造力的领域,你可以有很大的自由按照自己的方式做事,但它并不是完全的无政府状态!我们称之为“最佳实践”,指的是在特定情况下之前已经应用过的解决方案,通常为许多人带来了积极的结果。但最终,建造网站与建造桥梁不同,例如,因为社会已经建造了许多桥梁,因此我们每次都知道如何建造。我们知道什么有效,什么无效,因此我们在建造时遵循经过验证的建筑技术。虽然最佳实践在网页开发中为我们提供了一些这样的东西,但它往往比你想象的要少,这也为大量的创造力打开了大门。
换句话说:虽然建造桥梁无疑是科学和艺术的结合,但在大多数情况下,科学部分将是更大的部分。在软件开发,尤其是网页开发中,情况通常正好相反。
关注细节,为了乐趣和利润
因此,网页开发者必须喜欢解决问题,并且必须具有创造力。他们还必须具备严谨的细节关注力。当我说到,有时你会因为试图让某物工作而拔掉头发,最终发现它之所以不工作,是因为某个数字、字母或符号出了问题。你越关注细节,你拔掉所有头发的时间就越长!

图 1.4:网页开发者经常诅咒,但总是对着无生命物体
单独或团队合作,都同样重要
网页开发者必须擅长独立工作,同时也能与他人很好地合作。正如我之前提到的,与客户和利益相关者打交道是与他人合作的一个明显例子,但有很大可能性你不会是唯一开发网站的人。
你当然在某些情况下可能会独自工作,但更常见的情况是你会有合作伙伴。你可能负责构建网站的某个部分,而其他人则负责另一部分,然后你必须确保所有部分都能协同工作。能够沟通、协作、规划和执行是关键要求。
软技能,换句话说,也是网页开发者另一个关键要求。无论以书面形式还是口头形式,无论是否技术性,能够清晰、精确地表达你的想法是一项你必须掌握的技能。
跟上时代
你会反复听到这一点,但学会学习,并适应变化,是成为一名网页开发者需要掌握的顶级技能之一。这个领域的变革速度既令人惊叹,又令人兴奋……但同时也非常令人沮丧!当你学会一项技能并对其感到自信时,它可能就会过时,并被其他东西所取代。
这导致了一个被称为“冒充者综合症”的问题,几乎每个人都会遇到。这就是你感觉自己像是一个冒充者,一个不知道自己在做什么的人,即使你实际上知道。尽管我已经从事这类工作很长时间了,但有时它仍然会影响到我。技术的不断更新导致了这种感觉,因为你总是在不得不面对新事物。不幸的是,没有银弹可以用来处理冒充者综合症。但能够边学边用,并随着时间的推移证明自己能够做到,可能是减轻其影响的一种唯一方法。
跟踪领域的变化有时是一项全职工作,如果你喜欢发现过程,那就更有帮助了。如果你喜欢学习新事物,有时喜欢处于边缘,那么网页开发可能会让你笑很多。
随遇而安
你必须有一个厚脸皮才能成为一名网页开发者,因为总会有人有意见需要你倾听并采取行动。无论是客户还是老板,你必须愿意接受反馈,不要把它当作个人攻击,并愿意根据你听到的内容修改你的工作。正如作家们喜欢说的:你必须愿意杀死你的孩子。在网页开发中,这可能意味着尽管你认为你构建的网站看起来很酷,但如果客户有不同的看法,你必须愿意放弃并重新开始。
找到你需要的东西
作为一名网页开发者,你将花费大量时间做一些看似不是网页开发的事情。当然,我谈到了与他人沟通的事情,但在这里,我谈论的是研究和构思你将要构建的东西,无论是网页布局、背后的代码等等。你会惊讶于网页开发者花费大量时间只是盯着空中,想象着事情!但是,除此之外,你必须能够找到信息并将其综合到你的思考中。你必须知道去哪里获取你绝对会有的所有问题的答案。
你知道如何在网页上水平垂直居中一个div元素吗?你知道这些词的意思吗?不知道?好吧,你最好知道去哪里查找这些信息!你知道 React 的useEffect()函数的函数签名是什么吗?不知道?我希望你能找到答案,否则你会遇到麻烦!你今天必须使用 Vue(你对它一无所知)而不是你通常使用的 React 吗?太好了——你将不得不做一些研究,不是吗?
所有这些的要点是,成为一名网页开发者意味着你需要拥有很多技能和品质,而不仅仅是纯粹的技术知识。实际上,技术技能只是容易的部分。如果你不享受这一切的挑战,那么网页开发领域可能并不适合你。你必须接受挑战,接受疯狂!如果你这样做,我认为,抛开金钱不说,你将从从中获得的成就感中享受到很多乐趣。没有什么比解决困扰你一段时间的问题,并在屏幕上看到最终工作起来的感觉更令人欣慰了。这是令人满意的。即使你所构建的东西对你来说并没有根本的兴趣,第一次看到它工作起来感觉也非常不错!
因此,在这个领域,除了金钱之外,确实有回报,但我认为你了解你要进入的是什么领域是公平的。肯定会有一些日子,你会对着无生命的物体尖叫和喊叫。你将感受到冒名顶替综合症一直存在,并不得不与之抗争。几乎每天都会介绍一些你必须接受并利用的新事物。但如果这是一个让你兴奋的挑战,那么网页开发可能正是你需要的!
耐心和应对失败
除此之外,你还需要具备的两个技能,或者说素质,是耐心和应对失败的能力。你需要耐心去研究和学习,这是显而易见的,但你在构建网站时也需要耐心,因为需要时间来确保所有细节都正确。你经常会不得不多次检查,一点一点地调整,才能使其恰到好处。
这与应对失败的概念相吻合。每次你编写代码时,都存在它第一次没有正确工作的风险。事实上,你几乎可以保证它不会!你可以让这影响到你,这样你就会有一个非常糟糕的经历,而且不会取得好的进展,或者你可以学会接受事情不工作只是过程的一部分,并且必须继续努力。
在某种程度上,你必须善待自己。在开发网站的过程中,你将会有很多小小的失败,你将不得不不为此自责,因为这是过程的一部分。但这是网页开发,以及实际上软件开发通常的独特之处:你可以多次出错,并逐渐将其纠正得更接近正确,而没有任何真正的后果。这不像设计建筑,如果你一开始就出错,整个项目可能就会崩溃,浪费大量时间、金钱,甚至可能危及生命。不,如果你的代码第一次没有正确工作——而且,再次强调,这很可能——这并不会造成真正的伤害。当然,这会浪费一点时间,但仅此而已。
因此,耐心和能够处理失败也是成为一名网页开发者需要具备的关键品质,通过结合它们,你就能得到对自己宽容的理念。实际上,学会不把每一行不正确的代码都视为失败,这才是关键。这只是旅程的一部分,每次都是,而且完全没问题!
你会注意到我没有列出作为网页开发者需要具备的品质之一,那就是很多进入这个领域的人都在思考的问题:是否需要大学学位。那么,让我们接下来谈谈这个问题!
你需要学位吗?
在所有关于进入网页开发领域的问题中,你是否需要上大学并获得大学学位——通常是一个计算机科学学位——可能是最复杂的问题之一。如果你看看在这个领域取得成功所需的全部技能,似乎显而易见的答案应该是肯定的,你确实需要学位。但现实情况却大不相同。
让我简单介绍一下我自己。截至写作本文时,我 51 岁。我作为一名专业开发者已经工作了 28 年,在那之前我还做过超过 5 年的咨询工作。我以某种形式编程已经大约 45 年了——是的,我真的从大约 6 岁就开始了!这一直是我一生的热情,而且我有一个漫长而富有成果的职业生涯,这在某种程度上只是一个额外的奖励。
但你知道我缺少什么吗?没错:一个大学学位。
我确实上过一段时间大学,但由于个人原因,在大约离学位还有两年的时候我不得不辍学了。后来我稍作休息,又去上了一些课,打算拿到那个学位。但那时我主要是出于职业发展的考虑,简单地说,事实是:没有学位并没有阻止我进入这个领域,也从未阻碍过我。
现在,我不想给人留下任何错误的印象:如果我从一开始就完成了学位,我的道路可能更容易——当然也更短——而且我一开始就能赚得比现在更多的钱。我还花了很长时间相信,没有那张挂在墙上的纸,没有人会给我机会,所以我甚至都没有尝试。
那是一个错误。
事实上,在这个领域,如果你能证明你懂得自己的东西,那么学位不会阻止你入门。一开始可能意味着收入会少一些,也可能意味着你的职业发展有一个天花板——我可能永远没有机会获得首席执行官级别职位(CEO、CIO 等)——但你可以肯定,没有学位也能找到一份有报酬的工作。这完全取决于你的能力展示——即使你有计算机科学学位,虽然这表明你可能有一个相当坚实的基础,正如我们之前讨论的,你仍然需要在工作中进行大量的学习。拥有学位表明你有很多优点,但这并不一定意味着你拥有成为优秀网页开发者所需的知识的一小部分。
要明确的是,一些公司确实认为拥有学位非常重要,甚至有些公司会将其作为一项试金石,这意味着如果你没有学位,你的简历可能会立即被扔进垃圾桶。虽然在我的经验中,这可能是例外而不是规则,但这确实是一个硬性要求。说实话,这很遗憾,因为这意味着这些公司在没有充分理由的情况下,错过了一些可能非常优秀的候选人。但,最终,这是他们的选择,作为求职者,你对此无能为力。如果你遇到这样的情况,你只能笑对人生,并相应地调整你的搜索策略。
因此,总结一下:不,拥有学位并不是成为网页开发者的必要条件——在大多数情况下,没有学位也不是一个决定性的因素,我就是最好的证明!当然,这并不意味着没有学位没有好处,但这主要取决于你自己的目标和期望的道路。如果你认为学位很重要,那么无论如何,都应该去获得一个。如果你在每个阶段都希望最大化你的收入,那么是的,一个学位可能会有帮助。如果你梦想着最终能乘坐公司飞机并在首席执行官办公室管理一切,那么是的,你可能需要学位。但你可以肯定,作为一个没有学位的网页开发者,只要你投入时间和精力,让自己成为那些好公司不愿意错过的对象,即使墙上没有那张装帧好的证书。
现在你已经对成为网页开发者所需的能力、学位等方面有所了解,让我们来看看典型的工作日可能是什么样的。
考察一个网页开发者典型的一天
现在你已经对网页开发者是什么以及成为其中一员需要什么有所了解,下一个合乎逻辑的问题(记住,我们这里很重视逻辑!)就是网页开发者典型的一天是什么样的?你知道的,除了狂热的英雄崇拜之外!

图 1.5:是的,我们网页开发者就是这样酷!
这是一个复杂的问题,因为涉及许多变量。但如果我们思考一下我和我合作的那些人每天做的事情,就会开始出现一个非常粗略和普遍的结构,这个结构似乎适用于大多数网页开发者大多数时间。让我们看看这会是什么样子。
网页开发者的职责
首先,有一些常见的任务,你会在大多数日子里完成:
-
编码:好吧,我相信这不会让你感到惊讶!你显然会花很多时间编写代码来实际制作网站。但根据不同的日子,有时这将是大部分时间所在,有时它最终会成为最少花费时间的地方。
-
会议:就像蒙提·派森和他们的西班牙宗教审判片段一样,没有人能逃脱会议。这可能是与利益相关者回顾前一天的工作。也可能是与其他开发者协调。也可能是与你的经理讨论下一步要做什么任务。但无论是什么原因,会议肯定是你日常生活的一部分。
-
版本控制:这是一个我稍后会深入探讨的话题,但简而言之,版本控制是确保你的工作安全,避免丢失的方法,也是在你需要时能够回到之前工作版本的方式。这不仅仅是这些,但这就是从宏观角度看的。
-
测试:一旦你编写了代码,你当然需要确保它工作正常,因此测试将成为你工作的重要组成部分。有时你会与其他人合作进行测试,有时你会自己进行测试(而且有多个测试类型和级别,但这将是稍后讨论的话题)。
-
文档:编写各种形式的文档是一件重要的事情,不仅对你自己,也对他人来说都很重要。这可能包括解释你正在构建的内容的设计文档,或者为最终用户编写的使用文档,仅举两个例子。
-
任务管理:你参与的每个项目都会被分解成许多不同的部分,因为超过一定程度的复杂性——一个你可以很快达到的水平——项目就太大,不能一次性完成。这些部分将成为任务,管理这些任务对于管理项目来说变得很重要。这通常以任务管理系统的形式出现,比如 Jira,你将会有工单——本质上就是任务——需要处理,并且随着你处理任务,你需要更新任务的状态。
-
部署:一旦你有准备好的代码,或者甚至只是用于测试的代码,你必须将其放置在其他人可以访问的地方。这被称为“部署”代码,虽然这不一定是你每天都会做的事情,但它可能是一个频繁发生的事件。
-
代码审查:当你与其他人合作时,审查彼此的工作并不罕见。他们可能会发送他们的更改并要求你测试它们,或者只是阅读代码并寻找问题,反之亦然。
-
研究:花时间探索新技术或找出如何有效地使用它们,这将是作为网页开发者工作的一部分。构建概念验证(PoC)代码是这种研究的一种常见表现。PoC 与原型非常相似,因为它可能是也可能不是后来被塑造成最终形式的代码,并且绝对不是一开始就按照这样的目的编写的。关键的区别——虽然我事先会说明这是一个很细的界限——是,当你不确定某件事是否真的可行时,你想要证明这个想法,这时通常使用 PoC 这个术语,而原型则意味着你基本上知道它是可行的,你只是想要在真正开始之前制作一些人们可以查看和尝试的东西。
一个典型的一天
好的,这些都是你每天(几乎)都需要做的事情。但是,一个典型的一天是什么样的呢?再次强调,这是一个非常概括性的描述。没有两个开发者的日子是完全相同的,即使是同一个开发者,他们的日子也可能有很大的变化。但是,作为一个粗略的概括,这很可能与你大多数日子所经历的情况相差不远:
-
8:00 am:到达办公室或开始远程工作。检查电子邮件和 Slack/Discord/Teams 消息,并回顾可能发生的任何问题。
-
8:30 am:每日站立会议。这是一个简短的团队成员会议,你提供你的当前状态、前一天做了什么、今天计划做什么以及可能遇到的任何障碍。它被称为站立会议,因为通常是以站立的形式进行的,目的是确保会议只持续几分钟。
-
9:00 am:回顾并优先处理当天的任务,并在任务管理系统中根据需要更新它们。
-
9:30 am:深入编码。这可能意味着开发新功能、修复错误或审查他人的代码,现在是时候真正加油干,做一些工作了!
-
11:00 am:与利益相关者开会讨论项目。你可能向他们展示你最近完成的工作,或者只是讨论下一步和需求。
-
12:00 pm:午餐休息。这也是与同事社交的好时机。虽然你绝对没有必须与同事成为朋友的义务,但与他们友好相处确实能让你有更好的工作体验,所以不要忽视你这一天中的这部分时间!
-
1:00 pm:继续编码。这通常是一天中集中精力处理更复杂任务的好时机,因为大多数人在这段时间精神状态较好(当然,这并不适用于所有人,你需要找出适合自己的方式,并相应地安排你的日程)。
-
下午 2:30: 参加一个会议来规划未来的工作。许多公司使用所谓的敏捷方法,我们将在未来的章节中详细介绍。但简而言之,这是一种管理项目的方法,强调小批量生产力,称为冲刺,并允许团队自我管理(大部分情况下)。为了使这成为可能,进行冲刺规划和待办事项整理(本质上,是优先处理已知未来工作的行为)的会议相当频繁。
-
下午 3:00: 回到编码,也许这次会专注于单元测试(开发者对大型系统的小部分进行的小型、高度集中的测试,以确保它们单独工作正确)或调试(修复不完全工作的代码)。
-
下午 4:00: 更新文档,将你当天所做的更改保存在版本控制系统中以备安全,并开始规划第二天的工作。
-
下午 5:00: 开始为一天画上句号,或者根据当天的情况部署代码。
-
下午 6:00: 离开,出门,无论你更喜欢用哪个短语来表示“现在是时候忘记这个地方,享受一下乐趣了”,因为工作与生活的平衡至关重要,你应努力不把工作带回家。一旦你离开,就尽可能把它抛诸脑后,享受你的生活。
我必须再次强调,这是一个非常粗略且非常概括的概述。你的日子可能看起来与这个大不相同。你可能需要参与其他任务,对于其中一些,你可能几乎不做任何事情。有些日子,你可能将 99%的时间花在编码上,而其他日子,你可能几乎连一行代码都写不出来。即使前一天的工作结构很典型,日子肯定会有所波动,你需要接受这一点。但这个概述不应该与大多数环境中的大多数日子的事实相差“太远”。
虽然我之前至少暗示过这一点,但确实有一个关键技能,我认为任何网页开发者绝对需要具备,那就是逻辑思维。
理解一项真正无价的能力:逻辑思维
我已经讨论过作为网页开发者你需要具备的几种技能和能力,毫无疑问,它们都很重要。但在这其中,我认为最突出的还是逻辑思维。我之前也说过这一点,但让我们更详细地谈谈为什么它如此关键。
逻辑思维能力是我们作为网页开发者所做事情的核心,因为最终我们是在告诉计算机做什么。正如俗话所说,计算机会精确地做你告诉它们做的事情(当然,随着人工智能的加入,这种情况每天都在变得越来越不真实,我们当然都希望这种情况不会改变,它们不会对我们发动类似天网的攻击!)。除此之外,计算机会做得非常快——快到连你都无法理解,如果你告诉它们做错事,那么它们也会非常快地做错!
因此,你能够组织你的思路并正确指导计算机以获得所需的结果是至关重要的。这意味着能够从事实 A 开始,逻辑地推断出事实 B 是什么,然后想出如何到达那里,重复几千次,最后你可能会得到一个可以工作的网站或软件。换句话说:步骤的逻辑顺序,以及确定在代码执行时的条件下,你可以从步骤 B 分支到步骤 C,或者从步骤 B 分支到步骤 D,这是关键所在。
这就是所有这一切背后的理念,但我们可以将其进一步细分为子类别,因为逻辑有序的思考并不是一个单一的具体技能;它实际上是几个广泛技能类别的融合。
问题解决
能够解决问题是一种需要逻辑思维和理解能力的技能。对期望的结果有一个清晰的了解,并看到达到它的逻辑步骤,这是确定在那一系列步骤中问题出现在哪里,然后如何修复它的必要条件。
当然,“问题”不仅仅意味着某件事没有按预期工作!当你是第一次从头开始构建某件事时,需要关注实现目标所需的步骤,这也是一个问题。能够将复杂任务分解成可管理的部分是优秀逻辑思维者的标志。
我还应该指出,你应该能够在前进的过程中调整你的思考并获取新的事实,因为这在项目进展过程中几乎总是会发生。用一句著名的军事名言来概括:没有开发项目能够在实际开发过程中幸存,这意味着无论你的思考多么逻辑和看似完整,几乎没有人从头到尾 100%正确。你必须适应那些“哦,我没想到它会这样”的时刻,这些时刻几乎是不可避免的。
可扩展性
在构建软件时,无论是网站还是其他类型的软件,一个主要担忧是它是否能够扩展,也就是说,随着时间的推移,它是否能够处理更多的使用量。一个网站一次只能处理这么多用户,这是由许多因素决定的,包括网络、托管它的服务器,当然还有代码本身。能够进行逻辑思考将使你能够看到扩展性受限的地方。例如,如果你的代码经常从数据库中读取数据,你可能考虑缓存数据,这意味着将其存储在一个可以比数据库更快访问的位置。
扩展性的另一个方面是能够随着时间的推移改变代码。逻辑思考将使你能够以这种方式组织代码,使得未来的更改更容易,并且不会破坏现有的功能。这可能意味着抽象层(一种编码概念,试图通过一个可以比两个部分更容易更改的层将系统的两个部分分开),或者可能是一个插件机制(以标准方式添加新代码的能力,而无需更改现有代码),或者有无数其他可能性。
团队协作
这可能并不明显,但与他人互动时的逻辑思考可能非常重要,因为它帮助你理解他们所说的话,即使他们的言辞可能并不清楚。这在与其他开发者互动时尤其重要。如果你都进行逻辑思考,这几乎可以是一种神奇的经历!你甚至可能会发现自己完成了对方的(技术)想法。
用户体验
用户体验的概念是一个很大的关注点。网站是否易于使用?其功能对大多数人来说是否明显?它是否设计得让用户难以犯错?如果他们确实犯了错,是否容易从中恢复?界面是否几乎神奇地预见了用户的需求?所有这些以及更多都是用户体验(UX)的方面,在这些方面逻辑思维肯定是一个重要因素。
这尤其正确,因为用户体验(UX)往往基于可量化和可观察的事实。例如,用户研究就会发挥作用,你观察用户如何使用你的网站,看看他们在哪里会遇到困难(并让他们具体告诉你)。你通常会发现,他们的心智模型,即他们的头脑告诉他们事情应该如何工作的方式,并不总是与现实相符,你需要逻辑地分析他们的期望与你的构建之间的差异,并找出如何使它们更接近。
错误处理
任何类型的软件都是考虑到“故障模式”来设计的。故障模式是你所编写的代码可能失败的方式,你可以事先确定。例如,也许你的网站需要向另一个网站请求横幅广告,但如果那个远程系统没有响应怎么办?你的代码必须预测并处理那种可能性,而不会在用户面前让整个网站崩溃和燃烧。这是你在编写代码时就知道可能发生的事情,所以你编写代码来处理那种特定的故障模式。能够进行逻辑思考将允许你找到那些可能的故障模式并为它们编写代码,从而产生更可靠的代码和更好的用户体验。
安全
如你所知,安全在当今时代至关重要。似乎没有一天我们不会听到某个网站发生的一些重大数据泄露事件。安全非常难以做到正确,但没有逻辑思维基本上是不可能的。
然而,奇怪的是:威胁行为者——或者人们通常所说的黑客——工作的方式是进行非常逻辑的思考,但却是非常出乎意料的方式。你看,他们知道你的网站如何处理其安全令牌,例如。他们非常逻辑地理解它。然后,他们会把逻辑抛到窗外,开始尝试一些看似纯粹疯狂的事情,有时甚至如此!不幸的是,对于开发者来说,他们通常会以这种方式发现我们安全中的漏洞。或者有时他们会采取一系列单独看没问题但结合起来突然导致安全漏洞的逻辑步骤。
你唯一的希望是能够匹配他们逻辑思考和跳出思维定势的能力。但除非你能做到前者,否则你无法做到后者!
测试
与安全一样,测试需要逻辑思维,因为你要尝试确保你的代码按照预期处理事情,同时也处理当条件不完全正确时的情况。例如,如果你有一段代码用于除以两个数字,你当然会测试 10 除以 2 是否等于 5。这将是一个测试。但你还会进行另一个测试,测试如果你尝试 10 除以 0 会发生什么。代码那时会做什么?除了你需要以某种有意义的方式编写代码来处理那种情况之外,你还需要测试那种处理,而逻辑思维正是让你能够想出那些测试的方法。
数据流
数据流的概念很简单,就是数据如何在你的代码或系统的不同部分之间移动。如果你有一些描述一个人的数据——比如他们的名字、年龄和电话号码——这些数据可能需要在代码中移动以执行与之相关的各种功能。这被称为“数据流”,一个例子可以在图 1.6中看到:

图 1.6:程序不同部分之间的数据流示例
图 1**.6 展示了我为我的早期书籍之一编写的一个应用程序中组件之间的数据流——你现在不需要理解这一点;这只是提供一个数据流可能看起来像什么的例子。能够逻辑地思考数据如何通过你的代码的各个部分是关键。
而当数据可以在系统之间或可能不在同一台物理机器上运行的系统部分之间移动时,这一点尤为重要,无论是数据库还是其他代表你执行某些功能并返回结果的远程系统。
资源管理
无论你编写任何类型的软件,你都需要考虑资源利用。实际上,如果你获得了计算机科学学位,你将非常熟悉诸如大 O 符号之类的事物,它旨在允许量化这种资源利用。
计算机只能做这么多工作。它受许多因素的影响,例如它的中央处理单元(CPU)有多快,它有多少内存以及内存有多快,它的网络连接有多快以及可以通过它传输多少数据,其存储设备的效率,等等。你编写的代码以不同的程度使用所有这些资源,你必须考虑到这些资源来有效地设计代码。这意味着不仅确保你不会试图使用比你拥有的更多的资源,而且还要充分利用你运行的系统的全部潜力。
而这一切都需要逻辑思维来理解资源随时间如何被利用。
调试
当我们编写软件时,我们会有故障模式,正如我之前提到的。此外,我们还有错误,它们基本上是我们没有预料到的故障模式。用户在期望数字的地方输入字母,然后我们的代码试图对其进行数学运算可能就是这样的例子之一。以逻辑、有序的方式思考可以提高你系统地逐步通过代码以识别问题的能力。

图 1.7:错误——它们会一直咬你!
另一种方法是“试错”方法,你看到某些东西出了问题,几乎随机地做出改变,然后看看结果是什么。诚然,有时你还是会这样做,因为你可能没有所有你需要的信息来逻辑地解决问题。但逻辑思考的能力将帮助你避免这种情况,这在大多数情况下是有帮助的,因为试错通常效率较低。
作为旁白,你知道我们称之为“bug”的这些未预见的故障模式最初是从有人移除实际的虫子开始的吗?这可以追溯到 1946 年,一个名叫 Grace Hopper 的女士。当时,计算机的工作方式与今天大不相同。其中一种很大的不同是,它们不是使用晶体管——小型的电子元件——而是使用继电器。继电器本质上是一个小开关,与墙上开关类似,但关键的区别在于当通电时,开关可以移动。这些继电器今天的作用与晶体管相同:它们在我们的系统中代表 0 和 1(其中 0 表示没有电流流动,1 表示有电流流动)。这可能听起来不可能,但就是从这样一个简单的原则出发,你可以构建一个复杂的计算机系统。
但你也能从这些简单的基石中找到一些有趣的问题!一天晚上,Grace 发现她正在工作的计算机给出了错误的结果。她开始追踪这个问题(当然是用逻辑思维!)以找出问题所在。她最终找到了:其中一个继电器里有一只死去的蛾子。当通电时,蛾子阻止了继电器关闭,这意味着计算机将其视为 0 值而不是 1。这个事件的报告实际上将问题的原因列为继电器中的“虫子”;因此,一个现在广为人知的计算机术语就这样诞生了!这也是“调试”这个术语的由来!
文档
在编写各种文档时需要逻辑思维,因为如果不按逻辑编写,没有人会理解它,而且几个月后那个人可能就是你!
其中一部分是能够进入你用户的思维中,从某种意义上说。你必须理解他们将如何使用你创建的东西,然后你必须编写文档,预测他们的问题并提供他们需要的答案。
当然,为用户编写的文档只是文档的一种类型,实际上这是一种你可能永远不需要编写的类型,这取决于你工作的环境(在我公司,我们有一个团队专门负责这项任务,所以当我与他们合作时,他们主要对此负责)。你的代码中也可能嵌入文档,或者至少应该是这样的!这种类型的文档被称为注释,它解释了代码的预期用途,以及编写过程中可能出现的任何复杂问题,这些问题可能在以后需要有人了解。你为可能需要在未来某天处理代码的其他开发者编写这些注释,但你也为你自己编写它们,因为相信我,你绝对不可能记得几个月前编写的代码中的所有细节!在代码中直接有解释非常重要,而且这些注释必须清晰易懂,否则它们就不会有很大的价值(如果写得不好,它们实际上可能是有害的,因为它们可能会引导开发者走向错误的方向),所以逻辑思维在这里也非常有用!
适应性
正如我提到过几次,你正在寻求进入一个不断变化的领域,新技术不断涌现。但这里有一个小小的秘密:这些“新”技术的绝大多数实际上只是旧想法的重新组合,对其进行包装——是的,也许还略有进步——并将它们作为新事物呈现。
当你拥有一些技术知识和经验,并看到“新”的热门事物时,你也会开始看到它如何与之前的内容相关。你会更快地连接起这些点,这意味着你将能够更快、更有效地适应新事物。
决策
在做决策时进行逻辑思考可能是一个显而易见的事情,但值得提一下,因为决策可以以各种形式和规模出现。例如,你应该为给定的代码块使用哪种设计模式?或者你应该如何构建你的CSS(层叠样式表,我们稍后会详细介绍,但简而言之,它允许你将颜色、字体和定位等应用到网页上的元素),以便使其可重用且易于维护?作为网页开发者,你的生活将是一系列大小不一的决策,你越能够逻辑地思考你所拥有的选项以及选择每个选项的后果,你将越成为一个更好的决策者。
沟通
最后,所有形式的简单沟通都极大地得益于逻辑思维能力。就像文档一样——毕竟,它也是一种沟通形式——你越能清晰地表达你的思想,你将越是一个好的沟通者。这可以是技术沟通,也可以不是。例如,管理一个项目有时是一项艰巨的任务,能够逻辑地讨论当前和未来的状态是使项目可行的一项关键能力。
那都是很好的,但逻辑思维可以学习吗?
说到逻辑思维,我最后要说的就是尝试回答你现在头脑中可能存在的逻辑问题:你能学习如何进行逻辑思考,还是这仅仅是一种纯粹的天赋能力,你或者有或者没有?直截了当地说:是的,我相信这是一种你可以学习的技能,尽管这需要努力,同时,我也认为有些人比其他人更自然地拥有这种能力。或者,也许更精确地说,无论你有多少先天的逻辑思维能力,都可以随着时间的推移得到加强和提升。
我们已经讨论了很多成为网络开发者所需的东西,但事实上,“网络开发者”这个术语可以意味着几件不同的事情。没错,真的有几种不同类型的网络开发者!现在让我们谈谈这一点。
探索不同类型的网络开发者
成为网络开发者并不意味着只做一件事。好吧,至少不一定。有各种变体。这归结为三个主要类别,一个“网络开发者”可以落入其中:前端、后端和全栈网络开发者。
但在我们真正讨论这三者之间的区别之前,你必须至少对网络、互联网以及在其之上的万维网是如何运作的有一个初步的了解,所以让我们来看看这一点,这样你就有必要的基础来理解这三个类别。
互联网和万维网的基础(非常基础)
在我们深入探讨这一点之前,我必须确保你对这个被称为万维网的东西有一个基本的了解,万维网是万维网的简称(顺便说一句,这就是网站地址中的“www”的来源,例如www.google.com)。它在一个高级别上是如何工作的。
首先,重要的是要意识到互联网并不等同于万维网。诚然,多年来它对大多数人来说已经意味着同一件事,但严格来说,这并不正确。万维网只是互联网的一个组成部分,而互联网本身是由大量通过网络连接在一起的计算机组成的,类似于图 1.8所示。8*:

图 1.8:一个(小型)网络结构的示例(互联网是百万倍于此!)
网络是一个组成部分,但还有一些其他的是 Usenet(新闻组)、POP3/IMAP/SMTP(电子邮件)、Gopher(这实际上已经不存在了,但它曾是网络的先驱),以及一些现在基本上已经无关紧要的其他东西。所有这些都在互联网上连接,但网络有点特别,因为正如我提到的,它已经变成了互联网的同义词,因为现在几乎所有的互联网活动都是通过网络进行的。
但是,就我们在这里的目的而言,这些都只是历史和学究式的知识。重要的是当你将网站地址输入浏览器地址栏时会发生什么。主要需要理解的是,你的电脑正在连接到另一个外部的电脑,我们称之为google.com,你的电脑连接到由谷歌拥有的电脑。那个服务器返回构成谷歌网站的代码到你的浏览器,然后浏览器使用这些指令在屏幕上显示页面。
当你开发一个网站时,有一些代码在浏览器中运行,但也有一些代码在服务器上运行,这些代码产生了浏览器执行的代码。从某种意义上说,这种二分法就是两种类型的网络开发者发挥作用的地方:前端开发者和后端开发者。
硬币的两面——前端和后端开发者
前端开发者编写浏览器执行的代码。他们处理诸如HTML(超文本标记语言的缩写)、CSS 和 JavaScript(从现在起我将简称为 JS,这是一种浏览器理解的编程语言)等问题。我们将在接下来的章节中深入探讨这些内容,但就目前而言,关键要理解的是,这些基本上是网络的三个支柱。它们在很大程度上是浏览器知道如何与之交互的,它们是所有网站构建的基础的三种技术。
现在,因为我不想让你感到困惑,这里有一个所有这些技术的示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>A very simple example of Web technologies</title>
<style>
.description {
padding-top: 40px;
font-weight: bold;
}
</style>
<script>
function start() {
setInterval(function() {
const red = Math.floor(Math.random() * 256) + 1;
const green = Math.floor(Math.random() * 256) + 1;
const blue = Math.floor(Math.random() * 256) + 1;
document.getElementById("myHeading").style.color =
`#${red.toString(16)}${green.toString(16)}${blue.toString(16)}`;
}, 250);
}
</script>
</head>
<body onLoad="start();">
<h1 id="myHeading">Fisher Price's My First Website™</h1>
<hr />
<div class="description">This is a very simple example that shows HTML, CSS, and JS</div>
</body>
</html>
别担心,你现在根本不需要理解任何这些代码(或者它所使用的数学)——但到这本书的结尾时你就能做到了!现在的目标只是简单地给你提供一个完整、可工作的网站示例,这个示例可能对你这个读者来说有点乐趣。
实际上,为了获得完整的体验,打开一个文本编辑器,比如记事本,输入代码,将其保存为名为test.html的文件,然后双击它以在浏览器中打开。当然,结果可能没有什么值得大书特书的,但确实是一个完整的网站,并且它确实以非常基本的方式展示了那三种关键技术。
相比之下,后端开发者编写在服务器上执行的代码。你可能需要处理许多不同的技术。目前最受欢迎的包括 Java、Node、PHP 和.NET,但这远非详尽的列表。后端开发者专注于这些技术,而且大部分情况下并不直接关心最终出现在你屏幕上的内容,至少不像前端开发者那样直接。相反,他们更多地处理诸如数据库、云计算层(我们稍后会涉及)、缓存、应用服务器、会话管理、身份验证、授权(证明你是谁以及你被允许做什么)等方面。
一些开发者专注于这两个领域中的任何一个,并让其他人做方程式的另一边。在一些组织中,可能会有整个团队致力于客户端/服务器划分的每一侧,而且他们很少会直接见面(当然,他们必须沟通和合作,但除了他们必须集成工作的地方,他们基本上是独立运作的)。
然后,嗯,硬币的第三面——全栈开发者!
与此相反,也存在全栈开发者,这意味着处理前端和后端编码的网页开发者。我认为现在说有更多全栈开发者而不是专业开发者是合理的,而且确实对于全栈开发者来说,工作机会似乎也更多。然而,显然这需要学习更多并且能够做更多。
在下一章中,我将更详细地介绍这些角色,特别是全栈开发者角色,但这些描述只是大致的轮廓,可以这么说。
另一种对网页开发者进行分类的方法
除了前端与后端以及全栈的分类之外,另一个往往将网页开发者区分开来的因素是,你是某个地方的全职员工,还是“零工经济”的一员。
成为全职员工意味着在其他任何工作中都意味着的那样:你为某人工作,通常每周大约 40 小时。你将受他人摆布,你的客户、利益相关者本质上就是你的老板。他们会告诉你要建造什么,你当然会有意见,但最终由他们决定做什么,以及如何完成,这在一定程度上取决于他们。
成为全职员工有一些明显的优势。首先,工作稳定。你知道你每周或每两周都会收到工资,而且你很可能会拿薪水,这样收入就会保持稳定。像医疗保险这样的福利通常由公司处理(这也可以是负面的,取决于他们提供的内容,但至少你会有选择,而无需自己出去获取)。像 401K 计划、奖金和利润分享可能也是你总薪酬的一部分。
另一条职业道路是成为一名顾问、承包商或自由职业者,这些都或多或少是同一回事:你为某个人工作一段固定的时间,然后转到另一份工作。这条道路对许多人来说很有吸引力,因为它意味着更多的自由,以及接触到更多技术和更多样化的工作。有些人发现这非常刺激和有回报。一般来说,由于你可以设定自己的收费标准,如果有人愿意支付这个价格,那么这可能会比你在一个全职工作中能得到的要多得多。
但也有需要注意的负面因素。记得我提到过医疗保险吗?嗯,那将是你的问题;你必须出去,找到适合你的保险,并购买它。你需要担心每个月支付账单,而不是像全职工作那样直接从你的工资中扣除。此外,你还得自己处理和支付税款。这种独立工作可能比常规工作更令人沮丧,因为客户有时会对待承包商不如对待他们的正式员工那么友好(这显然取决于客户——有些客户绝对出色,只要你为他们做好工作,就会几乎像对待家人一样对待你!)。
最终,你选择哪条道路,无论是常规的全职工作还是自由职业方式,完全取决于你。你必须权衡利弊,并决定对你来说什么才是最重要的。注意,无论你选择哪种方式,你仍然可以选择专注于前端或后端,或者成为一名全栈开发人员(尽管如果你决定成为一名专业自由职业者,这可能会比全职工作限制你的机会更多,所以你可能也需要考虑这一点)。
了解有哪些类型的网络开发人员以及我们如何对网络开发人员的工作进行分类,这对确定无疑是有帮助的,因为它允许你根据你感兴趣的角色调整你的学习内容。考虑到这一点,让我们谈谈除了自学之外,你可能采取的其他一些方法来达到你的学习目标。
与他人一起学习,并从中学习
我已经多次提到过自学这个概念,至少是它的基本概念。在所有领域,仅靠自学是不足以找到工作的,但这是一个肯定可以,甚至随着时间的推移是必要的领域,因为你永远无法完全停止学习。
但在本节中,我想指出这并不是你唯一的选择。你需要弄清楚自己的个人学习风格,因为并不是每个人都能以相同的方式有效地学习。有些人可以阅读这本书,从中获得他们所需的一切。其他人可以观看 YouTube 上的大量视频,从中受益匪浅。还有一些人需要在教室环境中学习,他们必须有可以互动和提问的人。所有这些都是有效的学习方式,你永远不应该为自己的学习风格感到羞愧。相反,一旦你了解了它,就接受它,寻找适合它的选项,以最大限度地有效利用你的学习时间。
但如果你确定自学——无论采取何种形式——不是最适合你的,那么除了正规教育之外,这里还有一些你可以考虑的更多选项。
训练营
除了大学这种明显的选项之外,现在还有其他的学习机会可供人们选择,其中最著名的就是训练营。训练营本质上是一种相对较短——与大学相比——但非常密集的课程或一系列课程。它们通常持续 3-6 个月,但可能更短或更长。训练营的目标是快速教授你所需的知识,以便你能够尽可能快地进入职业生涯。这些对于某些人来说是个不错的选择,尽管可能不是每个人。

图 1.9:一个训练营,一个不会一直对你大喊大叫的军士长!
首先,训练营通常最适合那些已经有一定技术知识的人。虽然不是不可能,但要从几乎零技术知识直接通过训练营学习到足以在这个领域找到工作的知识是非常困难的。但是,如果你已经有一些知识,训练营可以快速有效地在此基础上建立。
然而,训练营并不是免费的。它们的费用可能相差很大,从几百美元到几千美元不等。这就是自学的一个优点:虽然有很多付费选项,但也有大量的免费资源。尽管如此,有一个人引导你,有一个人在你学习过程中可以提问,这也是训练营的一个好处。
对于已经工作的开发者来说,训练营也是一个可行的选择。你可能需要转换方向——比如从前端转向后端——并迅速提升技能。训练营对于这种情况来说可能非常理想,尤其是如果你的当前雇主愿意承担费用的话。
如你所料,训练营的质量参差不齐。如果你进入了一个好的训练营,你肯定会物有所值,并且能快速学到很多东西。但有可能你选择的训练营并不好,只会给你一个非常表面的理解。如果你已经是一名开发者,只是在学习一项新技能(尽管当你为此付费时这仍然不是理想的情况),这可能不是一个大问题,但对于那些期望从训练营中找到有偿首次就业的人来说,这是一个更大的风险。所以,在你报名之前,一定要做好研究。尽可能多地阅读评论,并尝试找到经历过的人,以获得直接的看法。训练营有价值,但它们也不是万能的。
个性化接触
你可以采取的另一种方法是寻找一位导师。
在 20 世纪 80 年代初,当我开始学习为老式的 Commodore 64 计算机编写的汇编语言(这是一种特别不愉快的编程语言)时,我有书籍和杂志文章可以阅读,但总觉得不够。我通过这种方式解决了很多问题,但我从未能够做到我在当时的视频游戏和其他程序中看到的那种更复杂的事情。当我找到一个特别善良的、能够做到这些事情的大孩子,并收我为徒时,这种情况发生了改变。我的学习几乎一夜之间呈指数级加速,不久之后,我开始创造出我以前认为永远不可能做到的事情。
现在寻找导师很困难,但并非不可能。这需要你与那些知道你想要学习什么的人取得联系,并接近他们,希望有人会表现出像我之前遇到的那个孩子一样的善意。这确实是一个很大的请求;我们这些人都非常忙碌,为别人抽出时间不是一件小事。但偶尔,你会遇到愿意这样做的人,这可以是一个很大的帮助。
当你还没有进入该领域时,你将如何遇到这样的人呢?好吧,有一种方式——稍后我会更详细地介绍——那就是聚会,或者我们以前称之为:用户组。有一些网站会给你列出你所在地区的此类活动,并且他们通常允许任何人参加。在那里,你会遇到各种类型的技术人员和不同经验水平的人。如果你是一个外向的人,这是一个认识他们、了解他们,并最终可能请求某人成为你的导师的绝佳机会。再次强调,这不是你可以期望从别人那里得到的东西——你当然不能对任何说“不”的人发火——但有些人确实喜欢教授他人的过程。这也是我写了这么多本书的主要原因之一。如果你能找到具有这种心态的人,那么你就找到了一个很好的资源,你不必害羞地去利用它。
在职学习
我要提到的最后一件事是在职培训。Web 开发不像一个贸易领域,你有时可以找到作为学徒的入门级职位,你将从零开始学习这门手艺。有时会有一些与这些相关的 Web 开发实习,但它们相当罕见,通常是为刚从学校毕业的学生准备的——除此之外,软件开发中的入门级工作,你不需要事先知道任何东西,几乎是不存在的。
相反,你将从第一天开始就需要了解一些东西。当然,这并不是说你必须知道所有东西——当然,你作为一项日常工作当然会学到东西——只是你不能在没有至少基础知识的条件下进入。
所有这些的底线,无论你是在考虑训练营、导师制,甚至如果你能找到的话,实习,都要始终在你能够的时候利用他人的知识。倾听他们,向他们学习,并尽可能从他们的经验和专业知识中获得尽可能多的好处。在其他的学习旅程中,除了课堂设置之外,其他人可以成为宝贵的资源,所以不要错过利用他们的机会。
介绍路线图
最后,在这本书的每一章中,都将开始于一个像图 1.10中可以看到的图表:

图 1.10:路线图
如你所见,目前它显示的并不多。它所显示的是中心起点——Web 开发者——以及关键的技能逻辑思维(在左侧),这可能是 Web 开发者真正需要的基础能力。随着我们继续阅读本书,每个方块都将被揭示,展示成为 Web 开发者所需的关键技能。
注意
虽然与主题有些偏离,但可能值得指出的是,这种类型的图表,通常被称为思维导图或有时称为创意径向图,在开发所有类型的软件时都经常看到,包括网站。它显示了更大概念各部分之间的关系,无论这个概念是像这里的 Web 开发者,还是一个网页(中心点可能是用户首次来到网站时看到的屏幕),或者可能是展示系统的不同组件以及它们如何相互连接。所以,在这里看到它并能够理解它本身就是一个有用的技能要学习!
摘要
在本章中,你对 Web 开发领域进行了简要的介绍。在这里,你形成了一个关于这份工作包含什么、需要哪些技能以及通常哪些人擅长这份工作的心理图像。
我们讨论了如何确定它是否适合你,包括你喜欢单词谜,因为这在很大程度上就是这份工作的全部内容!如果你觉得这很有趣,就像我一样,那么你将热爱这个领域!
我们还开始构建一个知识体系的基础,我们可以在此基础上继续前进,并学习了逻辑思维是一个关键要求,正如学习新事物的能力一样。你第一次看到了一些真正的代码,并且希望你在真实的网络浏览器中玩过它,看到了它的实际应用。虽然它并不宏伟,但我希望它至少让你对即将到来的内容感到兴奋。
此外,你瞥见了网络开发者典型的一天,并对不同类型的网络开发者(以及这些类型之间的重叠)有了了解。也许你甚至开始考虑你更倾向于前端/后端哪一边(尽管要准备好,因为从现在开始,你将体验到两者!)。
事实上,在下一章中,我们将更深入地探讨前端开发者和后端开发者之间的差异,以及两者在全栈开发者这一职位中的交集。这是一个值得进一步探讨的话题——我希望你会觉得它既有趣又富有启发性——这正是我们接下来要走的路!
第二章:桥梁建设——客户端(前端)与服务器(后端)开发
现在,我想深入探讨在第一章中引入的想法——即网络开发者是软件开发者——然后讨论你可能会遇到的各种职位(你可能会被惊讶地发现“网络开发者”可能是最不常见的!)。
在这个过程中,我将开始介绍构建这个技术基础所需的一些概念,例如更详细地了解 HTML、CSS 和 JavaScript,什么是事件处理器,什么是 DOM,介绍服务器端编程思想如 Node,最后看看你在这个领域可能会遇到的某些职位名称。
因此,在本章中,我们将涵盖以下主题:
-
理解客户端(前端)
-
理解服务器(后端)
-
定义网络开发者类型和职位名称
技术要求
GitHub 是一个在线服务——个人使用免费——你可以在这里存储你的代码,并且可以选择与全世界分享。GitHub,正如名字所暗示的,使用 Git 源代码仓库,或称为 SCM(源代码管理)软件。我们稍后会详细介绍这一点,但就目前而言,只需知道 GitHub 是你放置东西的地方,以及访问他人提供的内容即可。这些“东西”,通常是代码,被放入仓库,或简称 repos。现在,你可以把 repo 想象成你电脑上的一个目录。
对于这本书,请访问此处:github.com/PacktPublishing/Web-Development-Career-Master-Plan。这是你将找到这本书所有源代码的仓库。在此阶段,花几分钟时间在那里为自己创建一个账户并下载代码,以便从这个点开始准备。在创建账户并访问这本书的仓库后,你可以通过点击大绿色的代码按钮来下载代码;从那里,你可以下载一个包含仓库所有内容的 ZIP 文件。
理解客户端(前端)
当一个开发者说前端时,他们通常指的是网站的客户端。好吧,那很好,你可能会说,但这意味着什么呢?
在第一章中,我向你展示了一个非常简单的网页示例。该页面使用三种主要技术构建:HTML、CSS 和 JavaScript。这三种技术是网络浏览器最终理解的所有内容。在那章中,我还解释了 HTML 代表超文本标记语言,但我没有解释它的含义。为了做到这一点,我们必须逐字分解。
解构 HTML
为了解释 HTML,我将跳过第一个词,超文本(我们稍后会回到它)。现在,让我问你一个问题。想象一下,我在一张纸上写了以下内容:
“如果找到我的狗贝蒂,请归还!”
然后,我把那张纸给了招牌制造商,我想让他们在招牌上把那段文字做成红色。我会如何指导他们这样做?
我可能会有很多种方法来做这件事,但无论我选择哪种形式,我会称这个指令为什么?嗯,有一个术语可以提供答案:标记。我们可能会写一些像这样的事情:
“RED -> 请归还我的狗贝蒂 如找到!”
我们可以这样说,那个 RED-> 文本“标记”了内容,并为理解你的标记语言的人提供了一个指令——使这个变红。如果你事先已经同意了这种语言,那么你就可以通过这种方式传达你想要发生的事情。
另一种说法是,RED-> 文本是 元数据。元数据是一个常见的编程概念,用最简单的术语来说,就是描述其他数据的数据。我们希望底层数据——标牌上的文本——是红色的,因此我们需要一些其他数据来描述该数据的颜色。
HTML 是一种标准化的元数据语言,这就是“标记”中的 M 的来源。其理念是,要在屏幕上显示的内容以各种方式标记,以告诉浏览器如何显示它或对它进行操作。
在 HTML 中,标记内容是通过使用 标签 来完成的,并且它们具有一个常见的格式:
<some-known-tag-type>xxx</some-known-tag-type>
当你需要标记一些内容时,你必须首先创建一个 开始 标签。你可能看到的例子包括 <body>、<div>、<p> 和 <span>。<> 字符内的文本决定了标签的类型,这提供了你标记的意义。然后,接下来是你要标记的内容(在这个例子中,用 xxx 来表示它可能是任何东西)。在该内容的末尾,你添加一个 结束 标签,它与开始标签相同,但在标签名称前有一个 /。每个开始标签都必须有一个匹配的结束标签,形成一个标签对(或者有时被称为 块);否则,你可能会遇到问题,通常表现为页面没有按照你预期的样子显示在屏幕上。此外,请注意,一些标签可以是 自闭合 的,例如 <hr/>,这实际上是将开始和结束标签合并在一起。不过,这样的标签并不多。
说到“不多”,存在一组有限的、浏览器能够理解的标准化 HTML 标签。和大多数事情一样,你不必试图记住它们所有,而且有些你可能会很少使用。此外,随着时间的推移,一些标签会被弃用,这意味着它们不再被使用,或者至少不应该被使用。但是,网络浏览器喜欢保持向后兼容性,这意味着旧网站应该在新的浏览器中仍然有效,所以弃用的标签往往会持续一段时间,有时甚至很多年。以下是一个例子:
<b>I really like pie!</b>
<b> 标签已经很久以前就被弃用了,不应该再被使用,但你仍然会在最新的浏览器中找到它仍然有效。它可以使文本加粗,但它被弃用是因为这项工作可以用 CSS 更好地完成。
在我们继续前进之前,让我明确一点:标签名称不区分大小写。你可以写成 <body>,<BODY>,<Body>,甚至 <BoDy>,只要你喜欢,浏览器都会很好地处理它们。话虽如此,大多数开发者倾向于将它们全部写成小写,这也是我将在整个文档中使用的惯例。值得注意的是,软件开发中有许多东西是区分大小写的——例如 CSS 和 JavaScript——但 HTML 不是,至少就标签名称而言不是。使用哪种惯例并不那么重要,但保持一致性至关重要。当你进入专业环境时,这将是对你的一种很大期望,所以这是一个我强烈建议你养成的习惯。
现在,让我们看看网页的基本结构。
HTML 文档的结构
当我们编写 HTML 时,我们创建了一个被称为 HTML 文档的东西。这个文档是一个文件,通常带有 .html 或 .htm 扩展名。它是一个纯文本文件,可以在任何文本编辑器中编辑,例如 Windows 上的记事本——不需要任何特殊工具。HTML 文档有一个标准的结构,从高层次来看,看起来像这样:
<html>
<head>
</head>
<body>
</body>
</html>
HTML 文档本身是通过以 <html> 标签开始并以其对应的结束标签 </html> 结束来创建的。这些标签之间的所有内容代表它们所标记的内容,在这种情况下是整个 HTML 文档本身。在这里,你可以看到关于 HTML 的一个新事实:标签可以是其他标签的内容。我们说这些标签是“嵌套”的,或者说它们是其他标签的“子标签”。
在代码中,我们有一个 <head> 标签和一个 <body> 标签嵌套在 <html> 标签内,因此它们是 <html> 标签的子标签(并且由此可以反过来,可以说 <html> 标签是 <head> 标签的父标签)。
注意
从现在开始,除非有明确的原因,否则我不会列出结束标签。你应该假设存在一个结束标签,就像这里的 </head> 和 </body> 一样,除非另有说明。
<head> 标签定义了 HTML 文档的两个部分之一:头部。这个部分包含不显示在屏幕上的内容。它更像是浏览器可以用各种方式使用的信息。例如,当我们学习 CSS 时,你会看到它通常被放在文档的头部。JavaScript 代码也是如此。你可能会在头部看到其他标签,例如 <title>,它提供了你将在浏览器顶部看到的标题,或者 <meta>,它向浏览器提供了有关页面的一些元数据(例如,当你查看你已收藏的网站时通常看到的图标可以在这里定义)。然而,这些都并非必需,因此我没有在这里展示。从技术上讲,头部本身也不是必需的,但始终包含它是良好的实践,即使你什么也不放进去。
<body> 标签定义了文档的主要部分——包含将在屏幕上显示的内容的部分。而且——你猜对了——文档的这个部分就叫做主体!你可以放在那里的最简单的内容就是纯文本。你不需要以任何特殊的方式标记它;浏览器会直接以原样显示它。
但真正的威力开始显现,是在你开始在正文中使用其他 HTML 标签的时候。让我们来看一个更高级的例子:
<html>
<head>
<title>Babylon 5 Characters</title>
<style>
* { box-sizing: border-box; }
#nav {
float: left;
width: 30%;
height: 300px;
background: #606060;
color: #ffffff;
padding: 20px;
}
#characterBio {
float: left;
padding: 20px;
width: 70%;
background-color: #b0b0b0;
height: 300px;
}
</style>
</head>
<body>
<h1>Babylon 5 Characters</h1>
<hr />
<p>Here is a list of characters from the best television show ever: Babylon 5! Click them to read their bio.</p>
<div id="nav">
<ul>
<li>Sheridan</li>
<li>Delenn</li>
<li>Londo</li>
</ul>
</div>
<div id="characterBio">
<h2>Sheridan</h2>
<p>John Sheridan is an EarthForce captain who took control of Babylon 5 in 2259.</p>
</div>
</body>
</html>
这里发生的事情还有很多,但让我们从最基本的部分开始,一步一步地来,在这个过程中介绍一些新的标签和概念。别担心——实际情况并没有看起来那么糟糕!
头部
我们从之前的 <html> 标签开始,然后是一个 <head> 标签来定义文档的头部。在头部内部,我们有一个 <title> 标签,它定义了文档的标题(通常在浏览器标题栏中看到,也是你为该网站创建书签时的文本)。<style> 标签是我们放置 CSS 的地方,它告诉浏览器我们希望内容如何显示——但现在我们会跳过这一点。
HTML 文档的头部包含有关文档的元数据——在浏览器中不可见的内容(除了标题,通常在浏览器窗口的标题栏中显示)。然而,这些内容可能会影响显示效果,比如通过导入 JavaScript 来创建内容,或者我提到的 CSS,但我们会稍后再讨论这一点。你有时会在头部找到实际的 <meta> 标签,就像你在第一章中看到的例子一样,来定义文档使用的字符集、视口(基本上就是浏览器窗口)的大小以及更多。但关键点是,这些都不是旨在明确显示的内容。这就是文档主体的作用。
主体
在 </head> 标签之后,我们有了开头的 <body> 标签;这是我们开始显示内容的地方。这个内容从 <h1> 标签开始,它用于标记文本作为标题。有几个 <h> 标签,从 <h1> 到 <h6>。你可以把这些想象成写一本书的大纲:<h1> 标签是你的顶级主题,<h2> 标签是 <h1> 标签下的子部分,以此类推,数字越大,文本越小。
在标题下方是 <hr> 标签,这是一个罕见的自闭合标签。这个标签在标题下方画一条线,或者说是水平线。
接下来是一个<p>标签,代表段落。其中的文本将一直显示,直到它太宽以至于浏览器窗口无法显示,此时它将流到下一行,依此类推。任何在</p>标签关闭之后的内容都将自动在新的一行下面开始。这在构建网页时是一个常见的范式:我们主要关注定义像这样的水平部分,而<p>只是实现这一点的众多方式之一。
接下来的<div>标签是创建水平部分的另一种方式,这可能是最常见的方式。<p>和<div>之间的区别在于语义。这里的术语语义指的是标签的含义,它定义了标签内内容的用途和角色。<p>标签的目的是表示“这是一个段落,一段文本”,而<div>则没有这样的固有含义。但在实践中,它们的功能相同,并提供相同的视觉结果,所以在很多情况下,使用哪个并不重要。
标签属性
不论差异如何,这个<div>标签提供了一个新事物的例子:id。这给标签赋予了一个名称,我们可以在代码中稍后使用这个名称来对该标签(或元素,当我们谈论网页上的成对标签及其所有内容时,我们倾向于这样称呼)进行操作。
属性总是以name=value的形式存在。因此,在这里,id是属性的名称,而nav是赋予它的值。请注意,在编程的一般情况下,当我们有一个不是数字的字符序列时,我们实际上称之为字符串,并且几乎总是用引号括起来。
回到标记
回到标记,这里的<div>标签定义了我们的导航部分,因此有id值。嵌套在这个<div>内部的是另一个常用的标签,用于生成项目的项目符号列表,即<ul>。这代表<ol>,它生成一个列表,列表中的每个项目都会自动编号。列表中的任何一种类型的每个项目都将放入一个<li>,或列表项,标签中。
在适当地关闭<li>和<div>标签之后,我们遇到了另一个<div>标签,这个标签定义了一个我们称之为characterBio的区域。这就是我们将展示所选角色的传记信息的地方。这个页面的想法是用户可以点击一个角色并查看他们的传记。然而,请注意,它并不像这里这样完整,这是有意为之。我们将在稍后在此基础上添加交互性。
在characterBio <div>标签内部,我们有一个<h2>标签,用来显示比普通文本稍大的角色名字,但并不像顶部的主<h1>标题那样大。然后我们还有一个<p>标签,其中包含角色的生物文本。
最后,我们关闭<body>和<html>标签,我们就有一个完整的网页了!
在您选择的网页浏览器中打开该文件后,您应该看到图 2.1 中所示的内容:

图 2.1:我们 HTML 的结果(是的,我是个大粉丝!)
好的,我之前承诺过我们会回到 HTML 的超级文本部分,现在让我履行这个承诺吧!
那个超文本部分怎么样?!
HTML 中的术语超文本指的是一个 HTML 文档能够链接到另一个文档或它所依赖的资源的能力。正是这些链接构成了网络。这些链接可以通过几种方式创建,并且它们在很大程度上归结为一些新的标签:
-
<a>:这个标签创建了一个指向另一个页面的显式链接。有了它,你可以写出以下内容:<a href="https://www.google.com">Go to Google</a>与大多数标签一样,你有一个打开标签和关闭标签对,中间有一些内容。在这种情况下,内容是用户将在屏幕上看到的文本,并且可以点击的文本。
href属性(简称 HTML 引用)提供了点击时导航到的 URL。 -
<img>:这是在页面上显示图片的方式(至少是最简单的方式):<img src="img/company_logo.png">这个标签有点好奇,因为没有关闭的
</img>标签,而且你也不需要自己关闭它(实际上,如果你这样做,一些浏览器可能无法正确显示图片)。无论如何,只要图片文件company_logo.png与 HTML 文档位于同一位置,当页面渲染时就会显示出来——也就是说,显示在屏幕上。或者,你可以在src属性(简称“源”)中指定一个完整的 URL,这允许你从其他位置显示图片。 -
<script>和<link>:这些允许你链接到 JavaScript 文件和 CSS 文件,分别(技术上,<link>允许你链接到其他类型的文件,但 CSS 文件通常是它的用途):<script type="text/javascript" src="img/my_code.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" />在
<script>标签的情况下,你将始终有一个关闭的</script>标签,其中没有内容介于它和打开标签之间。type属性指定了语言,尽管在这个时代,它几乎永远不会是除了text/javascript之外的其他内容。对于
<link>标签,rel定义了 HTML 文档与其所链接的文件之间的关系,并且像<script>标签的type属性一样,尽管它支持其他值,但它几乎总是stylesheet。这些天,type属性也几乎总是text/css,你已经知道href是什么了。注意,与
<script>不同,<link>标签将始终是自闭合的,如下所示。与<img>标签一样,你可以指定一个与 HTML 文档位于同一位置的文件,如下所示,或者你可以指定一个完整的 URL 来链接到其他地方的文件,无论是整个其他服务器,甚至是你自己无法控制的资源。当你像这样链接到 JavaScript 或 CSS 文件时,我们说该文件正在被导入,我们称这些文件为外部脚本或外部样式表。我们可以将它们与定义在
<style>块中的样式表进行比较,这是内部的,因为它直接嵌入到 HTML 文档中。对于 JavaScript 的<script>块也是如此。
虽然有其他方式链接到其他 HTML 文档和资源,但这些无疑是最常见的。
到目前为止,你已经听我提到了 CSS 几次,那么让我们跳转到这个主题,看看它究竟是什么!
CSS 剖析
我不会对你撒谎:CSS,即层叠样式表,可能会有些困难。并不是它背后的基本概念很难——它们相当简单——但是内容很多,有时即使是经验丰富的开发者也会遇到困难。然而,就像我们一直在讨论的每一件事一样,如果你一点一点地学习,不试图一次性学习所有内容,那么这是你可以处理的!
在开始时,CSS 相对简单易懂。如果 HTML 大致代表了你的房屋结构——墙壁、墙体内的梁、屋顶、地基等等——那么 CSS 就像是后来到来的画家,让它变得漂亮!对于你的 HTML 文档中的每个元素,你都可以使用 CSS 告诉浏览器应用哪些样式。
假设你的文档体中有以下 HTML 代码:
<h1>Luke Skywalker</h1>
<div id="relatives">Anakin, Padme, Leia</div>
<p class="friends">Obi-Wan, Han, Chewie</p>
现在,假设我想将伟大的绝地大师的名字设置为红色,他亲戚的名字设置为绿色,他朋友的名字设置为蓝色。我可以通过在文档的<style>标签内编写以下 CSS 来实现:
h1 { color: red; }
#relatives { color: green; }
.friends { color: blue; }
这一小段 CSS 展示了你需要内化的大多数核心概念,从选择器的概念开始。简单来说,你写的每一行 CSS 都将应用于页面上的一个或多个元素,而它将应用于哪个元素则由选择器决定。在这里,我展示了三种最常见的选择器——标签选择器、ID 选择器和类选择器:
-
标签选择器适用于特定类型的所有元素或标签。在这里,通过编写
h1 { color: red; },我表示我想让页面上的所有h1标签都被设置为红色。 -
一个 ID 选择器,如
#relatives { color: green; }所示,表示具有 ID 为relatives的元素应该被设置为绿色。由于id属性用于标识具有特定名称的元素,因此在一个页面上只能有一个具有给定 ID 的元素(如果你不小心重复使用了 ID,那么在阅读 HTML 时最后出现的元素将拥有该 ID,所以你可以想象,如果出错,这可能是某些隐蔽错误的来源)。 -
类选择器——例如,
.friends { color: blue; }——表示任何具有class属性值为friends的元素将被设置为蓝色。你可以将其应用于任意多个元素。
这三行代码中的每一行都定义了我们所说的color来使文本着色。给定类的选择器是开大括号前的文本。在这三个案例中,它是什么类型的选择器取决于我们开始的字符。如果我们以哈希(#)开头,这意味着它是一个 ID 选择器。如果我们以点(.)开头,这意味着它是一个类选择器。如果它只是 HTML 标签的名称,开头没有特殊字符,那么它就是一个标签选择器。
但样式是如何定义的呢?我们通过将样式属性设置为期望的值来定义样式,其中颜色是大量可能属性之一。有一些属性与排版相关(font-size、font-family和font-weight),一些与在页面背景中放置内容相关(background-color和background-image),一些用于在事物周围放置边框(border-color、border-radius和border-style),用于设置元素大小的属性(width和height),用于更改元素几何形状的属性(transform),用于隐藏和显示元素的属性(display和visibility),以及用于更改元素位置的属性,从它通常渲染的位置(left、top、position和z-index)。这还远非详尽的列表。我只是快速数了一下,目前看起来有超过 200 种不同的样式属性可用。哇!
当我们谈论 CSS 时,样式表部分是相当显而易见的,您刚刚也看到了:这三个样式类及其相应的选择器组成了一个样式表。但级联部分又是什么呢?好吧,当您对一个给定元素的应用样式属性有多个样式时,它就派上用场了。这可能发生在您链接到多个样式表(因为无论您是否在<style>标签中也有自己的样式,您都可以这样做)或者多个样式类匹配的情况下。例如,假设我将h1样式类选择器更改为p选择器。在这种情况下,哪个类——以及由此延伸的颜色——将适用?文本会因为与<p>标签的匹配而变成红色,还是因为类名匹配而变成蓝色?
答案由一个级联算法决定,其中算法是一种作为代码片段实现的程序,用于解决特定问题或执行特定任务。这个算法使用的级联规则在实践中可能相当复杂,但简而言之,以下情况发生:
-
生成一个可能应用于该元素的任何选择器的列表。
-
这些选择器按照其重要性的顺序排序,这基本上意味着那些在其他人之后定义的选择器具有优先权,尽管其他因素也可能产生影响。
-
选择器是基于
<p>标签选择的,但还有一个是基于id值应用的,因为id值在文档中是唯一的,所以它被认为更具体,因此它将覆盖基于元素是<p>标签而应用的值。 -
根据样式定义出现的顺序做出最终决定,这基本上与基于重要性的决定相同,但稍微直接一些。
这涵盖了 CSS 的基础知识,但如我之前所说,CSS 可能很难,因为它有很多内容。所以,肯定比我说的还要多,对吧?确实如此,我会在接下来的内容中至少触及一些。
其他 CSS 功能
CSS 可以做的不仅仅是改变颜色:
-
有一些属性可以用来定义页面的布局,例如。使用这些属性,你可以将页面划分为一个网格,然后定义各种元素应该位于该网格的哪些方块中。
-
你还可以使用 CSS 来使页面在浏览器窗口大小变化时“重排”。这允许你拥有一个针对较小移动设备屏幕以及较大桌面屏幕优化的页面布局,而无需编写任何代码。
注意
CSS 通常不被认为是代码——它更像是 HTML,因为它是一种元数据。然而,考虑到 CSS 今天的强大功能和其可能变得多么复杂,我认为我们现在正处于代码和非代码之间的灰色地带。
-
CSS 还提供了可以让你以各种方式在页面上动画化元素的属性。你可以使用 CSS 单独使页面交互式,而无需编写任何代码(或者至少是明显的代码)。你可以使元素在用户悬停时增长,在点击时旋转,等等。
-
除了在
<style>块中定义你的样式,通常在 HTML 文档的<head>区域,你还可以在给定的元素本身上定义 CSS 内联 ——也就是说,在该元素上:<div style="font-size:40pt;">Luke Skywalker rules!</div>style属性在大多数元素上都是可用的,它允许你仅对该特定元素应用样式。回到层叠的概念,这种类型的样式将(几乎)总是优先于在<style>块中定义或在外部样式表中链接的任何样式。
除了常见的内联样式之外,其余的都是更高级的主题,而且由于这里的目的是只给你一个 CSS 的基本概念,我不会深入探讨它们。但我希望你知道它们存在。我说 CSS 可能很难时,并不是在开玩笑!CSS 有很多内容,它已经变得非常强大。但前一部分描述的是核心概念。
现在我们已经了解了 HTML 和 CSS,让我们深入到真正的、明确无误的编程领域:JavaScript!
拆解 JavaScript
如果你把 HTML 视为房子的蓝图,把 CSS 视为房子的装饰,那么你可以把 JavaScript 视为当你按下按钮时使车库门打开的东西,或者当你拉动开关时天花板风扇和灯光亮起的东西。
HTML 和 CSS 是我们所说的 声明性 语言,这意味着你写下你想要发生的事情,浏览器就会去实现。例如,如果你写下 <hr>,浏览器就会知道你想要在屏幕上有一条线。如果你在样式表中写下 color:red,它就会知道你想要应用该类选择器的元素变成红色。
相比之下,JavaScript 是我们所说的 命令性 语言。这意味着你必须告诉计算机在每一步要做什么。你不能像 HTML 和 CSS 那样简单地声明你的意图——你必须具体详细地说明浏览器(以及计算机)必须做什么。大多数人可能会倾向于说 JavaScript 是真正的编程,而 HTML 和 CSS 不是。这到底是不是真的有点值得商榷,但 JavaScript 的本质是不变的:你必须为计算机编写指令,它会按照你的指示去做,不会多做也不会少做。
展示 JavaScript
作为例子,让我们在我们之前查看的代码中添加一行,就在最后一个 <div> 标签之后:
<button type="button" onClick="document.getElementsByTagName('h1')[0].style.color='yellow';">Click Me</button>
这段代码展示了几个方面。首先,有一个新的 HTML 标签:<button>。正如其名所示,它允许我们在页面上添加按钮,用户可以点击它们来执行某些操作。type 属性允许我们选择三种按钮类型之一:button、reset 或 submit。后两种在表单的上下文中使用,表单是另一个 HTML 元素,代表一系列输入字段,例如,在网站上创建账户时填写个人信息。然而,基本的 button 类型是一种更通用的类型,它不必与表单相关联,就像这里的情况一样。
前面的代码还向我们展示了一个新概念:事件处理器。事件处理器是响应某些事件执行的 JavaScript 代码片段,这是页面上发生的事情。这可能是在这里用户点击按钮,或者我们可能想要在页面首次加载时执行某些代码,这也可以通过事件处理器来实现。在这里,我们想要处理的具体事件被称为“click”,由于事件处理器是响应事件执行的,所以我们说它们是在该事件执行时发生的,这就是为什么我们用 onClick 来指定这个事件处理器。
事件处理器可以是内联的,也可以是元素外部的。当它是外部的时候,它可能位于页面上的某个 <script> 块中(或者可能是一个我们导入的外部 JavaScript 文件)。然而,在这里它是内联的。onClick 属性的值仅仅是当点击事件发生时我们想要执行的 JavaScript 代码。让我们具体看看那段代码:
document.getElementsByTagName('h1')[0].style.color='yellow';
这展示了 JavaScript 中的几个关键概念,首先是对象。对象简单地说就是一组被视为单一单元的数据。例如,想想你自己:你有一个名字,一个姓氏,年龄,电话号码等等。如果我们把所有这些信息都存储在计算机的内存中,我们可以在我们的代码中把这一组信息称为BookReader。如果我们只想从这一组信息中获取名字,我们可能会写BookReader.firstName。点号代表我们告诉 JavaScript 我们想要一个单一的信息——一个BookReader。
在前面的代码中,document就是这样一组信息。碰巧的是,这是浏览器为我们创建的,用来表示 HTML 文档本身,以及所有放入其中的东西,以及关于它的所有信息。我们可以用这个对象做很多事情。例如,我们可以向它提出关于我们文档的问题。我们可以通过调用方法来实现这一点。方法是一个附加到对象上的 JavaScript 函数。但是,我听到你在问,“什么是函数?”让我们现在就讨论这个问题,因为它是一个很好的问题!
理解函数
但你可能会问:“什么是函数?”嗯,函数就是一段我们给它起名字的 JavaScript 代码。我们这样做是为了能够执行那段 JavaScript 代码,可能很多次,通过它的名字来调用它。
每次你调用 JavaScript 函数时,它都有以下形式:
function_name(arguments)
函数名是我们分配给那段 JavaScript 代码的名字,参数是可选的,是函数完成其工作所需的信息片段。当它们存在时,我们说它们被传递给函数。
函数可以是它们自己的独立事物。一个例子是如果你写下以下内容:
<button type="button" onClick="alert('hello');">Click Me</button>
在这里,alert()函数在浏览器中自动存在,它就像独自漂浮在那里一样。
然而,函数也可以被附加到对象上,这只是一个说法,意思是对象的某个特定属性是一个指向函数的引用。在我们的代码中,getElementsByTagName()就是这样一种函数,所以我们说它是附加到它所附加的对象上的方法。请注意,当我们像这样在文本中提到函数的名字时,通常会在括号中包含一个空集合,这是我在整本书中会使用的惯例。
这个方法允许我们要求document对象给我们一个 HTML 文档中所有<h1>标签的列表,这就是为什么将'h1'作为参数传递给方法的原因。在这里,'h1'必须用引号括起来——在这种情况下是单引号(在大多数情况下可以互换使用)——因为它是一个所谓的1234,尽管我们不需要单引号,因为那不会是一个字符串,而是一个数字,数字不需要被引号括起来。
getElementsByTagName()方法返回一个值,就像函数可以做到的那样。它返回的值可以是许多不同的事物,但在这个例子中,它返回的是所谓的数组。数组简单地说就是一系列值。这些值可能是字符串、数字或对象(甚至可以是函数,但这是一个更高级的话题)。数组允许我们通过一个索引来获取它的值,即数组中的位置。
虽然在谈论数组时,我略过了另一个可能在某些方面是 JavaScript(以及编程的一般)中最重要的概念,那就是变量。现在让我们来谈谈这个,这样你就能更好地理解数组是什么。
理解变量
简单来说,变量是命名的东西,它们指向计算机内存中的某个位置。
例如,假设我们有一个名字列表:Frank、Traci、Andrew 和 Ashley。我们可以把它们放在一个数组中,就像getElementsByTagName()返回的那种类型。这允许我们通过使用它的索引来访问每个名字:Frank 在位置 0(因为数组总是从 0 开始计数,而不是像你预期的 1),Traci 在位置 1,Andrew 在位置 2,Ashley 在位置 3。
那么做所有这些的代码是什么样的呢?其实很简单:
var myArray = [ "Frank", "Traci", "Andrew", "Ashley" ];
在这里,我们创建了一个名为myArray的变量。那么它指向的那个内存,就是我所说的变量本质上是什么?坦白说,那不是我们的问题!计算机将为我们处理这个问题。我们只需要知道,当我们想要访问这些数据时,我们会使用myArray。
在myArray之前的单词var被称为var,代表变量。我们在这里说的是,“嘿,JavaScript,我在这里声明了一个名为myArray的变量。”然后它会为我们找到一些内存,并确保myArray指向它。
声明一个变量意味着向计算机介绍它,但还有一个相关的概念叫做定义,或者定义一个变量。变量的定义是可选的;你可以声明一个变量而不定义它(也就是说不给它一个起始值),然后稍后给它一个值。但通常我们会同时看到声明和定义,就像这里的情况一样。等号就是这样做的:它说,“嘿,计算机,myArray的值等于这个。”
此外,请注意,在 JavaScript 代码行末尾放置分号是可选的,但养成这个习惯是个好习惯。这样,我们可以清楚地看到一条语句(即对计算机的一个指令)在哪里结束,以及下一条语句从哪里开始(而且也有一些非常微妙但幸运的是非常罕见的情况,如果不放置分号可能会引起问题,尽管这种情况很少,但通过良好的习惯完全避免这种可能性是更好的)。
我们给出的值是一个数据数组,我们通过将我们想要放入数组中的值用方括号括起来来表示。如果我们只是定义一个字符串作为另一个例子,那么它们就不需要了,对于数字或对象也是如此(对象使用大括号代替括号,但这更高级,所以我们现在先不考虑它)。例如,这里有一个字符串的例子:
var myString = "Hello";
这里有一个数字的例子:
var myNumber = 123;
如您所见,它有相同的基本格式:var关键字,后面跟着变量的名称,然后(可选地)一个等号和要放入变量的初始值。
回到我们的数组,一旦数组被声明和定义成这样,我们可以通过写下以下内容来访问名称:
myArray[2]
这将从数组中索引 2 的位置获取值,这将是一个Andrew,因为,再次强调,数组从 0 开始。
现在你有了基础,让我们看看之前的事件处理器代码中的所有内容是如何运作的。
更好地理解事件处理器
带着这个知识,让我们回到之前的事件处理器代码:
document.getElementsByTagName('h1')[0].style.color='yellow';
因此,这里我们调用的是document对象的getElementsByTagName()方法,要求它返回页面上的所有<h1>标签。这个方法返回一个数组,我们可以通过括号来访问数组的元素。因为我们知道在这个数组中我们只会得到一个标签,因为我们页面上只有一个<h1>标签,所以索引总是 0。这个结果是对那个标签的引用,然后我们可以访问它的属性。属性可以是读取的或者写入的(在大多数情况下),在这种情况下,我们正在写入一个:style。这对应于应用于该标签的样式表,一开始使它变成红色。然而,我们想要将颜色改为黄色,以便我们可以设置color属性的值。
你可以写myH1Tag.style.color的原因是因为一个对象的属性可以指向其他对象。这被称为嵌套,你可以嵌套到你需要的深度。我们称这种对象的嵌套为层次结构。在这里,myH1Tag是一个对象。在这种情况下,style属性指向另一个对象。最后,color是那个嵌套对象的属性。你有时会在 JavaScript 中看到长链的 XXX.YYY…代码,当你钻入嵌套对象的层次结构时。但不管层次结构有多深,你看到的每个 XXX.YYY 只是意味着“对象 XXX 的 YYY 属性。”
有时候,像那样的事件处理器这样的单行代码可能很难理解,因为实际上正在执行多个步骤,而且往往很难跟踪每一个步骤,尤其是在你很久没有阅读 JavaScript 代码的情况下。可能通过将单个步骤分解成单独的行来重写代码会更有意义,如下所示:
var tagsArray = document.getElementsByTagName('h1');
var myH1Tag = tagsArray[0];
myH1Tag.style.color = 'yellow';
现在,你可以更清楚地看到代码正在执行的三步离散操作:获取 <h1> 标签的列表,并声明一个名为 tagsArray 的变量来指向它(这是通过方法调用返回的数组定义的),然后获取该数组的第一个元素,并创建一个名为 myH1Tag 的变量来引用它,最后将其 color 样式属性设置为黄色。
我知道这需要吸收很多信息,尤其是如果你之前从未做过任何编程的话。但是,好消息是:你已经接触到了 JavaScript 和编程背后的许多基本概念!随着你通过这本书的进展,你将学到更多,但这对现在来说是一个很好的开始。
到目前为止,我们需要讨论另一个与 JavaScript、HTML 和 CSS 都密切相关的话题,那就是 DOM。
创建 DOM
DOM,即文档对象模型,是浏览器解析 HTML 文档的具象结果。当我们说某个东西——在这个例子中是浏览器——读取一些数据——在这个例子中是 HTML 文件——并以某种方式处理它时,我们就是这样称呼的。它是一组存储在计算机内存中的对象集合,代表该文档。
让我们考虑以下 HTML 文档:
<html>
<head>
<title>DOM Example</title>
<style>
.cssDiv2 { color: #ff0000; }
</style>
</head>
<body>
<div id="divGreeting">Hello!</div>
<div class="cssDiv2">Welcome to the book!</div>
</body>
</html>
当浏览器解析该文件时,它会生成一个 DOM,如图 图 2.2 所示:

图 2.2:DOM 的图形表示
你所看到的是一个位于底部的 <html> 框,在概念上就像一棵树的树干。从它“生长”出来的每一件事都是一根树枝,然后任何没有东西生长出来的框就像一片叶子(在树语中我们称之为 节点)。所以,你可以看到从 <html> 出发的一根树枝通向 <head> 和 <body>,因为它们是 <html> 的直接子元素。
你还会注意到图中有一个写着 #text: 的框,它是 <html> 的子元素;你会在整个图中看到更多这样的框。这些代表 HTML 文档中不包含在任何标签内的文本内容。
为了理解这一点,看看源 HTML 代码。看到哪些行是缩进的吗?嗯,造成这种缩进的空格也成为 DOM 树中的一个元素,这就是那些 #text: 框的内容。对浏览器来说,这些空格是和标签内任何内容一样有效的内容,即使它们可能不会在页面上显示。一般来说,HTML 文档中的任何空白都不会在屏幕上渲染,至少不会超过一个空格字符(换句话说,多个空格字符会合并成一个空格字符)。当然,有方法可以使多个空格显示出来,但像缩进这样的东西通常默认是不会显示的。
除了这些,基本想法是每个标签及其内容都成为 DOM 树中的元素或节点,更复杂的 HTML 将产生更复杂的树结构。但正是这棵树允许 CSS 和 JavaScript 做它们的事情,更不用说浏览器本身了。当你需要通过 JavaScript 获取 DOM 节点的引用来对其进行操作时,浏览器会咨询这棵树来为你找到它。当你需要指定一个 CSS 选择器来应用样式类时,浏览器会使用这棵树来完成其工作。稍后,你会了解到 DOM 可以在最初渲染后通过 JavaScript(以及 CSS)进行更改,当这种情况发生时,浏览器将相应地更新树(可能添加新的分支或节点,或者删除其他节点,具体取决于更改的内容)。DOM 使所有这些成为可能。
作为一名网页开发者,你通常不会直接处理 DOM,在这个意义上,你不会看到像之前那样的图表,至少不是完全那样。但它始终存在,无处不在,允许浏览器——以及通过扩展 HTML、CSS、JavaScript,最终是你!——做它们的事情。因此,了解它是什么以及如何在你的脑海中构思它是很重要的。
现在你已经有机会了解 HTML、CSS、JavaScript 和 DOM,让我们谈谈当用户在浏览器的地址栏中输入一个地址并按下 Enter 键时会发生什么,因为这是理解的关键概念。而且其中还有更多内容超出了表面现象!
理解客户端-服务器模型
当你作为用户在浏览器的地址框中输入一个地址并按下 Enter 键时,首先发生的事情是浏览器向 域名服务器 (DNS) 发出请求。DNS 服务器是由诸如 互联网服务提供商 (ISPs) 或大公司等实体维护的特殊类型的计算机。DNS 服务器的唯一任务是充当电话簿,将人类友好的 URL(如 google.com)映射到 IP 地址(例如,192.158.1.232)。
一个 互联网协议 (IP) 地址是每个连接到网络的机器所分配的唯一地址。它就像你手机的电话号码:如果有人知道它,他们就可以连接到你。但你不会想要记住 192.158.1.232 是 google.com 的地址——如果你愿意,那就是电话号码。因此,相反,DNS 服务器提供这种映射,使我们人类更方便。
一旦 DNS 服务器响应了 IP 地址,浏览器就能够连接到与该 IP 地址对应的服务器,并请求 URL 中指定的 HTML 文档。如果没有指定(如 google.com),则返回默认文档。
浏览器随后读取那个 HTML 文件并解析它,从中创建 DOM。然后,它为 HTML 文档中引用的每个资源单独发出请求,例如在<img>标签中引用的图像、在<link>标签中引用的外部样式表,或在<script>标签中引用的外部 JavaScript 文件。换句话说,它向服务器请求所有使该网页正常工作并正确显示所需的东西。
一旦所有这些资源都被检索到,浏览器就会将内容渲染到屏幕上,你就可以看到一个可以查看的网页了!
现在,尤其是在 JavaScript 的混合使用中,这可能并不是结束。当用户在网页上执行各种操作时,例如点击按钮或滚动,JavaScript 代码可以对这些操作做出响应,并且这些代码可能会对 DOM——以及你在屏幕上看到的内容——进行更改,或者它可能会向服务器发出请求。这些请求可能是获取随后在页面上显示的数据,或者可能是一个请求整个新的 HTML 文档,这将替换当前显示的内容。在这些情况下,我们称之为导航从一个页面到另一个页面。
我已经提到了几次服务器这件事,你知道它是一台响应你请求的计算机,但还有更多。那么,让我们稍微深入一点,好吗?
理解服务器(后端)
如我之前所述,客户被认为是等式的前端;后端是我们对服务器这一概念的理解。服务器这个词有两个含义——一个是物理的,另一个不是。
当从网络中心的角度来看待事物时,服务器是一台物理计算机,与客户端运行的计算机分开。它们通过网络进行通信。
然而,服务器也是一种在物理计算机上运行的软件形式,正是这种软件回答了客户端的请求。换句话说,当我们说“网页服务器”时,这可能意味着客户端连接的物理机器,以便它可以查看网页,这可能意味着在物理计算机上运行的软件,它响应网页的请求,或者它可能意味着两者的结合。
因为服务器在某种情况下至少是一种软件,这也意味着客户端和服务器可以运行在同一个物理机器上,正如你将在下一节中看到的。
这种服务器软件有各种不同的形状、类型和大小,包括用于创建它的编程语言。Java、Python、.NET、Node和PHP是编写服务器软件的常见语言。每种语言都有其优点和缺点。Java、Python 和.NET 是非常健壮的语言,提供了大量的预编代码,你可以使用这些代码来节省你自己编写代码的时间。Node 和 PHP 也是如此,但 Node 是一个允许你在服务器上使用 JavaScript 的平台,而 PHP 是一种语言。
无论你使用什么语言,无论你在什么平台上构建,你都需要提供处理给定类型请求的代码。为了演示这一点,我想使用 Node 编写一个非常简单的服务器。我认为这是展示给你看的最合适的选择,因为我们之前稍微了解了一些 JavaScript,所以它可能不会像其他人那样陌生。
Node 入门
要开始,你需要安装 Node 本身。幸运的是,这非常简单!在你的浏览器中转到nodejs.org,你会看到一个类似于图 2.3所示的页面:

图 2.3:Node.js(或仅 Node)的主页
从这里,点击写着下载 Node.js (LTS)的大绿色按钮。这将下载一个安装程序,然后你可以运行它来安装 Node。
注意,你也可以通过以下链接下载最新版本,该链接位于以“想要更快地获得新功能?”开头的句子中。区别在于,按钮为你提供的是 LTS(长期支持)版本,这通常意味着它更稳定,因为其中包含的新功能可能尚未完全准备好使用。然而,对于我们在这里的目的来说,这不应该有任何区别,尽管值得注意的是,我确实倾向于在我的机器上安装最新版本。所以,如果你遇到代码无法按预期工作的问题,并且你安装了 LTS 版本,请尝试使用最新版本,看看是否可以工作。
安装程序完成后,打开命令提示符并输入以下命令:
node --version
你应该会看到以下输出:
v18.18.2
数字可能不同——这是我写的时候的情况——但它应该与网页上说的相匹配。然而,如果你得到这样的数字,这意味着Node已安装并准备好使用。
使用 NPM 创建项目
要开始创建服务器,我们将使用与 Node 配合使用的工具,即NPM。这代表Node 包管理器,是一个用于管理包的工具,这些包是我们可以在我们的代码中使用的现有代码集合。
创建服务器的第一步是创建一个目录来存放我们的项目代码,也许可以叫它simple-server,导航到它,并输入以下命令:
npm init
此命令告诉 NPM 你想要开始,或初始化,一个新的项目。你将遇到一系列问题。对于我们在这里的目的来说,只需简单地按Enter键回答每一个问题,直到它最终询问你是否确定。再按一次Enter键确认,然后查看目录的内容。
你应该找到一个单独的package.json文件。此文件包含 NPM(以及通过扩展 Node)与你的项目一起工作所需的所有元数据。到目前为止,其内容应该是这样的:
{
"name": "simple-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
如果有一些小的差异,不必过于担心,但它应该大致看起来像这样。正如你所看到的,这里的元数据是从那些问题的答案中提取的,或者更具体地说,是从 NPM 为你提供的默认值中提取的。
编写服务器
到目前为止,你已经准备好了一个可以启动的项目,但还没有任何代码。让我们来解决这个问题!为此,在package.json所在的目录中创建一个名为index.js的文件,并将以下代码放入其中:
const http = require("http");
const server = http.createServer(function(inRequest, inResponse) {
inResponse.end("Hello from your first Node server!");
});
server.listen(80);
保存该文件后,在命令提示符中,输入以下命令:
node index.js
在这一点上,你不会看到任何东西,但只要你没有看到任何错误,那么服务器正在运行。要测试它,请打开你的网页浏览器,在地址栏中输入localhost,然后按Enter。一旦你这样做,你应该会看到一个表示来自你的第一个 Node 服务器!的问候。
你刚刚创建了一个服务器!
为了稍微解释一下代码……首先,我们需要使用 Node 提供的内置包——http包。require()函数告诉 JavaScript 使该包对我们的代码可用。这只是一个函数调用,这意味着它会返回一些东西给我们,在这种情况下,它是对该包的引用,然后我们将其存储在http变量中。请注意,在这里,我使用const而不是var,除非你知道你以后需要更改该变量中存储的值。如果你遵循这个建议,你会避免很多讨厌的 bug,相信我!当你知道你以后需要更改值时,你可以使用var和let,它们是等效的。它们并不相同,但解释它们之间的差异需要涉及到一些其他的话题,这些话题在这个早期阶段最好跳过。
在导入http包之后,我们可以调用它的createServer()方法,正如其名称所暗示的,它为我们创建了一个服务器。在这里,你可以看到 JavaScript 的另一个关键特性:函数是一等公民,这意味着它们可以被当作值来处理,就像字符串和数字一样。这意味着我们可以将它们作为参数传递给其他函数,这正是这里所做的事情。
createServer()的参数是一个函数,每当有请求进入服务器时都会执行。这个函数可以完成任何需要的服务请求。在这种情况下,我们只是用一个简单的问候来响应。为了做到这一点,我们必须调用inResponse对象的end()方法,当请求进入时,这个方法会被传递给我们的函数。这是服务器为客户端生成的响应,所以通过调用end(),我们可以同时告诉服务器“这是响应的结束”,并且“这是要返回给客户端的内容”。
我们最后需要做的就是启动服务器,这通过调用其listen()方法来完成。我们必须告诉它要监听哪个端口,在这种情况下是80。在这里,端口是一个网络结构,代表在物理服务器机器上运行的程序。你可以在单个物理服务器上运行多个软件程序,所有这些程序都在监听请求。然而,为了知道客户端应该与哪个程序通信,每个软件程序都必须分配一个监听端口,然后客户端必须指定要连接的端口。
注意,当你将http://localhost输入到你的浏览器中时,你并没有指定端口。这是因为,默认情况下,网站总是使用端口 80。本质上,浏览器在 URL 的末尾为你附加了:80(localhost:80/),你应该将其附加到 URL 的末尾(所以是 http://localhost:80),并尝试一下以证明它工作:你应该仍然看到问候语。我还应该指出,localhost是一个特殊的名称,它始终意味着“我正在运行的机器。”
我希望你们能同意,出人意料的是,使用 Node 创建一个工作着的——尽管非常简单——服务器所需的代码量很少。这也是 Node 成为服务器端开发如此受欢迎的选择之一的原因,除此之外,对于全栈开发者来说,这意味着他们可以在客户端和服务器端使用相同的语言,这通常会使得生活变得容易一些。
现在你已经看到了前端和后端开发的一些代码,让我们谈谈这些词的含义,以及它们如何扩展到你在现实世界中可能遇到的职位名称。
定义网络开发者的类型和职位名称
当你想到我在第一章中提到的三种类型的网络开发者时,你将开始对每个人所做的工作类型有一个很好的感觉。在这里,我想主要谈谈全栈角色,但首先让我们简要地谈谈其他两个。
前端开发者
前端开发者主要关注屏幕上显示的内容。他们的关注点包括用户界面设计(屏幕布局和一切看起来如何)、用户体验(用户如何与网站互动,确保它对用户来说工作良好),以及我们所关注的 HTML、CSS 和 JavaScript 等前端技术。
他们执行的任务包括将线框图解释并转化为功能性的网页。他们还关注性能以及搜索引擎能否高效地找到网站。
现代前端开发者经常会使用各种框架来完成他们的工作。例如 Angular、React 和 Vue,这些我们将在稍后更详细地探讨。这些框架的目标是增强 HTML、CSS 和 JavaScript 这三个核心支柱技术,并在一定程度上使您不必直接编写它们。目标是让开发者用更少的代码产生更多的成果,或者在某些情况下编写更健壮、更容易理解的代码。正如我之前提到的,我们将在稍后详细讨论这些内容。
后端开发者
与之相比,后端开发者不必太担心用户浏览器上的屏幕内容。相反,他们的主要关注点是等式中的服务器端发生的事情。后端开发者的核心工作是诸如数据库和数据建模、安全性、为前端提供 API 以及实现应用程序的核心逻辑。
后端开发者使用一套完全不同的技术,其中涉及到像 Python、Java 和 PGP 这样的技术。然而,有一个选项——Node——允许他们在服务器上编写 JavaScript,就像在客户端一样,因此可能会有一些重叠。
现代后端开发者通常也会使用框架,尽管在这里,我们经常看到 Django 和 Spring Boot 这样的名字。
全栈开发者
相反,全栈开发者既擅长前端开发方法和技术,也擅长后端开发方法和技术。他们必须是一个多面手,既不专注于前端也不专注于后端。
全栈开发者与集成密切相关——也就是说,构建前端和后端之间的桥梁,并确保数据在这两者之间正确流动。因为他们知道所有部件是如何组合在一起的,因为,嗯,他们构建了所有这些部件!
成为全栈开发者意味着需要学习更多,但也意味着你对潜在雇主具有更大的价值。当然,有些环境需要专家,无论是前端专家还是后端专家。但全栈开发者往往是更多网络开发职位的一部分。
要在脑海中形成一个涵盖前端和后端的整个系统的图像可能很困难,但与此同时,以其他方式履行你的职责可能更容易。这是因为当你理解所有部件如何相互作用时,你将比一个专家有更全面的视角,更容易看到问题。
最终,在职场中,没有绝对的正确答案。有些人坦率地更喜欢前端而不喜欢后端工作,有些人则正好相反。然后,我们中的一些人喜欢同时做两者的挑战。随着你的进步,你必须自己决定哪条路适合你。
然而,无论你决定走哪条路,让我们谈谈你可能遇到的某些职位名称,它们如何映射到这些类别,以及它们的意义。
理解职位名称
虽然你确实可能会看到“Web 开发者”的职位启事,但在这个时候,我希望你开始意识到头衔有些含糊不清,并且肯定是不完整的。它是否意味着专注于前端开发的人?或者它是否意味着从事更多后端开发的人?而且,更糟糕的是,同一个头衔在不同的公司之间可能甚至意味着完全不同的事情!
为了试图弄清楚这一点,让我们看看在招聘启事中可能会找到的一些常见职位名称列表,并尝试至少大致定义它们在大多数地方可能意味着什么:
-
程序员/计算机程序员:这是一个通用头衔,一些公司可能会用它来表示他们需要能够用各种语言编程的人——或者可能是在一种特定的语言(例如 Java 程序员)中。通常,这个头衔意味着这不是一个 Web 开发职位,但这远非规则。尽管如此,它通常意味着一个人正在从事嵌入式系统或可能国防工业等方面的工作。
-
工程师/软件工程师:这是另一个通用头衔,在大多数情况下意味着与程序员相同,但通常暗示着更高的职位。虽然提到程序员的职位列表可能倾向于表明这是一个更基础的职位,但带有工程师字样的职位可能表示需要以前的经验。这些头衔也倾向于意味着非 Web 开发。
-
高级软件工程师/高级软件开发者:一旦你有一些经验,更高层次的开发职位再次不太可能是针对 Web 开发的。在高级水平上,你将需要承担更大的挑战,并能够更独立地工作。
-
开发主管/团队主管/技术主管:一旦你有合适的经验和证明的知识,你通常会发现自己进入了一个“主管”职位。在这个角色中,你将需要在技术方向上起到指导作用,通常作为更年轻团队成员的导师。当然,你仍然会编码,但你的大部分时间将用于编写文档来指导他人的工作。这些头衔并没有说明它们是否与 Web 开发相关;它们在软件开发树的两个分支上应用得相当平等。
-
首席软件工程师:任何带有“首席”字样的职位名称通常意味着这个人已经在这个行业里待了很长时间,并被视为一个指导者。你可能会同时参与多个项目,作为他人的力量倍增器,并参与更多关于应该或不应该开发什么以帮助业务的战略讨论。
-
测试工程师/质量保证工程师:这些角色与之前提到的任何角色都不同,但它们是相关的。这类工作更侧重于构建测试,实际上大部分还是意味着编写代码,但具体是编写测试其他代码的代码。例如,当传入 2 和 3 时,
addNumbers()函数是否返回 5?你可以编写调用该函数并确认其返回值的代码,从而测试该函数。为了明确,所有开发者都会这样做,但有一类人一直在做,并且做得更深入。与此相关的是质量保证工程师,他们是那些让我们开发者感到头疼的必要人物!他们会尝试将 ABC 输入只应接受数字的字段中,看看系统是否会崩溃。一些开发者后来会转向这类工作,反之亦然,所以这里有一个明显的关系。 -
网页开发者/网页设计师:啊,是的,这个你应该知道!有时候,一个职位发布可能会只列出这个头衔,这很困难,因为它告诉你你将从事网页开发,但并没有说明是前端、后端还是全栈。根据我的经验,我可以说它通常意味着全栈,但这并不是绝对的保证。
-
前端开发者:有时候,雇主会具体说明是前端开发,这样你就知道你将做什么了:写大量的 HTML、CSS 和 JavaScript!
-
后端开发者:同样,后端职位告诉你它是网页开发,并且告诉你你将从事服务器代码的开发,但具体你会使用哪些技术并没有说明,这实际上意味着你可能需要使用不止一种技术。
-
全栈开发者:最后,我们来说说那个古老的“全栈”头衔,这意味着你将需要了解一些关于所有事情的知识。我必须强调,在大多数情况下,你不需要一开始就成为所有领域的专家。但你会被期望能够边学边进步,这既是令人兴奋的,也是令人恐惧的!
-
系统分析师/计算机系统分析师:就像测试和质量保证的头衔一样,任何带有“分析师”字样的头衔都是另一回事,但它们是相关的。分析师主要专注于与利益相关者交谈,确定他们的需求。工厂主说“我需要这条装配线更有效率”很容易,但那是什么意思,如何实现,如何将其转化为软件解决方案的蓝图?这就是分析师的用武之地。他们深入了解涉及的业务流程,真正理解它们,并将其转化为软件方法,无论这意味着基于网络的软件与否。他们通常不会详细定义软件将如何编写,但他们将定义系统的整体流程以及各个部分如何组合在一起。随着时间的推移,开发者有时会变成分析师,也可能反过来。
-
架构师/系统架构师/企业架构师/软件架构师/首席架构师:包含“架构师”一词的职位是指那些更专注于大型软件系统结构的人。他们将定义系统的各个部分,它们将如何交互,选择实现它们的技术,并绘制出软件开发者可以用来构建他们所设计内容的文档。因此,架构师职位是开发者经过一段时间后的自然发展路径,只要他们具备必要的才能。如果你认为分析师和架构师之间存在很多重叠,这在很大程度上是正确的。关键的区别在于,分析师更多地从业务角度看待事物,而架构师则从技术角度看待。分析师和架构师是紧密合作的。因此,他们会为开发者制定蓝图,以便开发者可以开发出他们所设计的内容。架构师有时也会编码,但随着职位的提升,这种情况越来越少。
-
技术经理/工程经理:我将要讨论的最后一类是通常位于执行层之前的管理层级。在这里,我指的是技术经理,有时也被称为工程经理。正如其名所示,这些是管理职位,你将几乎不会亲自进行实际开发,无论是网页开发还是其他类型的开发。你将花费更多的时间在人力资源管理事务上,参加讨论方向的会议,以及与团队一起实施这些方向。虽然这些是管理职位,但你仍需对技术保持敏感,这意味着你需要有丰富的经验可以借鉴。当然,技术经理有时仍会参与实际开发或与之相关的任务,但这不再是他们日常工作的主要焦点。
再次强调,这些头衔在任何一个地方都没有明确的定义,它们也不是你可能会遇到的唯一头衔。例如,问 100 个人什么是技术经理,你可能不会得到 100 个不同的答案,但你可能会得到不止一个!请注意,这是一个基于我多年经验和在线研究的大致指南,因此它不仅应该能给你提供一个可能看到的职位头衔的概览,还能告诉你它们的意义,它们之间的关系,以及你通过这些职位进行职业发展的可能路径。
我还想指出,从程序员到经理的晋升并不是理所当然的!你在旅途中每个步骤都必须对自己诚实,并决定对你来说什么最重要。几年前,我有一个经理自愿减薪,以便他们可以回到“仅仅”是程序员的身份,因为他们喜欢这份工作的这一方面,但不喜欢管理方面。那很好!并不是每个人都想成为经理、架构师或甚至是一个领导。无论你有什么头衔,不喜欢日常的工作比任何头衔甚至可能随之而来的金钱都更重要。
现在,让我们触及一个最后的主题,那就是我们在阅读这本书的过程中将构建的路线图。我们还有几个方框要揭露,就像 Vanna White 一样!
检视路线图
本章结束后,我们现在可以揭示我们网络开发者路线图上的更多几个方框:

图 2.4:路线图,填入了一些更多的方框
除了逻辑思维,我们还揭示了前端和后端方框,以及它们的子项HTML、CSS、JavaScript、Node和NPM。我们还揭示了全栈,它从前端和后端方框分支出来。我们正朝着网络开发者宾果游戏大步前进!
摘要
本章我们覆盖了很多内容!我们深入探讨了前端开发,包括 HTML、CSS 和 JavaScript——网络的基石,为每个提供了基础性知识。你还了解了页面如何链接到其他页面和资源,以及你感受到了网页浏览器如何加载和渲染页面,包括 DOM 是什么。然后,你看到了后端,看到了一个简单的 Node 示例,并发现了它如何与前端交互。
之后,我们谈了更多关于网络开发者是什么以及这个定义如何与前一章中讨论的前端和后端联系起来。最后,我们更多地讨论了全栈意味着什么以及它能为你的职业生涯带来的好处。
在下一章中,我们将探讨一些可能被认为是网络开发者“基础”知识的其他事物,在继续构建你需要成功的技术基础的同时,我们将在此基础上建立你在本章中看到的内容。
第三章:扩展基础——从前端到后端构建用户注册页面!
到目前为止,你已经开始构建技术基础,并对网页开发有一些背景知识。你也看到了一些前端(客户端)和后端(服务器),但到目前为止,你还没有看到它们是如何结合在一起的。现在让我们解决这个问题!
在这个过程中,你首先会了解一些关于网络的知识,因为互联网和网站都是建立在网络之上的。然后,你将接触到更多的 HTML、CSS、JS 和 Node,你将通过构建另一个简单的网页并查看它如何与服务器端通信来实现这一点。
这里的目标是了解客户端和服务器是如何连接的,信息是如何传输到服务器的,以及服务器是如何处理它并返回响应的。这是我们每天在网页开发中都会做的事情的一部分,因此显然,它非常重要。你不仅会接触到这一点,而且还会以两种不同的方式接触到,确保你能看到在网页开发中不止有一种方法可以“剥猫皮”。
你还将了解一些与你的网络浏览器一起提供的工具,以及它们的价值,因为当你努力调试并确保一切按预期工作的时候,你将不断地与这些工具互动。
在本章中,我们将涵盖以下主题:
-
通过网络连接
-
构建交互式网页应用程序——前端
-
构建交互式网页应用程序——后端
-
探索不同的方法——单页应用程序
-
爱上你的新最佳朋友——开发者工具
技术要求
为了本章的目的,你只需要确保已经安装并运行了 Node,这你应该在上章已经完成。你还需要确保你已经从 GitHub(github.com/PacktPublishing/Web-Development-Career-Master-Plan)下载了本书的所有源代码,你可以在第二章的技术要求部分找到。你可能已经注意到了,下载的代码中每个章节都有自己的目录,所以对于本章,你感兴趣的是ch-03/1-form和ch-03/2-spa目录。
这就是本章你需要了解的全部内容,所以现在,让我们自己构建一个用户注册页面吧!
通过网络连接
在我们真正开始编写代码之前,让我们先谈谈一些更基础的东西,即网络,以及网络是如何进行通信的。
我在上章提到了IP(互联网协议),但并没有深入细节。在这里我也不会过多地深入,因为当我们谈论包括 IP 在内的网络协议时,它很快就会变成一个广泛的话题。然而,作为一名网页开发者,你应该至少了解基础知识。
理解 IP
IP,正如其缩写中的第二个单词所表明的,是一个协议。协议简单来说就是两个事物之间通信的约定标准。在这种情况下,IP 描述了被称为数据报的小数据块如何在两台机器之间通过网络进行传输。正如你在第二章中看到的,它还负责为每台机器分配一个 IP 地址,该地址在网络中唯一地标识了它们。
然而,你可能惊讶地发现,IP 并不保证它传输的数据报的交付、顺序或完整性。换句话说,如果你想从一台机器发送一张图片到另一台机器,那张图片将被分成许多数据报,并且它们将使用 IP 在网络中传输,但它们可能或可能不会到达目的地,可能或可能不会以正确的顺序到达,以便其他机器可以重建图像(实际上,接收机器甚至没有一种方法知道它们应该以什么顺序到达),并且可能或可能以某种方式损坏。IP 被认为是一个“尽力而为”的协议,这意味着它将尝试将数据从机器 A 传输到机器 B,但它并不保证。
如果这听起来像是一场灾难的配方,你是对的!但它的好处是它非常简单且快速,而在技术中,当你拥有简单快速的东西时,几乎总是很容易扩展它或在其之上构建一些东西(我们在软件工程中非常重视层次)。这正是 TCP 的用武之地。
理解 TCP
TCP,即传输控制协议,通过与 IP 协同工作或在其之上工作,确保发送的信息是可靠的。它首先将需要发送的数据分解成数据包(然后 IP 将进一步将其分解成数据报),并确保所有数据包都到达目的地,它们是无错误的,并且它们告诉接收者一切事物的顺序。它是通过在机器之间建立连接并发送数据包来做到这一点的,接收者随后将检查这些数据包。数据包包含接收者可以使用以验证数据是否正确的数据。它们还包括序列号,告诉接收者它们的顺序。如果一个数据包损坏,或者接收者发现某些数据丢失,它可以请求发送者重新发送数据,这是 TCP 提供的另一个关键功能。
TCP/IP 是一个很好的组合,因为在拥有大量机器(如互联网)的大型网络中,它们之间有很多路由和连接,数据包可以通过这些路由传输。如果某个连接中断——可能是因为有人在修路时不小心切断了网络电缆——可以选择不同的路由。在这种情况下,IP 不提供具体保证的事实并不重要,因为 TCP 在其之上拥有所有所需的信息,可以根据需要请求发送者重试,并且这可以一直持续到所有数据都正确且按顺序接收为止。这为网络提供了冗余。是的,如果数据包必须绕地球半圈才能到达目的地,速度会稍微慢一些,但数据仍然会到达,这正是 TCP 与 IP 协同工作时提供的关键功能。
然而,尽管 TCP/IP 在概念上作为互联网的基础协议,但网络有其自己的协议,它位于 TCP/IP 之上,那就是 HTTP。
理解 HTTP
你可以将 HTTP(超文本传输协议的缩写)视为网络数据通信的基础。
HTTP 在两台机器之间作为请求-响应模型工作,一台作为客户端,另一台作为服务器。HTTP 是我们所说的无状态协议,这意味着每个请求都是独立的。换句话说,当你你在浏览器中输入一个 URL 时,从你的机器到服务器的连接就会建立,但一旦 HTML 页面返回给你,这个连接就会结束。当需要检索图片或样式表时,会建立一个新的连接。更现代的 HTTP 版本实际上会在一定程度上重用连接,但连接仍然不会永远持续,这就是我们所说的无状态——两台机器之间没有恒定的连接。
当你使用 HTTP 从一台机器连接到另一台机器时,每个请求都使用一个 HTTP 方法,它包含一组基本操作,可以用来与服务器交互。它们旨在(至少在一般情况下)向服务器传达客户端想要做什么。请注意,这与 JS 方法不同——一个附加到对象上的函数——这实际上是一种更复杂的方式来说明它告诉我们做出了什么 类型 的 HTTP 请求。
HTTP 定义了几个方法,其中每个方法都有一个典型的(尽管不是具体的)预期用途:
-
GET: 从服务器检索数据
-
POST: 向服务器发送要处理的数据
-
PUT: 更新服务器上的现有数据
-
DELETE: 从服务器删除数据
-
GET只是一个更有限的版本 -
OPTIONS: 发现资源上允许的方法
当您在浏览器中输入一个 URL 时,会发起一个 GET 请求。如果您这样考虑,这是有道理的——您的浏览器正在请求服务器上的数据,通常是 HTML 文档。其他情况下需要其他方法——当您向服务器发送信息时,您可能会使用 POST 或 PUT,具体取决于情况——但您很快就会看到选择其他方法是如何发挥作用的。
值得注意的是,HTTP 与 HTML 密切相关,有一个类似于 HTML 中的 head 和 body 的概念,但称为 headers 和 body。Headers 是提供一些元数据给接收者的数据块。有大量的 headers,您也可以创建自己的,所以在这里我不会深入探讨;相反,我会在必要时介绍 headers。然而,有一件事我会告诉您,它们是以 键值对 的形式发送的。这是一个常见的编程思想,看起来像这样:
first_name:Frank
或者,它也可以是这样的:
first_name=Frank
是否使用冒号或等号(或者可能是其他字符),这取决于上下文,但关键点是您有一个键(first_name)和一个值(Frank)。值是通过键来识别的。您几乎可以将它们视为 JS 中的变量,因为您可以在需要时查找与键关联的值。
HTTP 请求的主体可以包含任意数据,无论是文本、图像、音频文件还是其他任何东西。当一个 HTTP 请求有主体并且包含数据时,几乎总是使用 POST 方法。您很快就会看到一个例子,作为讨论其他内容的部分——HTML 表单。
HTTP 与 HTTPS 的比较
哦,如果还不够的话,还有 HTTPS。幸运的是,这很简单——它只是加密的 HTTP,其中的 S 代表 Secure。如果您看到一个带有 HTTP 的 URL,这意味着通过该连接发送和接收的所有内容都可能被网络上的其他人看到(这并不那么容易做到,但也不是特别困难)。然而,通过 HTTPS,所有内容都是加密的,没有一些称为 keys 的秘密信息,拦截数据的人基本上会看到很多乱码。
曾经有一段时间,HTTPS 只用于像银行网站和购物网站这样的敏感网站。这是因为,在很长一段时间里,加密和解密过程——它基于一些复杂的数学——对计算机来说是一件昂贵的事情。从金钱的角度来看,这很昂贵,更重要的是,在性能方面也很昂贵。HTTPS 连接简单地比 HTTP 连接慢。然而,现在情况已经不再是这样了。计算机足够快,并且通常有专门用于加密/解密的硬件,所以现在大多数连接实际上都是 HTTPS。浏览器甚至会让你知道连接不安全,有些甚至完全不允许非 HTTPS 连接。
对于大多数意图和目的,你通常可以一般性地将 HTTP 和 HTTPS 连接视为相同。然而,在开发方面,设置 HTTPS 连接需要一些额外的配置,这可能相当复杂。如果你听说过证书这个词,那就是涉及的。这是一个完全不同的主题,可能会相当复杂,所以它不会在本书中涉及。在你自己的机器上开发时,你可以简单地使用 HTTP,这是默认情况下如果你没有明确设置 HTTPS 将会使用的,你不需要担心证书和所有那些,除非你真的需要。
在掌握了互联网(以及大多数网络)上事物如何通信的基本理解之后,我们现在可以继续构建前面提到的注册应用程序。
构建交互式 Web 应用程序——前端
对于本章,我们不仅仅使用之前看到的简单 HTML,而是创建一个看起来更“真实”的应用程序。我的意思是,让我们创建一些你可能在某天的工作中真正需要构建的东西——一个用户注册页面。
目标将很简单——给用户一种方式来输入一些关键信息(他们的头衔、名、姓、年龄和电子邮件地址),然后将这些信息发送到服务器,在那里我们将生成一个响应来确认用户现在已经注册。我们实际上不会以任何方式注册用户——我们甚至不会存储他们发送的信息——但这足以看到这样的客户端-服务器交互是如何工作的。
构建注册页面
第一步是构建一个用户可以输入信息的页面。
嗯,我收回刚才的话,因为真正的第一步是开始一个项目。正如你在前面的章节中学到的,这可以简单到创建一个 HTML 文件;然而,你也看到了如何使用 NPM 来创建一个新的项目。既然我们知道我们将在稍后为这个项目构建一个服务器,让我们现在就做吧。
如同之前,只需创建一个目录,进入命令提示符,导航到该目录,并执行npm init。现在,你可以为每个问题都按Enter键,因为这将满足我们的需求。
完成这些后,是时候编写一些 HTML 了,其结果将是你将在图 3.1中看到的内容。

图 3.1:我们正在构建的注册页面
你会从在项目目录中创建一个名为index.html的文件开始这个项目,然后开始编写其中的代码。当然,我已经为你完成这部分工作了!在 GitHub 仓库中,你可以在ch-03/1-form目录下找到这个文件。由于它比之前的例子要复杂一些,我们将逐步解决它,将其分解成可管理的块,从<head>开始。
创建头部
对于我们的注册页面,我们像之前一样,从<html>标签开始,然后在其中添加一个<head>部分:
<html>
<head>
<title>User Registration</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
到现在为止,代码应该开始对您有了一些意义,因为您之前已经见过它。
然而,与前面章节中展示的示例不同,<head>中没有<style>块。相反,所有 CSS 都在一个外部的styles.css文件中。我这样做的主要原因——这样我就可以暂时跳过 CSS!实际上要处理的 CSS 并不多;然而,如果您先看到 HTML 是如何应用样式的,这会更有意义,所以我们将在最后回到它。现在,只需知道样式表文件通过<link>标签导入,并像在前面章节中看到的那样,直接在<head>中的<style>块中起作用。
在这个项目中也没有使用 JS,所以这就是<head>中所有内容。现在,我们可以继续创建<body>。
创建主体
与<head>一样,<body>开始时非常简单:
<body>
<h1>User Registration</h1>
<p>Please enter the following information to register for our website</p>
</body>
如您所见,我们有一个标题,下面有一段文字。当然,您知道还有更多——您在图 31 中看到的那些字段,用户可以输入他们的信息,以及提交按钮——但所有这些都构成了我现在要介绍的新概念——表单。
了解表单
在 HTML 中,表单是我们提供用户输入信息的方式。一个表单由一个或多个表单字段组成,这些字段有多种类型。您可能没有意识到,但除非您从未真正使用过网站,否则您知道许多这些字段,即使您没有意识到您在使用。先不考虑这一点,一切从定义表单本身开始,而在 HTML 中,这意味着一个新的标签:
<form action="/register" method="post">
<form>标签是我们开始表单的方式。这个标签有几个属性,但两个关键属性是action和method。
action属性告诉浏览器将用户输入的信息发送到哪个 URL。您可以将此 URL 表示为绝对 URL 或相对 URL。
绝对 URL 包含获取特定资源所需的所有信息。例如,www.packt.com/takeMyInfo 是一个绝对 URL,因为它以域名开头,并包含服务器上资源的完整路径。www.packt.com/books/zammetti/forms/takeMyInfo可能另一个例子(这是虚构的,仅供参考!)绝对 URL。
那么,什么是相对 URL?相对 URL 从绝对 URL 开始,或者更准确地说,是从其部分开始的。/takeMyInfo 是一个相对 URL 的例子。如果使用该 URL 的文档是从www.packt.com加载的,则浏览器将所有相对 URL 解释为相对于该基本 URL。因此,/takeMyInfo 指向的完整 URL 是 www.packt.com/takeMyInfo。
在一个更复杂的例子中,/books/zammetti/forms/takeMyInfo 可以是一个相对 URL。如果你从一个 HTML 文档 www.packt.com/books/zammetti 加载,那么你可以有一个相对 URL 为 /forms/takeMyInfo,因为这两个 URL 在浏览器幕后被合并,形成一个绝对 URL,然后浏览器将使用这个 URL 请求资源。
然而,在这个 <form> 中,/register 是表单将被发送到的 URL,你将在我们讨论这个项目的服务器部分时看到那里等待的内容。
另一个关键属性是 method。这是用于传输表单的 HTTP 方法,它将是 GET 或 POST 之一。区别在于当它是 GET 时,表单上的信息将以 ?aaa=bbb&… 的形式添加到 URL 中,其中 aaa 是键,bbb 是值。例如,在这个表单中,我们将要求用户输入他们的名字和姓氏。如果这个信息通过 GET 发送,那么一个查询字符串将被附加到 URL 上,作为 ?first_name=Frank&last_name=Zammetti。
在某些情况下这没问题,但在大多数情况下,这并不是一个好主意,因为它有安全影响,也因为 URL 有一个大小限制,信息可能比你拥有的空间要大。所以,你可以用 POST 方法发送信息,这将导致浏览器在 HTTP 请求的正文部分发送它,那里实际上没有限制,而且它对世界的暴露也不是“完全”的(它仍然很容易获取,但每一点帮助都很大)。
一旦我们有了 <form>,我们就可以开始向其中添加字段:
<p>
Title
<br>
<input type="radio" name="title" value="Mr">Mr
<input type="radio" name="title" value="Mrs">Mrs
<input type="radio" name="title" value="Miss">Miss
</p>
严格来说,<p> 标签在表单中不是必需的。实际上,你可以定义你自己的 HTML 和布局,但在这里,我只是想让每个字段沿着屏幕向下延伸,所以将每个字段放在 <p> 中简单地完成了这个任务。我还想每个字段都有一个文本标签,这就是 Title 文本的作用。我想让这个标签在字段本身之上,所以 <br>,换句话说,是一个换行符,确保了这一点。
<input> 标签定义了一个表单字段,用户可以在其中输入信息。在大多数情况下,屏幕上的单个输入字段在 HTML 中也是一个单个 <input> 标签,它有一个 type 属性,告诉浏览器它属于几种选择中的哪一种。然而,在单选按钮的情况下,这是一种可以从中选择一个且仅选择一个选项的控件类型,将会有多个类型为 radio 的 <input> 标签,每个选项一个。name 属性告诉浏览器输入值的名称(键)在表单中应该是什么。在这里,我们说在这个表单中,我们想要一个名为 title 的键,并且它将有一个三个可用的值(这些值由每个 <input> 元素的 value 属性直接定义)。当用户选择三个选项中的一个时,表单中的 title 值将反映所选的 value。
如果这看起来有点令人困惑,不要担心——我认为你会在本章的后面看到服务器交互时发现它更有意义。
之后是两个 <input> 字段,用于输入姓名的首字母和姓氏:
<p>
First name
<br>
<input type="text" name="first_name" size="20">
</p>
<p>
Last Name
<br>
<input type="text" name="last_name" size="30">
</p>
在这里,我们又有另一种 <input> 字段——一个 text 字段。这允许用户输入他们想要的任何任意文本。然后,同样,type 属性告诉浏览器我们想要哪种字段,而 name 属性告诉它哪个键将保存用户输入的值。
size 属性告诉浏览器字段的实际大小,它定义为字段应该有多宽。当我们谈论非比例字体——意味着每个字符占据可变的空间(例如,“I”字母在水平方向上占用的空间不如“W”多)——这个值可能有些灵活。但作为一个经验法则,如果 size 是 20,那么你通常可以在其中看到大约 20 个字符,略有增减。请注意,这并不设置实际可以输入的字符数上限;如果你输入的字符数超过了 size 属性允许的,字段将滚动(有一个 maxlength 属性设置了可以输入的最大字符数)。
接下来,我们有一个不同类型的输入字段:
<p>
Age
<br>
<input type="number" name="age" min="13" max="100" step="1">
</p>
type 为 number 确保只能输入数值;如果你尝试输入字母,浏览器将不允许这样做。此外,大多数浏览器将显示上下箭头以允许用户滚动查看可用值。此标签允许我们设置输入数字的限制;在这里,通过设置 min 和 max 属性,允许输入的范围是 13–100。
接下来,step 属性告诉我们当使用箭头按钮时数字变化的增量(如果值是 2 而不是 1,例如,当点击向上箭头时,它将从 13 跳到 15,然后到 17)。
图 3.2显示了上下箭头的呈现方式,这可能会根据浏览器而有所不同(此示例来自 Chrome):

图 3.2:Chrome 显示数字字段中的箭头方式
之后是最后一个输入字段:
<p>
Email Address
<br>
<input type="email" name="email">
</p>
email 类别确保输入的值是一个有效的电子邮件地址。如果你输入了一个无效的值(例如 none@@@nowhere),该字段将被突出显示,并且悬停在其上会显示一个工具提示来解释问题。请注意,这种呈现方式是浏览器特定的,但图 3.3显示了在 Chrome 中悬停在该字段时应看到的内容:

图 3.3:Chrome 显示电子邮件字段中的错误
在输入字段之后,我们需要一个按钮供用户点击以将信息发送到服务器:
<button type="submit">Register Now</button>
有几种类型的按钮,你将在本章后面看到另一种,但 submit 可能是最重要的,因为它实际上告诉浏览器,当点击时,它需要将输入的信息发送到服务器。在 <button> 标签的打开和关闭之间的是按钮上显示的标签。
现在只剩下最后一件事,那就是正确地关闭表单:
</form>
完成所有这些后,我们就有一个完整的 HTML 表单,当用户点击按钮时,它将发送其信息到指定的 action URL。但我们还没有完成;我们仍然有一个要完成的 HTML 文档!
完成主体和文档
当然,我们必须使用以下方式来关闭文档的主体:
</body>
然后,我们必须使用以下方式关闭文档本身:
</html>
完成这些任务后,我们现在有一个完整的 HTML 页面。
记得我之前说我们会跳过样式表吗?现在,是时候看看那个了!
添加一些样式
之前,你看到 styles.css 文件在 <head> 中被导入。这个文件的内容并不多,但它展示了一些新的 CSS 概念:
input:invalid {
outline: 2px solid red;
}
第一个新概念是 <a> 标签可以显示三种不同的状态——当用户从未点击它时(未访问),当用户之前点击过它时(已访问),以及当用户的鼠标指针悬停在其上时。如果你想使链接在未访问时为红色,在已访问时为蓝色,在悬停时为绿色,你可以创建一个名为 a:visited、a:unvisited 和 a:hover 的 CSS 类,并在每个类中设置适当的 color 属性值。冒号及其后面的单词是伪类。
在此情况下,我想确保当输入无效的电子邮件地址时,电子邮件字段将特别有一个红色边框。幸运的是,对于输入选择器有一个伪类,即 :invalid。因此,input:invalid 是用来定位无效输入字段的选择器。
第二个新概念是 outline 属性。这个属性允许我们在元素周围绘制边框(这是两种方法之一,你稍后会看到另一种)。这个属性实际上还展示了第三个额外概念——缩写属性。让我稍微解释一下。
你看,这个属性定义了三件事:
-
绘制在字段周围的边框宽度(2 像素 -
px是像素的缩写,它们是构成你显示器上图片的点) -
边框的样式(这里为
solid) -
边框的颜色
这些也可以由独立的属性定义——outline-width、outline-style 和 outline-color。然而,仅使用 outline 缩写属性就可以一次设置所有三个。
在 CSS 中,一些相关的属性组有这种简写属性,你稍后也会看到更多。然而,为了不让你感到悬念,其他的一些包括margin(margin-top、margin-bottom、margin-left和margin-right的简写)、padding(padding-top、padding-bottom、padding-left和padding-right的简写)、background(background-color、background-image、background-repeat、background-attachment和background-position的简写),以及font(font-style、font-weight、font-variant、font-size/line-height和font-family的简写)。
现在我们有了前端(客户端),让我们为它构建后端(服务器)以便与之通信。这将要求我们编写一些代码来接受用户在表单中输入的数据,并为浏览器提供一个响应以显示给用户。让我们开始工作!
构建交互式 Web 应用——后端
为了构建这个小型项目的服务器,我们再次使用 Node。然而,与上一章从头开始编写服务器不同,这次我们将使用 Node 最流行的包之一来处理许多基本细节——Express。
介绍 Express
当你从头开始编写 Node 服务器时,你必须自己处理所有细节。这意味着,例如,如果你想让服务器响应多个不同的 URL,在你的处理函数中,你需要检查请求的 URL,并根据它分支到一段代码以返回适当的响应。当应用程序足够大时,这会变成一个真正的头疼问题。
因此,我们经常会使用 Node 的附加包来处理这些事情——其中一个这样的包叫做Express。实际上,Express 是一个非常强大的模块,可以为我们处理各种事情,包括安全、文件上传和下载、操作日志记录以及模板化(以通用方式构建 HTML 和其他响应类型的能力,然后可以将数据插入到概要或模板中,以返回完整的响应)。
Express 也是你遇到的第一个框架。框架是一个预写的代码库,为构建自己的代码提供基础。它为你的代码提供了一种定义的结构,这样你就不必猜测并自己构建,而且它推动你使用最佳实践。本质上,它们旨在使软件开发更容易,并允许你编写更少的代码,这通常是一个好事情,因为它意味着你犯错误的机会更少。
更具体地说,Express 被称为“无意见”框架。而“有意见”的框架确保只有一种“正确”的方式来做事,你最好按照这种方式来做,否则你会发现自己在遇到挑战,而“无意见”框架则不会对你施加太多限制。Express 作为“无意见”框架,为你提供了更多的灵活性,在节省你大量努力的同时,仍然允许你大部分情况下按照自己的方式做事。关于“有意见”或“无意见”框架哪个更好的争论是一个由来已久的话题,我甚至不打算在这里尝试回答!
要开始使用 Express,我们必须将其添加为我们项目的依赖项,所以现在让我们来做这件事。
添加依赖项
记得当你运行 npm init 命令时创建的 package.json 文件吗?我说过它提供了 Node 和 NPM 关于我们项目的元数据,但我还说过我们基本上会忽略它。然而,我们现在必须查看它!
这是 npm init 命令的输出结果(记住,你的输出可能因你输入的值而略有不同,但应该看起来 大体上 相同):
{
"name": "form-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "4.18.2"
}
}
大部分内容仍然可以忽略,但我们现在必须关注的是 dependencies 元素。这个元素告诉 Node 和 NPM 我们的项目依赖于哪些包。你的项目此时可能为空,但我在我的项目中已经添加了 Express 依赖,并且我还指定了确切的版本。如果你访问 www.npmjs.com,你可以探索所有可用的不同包。NPM 使用这个网站来下载你添加到 package.json 文件中的依赖项。
但你实际上是如何将依赖项添加到 package.json 中的呢?好吧,鉴于这只是一个文本文件,你可以直接手动编辑它。然而,有一个更好的方法。首先,执行以下命令:
npm install -save express@4.18.2
然后,再次打开 package.json 文件,你应该会找到与该命令中显示相同的依赖项条目。npm install 命令告诉 NPM 安装你在项目中命名的包(它会被添加到 node_modules 目录中)。
在这里,我指定了版本为 @4.18.2。你不必像这样指定版本,实际上,你通常也不需要这样做。当你不指定版本时,NPM 将安装该包的最新版本。我在这里明确指定了版本,因为,对于一本书来说,你需要确保几个月或几年后读者仍然能够获得已知与代码兼容的确切版本;否则,更新的版本可能无法与书中描述的代码兼容(我为此付出了惨痛的代价!)。
最后,为了更新package.json文件,我们需要指定–save选项。如果不这样做,NPM 将只在node_modules中安装 Express,但不会将其添加到package.json文件中的依赖项列表中。当你只是尝试一个新的包,还不知道是否会在你的项目中继续使用它时,不指定–save是好的,因为这样不会永久更改任何内容。但既然我们知道我们希望 Express 保留下来,我们确实想要更新文件,而你想更新文件的原因是你可以将你的package.json文件给另一位开发者,他们只需要执行这个命令:
npm install
NPM 将接着查找package.json并安装列出的所有依赖项。这样,开发者就可以准备好在你的代码上工作,并将拥有与你相同的所有依赖项。
这是一种除非你亲身体验过,否则不会知道的事情,但设置新开发者的开发环境并不总是那么容易。在过去,他们必须上网并手动下载所有依赖项——这些依赖项并不总是在一个网站上——而且这还假设他们甚至有一份所需内容的清单。NPM 通过其package.json文件解决了所有这些问题!
我最后想提到的是,如果你查看你的项目目录,你会找到一个新目录被创建,称为node_modules。这是一个 NPM 创建的目录,用于保存它下载的所有包。在大多数情况下,你不需要查看这个目录或关心里面有什么;只需让 Node 和 NPM 处理它。你也应该意识到,有时它可能会变得相当大,因为如果一个包本身依赖于另一个包,那么那个包就会被下载,这种依赖链可能会持续一段时间。所以,node_modules将充满你不认识的包,因为它们都有依赖项。这就是它的工作方式。如果你任何时候删除node_modules,那没关系;你只需再次运行npm install,它就会被重新创建。
现在我们已经添加了所需的依赖项,让我们解决一个你可能甚至没有意识到的问题——那就是开发者效率问题——在这个过程中,你将看到package.json文件提供的新功能——脚本。
添加脚本
在过去,启动我们的服务器只需使用node index.js命令。然而,当另一位开发者想要在我们的项目上工作,他们可能不知道这一点。只有一个 JS 文件,猜测起来足够简单,但想象一下如果我们有成百上千个文件,这在大型项目中确实可能发生——你将如何知道从哪里开始呢?
当然,你可能会猜它是index.js,因为当你在一个项目中执行npm init时,NPM 会将默认值设置为package.json中的main属性,但 a)可能根本不存在index.js文件,b)即使存在,这也可能不是正确的答案,因为项目的开发者可能做出了不同的选择。我们可以告诉开发者它是哪个文件,但难道没有更好的方法来传达这个信息,而无需明确告诉某人吗?
有,这是由于package.json文件中另一个可用的元素——scripts。scripts元素提供了一种在某种程度上创建自定义 NPM 命令的方式。该元素中出现的任何内容都可以使用npm run XXX命令运行,其中XXX是scripts中的一个键。默认情况下,NPM 给我们一个test命令,所以你可以用npm run tests来运行它。
我们可以使用这个功能来提供一个启动服务器的方法:
"dev": "node index.js"
这样,我们可以执行npm run dev来启动服务器,scripts条目知道要运行哪个文件,因此开发者不需要这样做。这是一个常见的命令,所以许多开发者会自动知道它。但即使他们不知道,他们也可以简单地查看package.json并查看可用的命令。在某种程度上,这是一种记录我们项目正确启动命令的方式。
现在我们已经将 Express 添加到我们的项目中,并且有一个启动脚本来运行它,让我们用它来编写我们的服务器代码。
编写服务器代码
我们首先在项目目录中创建一个index.js文件。这将是前一章中的服务器代码。
每次你向 Node 项目添加一个包时,你几乎肯定需要将其导入到你的代码中,而这确实是我们在index.js中的第一件事:
const express = require("express");
这给我们一个名为express的对象,我们可以使用它的几个属性和方法。然而,在这个情况下,对象本身是一个函数——换句话说,express变量指向一个函数,因此我们可以像函数一样执行它:
const app = express();
express()函数的结果是返回给我们一个服务器对象。Express 处理所有服务器的设置和创建。然而,因为对象是返回给我们的,Express 允许我们配置服务器以满足我们的特定需求,我们首先需要做的是告诉它我们想要能够处理发送的表单:
app.use(express.urlencoded());
app.use()函数允许我们配置 Express 中间件。这实际上只是说我们可以告诉 Express 为每个进入的请求执行一个或多个函数。这对于记录传入的请求以简化调试、实现某种安全措施,或者在这种情况下,告诉它处理表单数据都很有用。
添加 Express 中间件
当一个表单通过POST方式发送到服务器时,它会在请求体中以独特的方式进行编码,如下所示:
title=Mr&first_name=James&last_name=Kirk&age=54&email=jame.t.kirk%40starfleet.com
为了让服务器能够理解这一点,它必须知道如何解析它以及使用哪个中间件函数来做到这一点。Express 提供了几个预构建的中间件函数,其中之一是在我们调用 express.urlencoded() 函数时提供给我们的。它返回一个函数,然后我们通过将其传递给 app.use() 函数将其添加到 Express 作为中间件。没有这个,Express 就无法处理我们的客户端代码将发送给它的表单数据。
你可以通过调用 app.use() 添加零个、一个或多个中间件函数。每次调用它时,你给它一个函数的引用,无论是你自己创建的,还是 Express 提供的,或者来自第三方来源的,它会被添加到 Express 将为每个进入它的请求执行的中间件函数链中。
使用 JS 箭头函数创建 Express 路由
也许 Express 的关键概念是 路由 的想法。路由实际上只是 URL。在 Express 中定义路由意味着定义针对给定 URL 执行的函数。对于我们的小程序,我们需要两个路由,从默认路由开始:
app.get("/", (inRequest, inResponse) => {
inResponse.sendFile(`${__dirname}/index.html`);
});
这是默认路由,因为它是在 URL 在域名之后没有任何内容时执行的。换句话说,如果你在服务器运行时访问 localhost,这将是被触发的路由,因为没有域名之后的内容,在这个例子中域名是 localhost。"/" 参数等同于“域名之后没有内容”。
调用 app.get() 函数将一个路由注册到 Express 中,该路由将通过 HTTP GET 方法访问,你可能会记得这是浏览器默认使用的,所以这就是我们在这里需要的。传递给此函数的第二个参数是我们所说的 处理函数,它只是一个普通的 JS 函数,当通过此路由收到请求时执行。
然而,在这里,我正在使用 JS 中可用的不同形式的函数,这是你之前没有见过的 – app.get() 调用,这样我们就可以单独查看它,它就只是这段代码片段:
(inRequest, inResponse) => {
inResponse.sendFile(`${__dirname}/index.html`);
}
这与以下代码等价:
function(inRequest, inResponse) {
inResponse.sendFile(`${__dirname}/index.html`);
}
因此,我们可以将 app.get() 调用写成这样:
app.get("/", function(inRequest, inResponse) {
inResponse.sendFile(`${__dirname}/index.html`);
});
使用箭头函数,你实际上是在用function关键字交换=>字符。在大多数情况下,这两个是绝对等价的,但有些细微的差异在更高级的情况下可能会变得很大。我现在先跳过这一点,因为还有一些其他的概念你需要先了解,这样你才能理解解释。目前,我只能说,在写作的时候,开发者倾向于使用箭头函数更多,因为它们更短(尽管只是字符或两个)并且更美观。你的观点可能不同,但最终,你通常可以随意选择你更喜欢的(至少直到你遇到选择很重要的情况,我将在后面的章节中提到)。根据开发者的需求,箭头函数也可以以其他形式编写,但我会在我们到达其他代码时解释这些。
除了箭头函数的语法之外,该函数执行一个简单的任务——将文件发送回浏览器。发送回的文件是index.html,通过调用inResponse参数提供的sendFile()方法发送。inRequest和inResponse是 Express 和 Node 创建并传递给函数的对象,我们可以使用它们来构建响应。它们提供了许多方法和属性来检查传入的请求,从中获取数据,并生成响应,sendFile()就是其中之一。
然而,让我们谈谈传递给sendFile()的内容。在 JS 中,反引号字符(`)用于定义一种特殊的字符串,称为${},其中包含花括号之间的内容。那么这个“某物”是什么呢?嗯,它可以是有任何有效的 JS!发生的情况是,当 JS 遇到这样的模板字符串时,它会替换每个这样的表达式,从而在它内部执行 JS。
在这里,要执行的 JS 实际上是 Express 提供的变量名称——__dirname。当 JS 看到这样的变量名时,它只是将变量的当前值插入到字符串中。__dirname变量包含在运行时index.js文件所在的目录的完整路径。当我们将其与/index.html连接起来——这是当这个模板字符串被 JS 解释时发生的事情——我们得到指向该文件的完整路径,这恰好是sendFile()需要返回给浏览器文件的。
我们必须为导入的样式表文件添加另一个特定的路由;否则,你会发现样式不起作用,因为文件无法加载:
app.get("/styles.css", (inRequest, inResponse) => {
inResponse.sendFile(`${__dirname}/styles.css`);
});
在这种情况下,我们正在使用特定的 URL 提供特定的文件,这是在index.html中的<link>标签中指定的。记住,我们的服务器不会做我们没有特别编程它去做的事情,包括对浏览器请求该样式表文件的响应。
该路由将允许我们看到注册页面,但我们还需要一个路由来提交表单,所以我们将定义它:
app.post("/register", (inRequest, inResponse) => {
inResponse.send(`
<html>
<head>
<title>User Registration - SUCCESS</title>
<style>
th { background-color: #e0e0e0; }
tr { border: 1px solid #a0a0a0; }
td { border: 1px solid #a0a0a0; }
</style>
</head>
<body>
<h2>Thank you for registering! Here is the information we got:</h2>
这次,记住我们将使用 HTTP POST 方法将表单发送到服务器,所以这次我们必须调用 app.post()。这次 URL 将是 localhost/register,所以路由是 /register(再次强调,相对于基本 URL,在这个例子中只是域名)。
我们这次传递的处理函数直接从这段代码返回内容,而不是从单独的文件中返回,inResponse.send() 函数允许我们这样做。我们传递一个字符串,这个字符串被返回到浏览器。在这个例子中,这个字符串是实际要返回的 HTML 文档,我们的注册确认页面。在这里,你可以看到为什么能够将模板字面量字符串扩展到多行是件好事——想象一下,如果所有的 HTML 都放在一个单一的、长行中,那我们会怎么做,因为我们只能用普通的字符串来做。
我想,到这个时候,大部分的 HTML 对你来说应该已经很直观了,除了那里定义的样式类。根据选择器,你知道这些类必须为一些名为 th、tr 和 td 的 HTML 元素设置样式,但到目前为止,你还不知道它们是什么。让我们通过讨论一个新的 HTML 概念——表格——来解决这个问题。
介绍表格
HTML 中的表格与电子表格非常相似——它们允许我们在行和列中组织数据。我们用 <table> 标签开始一个表格,这是意料之中的。
<table style="border:4px solid black;">
默认情况下,表格不会有边框,但在这个例子中,我想让它有边框,所以我为它添加了内联样式。border 属性非常类似于你之前看到的 outline 属性。它也是那些我之前解释过的简写属性之一,在这种情况下,它具有与 outline 相同的值——边框的像素宽度、样式和颜色。
一旦我们有了表格,我们就可以开始用数据行填充它。每一行都由一个 <tr> 元素定义(简称表格行):
<tr>
<th>Title</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Email Address</th>
</tr>
在一行内,我们定义一个或多个列,这里我们有选择:
-
我们可以使用
<th>标签,它代表<th>。 -
我们可以使用
<td>标签,它代表<td>(或者,如果你根本不想有标题行,你可以直接从<td>开始)。
无论我们使用 <th> 还是 <td>,我们通常将它们统称为 单元格。
每个 <th> 元素定义一个列,由于这是标题行,所以 <th> 元素内的值是每个列的标题文本。在这种情况下,我们将在标题行之后只有一个单行:
<tr>
<td>${inRequest.body.title}</td>
<td>${inRequest.body.first_name}</td>
<td>${inRequest.body.last_name}</td>
<td>${inRequest.body.age}</td>
<td>${inRequest.body.email}</td>
</tr>
再次,<tr>创建一个行,然后对于每一列,我们都有一个<td>元素。记住,这个 HTML 是在 JS 代码中的模板字面量字符串内构建的,因此我们可以使用我之前提到的那些表达式。对于每个单元格,inRequest对象中的一个属性被使用——body。这将保存从表单发送到服务器的信息,然后我们可以使用表单字段的属性名来获取每条数据。
然后,我们只需关闭表格:
</table>
这的结果在图 3.4中显示:

图 3.4:注册确认屏幕,使用 HTML 表格
关于表格使用的警告
表格在 HTML 中经常被使用,有时甚至被滥用,因为如果需要,表格可以用来布局页面。<td>元素内的内容可以是任何东西,甚至是更多的 HTML,因此可以在<td>元素内构建嵌套的表格,并使用所有种类的复杂 HTML,利用表格结构来定义页面的结构。然而,这种做法因性能、标记的复杂性以及更哲学的“这根本不是表格的用途”等理由而受到批评。在 CSS 中有更可取的布局方式,其中一些方式最终看起来非常像表格,但具有更多的灵活性。我之所以提到这一点,是为了让你知道——表格不应该被视为用于布局的工具,尽管它们可以(在 CSS 演变到今天这个样子之前,这样做是非常常见的)。
现在,你有了理解之前在生成/register路由的响应时看到的tr、th和td所应用的 CSS 类的上下文——这些元素是应用样式的<tr>、<td>和<th>标签!简单来说,它们在表头行中的<th>元素上设置了背景颜色,以便它们从数据中突出出来,然后每个<tr>和<td>元素周围都放置了一个边框,这主要是为了美观,以便数据不会全部连在一起。
完成响应
为了正确地结束,我们需要关闭由我们的响应构建的 HTML 文档的主体,以及关闭inResponse.send()调用和app.post()函数调用。这在这里完成:
</body>
</html>
`);
});
完成这些后,唯一剩下要做的就是启动服务器。
启动服务器
启动服务器意味着告诉 Express 返回给我们的对象开始监听请求:
app.listen(80, () => {
console.log("Server listening on port 80");
});
app对象中名为listen()的方法正是允许我们做到这一点。我们传递给它要监听的端口,以及可选的启动时执行的函数。由于我们不需要传递任何参数给它,所以只需要一个空的括号组(因为毕竟我们还是在定义一个函数),然后我们再次使用箭头符号来表示函数。
好吧,运行服务器(npm run dev)并稍微玩一下,看看它的实际效果,包括启动文本的显示,正如你在图 3.5中看到的那样。5:

图 3.5:console.log()的输出
弃用警告
顺便说一句,你可以在前面的屏幕截图中看到的警告可以忽略。这是一个弃用警告,这意味着服务器代码所依赖的包中有些过程发生了变化。该包的作者提醒我们,最终我们需要以某种方式更改我们的代码;否则,它将崩溃(在这个例子中,变化是,之前对express.urlencoded()的调用最终将需要一个带有扩展属性的对象——如果你愿意,现在可以添加{extended:false}作为该调用的参数来消除消息,但这还不是必要的,这就是弃用警告的目的)。
说到启动文本,在传递给app.listen()的函数内部,有一个调用名为console的对象的log()方法。很容易推断出console是一个表示命令行输出的对象,通常被称为控制台。而且很容易猜测log()是允许我们将文本写入该控制台的方法。但所有这些都开启了一个全新的知识世界,我们将在稍后的爱上你的新最佳朋友—— 开发者工具部分稍作探讨。
好吧,那也不是那么糟糕,对吧?编写服务器很容易!然而,到目前为止,你只编写了一种类型的服务器。现在让我们谈谈另一种编写这个服务器和注册页面的方法,这种方法稍微现代一些。
探索不同的方法——单页应用程序
当你点击我们的注册页面上的按钮时,表单会被发送到服务器,然后生成一个新的 HTML 文档。这个文档被发送回浏览器,浏览器用结果页面替换了整个注册页面。这通常被(至少像我这样的相对较老的)人称为“经典”的网页模型。简而言之,对于每个用户操作,服务器都会被联系,生成一个新页面来显示。
实话实说,这是一个低效的模型。想象一下,有成千上万的用户都在做同样的事情——服务器必须快速处理所有这些请求,因为用户讨厌等待!但即使它几乎是瞬间的,从用户的角度来看——屏幕一直在重绘。这看起来并不好,至少不像他们在操作系统上运行的那些应用程序。想象一下,如果你的网页浏览器每次你点击工具栏上的菜单项时都必须完全重绘自己。然而,这种情况并没有发生;菜单只是简单地出现。那么,为什么一个网站不能以同样的方式工作呢?
实际上,它可以!一种被称为SPAs的方法做到了。SPA代表Single Page App,这意味着你最初加载的 HTML 文档永远不会被丢弃并替换为另一个。相反,当请求发送到服务器时,服务器只响应数据,而不是完整的 HTML 文档。然后我们使用 JS 来处理这些数据。这可能只是简单地将其显示在页面上,我们可以通过 DOM 函数来实现。或者,也许我们用它来进行一些计算,并在屏幕上显示这些计算的结果,或者也许我们用它以某种方式修改页面。在所有情况下,主要观点是,最初加载的单个 HTML 文档仍然存在。
这可能现在听起来有点令人困惑,但我认为一旦你看到我们将要对我们的这个小注册页面做的事情,它就会变得很有意义。然而,在我们触摸代码之前,我想解决一个小的麻烦,让我们的生活作为开发者变得容易一些!
通过自动化提高开发者生产力
你可能没有注意到这一点,但如果你要对index.js文件进行修改,比如更改表单提交时返回的 HTML,你会发现这个更改并不会立即生效。你必须停止并重新启动服务器,以便更改“生效”,这么说吧。对我来说,这相当不方便!如果你要做出很多更改,很快就会变得烦人。幸运的是,有一个简单的解决方案——nodemon!
首先,由于nodemon是一个 NPM 包,我们需要将其添加到我们的项目中。为此,执行以下命令:
npm install –save-dev nodemon
之前,你看到了如何使用–save选项添加 Express,它将其添加到package.json中的依赖项元素。–save-dev选项类似,但略有不同。你看,package.json中可能还有一个devDependencies元素。它的工作方式与dependencies元素相同,但有一个关键的区别——这些是我们开发期间需要的依赖项,但我们的代码本身并不依赖于它们;nodemon就是这样一种依赖项。因此,执行该命令后,你会发现已添加了一个devDependencies元素,并且nodemon已作为依赖项添加到其中(注意这次我没有指定显式的版本号,因此 NPM 将安装nodemon的最新版本——由于我们的代码不依赖于它,这是安全的,而且很可能是期望的,因为拥有最新版本可以给我们带来它可能提供的任何新功能)。
现在,nodemon所做的是监视(或监控,因此得名nodemon)我们的文件以检测更改,一旦检测到更改,就会为我们重新启动应用程序。然而,为了实现这一点,我们必须使用nodemon启动服务器。我们这样做的方式是修改之前添加到package.json中的dev脚本。现在,它应该是这样的:
"dev": "npx nodemon"
npx 命令是 npm 的一个替代版本,基本上是这么说,“运行我们项目中依赖提供的程序。”在这种情况下,当然是指 nodemon。但请注意,作为一个开发者,你仍然会执行相同的 npm run dev 命令来启动服务器。这个命令就是会启动 nodemon,按照脚本使用 npx。默认情况下,nodemon 将简单地监控项目目录中的所有 .js 文件,由于我们只有一个,所以这就足够了(你可以配置 nodemon 只监控特定的文件、子目录或许多其他选项,但默认设置对我们来说已经足够好了)。
编写新的客户端类型
现在,我们将修改我们的注册页面,使其作为一个单页应用(SPA)工作。而不是列出所有代码,我只会展示所需的更改,以节省一些树木!如果我没有提到特定的更改,那么你可以假设代码与原始版本相同。
第一个更改很简单:
<form id="theForm">
action 和 method 属性已被移除,并添加了 id 属性。移除 action 和 method 是一个技术上我不必做出的改变,但它清楚地表明,表单将不会像以前那样提交。相反,表单的信息将以完全不同的方式发送到服务器,我们很快就会了解到这一点。不过,添加 id 属性是必要的,正如你很快就会看到的。
唯一的另一个更改是按钮:
<button type="button" onClick="registerNow();">Register Now</button>
现在,type 属性的值只是 button 而不是 submit。这是因为 submit 类型创建了一个特殊的按钮,当点击时会知道将表单提交到服务器。但既然我们不想这样,它就变成了一个普通的按钮,点击时没有固有的动作。要给它一个动作,我们需要给它附加一个事件处理器。onClick 处理器当然是在用户点击按钮时执行的,在这种情况下,我们需要它执行一些 JavaScript,具体是 registerNow() 函数,我们很快就会看到。
但首先,需要添加一些新的标记。
嵌入响应页面
记住,我们在这里试图实现的整体目标是提交表单数据到服务器,获取一些数据回来,并在屏幕上显示,而不覆盖现有的内容。因此,我们需要一个地方来显示这些数据,这就是我们接下来要发现的:
<div id="theResults" style="display:none;">
<h2>Thank you for registering! Here is the information we got:</h2>
<table style="border:4px solid black;">
<tr>
<th>Title</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Email Address</th>
</tr>
<tr>
<td id="results_title"></td>
<td id="results_first_name"></td>
<td id="results_last_name"></td>
<td id="results_age"></td>
<td id="results_email"></td>
</tr>
</table>
</div>
这段代码应该对你来说相当熟悉——它几乎与原始版本中服务器生成的标记完全相同。关键的不同之处在于,现在所有内容都被包裹在一个带有theResults ID 的<div>中。此外,这里使用了内联样式。CSS 中的display属性告诉浏览器这个元素内的内容是否应该可见。将其设置为none,就像这里所做的那样,意味着这个整个<div>将不会显示,至少最初不会。实际上,如果你现在运行这段代码,你会发现它看起来与原始版本完全一样,但<div>内的内容都没有显示。
其次,显示数据的行中的内容也有所不同。我们不再有原始版本中的表达式,因为,记住,这不是一个模板字面量字符串,而只是普通的 HTML。相反,每个<td>元素现在都被赋予了一个id,这将允许我们稍后在代码中引用它们。并且我给id值加上了results_前缀,这样就可以一眼区分出表单中的字段。
而且,说到代码,这正是我们接下来要去的方向!
执行新的 JavaScript 代码
回到我之前提到的registerNow()函数,你会发现它被包含在一个紧接在</html>标签之前的<script>块中。这是你之前没有见过的——你之前只见过在<head>内部使用<script>标签。
如你所猜,你可以把<script>标签放在任何地方,因为,记住,它们不是展示给用户的内容,所以无论你把它们放在哪里,都不会影响你的标记。我把<script>标签放在最后,只有一个原因——这样我就可以先讨论标记变化,而不必用“我们稍后再回来”的方式处理 JavaScript 代码,如果它在<head>中,我就不得不这样做!
但现在,我们已经准备好查看它了,我将把它分成几个部分来更容易地消化,首先是这个:
<script>
async function registerNow() {
你之前见过函数的定义,但那个async特性是新的。通常情况下,当一个函数执行时,它会从开始到结束一次性完成。然而,有些情况下我们可能需要在函数中做一些可能需要花费一些时间的事情。一个很好的例子——这里正在使用的例子——就是调用服务器。我们需要一种方法,实际上让我们的函数“暂停”,直到服务器响应,而它通常不会这样做。async关键字告诉 JavaScript 我们在这里正在做类似的事情。单独来看,它只是方程的一部分,你很快就会看到另一部分。
但首先,让我们回到函数的代码:
const theForm = document.getElementById("theForm");
document对象中的getElementById()方法,这是一个浏览器始终为我们提供的对象,允许我们根据 ID 获取一个 DOM 节点的引用。在这种情况下,表单有一个id为theForm,所以theForm变量现在引用了这个表单。
从那里,我们可以从表单中提取数据:
const userInfo = {
title: theForm.elements["title"].value,
first_name: theForm.elements["first_name"].value,
last_name: theForm.elements["last_name"].value,
age: theForm.elements["age"].value,
email: theForm.elements["email"].value
};
console.log("userInfo to be sent to server", userInfo);
表单中的每个输入字段都是名为elements的对象中的一个元素,这个对象是theForm变量引用的表单对象的属性。因此,我们可以使用括号符号访问它们,就像访问对象的任何属性一样。
我们在这里做的是创建一个对象,它为每个表单字段定义了键。我们定义一个带有开闭花括号的对象,然后以键值对的形式列出我们想要其中的属性。然后,为了证明这起作用了,使用console.log()在控制台显示这个对象,就像你在服务器端看到的那样。然而,在这里,你看到了这个方法的其他能力——能够传递多个参数。每个参数都会通过一些空格分开显示。如果我们传递一个对象作为参数,就像这里的第二个参数一样,console.log()将为我们显示该对象的内容。这不是非常有帮助吗?
我将在本章的结尾更详细地讨论console以及它所暗示的更广阔的世界,所以现在就把它放在心里吧。
一旦我们有了这个对象,我们就可以使用 JS 调用服务器:
const response = await fetch("/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userInfo)
});
这就是async谜题的第二部分发挥作用的地方——await关键字。这告诉 JS,随后的函数或代码可能需要一些时间来执行,我们不希望函数的其他部分在它执行之前执行,就像它通常那样。我们的意思是想要等待这个调用的结果。
在这个情况下,我们正在等待对fetch()函数的调用结果。这是一个浏览器提供的函数,允许我们向远程系统发起 HTTP 请求——在这个例子中,就是我们的服务器。fetch()函数可以接收大量信息来完成其任务,但在其最简单的形式中,它需要两个参数——发送请求的 URL(在这个例子中是/register,就像这个页面的原始版本一样)和一个包含所需数据的对象,以便进行调用。在这里,我们首先指定使用POST作为 HTTP method,就像原始表单一样。然后,我们需要提供一些headers。回想一下,headers 是可以在请求中发送的元数据,可以用来告诉服务器有关该请求的一些信息。在这种情况下,我们告诉它我们正在发送一种称为 JSON 的数据。Content-Type header 是键值对中的键,允许我们表达这一点,其值是application/json。
你会在 headers 中经常看到这样的字符串,当你这样做的时候,这被称为 MIME 类型,其中application/json值在当今特别常见,就像之前的userInfo对象一样,它也是 JSON!服务器需要知道客户端正在发送 JSON,所以这个 header 和那个特定的 MIME 类型就做到了这一点。
之后,我们当然必须包含userInfo对象,这将成为请求的body部分。然而,当你发起一个 HTTP 请求时,body始终是一个字符串。然而,在这个时候,我们只有userInfo作为一个 JS 对象,而不是一个字符串。幸运的是,JS 引擎提供了一个名为JSON的对象,它为我们提供了访问几个有用方法的能力,其中stringify()就是其中之一。你只需将这个函数传递给一个 JS 对象,它就会返回该对象的字符串版本,然后我们可以将其作为请求的body。
一旦响应返回,它将被放入response变量中。然而,此时它将是一个字符串,而我们想要一个 JS 对象,以便从中获取值。为此,我们可以调用response对象的json()方法。然而,这又是一个异步调用,因此我们必须再次使用await,之后我们就可以显示结果对象:
const results = await response.json();
console.log("results from server", results);
最后,现在我们有了服务器返回数据的对象,最后一步是在屏幕上显示它(嗯,这是在控制台显示之后的最后一步,你可以在浏览器中这样做,就像在服务器端代码中一样):
document.getElementById("results_title").innerHTML =
results.title;
document.getElementById("results_first_name").innerHTML =
results.first_name;
document.getElementById("results_last_name").innerHTML =
results.last_name;
document.getElementById("results_age").innerHTML =
results.age;
document.getElementById("results_email").innerHTML =
results.email;
document.getElementById("theResults").style.display = "";
}
</script>
在这里,五个数据值被插入到页面中。同样,使用document.getElementById()函数,引用之前分配给<td>元素的 ID。这给我们返回一个 DOM 节点对象,许多 DOM 节点对象都有一个innerHTML属性。这允许我们在两个标签之间插入内容——在这种情况下,在对应于给定id值的<td>和</td>标签之间。要插入的文本只是response.json()给我们的results对象的各个属性的值。
现在,所有数据都插入到这里真是太好了,但它仍然不可见。为了使其可见,我们必须更改包含表格的<div>标签的显示 CSS 属性。DOM 节点几乎总是有一个style属性,这允许我们更改该节点上我们想要的任何 CSS 属性,因此我们可以将display属性设置为空字符串。这听起来可能有点奇怪,但默认情况下,display属性没有值,这告诉浏览器它应该被显示,所以我们就是这样做的。
因此,你现在可以看到服务器发送回来的数据,如图图 3.6所示:

图 3.6:调用服务器的结果
现在我们已经重新设计了客户端代码以适应这种新的 SPA 方法,让我们看看在服务器端需要进行哪些更改,以确保客户端和服务器能够以新的方式正确地相互通信。
编写一种新的服务器类型
好的,太棒了,我们已经修改了前端代码以准备这个伟大的新 SPA 版本;现在,我们需要在服务器上进行一些更改。再次提醒,我会只展示这些更改;然而,在这种情况下,我确实建议查看 GitHub 上的完整代码,因为你会发现它更短。如果你这么想,这是有道理的——原始版本中服务器返回的样式和 HTML 现在实际上是 index.html 源代码的一部分。
首先,我们必须告诉 Express 它需要能够处理 JSON:
app.use(express.json());
express.json() 方法返回一个函数——这是 Express 中间件的一部分——它知道如何处理 JSON。太棒了,这很简单!
下一个变化出现在对 /register 路由的 POST 请求的处理函数中:
app.post("/register", (inRequest, inResponse) => {
console.log("Data sent from client", inRequest.body);
const responseObj = {
title: inRequest.body.title,
first_name: inRequest.body.first_name,
last_name: inRequest.body.last_name,
age: inRequest.body.age,
email: inRequest.body.email
};
inResponse.send(responseObj);
});
就像我说的一样,这更简洁!在这个应用中,服务器的唯一真正目的是将接收到的信息回显给客户端。通常,你可能需要以某种方式验证这些数据,将其存储在数据库中等等。但请记住,我只是在尝试演示客户端-服务器交互,这是本章的全部重点,所以这些都不需要发挥作用。
然而,我们将首先通过调用 console.log() 显示接收到的数据,以便在需要调试时可以使用。之后,创建了一个对象,由 responseObj 变量引用,就像你之前在客户端看到的。然后,这个对象被传递给 inResponse.send(),这导致自动调用 JSON.stringify(),就像你之前看到的,因为 Express 知道我们想要返回 JSON(以字符串的形式)。
让我们从头到尾梳理一下从客户端到服务器的整个流程,详细说明涉及的步骤:
-
index.html中的 JS 发送了一个 JSON 字符串形式的对象到服务器。 -
Express 将那个字符串转换成了一个 JS 对象。
-
我们的服务器端代码随后利用那个 JS 对象的特性来构建另一个对象,并将发送的数据复制到其中。
-
那个对象随后以 JSON 字符串的形式返回给浏览器中的 JS。
-
浏览器中的代码将字符串转换回对象。
-
然后,我们使用那个对象中的数据来填充屏幕上隐藏的表格单元格。
-
然后,我们通过更新包含
<table>的<div>的样式来显示表格。
关键的是,请注意原始注册表单仍然在那里,没有被服务器响应所触及。简而言之,这就是这个小型简单应用的新 SPA 版本的全部意义。如果你想象在一个更大更宏伟的规模上,比如 Google 的 Gmail 或 Microsoft 的 Office Online,那么你可能会很快开始看到好处。当然,涉及的代码更多,但与“经典”的 Web 方法相比,这种方法通常能带来更好的性能(通常),更少的服务器负载(通常),以及更少的网络流量(通常),这就是为什么前者实际上是今天整体上更常见的 Web 开发方法。当然,经典 Web 方法仍然有它的位置,但随着时间的推移,这个位置正在缩小。
现在你已经看到了这两种方法——“经典”的 Web 模型和 SPA 模型——让我们讨论我提到 console.log() 函数时暗示的话题——开发者工具。
爱上你的新最佳朋友——开发者工具
虽然 console.log() 在服务器端的 Node 代码中可用,但它只是浏览器客户端可用的更大工具集的一个极小部分。要开始探索这个工具集,请访问你选择的任意网站,比如 www.microsoft.com 或其他知名网站,页面加载完成后按 F12。你应该会在浏览器中打开一个新区域,看起来像 图 3**.7:

图 3.7:浏览器开发者工具“行动”中的样子
这些是浏览器开发者工具,它们以某种形式存在于所有现代 Web 浏览器中。在这里,你可以深入了解你正在查看的网站。正如你所看到的,有许多不同的标签页,每个标签页都包含大量数据:
-
console.log()语句。你还可以在这里看到 JS 错误以及网络请求。实际上,现在是一个很好的时机,在查看我们的小型用户注册应用时打开开发者工具,看看那里输出的console.log()消息是什么,尽管请记住,你只能看到在浏览器中运行的代码的这些消息,而不是在 Node 服务器端运行的代码中的那些(那些是输出到控制台的)。 -
你最可能经常使用的另一个标签页是网络标签页。在这里,你同样可以看到网络请求,但你会看到比控制台标签页中更多的请求,以及更多关于它们的细节。实际上,假设你现在正在某个网站上查看那个标签页,就点击一个请求并检查一些细节。你可以看到诸如请求发送的头部和如果有内容的话,请求的主体内容。
-
元素选项卡是另一个极其重要的区域,因为在这里你可以检查 DOM,检查单个节点,甚至可以直接更改它们的样式。是的,你实际上可以更改别人的网页内容!当然不是永久性的,只是在你的浏览器中。但这是了解其他开发者是如何做事以及你可以以多种方式修改他们的工作的绝佳方式。
-
源选项卡是你可以看到构成网站的各个 JS 文件、CSS 文件和其他资源的区域。同样,在这里你可以实际编辑这些代码,暂时性地。
-
另一个我认为最重要的选项卡是应用程序选项卡。在这里你可以找到诸如 cookie(网站存储在您计算机上的小信息块)和其他一些网站在您的机器上存储的数据。
还有许多其他选项卡,其中一些默认隐藏,除非你决定需要它们,但我会说这四个可能是你发现自己最常使用的。
但让我们暂时回到那个控制台对象,因为 log() 不是它提供的唯一方法。虽然不是详尽的列表,但以下是一些我认为你会觉得最有趣的:
-
clear(): 嗯,这并不令人惊讶,但它确实清除了控制台!这在你的代码中想要关注某些消息并去除旧消息的部分非常有用。 -
debug()/error()/info()/warn(): 这些方法与log()类似,但它们输出的消息通常会根据消息的严重性以某种方式着色或突出显示。例如,你可以调用console.error()以加粗红色文本显示消息,从而使错误与仅包含信息的消息(你可能使用console.info()或console.log()输出)区分开来。 -
time()/timeEnd()/timeLog(): 调用console.time()就像按下秒表的开始按钮一样,然后console.timeEnd()就像再次点击它一样,此时你可以调用console.timeLog()来显示两次(虚拟)点击之间的经过时间。当你想要找出某些代码执行所需的时间时,这非常有用,通常用于性能调整。 -
dir(): 这个名字可能不是最好的,但它非常实用。它显示了一个指定 JS 对象属性的交互式列表。你可以展开和折叠它的部分,这使得导航更大的对象变得容易得多。
与 DevTools 中的选项卡一样,还有许多其他方法,但这些可能是最常见的。请注意,在 Node 代码中可用的 console 对象具有这些方法中的大多数,尽管日志函数可能不会以与浏览器中相同的样式输出消息。
作为一名网页开发者,你会发现自己在使用浏览器 DevTools 时会非常频繁,所以我非常鼓励你用它们探索一些随机的网站,看看你能用它们做什么。那里有非常多的功能——如果你愿意,你可以只写一本关于浏览器 DevTools 的书——了解它们的能力的最佳方式就是简单地跳进去实验。
为了帮助你——假设你使用的是基于 Chromium 的浏览器,如 Chrome、Edge、Opera、Vivaldi 或 Brave——直接查看源代码可能是最好的选择,这意味着 Google DevTools 团队的网站:developer.chrome.com/docs/devtools。请注意,非 Chromium 浏览器,如 Firefox 和 Safari,也有 DevTools,它们将非常相似,尽管肯定存在差异,也许还有它们自己的功能。
记住,你可以随意玩弄 DevTools 而不会造成任何伤害,因为你不是在为任何人的网站修改代码,而是在自己的机器上,所以没有理由不捣鼓一下!如果你陷入困境,只需重新加载页面,一切就会恢复正常。
检视路线图
本章结束后,我们现在可以在我们的网页开发者路线图上填入一些更多的盒子:

图 3.8:路线图,填入了一些更多的盒子
通过本章,你在路线图上打开了网络和开发者工具的盒子。当然,你也在前一章中建立了一些关于已打开的盒子的知识。希望现在你的脑海中已经开始形成一幅大图,至少是一点点。接下来的章节当然会扩展这幅图——有的稍微一点,有的很多,但总是不断扩展!
摘要
在本章中,你使用“经典网络”方法构建了一个简单的用户注册页面和与之配合的服务器。你首先使用一些新概念构建了前端,包括基本的 HTML 表单来展示如何通过这种方式向服务器提交数据,以及一些新的 CSS 属性,包括缩写属性。然后,你看到了如何构建一个接受这些数据并为客户端生成响应的服务器,该响应随后在浏览器中使用另一个新概念,HTML 表格,来显示表格数据。
然后,你看到了一种完全不同的将数据传输到服务器并接收响应的方法,这种方法导致更现代的 SPA 范式。这包括构建前端代码,引入了一些新的 JS 概念,包括async/await和fetch()。你看到了服务器如何处理来自客户端的新类型请求并使用 JSON 生成新类型的响应,允许浏览器在不覆盖整个页面的情况下显示结果。最后,你看到了你的网络浏览器为开发者提供专门用于检查页面、探索 JS 和 CSS 以及在其背后的代码级别上与任何网站交互的工具。
在下一章中,我们将稍微从代码中退后一步(但并非完全退后!)并探讨一些网络开发者应该熟悉的其它主题,包括源代码控制、Linux、安全、集成开发环境以及 Python。
第二部分:扩展你的知识库
在这部分,你将继续获取关于网络开发的技术知识,包括开发工具、安全顾虑、云基础设施、虚拟机和容器,以及 DevOps。
我们还将讨论现代前端框架,如 React、Angular 和 Vue,并了解它们如何用于加速开发,以及讨论前端上的响应式设计。
然后,我们将转向查看一些后端关注点和技术,包括如何使用 Node.js 和 Express、Java 和 Spring Boot、以及 PHP 和 Python 构建服务器。
最后,我们将开始讨论一些旨在让你在减少编码的同时成为一名网络开发者的技术。这些包括内容管理系统(如 WordPress 和 Drupal)以及所谓的“无代码”解决方案,例如 GoDaddy 的网站构建器。
本部分包括以下章节:
-
第四章,管理、安全和处理代码
-
第五章,理解用户体验、部署选项和 DevOps
-
第六章,探索现代前端开发
-
第七章,从前端到后端 - 更多关于后端工具和技术
-
第八章,编写更少的代码 - CMS、无代码解决方案和生成式 AI
第四章:管理、安全和协作代码
在过去的两章中,我们花了很多时间查看实际代码,让你熟悉网络开发的细节。很明显,你大部分的时间可能会花在“玩弄位元”,正如我们这些软件极客喜欢说的!
但你也看到了,这不仅仅是编写代码那么简单。在本章中,我们将探讨更多“非编写代码”的事情,包括源控制(如何跟踪代码的变化)、操作系统(特别是 Linux)、集成开发环境(IDE)(帮助你在一个统一的应用程序中编写代码的工具)、安全,甚至一些常见的语言,这些语言对于网络开发者来说经常被用到。
在本章中,我们将简要介绍以下“非代码”主题:
-
控制代码 - 版本控制系统
-
像黑客一样思考 - 保护网络
-
在一个地方完成所有工作 - 集成开发环境
-
理解操作系统 - Linux
技术要求
就像上一章一样,我将假设你已经从 GitHub 下载了本书的源代码,正如在第二章的“技术要求”部分中概述的那样。对于本章,我们还将处理 ch-03/2-spa 目录中的代码。
你还需要做的一件事是安装 Git。为此,请访问 git-scm.com 并找到适合您系统的下载包。我将把这留作你的练习,但没什么不寻常的,所以我知道你能处理它。
控制代码 - 版本控制系统
好吧;所以,你已经花了几小时编写代码——也许你构建了一个小巧的用户注册页面,例如。就像黑手党的人可能会说:如果代码出了问题,那真是太遗憾了,不是吗?当然,一定有保护你代码的方法,而且确实有:版本控制。
让我先从告诉你我们这些老古董是如何做版本控制的开始。
我们会不时地在项目目录中创建一个新的目录,并给它起一个像“HTML_complete_8-6-1991_1-30pm”这样的名字。然后,我们会把项目中的所有文件复制到里面。我们会继续工作在项目上,也许稍后我们还会创建另一个名为“JS_complete_8-6-1991_4:30pm”的目录,再次将所有项目文件复制进去。
想想看。在我完成项目的 HTML 代码时,我实际上备份了项目所有文件当前的状态。当 JavaScript 完成时,我又做了另一个副本。我所拥有的就是项目文件的历史记录。后来,如果我在处理 JavaScript 时犯了一个错误,发现它突然不工作了,我可以回到那些目录之一,并将 JavaScript 与它最后一次工作时的状态进行比较。
虽然这很丑陋且完全手动,但这本质上是一种源代码控制,或者通常称为版本控制(很明显的原因:每个目录代表代码的一个过去版本)。
此外,我们经常,通常在一天结束时,将所有这些目录复制到某个中央服务器上,一个始终得到适当备份的服务器。这提供了一层保护:即使我们的电脑硬盘损坏,我们也会在服务器上有一个代码副本。如果我们正在编码,并且最近没有创建新的版本目录,我们可能会丢失一些工作,但比丢失一切要好。至少在最坏的情况下,我们可以回到最后保存的版本。
它是一个很好的系统,因为它提供了历史记录和一定程度的保护,但因为是手动操作,所以很容易出错。此外,它并不十分健壮。例如,如果我们需要将一个文件与之前的版本进行比较,我们该如何操作?当时,这“简单”到“将两个文件并排放置在屏幕上,逐个检查,试图找出变化。”这不是一个好的方法。然后还有其他你可能想要做的事情,比如在代码旁边记录你的进度。当然,你可以创建一个notes.txt文件并保存它,但同样,这也是一个手动过程,而且它因开发者而异,因为没有人会使用相同的目录命名约定和方法。
当然,现在,几十年后,肯定有更好的答案,对吧?确实如此:源代码管理(SCM)。
介绍 SCM
这就是软件配置管理(SCM)发挥作用的地方。我们所说的 SCM 系统,或者说版本控制系统,是一种软件,它会自动记录项目文件随时间的变化。SCM 为你提供了查看项目历史、比较文件、召回特定版本的文件、为你的更改添加注释等功能。
他们还做了在那些丑陋的手动源代码控制时代难以做到的事情:他们允许多个开发者同时处理同一份代码。他们通过跟踪每个贡献者的更改并管理合并过程来实现这一点。合并是指你有两个文件副本,分别来自两个人(通常是——尽管也可能是同一个人)的更改,你需要将这些更改合并成一个文件的规范版本。当你是一个单独的开发者在一个项目上工作时,这种情况不太可能发生,但在团队开发中,这是一个至关重要的关注点。
SCM 被认为是软件开发的基础工具,这意味着在一个专业环境中,你几乎总是使用 SCM。如今,一个单独的开发者不使用 SCM 的情况甚至变得不寻常,因为能够回溯到过去,从某种意义上说,查看文件在各个时间点的样子,具有巨大的价值。
SCM 有几个关键点:
-
版本跟踪:源代码管理(SCM)记录了存储库中每个文件所做的每个更改的历史,或简称为“repo”,这是描述你的项目目录的另一种方式(它并不完全相同,但从概念上讲,你现在可以这样思考)。它不仅记录了文件的内容,还包括了谁更改了它以及何时进行更改,通常还包括开发者对更改的一些注释。这有助于跟踪项目进度,了解代码库是如何演变的,当然也为你提供了一个回滚更改的方法。
-
回滚:这仅仅是当发现问题(或者你只是决定后续的更改可以被丢弃)时,能够检索文件先前版本的能力。
-
协作:多个开发者可以同时在同一代码库上工作。源代码管理(SCM)系统管理来自不同人的更改并将它们合并,允许在没有覆盖彼此贡献的情况下进行协作工作。
-
分支:有时,开发者可能需要做一些实验性的工作。为了避免这些实验破坏现有的可能良好的代码,他们可以创建一个分支,这实际上是在创建分支时项目的副本。然后开发者进行他们的工作,将更改保存到该分支,同时不触及良好代码。最终,如果实验证明是成功的,它可以合并到良好代码中,使其成为主项目代码的永久部分。
-
冲突解决:当两个文件的副本合并时,有时可能会发生冲突——例如,如果两个开发者修改了同一行代码。当这种情况发生时,源代码管理(SCM)可以提醒试图合并代码的开发者存在需要手动解决的冲突(通常意味着决定更改应该来自哪个文件),然后完成合并。
-
安全性:源代码管理(SCM)充当一种备份。如果项目文件损坏,可以从 SCM 系统中恢复。
现在你已经知道了什么是源代码管理(SCM),让我们来谈谈你在源代码管理系统中拥有的选项。
接受错误的选择——Git 就是!
好吧,我就直接说重点了:现在你几乎在源代码管理(SCM)系统选择上没有选择,它将是一种叫做 Git 的东西。
现在市面上有几种源代码管理(SCM)系统;如 Mercurial、CVS、Subversion、Bitbucket 和 Perforce 等名字你可能听说过。但简单的事实是,整个行业几乎已经完全围绕 Git 聚集,Git 是由创建了 Linux 操作系统的人创建的:林纳斯·托瓦兹。Git 是在 2005 年为那些在 Linux 内核上工作的人构建的,以便他们能够更容易地协作。
Git 与其他许多 SCM 系统相比,有些独特之处在于它是分布式的。这意味着每个开发者都有一个 Git 仓库的完整副本,包括所有更改的完整历史记录。这在其他大多数 SCM 系统中是不寻常的,因为它们是集中的,意味着有一个代码的权威副本,以及与之相关的所有历史记录,开发者只能在他们的机器上获取代码副本来工作,而不会得到完整的历史记录。分布式 SCM 的好处是它们不需要像集中式 SCM 那样始终连接到中央服务器(尽管需要注意的是,在大多数情况下,即使使用 Git,也存在一个集中副本——只是你不需要像使用其他 SCM 系统那样始终与之保持连接)。
Git 因其许多原因而变得流行,但其中一个重要原因是速度和性能。与其他 SCM 相比,合并更改有时可能需要相当长的时间。我们这里不是在说几个小时,但即使在工作日中,几秒钟的累积也会产生影响。Git 从一开始就非常优化速度,大多数操作几乎瞬间完成。Git 性能赢得大多数开发者青睐的关键方式之一是分支。Git 进行分支的方式在大多数其他 SCM 系统中是根本不同的,并且更加轻量。例如,在 Subversion 中创建分支可能需要几秒钟,甚至几分钟,如果分支的内容足够大,而 Git,无论内容大小,几乎都是瞬间的。
由于 Git 迅速获得了大量人气——并且到目前为止,它是软件开发界的既定标准 SCM——每个人都开始将其集成到其他工具中。因此,现在,今天,你几乎可以在所有开发者工具中使用 Git,其中一些我们将在本章后面讨论。它有一个非常大的社区和支持系统,所以无论何时你在 Git 中遇到问题,你都不会有问题获得帮助。
Git 可以从命令行(相对)简单地使用,许多开发者会告诉你这是使用 Git 的最佳方式。我不是那些开发者之一。但我确实认为你应该从这里开始,所以这正是我们现在要做的!
开始使用 Git
我们在这里要做的,是将上一章的项目(更具体地说,是源代码下载中ch-03/2-spa目录下的 SPA 版本)添加源代码控制。这允许你开始对代码进行修改,并让 Git 为你跟踪和管理这些更改,为你提供所有 SCM(源代码管理)带来的好处(例如,以后能够回退到之前的版本)。
你需要做的第一件事是复制项目目录。然后,在其内部查找.git目录:
-
如果你找到了,就继续删除它
-
如果你没有看到它,那没关系——你可以继续进行
这个目录是 Git 创建的用于存储它执行工作所需信息的目录。如果您从 GitHub 获取了这本书的源代码,那么它可能包含那个目录。然而,由于我们在这里要执行的操作将导致创建该目录,您需要确保您从一个没有任何 .git 目录的干净状态开始。
完成这些操作后,转到命令提示符并输入以下命令:
git –-version
按Enter键并确保您得到一个类似于git version 2.42.0.windows.2(版本可能不同,如果您在 Mac 或 Linux 上,则不会显示windows,但您应该明白这个意思)。只要发生这种情况,您就可以使用 Git 了。
到此为止,导航到您创建的项目目录的副本,让我们将这个项目置于源代码控制之下!
使用最常用的 Git 命令
在基本级别上使用 Git(这在大多数日子里都是您所需要的)并不太困难,并且归结为可能是一打常见的命令。现在,让我们通过使用上一章中的一个项目目录——比如说 ch-03/2-spa 目录——来运行这些命令。现在启动命令提示符并导航到该目录。
设置新仓库(或克隆现有仓库)
要设置新仓库,第一步是告诉 Git 您希望当前目录成为一个 Git 仓库:
git init
您应该看到类似于图 4.11*所示的内容:

图 4.1:初始化 Git 仓库
注意,在执行 git init 命令后,我列出了目录,但没有看到可见的 .git 目录,这是 Git 创建的用于存储有关您仓库的所有元数据的目录。这是因为它是隐藏的。因此,要查看它,您必须执行 dir /a。这确认了 Git 已经完成了它的工作,我们现在有一个仓库了。
Linux 和 Mac 用户注意事项
在这本书的整个过程中,因为我主要是一个 Windows 用户,所以我主要写了以 Windows 为中心的书籍。在大多数情况下,您在 Linux 或 Mac 机器上看到和做的将是一样的。但是,在少数情况下,Windows 和 Linux 或 Mac 之间会有所不同。一个例子是,虽然 dir /a 在 Windows 上有效,但在 Linux 或 Mac 下,您将使用 ls -a 代替。
当然,您可能并不总是从开始一个项目和相应的仓库。如果您开始处理别人的项目,您很可能会做被称为克隆他们仓库的事情。这意味着将他们的仓库复制到您的本地机器上。为此,您可以使用这个命令代替:
git clone <url>
这将为您初始化仓库的结果相同:当前目录变成一个 Git 仓库,并包含一个隐藏的 .git 目录。
初始化现有仓库和克隆现有仓库之间的主要区别在于,克隆还会将仓库中的所有文件复制到当前目录中。然而,在初始化一个空仓库后,仓库中没有任何文件(尽管它们可能物理上存在于目录中——但 Git 还没有意识到它们)。
添加文件
仅创建一个仓库并没有做什么。在这个阶段,Git 甚至还没有意识到目录中的任何文件,或者你可能稍后添加的任何文件。你必须告诉 Git 你想要它跟踪的每个文件。为此,你可以使用 git add 命令:
git add <file>
例如,在我们的项目中,目前应该有四个文件:index.html、index.js、package.json 和 styles.css。因此,为每个文件执行 git add 命令,并执行以下四个命令:
git add index.html
git add index.js
git add package.json
git add styles.css
此外,仅作为一个例子,如果你使用了一个错误的名字,输入以下命令:
git add badName.txt
输出应该像图 4.2*中所示:

图 4.2:添加项目文件,加上一个错误!
如你所见,我也尝试添加了一个不存在的文件,你可以看到 Git 提醒我们这一点。此外,请注意 Git 为 package.json 文件提供的警告。这取决于你的操作系统,无论如何,这不应该对我们有很大影响,但 Git 让我们知道它将做什么是很好的。
通常,没有错误或警告意味着命令成功。这是 Linux 命令中常见的模式,你需要记住这一点。Git 与 Linux 开发内在相关,如前所述,因此它以这种方式工作是有意义的。
检查状态
为了确保 Git 现在知道我们的文件,我们可以要求它提供当前状态:
git status
此命令显示更改的状态。在图 4.3*中,你可以看到所有四个文件都需要提交。这意味着它们位于 Git 仓库的一个特殊区域,称为暂存区。简单来说,这就是 Git 已知的更改,但尚未提交——也就是说,尚未成为仓库的永久部分——存储的地方:

图 4.3:状态,包括暂存区的内容
提交更改
我们如何将更改从暂存区移动到仓库本身?这很简单:我们提交它们!
提交更改可以使用 git commit 命令:
git commit -m "<commit message>"
你必须提供一条与提交一起的消息,这应该是对更改的一些有意义的描述。如果你正在开发一个新功能,并且此代码实现了它,那么你可能想说“这些更改实现了 X 变更。”但由你决定—— whatever makes sense. 你可能会输入如下内容,我认为这是一个足够合理的消息:
git commit -m "Adding initial files to repo"
在图 4.4*中,你可以看到该命令的结果:

图 4.4:将我们的项目文件提交到仓库
你还可以看到我再次执行了 git status,现在暂存区中没有文件,正如预期的那样。
查看提交历史
要查看你仓库中发生的提交列表,执行 git log 命令:
git log
此命令显示当前分支的按时间顺序的提交历史。我很快会谈到分支,但现在您只需要知道 Git 默认创建一个master分支,除非您告诉它否则,所有的工作都会放在那里。请注意,master有时被称为main,这取决于您使用的 Git 版本或可能覆盖 Git 的工具。无论您看到哪个名称,它都意味着相同的意思。
在图 4.5*中,您可以查看该命令的输出结果:

图 4.5:git log 命令的实际操作
但如果您想查看哪些文件被更改了呢?默认情况下,git log只会列出发生的提交,包括消息、日期/时间和谁提交了它。但这就是另一个命令git show发挥作用的地方。在图 4.5*中,我在执行git log命令之后执行了它,那里显示了我作为那次提交的一部分添加到仓库中的文件列表。如果您省略了–name-only选项,它还会显示那些文件的每个内容,以及作为那次提交的一部分它们的变化(显然,当我们添加文件时,从不在仓库中到在仓库中,从 Git 的角度来看,整个文件都发生了变化)。
顺便问一下,–name-only选项后面的那个e0b60a5e是什么东西?那是一个哈希值,在这个上下文中,它是与提交相关联的唯一标识符。更确切地说,它是哈希值的前 8 个字符——您可以在git log和git show输出的commit词后面看到完整的哈希值。8 个字符几乎总是足够保证 Git 可以找到唯一的哈希值(原因涉及到密码学和高级数学,所以您必须相信这是几乎不可能的,两个提交的哈希值相同),这是我们需要的所有内容(尽管如果您愿意,您可以输入完整的哈希值)!
删除文件和恢复已删除的文件
如果您可以向 Git 仓库添加文件,那么当然您也可以删除它们,而且确实可以!要删除文件,Git 提供了rm命令:
git rm <file>
与添加文件一样,git rm只影响暂存区。要删除文件,您需要再次执行git commit以使更改在仓库中永久化。
但而不仅仅是这样做,我想向您展示另一个重要的东西:恢复已删除的文件。在图 4.6*中,我已经删除了一个文件,但在那之后我还做了一些其他的事情(注意,您应该从上到下、从左到右阅读,因为这是一系列连续的事件,但我不得不将屏幕截图分成两半以适应这里):

图 4.6:删除和恢复文件
首先,我列出了目录以查看文件。接下来,我执行了git status命令以确保没有暂存更改。然后,我使用git rm styles.css删除了styles.css文件,立即列出目录以确认它已被删除,然后再次执行git status命令以查看它已被标记为已删除。在这个时候,删除操作是暂存的,但它还没有从仓库中永久删除。
由于这个事实,还有时间保存文件!首先,我们需要告诉 Git 忽略暂存区的删除操作。这可以通过以下命令完成:
git restore –-staged <file>
然而,当你再次列出目录时,你会发现文件仍然不存在。我们必须现在告诉 Git 物理恢复文件,因为之前的命令只是告诉它我们不再想删除它。所以,执行以下命令就能解决问题:
git restore <file>
但你必须先执行带有–-staged选项的上一条命令。Git 有时可能会有些令人困惑,确实如此,大部分的困惑都源于这种双重提交过程:首先,你告诉 Git 你想要做出的更改(你将它们暂存),然后你对仓库做出更改(你提交它们)。
即使你提交了删除操作,仍然有方法恢复文件。你必须使用git log命令回顾历史,找到你想要的版本,然后使用另一个命令来恢复它:
git checkout <commit hash>
这将把文件恢复到你的项目中,此时你可以根据需要重新提交它。
创建分支
当你在 Git 中工作时,你有时会想要工作在可能或可能不会最终进入你的仓库的某些代码上。也许你正在实验,不想冒破坏你良好、有效代码的风险。在这种情况下,你可以创建一个新的分支,你可以将其视为仓库中所有代码的完全独立的副本。你给分支起一个名字,然后通过这样做,你可以切换到不同的分支,这会改变你的更改存储的位置(或者换句话说,哪个代码副本受到影响)。一个分支(你的代码副本)的更改不会影响任何其他分支,除非你明确告诉 Git 你想让它们影响。
要创建分支,使用以下命令:
git branch <branch name>
这将创建一个新的分支,而你的master分支(Git 默认创建并使用的分支)保持原样。在这个时候,分支是你之前正在工作的分支的副本。在我们的例子中,这是master分支,因为我们只有一个分支。
切换分支
为了确保你的更改进入新分支,你必须切换到它。你可以在任何时间点通过简单的命令来切换到分支:
git checkout <branch name>
现在,你添加和提交的任何文件都将进入命名的分支。包括master在内的任何其他分支都不会受到影响。所以,你可以自由地实验,确信你之前的代码是安全可靠的。
合并
然而,最终,一旦你的实验分支工作正常,你可能会想要将代码从你的实验分支推送到 master 分支。这就是合并的作用。合并简单地说就是从一个地方取走更改并将其放入另一个地方。在 Git 的情况下,这些地方是分支。
假设你在一个名为 experiment 的分支上工作。你在这段代码上工作了一段时间,使其工作正常,现在你想要将这些更改合并到你的 master 分支中。你首先切换到 master 分支,然后执行以下命令:
git merge <branch>
假设 <branch> 是 experiment,那么该分支的更改将被合并到 master。新文件将被复制进来,已删除的文件将被移除,现有文件的更改将在 master 中进行。最终,experiment 分支中的所有内容,包括所有与你可能做的所有提交相关的历史记录,现在也将出现在 master 中。
如果你愿意,你现在可以删除 experiment 分支,因为你不再需要它:
git branch -d experiment
为了明确起见,虽然分支不是我会称之为高级主题的东西——因为它确实很常见——但它是一个完全可选的主题,你永远不必使用分支。这就是为什么我没有展示这些命令的例子——如果你需要,你可以了解它们。
在使用 Git 时,开发者倾向于采用一种特定的工作流程,这意味着他们如何与 Git 交互并推进工作。虽然一些 Git 工作流程非常强调分支,但其他工作流程则积极避免使用分支。一些工作流程规定所有工作都应该在小的“功能”分支上完成,然后合并到 master。另一些则说工作应该在 master 上完成,但每个发布都应该从 master 创建一个新的分支。还有一些工作流程说 master 应该始终是唯一的分支,而标签——Git 的一个功能,在概念上允许你在给定的时间点创建仓库的命名副本——是前进的方式。
在任何情况下,只要你知道什么是分支——你现在知道了!——你应该准备好处理你遇到的任何工作流程。而且,当你独自在一个项目上工作时,你可以自由地实施任何你喜欢的流程,或者保持简单,始终只处理 master。无论如何,你都会得到版本控制的好处。
我们需要讨论的最后一个话题与你在使用远程仓库时的情况有关。
推送和拉取更改
到目前为止,你只处理了本地仓库,这意味着它就在你的机器上。这是好事,因为它提供了版本控制和跟踪,但它不允许其他人与你一起工作,也不能保护你免受机器硬件故障的影响。为了处理这两个问题,你通常会处理远程仓库。
从本质上讲,远程仓库与本地仓库没有区别,只是它可以通过网络访问。如果你一直在跟随,那么在某种程度上,当你从 GitHub 获取这本书的代码副本时,你已经处理了一个远程仓库。但你很可能只下载了仓库中的文件副本,它实际上已经不再是一个 Git 仓库(如果没有隐藏的.git目录,那就是这种情况)。
当你与远程仓库一起工作时,无论你是自己创建它还是克隆别人的,你都需要考虑两个新的概念:推送和拉取。
当你对你本地的仓库进行更改时,即使它是别人远程仓库的副本,这也只会影响你的本地副本。要将你的提交更改推送到远程仓库,你必须将更改推送到它:
git push <remote> <branch>
这个命令将你的提交更改发送到远程仓库。remote值是远程仓库的名称,通常情况下会是origin。branch值是你想要从本地仓库中推送到远程仓库的分支。
这很好,但如何从远程仓库获取新的更改呢?要从远程仓库获取更改,你需要拉取它们:
git pull <remote> <branch>
这个命令将远程服务器上的更改合并到你的工作目录中。与推送类似,remote值通常会是origin,而branch是你想要将更改合并到本地仓库中的哪个分支。
只需要这两个命令,你就可以与远程仓库交互,允许你和他人同时工作在代码上。但那个远程仓库会住在哪里呢?有很多选择,包括自己托管,但最受欢迎的一个你已经见过:GitHub!
GitHub – Git,但更友好
GitHub对于 Git 来说,就像电脑对于电力一样。虽然电力和 Git 是实际工作的,但你可能更愿意与电脑和 GitHub 交互!正如你在上一节中看到的,你可以在命令行上使用 Git,但你可能已经注意到这不是最愉快的体验。而且请相信我,你看到的只是基础……它会变得更糟!
但 GitHub 通过提供一种更用户友好的方式来与 Git 一起工作来解决这一问题。GitHub,正如你访问这本书的代码时可能看到的那样,是一个基于网络的平台,开发者在这里存储和管理他们的项目,并且可以选择将其提供给全世界。它是建立在 Git 之上的,虽然 Git 负责跟踪项目中文件变更的繁琐细节,但 GitHub 提供了一个用户友好的界面来与之交互,以及许多额外功能,例如错误跟踪、任务管理、为开发者提供的社会化网络功能,甚至可以托管项目,如网站(称为 GitHub Pages)。将 Git 想象为引擎盖下的引擎,GitHub 则是围绕它构建的汽车,比单独的引擎提供更舒适和功能丰富的驾驶体验(哇,想象一下有人在路上骑着汽车引擎的画面,是不是很有趣?)。
GitHub 允许你克隆一个仓库,在本地工作,然后将更改推送到它(前提是你有权限这样做,这是它在 Git 之上添加的附加功能之一)。GitHub 还提供 Actions,这是仓库所有者可以设置在发生各种事件时执行的代码片段。例如,可能有一个动作在有人向仓库推送时构建项目,然后部署项目的新的版本,比如一个网站,所有人都可以访问。
GitHub 为仓库添加了一个问题跟踪器,这样你的项目的用户就可以提交错误报告,而你也可以跟踪和管理他们解决问题的进度。
GitHub 还提供了一个在线代码编辑平台,所以如果你想的话,可以在 GitHub 上完全完成一个项目。许多开发者喜欢他们的工具在本地机器上,他们只是将 GitHub 用作远程仓库,但如果你选择,它可以做更多的事情。
GitHub 甚至提供 AI 功能,即 CoPilot。这是一个可以在你键入时提供实时编码建议的功能,为你节省时间,甚至可能解决你原本可能会卡住的问题。它是通过在 GitHub 上的仓库上训练 AI 来实现的,因此你实际上获得了使用 GitHub 的每个人的综合知识、智慧和经验。
我可以继续说,因为 GitHub 不仅仅是一个 Git 仓库的托管者——它更像是一个开发者工作的中心枢纽。但它的好处在于,你可以从开始时浅尝辄止,然后完全投入其中,或者只留下脚趾头。你完全可以根据自己的意愿决定在 GitHub 中走多远。我还应该指出,GitHub 有替代品。但简单的事实是,GitHub 几乎是王者。它被比任何竞争对手都要多的开发者使用。在你继续你的网络开发者旅程的过程中,你几乎肯定会以某种方式与 GitHub 打交道,这只是一个程度的问题。
我假设你已经下载了这本书的代码,这意味着你已经创建了一个 GitHub 账户。但如果你还没有,现在就是时候了!无论如何,我强烈建议你花些时间在 GitHub 上玩玩。我会建议创建一个仓库,添加一个或两个文件,并在 GitHub 上本身编辑它们。然后,练习在本地机器上克隆仓库,再次编辑和添加文件,然后将更改推送到 GitHub,看看这些更改是如何显示在那里的。我认为你很快就会看到 GitHub 的价值,并欣赏它在过程中如何使 Git 的一些粗糙边缘变得平滑。
现在你已经了解了如何使用 Git 管理源代码,让我们谈谈一个不同的主题,这是每个开发者编码时都必须时刻牢记的主题:安全。
以黑客的思维思考——保护网络
创建安全的网站需要大量的时间、精力和对多个层面的细致关注。“深度安全”是一个常见的短语,意味着你不仅仅是要锁上门——你还需要一个安全摄像头、一只看门狗和窗户上的栅栏来保护你的家。同样,编写安全的代码也是其中的一部分,确保你的服务器能够抵御攻击。这是一个极其广泛的话题,而且它在过去几年中变得越来越重要。我们确实都看到了近年来发生的所有安全漏洞,我们中的大多数人可能以某种方式受到了个人影响。不幸的是,这些东西并不容易做对。
但一切始于编写安全的代码,而要做到这一点,你必须了解黑客——或者我们专业人士称之为“威胁行为者”——在代码中寻找以利用的某些类型的事物。好吧,他们通常不是在查看你的代码本身;他们是在查看你的网站,试图推断你的代码是如何编写的,然后寻找其中的弱点。
常见漏洞
好消息是,如今,利用——黑客可以利用来突破你安全性的弱点——基本上可以分为几个广泛的类别。
SQL 注入
如果不了解 SQL 是什么,这很难解释。但是,为了给你一个简略版……SQL 是一种特殊语言,允许你从数据库中获取数据。例如,如果你的数据库包含一个支票账户列表,你可能会通过执行select * from checking_accounts SQL 语句来获取这些账户的列表。
SQL 注入攻击基于这样的语句,以及开发者并不总是安全地编写它们的现实。当他们不这样做时,它允许威胁行为者通过表单中的巧妙值来修改这些语句。通过这样做,他们就能够访问他们不应该能够访问的数据。幸运的是,这是一个容易避免的问题:只需要开发者编写以特定方式使用 SQL 语句的代码。
跨站脚本(XSS)
XSS 是攻击者使用的一种技术,允许他们将恶意脚本(我们这里指的是 JavaScript)注入其他用户查看的内容中,可能窃取数据或冒充用户。这个漏洞是一个很好的例子,说明了为什么安全性如此困难:您可能认为这需要有人能够访问您的服务器来修改您的代码,但事实并非如此。相反,通过巧妙地使用电子邮件(在电子邮件中通常可以嵌入链接和其他网络内容),允许某人嵌入他们的恶意脚本,然后可以启动一个网络浏览器访问您的合法网站,该脚本随后能够像它属于您的网站一样执行其恶意行为。
这里的主要观点是,攻击者通常会间接地攻击您的网站,以您最初甚至都不理解的方式。 “像黑客一样思考”需要您对网络上的事物有良好的理解,然后才能以他们从未打算达到漏洞的方式连接事物,有时,这些连接的事物可能距离攻击目标有数步之遥。
您可以采取措施从发送到服务器的任何内容中移除可能危险的内容,并且您可以通过特殊方式编码输出,即使某些内容意外通过,在到达用户的浏览器时也不会产生预期效果。例如,在接收用户数据时,您可以查找任何 JavaScript 关键字实例并将它们删除,因为这通常不是您希望允许出现在用户输入中的内容。为了安全起见,您需要在服务器端执行此操作,因此您会运行所有用户输入通过一个清洗函数,该函数查找这些关键字或其他可能有问题字符串,如<script>,并将它们删除。您还希望在输出端执行相同操作,这意味着您发送到浏览器的任何数据都应该类似地被清洗,以防通过其他方式(例如,将数据加载到您的数据库中)意外地进入您的数据(当这种情况发生时,被称为存储型 XSS 攻击,因为恶意内容以某种方式被存储,并在返回浏览器时可能产生影响)。
跨站请求伪造 (CSRF)
CSRF 是一种欺骗用户浏览器执行某些不受欢迎的操作并使其看起来像是用户合法操作的方法。这有多种形式,其中一种更常见的方式是当网站使用 cookie(存储在用户浏览器上的小信息块)来识别该用户。如果有人能够窃取该 cookie,他们就可以有效地冒充合法用户。想象一下窃取一个用户的银行网站的 cookie——攻击者可以在真实用户意识到之前将所有钱转走。
如果不采取某些预防措施,窃取 cookies 并不像听起来那么困难(cookies 有各种安全设置,开发者应该正确设置以使它们更难被窃取)。使用 CSRF 令牌也很常见,这是一个随着每个请求而变化的唯一随机字符串。这样,即使有人偷走了你的 cookies,没有 CSRF 令牌,他们也无法做任何事情。CSRF 的其他变体需要其他缓解措施,但这取决于具体情况。
除了针对这些特定攻击的防御措施之外,还有一些基本的编码实践可以帮助你避免安全问题。
安全编码实践
当你编写代码时,你应该牢记几个关键点。这样做不仅能帮助你避免之前提到的问题,还能避免更多的问题。
实施安全的身份验证
你应该始终实施强大的密码策略,并在可能的情况下使用多因素认证(MFA)来保护用户账户。MFA 意味着用户必须通过多个因素来证明自己,其中因素是你是谁、你知道什么以及你有什么。例如,登录一个网站可能需要用户名(你是谁)、密码(你知道什么),以及可能发送到你的手机上的一次性代码(你有什么)。虽然攻击者可能设法窃取你的密码(你的用户名通常不是敏感信息),但他们不太可能同时拥有你的手机,所以通过要求多个因素,你现在已经保护了该网站。
使用加密
除非你有非常充分的理由不这样做,否则你应该始终使用 HTTPS,以确保浏览器和服务器之间传输的数据被加密。(我能想到的现在不使用 HTTPS 的唯一好理由是在开发过程中在你的机器上工作,因为使用 HTTPS 比简单的 HTTP 涉及更多的配置,你可能不想在开发过程中处理这些问题。)
这可以避免一种称为中间人攻击(MITM)的攻击形式。在这种攻击中,攻击者可能会控制你的数据在发送到服务器时通过网络的路由器。在这种情况下,他们可以查看这些数据,这些数据可能包括你的用户名和密码,如果你没有多因素认证(MFA),他们就可以随意登录。加密使得他们只能看到一串乱码。
正确处理错误
当你编写代码时,几乎每种语言都有处理错误条件的方法。这很重要,因为当这些错误发生时,它们可能会泄露关于你网站底层架构的信息,甚至可能允许威胁行为者偷偷将代码片段注入你的代码中,然后在你的代码上下文中执行。为了避免这种情况,你应该始终确保显示给用户的错误消息尽可能少地透露你的技术栈信息,并且你应该始终尝试使用你选择的语言提供的错误处理机制,以便没有任何东西“泄露出去”,也就是说,威胁行为者可以利用它来对付你。
执行代码审查并进行稳健的测试
定期审查代码以发现漏洞,并执行安全测试,包括渗透测试和静态代码分析。
静态代码分析工具在执行代码之前检查你的代码,寻找经常表示安全问题的模式。静态分析可以发现其他问题,而不仅仅是安全问题。它可以发现可能导致错误的逻辑错误,并指出你的代码没有按照可接受标准编写的位置。
渗透测试,通常简称为 pen testing,涉及人员(被称为“白帽”黑客)在获得你同意的情况下积极尝试攻击你的网站!他们试图通过例如寻找你使用的技术线索,然后搜索已知漏洞的方式来绕过你的安全措施。他们还可以根据代码的工作方式寻找代码中的缺陷,这使他们能够突破你的安全防线。
这两种方法都很有价值,因为它们能指出你可能自己忽略的问题,然后你可以进行纠正。这样积极主动的做法让你有机会在坏人介入之前修复这些发现!
实施最小权限原则
这通常是在操作系统级别而不是代码级别,但有时,它确实会渗透到代码级别。无论如何,理念是你应该只授予执行特定任务所必需的权限给特定用户,以减少如果发生安全漏洞可能造成的潜在影响。记得我们之前讨论过 Linux 中的 root 用户吗?嗯,如果威胁行为者能够以 root 用户身份获得访问权限,他们就有了王国的钥匙——他们可以做任何事情。然而,如果他们只能以对文件和目录只有有限访问权限的用户身份进入你的服务器,那么即使他们已经突破你的安全防线,他们能造成的损害也是有限的。
设置安全头信息
服务器可以在对浏览器的响应上设置各种头信息,称为内容安全策略(CSP)头信息,以及 X-Frame-Options 和 Strict-Transport-Security 头信息。这些告诉浏览器它们可以对服务器返回的内容执行什么操作。正确配置这些头信息可以限制威胁行为者在你的网站上能做的事情,在某些情况下甚至可以完全消除某些类型的漏洞(好吧,“完全”这个词在网络安全方面说起来永远不是什么好事,但它确实让坏人更难,而这基本上是所有这些的总体目标——让它们难以做到,基本上不值得他们花时间)。
定期更新
你几乎总是应该使用最新版本的诸如语言、库和服务器软件等,因为它们往往已经针对之前发现的安全问题进行了修补。新的问题总是可能被发现,因此这是一项持续的任务。
验证输入
当你从客户端获取数据时,你必须始终假设它包含安全漏洞。从服务器的角度来看,客户端永远不可信!因此,我们做一些事情,比如清理输入数据,这意味着确保所有输入数据都符合特定标准,以防止格式不正确的数据进入系统,这可能导致漏洞。例如,如果你有一个用于输入用户年龄的表单字段,它应该是一个数字。即使你在页面上有 JavaScript 确保这一点,威胁行为者也有其他方法让那些数据不是数字。当服务器接收到它时,如果它假设它是一个数字——如果它假设客户端是可信的——那么非数字数据可能会悄悄进入,并且可能存在利用这些数据的方法。因此,你的服务器代码必须验证客户端发送的确实是预期的数字。永远不要,永远不要信任客户端!如果你从这个整个部分中记住什么,记住这一点,因为它单独就能帮助你避免许多,如果不是大多数安全漏洞。
编码输出
想象一下,你有一个网站从远程服务获取股票行情数据。进一步想象,你将那些数据存储在你的数据库中并向用户展示。现在,你的网站可能代码编写得非常完美,因此它会清理从用户那里获取的所有数据。太好了!但是,你从那个远程服务获取的数据怎么办?他们是否像你一样很好地完成了他们的工作?也许没有!
在这种情况下,你可能会在你的数据库中以 JavaScript 代码的形式存储恶意内容,然后当你将这段 JavaScript 渲染到页面上时,它可以在你的页面上下文中执行,就像是你自己编写的,但执行的是坏人的命令。这被称为存储 CSS 攻击,因为恶意代码被存储在某个地方,通过“后门”悄悄进入。为了避免这种情况,你需要对发送给客户端的数据进行编码,移除可能导致漏洞的某些内容。
这种编码的实现方式因许多因素而大相径庭——太多以至于无法在此详细说明。但一般来说,它涉及用被认为是安全的其他字符或字符序列替换某些字符或字符序列。举一个例子,如果用户可以提交包含文本 alert(1) 的数据,并且它被保存在你的数据库中,然后后来在页面上使用,那么这段 JavaScript 代码将像你亲自编写的那样执行。为了处理这种情况,你可能需要将 ( 和 ) 字符分别编码为 ( 和 ),这些是特殊序列,将确保代码不会执行。
安全地管理配置
在代码中需要某些敏感信息是很常见的。例如,你的代码可能需要用户名和密码来连接到数据库。如果你将此信息嵌入到代码中,那么任何能够看到代码的人现在都有了这些凭据。这可以通过安全地存储这些凭据来避免,通常是在一些专门为这种敏感信息设计的特殊系统中。然后,你的代码只在需要时从该系统请求凭据,并且它们只在服务器内存中短暂存储。
监控文件上传
如果你的应用程序允许文件上传,确保对文件进行恶意软件扫描,并对文件类型和大小进行严格控制。例如,如果你正在构建一个人们可以展示他们相册的网站,你将希望实施代码以确保只上传图形文件。此外,你可能还希望有代码来确保没有大于可能 10 Mb 的文件被上传。这样,你可以阻止用户上传可能包含恶意软件的大型可执行文件,这些恶意软件可能被用来攻击你的服务器。
正确安全地存储数据
经常出现的情况是需要存储用户凭据——通常是用户名和密码。你可能认为将它们存储在服务器上的数据库中是安全的,但这并不是真的。有人可能能够访问那个数据库。如果你将它们存储在文件中而不是数据库中,情况可能也是如此。
不,你必须安全地存储这些数据。你在这个情况下会如何做?你可能认为你可以加密密码并存储它,对吧?加密是我们取一个值并生成另一个看起来像乱码的值,但这个乱码可以用来在给定另一个值——密钥——的情况下重新生成原始值。换句话说,加密是一个双向函数:一种方式是使用密钥值加密一些数据,另一种方式是使用密钥解密它(将其转换回原始数据)。
这看起来很安全,对吧?即使有人得到了你的数据库,他们也只能看到密码的乱码(通常不需要以任何方式保护用户名)。这是真的……除非他们也得到了你的加密密钥!考虑到你的代码需要密钥来解密密码并检查用户输入是否正确,这可能并不难。
在这种情况下,你应该做的是对密码进行哈希处理。与加密不同,加密是双向的,abc123(这不是一个好的密码,但请配合我继续这个例子!)如果我们对那个密码进行哈希处理,我们可能会得到一个哈希值z9x8。所以,我们永远不能从z9x8中得到abc123,但如果我们给哈希函数输入abc123,我们总是会得到z9x8。
这很有用,因为如果我们把z9x8存储在我们的数据库中,这意味着即使有人偷走了它,他们也无法得到真正的密码。但我们的服务器代码仍然可以验证用户!它只需要做的是,在用户登录时,对用户输入的密码进行哈希处理。如果我们得到的哈希值与数据库中存储的哈希值匹配,那么我们就知道他们输入了正确的密码。
哈希和加密之间的区别是开发者经常出错的地方,在我的经验中,这个场景是知道区别是安全登录系统与不安全登录系统之间的区别。
通过采用这些(以及其他)实践,网络开发者可以显著减少攻击面——所有可能被攻击者利用的安全路径——从而防御不断演变的网络威胁格局。
更多关于哈希的内容
哈希不仅用于密码和安全。你之前在讨论 Git 时看到了它们的使用。那里的哈希工作方式相同——提交的内容通过哈希函数运行以生成哈希值——但在 Git 中,它不是用于安全,而是用于唯一标识一个提交,因为哈希值将唯一对应于提交的内容,因为不存在其他字符组合可以产生相同的哈希值(严格来说,这并不完全正确,但两个不同的输入字符串产生相同哈希值的概率——我们称之为哈希冲突——在统计上如此之低,以至于我们可以认为这是真的)。
现在,让我们转换一下话题,谈谈在许多情况下将成为你日常处理最多的开发工具:集成开发环境(IDEs)。
在一个地方完成所有工作——集成开发环境(IDE)
在第一章中,我向你展示了一些非常基础的 HTML,并建议如果你使用 Windows 系统,可以将它输入到记事本这样的纯文本编辑器中。长期以来,就是这样——只有纯文本编辑器。过了一段时间,它们变得更加高级。例如,一些编辑器可以识别各种编程语言并提供颜色编码,使它们更容易理解,或者它们可以识别 JavaScript 中的函数,并在旁边显示一个列表。它们开始不仅仅只是纯文本编辑器——换句话说,它们开始意识到文本的内容。
在那之后不久,又发生了一次新的演变:集成开发环境(IDE)。这些软件为开发者提供了全面的设施。你几乎可以说 IDE 是一个对正在编辑的文本非常敏感的文本编辑器。它理解你在做什么,并以多种方式帮助你。IDE 通过将编写软件时涉及的大部分常见活动组合到一个应用程序中,提高了你的生产力。你在工作的地方保持不变,并且所有你需要的东西都在你的指尖,而不是需要跳转到几个窗口或界面来完成工作。
一个 IDE 通常包括以下内容:
-
代码编辑器:我的意思很明显,对吧?!但这是一个专门用于编写和编辑编程源代码的文本编辑器,通常包括诸如语法高亮、代码格式化和代码导航等功能。
-
编译器或解释器:一些编程语言在运行之前需要转换为其他语言。这听起来很奇怪,但这是真的!这个过程被称为编译,通常使用编译器来完成。那些可以直接运行的语言则使用解释器。然而,无论需要哪种工具,你都必须以某种方式与之交互,通常是一个独立的命令行界面。但有了集成开发环境(IDE),所有这些功能都内置其中,并通过 IDE 界面进行控制,因此你永远不需要离开它,这是 IDE 的一个关键优势。
-
调试器:调试器是一个工具,它通过允许执行代码的逐行执行、在任何时刻查看变量的状态或跳过代码段以便你能够专注于特定区域等方式,帮助你测试和调试代码。任何值得拥有的 IDE 都会内置一个强大的调试器。
-
构建自动化:一旦你编写完代码并测试和调试了它,你通常需要创建某种类型的包,当你想将应用程序分发给他人时,底层操作系统或运行时环境将使用这个包。我们说,这就是你“构建和打包”应用程序的时候,IDE 提供了自动化这个通常手动过程的方法。
-
源代码管理(SCM):IDE 几乎肯定知道如何与 Git 和其他源代码管理系统(SCM)一起工作,并且它们通常会提供一种更直观的方式来使用它们。有些人非常偏爱这种方法,而不是命令行——我就是其中之一——因为它允许您不必记住一大堆命令和选项,而是更抽象地与代码和 SCM 一起工作。
-
可扩展性:任何好的集成开发环境(IDE)都将有一些机制来扩展其功能,无论是称为插件、扩展还是其他什么。通过这种方式,您可以添加对新语言或工具的支持。
集成开发环境(IDE)有各种各样的形状和大小。有些是免费的,有些需要付费,有些只是带有一些附加功能的良好文本编辑器,而有些可以做很多事情,以至于如果您要求它们,它们似乎可以驾驶航天飞机!
在接下来的小节中,我们将探讨几个最受欢迎的集成开发环境(IDE)。当您阅读有关它们的内容并查看示例截图时,请注意它们的相似之处:它们通常都有一个用于编辑源代码的中心区域,周围环绕着几个小的工具窗口或区域。这基本上是 IDE 的目的:在中心提供一个坚实的、内容感知的文本编辑器,并围绕它提供上下文感知的工具(https://code.visualstudio.com)来帮助您完成工作。
VS Code
VS Code(https://code.visualstudio.com)可能是今天最受欢迎的 IDE。它来自微软,被宣传为免费、轻量级、跨平台(这意味着它可以在 Windows、Mac、Linux 上运行,甚至可以在网络上运行!)源代码编辑器,内置对 JavaScript、TypeScript(我们将在后面的章节中介绍)和 Node 的支持。它通常是一个相当基础的 IDE,但通过丰富的扩展生态系统,它可以非常容易地扩展,以支持其他语言、运行时、工具和技术,因此您可以使其完成几乎所有您需要的任务。
开发者倾向于喜欢 VS Code,因为它“不会妨碍他们”。有些人发现一些其他 IDE 的功能“有点太多”,信不信由你!毕竟,最终,一切都归结于您所编写的代码,所以一个占用您查看代码空间的环境有时可能不是您想要的。至少默认情况下,VS Code 在专注于您的代码方面做得很好,但您可以根据需要稍后添加功能。
图 4.7 展示了 VS Code,但请注意,这目前是我的设置,所以您看到的内容比默认设置要多,因为我已经安装了许多扩展(您可以通过左侧图标数量来判断——默认情况下,只有几个这样的图标,但我添加的一些扩展也添加了图标在那里):

图 4.7:VS Code
话虽如此,但请注意代码周围并没有太多东西。只有代码、左侧的项目目录列表和右侧的命令提示符。默认情况下,命令提示符甚至不会显示。这就是“不挡你的路”的意思,你可以将其与 Eclipse 或 IntelliJ IDEA 进行对比,我在稍后展示它们时,它们默认会显示更多内容。
Visual Studio
Visual Studio (https://visualstudio.microsoft.com) 在某种意义上是 VS Code 的“大哥”,而且恰好也是微软的产品。它比 VS Code 诞生早很多年(可能现在甚至几十年)。从历史上看,Visual Studio 主要用于处理微软的语言,如 .NET 和 C#,但多年来,它已经扩展到几乎涵盖所有技术,无论这些技术是否来自微软。微软提供了一种稍微有限制的免费社区版,以及一个功能齐全的版本,该版本附带付费订阅模式(在撰写本文时,这可能低至约 500 美元,高至约 6000 美元)。
Visual Studio 以前曾是一种笑话,因为它非常沉重、非常慢,而且往往不如你编写原始代码时希望的那样稳定。但现在,它已经成为一个非常受尊重和好评的 IDE,很多人对其赞不绝口。
图 4**.8 展示了 Visual Studio 的外观。与 VS Code 不同,我对我的 Visual Studio 安装并没有做太多的定制,所以全新安装看起来不会与这个有很大不同:

图 4.8:Visual Studio
我提到 Visual Studio 是 VS Code 的“大哥”。我的意思是 Visual Studio 包含更多的功能,至少在开箱即用的情况下。它拥有更强大的调试功能,可以更无缝地与各种类型的服务器协同工作,甚至可以实时调试 Windows 原生应用程序。VS Code 自诞生以来,已经缩小了这一差距,尤其是在安装了正确的扩展之后,但 Visual Studio 仍然在开箱即用的情况下功能更丰富。
Eclipse
Eclipse (https://www.eclipse.org/ide) 可能是市面上最著名的 IDE。它是一个免费(尽管捐赠非常受欢迎!)且开源的 IDE,历史上主要用于 Java 开发,但就像 Visual Studio 一样,它随着时间的发展已经变得无所不能。
虽然 Eclipse 相对较老,但我并不是在贬低它,只是说它已经存在很长时间了。就像 Visual Studio 一样,它并不是一开始就受到如此好评。多年来,它的性能显著提高,用户界面的相对简单性和一致性也有所提升,现在很多人都非常喜欢 Eclipse。
图 4**.9 展示了 Eclipse 在实际操作中的样子:](https://www.jetbrains.com/idea)
图 4.9: Eclipse
IntelliJ IDEA
IntelliJ IDEA(或简称 IDEA)(https://www.jetbrains.com/idea)是由 JetBrains 公司开发的一个 IDE。这是另一个主要为了特定技术——在这个案例中是 Java——而开始的 IDE,但它迅速发展,能够处理几乎所有其他的事情。这是我个人最喜欢的,因为它出色的性能、稳定性和极端的可扩展性。JetBrains 提供免费社区版,以及付费的高级版本。
值得注意的是,社区版与这里列出的其他版本相比,有一些更显著的限制,尤其是 Web 开发方面有所局限。幸运的是,付费许可证的成本并不高(截至本文写作时,第一年为 169 美元,第二年为 135 美元,之后每年为 101 美元,针对个人开发者的许可证),而且绝对物有所值,因为它将强大的功能交到了你的手中。当然,它更适合组织(每年 599 美元),所以这取决于你需要哪种许可证。
在任何情况下,图 4**.10 展示了 IDEA 的样子,至少在我的情况下,其中激活了相当多的扩展:

图 4.10: IntelliJ IDEA
这绝对不是一份详尽的列表,但我想这些四个在写作时可能是最受欢迎的。
现在,让我们转换话题,触及另一个你应该了解的主题,那就是操作系统——更具体地说,是 Linux。
理解操作系统——Linux
因此,你编写代码,并安全地编写它,将其置于版本控制之下。但当部署代码的时候——也就是说,将其放置在运行的地方——你该怎么办呢?这个问题没有单一的答案,因为你可以如何以及在哪里运行你的代码是一个有无数答案的问题。但有一点是肯定的:它们都将涉及某种形式的操作系统。
虽然截至本文写作时,Windows 仍然是世界上最受欢迎的桌面操作系统,但当我们谈到服务器时,情况就完全不同了。虽然你也会发现很多基于 Windows 的服务器,但大多数服务器都运行 Linux。
Linux 是由 Linus Torvalds 于 1991 年编写并首次发布的开源操作系统。Linux 的基本架构基于 Unix 操作系统,该操作系统最初于 1971 年发布,尽管它的历史——以一种稍微不同的形式——可以追溯到 20 世纪 60 年代。从某种意义上说,Linux 的历史也是互联网的历史,因为它的使用与互联网同步增长。它因其稳健性和灵活性而成为服务器基础设施的骨干,为网站和在线服务提供动力,并且也是免费/开源软件(FOSS)运动的典范,无疑是迄今为止最大和最知名的开放源代码项目。
Unix,因此 Linux,是稳定的、通常安全的、免费的,这使得它在许多用途中都相当受欢迎。它基于模块化和可重用性的原则,这意味着它可以有针对性地调整和重新组织。正是这种特性使其具有灵活性,并导致了数百(如果不是数千)个发行版——或称distros——它们只是 Linux 的变体,不同的系统库集合和调整过的内核等。不同的发行版有不同的重点。有些被构建成专门的服务器;有些针对桌面机器;有些具有非常小的内存和硬件要求,使其成为小型嵌入式系统的理想选择;有些针对音乐家;有些针对科学家……等等。
现在,让我们从高层次上了解一下 Linux 的一些基本概念,包括其整体结构、您可能会遇到的一些最常见命令,以及安全在 Linux 中的作用。
Linux 的结构
Linux 的基本结构可以理解为一系列层,一层叠在另一层之上。从最底层开始,我们有以下内容:
-
内核:一种主要负责管理硬件资源(如 CPU、内存和外设)的软件。
-
文件系统:任何操作系统都需要包含文件系统——信息在存储设备上的存储结构。Linux 支持许多不同的文件系统,但大部分情况下,它抽象了差异,以便您可以像对待任何其他文件系统一样对待任何文件系统。
-
Shell:用户用于与内核交互的界面。在大多数情况下,默认情况下,这是一个命令行界面(即使是具有 GUI 的 Linux 发行版,也始终有一个可用的命令行界面)。
-
实用程序:用于管理系统的小型软件工具。这些只是具有非常小、明确目的的小程序——例如创建用户、管理文件、查看系统资源使用情况等。
-
应用程序:用户级程序,如浏览器和文字处理器。
Linux 发行版将内核与各种软件捆绑在一起,包括实用程序和应用程序,以创建完整的操作系统。这些软件提供了您可以在 Linux 命令行中执行的基礎命令,默认情况下,这是您与操作系统交互的方式。
基础命令
虽然您可以在 Linux 命令行中执行数百条命令,但您经常使用的命令集要小得多。这个命令集将取决于您要做什么,但一些最重要且最常用的命令肯定包括以下内容:
-
ls:列出目录内容 -
cd和pwd:更改目录(在文件系统中导航)和打印工作目录(告诉您在文件系统中的位置) -
grep: 在文件中搜索文本 -
cp和mv:复制文件或目录,移动文件或目录 -
rm:删除文件或目录 -
sudo:以其他用户身份执行命令 -
mkdir:在当前工作目录中创建新目录 -
man:查看手册页(各种命令的说明) -
chmod:更改文件和目录权限 -
touch:创建新文件或更新现有文件的最后访问和修改日期/时间 -
ps和kill:查看系统上当前正在运行的过程信息,并在需要时停止(杀死)它们 -
less和more:检查文件内容(more是一个功能相当基础的程序,提供了相当基本的查看功能,而less提供了更高级的功能,例如双向滚动、强大的搜索和更多用户交互性)
这绝对不是一个详尽的列表,也不是试图成为。还有很多其他的命令,也许找到它们最好的地方是 Man 页面存储库:www.unix.com/man-page-repository.php)。
但是,在所有这些命令中,man 可能是你要记住的最重要的一个。这是因为它是一个可以给你提供关于 其他 命令信息的命令!例如,如果你忘记了 cp 命令的语法——语法是指命令的形式和结构以及你可能需要提供给它的任何信息——你只需执行 man cp 来获取这些信息,就像你在 图 4.11 中看到的那样:

图 4.11:执行 man cp 的输出
在这里,你可以看到你提供了有关如何使用该命令的信息,包括其目的的简要描述、一些语法示例以及它支持的各个选项。对于这个命令,页面上给出的信息比静态图像中看到的要多,但在实际使用 man 时,你可以继续滚动文档,对于某些命令来说,文档可能相当长,而对于其他命令来说则相当短——这完全取决于命令是什么以及它能做什么。
安全性和你需要知道的 Linux 一个“奇怪”事实
Linux 在其核心是非常注重安全的。它也是一个多用户系统。每个文件和目录都属于特定的用户和用户组,一个不拥有文件或不在适当组中的用户无法访问它。Linux 有一个强大的权限系统,也就是说用户或组对文件或目录的各种权利。例如,你可以给用户读取文件的权利但不允许编辑它。运行文件的能力——假设它是一种程序——是其自身的权利,这意味着你可能能够读取和编辑程序文件但不能执行它。
权限也是分层的,这意味着你可以被授予对目录的权限,这些权限默认情况下会向下过滤到其下的目录和文件(尽管你也可以更改特定文件或子目录的权限)。这允许对各种用户的访问权限进行非常细粒度的控制,使得他们在同一系统上工作而不会相互冲突变得安全。
所有这些安全问题的例外是特殊的 root 用户。这个用户在某种程度上是机器的神。root 用户可以随时对任何文件或目录做任何事情。始终存在 root 用户账户,尽管有时可能没有人能使用它。这是必要的,因为有些操作对于 Linux 本身来说非常核心,其他用户不应该能够执行它们。至少,它们应该非常有限。在这种情况下,Linux 提供了临时“切换”到 root 用户的能力,这意味着你作为一个普通用户,可以执行一个特定的命令,就像你是 root 用户一样,前提是你已被授予执行该操作的特殊权限。这允许在需要时将高级系统管理任务提供给用户,但以一种非常严格控制的模式。
我已经多次提到文件,你肯定知道什么是文件,因为它们与其他任何操作系统没有区别,但当你第一次接触 Linux 时,会发现一个有点奇怪的事实——Linux 中的一切都是文件。当我说是(几乎)一切时,我的意思确实是这样的。
在 Linux 中,“一切皆文件”意味着操作系统允许你以处理实际文件相同的方式处理那些不是文件的事物。换句话说,这是 Linux 将输入和输出到诸如硬盘、调制解调器、键盘、打印机和其他系统资源及设备视为通过文件系统暴露的数据流,就像它们是该文件系统上的文件一样。
这种做法的后果是,不仅可以通过命令行或其他程序使用文件操作(如读取和写入)与实际文件进行交互,还可以与目录、系统信息、硬件设备以及一些系统进程进行交互。例如,访问打印机等设备可能涉及向代表打印机的文件写入数据,这将导致打印机在纸上物理打印这些数据。或者你可能从代表系统内存使用的文件中读取,以查看你有多少空闲内存。这种设计使得编程和资源管理更加统一和简单,因为你不需要使用任何特殊方法来处理硬件设备和系统资源,你可以使用与常规文件相同的所有命令和实用程序。
既然你对 Linux 有了(非常)广泛的了解,让我们来谈谈为什么对于一个网络开发者来说了解 Linux 甚至很重要。
网络开发者的 Linux
由于 Linux 在服务器上被广泛使用,这意味着了解 Linux 对于网络开发者来说是一笔宝贵的财富。现在,为了清楚起见,了解 Linux 和 真正了解 Linux 是两回事。网络开发者可能不需要像服务器管理员那样成为一个冷酷的 Linux 专家;这些人必须对系统及其操作有非常深入的了解,而这需要大量的时间和经验。当然,作为一个网络开发者,你了解得越多越好,但你可以从一些基础知识开始慢慢学习,这样你就不会迷失方向。
幸运的是,在 Linux 中入门相当容易。有几个网站允许你在浏览器中运行 Linux,或者将你连接到服务器上的 Linux 实例,然后你可以通过浏览器与之交互。无论哪种方式,你都会在浏览器中获得一个命令行界面,无需在你的机器上安装 Linux!
在这里像这样玩 Linux 的一个好选项是 bellard.org/jslinux。在这里,你可以选择几个发行版,并在浏览器中运行它们,正如你在 图 4.12 中可以看到的。12*:

图 4.12:在浏览器中运行的 Alpine Linux,由 JavaScript/Linux 提供
我强烈建议花些时间在这里玩玩——你不可能造成任何伤害!为了让你开始学习一些基础知识,首先执行以下命令:
cd /
cd 命令代表 更改目录。在这里,你正在导航到 根目录,它是系统上所有其他目录的父目录。
要查看该目录的内容,请执行以下命令:
ls -lha
ls 命令的缩写是 list,即 列出目录内容。在这里,-lha 选项中的每一个都告诉 ls 你希望以特定的方式显示列表。l 选项告诉它你想要一个长列表,这意味着它会显示更多细节。h 选项告诉它你希望所有数字以更易于阅读的形式显示(例如,文件大小为 4k 而不是 4,096),而 a 选项告诉它不要隐藏以点开头的条目,这表示隐藏的文件和目录。
接下来,使用以下命令导航到你的主目录,这是每个 Linux 用户默认获得的目录:
cd ~
波浪字符是一个特殊的字符,Linux 知道这意味着你想要进入你的主目录。通常,当你使用 cd 命令时,你必须指定完整的目录路径,要么从根目录开始,要么从当前目录开始。但是导航到你的主目录是一件如此常见的事情,以至于 Linux 提供了这个有用的快捷方式。
然而,波浪号并不是真正的目录路径——它只是一个快捷方式。要查看真正的路径,请使用以下命令:
pwd
pwd命令代表打印工作目录。你看到的值始终是/home/<你的用户名>。你的用户名通常由机器管理员确定(在在线 Linux 游乐场的情况下,当 Linux 启动时,几乎肯定会创建一个用户)。当然,如果你自己设置了 Linux,那么你将在设置过程中选择用户名。
接下来,让我们创建一个文件:
touch my_file
再次执行目录列表(这次可能只需执行ls而不带任何选项以查看差异)现在你会看到一个名为my_file的新文件。在 Linux 中编辑文件和将内容放入文件的方法有很多,所以让我们尝试最简单的方法:
echo "hello there" > my_file
引号中的字符串将被写入文件(回显到它)。要查看这一点,你可以像这样列出文件的内容:
cat my_file
cat命令代表连接,但它也用于打印到标准输出,即命令提示符所在的屏幕。因此,cat my_file会将文件内容输出到屏幕,当你这样做时,你应该会看到其中的引号字符串。
我要讨论的最后一件事是用户和权限。之前我提到,Linux 系统的每个用户都会得到一个家目录。系统上的每个唯一用户不仅会有一个家目录,还会有一个用户账户。Linux 及其基础 Unix 本质上是多用户系统,正是这些用户账户以及它们拥有的权利使得系统可以被共享。除非你明确允许,否则系统上的其他用户不允许触摸你的家目录的内容。
如果你再次使用-lha选项列出目录内容,你会在数据的第二列和第三列看到一些内容,分别是文件的所有者和主要组名。文件所有者的含义应该是显而易见的:它将是你的用户名!由于你创建了文件,所以你拥有它,你可以随意处理它。然而,同一用户组的任何人(由第三列表示),也可以访问该文件。
组是用户在 Linux 中组织以便更容易管理的一种方式。当你创建文件时,组可能正好与你的用户名相同。这意味着在这个特定的 Linux 系统中,默认情况下,为每个用户创建一个与用户名相同的组。由于没有人会在这个组中,这意味着文件真正只能由用户访问。从某种意义上说,这意味着在这种情况下组是不相关的。
如果你想要将所有权赋予另一个用户,有一个命令可以做到这一点:
chown <new_owner_username> my_file
chown命令代表更改所有者。执行后,你将无法再自己访问该文件,因为命令中指定的用户名将拥有它(你通常不会在主目录中这样做,但你绝对可以)。
你不能允许特定用户编辑你的文件的能力,但你所能做的是给组中的任何人这样的能力:
chmod g+w my_file
chmod命令,简称“更改模式”,意味着更改某物的访问模式或权限。这个命令可以接受多种形式的广泛选项,但在这个例子中,g+w仅仅意味着向主要组(g)添加(+)写(w)权限。之后,该组中的任何人都可以编辑文件。
我之前说过,默认情况下只有你自己能编辑你的文件,但这并不完全正确。你也应该知道,Linux 系统中始终有一个名为 root 的用户。从某种意义上说,这是机器的神。root 用户可以随时做任何事情,root 用户账户是为机器的管理员(们)准备的。
然而,即使是管理员也经常以普通、非 root 用户身份登录系统,这意味着他们没有特殊权限。这是一个重要的安全原则,称为最小权限原则。这意味着用户应该拥有完成其职责所需的权利,而无需更多。然而,管理员需要更大的权利,所以他们允许的是临时提升他们的权限。
要执行特权命令,他们必须使用一个特殊的命令:
sudo
sudo命令代表“超级用户执行”,意思是“嘿,Linux,如果允许的话,我想以 root 用户身份执行这个命令。”系统会维护一个列表,列出每个用户可以使用sudo执行的命令,这样做等同于以 root 用户登录时执行该命令,但仅限于执行命令所需的时间。这就是关键。请注意,你可能无法在一个在线 Linux 沙盒中实验sudo,但这是一个需要了解的重要概念,因为即使是非管理员也常常可以通过sudo获得一定程度的提升权限,这在 Linux 上完成工作通常是必需的。
说到这里,我们可以说我们已经涵盖了这一章的“非代码”主题。
回顾路线图
本章结束后,我们现在可以在我们的网络开发者路线图中展示一些更多的方块:

图 4.13:路线图,填入了一些更多的框
好吧,现在内容开始变得丰富起来,不是吗?这一章揭示了源代码控制、安全性和Linux这些框。它还建立了一些其他内容,包括网络和开发者工具。我们正在稳步前进!
摘要
在本章中,我们提到了几个对网络开发者来说非常重要的主题,但它们并不直接涉及编写代码。这包括源代码管理(SCM)——主要是以 Git 的形式,Linux,安全的编码实践和集成开发环境(IDE)。你看到了源代码管理是如何成为你网络开发工作流程的关键组成部分,IDE 是如何使你的工作变得更容易,思考在编写代码时从底层考虑安全性是多么重要,以及 Linux 是如何统治所有操作系统的,一个操作系统去寻找它们,一个操作系统将它们全部带来,并在黑暗中将它们束缚(抱歉,我是个极客——我忍不住要引用《指环王》的廉价引用!)。
在下一章中,我们将继续探讨一些延续本章主题的内容——也就是说,一些你可能需要了解但并非直接与代码相关的话题(当然,最终它们都以某种形式与代码有关)。这包括著名的术语“云”,虚拟机、容器,DevOps(无论那是什么!),Python,图形设计,用户体验(UX)和响应式设计。让我们继续把这块巨石推上山,好吗?
第五章:理解用户体验、部署选项和 DevOps
在本章中,我们将讨论一些其他的“随机”主题,但这些随机性实际上分为两类:你网站的外观、感觉和功能,以及一些部署它以实际运行的选择。诚然,它们是两个不同的齿轮,但它们结合在一起,因为你希望你构建的任何网站看起来都很好,工作得也很好,但显然,如果你不能构建并最终部署到其他人可以访问的地方,那就不会是这样的情况。
在这里,你将了解如何使你的网站对尽可能广泛的受众既吸引人又实用。这些是重要的考虑因素,因为用户会被看起来好的网站所吸引。然而,如果网站不能被他们有效使用,那么外观好看也就不那么重要了,所以两者都是关键考虑因素。
然后,你将了解虚拟机(VMs)、容器和云,这些都涉及到你的网站在哪里以及如何运行的问题,因为人们必须能够访问它!这些选项提供了你将网站上线供全世界看到时所需的灵活性和稳定性。
最后,你将了解一些关于 DevOps 的知识,这是管理这些部署的现代方法,以及如何从源代码构建你的网站到最终形式。这很重要,因为随着网站变得更加复杂,保持一切井然有序并确保你为客户提供高质量结果的难度也在增加,而 DevOps 可以帮助你做到这一点。
因此,本章将涵盖以下主题:
-
理解使网站看起来漂亮的原因 – 图形设计
-
确保它不仅看起来好,而且对每个人来说都工作得很好 – 用户体验(UX)
-
虚拟化世界 – 虚拟机和容器
-
保持你的思维在云端 – 云服务提供商
-
恢复控制权 – DevOps
-
审查路线图
技术要求
在本章的后面部分,我们将讨论一些被称为虚拟机(VMs)和容器的东西。作为这部分讨论的一部分,我将向你展示一些基本的容器功能。为了跟上,你需要做两件事。
首先,前往www.virtualbox.org下载并安装 Oracle VirtualBox。这是一个允许你运行虚拟机(无论它们是什么!)的程序。
一旦安装完成,请前往www.osboxes.org/ubuntu下载 23.10 Ubuntu Linux VirtualBox 镜像。只需准备好这些,稍后使用即可!
最后,我们将简要介绍 Python,这是开发者经常使用的另一种编程语言。为此,你需要在你的系统上安装它,以便可以玩转示例代码。要做到这一点,请前往www.python.org下载并安装适合你系统的版本。
你还想要确保已经下载了这本书的源代码包(如第三章中所述)以及准备好的ch05/1-python目录。
理解是什么让网站看起来漂亮——平面设计
好的,所以你已经建立了一个能够正常工作的网站。这当然是一个值得骄傲的成就,但它看起来怎么样?它是否吸引人?它是否赏心悦目?它是否是一件艺术品?回答这些问题需要深入到平面设计的领域。但是,尽管我使用了“艺术”这个词,它却出人意料地科学!
平面设计,用最简单的术语来说,是将某些技术和关于人类心理学甚至生理学的知识应用于创建视觉内容,这些内容传达信息,并以一种被认为对许多人来说视觉上令人愉悦的方式传达。它结合创造力和技术,通过各种视觉手段传达思想,在品牌建设、市场营销和媒体制作中发挥着至关重要的作用。
平面设计师——以及经常发现自己扮演这一角色的网页开发者——使用各种工具和软件来创建视觉构图(在我们的案例中,是网页开发工具),这当然包括 HTML、CSS,甚至JavaScript(JS)。虽然平面设计确实根植于艺术,但一个好的设计通常会考虑平衡、对比和层次等原则,以确保有效的沟通。这就是我提到的“科学”部分。
尽管平面设计在很大程度上是关于视觉吸引力的——本质上,屏幕上的内容是否看起来令人愉悦——但它也包含了一个可用性的元素。可用性指的是用户在使用给定的人机界面(在这里,“工具”可以指一个网站)时高效完成工作的能力。那个想法涉及到一个叫做UX的东西——这个话题将在下一节中讨论,但它围绕着诸如用户是否能够高效地导航网站、网站在功能方面是否满足他们的期望以及更多类似的问题。
作为可用性的一个例子,如果你在浅绿色的背景上有浅黄色的文字,这可能会让用户难以阅读,使网站的可用性降低。另一个例子是动画背景,这在当今的互联网上非常流行。它们看起来很棒,但如果你没有正确考虑你放在它们上面的内容,那么你可能会让用户感到困惑,甚至可能让他们感到恶心。在这些情况下应用平面设计原则可以帮助你使设计看起来很好,同时保持良好的可用性。
让我们来看看构成平面设计的几个关键领域。
理解关键平面设计领域
平面设计涉及几个领域,包括以下内容:
-
排版设计:这包括有效字体选择、行宽和间距等因素,目的是使内容易于阅读和视觉上吸引人。
-
布局和印刷设计:这全部关于在空间中有效地安排视觉元素,确保负空间(空白区域)的使用令人愉悦,以更好地突出内容,以及元素的有效对齐,使用户扫描内容更容易。
-
品牌和标志设计:适当的品牌和标志设计为公司或产品创造了一个视觉身份。这也包括色彩选择。
-
动态图形:并非所有图形都是静态的,尤其是在当今的互联网上。添加动画元素可以极大地提升设计,但提升和分散注意力之间的界限很微妙,这种平衡必须保持。
虽然图形设计技术和工具不断演变,以及用于实现设计的技术的演变,但图形设计的核心始终如一:信息的视觉传达,有效且美观,对于网络来说,它还在很大程度上决定了网站对用户的易用性,意味着减少用户在实现目标时的挫败感和分心。
存在一系列称为格式塔原理的原则,可以指导你在图形设计工作中的职责,我们现在将探讨这些原则。
理解格式塔原理
格式塔原理是一套关于人类如何感知视觉元素的心理理论。它们解释了为什么某些元素会被感知为属于一个群体或以某种方式相关联。这些原则通常应用于图形设计,以增强理解和组织。以下列出这些原则:
- 邻近性:彼此靠近的元素通常被认为是有关系的或被分组在一起。在网页图形设计中,相关的按钮或菜单项通常放置在一起,以视觉上传达它们在功能上的相关性。在图 5.1中,注意你的大脑如何将左边的十二个方块、右上角的三个方块和中间的三个屏幕分组在一起:

图 5.1:格式塔邻近性原理
- 相似性:与邻近性一样,当物体看起来相似时,我们在心里将它们分组。这可能是形状、大小或颜色的相似。在图 5.2中,你的大脑倾向于将深色方块分组在一起,即使它们并不立即相邻:

图 5.2:格式塔相似性原理
以另一个例子来说,在网站上调整图标或按钮的大小,可以给人一种相关功能的感觉,并允许用户构建一个心理模型,即“看起来像这样的东西是按钮,我可以点击它们”,这有助于使网站更易用。
注意
心智模型是一个常用术语,指的是用户在他们心中形成的理解——无论是对是错——关于他们正在看什么以及他们正在做什么。用户在使用应用程序或网站时,会建立起关于它做什么、如何做、为什么以及预期行动结果的心智模型。我们的任务是确保用户建立的心智模型通过图形设计原则与现实相符。
- 封闭性: 我们的大脑倾向于填补缺失的信息以形成一个完整的图像。当我们观察视觉元素的复杂排列时,这一点就会发挥作用。我们的思维倾向于从它们中感知到一个单一、可识别的图案。在图 5.3中的例子中,尽管线条断裂,你的思维仍然感知到一个五边形:

图 5.3: 格式塔封闭性原则
这可能完全正常,甚至可能是所希望的,但它有时会导致混淆,因此你必须在设计中小心避免这种情况,除非这正是你想要的。
- 封装: 封装原则描述了人类视觉感知将元素组合在一起形成整体的倾向,尤其是在它们被某种类型的边界包围时,即使它们在其他方面是分离的。当我们看到一组由实线边界或甚至只是轮廓包围的元素时,我们会将它们视为一个单一的、统一的群体,几乎就像一个离散的形状,而不管它们的物理位置如何。图 5.4中的例子会让你的大脑潜意识地将大正方形内的三个小正方形组合在一起,尽管它们并不相邻:

图 5.4: 格式塔封装性原则
- 连续性: 我们的眼睛会跟随路径、线条或曲线,通常更喜欢看到它们沿着既定的方向继续,而不是突然改变。这个原则在引导用户注意力的特定方向上非常有用!在图 5.5中,你的思维在某种程度上得到了安慰,因为你潜意识地期望随着你向下移动,盒子会变大,正如它们所做的那样:

图 5.5: 格式塔连续性原则
例如,以我们之前工作过的注册页面为例,所有输入字段都垂直排列在页面下方,形成一条单线。用户的眼睛可以轻松地跟随这条线。如果其中一个字段突然对齐到页面的右侧,这可能会对用户造成干扰,而且从视觉上看可能也不会很好。
- 图与底:我们本能地将物体感知为前景(图)或背景(底)。这有助于设计师强调某些元素,但你必须确保你在正确的时间强调正确的元素,否则可能会让用户感到困惑。图 5.6 展示了一个例子,说明你的大脑倾向于将大矩形内部的盒子视为比外部的盒子更“突出”,即使你并没有有意识地意识到这一点:

图 5.6:格式塔图与底原则
- 连接性:通过统一视觉属性(如线条或颜色)连接的元素被视为比未连接的元素更相关。这在网络上通常作为分组框实现——围绕元素的小标题周围的框。在 图 5.7 中,两个连接组中形状的不同并不阻止你的大脑认为它们是连接的:

图 5.7:格式塔连接性原则
- 共同命运:朝同一方向移动的元素被视为相关或分组。例如,协同移动的动画可以表明它们是同一动作或概念的组成部分,或者即使元素以杂乱无章的方式移动或对齐,它们在我们看来仍然似乎被分组。图 5.8 展示了你的大脑将顶部的盒子视为连续线的一部分,而底部的盒子作为另一条连续线的一部分,尽管它们在空间或对齐上并不一致,但这是因为它们似乎以一致的方向或向共同命运移动:

图 5.8:格式塔共同命运原则
这些原则帮助设计师——以及我们这些网页开发者——创建易于用户理解和处理的视觉设计。通过了解人类如何自然地分组和分离视觉信息,我们可以创建更有效、直观和和谐的图形设计布局,对我们来说这意味着网站。
屏幕上的元素看起来很好,并且根据图形设计原则组织得很好,这些都是用户能够有效使用你的网站的因素,但还有更多。现在让我们进入用户体验(UX)的领域!
确保它不仅看起来很好,而且对每个人来说都工作得很好——用户体验(UX)
在上一节中,我们简要讨论了图形设计,并提到图形设计在所谓的用户体验(UX)中扮演着一定的角色。现在让我们更详细地探讨这个话题。
UX 指的是人与产品、系统或服务互动的整体体验,特别是在使用过程中的易用性和愉悦性。它关注他们对产品的感知,最终目标是让他们在使用你生产的产品之前、使用过程中以及使用之后都感到满意。
UX 是一个迭代的过程,在这个过程中,你研究、规划、测试,然后根据每次迭代的结果,改进人们与产品互动的各种接触点,试图逐步接近那些目标。这并非关于猜测;它往往非常科学。你可以猜测哪些产品特性会让用户感到高兴,但这些猜测——应该基于研究——只是起点。然后你需要将产品展示给人们,观察他们如何与之互动,获取他们的反馈,然后根据这些反馈调整产品。
UX 并非仅仅是网页开发或计算机相关的事物。实际上,关于 UX 最著名的书籍之一,唐·诺曼的《日常事物的设计》,几乎完全没有提到计算机,更不用说网站了——它更多地讨论了茶壶和汽车上的转向信号!这是因为 UX 适用于人类与任何技术或更广泛地说,任何物体互动的方式。但是,在数字设计的具体背景下,UX 专注于用户在网站或应用程序中的旅程。因此,让我们更深入地探讨这个话题。
UX 的关键概念
这里是一些构成 UX 的关键概念:
-
可用性:这关乎界面的用户友好性和直观性。它包括诸如学习容易程度(用户是否能够在不阅读文档的情况下发现应用程序的工作方式?)、使用效率(用户能否以最少的步骤完成任务?)、记忆度、错误频率、严重程度以及他们从错误中恢复的容易程度,以及整体用户满意度。
-
交互设计:这涉及到设计交互式数字产品、环境、系统和服务。它关乎创建具有深思熟虑行为的界面。你绝对不希望在任何软件产品中让用户感到惊讶。如果他们点击按钮,按钮会做什么应该是显而易见的;他们不应该需要猜测。交互设计在很大程度上定义了这一概念。
-
用户研究:通过调查、访谈和可用性测试(通常,你会实际观察用户如何与你的网站互动,寻找他们是否在导航时感到沮丧等问题)等方法了解用户的需求、动机和行为。这项研究为设计决策提供信息,尤其是在你迭代设计时。
-
信息架构(IA):这是关于有效且可持续地构建、组织和标记内容。良好的信息架构有助于用户找到信息并完成任务。信息架构的一个常见工具是卡片分类练习。假设您有一个网站需要涵盖的几个主题,其中一些是其他主题的子主题。为了组织这些,您可能会在每个主题上写一个实际的卡片,然后将顶级主题排成一行,然后将子主题放在它们下面。
-
内容策略:这涉及到规划、创建、交付和管理内容。内容应该对用户相关且有用,您应该尽量减少可能只是混淆他们或阻碍他们的无关内容。在图形设计中,负空间的概念很重要,即知道何时不在屏幕上放置某些内容。有时空白空间是有价值的,但同样,当信息不会帮助用户实现主要目标时,不提供信息也可能有价值。苹果公司是这方面的绝佳例子:它倾向于最小化用户可用的选项。这可能会让一些高级用户感到沮丧,但对于典型用户来说,这实际上有助于他们,因为通过不过度加载选项来避免分析瘫痪的现象。这不仅适用于选项,也适用于信息。
-
用户反馈:持续收集用户反馈对于用户体验的迭代改进至关重要。当然,在最初设计和构建网站时,获取反馈以便在过程中进行更改是有帮助的。然而,在网站上线后,寻求用户反馈不应停止。总有改进的空间,但您不应盲目行事。听取用户的意见是获取您需要的信息以基于数据进行改进的好方法。用户反馈可以是直接的——例如用户可以填写的调查——或者间接的——例如通过查看有关用户点击帮助链接频率等指标的度量。如果您发现每个访问您网站的用户都在寻找帮助,或者更糟糕的是,在做一些有意义的事情之前就放弃了网站,那么这种反馈可能是在告诉您,您的网站可能不像您想象的那么易于使用。因此,分析和性能数据是这一方面的一个方面。
-
原型设计和线框图:这些是用户体验设计中的基本工具。原型设计是指创建产品的初步模型,这可能是一个低保真草图或一个高保真交互模型。线框图是基本的布局,概述了网页或应用中页面元素、功能和导航的大小和位置。我们在第一章中讨论了这一点,但在这里再次提及是为了理解它与用户体验的关系。这些是您对整体设计的早期“猜测”,您希望尽可能早地向用户展示,以便开始必要的迭代。
-
用户画像:这些是为了代表可能使用服务、产品、网站或品牌的不同用户类型而创建的虚构人物。用户画像帮助设计师和开发者了解用户的需求、体验、行为和目标。用户画像是什么样的?以下是一个例子:
Persona 1: Mike, a small business owner Name: Mike Nelson Occupation: Owner of a coffee shop Demographics: 27 years old, lives in San Francisco. Married with two children, 3 and 6\. Has a generally middle-class income level. Opened the shop 2 years ago after getting married. Mike's Story: Mike was working at a large financial firm, but felt he didn't have enough free time for things important to him, including starting a family. So, he decided to quit his job and use his accumulated savings to open the shop. While some consider it silly, owning a coffee shop had been his dream since his college days (probably owing to how much time he spent in coffee shops during those years!) Mike's Challenges: Mike has poor accounting skills and is having difficulty keeping the company's books. In fact, even relatively simple questions like how much net profit he has every month are difficult for him to determine. He is spending too much time on these difficulties. What Mike Needs: Mike is looking for a product – in the form of a web-based application – that can handle the analysis of his income and expenditures and can automatically supply the information he needs. He would like to see things categorized so he knows, for example, how much he is spending on coffee grounds versus filters.使用这样的用户画像,可以做出设计决策来解决客户面临的挑战。例如,基于迈克的挑战,我们可能希望创建尽可能少地展示信息的设计,以防止迈克感到不知所措。此外,考虑到他的需求,我们开始了解界面将是什么样子;在这种情况下,我们很可能需要一些视觉隐喻来展示信息类别,可能每个类别都有一个标签,或者可能是可以展开和折叠的各个部分。
-
旅程映射:这涉及到创建用户与产品交互的视觉表示。它有助于理解和解决用户在整个旅程中的需求和痛点。与卡片分类一样,这通常以大版图上的纸张形式出现,使用绳子和图钉。这些纸张上可能有手绘的各种屏幕草图,然后绳子显示用户如何从一个屏幕或功能移动到下一个。当然,这不必采取这种形式——有些人更喜欢在图形或流程图程序中工作,并全部数字化处理——但低保真方法通常用于速度和确保你不会专注于任何技术方面。这里的关注点不应考虑技术,至少不应超过绝对必要的程度。你专注于逻辑流程,不再关注其他。
-
跨平台一致性:确保在各种平台和设备上提供一致的用户体验,这包括保持功能和设计的统一性。换句话说,你通常希望网站在 Windows 机器上和在 Mac 机器上看起来一样,而且通常不希望桌面机器上有的功能在手机上没有(当然,该功能的形态可能会有所不同)。当然,有时会有例外——有些时候,让事物在一个平台上看起来或功能上与另一个平台不同是有意义的——但它们往往只是例外。
-
情境设计:这是基于对用户在实际环境中如何与产品互动的理解来进行设计。这通常涉及观察用户如何真正使用你的产品,而不是在实验室环境中,因为有时会有你之前没有意识到的考虑因素。举一个例子,你可能会发现你的工厂控制系统用户必须戴着笨重的手套使用你的系统。这可能会影响你的设计,需要屏幕上的按钮比通常要大得多,以便更容易点击。如果没有在他们的自然环境中观察他们,你就不会知道这一点(这意味着在开始设计任何东西之前进行这项研究是有帮助的)。
-
可用性测试:定期与真实用户进行测试,以获取对设计和功能性的反馈,这对于迭代设计过程至关重要。你永远不希望假设你的设计是完美的;你希望尽可能多地测试这些假设,并根据你发现的内容进行修改。有许多形式的可用性测试:
-
在测试你的设计时,将摄像头对准用户。
-
在一个可用性实验室中设置,可以观察用户的行为,例如通过眼动追踪(以确定用户实际看什么以及他们注视什么)以及其他观察他们在行动中的表现。
-
进行用户访谈,你只需与用户坐在一起,询问他们关于其体验的问题(有时在他们玩你的网站时进行;有时在之后进行)。调查和焦点小组也可以考虑在这里。
还有其他几种形式,每种都有不同的目标和重点,但所有这些都有一个共同的目标,即验证你的设计决策是否符合真实的用户体验。
-
-
设计思维:一种涉及与用户共情、定义问题、构思解决方案、原型设计和测试的问题解决方法。作为设计师,很容易基于你喜欢的和认为最优的东西做出决定,但这并不总是对用户最好的。研究和测试在这个过程中扮演着重要角色,但还涉及到更大的思维模式。你必须克服做出假设和基于自己思考做出决定的冲动。最终,一切都是为了用户,这就是“以用户为中心的设计”这个术语的由来。用户是重点;他们是中心,你所做的一切都应该基于他们。
-
伦理和隐私:在设计时考虑伦理影响和隐私问题,尤其是在处理用户数据时。这可以有多种形式,但一个例子是简单地隐藏密码。你肯定见过当你在输入密码时,只有星号出现。这样做是为了防止有人站在你身后看到你的密码。这是一个简单的小例子,但它表明设计在隐私方面发挥着作用。
-
交互动画:微妙的动画可以通过提供反馈、展示因果关系,并为交互添加一层光泽和专业性来改善用户体验。我们在图形设计部分讨论了这一点,但鉴于它与可用性的关系,值得单独指出,因为动画可能是一个有争议的话题。过多的动画实际上会令用户烦恼和沮丧,甚至可能让他们感到恶心,而太少的动画则往往会导致设计不如使用恰到好处的动画那样令人愉悦。考虑到所有这些,这就是为什么使用动画来反映交互如此重要,以至于它成为一个关键主题。
-
视觉设计:尽管与 UX 不同,视觉设计(事物看起来如何)会影响 UX。它包括字体、配色方案和布局。实际上,这意味着与图形设计相同,但值得注意这个术语,因为你可能会在各个时候听到它。
将这些 UX 方面融入你的工作中可以极大地提高你开发或设计的软件产品的质量和用户满意度。在你作为网络开发者的角色中,将 UX 原则融入你的开发流程可以显著提高最终产品的有效性、效率和用户满意度。
虽然在某些组织中,可能会有一个完全独立的团队负责用户体验(UX),但这并不总是如此,即使如此,作为一个开发者,你对这些概念有所了解也是非常合理的,至少可以更好地与这样的独立团队进行接口。
如果你只从所有这些中记住一件事,那就让它成为:用户是这一切的核心!你必须基于他们的需求、他们的愿望以及让他们快乐的因素来做出设计决策。而且你需要基于良好的研究和测试来做出这些决策,而不是基于假设。
另一个在 UX 中扮演角色的主题是可访问性。
考虑可访问性
在某种程度上,可访问性是 UX 的另一个方面,但它是一个足够大的问题,可以成为一个单独的主题。
在数字设计的背景下,可访问性指的是使你的网站和应用尽可能由尽可能多的人使用,包括那些有残疾的人的做法。这包括广泛的情况,包括视觉、听觉、身体、言语、认知、语言、学习和神经系统的残疾。
这里有一些需要考虑的关键点:
-
网络内容可访问性指南(WCAG):这是由万维网联盟(W3C)(
www.w3.org/WAI/standards-guidelines/wcag)发布的一系列网络可访问性指南的一部分,包括一系列使网络内容更具可访问性的建议。它们包括许多你可以遵循的点,以帮助使你的网站对具有不同需求的各个用户群体更加可访问。 -
<img src="img/companyLogo.png" alt="This is a company logo showing an eagle with a fish in its beak">. Someone who is blind and is using a screen reader will have the alt text read out to them, providing a description of the logo. -
键盘导航:确保网站上的所有交互元素都可以通过键盘控制来访问和使用,这对于无法使用鼠标的用户至关重要。这通常很容易实现,在很大程度上是自动完成的,多亏了网络浏览器。然而,你需要记住的是相对简单的事情,比如表单中元素的顺序。除非你做错了什么,否则 HTML 表单应该默认可以通过键盘导航。但如果你没有将表单的元素按照逻辑顺序排列,可能会使得大多数用户需要通过键盘访问的元素变得更加困难。例如,在一个需要输入个人姓名的注册表单中,你很可能不希望将姓氏放在表单的末尾,中间有 10 个其他字段,而姓氏字段在顶部。
-
视觉设计:这包括考虑颜色对比以帮助色盲或视觉障碍用户,以及确保文本大小和间距可调整。其中一些又因为网络浏览器而免费提供,除非你编写了阻碍的糟糕代码。但也有一些设计元素发挥作用——比如使用与背景颜色搭配良好的颜色来显示文本,仅举一个简单的例子。
-
字幕和文本脚本:为视频内容提供字幕,为音频内容提供文本脚本,对于听力受损或听力障碍的用户至关重要。
-
一致的导航:在整个网站上保持导航的一致性,有助于认知障碍用户更好地理解和交互内容。当然,这对没有残疾的用户也是有益的,尽管区别在于,有残疾的人可能无法使用你的网站,而没有残疾的人可能只是不喜欢使用你的网站。前者更糟糕,但后者也不见得更好!
-
错误识别和说明:确保错误被清楚地识别,并提供说明以帮助用户理解如何完成任务,例如填写表格。首先,你绝对不希望冒犯你的用户,所以像错误消息的措辞这样的简单事情是一个因素。例如,写“需要输入名字”可能比“你没有输入名字”更好。但不仅如此,你还想确保你的错误消息和说明尽可能简洁地清楚地描述需要做什么。这比听起来要难,而且良好的用户研究在这里非常有帮助。
-
法律合规性:在许多地区,存在关于数字可访问性的法律要求,例如美国的美国残疾人法案(ADA)。这意味着我们在这里讨论的不仅仅是好的;在许多情况下,它是法律上必要的。
将可访问性融入你的软件开发和设计流程不仅扩大了你的用户群,而且反映了你对包容性和法律合规性的承诺。作为一名软件开发者/架构师,从项目一开始就考虑可访问性可以显著提高你应用程序的可用性和覆盖范围。
在用户体验的背景下,我想讨论的最后一个话题是,它经常与为移动设备设计(尽管不是唯一)相关,那就是响应式设计。
与响应式设计一起弯曲
另一个与用户体验相关但通常被视为一个独立概念的话题是响应式设计。今天,在全球范围内,人们通过手机等移动设备访问网站的人数往往比坐在办公桌前的大型显示器上的台式电脑要多。因此,采取以移动设备为先的设计策略通常是一个好主意——也就是说,设计你的网站使其在小尺寸设备上看起来和工作得很好,然后再增强其在更大屏幕上的表现。这就是响应式设计发挥作用的地方。
响应式设计是一种网站设计方法,其目标是创建提供最佳观看体验的网站,这意味着它易于阅读和导航,最小化调整大小、平移和滚动,适用于从大型台式电脑显示器到小型手机的广泛设备。虽然有一些特定的技术组件,但它更多地关乎基本方法,甚至是一种心态。它基于这样一个事实:如今,世界上很大一部分人口使用的是较小的移动设备来访问互联网,而不是通常配备大屏幕的台式电脑。
响应式设计的关键方面包括以下内容:
-
流动网格:网站的布局可以使用称为灵活网格的 CSS 结构。它们会自动适应观看者的屏幕大小,根据需要重新排列内容或重新排列。这也意味着,而不是设计固定宽度的布局,页面上的元素尺寸使用相对单位,如百分比,而不是绝对单位,如像素。
-
灵活的图片:响应式设计中的图片也是流动的。它们的尺寸使用相对单位来防止它们超出其包含元素显示。这确保了图片不会突然占据屏幕的大量空间,从而将更重要的内容推离原位。
-
媒体查询:CSS 媒体查询允许页面根据设备的特征使用不同的 CSS 样式规则,最常见的是浏览器的宽度。我很快会给出一个例子。
-
从移动端开始设计:这里的理念是您应该首先为小屏幕设计,然后根据需要添加更多功能和内容以适应大屏幕。这确保了从开始就为移动用户提供可访问性。
-
断点:在响应式设计中,断点用于定义网站布局应如何改变以适应不同的屏幕尺寸。常见的断点设置为适用于移动设备、平板电脑和桌面宽度。断点和媒体查询通常一起工作,断点通常部分通过媒体查询实现。
-
对于文本大小,使用
em或rem,这些是 CSS 中可用的替代单位(例如,与px和pt相比——CSS 中有许多单位可用于更高级的使用场景)。 -
触摸与鼠标:开发者应该意识到,一些用户将使用以触摸为主要交互方式的设备——例如智能手机——而桌面电脑更常使用鼠标。触摸交互有不同的需求,与鼠标交互不同。一个常见的例子是,按钮通常需要更大,以便于触摸交互。
-
性能优化:响应式网站需要在具有不同能力和连接速度的各种设备上加载。虽然许多用户将拥有现代、高性能的智能手机,但这并不总是如此。有些人可能拥有性能不足的设备,运行速度较慢。因此,优化图像、最小化 CSS 和 JS(通过各种工具减小其大小)以及利用如懒加载等技术(即图像和其他内容只有在用户滚动到足够远的位置以便它们可见时才加载),可以帮助提高所有用户的性能,尤其是那些使用较弱设备的用户。
-
测试:响应式设计必须在多个设备和浏览器上测试,以确保兼容性和可用性。这可能包括物理设备测试、模拟器和可以同时模拟许多不同设备和配置的在线网站。
在用户期望无论在手机、平板电脑、笔记本电脑还是桌面上都能获得无缝体验的网页环境中,响应式设计至关重要。对于网页开发者来说,理解响应式设计的原则对于创建在所有设备上都易于访问和用户友好的网络应用程序至关重要。
展示响应式设计
为了在非常简单的层面上展示响应式设计,请看这个 HTML 页面:
<html>
<head>
<title>Simple Responsive Design Example</title>
<style>
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
@media (max-width: 600px) {
.container { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div style="background-color:#b0b0b0;">Column 1</div>
<div style="background-color:#b0b0b0;">Column 2</div>
<div style="background-color:#b0b0b0;">Column 3</div>
</div>
</body>
</html>
为了分解这个过程,让我们首先看看<body>内容。正如您所看到的,它是一个包含在单个<div>元素内的三个<div>元素。外部的<div>元素被分配了container样式类,而所有三个内部的<div>元素都有一个内联样式,以赋予它们灰色背景颜色。
我们在这里做的是构建一个包含三个内容框的页面。也许第一个是导航菜单,第二个是主要内容,第三个可能是用于一些广告(因为似乎世界上每个网站今天都必须有广告,不幸的是!)这是一个相当典型的网站布局。
现在,在大型屏幕上,我们希望将此内容以三列的形式显示,如图图 5.9所示:

图 5.9:我们的网页以三列布局
然而,在较小的设备上(特别是宽度小于 600 像素的屏幕上),我们希望此内容可以包裹,使得所有三个框“堆叠”在一起,如图图 5.10所示:

图 5.10:我们的网页,现在“堆叠”使得框实际上变成了行
当你在大型屏幕上调整浏览器窗口大小时,这也会发生。当你使窗口变小时,内容应自动重新布局,如图图 5.10所示,我鼓励你现在实际操作一下,看看会发生什么。
是 CSS 负责这种内容的重新排列,所以现在让我们来看看。
首先,我们有container类定义,它应用于我们的<div>元素。这使用了一个名为display的 CSS 特性来grid,告诉浏览器这个<div>元素内的内容应该以网格形式布局,这意味着我们将在其中有一些行和列。设置grid-template-columns指定我们想要列,而repeat(3, 1fr)值指定我们想要三个大小相等的列。repeat()语句允许我们指定多个元素的风格,而fr单位是一个灵活的单位,允许我们定义每个列占总可用空间的多少。所以1fr意味着将有三个列,每个列应该获得总空间的一个相等部分,因此浏览器将在它们之间平均分配空间。最后,gap属性简单地告诉浏览器在列之间放置多少空间,如果有。总的来说,这定义了我们想要我们的三个框默认使用的网格布局。
这是响应式方程的第一部分。第二部分是@media语句,它是一个max-width部分。换句话说,这个媒体查询内的样式只会在浏览器窗口宽度小于600像素时应用。这意味着我们定义了一个600像素的宽度:我们希望页面的布局——更具体地说——<div>容器——在我们达到600像素宽时改变。
在 600 像素以下,应用于<div>容器的样式会覆盖container类本身定义的grid-template-columns值,将其设置为1fr。由于这次没有使用repeat(),我们将只得到一个列,而1fr现在意味着总空间中的一份应该分配给它,但由于只有一个列,所有空间都被它占用,这导致盒子内部相互包裹。
你可以定义尽可能多的断点,并且你可以使用媒体查询根据其他条件应用样式,但谈到响应式设计,几乎总是要使用这样的宽度。
渐进增强的进展
网站是否可访问还可能受到超出你控制范围的因素影响,例如浏览器的功能甚至用户的连接性。我们讨论过,如今许多用户使用手机等小型设备来访问网站,这有时意味着浏览器功能较弱。即使浏览器功能齐全,想象一下用户在乘坐火车时尝试访问网站的场景。在这种情况下,他们的连接速度可能比在家时慢得多,而且可能也不稳定。
这种类型的担忧正是被称为渐进增强的网页开发方法发挥作用的地方。这是这样一个理论:你应该从一个骨架设计开始,然后在根据连接速度和浏览器功能等因素访问时对其进行增强。
一个简单的例子可能从以下简单的标记开始:
<html>
<head>
<title>Progressive Example</title>
</head>
<body>
<form action="/handleForm" method="post" id="myForm">
Email: <input type="text" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
<div id="myDiv" style="color:red;"></div>
</body>
</html>
这是一个完整、功能性的——尽管非常简单——网站,可能允许用户订阅你的通讯。它几乎在任何设备上的任何浏览器上都能正常工作。然而,如果我们想确保电子邮件地址是有效的形式呢?这用 JS 很容易做到,但如果浏览器根本不支持 JS 或者用户禁用了它怎么办?我们需要确保页面即使在没有 JS 的情况下也能工作,但在 JS 可用时工作得更好。这正是渐进增强发挥作用的地方(这是ch-5目录中的progressive.html文件):
<html>
<head>
<title>Progressive Example</title>
</head>
<body>
<form action="/handleForm" method="post" id="myForm">
Email: <input type="text" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
<div id="myDiv" style="color:red;"></div>
</body>
<script>
document.getElementById("myForm").addEventListener("submit", function(event) {
const email = document.getElementById("email").value;
if (!email.includes("@")) {
document.getElementById("myDiv").innerHTML = "Email must contain @";
event.preventDefault();
}
});
</script>
</html>
现在,当页面完全加载时,<script>块会执行。在其中,我们通过document.getElementById()获取表单的引用,然后对返回的 DOM 节点调用addEventListener()。这为表单的submit事件添加了一个事件监听函数,当用户点击email字段时触发。然后它会检查是否包含@符号。虽然这绝对不是对电子邮件地址有效性的完整测试,但对于这个简单的例子来说已经足够好了。如果没有@符号,那么在myDiv <div>元素中会显示错误消息。最后,调用event.preventDefault()阻止表单提交(传递给函数的event对象包含函数被调用的所有事件信息——在这个例子中是表单提交,并提供了一些与之交互的函数,包括preventDefault()函数来终止默认操作,在这个例子中就是将表单发送到服务器)。
这是一件好事,因为当你可以在不涉及服务器的情况下在浏览器中捕获像这样的简单错误时,这是一种更有效的方法。但是,由于浏览器会忽略它们不理解的内容,包括如果 JS 不可用时的<script>块,所以它存在是没有害处的。如果 JS 不可用,那么你仍然有一个功能性的表单(并且假设电子邮件地址将在服务器上进行有效性检查)。但是当 JS 可用时,页面会增强以在浏览器中进行电子邮件地址验证。这就是渐进增强的基本理念。
响应式设计实际上是一种渐进增强的形式,因为显示可以根据屏幕大小进行变化,为所有用户提供功能。响应式设计和渐进增强都涉及到可访问性,因为可访问性的基本理念就是使网站可访问。如果表单提交依赖于JS 的运行,那么当 JS 不可用时,页面就会损坏,因此这些用户无法访问。通过渐进增强,我们确保一切大致以相同的方式工作。
渐进增强的理念也引出了另一个被称为渐进式 Web 应用(PWAs)的概念,这正是我们现在要讨论的内容。
PWAs
到目前为止讨论的所有想法都引出了一个最终的前端主题:PWAs。
PWAs 是一种通过使用常见的 Web 技术(如 HTML、CSS 和 JS,这些技术可能或可能不使用各种库,你将在下一章中探索它们)来编写更高级的网站——实际上是 Web 应用的方式。它们旨在在任何使用标准兼容 Web 浏览器的平台上运行。PWAs 具有几个定义性的特性和优势:
-
响应式:PWAs 应适应任何形态,无论是桌面、移动、平板,还是尚未开发或处于早期阶段的设备(比如虚拟现实设备?)
-
渐进式增强:应用程序的核心功能应该适用于所有用户,无论他们的浏览器和操作系统功能如何,但随后使用渐进式增强为功能更强大的平台上的用户提供更好的体验。
-
连接独立性:使用一个名为服务工作者的特殊 JS 代码片段,PWA 即使在用户离线时也能工作。这是通过在客户端存储资源(HTML、CSS、JS、图片等)并在用户无法连接到网络时使用这些存储的版本(称为缓存)来实现的。
-
类似应用的体验:PWA(渐进式 Web 应用)常常给人一种更像桌面应用(我们称之为原生应用)而不是网站的感觉。这主要是因为像操作系统生成的其他警报一样出现的警报、能够使用通常在网站上无法使用的硬件设备,以及更复杂的交互(例如多窗口)等因素。
-
新鲜感:虽然服务工作者允许离线访问,但它们还提供了一种机制,在在线时更新那些缓存的资源。这确保了 PWA 拥有最新的代码,因此对用户来说看起来是新鲜的。
-
安全性:PWA 只能通过 HTTPS 连接提供服务。这防止了窃听并确保内容未被篡改。
-
可发现性:由于在构建 PWA 时必须提供一些特殊的配置文件,因此浏览器和操作系统将它们识别为应用程序,而不仅仅是网站。这正是它们能够被安装的原因。
-
可安装性:用户可以在他们的手机主屏幕上添加应用程序,或者在 PC 或 Mac 的桌面/开始菜单上添加,而无需通过应用商店。只需像访问任何其他网站一样访问一个 URL,只要网站提供一些必要的组件(服务工作者和一个名为清单文件的配置文件,主要是),浏览器和操作系统就会将其视为可安装的应用程序,就像你可能安装的任何其他应用程序一样。
-
可链接性:由于 PWA 本质上仍然是一个网站——只是功能更强大——这意味着它可以通过 URL 轻松共享,无需复杂的安装。通常,访问设置为 PWA 的网站将触发一个请求,询问你是否想要安装它。之后,它看起来就像你手机或电脑上安装的任何其他应用程序一样。
对于软件开发者来说,构建 PWA 涉及创建一个响应式 Web 应用程序,该应用程序包括一个清单文件和一个服务工作者——清单文件向浏览器提供有关应用程序的信息(例如名称、作者、图标和描述),而服务工作者通过缓存和更新资源来启用推送通知、后台数据同步和离线功能等特性。PWA 弥合了 Web 应用和原生应用之间的差距,提供了原生应用的 UX 和功能,以及 Web 的覆盖范围和可访问性。
构建一个 PWA(渐进式 Web 应用)并不复杂,但它确实涉及一些可能很棘手的步骤,尤其是在处理仅需要通过 HTTPS 工作的需求时。正因为如此,我这里不会演示如何构建一个,因为这绝对是一个更高级的话题。虽然 PWA 在当今相当流行,但它们并不被普遍支持,也不是你绝对需要知道的东西。实际上,你可能根本不需要构建一个。如果你需要,那么本节应该已经为你理解基础知识奠定了基础,你可以在需要的时候再进行探索。
所以,是的——确保你的网站看起来不错,遵循良好的用户体验(UX)实践,以便对用户来说功能齐全,这一点非常重要。实现响应式设计通常是其中的一部分,使用渐进式概念也可以为用户提供更高效的使用体验。PWA(渐进式 Web 应用)是之后的另一个潜在层次。
但如果你没有将你的网站放在那些用户可以访问的地方,那么这一切都不会很重要。虽然这可以简单到将你的代码放在一个物理服务器上,但在当今这个时代,这并不一定是最好的答案。而且,不管怎样,这绝对不是唯一的答案。沿着这个思路,另一个在部署网站时起到作用的观念是虚拟化,通过虚拟机(VMs)和容器来实现,这正是接下来要讨论的主题!
虚拟化世界——虚拟机和容器
在上一章中,你通过一些在线 Linux 沙盒(你确实做了那件事,对吧?)对 Linux 进行了一些操作。你是否停下来想过这些网站是如何做到这一点的?你实际上是在登录一个运行 Linux 的机器,但肯定不可能为每个可能同时进行相同操作的用户都有一个物理服务器,对吧?很可能答案是否定的。相反,他们最可能使用虚拟机或容器来实现这一神奇的功能。
虚拟机(VMs)和容器是两种不同但相关的技术,用于创建高效、隔离的应用程序运行环境。它们都有相同的基本目标,即虚拟化一个环境。
但“虚拟化”是什么意思?为了解释这一点,你必须考虑计算机通常是如何工作的。
通常,你有一些硬件——显然,像主板、CPU、内存、各种用于组件之间通信的总线等。这些都是物理的东西——电子和所有这些。
然后,在硬件之上,你有一个操作系统或 OS。它可能是 Windows,也可能是 Linux,或者可能是 macOS 等等。敏锐的读者可能会意识到还有一个叫做固件的东西,这是一种永久嵌入在硬件中的软件。你可以把它看作是硬件的一部分,因为这就是它所在的地方。大部分情况下,它是硬件加上以操作系统形式存在的软件,当然,然后是所有在之上运行的应用程序,这又是更多的软件。
现在,虚拟化本质上意味着将所有这些——硬件、软件/固件,所有的一切——都在计算机中模拟出来。通过一些非常巧妙的编码,我们可以在软件中模拟出一个计算机,甚至可以模拟到硬件层面。这种工作原理远远超出了本书的范围,但幸运的是,这实际上并不重要!我们只需简单地为此魔法存在而感到高兴。
这种做法的好处,可能也是最大的好处,就是这种虚拟化计算机——它与运行它的计算机(通常称为宿主)完全分离。如果你在宿主机上创建一个文件,通常情况下,它无法从运行在虚拟机上的计算机中访问,反之亦然。如果你在虚拟机上安装了一些软件,它对宿主机没有任何影响。这实际上就像你有两台不同的计算机;它们彼此隔离。
虚拟化这种计算机的方式不止一种,其中两种最常见的是虚拟机和容器。它们都力求实现相同的基本目标——在另一个计算机内部模拟计算机——但它们在方法和架构上存在显著差异。现在让我们来谈谈这两种,看看它们如何比较和不同,以及为什么我们可能选择其中一种而不是另一种。
理解虚拟机(VMs)
虚拟机正是我刚才描述的。从架构上看,虚拟机不仅包括应用程序和提供用户可能需要的功能的必要二进制文件和库,还包括一个完整的操作系统,我们通常称之为客户机。
虚拟机运行在物理服务器上,并由虚拟化程序(如 VMware 的 ESXi、Microsoft 的 Hyper-V 或 Oracle 的 VirtualBox)管理。虚拟化程序实际上是一个管理虚拟机的程序,管理诸如它们对底层物理硬件的访问等问题。由于一个物理机上可以运行多个虚拟机,并且这些机器在某种程度上需要使用和共享真实的物理硬件,因此虚拟化程序管理这种共享并确保没有冲突。
虚拟机非常适合运行需要完整操作系统(OS)的应用程序,确保完全隔离,或者在不同类型的操作系统上运行应用程序。例如,如果你通常使用 Windows 机器,但想尝试 Linux,你可以使用你喜欢的 Linux 版本创建一个虚拟机,它将作为一个独立的窗口出现,正如你可以在图 5.11中看到的那样。然后你可以在 Linux 中安装软件,完全独立于你的 Windows 宿主机:

图 5.11:在 Windows 11 上运行在 VirtualBox 虚拟机中的 Ubuntu Linux
虚拟机非常兼容,因此可以运行你喜欢的几乎所有操作系统。实际上,你甚至可以运行那些在你的物理机上不再工作的旧操作系统,这为运行遗留应用程序或需要特定操作系统环境的应用程序提供了解决方案,而你通常不希望将这些应用程序运行在你的计算机上。
每个虚拟机都有自己的虚拟硬件集合(CPU、内存、硬盘等),包括存储设备,实际上是从你的物理存储中划分出来的。这可能导致宿主机上更高的存储需求。例如,如果你的计算机中有一个 500GB 的硬盘,你想要创建一个具有 100GB 硬盘的 Linux 虚拟机,这意味着你将失去 100GB 的物理硬盘空间给虚拟机。
虚拟机由于需要支持独立的操作系统和虚拟硬件配置,因此需要更多的管理开销。记住——你现在实际上要处理不止一台计算机了!当你第一次创建虚拟机时,它是“裸机”的,这意味着它就像一台没有任何东西的物理计算机。然后你必须自己安装操作系统(这意味着你需要为其获取适当的许可证)。稍后,当需要更新时,你需要在你的宿主机上执行这些操作,这还包括你可能需要执行的任何其他操作。这仅仅是需要你进行更多的管理,就像拥有两台物理机器一样。
现在的虚拟机提供了出色的性能,考虑到它们正在模拟一个完整的计算机,甚至到硬件级别,这有点令人惊讶。为了实现这一点,有很多技巧被使用,比如有效共享物理硬件。但不管怎样,在足够强大的宿主机上,你应该只会注意到在客户机上的微小性能差异。事实上,如果你最大化一个虚拟机运行的窗口,你应该会发现很难判断它此时不是物理机器。
现在,让我们在 VirtualBox 中设置一个 Ubuntu Linux 虚拟机,以便进行实验。
使用虚拟机
根据本章的技术要求部分,你应该已经安装了 VirtualBox,并且应该已经下载了 Ubuntu Linux 的副本。现在,要在 VirtualBox 中设置虚拟机,点击机器菜单,然后点击新建按钮。你应该会看到一个类似于图 5.12所示的对话框,在那里你可以输入一个名称,选择一个文件夹来存储所有虚拟机文件,并选择正确的类型和版本(Linux和Ubuntu,很可能是 Ubuntu 的 64 位版本):

图 5.12:在 VirtualBox 中设置 Ubuntu
然后,你需要告诉 VirtualBox 使用你下载的 VDI 文件。VDI代表虚拟磁盘镜像,它是一个存储存储设备内容的文件,如硬盘,直到字节级别,因此它是该设备的精确字节复制。为此,点击底部的硬盘部分,选择使用现有的虚拟硬盘文件,然后点击下拉菜单旁边的图标,该菜单中显示为空。然后你会看到图 5.13所示的对话框。点击添加并添加你之前下载的 VDI 文件。确保它被高亮显示,然后点击选择:

图 5.13:将 VDI 添加到 VirtualBox
那个对话框将会关闭,你将回到图 5.12的对话框。那里的所有默认值都应该没问题,所以你可以直接点击完成。你现在有一个 Ubuntu 虚拟机了!
现在你只需要点击osboxes.org)。当你尝试时,你会意识到,这就像你在那个窗口中连接到了另一台不同的计算机一样,这当然是虚拟机的全部意义。你可以运行各种应用程序并安装新的应用程序——你可以在真实计算机上做的任何事情都可以在这个虚拟机中完成。
正如我之前提到的,虚拟机只是虚拟化猫皮的一种方式。另一种极其流行的方式是容器,所以现在让我们来看看它们。
理解容器
容器与虚拟机不同,因为它们不是整个计算机的模拟,尽管你可能会被误导以为它们是。在许多情况下,你可以登录到容器中,并在其中做所有在虚拟机或物理机器上能做的事情。它们通过在宿主操作系统的底层隔离容器内部和宿主外部的内容来实现这一点。
容器主要是一个 Linux 设施,因为它们依赖于 Linux 内核中某些底层功能(整个 Linux 操作系统构建在其之上的核心代码)。你也可以在 Windows 上运行容器,但那里实际上是通过使用一个对你来说透明的 Linux 虚拟机来实现的!
但无论如何,容器的基本架构是这样的:容器将应用程序及其依赖项(如库、二进制文件等)打包在一起,但共享宿主系统的操作系统内核。而内核在虚拟机内部是模拟的,而容器使用的是宿主操作系统实际运行的内核。
就像虚拟机一样,容器虽然共享内核和其他系统资源,但它们与宿主操作系统是隔离的。操作系统本身会自动处理这种隔离,所以从我们的用户角度来看,它看起来像是一个虚拟机,而虚拟机当然看起来像一台物理机器。
容器相对于虚拟机有几个优点:
-
首先,因为它们没有模拟整个机器,所以它们明显更快。这在启动时尤为明显。虚拟机必须像真实计算机一样经历完整的操作系统启动周期,而容器则不需要,因此它们几乎可以立即启动。它们还使用较少的宿主资源,因为,再次强调,它们没有模拟整个计算机。
-
其次,容器提供了一个更一致的环境。想想我们之前章节中提到的,能够给开发者一个
package.json文件,他们可以使用它运行npm install来获取应用程序需要的所有依赖项。这确实很方便,但如果他们安装的 Node 版本与您不同怎么办?而且如果那个版本恰好有一个您没有的 bug 怎么办?那个开发者可能会发现,您的项目在他的机器上无法工作,但在您的机器上可以工作。容器通过捆绑您的代码及其依赖项,甚至包括 Node 这样的东西来解决这个问题。如果您给那个开发者一个容器,您就可以确保他们的环境与您的环境匹配,这样您就可以知道您的代码将能够工作。
容器是一个通用的术语,指的是操作系统的功能。但您通常不会直接与这个功能交互,因为它,用温和的话说,不是一个愉快的体验。相反,您几乎肯定会使用某种软件,这种软件是围绕该功能的包装。目前最受欢迎的这种包装软件是 Docker。事实上,它如此受欢迎,以至于人们通常称之为Docker 容器而不是仅仅容器。现在让我们就 Docker 下的容器进行一些讨论,并看看它们在实际中的应用!
使用容器
为了玩转容器,我们实际上将使用您全新的 Ubuntu 虚拟机(VM)!您看,由于容器需要 Linux,而且我不知道您通常使用什么操作系统,我至少可以确信您在这个时候在 VirtualBox 中运行了 Ubuntu,这样我知道您有一个可用的 Linux 机器,尽管是虚拟的!
在您的虚拟机(VM)内部,您首先需要启动终端应用程序。这将带您进入命令提示符。接下来,您需要使用以下命令安装 Docker:
sudo apt install docker.io
当提示确认安装时,请确认,然后根据您的网络连接速度,等待一分钟左右。一旦安装完成,执行以下命令:
sudo docker
您将看到帮助文档的提示。现在,您已经准备好启动容器了!
正如讨论编程中的任何看似相关内容时的惯例,我们将从启动一个“Hello, World!”容器开始。幸运的是,Docker Hub,这是一个由 Docker 背后的公司运营的公共镜像仓库网站,恰好有这样一个镜像可供使用!我们可以将镜像拉取到本地机器上,并使用它来创建一个容器,这一切都只需要一个简单的命令:
sudo docker run hello-world
继续执行该命令,几秒钟后,您应该会看到类似图 5.14的界面欢迎您:

图 5.14:您的第一个 Docker 容器!
实际上发生的情况是,首先,Docker 将在您的系统上寻找名为 hello-world 的镜像。当您创建一个容器时,您始终从一个称为镜像的蓝图开始。镜像几乎总是从一个操作系统开始,然后在其上添加一些内容,最终形成最终的镜像。您可以将镜像视为在特定时间点的操作系统文件系统的快照,但它不是您宿主操作系统的快照;它是构建镜像层层的步骤结果的快照,就像您亲自执行这些命令一样。然后 Docker 从该镜像创建一个容器。如果您愿意,可以基于单个镜像运行多个容器,就像您可以从相同的蓝图建造多座房屋一样。
如果指定的镜像尚未存在于您的系统上,Docker 将调用 Docker Hub 并寻找您请求的名称的镜像,如果找到(或者如果您没有找到,则给出错误)。从那时起,新的容器可以基于该镜像创建,而无需再次从 Docker Hub 请求。
现在您已经创建了第一个容器,让我们简要地看看一些您可能需要与容器一起使用的最常见命令。
学习基本的 Docker 命令
docker 程序是您进入 Docker 本身的接口,它提供了许多不同的命令来控制 Docker。让我们看看一些例子。
列出镜像
您如何知道系统上有哪些可用的镜像和容器?嗯,这很简单:
sudo docker images
这将列出之前下载到您系统上的所有镜像,您可以从这些镜像创建容器。
列出容器
对于列出容器,同样简单:
sudo docker ps
基于 Linux 的 ps 命令建模,此命令显示您的容器,但它只显示正在运行的容器。hello-world 容器完成工作后,会立即关闭。因此,您不会看到它通过此命令运行。其他容器将继续运行,并且会通过此命令显示;这完全取决于容器打算做什么。
然而,容器在某种意义上仍然存在,您可以通过在之前的命令中添加一个选项来查看它:
sudo docker ps -a
启动(和停止)容器
如果您想再次启动容器,可以运行以下命令:
sudo docker start <container_id_or_name>
然而,对于这个 hello-world 容器,当您这样做时,将不会发生任何事,容器会立即退出。它似乎只在初次运行时显示有用的信息。
您也可以使用此命令停止正在运行的容器:
sudo docker stop <container_id_or_name>
作为一个小插曲,此时你可能意识到,快速输入 Docker 生成的容器 ID 可能会变得很烦人(而且,此外,你也看到我写过<container_id_or_name>,这意味着你可以做些除了使用默认 ID 之外的事情)。幸运的是,Docker 还为每个容器生成一个名称,正如你在ps输出中看到的那样(而且看到它吐出什么内容通常很有趣!)。然而,自己给它起个名字会更友好,你可以使用以下选项:
docker run --name MyAwesomeContainer hello-world
现在,你应该能看到一个指定名称的容器,然后你可以用它来与之交互。
你还应该意识到,当使用 ID 时,你可以输入部分 ID,从开头开始输入足够多的字符以使其唯一。前几个字符通常就足够了。然而,在我看来,提供名称是更好的、更简单的方法,因为你不太可能不小心对错误的容器做了什么(至少在理论上是这样!)。
删除容器和镜像
你可能想在某个时候清理这些容器,为此有一个命令:
sudo docker rm <container_id_or_name>
如果容器正在运行,Docker 不会让你删除它,直到你停止它。同样,你可以这样删除镜像:
sudo docker rmi <image_id_or_name>
就像容器一样,Docker 不会允许你删除被容器使用的镜像,无论它是否正在运行,所以你需要先清理容器。
搜索镜像
虽然我认为通过网页访问 Docker Hub 查找镜像要容易得多,你可以在hub.docker.com做到这一点,但你也可以直接从命令行搜索镜像:
sudo docker search hello-world
这会返回一个包含hello-world字符串的许多镜像的列表。
连接到容器
有时候,你可能想将容器当作虚拟机来对待,并登录到它们,假设它是一个持续运行的容器。为了展示这一点,我们需要一个会持续运行的容器,所以让我们使用nginx:
sudo docker run -d -p 8080:80 --name my_nginx nginx
-d选项“断开”了终端会话与容器的连接,这导致它在后台运行(假设容器内部的内容不会立即退出)。你将返回到命令提示符,但容器将继续运行。-p选项用于暴露网络端口。在这里,我们说的是容器内部的8080端口应该暴露为主机网络接口上的80端口。
一旦执行此命令,执行sudo docker ps,你应该会看到第一次运行的容器。
现在,有一个容器正在运行,我们可以继续连接到它:
sudo docker exec -it my_nginx /bin/bash
在这里,你可以看到一些东西。首先,-i选项保持系统的STDIN输入流打开,以便与之交互。-t选项分配一个新的伪终端会话(从本质上讲,这意味着一个 Linux 命令提示符)。最后的/bin/bash是一个在容器启动后执行的命令,在这种情况下指定了一个 Linux bash命令提示符。这三个选项的组合意味着你“进入”了容器。
你看到的命令提示符是容器内部的命令提示符。你可以执行一些bash命令(例如,ls),你应该会注意到你所看到的内容与你的宿主操作系统不同(尽管如果你使用的是 Ubuntu,那么看起来可能非常相似)。执行exit命令,你将返回到实际的宿主虚拟机的命令提示符。
查看容器日志
最后,即使没有连接到容器,你也可以查看容器内部产生的日志:
docker logs my_nginx
在这里,“日志”包括任何被路由到标准输出的内容,除非容器内部进行了特定的配置。
现在你对虚拟机(VMs)和容器(containers)有了大概的了解,让我们来看看它们之间的关键区别,以及在你决定使用哪一种时需要问自己的问题。
虚拟机和容器之间的关键区别以及如何选择
虚拟机和容器之间有几个区别,这些区别通常会影响决定使用哪一种:
-
性能:容器通常比虚拟机更快、更高效。在考虑它们启动速度时这一点无疑是正确的,但它们运行时也是如此。记住——虚拟机是在模拟整个计算机,而容器不是,这种模拟会消耗宿主机的处理器时间和内存利用率,所有这些都会影响虚拟机和宿主机的性能。
-
开销:在某种程度上,这是性能的另一个方面,但它足够不同,值得单独指出。由于每个实例都需要完整的操作系统,虚拟机在 CPU、内存、存储和其他系统资源方面有更多的开销。容器更直接地共享宿主机的资源,因此虽然它们确实有一定的开销,但通常比虚拟机低得多。
-
便携性:容器在不同机器之间具有高度的便携性,只要它们运行相同的操作系统内核。换句话说,容器内的文件必须与宿主机的内核兼容,只要它们兼容,容器就可以在任何机器上运行。对于虚拟机来说,情况并非如此,这意味着它们比容器更具便携性,因为你可以在一个 Linux 宿主机上运行 Windows 虚拟机,在一个 Windows 宿主机上运行 macOS 虚拟机,或者任何你想要的组合。
-
安全性:虚拟机提供了更多的隔离,这可以有益于安全性,而容器在大多数情况下提供了足够的隔离,但需要谨慎管理以确保安全性。虚拟机的完全隔离可以提供一个更安全的环境,因为一个虚拟机的妥协不会直接威胁到其他虚拟机。鉴于容器共享主机操作系统的内核,应该很明显,至少存在一些安全问题的可能性,内核本身的漏洞可能会影响所有容器。然而,在实践中,只要你的主机机器是最新的,并且你的容器配置得当,它们在安全方面相当安全。
-
管理和编排:虚拟机通常使用相当高级的管理工具进行管理,例如 VMware vCenter 或 Microsoft System Center,这些工具提供了对多个虚拟机的集中管理。容器通常更多地从命令提示符进行管理,尽管当然也有图形管理工具。虚拟机管理工具允许轻松部署、控制和扩展虚拟机。扩展意味着如果虚拟机需要更多资源,它可以在运行时进行扩展。对于容器,答案通常是简单地创建更多实例的特定容器,并在它们之间平衡工作。这被称为编排,这也是更高级的工具如 Kubernetes 发挥作用的地方。这是一个 Google 产品,它允许对容器进行更灵活的控制——例如更有效地分组以处理负载。这是一个非常强大的工具,但它也比容器本身复杂得多。
-
快速部署和扩展:我在上一个要点中提到了这一点,但这是一个重要的观点:因为容器启动非常快且更轻量级,所以容器可以快速、轻松地启动、停止和复制,这使得它们非常适合动态、可扩展的环境。相比之下,虚拟机启动需要更长的时间,并且使用更多的主机资源。这意味着对于一台特定的服务器,你几乎肯定可以运行比虚拟机更多的容器,所以如果你在你的新网站上开始获得大量新用户,你可以创建更多的容器来更快、更轻松地处理流量,并且比使用虚拟机有更少的资源需求。
总结来说,虽然虚拟机和容器之间确实存在重叠,但每个都有其独特的优势和劣势。虚拟机提供了完整的隔离和完整的操作系统环境,这使得它们适合需要完整操作系统或不同操作系统类型的应用程序。但这使得它们在资源需求方面更重。
容器由于不是完整的运行操作系统,因此更轻便、更高效,对于需要便携性和快速扩展的应用程序来说非常理想。我甚至可以说,今天,通常更安全地假设你将比虚拟机更频繁地使用容器来部署应用程序或网站(如果你不是要在服务器上简单地运行代码,我们通常称之为裸机)。特别是在你预计你的负载需求(使用你网站的用户的数量)可能会迅速增加的情况下,容器通常是更好的选择(虚拟机也可以扩展,但过程效率较低)。另一方面,如果你主要关心绝对的安全性,并且/或者需要与同一物理机上可能运行的任何其他东西完全隔离,那么虚拟机是一个非常不错的选择。
现在你已经对容器和虚拟机有了一些了解,让我们谈谈你可能最终会运行它们的地方(或者只是运行“裸露”的应用程序,根本不使用虚拟机或容器),那就是云。
保持头脑清醒——云服务提供商
有些人喜欢陈词滥调地说,云只是意味着在别人的电脑上运行你的代码。我的意思是,这并不完全错误!但其中还有更多。
云指的是云计算,它是指通过互联网提供各种服务,包括数据存储、服务器、数据库、网络和软件(即:你的网站和应用程序)。云计算允许灵活的资源(这意味着你可以在需要时获得所需的一切),更快的创新(因为你不需要自己构建和配置服务器和服务),以及规模经济(因为云背后的基础设施成本被分摊到所有使用它的人身上)。你通常只为使用的云服务付费,这有助于降低运营成本,更有效地运行基础设施,并根据业务需求的变化进行扩展。
假设你不想使用云。这可能意味着你必须购买自己的硬件,设置它,并配置操作系统及其上的所有服务(数据库、邮件服务、用户身份验证服务、Web 服务器等等)。然后,你必须将代码放在上面并运行它,确保整个系统是安全的。然后,如果你的网站变得非常受欢迎,开始获得更多流量,你可能需要添加硬件来扩展容量或重新配置服务以处理额外的负载。最终,你可能需要添加整个服务器,并配置网络以实现负载均衡。
使用云服务,这些担忧都是自动的,或者至少可以通过更新云服务提供商提供的控制面板网站上的某些设置来改变。你更改一些设置,点击一个按钮,突然你就有更多的内存、更多的带宽和更多的 CPU 可用于处理扩展的负载。这是一件更容易做的事情。而且成本节约是显著的:当你自己构建服务器时,你必须猜测负载,通常最好是超额购买。如果你认为一个配备单核 3 GHz CPU 的服务器将足够,你可能一开始就想购买一个双核机器,以便有增长的空间。但是,如果你没有增长,现在你实际上已经浪费了钱。在云上,当你看到你需要第二个 CPU 时,你可以添加它,这样你就不会为不需要或可能不需要一段时间的 CPU 付费。
介绍云服务提供商
现在,有许多不同的云服务提供商,但最大的是 Amazon Web Services (AWS), Microsoft Azure 和 Google Cloud Platform (GCP)。Oracle 也有一个相当受欢迎的云服务,IBM 和一些其他较小的玩家也是如此,但亚马逊、微软和谷歌无疑是巨头。最终,尽管如此,他们都有相同的基本提议:在你的基础设施上运行你的代码,只使用你需要的部分。
听到大多数人将互联网与谷歌公司联系在一起,而谷歌的云服务,坦白说,并不像 AWS 和 Azure 那样受欢迎,这可能会让人感到惊讶。AWS 和 Azure 是截至本文撰写时最受欢迎的两个云服务提供商,几乎毫无疑问。事实上,当人们今天谈论“云”时,他们几乎肯定是指这两个提供商之一。他们在使用和市场份额方面比其他所有服务都要大得多。
然而,不管怎样,所有云服务提供商都提供了一大堆不同的服务,具有很高的稳定性。当然,这不是免费的:如果你不小心,使用公共云可能会相当昂贵。在网上找到关于人们因为他们的网站一夜之间变得超级受欢迎而醒来发现巨大的 AWS 账单的恐怖故事并不困难。在使用它们时,你绝对需要谨慎。幸运的是,所有好的云服务提供商都提供了控制支出的方法。例如,在 AWS 上,你可以配置账户的支出限制,这样你就不会突然收到一个改变你生活的账单。
然而,这不仅仅是这样:你必须考虑云是否真的是特定用例的正确答案。有时,它显然是:例如,当你知道你的负载需求将会高度变化时。在这种情况下,只使用你需要的容量是非常吸引人的。显然,自己设置云服务提供商提供的各种服务的复杂性可能会让人感到压倒,并且在这种情况下可能会非常昂贵。
但有时,拥有自己的硬件既便宜又简单,特别是如果你对未来的负载有很好的了解。而且如果你正在构建的东西不需要云提供的大部分功能,那么自己动手可能更有意义。
这不是简单的微积分,许多许多因素都会影响到这样的决策,其中许多因素与项目本身密切相关。我并不是试图提供一个用于此类决策的清单,因为这几乎是一项不可能完成的任务。相反,我只是想指出,是否选择云服务是一个你需要根据每个项目的独特需求来做出的决策。云服务当然有好处,但也有不利之处。
更复杂的是,云服务并不是单一的东西,无论它是谁的云。实际上,你需要熟悉三种类型的云。现在让我们来谈谈它们。
理解云类型
云可以是公有、私有或混合:
-
公有云:这些是通过公共互联网提供的云服务,对任何愿意付费的人开放。公有云通常包括 AWS、Azure 和 GCP 等服务。它们在由提供商提供、维护和完全控制的数据中心中运行。
-
私有云:这是仅由单个企业或组织使用的服务器基础设施,使其仅对内部开发团队可用。这通常由那些希望或需要更高控制水平的大型公司完成,特别是在像银行这样的领域,监管机构规定了更严格的敏感数据控制水平。
-
混合云:结合了公有云和私有云,通过技术手段实现数据和应用程序的共享。想象一下,在你的私有云上运行你的网站,然后通过连接到公有云中的数据库服务来节省一些费用(数据库软件在大规模上往往相当昂贵)。
除了这三种类型之外,云服务还可以细分为几个关键服务。现在让我们看看其中的一些。
识别关键服务
云服务有多种类型,但它们往往以“aaS”结尾,这是“as a Service”的缩写。当你自己搭建服务器,并且需要在其上运行数据库时,你需要安装数据库软件并对其进行配置。相比之下,数据库即服务(DBaaS),是指云服务提供商为你提供预配置的数据库服务。当然,为了满足你的需求,你仍需要进行一定程度的配置,但基本的数据库软件是你不必担心的。从你的角度来看,它只是你使用的一项服务,就像有线电视(嗯,还有人在用有线电视吗?!)
你可能会遇到的一些其他“aaS”服务——但肯定不是全部,因为似乎每周都会出现一个新的——包括以下内容:
-
基础设施即服务(IaaS):这是一种提供基本计算资源的服务,例如虚拟机(VM)、网络和存储。一个例子是 AWS 的弹性计算云(EC2)。这可以大致理解为有人为您设置服务器,然后您可以随心所欲地使用它。
-
平台即服务(PaaS):这项服务通过互联网提供硬件和软件工具。从概念上讲,它在 IaaS 之上,因为它通常包括操作系统以及安装在其上的软件。
-
软件即服务(SaaS):这项服务通过互联网以订阅方式提供软件应用程序。一个例子是微软的 Office 365,您可以通过互联网使用微软的所有 Office 应用程序。
-
容器即服务(CaaS):这为运行任何类型容器的运行环境提供支持。
-
灾难恢复即服务(DRaaS):这是一种可以用来备份您的重要数据并在必要时恢复它的服务。
正如我所说,还有许多其他的“aaS”服务,每个云服务提供商都将提供不同的选项。为了使问题复杂化,不同的提供商甚至不会总是对术语达成一致!例如,CaaS在不同的云服务提供商中可能并不总是指容器即服务。但话虽如此,这个列表中的服务通常相当一致,并且可能是您最常看到的服务。
不论名称如何,当您使用云服务时,您可能只使用一项服务,或者使用多项服务。例如,AWS 在撰写本文时,有 200 多项服务可供使用。但这也是云服务的另一个优势:您可以按需混合匹配服务,在过程中添加和删除。这就是汉堡王模式:您想怎么来就怎么来!
云计算的灵活性与其创新能力相结合,使其成为现代 IT 基础设施的基石,对于企业保持竞争力和敏捷性在快速发展的数字领域中至关重要。
但您可能根本不需要云服务。好吧,差不多吧。让我们来谈谈所谓的无服务器技术。
谁需要云服务甚至服务器?——无服务器
软件开发领域最近的一个发展是所谓的无服务器技术。这实际上仍然是一种云服务,但它与传统的云服务有很大的不同。一般来说,任何云服务都意味着有一些软件一直在运行。即使没有人使用它,它仍然在运行,尽管在非常低的水平上,因为云可以在这种情况下缩小操作规模以节省资源,并最终为您节省金钱。
但是,对于某些应用程序来说,您甚至不需要这些服务!
想象一下,如果你想编写一个可以解决数学方程式的应用程序。根据你到目前为止所学的内容,你可以想象创建一个网页,你可以输入一个数学方程式并将其提交给服务器端代码来解决方程式并返回结果。你可以进一步想象编写一些 Node 代码来完成这项工作。而且根据你到目前为止所学的内容,你甚至对代码的大致样子有一些了解,至少在高级层面上。最终,你将有一个 JS 函数,你的 Node 服务器会调用它来解决方程式。
但是,如果我说有一种方法可以在云中实现这个功能,并且像服务器一样调用它,你会怎么想?嗯,这正是无服务器架构的精髓!
从概念上讲,你可以将无服务器架构视为仅在请求到来时启动 Node 服务器,让它调用该函数,返回结果,然后立即关闭。但这一切发生得如此之快,以至于你甚至感觉不到——对你来说,服务器似乎一直在运行。
但是,更好的是,虽然代码并不复杂,正如你所看到的,如果你甚至不需要编写服务器代码,那不是更好吗?如果你只需要编写解决方程式的函数,那不是更好吗?
这就是无服务器架构的精髓!
AWS Lambda 就是这样一种无服务器服务。使用它,你可以编写函数,不仅限于 JS,还可以使用一系列其他语言。这些函数可以像在服务器上运行一样被调用。Lambda 负责所有的基础设施,换句话说,就是所有你需要自己编写的服务器代码。你只需专注于所需的逻辑,让 Lambda 处理其余部分。而且,最好的是,由于没有代码一直在运行等待请求,如果你的函数没有被调用,你就不需要支付任何费用。只有在它们被调用时,你才会根据它们使用的资源数量支付费用。所以,如果你编写的是快速、高效的代码,那么运行无服务器架构的成本会更低。
当然,无服务器架构并不是所有应用程序都适用的模型。但是,对于那些适用的情况,它可以非常有意义。
云是一个广泛的话题,你可能需要也可能不需要深入研究,这取决于你的需求。但我想讨论的最后一个话题几乎涵盖了我们之前讨论的所有内容,因为无论你是自己搭建服务器还是使用云服务提供商,你显然都需要做一些事情,比如将代码构建成可执行的形式并部署它,所有这些以及更多,都属于 DevOps 的范畴。
恢复控制权 - DevOps
信息技术(IT)的世界,其中网络开发只是其中一部分,是一个有趣的领域,因为技术和方法总是来来去去。还经常发生的事情是我们重复自己!有人有一个好主意;它变得流行,但最终被其他东西取代。然后,在某个时候,有人又有了一个好主意变得流行,但那些经历过上一个好主意的人通常意识到新的好主意实际上只是旧的好主意略作更新并穿上新衣服!
在早期,开发者做所有的事情。我们编写代码,然后我们就是那些将其部署到服务器上的人,这意味着将其安装到运行它的服务器上(无论是我们自己建造的服务器,还是由外部供应商提供的服务器,或者是云服务器)。这只是开发过程的一部分。然后我们担心诸如监控网站问题并处理这些问题之类的事情。
然而,过了一段时间,许多组织开始采取不同的方法,通过将一些职责分离出来并分配给除了开发者之外的其他团队,通常称为运维团队。在那个阶段,开发者仍然编写代码,但在部署的时候,另一个团队会负责。像监控这样的任务可能由一个完全不同的团队处理,并且处理出现的问题至少从特定的支持团队开始(尽管开发者最终仍然经常需要介入)。
现在,摆锤又开始向另一边摆动了,因为开发者通常在部署和支持以及运行一个实时网站的所有其他关注点中都有所参与,所以我们又重复了自己,尽管有一些不同。这次“新衣服”是指一种叫做 DevOps 的东西。
DevOps 是一套将软件开发(Dev)和 IT 运维(Ops)相结合的实践,旨在缩短开发周期并提供软件或网站的持续交付,同时仍然提供高质量的输出(通常意味着相对于更新速度,更少的错误或其他问题)。DevOps 因其强调软件开发人员和 IT 专业人员之间的协作、自动化和集成而著称。它通常涉及自动化和简化集成和部署流程的工具,旨在提高和加快交付。这种方法在敏捷软件开发背景下尤其相关,这是一种在许多阶段构建软件的方法,每个阶段都交付预期整体的一部分,同时允许在开发过程中进行更改。它可以显著提高软件生产和维护的效率和品质。
理解 DevOps 的关键方面
在高层次上,有几个关于 DevOps 的关键点:
-
文化转变:DevOps 强调心态的转变,鼓励开发和运维团队之间的协作。这打破了隔阂,营造了一种快速、频繁且更可靠地构建、测试和发布软件的文化。
-
自动化:DevOps 严重依赖自动化来加速软件开发和部署流程。这包括代码部署、测试、基础设施配置、监控等。任何可以自动化运行且几乎不需要人工干预的事情,DevOps 通常都会寻求去实现。
-
持续集成和持续交付 (CI/CD):这些是 DevOps 的核心实践。CI 指的是将来自多个贡献者的代码更改自动集成到单个软件项目中的过程。CD 是这一过程的扩展,其中软件可以随时发布到生产环境。基本思想是,当开发者将代码推送到 Git 时,自动化会看到新的代码,并构建软件或网站,将其部署到测试环境。通常,此时会运行自动测试,开发者会收到失败的警报,以便他们可以纠正问题。如果没有发现任何问题,并且如果一切配置得当,新的代码可以立即部署到生产环境,供最终用户立即使用。
-
监控和反馈:对应用程序及其性能的持续监控至关重要,尤其是在大量自动化和快速部署的情况下。它有助于早期发现问题,并根据用户反馈和系统级测试实时改进产品。例如,如果部署了新的代码,并且你设置了监控来提醒你错误开始发生,你可以在用户意识到问题之前采取行动。
-
工具和技术:一般来说,DevOps 以及特别是 CI/CD,都带来了一系列新的工具来使这一切成为可能。例如 Jenkins 和 GitLab 这样的名字出现,它们是运行所谓的 流水线 的平台。流水线实际上只是指一些知道如何自动构建、测试和部署代码的脚本。当然,容器在所有这些过程中都扮演着重要角色。例如,构建和部署代码的 CI/CD 流水线可能涉及流水线自动创建一个临时容器来进行构建。其他产品,如 Ansible 或 Puppet,也经常被使用。这些是用于管理环境配置的工具,通常是服务器。例如,CI/CD 流水线可能会执行一个 Ansible 任务,该任务负责确保生产服务器的配置满足应用程序的需求。进入 DevOps 需要学习许多新的工具和技术,而不仅仅是设计网站和编写代码所需的那些。
-
微服务架构:许多 DevOps 团队为了使管理代码更容易,会采用微服务架构。这是一种构建应用程序和网站的方式,将它们分解成许多更小的部分,每个部分都为更大的应用程序提供所需的服务。例如,你可能会创建一个在登录时验证用户的微服务。你也可能创建另一个处理注册新用户的微服务。而不是将这两个服务作为网站的一部分进行打包,你相反可以部署它们作为独立的程序,然后网站再使用这些程序。这允许你在不触及登录验证服务的情况下升级注册服务。这种架构非常适合 DevOps 方法,因为自动化这些较小的部分通常更容易。权衡的是,让所有这些部分协同工作有时可能具有挑战性,并且确实会使追踪问题变得更加困难。但是,好处往往超过了任何负面影响,包括你可以更容易地扩展服务以应对不断增长的需求,而不是尝试扩展一个单一的大型——单体,正如它们所知——应用程序。它还可以帮助团队更快地推出功能,因为添加新服务或更新单个服务而不触及其他代码的风险更小。
确定 DevOps 的好处(以及一些负面影响)
DevOps 有几个好处。
其中之一是增加部署频率的能力(因为一切都是自动化的,在大多数情况下会简单地比人工快得多)。实施 CI/CD 管道的团队通常会几天或几周内发布更改,而没有使用 DevOps 思维的团队可能需要几周或几个月才能发布。甚至有些公司每天部署代码,如果没有 DevOps,这几乎是不可能的——假设他们想要一个稳定的产品。
更快的上市时间(TTM)是另一个关键好处。由于一切自动化,团队可以更多地专注于编写代码,而不是担心代码最终如何部署和运行。
DevOps 通常也能降低新版本发布的失败率,至少在正确实施的情况下是这样的。这主要是因为测试通常被集成到 CI/CD 管道中,并且假设测试足够稳健,你可以相当有信心地保证新代码不会破坏任何东西,因此你可以有信心更快地发布。
通常,作为网络开发者的你可能会发现,DevOps 实践不仅使开发过程更加流畅,而且还能提高软件维护和部署的整体质量和响应速度。
当然,生活中很少有事情都是完美的!即使是最好的想法也往往有一些缺点,DevOps 也不例外。
当你精心开发的自动化突然失败时,可能会非常令人沮丧。也许它依赖于一个编译代码的服务,而这个服务可能因为某些原因而宕机。突然之间,你的管道失败,也许它们没有提供足够详细的错误信息,以至于无法立即看到问题所在。也许管理员升级了 Jenkins,新版本有一个导致你的管道在某些非常难以调试的方式中中断的 bug。或者,也许有很多团队同时运行管道,突然之间,你的管道需要 2 小时而不是 5 分钟。也许仅仅是因为难以编写管道脚本以完全满足你的需求,因为它有一些你在艰难中发现限制。
所有这些都是在我的工作中遇到的事情,这只是冰山一角。
如我之前提到的,微服务架构有很多好处,但出问题时进行故障排除绝对不是其中之一。当你有由几个不同团队维护的服务时,情况会变得更糟。现在,你不仅要弄清楚哪个服务出了问题,而且还要与那个团队接口,让他们纠正问题,而这并不总是像你希望的那样顺利。
但是,最终,大多数开发者倾向于认为 DevOps 的好处远远超过了任何负面影响。
现在你对 DevOps 已经有了一些了解,让我们来谈谈在 DevOps 环境中经常使用的一种技术:Python。
不仅仅是 HTML、CSS 和 JS——Python
Python是一种高级、解释型编程语言,以其可读性和简单性而闻名。由 Guido van Rossum 开发并于 1991 年首次发布,Python 已成为世界上最受欢迎的编程语言之一。其关键特性包括以下内容:
-
易于学习:Python 具有简单明了的语法,类似于英语语言,这使得大多数人可以快速掌握
-
动态类型:变量可以根据分配的值改变其类型
-
内存管理:自动内存管理和垃圾回收
-
广泛的标准库:包含用于各种任务的模块的大标准库
-
跨平台:在多个操作系统上运行,如 Windows、macOS 和 Linux
-
解释型:代码逐行执行,这使得调试更容易
-
多范式:支持面向对象、命令式和函数式编程风格
-
可扩展性:可以通过 C/C++扩展以用于性能关键任务
-
可嵌入性:可以嵌入到 C/C++程序中以提供脚本功能
-
社区:一个庞大且活跃的社区支持开发并提供众多模块和库
Python 被用于各种领域,如网站开发、数据科学、人工智能(AI)、机器学习(ML)、网络等。其框架如 Django 和 Flask 在网站开发中很受欢迎,而库如 NumPy、Pandas 和 TensorFlow 在科学计算和 AI 中被广泛使用。
它也是 DevOps 领域中的一种流行语言。你经常会发现用 Python 编写的各种脚本,然后从 CI/CD 管道或其他自动化工具中执行。它绝对不是 DevOps 的必需品,有时会使用其他语言和设施来代替它,但肯定在 DevOps 圈中是一个流行的选择。
让我们看看一些基本的 Python 示例代码,以便给你一些关于它是什么样的概念。
检查一些简单的 Python
Python 是一种广泛的语言,至少和 JS 一样复杂,而且可能更复杂,说实话。关于 Python 已经有整本书的论述,所以不可能在一章中教你关于 Python 的所有内容。但我至少想给你一些关于它的外观和功能的尝鲜。为此,我将向你展示一个承认是人为编造的例子,你可能会将其作为网站 CI/CD 管道的一部分来编写。我认为,鉴于你迄今为止对 JS 的了解,即使不深入研究其细节,你也会觉得 Python 很容易理解,这也是它变得如此受欢迎的原因之一。
因此,请查看 build_and_deploy.py 文件的内容:
import os
import shutil
import datetime
def make_index(source_path):
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
head = '<head><link rel="stylesheet" href="styles.css"></head>'
with open(os.path.join(source_path, 'index.html'), 'w') as file:
file.write(f"<html>{head}<body>{current_time}</html>")
print("index.html created")
def deploy(source_path, deploy_path):
if os.path.exists(deploy_path):
print("Deleting existing deployment")
shutil.rmtree(deploy_path)
shutil.copytree(source_path, deploy_path)
print("Website deployed")
# Execution begins here
source_path = "src"
deploy_path = "live"
make_index(source_path)
deploy(source_path, deploy_path)
首先,导入了一些库供我们的 Python 脚本使用:用于操作操作系统和文件系统的 os 和 shutil 库,以及 datetime 以便我们可以获取当前的日期和时间。
接下来,定义了两个名为 make_index() 和 deploy() 的函数(这就是 Python 中 def 关键字的意思)。make_index() 函数将一个名为 index.html 的文件写入 src 目录(写入的目录作为 source_path 传入),而 deploy() 负责创建我们的最终部署目录(在这个人为编造的例子中,我们假设它是一个网络服务器用来提供内容的地方)。
在 make_index() 函数内部,定义了一个名为 current_time 的变量,其值由 datetime.datetime.now() 函数返回,该函数由导入的 datetime 库提供。strftime() 函数允许我们指定我们想要的日期和时间的格式——在这种情况下,是一个形式为 YYYY-MM-DD HH:MM:SS 的字符串(例如,2023-12-10 18:10:53)。
接下来,定义了一个名为 head 的变量,它包含我们 HTML 文档 <head> 部分的 HTML。在其中,导入了 styles.css 文件,你会在 src 目录中找到它。其中的样式配置很简单:body { color : #ff0000; },使 <body> 部分中的所有文本都变为红色。
之后,将index.html文件写入,并将head的值插入其中,从而在我们的 HTML 文档中创建一个适当的<head>部分。因为写入文件的file.write()函数中传递的字符串是以f"开头的,所以我们可以在其中使用替换令牌,就像{head}一样。Python 会自动将head变量的值插入其中。另一个替换令牌{current_time}也做同样的事情,因此当页面加载时(好吧,当我们构建网站时的当前时间),我们会看到当前的日期和时间。
最后,当我们运行脚本时,print()语句会在控制台回显一个状态信息。
在deploy()函数内部,它首先检查网站是否已经通过检查deploy_path参数指定的live目录来确认是否已部署。如果是,则使用shutil.rmtree()函数来删除该目录,并通过print()函数显示相应的状态信息。然后,无论目录是否被删除,都会使用shutil.copytree()函数来创建部署目录。更具体地说,它会将src目录(由source_path参数指定)的内容复制到部署目录中。最后,再次使用print()函数显示状态信息。
当你运行此脚本时,执行实际上是从你看到的注释下面的部分开始的,该注释以井号符号开始。首先,定义源目录和部署目录为source_path和deploy_path变量。然后调用make_index(),这将导致index.html文件被写入到src目录中。最后,调用deploy(),这将确保我们有一个全新的名为live的部署目录,其中包含index.html和styles.css文件。现在我们实际上在live目录中拥有一个完整的网站——尽管非常简单——如果 Web 服务器指向该目录,网站将对访客可用。
最后,为了运行脚本,假设你已经安装了 Python,你只需在命令提示符下执行此命令:
py build_and_deploy.py
py命令应该在安装 Python 后可用。试一试!
如我之前提到的,Python 远不止这个简单示例所展示的。然而,它演示了许多关键概念,包括变量、函数、库、注释、条件和 Python 脚本的基本结构。而且这个示例展示了你可能在 CI/CD 管道中执行的内容。想象一下,如果每次你向 Git 推送新代码时,这个脚本都会自动执行,这是 CI/CD 管道通常执行的操作。
这样,你就可以将新代码自动部署到实时 Web 服务器上,供全世界的人查看。这就是 DevOps 和 Python 结合的力量!
检查路线图
哎,我们正迅速前进,揭示出无数的方块!这是更新的 Web 开发者路线图:

图 5.15:填入更多框的路线图
这次,你揭开了图形设计、DevOps、UX、Python、虚拟机和容器以及云基础设施这些框。这是一个很大的进展!让我们继续前进,不久我们就会全部揭示它们。
摘要
在本章中,我们涵盖了几个独特的领域。首先,你查看了一些前端关注点,包括图形设计、可用性和响应式设计,以了解为什么它们很重要以及它们如何在你进行网页开发的过程中为你带来好处。然后,我们转向服务器端,简要讨论了虚拟机和容器,以了解它们是如何用来运行你的代码的。接着,你对云进行了了解,并学习了它是什么。最后,我们简要讨论了 DevOps,以了解这种流行的开发方法如何增强我们交付高质量代码的能力。作为那次讨论的一部分,你了解了 Python,并对其进行了小小的尝试,以了解它提供了什么,特别是与 DevOps 相关的内容。
在下一章中,我们将回到前端,关注一些今天使用的一些最大的框架,看看它们如何为你提供开发上的优势,并允许你在减少努力的同时交付更健壮的网站。
第六章:探索现代前端开发
好吧,前两章在很大程度上专注于后端。当然,我们当然也触及了一些非后端的内容,但主要还是集中在后端。在本章中,我们将全力以赴地回到前端。
就前端而言,到目前为止我们走到了哪里?嗯,您已经很好地了解了 HTML、CSS 和 JavaScript,这些都是网络开发的基础。每个网络开发者都需要了解这些才能有效。
但如果我说,在大多数现代网络开发中,您可能不会以您迄今为止看到的形式使用它们,您会怎么想?实际上,在某些情况下,您可能几乎不会使用它们。这听起来是不是有点奇怪且矛盾?
然而,这在很大程度上是现实!
在本章中,我们将探讨一些建立在 HTML、CSS 和 JavaScript 之上,有时扩展它们的技术,最终达到一个您不再使用纯 HTML、CSS 和 JavaScript 工作的地方。这些包括如 React、Angular 和 Vue 这样的框架,以及 Bootstrap 和 Tailwind 这样的前端工具包,还有TypeScript(TS)。您还将看到它们在向客户交付内容方面具有许多优势。
到最后,您将很好地理解为什么这种远离纯 HTML、CSS 和 JavaScript 的趋势如此流行且有益。
因此,我们将在此处涵盖以下主要主题:
-
抽象化基础知识
-
面向现代前端框架
-
提升 CSS 功能
-
转译和数据处理
-
打包和部署——打包器
技术要求
对于本章,您需要从 GitHub 下载源代码包。由于我预计您已经安装了 Node(以及 npm),所以这就足够了!您所需的一切都将位于ch-06目录中,随时可用。
抽象化基础知识
很久以前,在一个遥远的互联网上,开发网站有点痛苦。您知道,长期以来,浏览器的工作方式并不完全相同。您可能会编写一些 HTML 代码,在 Netscape Navigator 中看起来很棒,但然后在 Internet Explorer 中看起来就不对了。或者,您可能会编写一些在 Internet Explorer 中运行完美的 JavaScript 代码,但然后在 Opera 中引发了错误。而 CSS 则需要各种技巧才能在浏览器之间实现大致相同的效果。
现在,我们倾向于编写符合标准的内容,这些标准是浏览器遵循的、关于 HTML、JavaScript 和 CSS 如何工作的明确指南。只要您编写的内容符合标准,并且浏览器正确地实现了这些标准,您编写的内容几乎总是可以在主要浏览器中保持一致。
但这并非一直如此,远非如此。
在标准成为主流之前,开发者认为使用纯 HTML、JavaScript 和 CSS 构建网站并不总是最佳体验。在那个阶段,他们开始在 HTML、JavaScript 和 CSS 之上编写抽象层。这些以各种 JavaScript 库的形式出现,实际上为你创建 HTML、JavaScript 和 CSS。它们这样做是智能的,意味着如果它们看到你在 Internet Explorer 中运行,它们知道如何为它输出正确的代码,但然后在 Netscape 浏览器中运行时可以输出正确的代码(如果你从未听说过 Netscape,请不要担心——它是一个在互联网早期非常流行的已停用的浏览器——也就是说,在 20 世纪 90 年代末和 21 世纪初)。
这些抽象的目标主要是三方面的:
-
其中之一是它们允许你编写更少的代码!如果你可以调用一个函数来生成一个带有按钮和一些文本的 UI,按钮可以展开和折叠,那么你就不必编写那么多的代码——远比你可能出错的自定义 HTML、CSS 和 JavaScript 要少得多。
-
基于前面的观点,编写更少的代码意味着你可以更快地交付结果。不必自己编写所有那些展开/折叠代码可以节省时间,客户通常都喜欢这一点。
-
第三大关键好处是共享代码往往更健壮。如果你使用别人写的代码,而且有很多人也在使用它,那么代码很可能非常可靠,没有错误,性能良好,并且通常没有问题。
在抽象领域,jQuery 曾经是最大的名字之一,而且在某些地方至今仍然是。可以争辩说,没有 jQuery,一个提供了一系列旨在跨所有支持浏览器工作的函数的 JavaScript 库,万维网今天可能不会成为现在这个样子。
例如,在纯 JavaScript 中,你可以使用以下代码获取 DOM 节点的引用:
const node = document.getElementById("mySpecialDiv");
如果你页面上有一个<div id="mySpecialDiv">元素,那么node变量将引用它,你可以以各种方式操作它。虽然这在今天是一个常见的、完全跨浏览器的功能,但并非总是如此。在某个时刻,你可能不得不为不同的浏览器编写不同的代码,而且有人必须确定在需要时执行哪种代码。
但是,随着 jQuery 的出现,你可以编写类似以下这样的代码:
const node = $("#mySpecialDiv");
$()函数——是的,$在 JavaScript 中是一个有效的函数名,尽管它看起来不像你之前见过的任何函数名,而且它只是一个单个的非字母数字字符——是由 jQuery 提供的(如果你将 jQuery 导入到你的页面中,那只是一个 JavaScript 文件,所以这很容易做到),并且它处理了浏览器可能存在的任何差异。有了 jQuery,那行代码在所有支持的浏览器中都能正常工作,你不必担心一个浏览器如何处理事情与另一个浏览器相比,因为 jQuery 为你处理了所有这些。
jQuery 是一个库,而且是一个小库。但在这个语境下,“库”这个词是什么意思?与通常在软件开发中(当然包括 Web 开发)广泛讨论的框架和工具包相比,它意味着什么?我们可以这样分解:
-
库是一组可以写成任何语言的函数和可调用例程。使用库时,你掌握着应用程序的流程和结构,你只在需要时使用库。你的代码总是调用库。
-
另一方面,框架为你的应用程序提供整体结构。你必须将你的代码放入框架规定的结构中(根据框架作者的思维方式,这种结构的刚性程度有所不同)。框架的代码控制着流程,在各个点上根据各种事件调用你的代码。
-
工具包是一组针对特定领域的工具或组件。最常见的情况下,工具包指的是用户界面组件——比如按钮、网格、进度条以及其他你可能在用户界面中看到的元素。实际上,根据我的经验,你很少在其他语境下看到“工具包”这个词,除了在构建界面时。
这三者之间的区别最终主要取决于谁控制得更多。如果你的代码在调用其他代码,那么你的代码在控制,那么其他代码很可能是库或工具包。相反,如果其他代码大多数时候在调用你的代码,那么其他代码控制得更多,那么有很大可能是框架。
当然,这是一个边缘性的学术讨论:在开发网站时,我们通常不需要太担心某物是库、框架还是工具包。了解它们之间的区别是值得的,但最终,只要某个东西能帮助我们完成工作,那么它可以是它想成为的任何东西!
但是,话虽如此,也值得指出的是,这些术语之间有一些重叠,甚至有一定的可互换性。你很容易在网上找到关于X或Y是库还是框架(工具包通常更明显)的辩论。它们是什么并不那么重要,重要的是它们能为你做什么。
现在,让我们来认识一下目前最受欢迎的前端框架中的三个:React、Angular 和 Vue。
面对现代前端工具
今天,在 Web 开发中存在一些分歧。有一群 Web 开发者喜欢“纯 vanilla”方法,这意味着你使用 HTML、CSS 和 JavaScript,就像你之前看到的那样。另一方面,还有另一群人认为在更高层次的抽象中存在更好的答案。他们认为,由于它们可以做到像 jQuery 库那样平滑浏览器差异,或者像 MUI 工具包那样通过提供丰富的预制小部件来构建界面,因此库、框架和工具集有很多好处。两种方法都有优点和缺点,而且没有公认的正确答案。
然而,提出“纯 vanilla 团队”现在是较小的群体并不疯狂,而且普遍的观点是,在大多数情况下,使用现代库、框架和工具集是更好的选择。当然,即使你只使用这些工具,你仍然需要知道 HTML、CSS 和 JavaScript。这是基础知识。这些现代工具基于它们构建,并扩展它们,但很少完全取代它们(你可以找到一些这样的工具,但它们往往是少数)。
因此,你到目前为止所学的绝对不是徒劳的努力——这是必需的,以便能够理解像 React 这样的东西。
React 独占鳌头
可能看起来我给 React 的关注比其他任何东西都要多,这是故意的:React 在写作时,是相当广泛的前端工具中最受欢迎的。你很可能在大多数环境中都会使用它。它也是最具争议性的,而且在许多方面看起来与其他工具相似。虽然它们都不同,但它们有足够的共同哲学,我认为如果你理解 React,那么当你需要使用其他东西时,React 的知识将为你提供足够的基础,以便足够容易地学习其他工具。
面对 React
React 来自我们的朋友 Facebook(或者我们的敌人——似乎每个人都对 Facebook 有意见,而且往往非常极端,很少中立)。React 是一个专注于构建网站用户界面的库,其他方面则很少涉及。在开发圈子里,关于 React 是否是一个框架,有一些争议,但鉴于其创造者称其为库,我认为我们可以合理地采用这种说法!
React 的潜在目标是使你在任何给定时刻都能轻松地推理出界面的状态和结构。你屏幕上看到的是 UI 显示的底层数据——状态——的函数。它是通过组件实现的,组件本质上是无状态的界面部分。当你组合大量组件时,你最终得到的是整体界面,以及一个由数据支持的网站或 Web 应用程序。
React 使用一个称为虚拟 DOM(VDOM)的概念来有效地完成所有这些。当你用 React 编写代码时,你并不直接与 VDOM 交互,但它始终存在。React 在幕后使用它来使一切工作,那么它究竟是什么呢?
你之前已经接触过 DOM 了,这是浏览器从你的标记中创建的元素树,它既可以通过代码也可以通过事件进行操作。当发生这样的变化时,浏览器必须执行大量工作,这在性能上可能是密集和昂贵的。有些变化不会影响页面的流程或结构,因此成本较低。例如,改变现有文本的颜色就属于这一类。但是,像在页面上插入一个新的<div>元素这样的变化会改变流程——DOM 的结构——因此,浏览器在重新绘制屏幕之前必须重新计算很多内容。DOM 处理所有这些,根据变化类型和程度,可能会导致界面反应迟缓、缓慢,无法立即响应用户交互,这不是一个好现象。
为了解决这个问题,React 引入了 VDOM 的概念。这是一个概念上位于真实 DOM 之上但用 JavaScript 实现的二级 DOM。当 React 中发生需要更新屏幕的事件时,而不是立即直接操作真实 DOM,VDOM 将被更改。这允许 React 进行调解和管理变化,以便在最终在真实 DOM 上执行时更加高效。
React 使用所谓的 diffing 算法,这是一种比较 VDOM 和真实 DOM 并收集差异列表的代码。然后 React 能够做出明智的决定,关于对真实 DOM 需要做出的最小更改集,并且可以执行诸如将它们全部批量处理(这通常比逐个处理更有效)或推迟到没有其他事件发生,通常试图最小化对真实 DOM 需要发生的变化的数量。一般来说,如果一切做得正确,这种方法可以比直接操作真实 DOM 提供更好的性能。
一个简单的 React 示例
让我们直接进入一个 React 示例:

图 6.1:无论多么简陋——一个(非常)简单的 React 示例
这个例子展示了一个友好的问候和一个点击时增加计数的按钮。它很简单,但足以演示基础知识。我们需要创建三个文件——react.html、react.js 和 react-greeting.js——这些文件可以在下载包的 ch-06 目录中找到。
让我们从 react.html 开始(稍后也会处理其他两个文件):
<html>
<head>
<title>Simple React Example</title>
<script src="img/react.development.js"></script>
<script src="img/react-dom.development.js"></script>
<script src="img/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="img/react-greeting.js"></script>
<script type="text/babel" src="img/react.js"></script>
</body>
</html>
要运行这个,你需要将其放在一个 web 服务器上;否则,你可能会遇到与 react.development.js 脚本相关的问题,如果 unpkg.com 域不允许来自除 unpkg.com 以外的域的请求,这可能会成为一个问题。
现在,实际上在这个案例中,这并不是一个问题,因为 unpkg.com 是浏览器中所谓的 react.html,也就是说,它不会允许来自 file:// 方案的 JavaScript 导入。当一个网站托管在 web 服务器上时,使用的方案是 http:// 或 https://,正如我们之前讨论过的。但是当你只是从本地文件系统中加载一个文件时,浏览器会使用 file:// 方案,而今天的大多数浏览器都会有 CORS 规则,如果内容不是来自本地文件系统,则不允许将其导入到页面中。
为了解决这个问题,我们需要将文件放在 web 服务器上,这样就会使用 http:// 方案,然后请求就会工作。你可以用很多种方法来做这件事,但其中最简单的一种是使用 http-server 包,这是一个 Node 包,它实现了一个简单的可执行程序形式的 web 服务器,你不需要编写任何代码。你只需运行它,它就会启动一个 web 服务器,使得你运行它的目录中的文件可以通过浏览器默认访问。
因此,为了设置环境,你需要创建一个 npm/Node 项目,这你已经知道如何做了:
npm init
你需要在 ch-06 目录中这样做(你会发现它已经被完成了;我只是带你走过我已经走过的步骤)并选择所有默认选项。然后,将 http-server 包添加为开发依赖项:
npm install --save-dev http-server
现在,进入 ch-06 目录并启动服务器,如下所示:
npx http-server
在这个阶段,你将能够访问 react.html 文件,如下所示:
http://localhost:8081/react.html
你应该在控制台中看到类似 图 6.2 中所示的内容:

图 6.2:http-server 已启动并运行,正在服务文件
现在你已经运行起来了,让我们来看看 HTML 文件,看看发生了什么。
首先,我们有三个已经被导入的 JavaScript 文件:
-
react.development.js是 React 本身,开发部分表示这是一个适合开发的版本。还有一个非开发版本,它适用于你真正部署网站时,区别在于版本将减小大小并在称为最小化的过程中混淆。这个过程不仅减小了大小,而且还将代码转换成更高效的形式,但同时也更难调试。一旦你的网站完全使用开发版本运行,那就没问题了,更重要的是要有一个更高效的版本在运行。 -
react-dom.development.js是 React 用来与浏览器本身交互的辅助库。两者都是使用 React 的必要条件。 -
babel.min.js是 Babel 的浏览器版本。这是一个我将在后面的转换和数据类型部分讨论的主题,但简而言之,它是一个可以将一种形式的代码转换为另一种形式的库。在这种情况下,它将把称为 JSX 的内容转换为普通的 JavaScript。
虽然这并不是我们这里主要关注的内容,但如果我不提一下你看到的这些 URL,那我就失职了。正如我之前提到的,这三个文件来自unpkg.com CDN。这是一个流行的网站,托管了各种库、框架和工具包。它托管了在 npmjs.org 上可以找到的所有内容,这也是 npm 获取你安装的所有内容的地方。你使用它的方式是指定一个通用的 URL 形式:
unpkg.com/<package>@<version>/<file>
这样,你可以获取任何你想要的任何版本,而不需要本地安装它。这不仅可能更快,因为像这样的 CDN 将在世界各地有多个服务器,并将请求路由到最近的那个服务器以提高速度,而且它还使得像这样的示例或真实项目能够快速轻松地启动运行,因为你不必担心自己获取依赖项。这只是工具箱中需要了解的另一个工具。
我们的第一个组件 – RootComponent
在完成三个导入后,它只是一个简单的 HTML <body>,其中包含一个单独的<div>。这是 React 创建我们的应用的地方,它将生成 HTML 并将其插入(以及 CSS 和 JavaScript)。但应用是在第二个文件中定义的,即react.js文件,它紧随<div>之后导入。没有它,我们只会得到一个空白页面!所以,让我们现在看看它:
const RootComponent = () => {
const [ count, setCount ] = React.useState(0);
return (
<div>
<GreetingComponent textColor="red" />
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<p>Count: {count}</p>
</div>
);
}
ReactDOM.render(<RootComponent />, document.getElementById("root"));
每个 React 应用都以一个单一的根组件开始。有时它可能只是唯一的组件,但这并不完全利用 React 的强大功能,即通过多个组件构建界面。
组件可以采取几种不同的形式,但在这里,我专注于新的形式,这在当今通常被认为更好:函数式组件。简而言之,你有一个使用箭头符号定义的 JavaScript 函数,其中变量引用 RootComponent。该变量成为你可以稍后使用的标签,正如你很快就会看到的。
任何 React 组件的职责是返回页面显示的 HTML。使用函数式组件,你只需从函数本身返回该标记。你如何返回标记完全取决于你,但最常见的形式如下所示:JSX。
JSX
在我们的根组件的函数内部,你返回组件的标记,可以是任何你想要的 HTML。但你可能正在看这段代码,觉得有些不对劲,你是对的:这不是有效的 JavaScript!
相反,这是一种名为 JSX 的另一种语言,它是 JavaScript 的扩展版本,其中你可以嵌入 HTML。这听起来可能有些奇怪,但它使得代码更加简单。没有 JSX,这里看到的内容需要几十行相对复杂的 JavaScript 代码来构建函数返回的 HTML。相反,有了 JSX,我们只需编写(一种形式的)HTML,之前导入的 Babel 将将其转换为调用 React 函数以生成此组件的 HTML、CSS 和 JavaScript 的 JavaScript 代码。
返回的标记相当简单。它只是一个包含 <GreetingComponent> 元素(无论是什么——我们很快就会了解到)、一个 <button> 元素和一个 <p> 元素的 <div> 元素。该 <button> 元素有一个 onClick 处理器,但那里的语法与之前看到的不同。在 JSX 中,HTML 标签属性的值必须用大括号括起来,而不是像之前看到的那样用引号。该值是一个 JavaScript 函数,当被调用时,会调用 setCount() 函数,并将 count 变量的值加一后传递给它。在 <p> 元素内部,有一些静态文本,然后是一个 JSX 表达式。与之前提到的插值字符串一样,JSX 允许你将 JavaScript 表达式的值插入到要返回的标记中。以下是 count 变量的值。
现在,我在这里跳过了一两个东西,所以让我们现在谈谈它们,从 return 语句之前的那个实现我们组件中所谓的状态的代码行开始。
组件状态
React 组件可以——但不必——有状态。这只是说它内部有数据的一种花哨说法。一般来说,状态不能从组件外部触摸。在组件中实现状态有许多许多选项,但关键的事情与 React 本身的名字相符。
React 这个名字来源于你用它构建的 UI 是 响应式 的,这意味着数据变化时它会改变,无论是由用户交互触发的还是不是。例如,由于这个应用程序显示了一个计数器和用于增加它的按钮,我们希望在用户按下按钮时屏幕上的计数器值被更新。在一个使用纯 JavaScript、HTML 和 CSS 的 Web 应用程序中,你可以想象当按钮被点击时执行的代码,该代码增加一个 JavaScript 变量,然后使用 DOM 方法更新屏幕。但是,使用 React,其中许多事情都是自动发生的,并且它基于状态的概念。
当组件的状态——其内部的数据——发生变化时,React 将销毁该组件并重新创建它,这意味着你编写的函数将再次执行以产生新的标记,该标记将使用新的状态数据。换句话说,Web 应用程序 响应 变化,无需你直接在代码中更新屏幕。
因此,React 必须知道组件的内部状态数据,因为它必须监控这些变化,以便知道何时销毁和重新创建组件。因此,我们通常不能(通常)只在那里使用普通的 JavaScript 变量,因为 React 不会知道它们。相反,我们必须以让 React 意识到它们的方式创建这些变量,这正是调用 React.useState() 所做的。
我们传递给它任何我们想要的 JavaScript 值——在这个例子中,只是一个数字——然后 React.useState() 返回一个值,这个值包含两件事:我们新状态变量的当前状态,以及一个用于更新它的函数。实际上,正是这个更新函数让 React 意识到变化:每次我们调用它时,React 就知道我们组件的状态已经改变,并且它可以决定组件是否需要被销毁并重新创建。
由于我们想要引用那个调用返回的两个东西,我们使用 JavaScript 的一种能力,即 const [ count, setCount ] 这部分是用于的。当执行时,它会导致创建两个变量,一个名为 count,另一个名为 setCount。count 变量获取我们在状态中设置的当前值,即 0,而第二个获取对 update 函数的引用。解构赋值是一种允许你将值从数组复制到数组或从对象复制到对象的特性。没有它,你需要编写的代码来从 React.useState(0); 返回的数组中获取值将如下所示:
const state = React.useState(0);
const count = state[0];
const setCount = state[1];
解构赋值只是编写代码的一种更简洁的方式。
有这些变量可用,我们现在可以在 <button> 元素上编写 onClick 处理器:调用 setCount() 函数,传入状态变量的新值,即 count+1。
React 将决定组件应该被销毁并重新创建,因此我们的函数将被再次调用,并返回新的标记。这次,注意在<p>内部是{count}表达式。这意味着我们的状态变量count的当前值将被插入,所以第一次点击按钮后,屏幕上会显示 1。重要的是要理解状态变量在组件销毁和重新创建之间保持不变。因此,当组件被重新创建时,调用React.useState()返回的不会是 0,尽管传递给它的值是 0,因为 React 已经知道这个状态变量,并在第一次调用React.useState()时已经用默认值创建了它。所以,它会返回当前值,而不是默认值。它不会重新创建组件的状态——换句话说,无论组件被销毁和重新创建多少次,它都会被保留。
这里代码的最后一行——调用ReactDOM.render()——是创建我们界面的关键。这类代码通常被称为引导加载,意味着这是工作开始的地方,或者更通俗地说,是一切启动的地方。我们传递给它的是将成为我们应用根组件的 React 组件,然后是它应该插入的 DOM 节点的引用。回顾一下 HTML,你会认出具有 ID 为 root 的<div>元素。无论这个根组件返回什么标记,都会插入到这个<div>中。
那么,关于<GreetingComponent>是怎么回事呢?这并不是一个真正的 HTML 标签!好吧,它可能是——毕竟,你不知道所有的 HTML 标签——但它不是,而且它与它有关的是,它是我创建的另一个 React 组件,它负责显示问候语。
添加另一个组件——GreetingComponent
你会注意到GreetingComponent是在react.js之前导入到react.html中的。这很重要,因为它必须在react.js中引用它之前导入;否则,当在react.js中引用它时,我们会得到一个关于它未定义的 JavaScript 错误。
那么,那个组件的代码是什么样的呢?它相当简单,位于react-greeting.js文件中,这是三个导入到index.html文件中的第三个:
const GreetingComponent = ({textColor}) => {
return (
<div>
<p style={{ color: textColor }}>I am a React app! There are many like it, but this one is mine!!</p>
</div>
);
}
这次,不需要状态,所以只是一个返回语句返回一些普通的 HTML。真正值得注意的是 style 属性值。通常,这可能是"color:red;"这样的东西,但由于我们的组件是用 JSX 编写的,所以这不会起作用。相反,我们需要使用花括号语法,然后列出要设置的样式属性,这仅仅是文本颜色设置为红色。
但我似乎忽略了一些东西,不是吗?那个textColor是什么意思?这涉及到 React 的另一个核心概念,即 props。
表扬
Props 是组件之间传递信息的基本机制。Props 总是从父组件到子组件工作,意味着在我们的例子中,根组件可以向GreetingComponent传递信息,因为它是根组件的子组件,但反之则不然。
Props 是您为自定义组件创建的标签上的属性,这就是为什么您可以编写<GreetingComponent textColor="red" />。由于GreetingComponent函数的签名中有一个带有textColor属性的 object,这足以让 React 知道这是一个有效的 prop,可以被传递给GreetingComponent。在函数内部,您可以像访问任何 JavaScript 变量一样访问textColor变量,因此我们可以将其插入到 style 属性中,如所示代码。这样,我们的父组件(RootComponent)就可以告诉GreetingComponent文本的颜色。
组件可以像您需要的那么简单或复杂,可以拥有您需要的属性数量(或完全没有),并且可以有状态(或没有)。但 React 以及几乎所有现代前端框架的整个要点是从许多小的组件中组合您的 UI。这使得测试它们变得更容易,并允许您使用可以多次重用的组件构建复杂的界面,随着项目的进展节省时间。
尽管 React 非常整洁,但有时它可能还不够,这就是所谓的元框架发挥作用的地方。
元框架
元框架是一种增强其他框架的框架,有时会结合多个框架(或库或工具包)。它建立在另一个框架或平台之上,提供更多功能,通常以自动代码生成的方式提供。
简而言之,元框架试图通过提供智能默认配置和功能,如读取纯文本文件并从中生成完整网站的能力等方式来节省您的时间。它们可以节省大量的时间和精力,主要的权衡是开发者控制度略有降低。
有许多元框架,但其中最受欢迎的是 Next,它是建立在 React 之上的。
您必须记住,React 是一个非常简单的库。它提供了组件必须遵循的基本结构,状态和 props,以及所有 React 应用和组件都会经历的良好定义的生命周期。然而,构建更复杂的 Web 应用通常需要更多的功能,所有这些都可以逐步添加到 React 中。
因此,您可以使用像 Next 这样的工具,它一次为您添加了许多功能,包括初始项目设置。Next 在 React 之上添加的一些关键功能如下:
-
服务器端渲染(SSR):Next 允许 React 组件在服务器上渲染,提高初始加载时间并优化搜索引擎排名(SEO,即尽可能在搜索引擎结果中排名靠前)。
-
静态站点生成(SSG):您可以在构建时而不是在运行时预渲染页面,这对于性能和 SEO 来说非常好。
-
基于文件的路由:Next 使用基于文件系统的路由机制。页面会根据指定目录中文件的名称自动进行路由。这为您的网站自动提供了一种常见的结构和导航模型。
-
API 路由:这允许您在 Next 应用程序中创建 API 端点。这对于使用统一代码库构建全栈应用程序非常有用。
-
零配置:Next 无需太多设置即可直接使用,但对于更复杂的需求,它也高度可配置。
-
自动代码拆分:Next 自动将代码拆分为可管理的块,仅加载当前页面所需的代码,这提高了性能。
-
内置 CSS 和 Sass 支持:Next 内置了对 CSS 和 Sass(CSS 的扩展形式)的支持,允许您直接将样式导入到组件中。
-
图像优化:Next 包含自动图像优化,以获得更好的性能。
Next 添加了所有这些功能,甚至更多,从而为您节省了手动添加所有这些功能的麻烦。因此,它是一个典型的元框架,利用了许多其他库和框架来提供这些能力。
现在,让我们看看 React 的一个竞争对手,另一个相对较小的库,它迅速获得了大量关注:Vue。
了解 Vue
Vue,由 Evan You 创建,并于 2014 年首次发布,是一个用于构建用户界面和单页应用的 JavaScript 库。它被设计为可增量采用,这意味着它可以轻松地集成到已经使用 jQuery 或其他库的项目中,或者当与现代化工具和支持库结合使用时,可以用于构建更复杂的应用程序。
Vue 的核心特性如下:
-
响应式和可组合的视图层:Vue 的核心关注点在于视图层——即用户界面。它通过简单的模板语法提供了一种将数据渲染到 DOM 的响应式方式,这意味着它就像 React 一样,知道何时在状态变化时重新绘制页面的一部分,并且您构建的组件使用基于简单字符串的标记生成方法。
-
组件化架构:Vue 鼓励使用组件来构建 UI。Vue 中的每个组件本质上都是一个具有数据、方法和生命周期事件的可重用实例。
-
指令:Vue 使用称为指令的特殊属性来为现有的 HTML 元素提供附加功能——例如,
v-if用于条件渲染和v-on用于处理事件。 -
过渡效果:Vue 提供了在 DOM 中插入、更新或删除元素时应用过渡动画效果的方法。
-
Vue CLI:这是一个用于脚手架(生成 Vue 应用程序的基本骨架)的命令行工具。它提供了一系列的构建设置以供开始,并且高度可配置。
-
Vue 路由器:Vue 的官方路由器。它与 Vue 核心深度集成,使得使用 Vue 构建单页应用(SPAs)和管理屏幕间的导航变得非常容易。
-
学习容易:Vue 的学习曲线通常被认为比其他库要平缓。其直观的语法和优秀的文档使其对初学者来说易于上手。
-
灵活性:Vue 的设计允许它根据项目需求变得简单或复杂。它可以被插入到现有项目中,或者从头开始构建整个项目。
-
性能:Vue 轻量级且提供良好的性能。它的小巧体积和高效的变更检测机制使其运行速度快。
-
社区支持:尽管比一些竞争对手年轻,但 Vue 拥有一个充满活力且不断增长的社区,提供了许多学习资源,以及广泛的第三方插件和组件。
Vue 非常适合那些刚开始接触现代 JavaScript 框架的人,以及构建复杂、大规模应用程序的开发者。它的简单性与强大功能相结合,使其成为初学者和经验丰富的开发者中流行的选择。
让我们简要看看 Vue 应用程序的实际运行情况,从 vue.html 开始:
<html>
<head>
<title>Simple Vue Example</title>
<script src="img/vue.js"></script>
</head>
<body>
<div id="app">
<example-component></example-component>
</div>
<script src="img/vue.js"></script>
</body>
</html>
这与之前提到的 React 示例看起来应该不会有太大的不同。类似于那个示例,我们有一个单独的 <div> 元素;Vue 构建的界面将在这里。然而,与 React 不同,只需要一个导入:vue.js 中的 Vue 本身。
但是,与 React 不同,我们必须在那里放置一些内容,即我们的根组件。我创建了一个名为 example-component 的自定义组件,因此我可以在那里放置 <example-component> 自定义标签。
然后导入 vueApp.js 文件,其中包含该自定义组件:
Vue.component("example-component", {
data: function () {
return {
message: "I am a Vue app! There are many like it, but this one is mine!!",
count: 0,
color: "red"
}
},
template: `
<div>
<p :style="{color}">{{message}}</p>
<button v-on:click="count++">Increment</button>
<p>Count: {{count}}</p>
</div>`
});
new Vue({ el: "#app" });
使用 Vue,没有 JSX(React 也可以不使用 JSX – 只是这样做更简单),我们调用 Vue.component() 函数来定义一个自定义组件。我们将其名称作为第一个参数传递,然后传递一个定义组件的 JavaScript 对象。
我们可以通过定义一个返回具有我们想要的任何状态数据的 data 属性(作为字段)的函数来拥有内部状态数据,就像在 React 中一样。Vue 会像 React 一样监控这个状态,以便在状态变化时根据需要更新界面,使一切变得响应式。
但与返回标记的 React 函数不同,Vue 组件必须提供一个定义该组件生成的标记的 template 字符串。在这里,我使用了 JavaScript 字符串插值,以便 template 字符串可以跨越多行,这样我就可以从状态数据对象中插入元素。
输出与 React 示例非常相似,如图 图 6**.3 所示:

图 6.3:经过几次按钮点击后的 Vue 示例
然而,这次,消息的颜色是在状态中直接定义的,而不是传递给子组件。:style属性语法,特别是那个开头的分号,对于 Vue 能够识别我们想要在生成的 HTML 中输出color值是必要的。没有它,你会发现生成的 HTML 中没有样式属性,文本不会变成红色。然后,{{message}}标记的作用是将消息文本插入到返回的字符串中。
<button>元素使用 Vue 指令v-on来定义点击处理器。与 React 不同,我们可以直接更改——或者像计算机科学界喜欢说的那样,变异——count值,因为 Vue 智能地将其挂钩,所以知道在看到这段代码时该做什么。请注意,这比 React 的等效版本要短。当前count的值通过{{count}}插入,给我们带来了与 React 版本大致相同的结果。
此外,与 React 一样,我们必须引导我们的应用程序,但在这里也稍微简单一些。我们创建一个新的Vue对象,将其传递给现有 HTML 元素的 ID,以便将根组件插入其中(el代表元素,#app类似于 CSS 选择器,用于找到 ID 为app的<div>元素),就这样!Vue 会为我们处理其余的事情。
因此,你已经看到了纯 HTML/CSS/JSS,你已经看到了 React,现在你也看到了 Vue。当然,这并不是你的唯一选择。还有元框架,但还有其他框架可以作为你的前端开发一站式商店,其中最大的可能是 Angular。
遇见 Angular
Angular是一个免费的、开源的 Web 框架,用于构建单页 Web 应用程序。它由 Google 开发并维护,此外还有更大的个人和公司社区。Angular 的第一个版本,即 Angular 2.0,于 2016 年 9 月 14 日发布。它是之前名为 AngularJS 的框架的完全重写。虽然它们有相似的名字,但它们相当不同,被视为完全不同的两件事(即使没有其他原因,因为使用 AngularJS 编写的应用程序很少能与 Angular 一起工作)。
Angular 自创建以来经历了许多快速更新,其中一些比其他一些更引人注目。例如,Angular 4 于 2017 年 3 月 23 日发布,Angular 5 随后于 2017 年 11 月 1 日发布,后者带来了对渐进式 Web 应用的支持等改进。Angular 6 于 2018 年 5 月 4 日发布,专注于工具链(你用来构建 Angular 应用程序的命令行工具,这是它所做的大事之一,有些人会说这是它的吸引力所在),以及未来开发的简便性。后续版本继续定期添加功能和改进。
Angular 以几个关键特性而闻名。一个是双向数据绑定,这是一种将屏幕上的 UI 组件与数据绑定在一起的方式。本质上,UI 组件中的更改会导致数据发生变化,反之亦然,自动进行,这导致开发者大多数情况下需要编写的代码更少,同时保持响应性。依赖注入是另一个关键能力。这允许框架创建代码中的对象,并将其提供给应用代码,而不是让应用代码自己创建它们。
与 React 和 Vue 类似,Angular 采用基于组件的架构,允许使用可重用组件和高效的开发实践。然而,Angular 的结构往往更复杂。虽然 React 组件通常是 HTML、JavaScript 和 CSS 的组合,但 Angular 应用通常会将其拆分为单独的文件,因此一个组件将由多个源文件组成,而不是(通常)像 React 或 Vue 那样只有一个。
Angular 由于其强大的特性和可扩展性,通常用于大型“企业”级 Web 应用。Angular 几乎为开发者提供了构建此类应用所需的一切,都在一个屋檐下。这与像 React 或 Vue 这样的轻量级库形成对比,在这些库中,你通常需要根据需要添加其他库。例如,让你的客户端代码与你的服务器端代码通信通常意味着需要添加一个库,如 Axios,这是一个开源的通信库,到你的 React 或 Vue 应用中。相比之下,Angular 提供了这种功能(你不必必须使用它,但 Angular 的一个主要吸引力就是只有一个依赖项需要处理,因此大多数 Angular 开发者都会倾向于使用它)。
那么,Angular 与 React 和 Vue 有何不同?让我们看看一些关键亮点:
-
核心哲学:Angular 是一个完整的框架,对应用的结构有强烈的看法,并且如前所述,理论上在一个地方提供开发者可能需要的所有东西。React 和 Vue 是专注于 UI 组件的库,这让你有更多的自由来使用其他库来处理应用的不同方面。
-
语言:Angular 使用 TypeScript(TS),这是我们稍后将要讨论的语言,但简而言之,它是 JavaScript 的扩展版本,增加了一些关键特性,主要是强类型。虽然你也可以用 TypeScript 开发 React 和 Vue 应用,很多人确实这样做,但 Angular 要求使用它(好吧,至少在你超越非常基础的示例领域时)。
-
数据绑定:如前所述,Angular 具有双向数据绑定,这意味着 UI 中的更改会自动反映在应用状态中,反之亦然。React 和 Vue 使用单向数据流,其中 UI 通过状态更改进行更新,这使得它更可预测,但需要更多的样板代码来更新状态。
-
学习曲线:Angular 的全面性使得其学习曲线比 React 和 Vue 更陡峭,而 React 和 Vue 由于其简洁性和对 UI 的关注,开始时更为直接。
-
性能:虽然 Angular 在整体性能上表现良好,但由于 React 的虚拟 DOM 系统和 Vue 的简洁性,React 和 Vue 往往具有更好的性能。
无论你是在谈论 Angular、React 还是 Vue,三者各自都有其强大的地方,选择通常取决于你项目的具体需求和偏好。
让我们快速看一下我们的计数器示例的 Angular 版本。与 React 和 Vue 类似,我们从一个普通的 HTML 文件开始:
<html>
<head>
<title>Simple Angular Example</title>
<script src="img/angular.min.js"></script>
<script src="img/angular.js"></script>
</head>
<body ng-app="app">
<root-component></root-component>
</body>
</html>
到目前为止,这种基本形式应该开始看起来熟悉了!Angular 在最基本层面上,只需要导入一个 angular.min.js 文件。使用 Angular,应用将被渲染到的元素(换句话说,根组件将被渲染到的元素)通常是 <body> 元素本身。
在 <body> 标签上,你可以看到 Angular 与 React 和 Vue 不同之处,并且这种情况很多,那就是使用 ng-app 的例子。
你看,当一个使用 Angular 的页面加载时,Angular 代码会遍历 HTML 并查找这些指令,将它们(以及通常整个标签)转换为 Angular 需要的其他 HTML 标记。在这里,ng-app="app" 将 <body> 元素标识为包含 Angular 应用。Angular 包含许多用于执行许多任务的指令,其中一些包括创建重复的标记部分(NGFor)、根据某些逻辑条件添加或删除元素(NGIf),以及使用双向数据绑定将事物绑定在一起(NGModel)。
一旦我们为应用标记了 ng-app 指令的容器元素,我们就可以开始在其内部使用自定义组件,而 <root-component> 元素正是这样一个自定义根组件。与 React 和 Vue 类似,所有组件都将直接或间接地成为根组件的子组件。以下是该组件的代码,包含在导入的 angular.js 文件中:
var app = angular.module("app", []);
app.component("rootComponent", {
template: '<p style="color:{{textColor}}">{{message}}</p><button ng-click="count=count+1">Increment</button><p>Count: {{count }}</p>',
controller: function($scope) {
$scope.message = "I am an Angular app! There are many like it, but this one is mine!!";
$scope.count = 0;
$scope.textColor = "red";
}
});
第一行创建了一个名为 app 的 Angular 模块。模块允许我们更逻辑地将代码组织成离散的单元。第一个参数只是模块的名称,通常为 app – 如此所示 – 代表应用本身的模块。第二个参数,一个空数组,表示此模块不依赖于其他模块,它们通常会有,但在这个简单的例子中不会。
接下来,定义了一个名为 rootComponent 的组件。您在这里可以看到的方法可能是最简单的——即使用传递给 app.component() 的对象的 template 属性作为第二个参数。这个值定义了组件的 HTML 结构。它应该与之前提到的 Vue 示例看起来没有太大区别。再次强调,可以根据组件的状态将标记插入到标记中,Angular 也有这个概念。第一个 <p> 元素的 style 属性使用 Angular 的数据绑定来根据作用域中的 textColor 属性设置其颜色,这本质上与 React 中的状态相同。
controller 函数定义了组件的行为。$scope 参数是 Angular 依赖注入的一个例子,当组件创建时,框架会自动创建并注入到控制器中。controller 函数在 $scope 上设置了三个属性:message、count 和 textColor。这些属性随后在组件渲染时用于组件的模板中。
在 Angular 应用中使用此组件时,您必须使用自定义的 <root-component> HTML 元素。请注意,Angular 会自动以这种方式更改所有组件的名称,以便您最终拥有一个一致的命名模式来命名您的自定义组件。当 Angular 运行时,它将用指定的 template 内容替换此元素,控制器将管理组件的行为,例如当用户点击按钮时,将触发组件的重新渲染,并更新 count 属性的 scope 值。
这是一个 Angular 构建 UI 的声明式方法的简单示例,其中您定义您想要的内容,然后让框架处理 DOM 操作和数据绑定。这次,我将节省一些纸张,不会在页面上放置截图,因为它看起来与 React 和 Vue 示例没有太大区别。但这是故意的:这个练习是为了向您展示以三种方式完成的相同基本示例,以便您可以进行比较和对比。
我认为重申这一点很重要,那就是这几乎是 Angular 可以达到的最简单程度。一个功能更全面的 Angular 应用在许多方面将明显更复杂,并且将使用 TS 编写,我们将在本章后面讨论。到目前为止,我们已经讨论了 JavaScript 库和框架,所以让我们转换一下话题,谈谈如何以类似的方式增强 CSS,以提供更强大的功能集。
提升 CSS 功能
虽然 React 和 Angular 等库和框架如今通常是主流,并且通常涵盖了 HTML、CSS 和 JavaScript 的各个方面,但仍然有方法可以增强这些技术,例如使用 jQuery 来增强 JavaScript,同时完全保留其他两个不变。CSS 也可以通过使用 CSS 库来增强,这些库可能只是一个导入的样式表文件,也可能是一个结合 CSS 和 JavaScript 的工作组合,根据你简单的配置动态生成 CSS。
与 Angular 等框架结合使用这样的 CSS 库并非不可能,甚至也不一定不寻常,尽管随着这些框架功能的增强,这种情况变得越来越少见。但无论你是单独使用它们还是与其他东西结合使用,让我们来看看其中最受欢迎的两个:Bootstrap 和 Tailwind。
检查 Bootstrap
Bootstrap 被宣传为一个前端工具包,用于开发响应式和以移动设备为先的网页。它提供了一系列 HTML、CSS 和 JavaScript 工具,用于创建网站和 Web 应用。Bootstrap 包含预设计的组件,如按钮、表单、导航栏、模态框等,这些组件可以轻松定制。使用 Bootstrap 很简单,如下例所示:
<html>
<head>
<title>A simple Bootstrap example</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<script src="img/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container">
<h2>My First Bootstrap Example</h2>
<button type="button" class="btn btn-info" data-bs-toggle="collapse" data-bs-target="#text">Show Text</button>
<div id="text" class="collapse">
It was the dawn of the third age of mankind, ten years after the Earth/Minbari war. The Babylon Project was a
dream given form. Its goal: to prevent another war by creating a place where humans and aliens could work out
their differences peacefully. It's a port of call, home away from home for diplomats, hustlers, entrepreneurs,
and wanderers. Humans and aliens wrapped in two million, five hundred thousand tons of spinning metal, all alone
in the night. It can be a dangerous place, but it's our last best hope for peace. This is the story of the last
of the Babylon stations. The year is 2258\. The name of the place is Babylon 5.
</div>
</div>
</body>
</html>
只需导入一个 Bootstrap CSS 文件,可选的(尽管通常是)还可以导入一个 JavaScript 文件。
一旦完成,你用 Bootstrap 做的大部分事情就是应用样式类,例如 container,表示你想要一个根据屏幕可用宽度调整大小的容器,正如第一个 <div> 元素所示。btn 类将一个普通的 <button> 元素转换成 Bootstrap 控制的按钮,这意味着你可以进一步对其应用样式,例如 btn-info,这会给我们一个带有圆角的蓝色按钮,比典型的浏览器提供的按钮要好看一些。
这使得 Bootstrap 能够识别这个按钮,然后我们可以通过使用 data-* 属性来附加行为。每次你在标签上看到 data-*,那都是其他东西可能依赖的定制数据。你可以随时添加这样的 data-* 元素,并且它们对你的标记的显示没有影响,至少不是直接的影响。浏览器简单地忽略它们,因为它们是为了你,而不是为了它。
但是当 Bootstrap 在元素上看到这些属性时,它知道你想要对它做些什么。在这种情况下,data-bs-toggle 告诉它你想要按钮展开和折叠内容部分,即这里具有 text ID 的部分,然后在 data-bs-target 属性中命名。跟随按钮的 <div> 元素具有 text ID,并且还应用了 Bootstrap 的 collapse 样式类。结果是,我们无需自己编写任何 JavaScript 或 CSS,就可以点击按钮来展开和折叠 <div> 中的内容。
Bootstrap 不仅仅是一个 CSS 库。虽然它确实包含了许多可以直接使用的 CSS 样式,还提供了组件(如这个示例中看到的按钮和可折叠容器),以及用于布局的各种其他类型的容器,包括响应式设计的断点,甚至还有 JavaScript 辅助函数。它还支持扩展,你可以添加插件来扩展其功能,添加主题来全面改变其组件的外观和感觉。Bootstrap 还支持自定义,你可以覆盖大多数样式,并且可以轻松地改变结果的外观。
所有这些因素使得 Bootstrap 在很长时间内都备受青睐,并且它使用起来相当简单,同时提供了相当多的功能。但它的受欢迎程度正在逐渐下降,这在很大程度上是因为使用 Bootstrap 构建的网站都倾向于看起来很相似,因为 Bootstrap 提供的组件在很大程度上定义了网站的外观和感觉,而人们通常不喜欢这样。尽管 Tailwind 并非一个完全的替代品,但这个新出现的 Tailwind 已经迅速获得了开发者的关注。
检视 Tailwind
Tailwind 被宣传为一个以实用类为先的 CSS 框架,用于快速构建自定义设计。与其他 CSS 框架不同,如 Bootstrap,它提供了预定义的组件,Tailwind 只提供低级别的实用类,这使得开发者可以更轻松地构建完全自定义的设计,而无需离开 HTML。
Tailwind 比 Bootstrap 提供了更多的样式类,这其实也是它的一个特点。与 Bootstrap 不同,在 Tailwind 中,你不会直接在 <button> 元素上使用 btn 类,而是可能会写成这样:
<html>
<head>
<title>A simple Tailwind example</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-4">
<h2 class="text-2xl font-bold mb-4">My First Tailwind Example</h2>
<button id="toggleButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick="document.getElementById('text').classList.toggle('hidden')">
Show Text
</button>
<div id="text" class="mt-4 hidden">
It was the dawn of the third age of mankind, ten years after the Earth/Minbari war. The Babylon Project was a
dream given form. Its goal: to prevent another war by creating a place where humans and aliens could work out
their differences peacefully. It's a port of call, home away from home for diplomats, hustlers, entrepreneurs,
and wanderers. Humans and aliens wrapped in two million, five hundred thousand tons of spinning metal, all alone
in the night. It can be a dangerous place, but it's our last best hope for peace. This is the story of the last
of the Babylon stations. The year is 2258\. The name of the place is Babylon 5.
</div>
</div>
</body>
</html>
这个示例与 Bootstrap 的示例等效,但你可以看到一些关键的不同之处。首先,Tailwind 只需要我们导入一个样式表,而不是 JavaScript 文件,也没有 jQuery 依赖。通常来说,有更少的东西需要导入是件好事,但这里有一个代价:我们没有像 Bootstrap 那样的自动展开/折叠功能,我们必须自己编写一些 JavaScript 来实现这一点。这是在 <button> 元素上。响应 onClick 事件时,我们通过调用 document.getElementById() 获取文本内容的引用,它返回一个 DOM 节点。这个节点应用了一系列的类,通过调用 toggle() 方法并传递一个类的名称,根据当前状态添加或删除该类。
这里还有另一个需要注意的地方,这也是 Tailwind 的一个标志,那就是应用到 <button> 元素上的所有样式类(这同样适用于其他元素,但让我们专注于 <button>)。这些是 Tailwind 的实用样式类。与 Bootstrap 中的单个 btn 类相比,它包含了按钮的所有样式,你只需应用你想要的实用类:
-
bg-blue-500类为按钮提供了蓝色背景色 -
hover:bg-blue-700类定义了一个悬停伪样式,当鼠标悬停时使其变为不同的蓝色阴影 -
text-white类使文本变为白色,而font-bold使其加粗 -
py-2类在顶部和底部应用填充 -
px-4类在按钮中的文本左右两侧应用填充 -
rounded类使按钮具有圆角
正如你所见,你对按钮的外观有更多的精细控制。例如,你可以轻松地将文本设置为红色,或者改变填充量。使用 Bootstrap,你需要覆盖它提供的样式,但使用 Tailwind,你总是能得到你想要的结果。有更多的样式类需要处理——很多——但默认情况下,你拥有更多的控制和灵活性,这意味着使用 Tailwind,你的网站更有可能不会像其他使用 Tailwind 的网站那样看起来一样。
现在,让我们转向另一个主题,这个主题在很大程度上定义了大多数人认为的现代网络开发,那就是转译的概念,特别是使用一个名为 Babel 的工具。
转译和数据类型
JavaScript 在很多方面都是一门有趣的语言,它非常灵活,相对容易学习。然而,虽然对一些人来说很有趣,但对另一些人来说,他们会告诉你这是一场绝对的噩梦!对于后者,投诉主要归结为一件事:
let myVar = "abc";
myVar.toUpperCase();
myVar = 123;
myVar.toUpperCase();
这是一段完全有效的 JavaScript 代码,但也是会导致错误的代码。为什么?是什么原因呢?!
首先,myVar 变量被赋予了一个值为 abc 的字符串。然后,我们调用该字符串上的 toUpperCase() 方法,这是一个所有 JavaScript 字符串都有的方法,用于将其转换为大写。之后,myVar 的值被更改为 123,这是一个数字。错误发生在第二次调用 toUpperCase() 方法时,因为该方法在数字上不可用。
我们遇到的问题与编程中的一个概念有关,称为 数据类型。这是指一个变量持有某些数据,通常,我们需要在我们的代码中明确声明它的数据类型。语言通常需要知道这一点,因为并非所有操作都适用于所有类型的数据。如前所述,你不能将数字转换为大写,因为这没有像字符串那样有逻辑意义。因此,只要语言知道变量持有的数据类型,它就可以主动通知我们可能在数据类型方面犯的错误。
在这个例子中,问题在于 JavaScript 是一种被称为 动态类型 的语言(有时称为松散类型),这意味着变量的类型可以随意更改。这与 静态类型 语言(或强类型)相反,例如 Java,你必须编写如下代码:
String myVar = "abc";
myVar.toUpperCase();
myVar = 123;
myVar.toUpperCase();
现在,错误将出现在第三行。在 Java 中,当声明 myVar 时,我们必须为它指定一个类型——String——这样 Java 就知道 myVar 只能包含字符串。代码甚至无法到达对 toUpperCase() 的第二次调用,因为尝试将 myVar 设置为 123 将导致错误。
但关键点是:这个错误将在编译时发生。你看,JavaScript 不需要编译——也就是说,从源代码转换成计算机可以执行的形式。相反,源代码本身就会被执行。JavaScript 只有一个运行时,这意味着代码运行时。
与此相反,Java 需要编译,这是将源代码转换成另一种形式(在 Java 的情况下,这种形式被称为中间语言,或简称IL)以便计算机可以执行的时候。这对于本次讨论来说并不特别重要,但重要的是,当我们尝试编译代码时,第三行的错误会被捕获。换句话说,我们甚至无法尝试执行它,因为 Java 编译器甚至不会生成 IL。它会在编译过程中发现错误,并在它走到那一步之前告诉我们。这一切都会发生在你的开发周期中。
相比之下,你只有在真正运行 JavaScript 代码时才会发现错误。如果你很幸运,这意味着在开发过程中。但如果那段代码在开发过程中没有被执行呢?也许它在一个很少使用的函数中。现在,猜猜谁会发现错误?答案是 你的用户 当他们在你的实时网站上遇到那行代码时!
这个问题,主要是导致创建了一个名为 transpiler 的东西的原因。transpiler 就像编译器一样,它将代码从一种形式转换成另一种形式。但编译器将源代码转换成可执行形式,而 transpiler 则将一种源代码形式转换成另一种形式。
Transpilers 可以帮助解决的另一个大问题是运行时环境中的差异(在 JavaScript 的情况下通常意味着一个网络浏览器)。Transpilers 可以为你平滑这些差异。例如,假设 Google Chrome 支持字符串上的 toUpperCase() 方法,但也许你会发现 Firefox 不支持(别担心——它们都支持它!)。你当然可以编写一些像这样的代码:
if (running_in_chrome) {
myVar.toUpperCase();
} else {
myOwnFunctionToDoUpperCase(myVar);
}
你随后需要弄清楚如何确定你的代码是在 Chrome 还是 Firefox 中运行(给 running_in_chrome 变量一个 true 或 false 的值),因此你必须自己编写 myOwnFunctionToDoUpperCase() 的代码,它执行 toUpperCase() 的等效操作。在这种情况下,这并不是什么大问题,但你可以想象并不是所有情况都这么简单。这种技术被称为 polyfill:编写允许你在不支持现代浏览器功能的浏览器中使用现代浏览器功能的代码。
编译器本质上为你做这项工作。使用编译器,它会看到使用了toUpperCase()函数,认识到它不会在你指定的浏览器中工作,并自动添加必要的填充。这样,它将生成可以在所有浏览器上工作的 JavaScript。是的,它是在将 JavaScript 转换为 JavaScript,但输出侧是修改过的 JavaScript。
然而,现在让我们谈谈最著名的编译器之一,尤其是在 Web 开发领域:Babel。
介绍 Babel
Babel 是一个 JavaScript 编译器,它允许你今天使用下一代 JavaScript。它将新的 JavaScript 代码形式转换为与旧浏览器或环境(如 Node)向后兼容的版本。关键特性包括转换语法(例如,将() => { }形式的函数转换为原始的function xxx() { }形式),填充目标环境中缺失的功能,以及源代码转换(通常是应用特定的编码标准,使所有源代码看起来相同)。Babel 在 Web 开发中被广泛使用,以确保广泛的兼容性,同时利用最新的 JavaScript 特性,同时保持对旧平台的支持。
关于 Babel 的一些关键点如下:
-
插件和预设:Babel 的功能可以通过插件进行扩展。例如,JSX 可以通过插件来支持。预设本质上是将许多不同的配置选项组合成一个单一选项。例如,
@babel/preset-env预设将提供对现代浏览器的支持,而无需配置每个可用的选项。 -
配置:Babel 可以通过创建
.babelrc文件或在一个package.json文件中添加 babel 部分来进行配置。此配置指定了要使用的预设和插件,以及其他可用的选项。 -
与构建工具的集成:Babel 被 Webpack、Rollup 和 Vite 等打包器所使用。Babel 提供了这些打包器提供的许多功能(当然,不是全部,当然)。
-
社区和生态系统:Babel 有一个庞大的社区和生态系统,提供了许多第三方插件和工具。这使得它成为一个多才多艺的工具,可以适应许多不同的项目需求。
Babel 在确保开发者可以在不牺牲兼容性的情况下使用最新的 JavaScript 特性方面发挥着至关重要的作用,使其成为现代 Web 开发的基石。
尽管 Babel 非常有用且功能强大,你可能已经注意到它并没有解决类型问题。尽管 Babel 能为你做很多事情,JavaScript 仍然是弱类型语言。为了解决这个问题,我们需要看看另一种叫做 TS 的东西,以及它提供的编译器,它可以替代 Babel(尽管 Babel 也可以处理 TS!)。
介绍 TS
TypeScript,简称 TS,是微软的产品。它是一种编程语言,是 JavaScript 的超集,这意味着它为 JavaScript 添加了新功能,最重要的是静态类型。这有助于在开发早期捕获错误。你需要将其转换为纯 JavaScript,以便它可以在任何支持 JavaScript 的地方运行。由于其强大的工具和使代码更易于维护和理解的特性,TS 在大型项目中很受欢迎,但当然不仅仅适用于大型企业项目!
虽然 TypeScript 不仅仅是关于静态类型,但它还有其他好处:
-
接口和类:TypeScript 支持 面向对象编程(OOP)概念,如接口和类,这使得结构化复杂系统并强制代码不同部分之间的契约变得更加容易。这是一个更高级的话题,本书中不会涉及,但这是 TypeScript 支持的重要特性(尽管 JavaScript 本地支持类,但 TypeScript 在这方面增强了很多)。
-
高级类型:TypeScript 包含高级类型,如枚举、元组和泛型,这使得代码更加精确和灵活(这些也是本书中未涉及的高级话题)。
-
类型推断:TypeScript 通常可以推断类型,减少了显式注释代码的需求。这意味着你不必 总是 指定类型,因为 TypeScript 通常可以自己确定正确的类型。
-
与 JavaScript 的兼容性:TypeScript 是 JavaScript 的超集,所以任何有效的 JavaScript 代码也是有效的 TypeScript 代码。如果你有一个现有的 JavaScript 代码库并希望将其转换为 TypeScript,这将使渐进式采用变得更加容易。你不需要一次性完成,这对于大型代码库来说非常有帮助。
-
工具:TypeScript 为大型项目提供了强大的工具,如自动完成、导航和重构,这得益于其在 IDE 和其他开发者工具中的高度支持。
现在,让我们看看 TypeScript 在实际中的应用。
一个简单的 TypeScript 示例
要开始使用 TypeScript,第一步是安装 TypeScript 编译器(实际上是一个转换器)。为此,首先在一个新的 npm 项目中使用 npm init,然后安装 TypeScript 本身:
npm install --save-dev typescript
如前所述,这已经在 ch-06/typescript 目录中完成。在同一个目录中,你会找到一个名为 example.ts 的文件。要编译它,执行以下命令:
npx tsc example.ts
tsc 命令是 TypeScript 编译器。它将生成一个 example.js 文件,将我们的 TypeScript 代码转换为纯 JavaScript。然后你可以使用 Node 运行那个 JavaScript 文件(你无法用 TypeScript 文件这样做——试试看吧!):
node example.js
现在,让我们看看 example.ts 中的代码。这是我们实际的 TypeScript 源代码:
class Person {
name: string;
age: number;
constructor(inName: string, inAge: number) {
this.name = inName;
this.age = inAge;
}
speak(wisdom: string) {
console.log(`Hello! My name is ${this.name} and I am ${this.age} years old. A bit of wisdom for you: ${wisdom}.`);
}
}
let p: Person = new Person("Xander", 36);
p.speak("Man has not evolved an inch from the primordial slime that spawned him");
我们首先做的是创建一个类。这是我在前面提到的 OOP 的一部分,简单来说,一个类是一个蓝图。它定义了一个我们可以创建实例的东西。在这里,我们有一个Person类。类通常在字段中持有某种数据,这里有两个:name和age。这也是你第一次看到一些数据类型的使用:字段名和分号后面的部分是那个字段的类型。所以,我们说name字段必须是string类型,age字段必须是number类型。
接下来,我们需要向类中添加一个函数。在这种情况下,它是一个名为constructor()的特殊函数。构造函数是一个类中的函数,每次我们创建该类的实例时都会执行(在大多数情况下,你可以有多个类的实例)。这个特定的函数接受两个参数,inName和inAge。在这里,同样有类型——被称为inName,如果我们编译这段代码,将会得到一个错误。在函数内部,name和age字段的值被设置为传递进来的值(分别是inName和inAge)。在字段名前写this意味着我们正在设置稍后创建的类的实例的值。
接下来,我们添加另一个函数,这个函数名为speak()。这个函数将输出一个简单的字符串,告诉我们这个人的名字、年龄以及我们稍后提供的智慧。这仅仅使用了基本的字符串插值来输出字段的值,再次使用this来引用。
现在,我们已经定义了Person类,但仅凭它本身并不能做什么。我们必须通过创建一个或多个该类的实例来利用这个类。这是类定义结束后的第一行代码。在这里,定义了一个名为p的变量,并给它应用了Person类型的类型注解。要创建类的实例,我们使用new Person()代码作为变量的值,并向它传递name和age值。这执行了constructor()函数,现在我们有一个通过p变量引用的Person对象,其中name字段值为“Xander”,age字段值为 36。
最后,我们想让 Xander 说话,所以我们调用p上的speak()方法,并通过console.log()(一部老电影《杰基尔与海德:再次在一起》中的机智对白——去看看,很有趣!)传递给他要展示的智慧。
现在,让我们看看编译那个 TS 代码的结果是什么,在创建的example.js文件中:
var Person = /** @class */ (function () {
function Person(inName, inAge) {
this.name = inName;
this.age = inAge;
}
Person.prototype.speak = function (wisdom) {
console.log("Hello! My name is ".concat(this.name, " and I am ").concat(this.age, " years old. A bit of wisdom for you: ").concat(wisdom, "."));
};
return Person;
}());
var p = new Person("Xander", 36);
p.speak("Man has not evolved an inch from the primordial slime that spawned him");
嗯,这看起来有点不同,不是吗?差异始于第一行。由于类可能不被运行此代码的浏览器(或 Node 版本)原生支持,tsc使用了一个叫做IIFE(立即调用的函数表达式)的技巧。这是一个巧妙的小技巧,你创建一个函数(一个匿名函数,因为它没有名字),然后立即调用它,同时将执行包裹在括号中。以下是这种技术在纯 JavaScript 中的基本形式,没有所有前面的代码来混淆问题:
(function() {
// Do something
}());
我们这样做的原因是,括号内部的所有内容都创建了我们所说的Person对象,然后返回它,这个返回值被分配给外部的Person变量。如果我们不返回它,那么它将无法被括号外的代码访问。结果是,在 JavaScript 中与在 TS 中定义类等效,但正如你所见,这需要更多的代码。
顺便说一下,example.js代码中的/** @class */行是给某些工具(其中一些将在下一章讨论)的一个提示,这些工具可以帮助它完成其工作。它们是标记——也就是说,提供一些元数据的标记。在这种情况下,它们在告诉它这是一个类。
在 IIFE 内部,你可以看到 speak()函数是如何被添加到Person函数中的。这是 JavaScript 中你可以做到的一件事,因为函数是对象。每个对象都有一个原型,从概念上讲,它与 OOP 中的类相同:它是一个蓝图,所有实例都由此而来,所以原型上有的任何东西,它的所有实例都会有。所以,通过Person.prototype.speak代码,我们是在说我们想要给Person的所有实例添加一个名为speak的东西,而我们想要speak的是我们分配给它的函数。
然后,在 speak()函数内部,你可以看到tsc是如何用concat()函数替换我们的字符串插值标记的,这是 JavaScript 提供的一个用于将字符串拼接在一起的函数。由于字符串插值是 JavaScript 的一个相对较新的特性,它可能还不会被某些浏览器或 Node 版本支持。tsc默认知道这一点,并通过使用concat()来填充它们。
注意,在这段代码中已经移除了所有的类型注解。这是因为它们在纯 JavaScript 中会是语法错误,而且无论如何,它们也不再需要了。记住,TS 的整个目的就是在编译时捕获数据类型错误,所以一旦代码编译(实际上是通过转译)成 JavaScript,它们就不再有任何作用了。
从这个相对简单的例子中,你可以对 TS 有一个感觉。你可以看到它只是 JavaScript 的一个扩展,包括构建类和用类型注解注解事物。
现在是时候看看我们为什么要这样做,以及当你有一个数据类型问题时会发生什么。所以,在代码的最后一行之后,添加这一行:
p = "abc";
然后,再次执行 npx tsc example.ts;你应该会看到一个错误信息,告诉你错误的具体位置。这就是 TypeScript 的一个重要特点:当你的网站上线时,这种错误不会发生,并且由于 tsc 给出的信息,关于错误的内容不会有任何神秘感。这是一个很大的好处,这也是为什么 TypeScript 迅速成为现代网站代码编写的事实标准。现在越来越少看到人们编写“裸”JavaScript。
TypeScript 是一个比这里展示的更健壮的语言,但要详细说明所有这些内容需要更多的页面。与这本书的主题一样,我并不是试图教你 TypeScript 的每一个细节——我只是介绍这个概念,并给你一些理解的基础。
既然你已经了解了转译器和 TypeScript,让我们看看一类可以同时利用两者的工具,即打包器。
打包并部署 - 打包器
曾经,网页开发很简单。你会创建一些 HTML、CSS 和 JavaScript 文件,将 CSS 和 JavaScript 文件导入到你的页面中,然后就可以开始了。然而,过了一段时间,事情变得复杂得多。这个 JavaScript 文件依赖于那个,也许你使用的某个库的部分内容甚至并不需要,那么为什么还要在你的页面上加载它呢?
当你的项目超出一个或两个源文件时,如果你想在确保网站性能最优的同时保持一定的理智,那么事情会迅速变得复杂。
管理所有这些复杂性正是打包器发挥作用的地方。
打包器的总体目的——它通常只是一个命令行界面工具——是简化并优化将你的开发代码转换为生产就绪包的过程。这通常涉及将多个 JavaScript 文件合并成一个单一的 JavaScript 文件,其中移除了所有不必要的代码。这也可以包括合并 CSS 文件、将图片转换为 JavaScript 结构、将 TypeScript 和 JSX 转译为 JavaScript 等更多操作。
以下是打包器对现代网页开发重要的几个关键原因:
-
处理依赖关系:现代网站和应用经常使用各种库、框架和工具包。打包器高效地管理这些依赖关系,确保你的应用需要的所有内容都包含在最终的构建工件中,并且没有任何不必要的冗余增加了最终产品的体积。
-
模块打包:JavaScript 已经发展到使用模块,这是一种更好地组织代码的方式,但浏览器对模块系统的支持程度各不相同。打包器可以将用不同格式编写的模块组合成一个文件,或者几个通用的文件。我们称这些文件为包,但最终,它们只是 JavaScript 文件,尽管在某些情况下它们可以包含除了 JavaScript 之外的内容——例如,图像和 CSS 文件可以转换成 JavaScript 的形式,这样在您的网站加载时,只需要请求一个文件,而不是多个如果这些是单独的文件的话。
-
性能优化:打包器会压缩和压缩您的代码,这减少了用户加载时间。这包括摇树(记住这个想法!),将代码分割成更小的块以实现更快的加载,以及优化资产加载,如图片和样式表。
-
开发效率:打包器通常带有诸如热重载等特性,这允许您在不进行完整页面重新加载的情况下实时查看更改。这加快了开发和调试速度。它们通常还包括一个内置的开发服务器,这可以节省您设置 Web 服务器(就像您之前看到的那样简单)。
-
转译支持:它们还与 Babel 等工具集成,允许您使用更多现代 JavaScript 特性,这些特性在所有浏览器中尚未得到支持,从而确保兼容性。
-
环境变量:打包器允许您根据您是在开发还是生产环境中使用不同的设置或配置。换句话说,只需在配置文件中设置一个变量,打包器就可以知道执行您只想在最终、准备投入实际使用的成品中完成的步骤。
打包器通过构建一个依赖图来工作。这是一种说法,即它扫描您的项目中的所有源文件,并确定哪些代码依赖于其他代码。然后它使用一种称为摇树的技术来去除不需要的代码。这就像当你去砍圣诞树时,他们把树放在那些摇树的机器里,这样任何松散的树枝等都会掉下来。通过摇树,可以安全地移除未使用的代码。
此过程的输出是一个或多个高度优化的 JavaScript 文件,其中只包含您的网站工作所需的内容。这意味着文件将加载得更快,因为要传输的网络内容更少,浏览器可以更有效地使用它们,因为它要处理的代码更少。此外,CSS 甚至图像可以嵌入到这些 JavaScript 文件中,这意味着当您的 HTML 页面加载时,需要发出的请求更少,从而进一步提高了性能。
当今的打包器中,有一些响当当的大牌,包括 Webpack、Rollup 和 Vite。它们都有着相同的基本目标,并且工作方式相似,但它们之间也存在一些差异。
在这个阶段,Webpack 可以说是村里的长者,它是首批打包器之一(尽管不是第一个:这个荣誉属于 2011 年的 Browserify),而且在写作时无疑是成功和知名度最高的。你通过 JSON 文件来配置它,告诉它你的项目结构以及当你打包项目时你希望它执行的操作。它提供大量插件来扩展其功能。例如,HtmlWebpackPlugin 可以帮你省去编写 HTML 文件的麻烦,它会自动为你创建。ImageMinimizerWebpackPlugin 可以压缩图片,使它们体积更小,从而加快加载速度。大约有 30 个官方插件,以及可能由 Webpack 用户社区创建的另一打左右。它是一个功能齐全的打包器,几乎可以做任何打包器应该做的事情。
但由于 Vite 和 Rollup 等竞争对手的出现,Webpack 逐渐失去了青睐。这两个工具都试图在配置更简单、整体上更容易理解(Webpack 有时在心理上可能难以跟上)以及通常更快、更轻量级方面超越 Webpack。所有这些工具都提供了一个插件生态系统、基本的模块打包功能,以及在你编码时作为你的开发服务器的能力。它们都可以进行所谓的 代码拆分,即将你的代码分割成逻辑单元,你可以在需要时才加载,而不是一开始就加载所有内容(可能还不需要,甚至可能永远不需要)。
虽然这三个都服务于相同的基本目的,并且有很多共同之处,但它们也存在差异(并且显然我要在这里说的是:我在这里谈论的是一些宽泛的概括):
-
全面性:虽然 Vite 和 Rollup 提供了很多相同的功能,但 Webpack 由于其历史原因,通常会提供更多一些。Webpack 是一个功能齐全的打包器,除了打包之外,还处理广泛的任务(如资产管理和服务器端渲染)。
-
配置:Webpack 以高度可配置而闻名,但这可能很复杂,令人不知所措。Vite 和 Rollup 通常更简单,有时简单得多。它们通过提供智能默认值来实现这一点,这意味着你使用这些工具时,通常不需要自己编写太多的配置,与 Webpack 相比要少得多。
-
构建时间:与 Vite 和 Rollup 相比,Webpack 通常会有更慢的构建时间,尤其是项目规模越大时。
-
开发体验:Vite 和 Rollup 支持热模块替换(HMR),比 Webpack 做得更好,这意味着当你使用这些工具进行开发时,代码更改时你可以拥有更好的体验,因为重新加载东西将主要被自动处理。虽然 Webpack 也能做这些,但 Vite 和 Rollup 在这方面通常做得更好,尤其是在更现代的代码库中。
-
输出效率:Vite,尤其是 Rollup,在优化输出包方面通常比 Webpack 做得更好。
-
开销:Vite 和 Rollup 不像 Webpack 那样包含许多内置功能,这导致开销更小,但可能需要更多手动设置来完成复杂任务。它们在很大程度上是 80/20 的工具,这意味着它们可以非常容易地完成你需要的 80%的工作,但最后 20%可能相当困难(而 Webpack 则相对而言总是更困难)。
虽然打包器在现代网络开发中非常常见,但它们是一个稍微高级一点的话题,因为它们总是可选的。首先,你可能根本不需要它们提供的东西。其次,即使你需要,你也可以简单地编写诸如 shell 脚本、批处理文件或简单的 Python 或 Node 脚本,以更符合你需求的方式完成它们所做的工作。然而,它们确实提供了现代 Web 开发者需要的许多功能和能力,这也是为什么它们在近年来变得如此受欢迎的原因。
总结来说,打包器是一个好东西,因为它们处理了现代网络开发的复杂性,优化了性能,并提升了开发体验,使得构建复杂且高效的网站和 Web 应用变得更加容易。
检查路线图
现在方块的神秘感正在迅速消失,不是吗?!图 6.4显示了更新的 Web 开发者路线图:

图 6.4:路线图,填入了一些更多的盒子
这次,你已经揭开了框架、TypeScript和打包器的盒子。现在剩下不多了吧?哇哦!
总结
在本章中,我们查看了一些工具,从广义上讲,它们构成了我们所说的现代网络开发。这包括 React 和 Vue(以及 jQuery,尽管那个不再被认为是现代的),Angular 这样的框架,以及 Next 这样的元框架。我们还查看了一些 CSS 工具,特别是 Bootstrap 和 Tailwind。所有这些都被证明在尽可能少写代码以完成任务、提高交付速度和最终产品的健壮性方面提供了很多好处。
我们还讨论了 TS 和 Babel,你学习了数据类型在现代开发中的好处,但代价是需要进行转译。
最后,我们讨论了一些现代构建工具,例如打包器和打包器,以及它们如何帮助你部署所需的代码,并消除构建应用程序的许多繁琐工作。
在下一章中,我们将回到服务器端,并讨论一些除了 Node 之外你可能选择用于构建服务器端代码的其他技术。这包括 PHP、Python(稍微多一点)、Java 及其流行的 Spring Boot 框架,以及.NET。我们还将讨论一些在服务器端非常重要的通用主题,包括数据库和称为 REST API 的东西。
快速吃点小吃和喝点东西(酒精或非酒精完全取决于你!)然后直接回到这里,继续阅读下一章的精彩内容!
第七章:从前端到后端 - 更多关于后端工具和技术
在上一章中,我们主要关注前端。在本章中,我们将回到后端,并探讨一些其他的服务器端技术和概念。一个完整的网站或网络应用程序很少只有客户端 - 它几乎总是需要某种服务器来与之交互 - 因此,即使你最终决定专注于某一侧,了解这两者如何结合在一起对成为一名网络开发者也是有利的。
在本章中,我们将探讨可能是最重要的服务器端概念:数据。你将了解数据是如何存储和访问的,以及你将理解可以在数据上执行的基本操作。你将看到多种存储数据的方式,然后你将学习如何构建一个客户端可以使用来访问这些数据的设施。
当然,了解如何处理数据显然很重要,因为没有数据,一个网站可能不会有多大用处,但了解如何以一致的方式向客户端暴露数据(在这个上下文中,“客户端”通常意味着基于浏览器的应用程序)也很重要,因为它不仅允许你的应用程序或网站使用数据,还可能允许其他人以他们自己的方式使用数据,这是一项很棒的灵活性。
你还将了解一些构建服务器端代码的不同技术,除了你已看到的 Node 之外。这包括 Java、PHP、.NET,甚至一些 Python 选项。了解各种选项很重要,这样你就可以在构建服务器端代码时做出明智的决定(你可能会发现这个决定在很大程度上取决于个人偏好和先验知识,这通常是可以接受的)。
因此,在本章中,我们将涵盖以下主题:
-
抓住问题的核心 - 数据
-
使用 REST、Node 和 Express 构建网络 API
-
回到未来 - SSR
-
在服务器端使用 PHP
-
使用 Java 和 Spring Boot 进行构建
-
介绍.NET
-
使用 Python 网络应用程序框架
-
检查路线图
技术要求
如往常一样,你将想要这本书的代码下载包,以及ch-07目录中找到的代码。
但具体到这一章,你需要安装一个名为 Postman 的工具,稍后我们将用它来测试我们的后端代码。尽管在撰写本文时你可以不注册就下载这个工具,但你可能无论如何都会想要一个账户,因为它提供了扩展的功能。因此,请访问www.postman.com,注册并下载 Postman 桌面应用程序。该应用程序适用于你使用的任何操作系统,所以你不太可能遇到安装上的麻烦。
你还需要安装.NET SDK,你可以从这里获取:dotnet.microsoft.com。这是微软提供的一个平台,用于开发各种应用程序,包括 Windows 原生应用程序和 Web 应用程序。这同样是一个标准安装程序,所以你 shouldn't have too much trouble – just grab the latest version available. Once you install it, go to Command Prompt and execute the dotnet --version command to confirm it’s ready to go.
最后,你还需要安装最新的 Java JDK,你可以从这里获取:www.oracle.com/java/technologies/downloads。Java 是目前使用最广泛的编程语言和平台之一,我们稍后会对其进行探讨。版本在这里并不重要,所以只需获取最新的版本(截至本文写作时为 21)。安装完成后,转到命令提示符并确保java -–version返回正确的版本。请注意,如果你遇到 JDK 或.NET SDK 无法正常工作的问题,你可以在它们各自的网站上找到安装帮助。
抓住问题的关键——数据
大多数网站或 Web 应用程序都需要某种类型的数据才能执行它们设计的目的。这当然不是一个令人震惊的声明。如果没有可供显示拍卖的数据,eBay 将无法工作。没有(大量)数据,Google 将无法进行搜索。没有支持数据,CNN 将无法展示最新的新闻标题。
但这些数据是如何存储的,又是如何被使用的呢?
到目前为止,你所看到的一切基本上都是静态的,这意味着它不会改变。页面在你创建它时是什么样子,它就会一直是那个样子。哦,可能会有一些微小的动态交互——比如当你将鼠标悬停在其上时按钮会突出显示——但这并不是我提到的那些网站产生的动态内容。那只能通过数据来实现。
网站可以使用的存储数据的方式有很多。可能最简单的方式是将数据存储为服务器上的纯文本文件。这些文件可以被读取——例如,可能使用基于 Node 的代码——并且它们的内 容可以在请求时用来创建动态响应。
简而言之,这正是我们在这里讨论的:当客户端(通常是 Web 浏览器)请求时,在服务器上动态创建响应,并将它们返回给用户进行显示。
虽然纯文本文件可以工作,但更常见的方法是某种形式的数据库。数据库通常是一段运行在服务器上的软件,与你的 Web 服务器软件以及你的应用程序代码分开,用于存储数据并能够响应对数据的请求(或写入数据)。虽然在某些情况下,网络浏览器可以直接访问这样的数据库软件,但更常见的是有一些服务器端代码来这样做,然后使用这些数据来生成响应。这更常见的原因仅仅是安全性:你的代码可能受到对数据库滥用的保护,而直接给用户访问数据库的权限则很可能缺乏这种保护。
有几种不同类型的数据库,但对你这个初出茅庐的 Web 开发者来说,最相关的两种是关系型数据库和NoSQL 数据库。我们先来谈谈关系型数据库。
关系型数据库
关系型数据库在概念上看起来与电子表格非常相似——比如说,Microsoft Excel。在电子表格中,你有行和列。它们相交的地方是单元格,在单元格中,你通常会有一些数据。在 Excel 电子表格中,你也可以有多个工作表,通常用于存储不同类型的数据。图 7.1展示了这些概念:

图 7.1:一个 Excel 电子表格,展示了多个工作表以模拟数据库和表
如果你已经理解了电子表格的基本结构,那么要理解关系型数据库,你实际上只需要改变一些词汇!
Excel 中的整个电子表格在概念上就是我们所说的数据库,而电子表格中的每一张表在那个数据库中被称为表。在数据库软件中,如果你需要的话,通常可以拥有多个数据库。这可能会让人困惑,因此为了消除歧义,我们倾向于说数据库服务器与普通的数据库:
-
数据库服务器指的是运行在服务器上(如 Oracle MySQL、PostgreSQL 和 MS SQL Server 等)的软件,它包含数据库并提供与数据交互的基本功能
-
术语数据库——与上述的数据库服务器不同——指的是为特定目的而实际收集的表集合,可能每个服务器上托管一个网站,但所有这些都生活在数据库服务器软件中
在数据库表中,你定义有哪些列——你想要存储关于表中每一行数据的哪种类型的数据——然后你就有数据本身的那一行。如果你把每一行数据——我们通常称之为记录——看作是一种实体,比如说一个人或一个银行账户,那么列实际上描述了你想要存储的关于每个实体的信息。
例如,假设你想要创建一个数据库表来存储一些关于人的信息。我们将非常缺乏创意,所以我们将称这个表为People。也许对于每一行数据——它将是一个人实体——你想要存储他们的名字、姓氏和年龄。结果,你可能会得到一个看起来像图 7.2的数据库表:

图 7.2:People表的结构
注意
作为补充,你可能永远不会存储一个人的年龄,因为显然一年后那个年龄就不会正确了。相反,你可能会存储他们的出生日期,并在需要时从那个日期计算他们的年龄。但在这里,我尽量让这个例子尽可能简单。
对于每一列,你必须告诉数据库将存储什么类型的数据。每个数据库都会以不同的方式指定每列的数据类型,但它们都与你所知道的内容相关。在这里,你可以看到first_name和last_name是字符串(在这个特定的数据库中,它们的数据类型是TEXT),而age是一个数字(数据类型为INTEGER)。在这里需要更改的另一项术语是,我们通常将数据库中的一个单一数据元素称为字段,而不是像电子表格中的单元格那样。但它们仍然是相同的基本东西:它是行中对应给定列的值。
在定义了基本的表结构之后,我们可以在其中存储一些数据行;也许这些数据(是的,这些确实是系列开始时的实际年龄——我做了研究!):
FIRST NAME, LAST NAME, AGE
James, Kirk, 32
Leonard, McCoy, 38
Montgomery, Scott, 43
但我们实际上如何存储这些数据呢?这就是结构化查询语言的概念发挥作用的地方,这是我们接下来要讨论的。
介绍 SQL
结构化查询语言(SQL)是一种用于与关系数据库交互的特殊语言。它允许我们使用一种通常容易理解的形式来请求数据,试图让它看起来更像 JS 而不是编程语言。
举例来说,让我们直接用一个例子开始,假设你想要从我们的People表中获取所有三条记录。对应的 SQL 语句会是这样的:
SELECT * FROM People;
就这些了!你将得到一个记录列表,每个记录都包含每个人的名字、姓氏和年龄。这显示了最基本的 SQL 语句类型:SELECT语句。我们告诉它我们想要从每条记录中获取什么数据(在这种情况下,星号表示所有),然后我们必须说明从哪个表获取记录。
如果我们只想获取姓氏,我们会执行以下操作:
SELECT last_name FROM People;
如果我们只想获取姓氏和年龄,我们可以执行以下操作:
SELECT last_name, age FROM People;
如果我们只想获取超过 40 岁的人呢?这就是WHERE子句发挥作用的地方:
SELECT * FROM People WHERE age > 40;
现在,我们将只获取一条记录——蒙哥马利·斯科特,因为这是唯一一条age值大于40的记录。
在这里,SELECT 语句如此类许允许其他功能,包括对返回的数据进行排序、以各种方式过滤,以及许多可以用来操作返回数据的函数(例如,将名称转换为大写,也许)。但最终,获取数据的基本能力才是关键所在。
当然,检索数据很棒,但如果我们要添加一个人呢?这也可以通过 SQL 完成,使用一个 INSERT 语句:
INSERT INTO People (first_name, last_name, age) VALUES ('Pavel', 'Chekov', 21);
我们必须提供要插入数据的表名,然后列出要插入的字段,然后是每个字段的值。执行该语句后,表中将有四行数据。
关于更改现有数据呢?这也可以使用 SQL 的 UPDATE 语句来完成:
UPDATE People SET age=50 WHERE last_name='Kirk';
再次强调,我们必须指定我们更改数据的表,然后指定要更改的字段。
当然,如果我们想更新多个字段,也是可以的:
UPDATE People SET first_name='Jim', age=50 WHERE last_name='Kirk';
WHERE 子句在这里实际上是可选的,但如果不提供它,结果将是所有表中的记录都将被更新,因为没有限制它影响的记录。因此,在处理 SQL 时,确保你的数据更改语句只更改你打算更改的记录是很重要的。
你需要了解的 SQL 语句的最终形式是用于删除数据:
DELETE FROM People WHERE age < 30;
再次强调,你必须指定要操作的表,然后指定一些 WHERE 子句来确定要删除的记录。与 UPDATE 语句一样,WHERE 子句是可选的,所以除非你真的打算删除表中的所有记录,否则请务必包括正确的标准来匹配记录!
这些当然是可能的最基本的 SQL 语句——随着你进入更高级的使用案例,SQL 可以变得相当复杂——但如果你理解这四种类型语句背后的基本思想,那么你将很好地开始理解更高级的使用案例。
现在,其中一个更高级的使用案例是当表之间有直接关系时,这就是关系型数据库中“关系”一词的由来,所以让我们接下来谈谈这个。
在关系型数据库中引入关系
如你所知,数据库中可以有多个表。例如,除了 People 表之外,我们现在添加一个 Starships 表,如图 7.3 所示:

图 7.3:Starships 表的结构
目前,这两个表之间没有真正的关联,也没有任何东西将它们联系在一起。有时这没问题,但如果我们想知道每个人服务的飞船是哪一艘?我们可以通过修改 People 表来实现,如图 7.4 所示:

图 7.4:People 表的结构,现在包含 starship_id 列
现在,已经添加了starship_id列,并且假设每行数据中该字段中的值与Starships表中记录的id值之一相匹配,我们实际上已经连接了这两个表,现在至少可以间接地告诉每个人在哪个星球飞船上服役。换句话说,我们在这些表之间创建了一个关系,这使得现在成为一个真正的关系型数据库。
但这对我们的 SQL 查询有什么影响呢?嗯,它们的工作方式与之前相同,但现在我们有一些新的能力在我们的指尖,因为我们可以根据关系同时查询两个表中的数据:
SELECT p.first_name, p.last_name, p.age, s.starship_name FROM People p, Starships s WHERE s.id = p.starship_id;
现在,我们将为每个返回的记录获取四条数据:每个人的名字、姓氏和年龄,以及他们服役的星球飞船。但我们需要指定我们正在从两个表查询并将数据组合在一个结果集中,这个概念被称为p代表Person表,s代表Starships表。
然后,我们将这些名称用作查询中所有列名称的前缀(再次,以确保 SQL 确切知道我们想要哪些字段)。
最后,我们必须在这里提供一个WHERE子句。它表示对于People表中的每个记录(因为它是FROM子句中表列表中的第一个),在Starships表中找到一个与People表中的starship_id值匹配的id值的记录。并且由于我们在字段列表中返回s.starship_name,我们最终得到了来自People表每个人的数据,以及他们服役的星球飞船的名称。
这实际上是关系型数据库的主要点:能够根据它们之间的一些关系返回来自两个或更多表的数据组合。这里的关系是id字段,所以当它们匹配时,两个表的数据就组合在一起,并作为查询结果的一部分返回。连接是一个广泛的话题,可能会很快变得相当复杂(它涉及到集合理论的数学概念),所以我们在这里不会详细讨论。但基本概念始终相同:你的查询通过基于一个或多个匹配条件将多个表的数据组合起来,并将它们作为一个单一的数据记录列表返回。
注意
如果你想要更深入地了解各种连接类型以及如何编写它们,一个好的参考资料可以在这里找到:learnsql.com/blog/sql-joins-types-explained。
即使从这个简单的例子中,我相信你也能开始看到关系型数据的强大之处。你可以有一个学生列表和一个单独的班级列表,并且能够编写一个查询来获取每个学生参加的班级列表。或者,你可能有银行账户列表和每个账户的所有者列表分别在不同的表中,但在需要时能够将它们联系起来。
然而,为什么要这样做呢?关系型数据库的主要目标之一是不要重复数据(或者更精确地说,尽可能少地重复数据——这个过程被称为People表本身,在每一行数据上。这对我们的目的来说效果是一样的。但是,如果星际飞船的名字改变了,那么我们就需要相应地更新People表中的所有记录。如果我们有两组表并且像这样连接它们,那么你只需要在Starships表中更改名字——在一个地方,一个记录——实际上,在查询时,它对每个服务在该飞船上的人(即当时)都会自动更改。这是一种更逻辑地处理数据的方式,因为星际飞船的名字在逻辑上并没有与一个人绑定——它们实际上是独立的实体,而且在存储空间(在某些情况下,性能也有所提高,但这很大程度上取决于具体情况,实际上可能会有相反的效果)方面也更有效率。
在这个阶段,你可能想知道如果你正在处理的数据本身不是关系型的,你会怎么做。还有,如果你想要存储的数据形式不像你迄今为止所看到的表格那样具体,你会怎么做?嗯,对于这种情况,还有一种可能更适合的数据库类型——NoSQL 数据库。
NoSQL 数据库
简而言之,NoSQL 数据库实际上是指任何不使用 SQL 来操作数据的存储数据机制(我们通常称之为数据存储)。这包括以下类型的数据库:
-
"first name",该键的值是"Frank"。这几乎是 NoSQL 中最简单的类型了,它用于数据非常简单的情况。你可能听到的这种类型的常见名称包括 Redis 和 DynamoDB,这两个产品提供了键值数据库存储。 -
图数据库:这些数据库相对较新,专门用于具有(通常很多)实体之间定义良好的连接的数据类型。一个例子可能是一个社交网络:你是一个实体,你可能有很多朋友,他们也可能有很多朋友,以此类推。虽然这些类型的关系可以在关系型数据库中建模,但图数据库可能更合适,尤其是当你需要遍历图时,这意味着从一个实体开始,跟随其连接到某个远程实体。你可能听到的这种类型的常见名称包括 Neo4j 和 Amazon Neptune。
-
宽列存储:这种数据库类型主要用于需要分析特别大的数据集的情况。它们通过将数据列存储在行中来工作,这允许优化每个实体中许多字段上的复杂查询。这种类型的例子包括 Apache Cassandra 和 Google Bigtable。
还有一种称为文档数据库的第四种类型——我把它从列表中省略了,因为虽然所有前面的类型都是 NoSQL 数据库,但事实是,当有人说 NoSQL 数据库时,十有八九他们指的是文档数据库。
在文档数据库中,数据存储在对象中,通常是 JSON 对象,尽管不一定是。你已经见过几次 JSON 了:当你初始化一个项目时,npm生成的package.json文件。但存储在其中的数据格式是JSON,代表JavaScript 对象表示法。有趣的是,尽管它肯定是在 JS 的背景下发明的,但现在它经常被用于这个背景之外,因为它相对简单且易于处理。归根结底,它只是一个字符串,可以轻松解析并以各种方式使用。
其中一种方式是作为一个文档存储在文档数据库中,比如 MongoDB,它是文档数据库中当之无愧的最大品牌(CouchDB 也是一个流行的品牌)。例如,如果我们想在 MongoDB 中存储之前提到的People表中的数据,我们最终会得到四个独立的文档,可能看起来像这样:
{ first_name: "James", last_name: "Kirk", age: 32 }
{ first_name: "Leonard", last_name: "McCoy", age: 38 }
{ first_name: "Montgomery", last_name: "Scott", age: 43 }
与其作为表中的行不同,每一个都是独立的对象——一个文档——存储在集合中,这大致对应于关系型数据库表。
关系型数据库和文档数据库之间有一个很大的区别。假设我想存储一个昵称,但只针对莱纳德·麦科伊。在关系型数据库中,我需要在表中添加一个nickname列,并且每一行都需要有一个昵称(当然,对于没有昵称的人来说,它可以是空的)。然而,在文档数据库中,我可以简单地这样写麦科伊的文档:
{ first_name: "Leonard", last_name: "McCoy", age: 38, nickname: "Bones" }
其他文档没有nickname字段并不重要;这是完全有效的。你看,数据库的结构,包括表中的字段,被称为模式。由于表中的所有数据行都共享相同的列,因此表本质上定义了所有行的模式。
但在文档数据库中,定义模式的是文档本身。实际上,是你作为开发者(!),但重点是每个文档在某种程度上可以有自己的模式。
这有几个好处。其中一个好处是,使用文档数据库通常可以更快、更轻松地开发,因为与关系型数据库不同,你不必一开始就做对一切。在关系型数据库中,你必须考虑你想存储的数据,并且你必须在开始时构建具有适当列和数据类型的表(虽然你可以在表创建后更改表的结构,但这有时会带来限制,有时甚至不可能,即使可能,也需要时间和精力)。
另一个好处是,随着你的开发,当你意识到需要在新文档上使用稍微不同的模式时,你不必一定修改现有的数据。只要你的代码理解可能的不同,你就可以有效地处理文档的两个版本。
现在,一个常见的误解是,NoSQL 数据库,特别是文档数据库,没有关系数据。当然,可能 是这样,但使用文档数据库并不意味着你的数据 不能 有关系。可能公平地说,大多数情况下 它不会有关系,如果它确实有关系,那么可能更合适使用关系数据库。但一个不会否定另一个。
NoSQL 数据库确实提供了执行所有基本 CRUD 操作的机制,包括查询相关数据。这里我不会详细介绍这些,因为它们可以从数据库到数据库有很大的不同。然而,鉴于它是最受欢迎的 NoSQL/文档数据库,这里有一个来自 MongoDB 的例子,以便你有一个大致的了解:
db.users.find({ "age": 38 })
这在 SQL 中相当于以下操作:
select * from People where age=38;
就像 SQL 一样,随着文档的复杂度增加,这样的语句可能会变得相当复杂,如果它们之间存在关系,等等。但简而言之,这就是你如何处理它们,而且根据你的需求和服务器端基础设施,这些代码可以用多种语言编写。
所有这些数据库讨论最终导致最后一个一般性话题,但我要警告你,在阅读下一节之前,你可能需要花一点时间停止咯咯笑,因为接下来会有一些 CRUD!
基本 CRUD 操作
我知道,我知道——这听起来很脏,但我发誓这是一个真正的术语!首字母缩略词 CRUD 代表 创建(Create)、读取(Read)、更新(Update)和删除(Delete),它定义了你可以对数据进行的基本操作,无论是在关系数据库中还是不是。你当然可以执行以下操作:
-
创建数据,就像使用一个
insertSQL 语句一样 -
读取数据,就像使用一个
selectSQL 语句一样 -
更新数据,就像使用一个
updateSQL 语句一样 -
删除数据,就像使用一个
deleteSQL 语句一样
即使只是为了能够与其他开发者进行有意义的对话,了解这些术语也是很有价值的,即使你每次说这些术语时可能会自己笑出声!而且这些操作在代码中的实现方式并不重要;重要的是,从概念上讲,它们是你以任何形式处理数据的唯一方式。
但是,当然,这些操作如何实现 在其他方面 也是很重要的。这些 CRUD(哈哈)操作通常是构建所谓的 Web API 的基础,这些 API 是可以从远程系统调用的函数集合。当涉及到构建 Web API 时,今天最常见的方法是使用另一个首字母缩略词(这个领域充满了首字母缩略词,如果不是别的的话):REST。这就是我们接下来要调查的下一个东西。
使用 REST、Node 和 Express 构建 Web API
在我们谈到 REST 之前,先让我们一般地谈谈 API。
API是应用程序编程接口。这可以简单地是一组函数,程序或库为其他代码使用提供的。API 可以是位于调用它的代码所在同一台机器上的代码,或者 API 可以通过网络公开,例如互联网和万维网。在后一种情况下,我们称之为 Web API、远程 API,有时也称为云 API。所有这些术语的含义相同,即:可以通过网络调用的函数,而且它们在以这种方式构建时往往具有语言和甚至技术无关性,这意味着一个 JS 客户端可以调用用 C 构建的 Web API,或者用 Python 编写的代码可以调用用 PHP 构建的 Web API,等等。
结构化和构建这样的 Web API 有非常多方法,有很多不同的技术和模式可以用来构建它们,也有很多不同的形式。然而,在尝试了多种不同的选择之后,有一种方法脱颖而出,那就是 REST。
介绍 REST
在其核心,REST,即表示状态转移,只是以(相对)一致的方式结构化 HTTP 请求的一种方法。
Web 本身使用 REST 原则。例如,当你在浏览器中输入一个 URL 时,你是在请求一个基于唯一 URL 的资源,无论这个资源是一个 HTML 页面、一张图片,还是以 JSON 形式(在谈论 REST 服务时最常见)的一些数据。这就是万维网基本上是,这就是 REST 实际上也是!然而,现代的 REST 概念对这一基本想法进行了细化,使其更加明确。
我们所说的 RESTful 服务,即使用 REST 构建的 Web API,以明确的方式定义了标准 HTTP 方法的意义——例如GET、POST、PUT和DELETE——这样它们就有特定的含义。例如,使用GET发出的请求意味着检索资源。PUT(通常)意味着更新某些现有资源,而POST(通常)意味着创建一个新资源。DELETE当然字面上意味着删除一个现有资源。当然,我们是在谈论某个服务器上的数据。
将构成缩写词REST的单词分解开来有助于理解它:
-
词语representational指的是当你从 API 请求数据时,你得到的是一些资源的表示,这种表示是以你可以消化在代码中的形式,但并不是资源本身。例如,虽然一个支票账户资源可能以某种二进制形式存储在服务器上的数据库中,这种形式对客户端来说像是乱码,但你可以用诸如 JSON 这样的东西来表示该账户,JSON 本质上只是具有特定格式的纯文本,任何客户端都可以使用。
-
“状态”这个词指的是资源的当前状态。换句话说,当你请求一个支票账户的表示形式时,你得到的是它在请求时的表示形式。
-
“转移”这个词字面意思是将那种表示形式通过网络传输。
所以,把这些放在一起:REST API 是一种将服务器上存储的资源表示形式在当前状态下传输,或者以某种方式允许更新当前状态或删除资源的 API。
那是理论;现在,让我们从更实际的角度来看一下 REST。
实践中的 REST
当你与 REST API 一起工作时,你可能会有一个指向服务器上资源的 URL——比如说,一个支票账户:
my-awesome-web-api.com/checking-accounts/1234
如果你向那个 URL(它不是一个真实的 URL,但让我们假装它是)发起GET请求,它将返回给你账户编号为 1234 的支票账户的表示形式。这种表示形式可能有多种形式,但使用 RESTful API 时最常见的是 JSON。所以,你可能会得到如下所示的响应:
{ account_number: 1234, balance: 546.32, date_opened: "12-15-2017" }
如果你想要获取所有账户的列表,那么你只需从 URL 中删除/1234部分(很可能是这样),你很可能会得到一个对象表示形式的数组。
现在,如果你想更新那个账户的余额(显然,在这个讨论中我们忽略了安全问题!),你通常会使用相同的 URL 发起请求,但这次使用 HTTP POST方法,并在请求体中提供一些 JSON 数据,如下所示:
{ new_balance: 771.10 }
API 的确切语义——你得到的数据格式和你为更新传递的数据——会因服务而异,但这里的基本思想是 HTTP 方法的意义以及 JSON 在两种情况下都被用来表示支票账户的事实。一些 API 可能要求你发送回从GET请求中接收的确切表示形式,其中要更新的字段具有新值;而其他 API 可能只需要更新字段,就像我这里展示的那样。
注意一个关键点,即 URL 本身就是识别我们正在操作的资源——在这个例子中是支票账户——的标识符。这就是为什么它不需要包含在发送的 JSON 中(很可能——再次强调,语义可能因服务而异)。
同样地,要删除那个账户,你还是会使用相同的 URL,但现在使用 HTTP DELETE方法。这种变化就足以让服务器端代码识别你想要做什么,因为在这个对话中,HTTP 方法本质上是一个动词,描述了你对 URL 指向的资源想要执行的操作。
要创建一个新账户,这会变得有点棘手。虽然我们知道我们需要使用PUT方法,但显然 URL 上没有现有的检查账户号码可以放置,那么在这种情况下 URL 看起来像什么?答案是这又取决于 API 的语义。有些人会让你在账户号码的位置放置诸如/add或/addNew之类的路径。有些人可能不需要任何东西,只需将PUT方法视为你想要添加账户的信号。
这引出了一个重要的观点,我在整个讨论中通过使用模糊术语暗示过:有 REST,或者 RESTful,还有 REST-like。这意味着一些开发者会非常严格地遵循 REST 原则(成为 RESTful)。这样做的问题在于,有时你将不得不在 API 的设计中做出妥协,而这些妥协并不是最优的。例如,有些人说之前描述的/add模式不是“正确”的 REST,但他们通常没有很好的答案来解释“正确”的答案是什么。
相比之下,你可以更加灵活一些,设计你的 API 使其成为 REST-like,这意味着你遵循定义 REST 的大多数架构指南,但允许其中有一定的可互换性。几乎总会有一些情况,事情并不像你希望的那样完美地符合 REST 模型。即使是 HTTP 方法的含义也可能存在争议。虽然GET和DELETE几乎从未被争论过,但一些开发者说,POST应该用于更新而不是PUT,等等。
没有一个标准的“你必须遵循这些 REST 规则,否则会有麻烦!”的规则,任何人都可以指向。REST 不是一套指南;它是一个架构原则,这些通常没有明确的规则可以遵循。
因此,我的建议是偏向于 REST-like。只要你在使用具有一致意义的 HTTP 方法,并且只要你在使用 URL 来标识要处理资源,那么你大约已经完成了 90%的“正确”REST。如果你还使用 JSON 来表示这些资源,那么这可能会让你达到大约 98%。
所以,这就是 REST 的本质,但你是如何构建一个 RESTful Web API 的呢?让我们看看一种你多少熟悉的方法:Node 和 Express。
构建你的第一个 REST API
我们将像往常一样,使用 NPM 初始化一个新的项目。然后,我们将使用现在熟悉的npm install –save express命令添加 Express 作为依赖项。这为我们编写 RESTful API 奠定了基础。
让我们以我们之前的星舰人员数据库为例,创建一个 API,它将允许我们添加人员、删除人员、更新人员,当然,查询人员。为了简化问题,我们的“数据库”(在这里简单地说是指数据集合,而不是之前讨论的正规数据库)将只是 JS 变量,因为我们现在专注于 API 部分,而不是数据库部分。我会将代码分解成易于消化的小块,但完整的文件是下载包中 ch-07/rest 目录下的 restAPI.js,它开始如下:
const express = require(`express`);
const app = express();
app.use(express.json());
当然,我们需要导入 Express 本身并创建一个 Express 应用程序,然后添加一些中间件来处理请求中发送的 JSON。这些内容你在第三章中已经看到了。
之后,我们在 JS 变量中创建我们的“数据库”:
let people = { };
people[1] = { first_name: `James`, last_name: `Kirk`, age: 32 };
people[2] = { first_name: `Leonard`, last_name: `McCoy`, age: 38 };
people[3] = { first_name: `Montgomery`, last_name: `Scott`, age: 43 };
let nextKey = 4;
首先,创建一个空对象并将其分配给 people 变量。
接下来,我们在数据库中添加之前在数据库讨论中看到的三个船员,每个都是一个 JS 对象。
这里需要注意的是,JS 对象是所谓的关联数组。这是一种将值映射到键的数据结构。在 JS 对象中,字段名是键。这允许我们使用数组括号符号来访问这些字段(键名与值相关联,我们可以使用数组符号来访问它们,因此称为关联数组)。
而有趣的是,在 JS 中,对象的字段名可以是数字,所以我在这里使用了数字作为键。这允许我简单地通过递增 nextKey 变量(你在最后一行定义的)来为每个新添加的人员创建一个唯一的键。
因此,当该代码执行时,它会产生一个具有以下结构的 JS 对象:
People = {
1: { first_name: `James`, last_name: `Kirk`, age: 32 },
2: { first_name: `Leonard`, last_name: `McCoy`, age: 38 },
3: { first_name: `Montgomery`, last_name: `Scott`, age: 43 }
}
然后,我们可以使用键值(1、2 或 3)来访问对象中的人,以访问特定人员的对象。本质上,键作为人员的标识符,这允许我们通过该标识符请求人员。
数据库定义好后,我们可以在我们的 API 中设置第一个函数,这将是一个用于创建新人员的 POST 请求:
app.post(`/people`, (inRequest, inResponse) => {
const { first_name, last_name, age } = inRequest.body;
const key = nextKey;
nextKey = nextKey+ 1;
people[key] = { key, first_name, last_name, age };
inResponse.status(201).send(people[key]);
});
正如你在第三章中看到的,我们使用 Express 设置了路由,其中路由对应于 URL,我们调用应用程序对象上对应于路由应响应的 HTTP 方法的适当方法来完成此操作。这里,我们最终得到一个可以发送的 URL,如 localhost:8080/people,这将触发此代码。
在函数内部,我们的中间件已经解析了传入的 JSON,其形式如下:
{ "first_name": "Pavel", "last_name": "Checkov", "age": 21 }
单个值将被提取并添加到inRequest.body对象中。我们可以使用 JS 解构符号(如第六章中解释的那样)将值放入单独的变量中。从那里,我们取nextKey的值作为新对象的键,并确保我们增加它,以便下一次创建时得到一个新的键。然后,我们只需添加一个包含请求中接收到的数据的新对象,在刚刚创建的键下。
创建我们的 Web API 的最终一步是返回一个响应,因为没有它的话,这并不会有什么用处!但是,这里还有“正确”的 REST API 设计的一个方面:返回码也应该有一些意义,就像 HTTP 方法一样。
你可以在这里返回一个 HTTP 200 状态码(或响应码),因为 200 表示“OK”,或者“操作成功。”然而,代码 201 表示“已创建”,这似乎更适合像这样的对象创建函数。关于哪个更合适可能会有争议,但关键是要确保你的函数与响应码的意义保持一致。
无论你做出什么选择,它们都应该始终是合理的!虽然 200 或 201 是有意义的,但例如 500 就不合适,因为这个代码表示内部服务器错误(你可以在这里看到代码列表及其含义:developer.mozilla.org/en-US/docs/Web/HTTP/Status)。
除了响应码之外,你返回的内容也是一个重要的考虑因素。而且,风险是听起来像一张破唱片,你可能会发现关于各种操作应该是什么样子的争论。但是,当创建像这样的资源时,一个好的做法是返回新的资源本身,因为调用者可能想要一个它的副本,尤其是如果包含了标识符,就像这里一样。这允许调用者拥有他们需要的所有信息——关键——以便稍后无需再次查询 API 即可删除或更新资源。
接下来,让我们看看一个获取所有人列表的函数:
app.get(`/people`, (inRequest, inResponse) => {
inResponse.status(200).json(Object.values(people));
});
是的,这只是一行简单的代码!我们只需要返回一个状态码 200(对于一个GET操作来说这是合理的,在我看来没有比这更好的了),这是一个 JSON 表示的值——people对象中每个人的对象。
我们将要编写的下一个函数是用来获取一个单独的人:
app.get(`/people/:key`, (inRequest, inResponse) => {
const key = inRequest.params.key;
inResponse.status(200).json(people[key]);
});
当你定义 HTTP 请求的 URL 时,在这里通过再次调用app.get(),你可以为给定的 HTTP 方法有多个处理程序,但每个处理程序的 URL 必须不同,否则 Express 将不知道你想使用哪一个。在这里,通过添加:key元素,我们使 URL 不同。这是一个 Express 将填充并使它可在inRequest.params对象上可用的令牌。如果客户端向/people发出GET请求,那么之前定义的app.get()路由处理程序将执行,但如果他们改为向/people/1发出请求,那么这个函数将执行,URL 中的1值将成为inRequest.params.key的值。
这允许我们将值放入key变量中,然后我们可以使用它来访问people中的正确人员对象,并将其作为 JSON 发送给调用者,以满足请求。
注意
我应该利用这个机会指出,我非常简单化地编写了这段代码,为了实现这个目标,我忽略了一件事,那就是错误检查。例如,如果调用者传递了一个在people中不存在的key变量,会发生什么?按照现在的编写方式,他们将不会收到任何响应(或者,实际上,一个空响应——但这不是调用者期望的;这是重点)。你很可能想检查这种条件,并返回不同的状态码,比如 404 Not Found,也许在响应中还有一个字符串说它没有被找到(这也是那些可辩论的 REST 决策之一——有些人认为 HTTP 响应代码就足够了;其他人总是希望无论发生什么,函数都能返回some响应)。
好的,太好了——我们可以创建人员,我们可以获取所有人的列表,甚至可以获取一个特定的人员,那么更新现有人员怎么办?下一个是:
app.put(`/people/:key`, (inRequest, inResponse) => {
const key = inRequest.params.key;
const { first_name, last_name, age } = inRequest.body;
people[key] = { key, first_name, last_name, age };
inResponse.status(200).send(people[key]);
});
URL 与获取人员的 URL 相同,这在逻辑上是合理的,因为在这两种情况下,调用者都需要指定我们正在处理哪种资源。但是,当然,我们需要不同的 HTTP 方法,所以这次使用app.put()来注册此路由的处理程序。在处理程序函数内部,我们将保持简单,并始终更新目标资源中的所有字段(通过创建一个新对象来实现)而不是挑选它们。这意味着调用者必须始终发送所有数据,即使是不变字段。然后,只需创建一个新的对象,用发送的数据填充它,将其分配给people对象中的适当键,并返回一个合适的响应。
由于没有 HTTP 响应代码能更清楚地表示更新成功,所以我选择了普通的 200(OK),这次只返回了key变量,因为调用者已经有效地在其当前状态中拥有数据的副本(可以说,甚至不需要发送回key变量,但在这个案例中,我更喜欢有something返回)。
我们必须编写的最后一个路由是用于删除人员:
app.delete(`/people/:key`, (inRequest, inResponse) => {
const key = inRequest.params.key;
delete people[key];
inResponse.status(200).send(key);
});
是的,就这么简单!这次,我们使用app.delete(),因为这是使用此功能所需的 HTTP 方法。我们从 URL 中获取key变量,并使用 JS 的delete关键字,它从对象中删除一个字段,以删除指定的人。最后,发送一个 HTTP 200 响应代码,以及被删除的key变量(再次,因为我认为不只是一个空响应更合适,但这将是你自己工作中需要做出的决定)。
有了这个,我们就有了我们需要的所有 Web API 功能。现在,我们只需要启动 Express 服务器,从而使我们的 API 变得活跃:
app.listen(8080, () => {
console.log(`API is up and running on port 8080`);
});
我们在端口8080上运行它,一切正常。
现在,你当然可以离开并编写一些代码,比如一些在浏览器中运行的 JS 代码,以调用这个 API。但在你这样做之前,作为一个想要成为优秀的 Web 开发者,你可能需要测试一下,对吧?你有很多选择,但我接下来要特别谈谈一个非常受欢迎的选项:Postman。
使用 Postman 测试 API
一旦你有一个 API 正在运行,测试它可以通过多种方式完成,但许多开发者使用的一种方式是名为 Postman 的工具。这是一个 GUI 桌面应用程序,允许你向任何你需要的东西发送各种 HTTP 请求,包括你现在应该在机器上运行的 API。使用 Postman,你可以定义请求,包括它们使用的 HTTP 方法、使用的 URL 以及与它们一起发送的任何数据,并且你可以通过点击按钮来触发这些请求。Postman 可以显示响应,甚至可以将原始响应重新格式化,使其更容易阅读。
例如,测试从我们的模拟数据库检索所有人的GET操作很容易:
-
启动 Postman 并在必要时登录。
-
如果你还没有工作区,创建一个(它可能会默认为你创建一个)。
-
在左侧,你应该看到一个集合标签。集合只是逻辑上相关的请求的组。使用你将在集合标签旁边看到的+按钮创建一个新的集合,并给它起一个你喜欢的名字(我创造性地把它叫做书)。
-
点击集合,使其高亮显示,此时你应该在名称右侧看到一个带有三个点的菜单。点击菜单,然后选择添加请求选项。你应该看到一个新请求被添加,在右侧,应该出现一个新的标签,其中包含请求的所有详细信息;Postman 应该自动将焦点放在请求的名称上,以便你可以在那里输入任何你想要的。
-
确保在下拉菜单中选择请求名称下方的GET方法。
-
在方法右侧的框中,最初应该显示为输入 URL,输入以下 URL:localhost:8080/people。
-
然后,你应该能够点击发送按钮来发送请求,并且结果应该出现在底部的响应部分。
图 7.5 展示了所有这些操作完成正确时的样子:

图 7.5:Postman,包含一个书集合,添加了一个 Get All 请求,并成功返回了 Get All 响应
然后,你可以添加一个请求来测试创建新人员。这和开始时在集合中创建新请求的流程相同;然而,这次,方法将是 POST 主体内容以发送。它将只是某些 JSON,可能如下所示:
{ "first_name": "Pavel", "last_name": "Checkov", "age": 21 }
在 Body 选项卡的右侧,你还需要选择数据类型为 JSON。完成这些后,你应该能够触发请求并获得有效的响应,如图 图 7.6 所示:

图 7.6:Postman,显示一个成功的 Create 响应
作为练习,请继续添加更新和删除人员的请求。记住,在这些情况下,URL 需要包含末尾的键(例如,如果 4 是从创建调用返回的键,那么可能是类似 localhost:8080/people/4 的内容)。稍作尝试,创建一个人,然后进行 Get All 操作以查看它已被添加,然后可能更新一个人并为此特定人员进行 Get 操作以查看更新,最后删除那个人,然后进行最终的 Get All 操作以查看他们已被移除。
这只是使用 Postman 的基本操作;它具有比这更多的功能:
-
你可以编写脚本来执行多个请求,以更全面地测试你的目标
-
你可以包括 cookie、头部以及 HTTP 请求允许的所有其他好东西
-
你可以为需要安全凭证的 API 提供安全凭证
-
你可以模拟浏览器发送表单而不是发送 JSON
-
你可以测试 GraphQL 服务器,这是一种你可以以安全的方式直接将数据库暴露给客户端,并且根据他们自己的特定需求查询数据的方法。
-
你可以设置证书以能够测试 HTTPS 端点
简而言之:如果是通过 HTTP/HTTPS 完成的,那么你几乎肯定可以用 Postman 完成。这是一个伟大的工具,它允许你在编写所有客户端代码之前测试你的服务器端代码。
但除了开发期间,另一个可以使用像 Postman 这样的工具的情况是,如果你的代码对于客户端到服务器的特定请求不起作用。问题可能出在客户端代码或服务器代码中。你怎么知道是哪一个呢?一个很好的开始方法是完全从方程中移除一个或另一个。如果你使用 Postman 测试你的 Web API 并发现出现问题,那么你就发现了问题一定在服务器端,因为客户端没有参与。好吧,公平地说,随着你经验的积累,你会发现这个说法并不总是像看起来那样具体!但至少这是一个通向正确道路的一般稳健方法。
现在你已经对数据库和 Web API 有了一些了解,让我们谈谈之前已经触及但还有更多话要说的话题,那就是“经典”Web 与“现代”(基于 SPA 的,主要是)Web 的比较,以及旧的就是新的,在某种程度上,以 SSR 的形式。
回到未来——SSR
在第三章中,我们讨论了 SPA 以及它们如何导致不同的 Web 开发模型。我们讨论了这种新模型与经典模型的不同,在经典模型中,页面上的每一次交互都会导致对服务器的请求,服务器生成一个全新的 HTML 页面来显示。而新模型则依赖于 JS:客户端向服务器请求数据,客户端代码随后对其进行处理,最常见的是以某种形式显示它。
当我们查看 React、Vue 和 Angular 时,我们看到了初始 HTML 文档基本上是空的,最终在浏览器中显示的所有内容都是由客户端机器上的 JS 即时创建的。
然而,有趣的是,正如这个领域中的许多事物一样,摆锤正在稍微向相反方向摆动,这一次是以SSR的形式,它代表服务器端渲染。使用 SSR,网页在服务器上渲染并返回给浏览器。
但等等——这难道不是在 SPAs 之前就存在的事情吗?!
是的。是的,确实如此。车轮转动,不是吗?
但正如你可能猜到的,SSR 和仅在 Web 服务器上拥有静态 HTML 页面之间还是存在区别的。
使用 SSR 时,通常只有第一次请求会生成新的 HTML 页面。服务器将一个本质上为 HTML 页面的模板合并一些数据,然后返回最终的 HTML 给客户端。然而,有趣的是,其中一些工作实际上可以通过在服务器上执行 JS 而不是在浏览器中执行来完成。实际上,服务器就像一个网络浏览器,尽管它不显示 HTML。你甚至可以使用 React、Vue 和 JS 等库在服务器上渲染内容。服务器生成最终的 HTML 文档,就像浏览器通过执行 JS 一样,并将其完整地返回给浏览器。这通常(实际上几乎总是)包括所有必要的 CSS 和 JS,它们都嵌入在 HTML 文档中。
客户端发生的后续操作可能会导致调用服务器以生成整个新的 HTML 文档,在这种情况下,SSR 就像经典模型一样。但更常见的是,在初始页面加载后,事情开始像 SPA 一样运作,即服务器被调用以获取数据,然后以某种方式在客户端渲染或使用这些数据。我们最终得到一种混合的方法。
你可能会问,为什么有人想这样做?有几个很好的理由:
-
性能:特别是当我们谈论网站最初加载速度时,性能是一个关键指标。通过使用服务器来完成这项工作——这些服务器在大多数情况下比客户端机器更强大,并且更接近它们所需的数据——返回完整的文档可以比返回 HTML 文档(然后创建新的请求以获取资源,如 JS、CSS 和图像文件,然后使用 JS 渲染内容)明显更快。
-
搜索引擎优化(SEO):这是在网站上包含搜索引擎用于对您的网站进行排名的内容的艺术。正确调整此内容可以使您在搜索结果中更快地显示出来,这对于公共网站来说是一个关键考虑因素。对于仅作为 SPA 运行的网站,该内容仅在请求时存在于客户端机器上,因此搜索引擎更难索引您的网站,您也更难进行 SEO。但是,使用 SSR 时,由于服务器返回的是完整的 HTML 文档而不是几乎空的文档,因此您可以启动 React、Vue 或 Angular 应用程序,搜索引擎“看到”更多内容可以索引,当 SEO 做得正确时,这会导致搜索结果中的排名更高。
-
更好的设备支持:因为大多数渲染完整 HTML 的繁重工作都在服务器上完成,这允许较弱的客户端设备有更好的体验。例如,全球仍在使用的旧手机,通常在渲染完整的 HTML 页面(从其角度来看是静态的)方面会比在手机上运行大量复杂的 JS 来动态生成 HTML 要容易得多。
大多数现代框架现在都支持 SSR。实际上,在某些情况下,这可以像在配置文件中切换一个开关一样简单,突然间,你会发现你的初始 HTML 文档已经在服务器上被渲染了。尽管在许多方面,比如经典的模型,这个模型到现在已经有 30 多年历史了,SSR 相对较新。好吧,有点新:它实际上在 2010 年左右就被引入到一些现代框架中,而在网络开发中,10 年真的不算长!但它在开发者心智份额方面开始崭露头角,因为它已经成为现代框架的一个更标准的部分。
但它仍然是一个可选的东西。没有 SSR 也能做 SEO 吗?是的,可以,尽管这可能更具挑战性。例如,你有时会发现你可能需要在你的静态 HTML 页面中包含一些不可见的内容,当它到达浏览器时会被移除——但然后,你会发现搜索引擎似乎已经发现了这样的伎俩,所以它可能不会像你希望的那样有效,或者在某些情况下根本不起作用。而且,你的用例可能根本不值得使用 SSR,因为它确实使事情复杂化,每次你在网络开发中增加复杂性时,你都必须进行成本效益分析,以决定它是否值得。
另一种可能性是使用服务器端技术来渲染你的初始 HTML 文档(以及处理后续的数据请求),这在某种程度上实际上是一种 SSR(服务器端渲染)的形式,但你在其中拥有更细粒度的控制。当然,这可以用 Node 来实现,但还有其他选择,这正是我们接下来要讨论的内容。
在服务器端使用 PHP
你可以使用许多服务器端技术;你不仅限于 Node 和 JS。其中一种技术——实际上是最古老的之一——是 PHP。PHP 首次出现在 1995 年(尽管花了几年时间才开始流行),PHP 被称为预处理器。本质上,它是对 Web 服务器的一种扩展,对请求的资源进行某种操作。
在 PHP 的情况下,这种操作是执行嵌入在 HTML 文件中的 PHP 语言编写的代码。该文件被赋予.php扩展名,当请求时,该扩展名会告诉 Web 服务器需要将该文件交给 PHP 预处理器,允许它对其进行转换,然后返回转换的输出给调用者。
这可能听起来有点复杂,但我认为一个简单的例子会使其变得清晰(你可以在ch-07/php目录下的hello_world.php文件中找到这个例子)。像往常一样,在编程中,让我们使用一个“Hello, world!”的例子:
<html>
<head>
<title>A simple PHP example</title>
</head>
<body>
<?php
echo "Hello, World!";
?>
</body>
</html>
正如你所见,它是一个简单的 HTML 文档,直到你看到那个看起来很奇怪的<?php。这标志着 PHP 代码块的开始,而?>则关闭该块。在这两者之间的任何内容都必须用 PHP 编写。当这个文件从一个安装了 PHP 的 Web 服务器请求时,Web 服务器看到.php扩展名,并将文件交给 PHP 处理器。处理器会寻找任何这样的 PHP 代码块(你可以有任意多个),并执行其中的代码。无论代码的输出是什么,如果有输出的话,它将替换最终输出中的块。
因此,在这种情况下返回给浏览器的将是以下内容:
<html>
<head>
<title>A simple PHP example</title>
</head>
<body>
Hello, World!
</body>
</html>
正如你所见,这里已经不再有任何 PHP 代码。取而代之的是,通过echo语句输出的文本替换了它。这是一种许多服务器端技术中常见的做法,“在特殊块中执行一些代码,并用其结果替换它”,这意味着客户端不需要了解任何关于 PHP 的知识;所有这些都由服务器处理。客户端只知道它会收到一个 HTML 文档;它不知道服务器是如何生成它的,但 PHP 和类似的技术为我们开发者提供了一个非常简单的方法,将动态内容嵌入到其他静态资源中,而无需任何复杂的基础设施或代码,例如与 Node/Express 应用程序相比。
PHP 是一种强大且功能丰富的语言,就像 JS 一样,并且有许多库支持它,这使得它可以轻松地与数据库交互,调用远程系统,操作图像,以及真正地做任何有用的服务器端语言会做的事情。随着时间的推移,它已经发展成为一个具有现代语言开发者所期望的所有特性的现代语言。
为了让你对 PHP 有更深的了解,让我们实现一个用 PHP 做得很常见的事情,或者说是任何服务器端技术:处理表单的提交。
首先,我们将创建一个简单的表单(form.html):
<html>
<head>
<title>PHP Form</title>
</head>
<body>
<form action="form.php" method="post">
Your Name: <input type="text" name="username"><br>
<input type="submit">
</form>
</body>
</html>
正如你所见,它只是一个简单的 HTML 表单,你之前已经见过。我们允许用户输入他们的名字并提交表单;仅此而已。表单提交的目标是form.php,我们将在下一部分创建它:
<?php
$username = $_POST["username"];
echo "Hello, " . $username . "!";
?>
在这里,我们通过从请求中获取 username 变量并使用它构造一个字符串来构建一个纯文本响应。PHP 中的变量以 $ 符号开头,$_POST 变量是 PHP 提供的一个变量,它将保存通过 POST 请求提交的任何数据的值。我们唯一需要担心的是 username,所以我们最终得到一个包含用户在表单中提交的任何值的 $username 变量。然后,使用 echo 创建输出,在 PHP 中,点号用于表示字符串连接,因此我们构建一个如 "Hello, Frank!" 的字符串并将其返回给客户端(注意,我们不需要构建一个完整的 HTML 文档,因为浏览器完全能够显示这样的纯文本响应)。
PHP 相对简单直接,但开辟了众多可能性。然而,它绝不是唯一可用的此类选项。另一个在有些情况下更为流行的选项是 Java,特别是为其提供的 Spring Boot 库。现在让我们快速看一下这个。
使用 Java 和 Spring Boot 进行构建
现在,Java 和 Spring Boot 在某种程度上是相辅相成的,因为 Spring Boot 是人们现在使用 Java 编写应用程序最流行的方式,但 Spring Boot 没有 Java 就无法存在。所以,让我们从 Java 开始,然后讨论一些与之相关的可以帮助你构建 Web 应用程序的事情,最后到达 Spring Boot。我认为一旦你阅读完这一节,你将很好地理解这些部分是如何相互配合的。
介绍 Java
Java,我会在一开始就告诉你它与 JS 没有任何关系,除了有相似的语言语法,并且是现在已解散的 Sun Microsystems 的产品,现在由 Oracle 拥有,并且它可能是所有语言和平台中最广泛使用之一。它在企业环境中特别受欢迎,在这些环境中,稳定性、灵活性——以及如果出现问题,公司会起诉,坦白说——是首要考虑的因素。
Java 的流行基于其“一次编写,到处运行”的原则。它是最早允许一段代码(无需任何修改)在不同的硬件平台和操作系统上运行的编程语言之一。在 Java 之前有几种方法可以实现这一点,但没有一种像 Java 那样简单。
Java 通过引入JVM,即Java 虚拟机来实现这一点。从概念上讲,它与您已经遇到的虚拟机类似,但它并不是一个完整的机器模拟。相反,它是对一个人工机器的模拟,这个机器在现实世界中并不存在。它最终成为编译代码的目标。换句话说,Java 不是将代码编译成可以在本地机器上运行的格式,而是编译成可以在 JVM 代表的虚拟机器上运行的中间格式。JVM 也不是像虚拟机那样的机器和操作系统的封装;它只是机器,上面运行着编译后的 Java 代码,并且是唯一运行在它上面的东西(或多或少)。
Java 还附带了一个非常广泛的标准的库和成千上万的函数供您使用,经过实战检验且非常稳固。这意味着您有一个很好的基础可以在此基础上构建,甚至在您考虑添加任何第三方库之前,Java 当然支持第三方库。
当您使用 Java 时,您通常会与它的JDK或Java 开发工具包交互。JDK 是SDK(软件开发工具包)的一种特定形式。SDK 是一组用于与特定开发平台或语言一起工作的工具,而 JDK 是针对 Java 的 SDK。您安装一个 JDK,或者实际上通常只需展开存档而不真正安装它,然后使用其命令行工具编译代码、调试代码以及执行您可能想要用 Java 代码做的所有其他事情。
编写 Java 应用程序
那么 Java 代码看起来是什么样子呢?这里可能是你可以编写的最简单的 Java 应用程序(这是位于ch-07/java目录中的HelloWorld.java文件):
public class HelloWorld {
public static void main(String inArgs[]) {
System.out.println("Hello from your first Java
app!");
}
}
这被称为 Java 应用程序,它表明 Java 中的所有内容都是以一个类开始的,这是在class关键字之前带有public关键字和名称的基本概念,被称为访问修饰符,它告诉 Java 这个类可以从哪里使用。public访问修饰符意味着任何其他 Java 代码都可以使用它。
类中可以包含很多东西,但这个类只包含一件事情:一个名为main()的函数(或方法,因为它是类中的函数)。该方法也有public访问修饰符,所以任何其他 Java 代码都可以调用这个方法(相比之下,一个方法可以是私有的,这意味着只有类中的其他代码可以使用它)。
static关键字表示可以在不首先创建HelloWorld类的新实例的情况下调用main()方法(这通常是我们在类上所做的——从它们创建对象实例,但在这个例子中,我们只想能够执行方法,所以我们可以使用static来跳过这个要求)。
void 关键字表示该方法不会返回任何内容。
此方法接受一个字符串数组作为参数,并且当运行时,如果提供了任何选项,它将来自我们传递给它的命令行(我们这里不需要,但我提到这一点,因为 Java 需要它来使 Java 应用程序工作)。
main()函数中的System.out.println()语句——它是里面唯一的代码——是一个将文本输出到控制台的方法。
你必须首先编译这个应用程序,这是使用 Java 编译器完成的:
javac HelloWorld.java
这将生成一个HelloWorld.class文件。.class文件是实际上在 JVM 上运行的文件,你现在可以轻松地这样做:
java HelloWorld
注意,javac是 Java 编译器,而普通的java实际上是 JVM。JVM 将在当前目录中查找名为HelloWorld.class的文件,假设你已经编译了HelloWorld.java,然后会在其中查找public static void main()方法(它必须是这个确切的方法签名),这是 Java 应用程序必须包含以使其可运行的标准方法。找到后,它将执行该方法,文本应该输出到控制台。
Java 应用程序很棒,你可以用它们做几乎任何事情,但当我们谈论使用 Java 进行 Web 开发时,我们很少用纯 Java 应用程序来做。在 Web 开发中,比应用程序更高的步骤是 JSP 和 servlets。
介绍 JSP 和 servlets
与 PHP(以及你稍后将在.NET 中看到的.cshtml文件)一样,Java 提供了一种名为JSP或Java Server Pages的模板语言。这允许你在 HTML 文件中嵌入 Java 代码。一个简单的例子如下:
<html>
<head>
<title>Hello World JSP</title>
</head>
<body>
<% out.println("Hello World!"); %>
</body>
</html>
任何在<% %>块内的代码都是 Java 代码,并且将像 PHP 一样被执行。
当请求 JSP 时,它将由 JVM 处理,会发生的事情是生成一个名为servlet的东西。servlet 是一个特殊的 Java 类,知道如何响应 HTTP 请求。但 servlet 不能单独工作。相反,它们必须作为Java Web 应用程序的一部分运行,该应用程序在应用服务器上运行。应用服务器管理 Web 应用程序,处理其生命周期事件,并充当 Web 服务器。当它第一次看到对这个 JSP 的请求时,它将生成一个 servlet 类,然后执行它(后续请求可以立即执行)。
为我们的 JSP 生成的 servlet 代码可能看起来像这样:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class GeneratedServlet extends HttpServlet {
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello World JSP</title>");
out.println("</head>");
out.println("<body>");
out.println("Hello World!");
out.println("</body>");
out.println("</html>");
}
}
很可能还有其他方法来处理 servlet 生命周期中的各种事件,但这是基本概念,你可以看到_jspSevice()方法,它是 servlet 中的主要方法,从我们的 JSP 中输出 HTML。当然,JSP 中的更复杂 Java 代码会导致 servlet 中的代码更复杂。
你也可以直接编写 servlet;你不必从 JSP 开始。事实上,虽然没有 servlet,JSP 无法使用,但 servlet 确实可以在没有 JSP 的情况下使用。
注意,我没有在代码包中包含所有这些的完整示例,原因有两个。
首先,设置一个可工作的 Java Web 应用远不止这些,从需要安装和运行应用服务器(它管理 JSPs、servlets 以及它们所属的 Web 应用,以及称为Java 企业版(或简称JEE)的东西)开始。虽然 Java 本身带有强大的标准库,但 JEE 在此基础上进行了显著扩展,提供了现代 Web 应用所需的大量高级功能。虽然 Java 提供了基本的数据结构和算法,但 JEE 增加了 JSPs、servlets、数据库访问以及一大堆其他“高级”功能。
其次,我想为下一部分节省空间,我已经为 Spring 和 Spring Boot 的主题提供了一个工作示例,因为现在 Spring 和 Spring Boot 的话题可能比从头开始使用 JSPs 和/或 servlets 以及 JEE 构建 Java Web 应用更重要,这就是我们接下来要探讨的内容。
介绍 Spring 和 Spring Boot
Spring 无疑是 Java 中最受欢迎的第三方库。它为 Java 提供了极其庞大的功能列表:包括安全、简化的数据库功能、高级 Web 应用架构、REST 服务构建和消费、任务调度等等。
然而,它最著名的可能是其依赖注入(或简称DI)框架。DI 的理念是,而不是您的代码构建各种类型的对象,更好的做法是让某个高级实体将它们注入到您的代码中。这样做有很多原因,但最大的可能是测试。如果您有一些代码用于建立数据库连接,然后您想测试这段代码,您可能需要连接到一个单独的测试数据库。而不是将这构建到代码中,您可以选择让测试框架建立连接,知道如何为测试数据库而不是真实数据库建立连接,并将连接注入到您的代码中。这样,您测试的代码无论连接到哪个数据库都是相同的。
Spring 因其 DI 框架而闻名,但在此之后它迅速发展,几乎包含了您可以用 Java 做的任何事情。事实上,现在它如此全面,以至于很多人认为 Spring 是 Java 的核心,尤其是 JEE。虽然 Spring 在底层仍然需要 JEE,但在使用 Spring 时,如果您愿意,几乎可以忽略 JEE,转而使用 Spring 的功能。
在 Spring 的基础上,出现了名为 Spring Boot 的东西。简而言之,Spring Boot是一个框架,它帮助您启动(设置)并开发基于 Spring 的 Java 应用,通常是 Web 应用。Spring Boot 通过以下方式实现:
-
这简化了设置和配置 Spring 应用的过程。
-
独立运行:Spring Boot 应用是独立的,可以直接使用 Java 运行,这使得构建和测试 Spring 应用变得更加容易。
-
默认配置:Spring Boot 提供了一套默认配置,以便快速设置项目,同时也提供了覆盖这些设置进行自定义的选项。这意味着你不需要进行太多的前期配置就可以使 Spring Boot 应用程序工作,但你可以根据需要覆盖默认设置。
-
内嵌服务器:Spring Boot 应用程序直接包含 Tomcat、Jetty 或 Undertow 应用程序服务器,因此你不需要将应用程序部署到应用程序服务器。Spring Boot 为你提供了一个可运行的单元,除了 Java 本身之外不需要任何其他东西来工作。
-
功能丰富:开箱即用的 Spring Boot 支持广泛的数据访问、安全和云服务,使其成为各种应用的通用工具。
现在我们用 Spring Boot 构建一个简单的 Hello World Web 应用程序,看看需要哪些步骤。
一个简单的 Spring Boot Web 应用程序
开始 Spring Boot 应用程序最简单的方法是访问start.spring.io,让其他人为你做这项工作!这是一个由 Spring 和 Spring Boot 的创建者提供的网站,可以为你生成一个基本的 Spring Boot 应用程序。
你可以在图 7.7中看到这个网站。如果你想亲自尝试,选择你在这里看到的选项(包括在右侧添加Spring Web依赖项,否则该项目将无法工作!)。

图 7.7:带有创建示例项目选项的 Spring Initializr 网站
一旦你点击包含你的启动项目的.zip文件。解压该存档,进入目录中的命令提示符(如果你输入了截图中所显示的确切内容,该目录将被命名为demo),然后执行以下命令:
mvnw spring-boot:run
你会看到很多东西快速闪过,只要它不以某种错误结束,实际上并不重要。但是,为了给你一些概念……mvnw命令代表的是 Maven,这是一个 Java 构建工具。它知道如何将你的项目构建成一个可运行的单元。你知道,真正的 Java 应用程序很少只有一个需要编译的.java文件。通常,需要各种第三方库,大量的源文件需要编译,以及其他必须以各种方式包含的文件。与其自己弄清楚如何做所有这些,只要遵循标准结构并提供一些配置文件,那么 Maven 就会知道如何构建你的项目,为你获取所有依赖项,并处理所有那些令人讨厌的细节。
通常,用于 Maven 的命令只是简单的mvn。但是,为了使其工作,Maven 必须已经安装并且在你的路径中。为了避免自己动手做这件事,mvnw是一个 Maven 包装器。这些文件知道如何使用 Maven,而无需你首先安装它。当你第一次运行该命令时,包装器将下载必要的 Maven 文件,所有 Maven 运行所需的一切,然后它将开始构建项目,在过程中下载所有依赖项。
最后,它将运行项目,启动 Spring Boot 提供的服务器。然后你可以在浏览器中访问它,地址为localhost:8080。然而,如果你立即这样做,你会看到一个“Whitelabel Error Page”屏幕。这是因为我们没有提供任何代码来处理默认路由,Spring Boot 不会为我们做这件事!
为了修复它,我们需要在父项目目录下的src/main/java/com/example/demo目录中添加一个名为HelloController.java的文件,并将以下内容放入其中:
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.
RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Hello, world!";
}
}
这是一个处理指定 URL 请求的控制器类。在这种情况下,@GetMapping("/")行告诉 Spring Boot 在接收到域名之后没有其他内容的 URL 时执行此方法(其中localhost是我们的域名)。
这个声明,加上@RestController行,就是所谓的@RestController注解告诉 Spring Boot 这个类处理 HTTP 请求(它通常用于构建 REST API,但鉴于对 URL 的简单GET请求仍然是一种 RESTful 操作,它适用于这个用例),Spring Boot 将维护 URL 和控制器之间的映射,并在请求到来时将请求传递给适当的控制器。
被@GetMapping注解的方法随后执行并返回我们的问候字符串——这里没有任何花哨的地方。实际上,这几乎就是 Spring Boot 应用能变得这么简单的原因,但它很好地展示了基本思想。
Spring Boot,以及 Spring 和 Java 本身,功能如此广泛,可以做很多事情,以至于你不可能一次性全部学会;你必须随着需要引入新概念,并在过程中学习,这一点你可能会记得,是成为一名网络开发者的一个重要核心要求!
现在你已经看到了一些 PHP 和 Java,让我们来看看另一种服务器端编码的选择,这次来自我们的朋友微软:.NET。
介绍.NET
虽然 Java 在企业领域占据主导地位,但并非唯一的选择。微软有一个名为 .NET 的竞争对手。与 Java 类似,它是一个庞大而分散的平台,几乎涵盖了开发者所需的所有基础功能。它可以处理数据库访问和网络通信,并且可以用来构建 RESTful API 以及其他基于服务器的设施。它可以用于 人工智能 (AI) 应用,并且通过 公共语言运行时 (CLR) 或 CLR 提供对几种不同语言的支持,这在概念上类似于 Java 的 JVM。
就像 Java 一样,虽然 .NET 确实用于桌面开发——实际上比 Java 更多——但它已经主要成为服务器领域的一部分,并且是微软 Azure 云中的主要支持语言/平台。而且尽管 .NET 最初主要是 Windows 技术,但目前它也正式支持 Linux 和其他操作系统。
.NET 通常与微软的 Visual Studio IDE 一起使用。实际上,在实践中,你可能找不到不使用它的 .NET 开发者!但最终,它不是必需的,因为 .NET 提供了一个类似于 Java 的 SDK,你可以仅从命令行与之交互,并且就我们的目的而言,这里我们不会使用 Visual Studio。
生成 Hello World 网页应用
假设你已经安装并配置了 .NET SDK,它使得构建 Hello World 应用程序比其他选项更容易,因为它直接内置在 .NET 中!你所需做的只是为你的项目创建一个目录并执行此命令:
dotnet new mvc -n HelloWorld
你将得到一个名为 HelloWorld 的新目录,其中将包含一个完全功能性的 Hello World 应用程序!要运行它,导航到该新目录并执行以下命令:
dotnet run
你当然会在 ch-07/dotnet/HelloWorld 目录中找到一个已经构建好的项目,但自己从头创建和运行它是一个很好的练习。一旦你做到了,你就可以通过 http://localhost:5262 访问该页面。
从查看 图 7.8 可以看出,它比前例中的简单“Hello, World!”页面内容更多(这就是为什么我在这里展示它,而未展示其他页面)。不幸的是,因此,这个“简单”示例背后的代码比 PHP 或 Java 版本都要多——太多以至于无法一一介绍:

图 7.8:Hello World 应用的 .NET 版本
但是,为了避免让你一无所获,涉及的主要三个文件可能是 Program.cs、HomeController.cs 和 Index.cshtml(.NET 还会创建许多其他文件,但那些是关键文件)。这些文件是我们将在接下来的三个小节中要查看的内容。
.cs 扩展名表示这些是 C# 源文件,其中 C# 是你可以在 .NET 中使用的语言之一,而且是最常用的一个(几乎排除了所有其他语言,我甚至可以说)。通过扩展,.cshtml 表示一个嵌入 C# 代码的 HTML 文件,就像 .php 文件可以嵌入 PHP 代码一样。
服务器启动点
这是 Program.cs 文件,它是设置服务器的起点:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
如果你自己生成此应用程序,你的版本可能因 SDK 的版本而略有不同,但应该大体相同。从它可以看出,C# 看起来与所有你见过的语言都很像,这是可以预料的,因为它们都基于 C 语言的语法和结构(例如,JS 和 C# 之间有很大的差异,但它们看起来足够相似,并且共享足够的概念,即使你以前从未见过基于 C 的特定语言,你也能理解正在发生的事情)。
与使用 Node 和 Express 构建服务器类似,你构建一个 app 对象,在这种情况下使用一个名为构建器的工具,这是一段知道如何为你构建 app 对象的代码(我知道,很明显,对吧?)。之后,添加中间件以执行诸如处理错误(UserExceptionHandler)、服务静态文件(UseStaticFiles)以及设置路由到控制器(MapControllerRoute)等任务。
控制器
控制器是一段处理特定请求的代码,而 HomeController.cs 文件就在这里发挥作用,因为它处理我们的主要“主页”路由:
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using HelloWorld.Models;
namespace HelloWorld.Controllers;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location =
ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId =
Activity.Current?.Id ??
HttpContext.TraceIdentifier });
}
}
这部分是一个 MVC 应用程序,代表 模型-视图-控制器。这是一种应用程序架构模式——换句话说,一种组织应用程序的方式——它试图将视图(通常是 HTML 文件或类似的东西)从模型(本质上意味着数据)和控制器(将其他两个绑定在一起)中分离出来。这三个通常作为独立的代码单元实现,就像这里的情况一样,但它们一起工作,为给定 URL 的客户端生成响应。这样做的原因是,如果你想更改,比如说,你的数据库,你可以这样做而不必触及视图或控制器层;只需按需更改模型层即可。
注意,在这个例子中,实际上并没有模型层,这是可以的:即使省略了视图或模型,你仍然可以有一个更多或更少的 MVC 架构(控制器始终存在,而且通常不会没有视图层,但如果你不需要数据,那么你不需要模型层,这实际上使其成为一个 MV 架构,但我们仍然倾向于称之为 MVC,我想只是因为开发者有时会很奇怪!)。
视图
最后,由 Index.cshtml 文件生成视图:
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about
<a href="https://learn.microsoft.com/aspnet/core">
building Web apps with ASP.NET Core</a>.</p>
</div>
而且这是三者中最简单的一个!就像 PHP 和 JSP 一样,本质上它是一个模板,其中${ }块代表在渲染时执行的 C#代码。其余的都是纯 HTML。
虽然 PHP、Java 和 .NET 不是唯一的选择。你还可以考虑在前面章节中看到的一种:Python。让我们看看 Python 如何被用来构建 Web 应用程序。
使用 Python Web 应用程序框架
在第五章中,你遇到了 Python,我们讨论了它如何在 DevOps 环境中被使用。然而,Python 的应用场景远不止于此。当然,你也可以为各种目的编写独立的 Python 应用程序。但除此之外,Python 还可以作为你的网站和 Web 应用程序的后端平台。
正如你所见,当使用 Node 开发服务器端代码时,开发者有两个主要路径。一个是仅使用 Node 内置功能构建服务器代码,这可能适合小型项目。然而,对于大型项目,大多数开发者更喜欢使用库或框架,如 Express 来增强他们的开发过程。
类似地,Python 提供了从头开始构建服务器的所有基本要素。但与 Node 一样,许多开发者选择使用更全面的解决方案,即框架,以避免编写大量的重复性基础代码,其中之一就是 Django。
Django
Django是一个旨在简化开发的框架;Django 通过提供各种预构建的默认值来减轻开发负担,显著减少了开发者需要编写的基代码量。Django 以其类似于 Angular 的方式而闻名,即它试图包含大多数,如果不是所有,典型开发者完成工作所需的功能。
为了让你对 Django 应用程序可能的样子有一个简要的了解,你需要首先创建一个目录来托管你的项目(你可以在下载包中的ch-07/python找到它),然后安装 Django 本身:
pip install django
在 Python 的世界里,pip在 Node 世界中所扮演的基本角色与npm类似,负责安装和处理包,这个特定的命令全局安装 Django 包,意味着它可以从任何地方使用。之后,我们可以使用 Django 提供的命令来创建一个新的 Django 项目:
django-admin startproject helloworld_project
当你在执行时,你会在当前目录下得到一个helloworld_project目录,然后你需要导航到该目录进行下一步操作。
使用 Django,一个项目可以托管多个应用程序,但我们现在只需要一个,而且也有一个命令可以创建它:
python manage.py startapp hello
manage.py文件是 Django 提供的一个脚本,用于在项目及其应用程序上执行各种设置和管理任务。执行该命令后,你会在helloworld_project目录内找到一个名为hello的目录。
下一步是创建一个视图,这在概念上类似于 Express 应用中的路由:它将响应到特定 URL 的请求。为此,进入 hello 目录,打开那里创建的 views.py 文件,并在注释行下面添加以下内容:
# Create your views here.
from django.http import HttpResponse
def hello_world(request):
return HttpResponse("Hello, World!")
这提供了一个函数,当我们在配置的 URL 上发出请求时,它将执行并返回一个简单的文本响应。
接下来,你需要告诉 Django 使用这个视图。这需要两个步骤。
首先,在 helloworld_project 目录中,你会发现一个自动创建的目录,其名称也是 helloworld_project。我知道——名字很混乱,但这就是 Django 的做法:你得到一个与项目目录相同的名称的目录。在这个内部 helloworld_project 目录中,打开 urls.py 文件。在里面,你会找到一些说明性注释和一些代码行。这里需要做两件事。首先,在现有的导入下面添加一个新的导入:
from django.urls import include
这添加了一个名为 include 的包,这是我们完成后续操作所必需的。
然后,在定义 urlpatterns 数组的行中,在已经存在的 /admin 之后添加一个新条目:
path('hello/', include('hello.urls')),
这实际上设置了一个到 hello 应用的路由,并告诉 Django 在其 hello.urls 文件中查找路由。
那个文件是使一切工作的最后一步。你需要在 hello 目录下创建一个 urls.py 文件,内容如下:
from django.urls import path
from . import views
urlpatterns = [
path('', views.hello_world, name='hello_world'),
]
这在 hello 应用本身中设置了一个路由到我们创建的视图。
到目前为止,你的第一个 Django 应用应该已经准备好了!方便的是,Django 包含一个内置的 Web 服务器用于测试,你现在可以启动它来尝试你的应用:
python manage.py runserver
你应该会发现你的应用可以响应到 localhost:8000/hello 的请求。
对于这样一个简单的示例,可能看起来 Django 涉及的内容很多,但随着应用变得更加复杂,它通过提供的高级功能开始发挥更大的作用,尽管它也可能需要这种类型的设置,但它往往有更好的投资回报率(ROI),这意味着好处值得成本。
但如果你觉得 Django 不适合你,另一个流行的框架 Flask 可能更适合。
Flask
与 Django 一样,Flask 是另一个流行的 Python Web 框架,其 Hello World 示例要简单得多,它从安装 Flask 开始:
pip install flask
接下来,为你的项目创建一个目录(你可以取任何你喜欢的名字),并在其中创建一个 app.py 文件,如下所示:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
猜猜看?实际上,你现在就已经完成了!你现在可以运行 Flask 提供的开发服务器,就像 Django 一样:
flask run
然后,你可以通过 localhost:5000 访问该应用。
显然,Flask 要简单得多,为什么任何理智的人会选择 Django 呢?!
答案是它们服务于不同的目的:
-
Django 正在努力成为尽可能全面的存在。它提供了与数据库、安全、内置管理应用、处理会话和认证的功能、模板引擎、高级 URL 路由以及更多。
-
相反,Flask 是所谓的 微框架。Flask 被设计得更加轻量级,并且简单地不做 Django 那么多事情,这意味着你需要根据需要添加其他库。
它们各有优缺点,这大概就像比较 React 或 Vue 与 Angular 的情况:前两者设计上功能较少,但小框架的灵活性更高,因为你可以选择如何提供特定功能。
检查路线图
好吧,这又少了一些,不是吗?在 图 7**.9 中,你可以看到更新的 Web 开发者路线图:

图 7.9:填入更多框的路线图
通过本章,你揭开了 数据库、Java 和 Spring Boot、PHP 和 .NET 的模块。看看吧——整个后端部分现在已经完全揭露了!
摘要
在本章中,你了解了许多服务器端技术,从数据库开始。你学习了关系型数据库和非关系型数据库之间的区别,在这个过程中,你对 SQL 语言也有了一定的了解。然后,你学习了如何通过 REST 接口公开这些数据,这可能是目前提供 Web API 最常见的方式(哦,顺便说一下,你也学习了什么是 Web API,不是吗?)。
之后,你看到了一些用于构建服务器端代码的服务器端技术,包括 Java 和 Spring Boot、PHP、.NET 以及一些 Python Web 框架,如 Django 和 Flask。我们为它们都提供了代码示例,以便你了解如果你选择它们可能会涉及什么,我们还谈到了在选择一个而不是另一个时需要考虑的决策点——你了解到在很大程度上,这取决于个人偏好以及你最有经验的领域!
在下一章中,我们将从某种意义上结束这本书的技术部分,并探讨一些替代的编码方法,在某些情况下,是一些试图使 Web 开发减少开发的工作的尖端技术。这包括人工智能、内容管理系统(CMS)和其他所谓的“无代码”替代方案。
第八章:编写更少的代码 – CMS、无代码解决方案和生成式 AI
开发者本质上是一群懒惰的人。我知道这听起来很奇怪,但请听我说!我们总是在寻找写更少代码的方法。有时,这样做有很好的理由:如果有人已经投入了大量时间和精力编写了一个库,比如加密数据,那么我们为什么还要浪费时间重新发明这个轮子呢?在大多数情况下,我们可能不应该这样做。
相反,我们总是在寻找自动化任务、为我们生成代码或以其他方式帮助我们摆脱困境的工具!当然,我之前说我们很懒惰时是在开玩笑。不,这更多的是关于我们时间的有效利用。毕竟,如果你需要,你可以用螺丝刀把钉子敲进一块木头里,但使用实际的锤子要快得多,也有效率得多。就像在木工中一样,选择适合工作的正确工具是网络开发的关键。
因此,在讨论了编写代码的几个章节之后,我们现在将讨论如何避免这样做——或者至少尽可能最小化它,并使用工具更有效地编写代码!尤其是当有人按小时付给你时,你应该始终尽可能优化你的时间;有工具可以做到这一点,这就是本章要介绍的内容,从 CMS 的概念开始,包括 WordPress 和 Drupal。
我们接下来将转向所谓的“无代码解决方案”,这些平台允许您以更直观的方式构建网站。最后,我们将讨论目前正在发生的人工智能(AI)革命如何进一步增强您无需编码(至少不是那么多的)就能创造的能力。
因此,在本章中,我们将涵盖以下内容:
-
介绍 WordPress
-
了解 Drupal
-
完全放弃编码——“无代码”解决方案
-
利用人工智能
介绍 WordPress
我们将要讨论的第一件事被称为内容管理系统(CMS)。CMS 是一种软件,允许用户在无需具备技术知识(或至少尽可能减少它)的情况下创建、管理和更改网站上的内容。
一个典型的内容管理系统(CMS)将允许用户在网站上创建页面,通常就像在 Microsoft Word 或任何现代文字处理程序中创建文档一样,以直观的方式进行。例如,它们将允许您将图片拖放到适当的位置,围绕它们添加文本,在页面上绘制部分,以及其他不编写代码就能创建页面的方式。然后,CMS 通常会提供发布该内容的能力,使其对访客可见,并提供管理功能,以便您可以删除页面、重新组织它们、修改它们,以及其他维护它们的方式。您可以使用 CMS 创建一个完整的网站,其复杂程度与创建 Word 文档大致相同。
一个 CMS 甚至可以提供更高级的功能。例如,它们可以让你在页面上拥有一个评论区,访客可以相互社交互动。它们可以提供连接到流行的社交媒体网站的方法,接受用户的上传,实施调查以获取用户的反馈,以及许多其他功能。
现在有许多 CMS,但最流行的可能是 WordPress。WordPress是一个开源的 CMS,特别以其易用性、灵活性和庞大的社区而闻名,该社区贡献了插件和主题,可以让你轻松扩展其功能。WordPress 最初是一个博客平台,但现在已经发展到支持广泛的网站类型,包括电子商务、作品集和商业网站。几乎你可以想到的任何事情都可以用 WordPress 完成,因为它特别以其可定制性而闻名。
在图 8**.1中,你可以看到在安装 WordPress 时,使用基本默认选项自动创建的网站:

图 8.1:从基本 WordPress 安装生成的默认网站
由于其用户友好的界面和广泛的文档,它对初学者和高级开发者都是一个很好的选择。许多开发者会在 WordPress 上构建网站,但非技术用户也可以同样使用它。
接下来,让我们看看 WordPress 的一些主要功能。
主要功能
WordPress 提供的一些最重要的功能包括以下内容:
-
主题和插件:WordPress 有数千种主题和插件可供选择,使用户能够在不涉及 HTML、CSS 和 JavaScript 的情况下增强其网站的功能和外观。主题控制视觉呈现,而插件添加了如联系表单、用户调查、消息表单和电子商务功能等特性。
-
SEO:WordPress 网站往往在搜索引擎中排名很高,因为它们提供 SEO 友好的功能和插件,如 Yoast SEO,以优化内容。它产生的网站在设计上本身就倾向于 SEO。
-
用户友好:WordPress 拥有直观的仪表板界面,易于导航,使得内容管理和网站管理变得简单。你需要的一切都可以在一个地方找到,并以易于理解的方式呈现。
-
社区:拥有庞大的全球社区,WordPress 拥有广泛的论坛、教程和资源,用于故障排除和学习。你永远不会孤立无援,试图自己解决问题;帮助总是只需几个按键的距离。
-
更新:WordPress 团队经常更新他们的代码以确保安全和功能,确保软件保持最新、稳定和安全。
-
适用范围:WordPress 被博客作者、小型企业和甚至大公司广泛使用,这证明了它作为一个网站平台的多样性和可靠性。
您可以在图 8.2中看到的仪表板界面是您与 WordPress 交互的主要方式。它允许您控制其所有功能,了解 WordPress 的全面性是值得一看的。当然,请记住,您在其中看到的内容可能因安装而异,具体取决于您安装了哪些插件。

图 8.2:WordPress 仪表板的全部辉煌
还需要注意的是,WordPress 不仅仅是一件事,不仅仅是一块软件。它分为两个版本,服务于不同的需求,并且对开发者的要求与您不同:
-
WordPress.org (wordpress.org): 您可以在这里找到免费使用的开源软件。要使用它,您需要有一个服务器来运行它,并且很可能会需要购买自己的域名。它提供了对所有插件和主题的完全定制访问,包括免费和付费的。这个版本非常适合那些想要完全控制他们网站的功能和设计,并且不介意管理自己的服务器和上面软件的人。
-
WordPress.com (wordpress.com): 这是一个商业服务,提供使用 WordPress 创建的网站托管服务。使用这个版本,您不需要自己的服务器,也不需要自己安装 WordPress;所有这些都将由他们为您管理。您只需购买一个计划,就可以立即开始构建您的网站,但除此之外,它还提供了与自托管相同的所有功能。
那是功能角度,但 WordPress 建立在什么技术上呢?让我们简要谈谈这一点。
理解技术视角
从技术角度来看,WordPress 是一个强大且灵活的内容管理系统,具有以下技术特性:
-
PHP: WordPress 是用 PHP 编写的。它处理服务器端执行,并提供 WordPress 提供的所有功能,包括在实时网站上无缝更新 WordPress 本身的能力,而无需停机。
-
MySQL: MySQL 是最受欢迎的开源关系型数据库之一,WordPress 使用 MySQL 来存储实际的用户生成内容、设置和用户数据。
-
插件: 插件架构允许在不更新到全新版本 WordPress 的情况下扩展功能。通过它,您可以模块化地添加自定义功能和功能,而无需更改 WordPress 的核心代码。
-
主题: WordPress 的表现层由主题控制,这些主题是 PHP、HTML、CSS 和 JavaScript 文件的集合,它们协同工作以定义网站的视觉布局和样式。
-
循环: 这是 WordPress 的一个基本部分,负责处理每个帖子并显示其内容,允许动态内容。例如,显示从数据库中拉取并通过主题显示的新博客帖子就是循环的主要内容。循环的基本结构如下:
<?php if (have_posts()) : while (have_posts()) : the_post(); // Display post content endwhile;endif;?>have_posts()函数将确定是否有要显示的帖子。如果有,while循环开始。在该循环中,对于每个帖子,调用the_post()函数以获取帖子的数据。然后,使用主题的模板文件与帖子的数据结合使用。这些数据被插入到模板中特定的标签位置,结果是为每个帖子生成要显示的 HTML。 -
REST API: WordPress 提供了一个强大的 REST API,为开发者提供了一个接口,可以通过发送和接收 JSON 对象远程与 WordPress 站点交互,使 WordPress 成为无头 CMS 选项。这意味着您可以在 WordPress 中存储和管理内容,但如果需要,也可以通过自定义代码来显示它。
-
动作: 这些是在执行过程中或在特定事件发生时 WordPress 核心执行的操作(基本上是代码片段)。插件可以指定在这些点执行的动作,提供在特定事件发生时增强 WordPress 的方法,例如,当用户发表评论时。
-
过滤器: 这些是在数据保存或发送到浏览器之前允许修改数据的代码片段。这对于在不更改核心文件的情况下自定义 WordPress 的功能至关重要。例如,过滤器可以用于自动执行诸如更改内容格式(可能是突出显示关键短语)、在您的内容下方放置相关帖子或截断文本以确保它始终以美观的方式适应页面等操作。
-
安全措施: WordPress 内置了诸如定期更新之类的安全措施。但由于它是一个流行的在线内容平台,因此经常成为攻击的目标。因此,通常通过插件、安全托管和网站管理的最佳实践来加强安全性。例如,一些插件可以监控黑客攻击并提醒您,而其他插件可以强制执行用户的密码策略,以促进更安全的密码。
-
多站点功能: WordPress 允许从单个 WordPress 安装运行多个站点。这允许高效地使用服务器资源:您不需要为每个 WordPress 站点构建单独的服务器,因为一个服务器可以处理多个站点。
-
兼容性和标准: WordPress 始终努力遵循 W3C 设定的网络标准,确保与不同浏览器和设备的兼容性,并为有障碍人士提供可访问性。
从开发者的角度来看,WordPress 提供了用户友好界面和广泛的定制性之间的平衡,使其既适合初学者也适合经验丰富的开发者。但它的真正吸引力不是编写代码。使用所见即所得(WYSIWYG)界面允许开发者和非开发者 alike 快速轻松地拖放内容。不过,了解这一点也很不错,那就是对于更高级的使用案例,有方法你可以以更传统的开发者方式与 WordPress 交互。无论如何,作为一个初出茅庐的网页开发者,我认为你应该对 WordPress 的技术结构有一个大致的了解。
虽然 WordPress 不是镇上唯一的流行 CMS,但让我们现在看看它的另一个主要竞争对手,在街头被称为 Drupal。
了解 Drupal
Drupal 是另一个流行的开源 CMS,以其健壮性和灵活性而闻名。由于其特别适合复杂的大型网站和应用,因此备受青睐。
你可以在图 8.3中看到,当你使用基本默认选项安装 Drupal 时,Drupal 创建的示例网站。3:

图 8.3:由基本安装的 Drupal 创建的默认网站
在某种程度上,Drupal 更是一个以开发者为中心的 CMS,因为设置和使用它需要比 WordPress 更多的技术知识。WordPress 从核心设计就是用户友好的,并且面向非技术用户,而 Drupal 则不是,至少没有达到同样的程度。其中一个关键原因在于,WordPress 提供托管选项,而 Drupal 则没有。如果你想使用 Drupal,你需要在自己的服务器上设置自己的实例,这需要更多的技术知识。
此外,WordPress 是开箱即用的 WYSIWYG,而 Drupal 则不是。Drupal 提供了一个更有限的界面来创建和维护内容,本质上只是一个可以应用一些格式的文本编辑器。使用 Drupal 创建完整的网站需要直接编辑内容,而不是像 WordPress 那样简单地拖放元素。尽管如此,Drupal 可以通过插件扩展,使其在这方面与 WordPress 达到更多或更少的相似性。再次强调,Drupal 更倾向于开发者,虽然你并不是在编写代码,但它比 WordPress 的视觉内容创建方法更接近。
在图 8.4中,你可以通过查看管理控制台中的配置选项卡来开始了解 Drupal 比 WordPress 更技术性的方面,这是众多相对复杂的屏幕之一,拥有许多更多技术导向的选项。

图 8.4:Drupal 管理控制台的配置选项卡
让我们接下来看看 Drupal 的一些关键特性。
关键特性
考虑 Drupal 的一些关键点包括以下内容:
-
PHP:与 WordPress 一样,Drupal 是用 PHP 编写的,这使得它灵活且可扩展。
-
数据库集成:虽然 WordPress 仅支持 MySQL,但 Drupal 支持多种数据库,包括 MySQL、PostgreSQL、SQLite 和 MariaDB,用于存储内容和设置。它是通过数据库抽象层实现的,这意味着一个 API,Drupal 代码就是针对这个 API 编写的,然后它将 Drupal 函数调用转换为特定数据库的调用。
-
模块化和可扩展性:Drupal 不是使用插件来扩展其功能,而是使用模块。名称不同,但基本理念相同。社区贡献了数千个模块,可用于各种功能,包括 WordPress 提供的一切。
-
主题:与 WordPress 类似,Drupal 使用主题来定义网站的样式和感觉。Drupal 的主题可以通过 HTML、CSS 和 JavaScript 编码进行高度定制。
-
内容结构:以其强大的内容结构能力而闻名,Drupal 允许创建具有自定义属性和字段的多个内容类型。在 Drupal 的世界里,不仅仅是网页;你需要任何类型的内容,无论是视频、图片、PDF 文件、Excel 电子表格等等。
-
访问控制:Drupal 提供高级用户角色和权限系统,为不同类型的用户提供细粒度的访问控制。由于 Drupal 以其强大的安全功能而闻名,它通常被选用于政府和企业网站。它还提供深入的安全报告和定期更新。
-
性能和可扩展性:Drupal 从一开始就是为了性能和可扩展性而设计的,能够高效地处理大量数据和流量。它具有内置的缓存功能,并且可以与外部缓存系统集成,适用于高流量网站。
-
社区和生态系统:Drupal 有一个活跃的开发者、设计师和内容经理社区,通过论坛、文档和活动提供广泛的支持。
-
以 API 为第一的方法:Drupal 支持无头或解耦架构,这意味着前端(用户看到的部分)与后端(内容存储的地方)是分离的,这允许在内容交付方面有更大的灵活性。
-
多语言功能:Drupal 自带广泛的多语言功能,允许轻松进行内容翻译和网站本地化。
-
版本更新:与 WordPress 可以无缝更新不同,Drupal 的版本更新是重大的,通常需要相当大的努力才能从一个主要版本迁移到另一个。
Drupal 特别适合复杂、数据驱动的网站以及需要大量内容组织的网站,如社区平台、在线商店和公司网站。其陡峭的学习曲线对初学者来说可能具有挑战性,但为经验丰富的开发者提供了强大的解决方案。
由于更注重开发者导向,你可能想象从技术角度来看,关于 Drupal 还有很多可以说的,而且你是对的,这正是我们接下来要讨论的。
理解技术视角
从技术角度来看,Drupal 因其强大的架构、先进的定制能力和对可扩展性和安全性的强烈关注而脱颖而出。以下是一些关键技术点:
-
模块化:Drupal 的架构高度模块化,允许进行广泛的定制。核心功能可以通过自定义模块进行扩展或覆盖。
-
钩子系统:Drupal 采用钩子系统,允许模块与核心交互,修改或扩展其行为,而无需修改核心文件。
-
性能:如前所述,Drupal 具有广泛的缓存机制(如页面缓存和块缓存)来提高性能。它还支持与外部缓存系统(如 Varnish 和 Memcached)的强大集成,以进一步提高性能。
-
主题化:Drupal 使用 Twig 模板引擎,这是一个灵活、安全且快速的模板引擎,用于实现其主题。Drupal 的主题天生具有响应性,确保网站在各种设备上都能良好运行。
-
以 API 为首要方法:Drupal 支持 RESTful API,允许实现前端与后端分离的无头或解耦架构。实际上,它就是这样内部实现的,这意味着 Drupal 在概念上就是这样分离的。
-
安全性:Drupal 提供了一套复杂的基于角色的访问控制(RBAC)系统,允许详细的权限和用户管理,通常被认为更健壮且更适合大型企业的需求。
-
质量保证:Drupal 包括一个全面的测试框架,支持单元测试、功能测试和集成测试,并强制执行严格的编码标准,有助于提高代码的整体质量和可维护性。
-
分类系统:Drupal 拥有一个高度复杂的分类系统,能够实现内容的复杂分类和标签化。
-
本地化支持:Drupal 在核心中集成了强大的多语言和本地化功能,使其适合国际和多语言网站。
-
配置管理:Drupal 拥有一个复杂的配置管理系统。它允许开发者轻松管理和将配置更改从开发环境转移到生产环境,这对于复杂和大规模网站开发是一个关键特性。在某种程度上,它是内置的 DevOps!
-
视图模块:Drupal 最强大的功能之一是视图模块,它允许用户创建、管理和显示内容列表。与 WordPress 通过插件实现类似功能不同,视图模块在 Drupal 的最新版本中已集成到核心中。
-
块系统:Drupal 的块系统是另一个关键特性。它允许在页面上特定区域块中放置内容和其它元素,提供了广泛的布局定制。
-
APIs:Drupal 不仅作为一个 CMS,还作为一个框架,允许开发者在它之上构建自定义应用程序。它提供了一系列 API,用于不同的功能,如数据库管理、主题和用户管理。
总结来说,Drupal 在某种程度上是一个开发者的 CMS。而 WordPress 虽然可能仍需要一些技术知识,但更多地关注最终用户。
但最近,一个新的小家伙出现了,它将 WordPress 的愿景提升到了一个新的水平,那就是无代码解决方案的领域,这是我们通往网络开发者专业知识的下一站!
完全放弃编码——“无代码”解决方案
虽然像 WordPress 这样的 CMS(内容管理系统)——以及程度较低的 Drupal——可以非常用户友好,WordPress 的情况下甚至可以由非技术用户使用,但还有另一类工具将这一概念推进了一大步,进入了无代码解决方案的领域。WordPress 确实非常接近无代码解决方案,有些人甚至可能争论说它实际上就是一个无代码解决方案,尤其是 WordPress.com 这一部分。
但真正的无代码解决方案真正提升了这一范式,并成为任何人都可以使用,几乎不需要任何技术知识的工具。
无代码解决方案是允许用户在不需编程技能的情况下构建和管理应用程序和网站的工具。这些解决方案旨在易于使用,通常使用拖放界面和预构建模板,并且通常向非技术用户提供一个视觉开发环境。
这些解决方案使网络应用和网站开发民主化,使商业专业人士、企业家和爱好者能够创建数字内容。虽然你当然可以用它们创建强大的网络应用,但更常见的是用它们构建静态网站。这很大程度上归因于它们通常采用的视觉开发范式:在视觉构建工具中表达静态内容比动态交互要容易得多(尽管并非不可能,要清楚这一点)。
无代码解决方案在小型企业、初创公司和大型组织中的非技术用户中特别受欢迎。它们可以用来构建网站、移动应用和内部业务应用程序,甚至可以用来自动化业务工作流程。
需要注意的是,无代码解决方案通常不是你自己安装的东西,它们本身也不是软件本身。几乎总是,它们是你可以访问的网站,提供你所需的所有功能集中在一个地方。你不需要担心服务器、域名,当然,也不需要担心编程或其他任何事情,因为这些解决方案是全面的,包括托管。你将在那里建立你的网站,点击一下按钮,它就会立即在互联网上可用。
这些无代码解决方案的关键方面包括以下内容:
-
用户界面(UI): 无代码解决方案通常具有图形用户界面(GUI),允许用户通过视觉操作元素来构建应用程序和网站。这可能包括将文本框、按钮和图像等组件拖放到画布上。
-
定制: 虽然无代码解决方案提供的定制选项比手动编码少,但许多这些解决方案提供了一系列模板和模块,可以根据特定需求进行定制。用户通常可以自定义其结果的外观和感觉,再次以视觉驱动的方式,无需编码。
-
集成: 许多无代码解决方案提供一键式与其他软件和服务的集成。这使用户能够将他们的无代码解决方案与现有的工具和系统连接起来,例如数据库、CRM 系统或电子邮件服务器。
-
数据处理和分析: 许多无代码解决方案提供内置的工具用于数据管理、分析和报告。用户可以创建表单、管理数据库,并可视化数据,而无需编码。这个特性对于需要收集有关其网站访问情况的见解的企业特别有用,或者对于处理数据,但缺乏复杂数据处理技术专长的企业。
-
协作: 无代码解决方案通常强调协作,允许多个用户同时在一个项目上工作。这可能包括版本控制、基于角色的访问和实时编辑,但比 GitHub 等工具提供更用户友好的模型。
-
响应式设计: 大多数无代码解决方案自动确保生成的结果是响应式的,这意味着它们将适应不同的屏幕尺寸,从桌面到智能手机。在当今的多设备环境中,这一点至关重要。
-
市场: 一些无代码解决方案提供市场,用户可以找到额外的插件或扩展,这些插件或扩展是由解决方案提供商或第三方开发者创建的。这些附加组件可以扩展基础平台的功能。
-
社区和支持: 无代码解决方案的兴起导致了充满活力的用户社区的诞生,人们在这里分享技巧、模板和最佳实践。大多数解决方案还提供强大的客户支持和教育资源,以帮助用户最大化解决方案的潜力。
-
跨平台:无代码解决方案可以从任何设备使用,无论硬件规格、操作系统或其他考虑因素。这使得它们对更多人来说更容易获取。
-
安全和合规性:任何好的无代码解决方案都会考虑到安全性,并遵守行业标准合规法规。最终,如何实施这些解决方案将由用户决定,但应该提供确保安全性的工具。
-
经济影响:通过使更多人能够构建网站和 Web 应用程序,无代码解决方案正在降低科技世界的进入门槛,可能导致更多创新和经济增长,尤其是在技术采用之前受限的行业。
要开始了解这种解决方案的样子,请看看*图 8.5**。这是 GoDaddy 的设计师界面,它是一个流行的无代码解决方案。在其中,你可以看到只需点击一个元素,就在右侧提供了一个编辑器,你可以轻松地更改所有 sorts of 参数(这是为我从一个在线蜡烛店起始模板生成的网站)。

图 8.5:GoDaddy 设计师界面在实际操作中的效果(无论如何,在印刷页面上能有的“效果”也就这样了!)
无代码解决方案的主要优势是速度和效率。由于不需要定制编程,它们通常创建得更快。这也使它们更具成本效益,因为不需要(通常是昂贵的)网络开发者。所有这些都有助于赋予非开发者权力,让那些没有编码技能的人参与到开发过程中。
以 GoDaddy 的网页设计师为例,它允许采用完全基于视觉的方法进行设计,这是一种用户可以拖放组件并在页面上进行编辑的范式,可以根据需要编辑每个组件的详细信息,而无需编写代码。
当然,当涉及到无代码解决方案时,并不全是玫瑰和彩虹。一方面,随着组织的需求增长和演变,无代码解决方案的限制可能会变得明显,因为它们可能在某个时候无法提供你所需要的灵活性。虽然它们在快速部署和验证想法方面非常出色,但过渡到更可定制的基于代码的解决方案可能对于扩展和复杂定制是必要的。话虽如此,大多数好的无代码解决方案会随着你一起成长一段时间,并为你的在线旅程提供肥沃的土壤。
另一方面,无代码解决方案使你依赖于该解决方案的提供商。虽然当你自己托管硬件时并非如此,但无代码解决方案确实倾向于更深入地绑定你,如果需要的话,可能很难离开它们。当然,这对许多人来说可能不是问题,但至少这是一个需要意识到的潜在问题。
无代码解决方案代表了技术领域的一次重大转变,使得应用程序和网站开发更加易于访问,并通过允许非编码人员产生以前需要定制开发的结果,从而改变了科技行业的传统动态。
现在让我们谈谈无代码运动中的一些最大名字。
介绍一些流行的无代码解决方案
在网络开发领域,一些流行的无代码解决方案因其易用性和强大的功能而获得了广泛的认可。以下是一些值得注意的例子:
-
Wix:Wix 的方法与 GoDaddy 非常相似,再次提供了一套无需编码的视觉设计工具。它可能比 GoDaddy 略微不太用户友好,但如果有的话,这种差异并不剧烈。
-
Squarespace:因其简洁、专业设计的模板而受到青睐,非常适合用于作品集、博客和企业网站。它提供强大的设计选项和电子商务功能。
-
Weebly:一款用户友好的解决方案,具有拖放工具,非常适合小型企业和在线商店。它集成了电子商务功能并提供各种设计模板。
-
Shopify:主要专注于电子商务,Shopify 允许用户无需编码即可创建在线商店。它提供广泛的电子商务工具、模板,并与众多支付网关和其他服务集成。
-
Carrd:专注于单页网站,Carrd 是创建个人资料、着陆页和简单作品集的简单、轻量级工具。
-
Bubble:虽然它比网站更专注于网络应用程序,但 Bubble 是一款强大的无代码工具,允许通过可视化编程界面开发复杂、数据驱动的网络应用程序。
这些解决方案因其易用性、多样化的设计选项以及满足各种需求的能力而脱颖而出。
但如果连拖放和可视化构建器对某人来说都太多,那该怎么办?好吧,有一种东西像其他东西一样突然出现在场景中,可能提供了一个答案,那就是人工智能。
利用人工智能
人工智能并不新鲜。它自 1956 年以来以各种形式存在,但 2023 年是其一个分水岭,我们看到了该领域创新的突然爆发。
现在许多无代码解决方案都在增加人工智能功能。GoDaddy 和 Wix 都集成了人工设计智能,或称为ADI。这是一种通过自动根据用户对一系列问题的回答创建网站来简化设计过程的 AI 形式。这种方法特别适合那些想要拥有专业外观的网站,而不需要投入太多时间或技术技能的人。你只需回答一系列问题,例如你的网站是关于什么的,它位于哪里,以及任何关于它的关键点,这些平台就可以根据这些回答创建网站,虽然这不是一个完整且现成的结果,但可以作为你设计工作的良好起点。
当然,这样的事情并非没有局限性。依赖 ADI 意味着网站可能缺乏独特性,因为 AI 的创造力还远不如人类,用户可能会发现设计相对有限,与竞争对手相比,因为任何 AI 都必须有护栏,即它能够产生的限制。虽然能够通过简单的描述生成网站非常强大(我甚至可以说很酷!),但它并不提供太多的创意自由,并且缺乏某些高级功能,例如有效地集成第三方应用的能力。
这款由人工智能驱动的无代码解决方案最适合那些需要快速搭建简单、整洁、专业的网站,且不打算深入研究定制化或高级功能的企业或个人。它特别用户友好且价格合理,提供多种定价方案,包括免费选项,使其成为小型企业或初创企业家的实用选择。
但这只是更通用概念的一个具体例子,这个概念真正席卷了全球,你作为网页开发者也可以以其他方式使用它,这个概念被称为生成式人工智能。让我们更深入地探讨一下。
生成式人工智能
如果你大部分时间都在 2023 年躲在岩石下,那么你可能没有听说过生成式人工智能。当然,AI 本身并不新,它以各种形式存在,自 1956 年以来就有,但 2023 年是一个转折点,当生成式 AI 的标志性产品 ChatGPT 出现时。
生成式 AI是一种能够创建新内容的 AI 类型,无论是文本、图像、编程代码、音乐,甚至是整个网站。它是通过首先摄入大量数据,并在某种程度上从中学到东西来做到这一点的。这些数据可以包括书籍、现有的音乐、GitHub 上的源代码以及各种类型的文档。AI 可以理解这些数据中的模式和结构,然后使用它来生成新的(可以说是)原创输出。例如,像 ChatGPT 这样的生成式 AI 可以撰写新闻文章,创作音乐作品,或创建一个以前从未存在过的图像。正如你可能已经注意到的,生成式 AI 有潜力彻底改变网站——以及许多其他事物——的建设方式,提供新的效率、定制和创造性的水平。
关于这种 AI 是如何工作的,不涉及所有复杂的数学和概念,主要组件是摄入我提到的所有数据的结果:被称为模型的东西。它是以神经网络形式对所有数据的复杂表示。神经网络本质上是一种专门构建的算法,能够识别模式,这与人类大脑的工作方式相似。当你向 ChatGPT 提问时,它使用复杂的统计方法遍历这个模型,找到与它在遍历的每一步中统计上认为逻辑上可能跟随的模式相匹配的模式。最后,它们输出模型统计上认为最有可能回答所提请求的东西。
有许多不同的模型,它们是从许多不同的数据集中训练出来的。GPT 是 ChatGPT 的基础(实际上是一个 UI 和该模型的组合),专门用于文本内容。另一个模型 DALL-E 是在大量图像上训练的,并能够从中生成新的图像。一个模型训练的数据越多——假设数据质量足够高——它在满足请求时的生产力和准确性就越高。
在图 8.6.6 中,你可以看到 ChatGPT(chat.openai.com)的界面看起来是什么样子,我在这里进行了一些关于编程的讨论,以便给你一个这样的互动可能是什么样的概念。

图 8.6:与 ChatGPT 的对话,当今最杰出的生成式 AI 解决方案
在左侧,你可以看到我与 ChatGPT 的聊天历史(不要介意其中一些愚蠢的对话;这东西既有趣又实用!)。在右侧是当前的对话,你可以看到它能够生成一些示例代码来回答我的问题。聊天持续了一段时间,我提出了澄清问题,并要求使用特定的库来举例。这次交流对我正在工作的项目非常有帮助。
但 ChatGPT 和其他类似生成式 AI 模型可以做得更多。各种模型可以用于特定目的,并且这些工具可以集成到其他产品中,扩展其功能。以下是一些有趣的应用列表,你可以在当今世界找到它们在网站开发中的应用:
-
原型和线框图:生成式 AI 工具可以快速生成网站原型和线框图,使设计师能够比其他方式更快地可视化和完善他们的想法。
-
自动设计创建:生成式 AI 可以根据用户偏好或预定义标准生成网站布局和设计——甚至可以生成它之前生成的原型和线框图!一些大型无代码解决方案开始整合这些功能,效果令人难以置信。
-
文案生成:生成式 AI 可以用于为网站创建文本内容。例如,它可以生成针对网站受众的产品描述、博客文章或营销文案,从而节省人类的时间和精力(坦白地说,这基本上就是所有这些的通用主题)。
-
视觉资产创建:生成式 AI 可以生成符合网站主题和需求的视觉资产,如图片、图标甚至动画。
-
代码生成:生成式 AI 可以生成构建网站所需的实际代码,特别是对于标准组件。这可以显著加快开发过程,尽管可能需要人类开发者的监督和细化,这是任何生成式 AI 模型的一个常见主题。我将在下一节中对此进行更多介绍。
-
SEO 优化:AI 可以分析搜索引擎算法,并提出或实施 SEO 策略以实现更好的排名,包括关键词优化、元标签和内容结构。例如,它可以分析你希望网站排名更高的搜索结果中的顶级结果,然后查看可以对你的网站进行哪些更改以实现该目标。
-
个性化:AI 可以在实时为个别访客个性化网站体验。它可以根据用户行为、偏好和互动改变布局、内容和推荐。这与前面的点不同,因为在这里我们不是在谈论使用 AI 来构建网站;相反,我们是在谈论代表用户运行的 AI,因为他们实际上正在使用网站。
-
UI 和 UX 优化:与个性化一样,AI 可以在网站上线后分析用户交互数据,以提出改进 UI/UX 设计的建议。这可能以在从使用你网站的用户收集的数据上训练模型的形式出现,然后你可以从模型中提取关于改进事项的建议。
将生成式 AI 和其他形式的先进 AI 系统集成到 Web 开发工具和解决方案中仍在不断发展。我们仍在探索如何使用这些新技术和工具。尽管如此,随着 AI 技术的进步,它似乎很可能会成为 Web 开发过程的一个基本组成部分,提供更复杂和直观的网站构建方式。我个人并不认为它会取代 Web 开发者,但 Web 开发者肯定需要理解和适应这些工具,将它们整合到他们的工具箱中。在我看来,那些不这样做的人可能会被落下,所以,即使只是为了自私的“我想有工作能力”的视角,了解 AI 的进步并跟上它们如何被用来使您成为一个更有效的 Web 开发者也是非常重要的。
我想讨论的这个主题的最后一个领域是您如何可以使用生成式 AI 工具来编写代码,其中一个特别的方法是:GitHub Copilot。
GitHub Copilot
虽然使用生成式 AI 生成整个网站或网站内容,或将其用作帮助构建和增强网站的工具的想法是上一节的重点,但在这里我想谈谈使用它来编写代码的想法:换句话说,继续作为 Web 开发者,但使用工具使这项工作更容易、更高效。虽然 ChatGPT 可以用这种方式使用,因为它非常擅长为您生成代码或帮助您调试代码,但当你使用集成了 Microsoft 的 GitHub Copilot 的优质 IDE 时,这些能力会提升到另一个层次。
Copilot实际上是由 GPT 模型驱动的,这个模型与 ChatGPT 使用的模型相同(或者至少是几个可用模型中的一个)。但模型基于的训练数据是 GitHub 上的所有内容(好吧,至少是至少没有明确禁止用于此目的的内容)。
它允许两个关键功能:
-
您写出的代码能够自动补全的能力
-
从注释生成代码的能力
您可以在以下链接中找到如何在您特定的 IDE(它支持大多数 IDE)上安装 Copilot 的信息:docs.github.com/en/copilot。然后,当您输入代码时,Copilot 可以提供补全建议。
让我告诉你,它有时会变得非常出色!因为它了解您项目中的代码,以及 GitHub 上托管的所有仓库,它能够提供在您编写的代码中具有意义的建议——上下文相关的建议,而不仅仅是来自他人代码的随机胡言乱语。
图 8.7 展示了 Copilot 在 IntelliJ IDEA 中的自动完成功能。在这里,你可以看到它为我建议了一行代码,这行代码是基于我已经写的内容构建的。由于屏幕上存在而打印页上不存在的颜色,所以这在打印页上看起来有点难看,但所有在突出显示的return语句之后的代码都是由 Copilot 建议的。我现在只需要按下Tab键接受建议,代码就会被插入到我的编辑器中。

图 8.7:使用 Copilot 的注释转代码功能的示例
微软称 Copilot 为“你的 AI 编程伙伴”。结对编程是一种两个开发者坐在一起一起编码的想法。一个人可能控制键盘,或者它可能在他们之间传递,你们以对的方式编写代码,两个大脑一起工作,希望和谐!如果你看过电影《环太平洋》,它就像允许两个飞行员控制巨型机器人 Jaegers(用来与哥斯拉(巨型怪物)战斗)的 Drift 一样,共享心理压力。有了 Copilot,你的伙伴在某种程度上是所有在 GitHub 上发布代码的开发者!
除了那些自动完成建议之外,Copilot 还有另一个绝招。如果你用普通的语言写一些描述函数的注释,Copilot 就可以为你生成那个函数!当你看到它在实际操作中的表现时,这真的很令人惊讶。
图 8.8 展示了 Copilot 在 IntelliJ IDEA 中的使用情况。在这里,你可以看到它根据我的注释为我建议了一个函数的实现,用于在数组中搜索对象。我所做的就是输入注释,按Enter键,然后等待一会儿,Copilot 生成你看到的代码。我现在只需要按下Tab键接受建议,代码就会被插入到我的编辑器中。

图 8.8:使用 Copilot 的注释转代码功能的示例
到写作的时候,第三个功能——聊天——正在变得普遍可用。这把 ChatGPT 的机制直接集成到了 Copilot 中。现在,你可以与 Copilot 就你遇到的某些棘手的编程问题进行来回讨论,或者请求关于某事的文档,生成代码或测试用例,帮助你进行调试。虽然 ChatGPT 也能做这些,但将其集成到你的 IDE 中,并且能够立即了解你正在做什么,这是一个变革性的变化。然而,与 ChatGPT 不同,Copilot 中的聊天只能回答关于编码的问题;它设计上不像 ChatGPT 那样通用。
好消息是:你可以免费试用 Copilot!微软提供了一个相当慷慨的免费试用期,之后 Copilot 将收取象征性的费用(截至写作时,个人用户每月 10 美元)。我个人觉得这完全值得这笔开销。
现在,这一切听起来可能都很不错,但关于 Copilot,有一个很大的警告,实际上对于任何生成式 AI 都是一样的:你不能盲目地信任它们!生成式 AI 模型是出了名的会“产生幻觉”,也就是说,它们会编造一些基本上是胡言乱语的东西。当然,我们实际上是在要求 AI 编造东西,但我们期望这些内容是有效的,但它们并不总是这样。同样,这些模型产生的答案有时也是事实错误的。它们有时只是弄错了。通常,这是因为训练数据有缺陷,有时是因为支撑它们的复杂数学导致错误的答案。
查看图 8.9 了解一个例子。当我在工作中试图解决一个问题的时候,我询问了有关 JavaScript 日期的问题,而它给出的答案并不正确。尽管我指出了这一点,AI 也进行了纠正,但如果我没有一些基础的知识,我可能就不会注意到这一点,我可能会走上错误的道路,浪费时间和精力。所以,你必须留意这些事情,而不仅仅 100%地依赖它们,这正是我要表达的观点。

图 8.9:ChatGPT 出错
因此,你不能将它们的答案视为绝对真理;你必须自己思考和验证。这意味着生成式 AI 要求你对与它讨论的主题已经有一些了解,这意味着像 Copilot 这样的工具——它专门用于编写代码——对于不懂得如何编码的人来说可能不会那么有帮助。如果你看到一些你认为不正确的东西,有趣的是你可以挑战 AI,这时它通常会纠正自己,就像你在图 8.9 中看到的那样。
无论是对是错,能够与这些事物进行持续对话的能力,正是你开始看到最大好处的时候。你可以提出一个问题,得到一个答案,然后要求对那个答案进行扩展。你可能会在答案中看到一些你以前从未听说过的内容,并且能够进一步证明。作为一个个人轶事,这曾经发生在我身上,当时我正在询问如何用 Java 做某事。我坦白地说,我不记得我试图解决的问题是什么,但重点是 ChatGPT(在我开始使用 Copilot 之前我使用的工具)提供了一个解决方案——这个解决方案效果很好——但使用了我在以前从未见过的语言特性。我能够就那个特性提出后续问题,并得到了很好的解释。然后我能够提出另一个后续问题,要求一个例子,并得到了它。所有这些都让我相信,我比如果只是谷歌搜索并阅读一些文章,对那个特性的理解要好得多。
你不应该低估——毫无羞耻地——一个接一个地问问题,即使你认为其中一些问题是“愚蠢”的。尤其是在你作为网络开发者的早期旅程中,你会有很多问题,这些问题可能很难通过像谷歌这样的搜索引擎得到答案,而且你可能不好意思向别人询问,即使你有可以询问的人。好吧,首先,你真的不应该感到尴尬,因为我们都是从不知道一切的地方开始的,提问是学习的途径!但我理解:我们都是人,有时我们觉得自己在问愚蠢的问题。
好吧,ChatGPT 或 Copilot 永远不会评判你!你可以不断地问它你需要问的任何问题,不断地要求它提供不同的例子或不同的解释,它会一直尝试,直到你需要它这样做。你可以用它随着时间的推移建立你的知识,不断地走偏锋,而它永远不会对你感到厌倦或烦恼。这是我们作为一个社会以前从未拥有的能力,它真的可以具有变革性。
因此,我强烈建议你开始使用这些生成式 AI 工具,无论是 ChatGPT 还是 Copilot,或者无论这些话被写下来之后出现什么其他工具。正如我所说,不要完全信任它们,因为它们确实会出错,但根据我的经验,它们做得足够好,以至于好处远远超过了任何负面影响。
虽然有些人担心这种新型 AI 工具和无代码解决方案最终可能会夺走我们的工作,但至少对我来说,相反的情况可能会发生:这些工具将成为网络开发者必须接受并学会有效使用的必要工具。
嘿,如果他们最终变成了天网并派出终结者追捕我们所有人,至少我们在那之前会建成一些很棒的网站,对吧?
检查路线图
当我们结束这本书的技术部分时,你发现了一些更多的事项。在 图 8.10 中,你可以看到更新后的网络开发者路线图:

图 8.10:包含更多填充框的路标
通过本章,你发现了 CRM(这完成了路线图中的 前端 部分!),无代码解决方案 和 生成式 AI。这可能看起来并不多,但嘿,这些都是相当酷的话题,不是吗?
摘要
在本章中,我们讨论了一些允许你无需编写代码或至少最小化编写代码的工具体验。你看到了如何使用 WordPress 和 Drupal 等工具,即使是非技术用户也能至少在一定程度上创建网络内容。你还看到了来自 GoDaddy 或 Wix 等公司的无代码服务如何完全消除网络开发者的需求……尽管你也看到了这些工具如何让你成为一个更有效的网络开发者,从而(我相信!)逆转了这一观念。
在下一章中,这本书的最后一部分即将开始,我们将完全放下技术内容,专注于其他问题,比如职业发展、技能增长以及如何找到你的第一份网络开发者工作。毕竟,那才是最终目标,我们将用几章内容一起探讨如何实现这一目标!
第三部分:获得和保持工作以及职业发展
在本部分的最后,我们将主要脱离特定技术的知识,开始探讨在网页开发领域找到工作所需具备的条件。我们将讨论什么是 FAANG 公司,为什么你可能或可能不想考虑它们,以及在这个领域你可能会期望什么样的薪资。
我们将深入探讨如何构建简历和作品集,以及典型的网络开发者面试可能包含的内容。我们将讨论如何准备面试,你可能会遇到的一些常见问题以及如何应对它们。
我们还将讨论工作与生活的平衡,以及它在保持理智的同时在网页开发领域成功发展的关键重要性!
本部分包括以下章节:
-
第九章,找到你的第一份工作
-
第十章,作为网络开发者发现高质量工作的关键
-
第十一章,回顾软技能(它们让你难以否认)
-
第十二章,职业发展
第九章:找到你的第一份工作
好吧,所以,你已经花费了很多时间和精力学习网页开发的技能方面。你已经编写了一些代码,现在你认为你已经准备好下一步:找到你的第一份工作,开始你的网页开发职业生涯。但你怎么实际上做到这一点呢?!
我年纪足够大,记得过去找工作意味着查看实体报纸上的招聘广告(唉,我讨厌这么说!),但如今,人们很少通过这种方式找到工作。即使那样,一旦找到工作机会,申请、面试以及最终获得工作的过程通常不仅仅是一次与经理的简单交谈,尤其是在网页开发领域。你可能需要让不止一个人印象深刻,才能成为一名受雇的网页开发者。
我在这个领域也足够老,经验也足够丰富,我自己也经历过几次面试,也许更重要的是,我主持了数百次面试。这意味着我了解坐在桌子的两边是什么感觉——我知道作为候选人有多么可怕,也知道进行一次好的面试有多么困难(说真的,它看起来只是提问,但做得好且公平似乎比看起来要难得多)。我将把所有这些知识和经验融入到本章中,希望对你有所帮助!
考虑到这一点,本章将为您提供寻找网页开发工作机会所需的知识,如何申请这些职位,面试可能是什么样的,如何有效地准备和应对它们,以及一些我认为重要的其他主题,例如工作与生活的平衡。
本章将涵盖以下主题:
-
理解 FAANG 与非 FAANG 的分歧
-
利用网络成为网页开发者!
-
精炼你的简历,使其光彩照人
-
拥有仓库、作品集和样品展示
-
理解典型的网页开发者面试
-
应对常见的面试问题
-
记住,工作并非一切
理解 FAANG 与非 FAANG 的分歧
我在第一章中简要提到了 FAANG,但让我们更深入地探讨,并涉及一些相关观点。
如果你现在上网查看 Reddit 上几个最受欢迎的 subreddit,或者在其他许多人们聚集讨论如何获得 IT 领域工作的网站上,你很快就会遇到一个常见的术语,即这个领域只有一个目标和一种进入方式,而这个术语就是 FAANG。它是什么意思,为什么人们如此关注它?
FAANG代表Facebook, Amazon, Apple, Netflix, and Google。它最初在股市圈子中用来指代最大、最知名的技术股票。如今,你可能会看到几个其他的缩写,例如FAAMG(Facebook, Amazon, Apple, Microsoft, and Google)、MAMAA(Meta, Apple, Microsoft, Amazon, and Alphabet)和MATANA(Microsoft, Apple, Tesla, Alphabet, Nvidia,和 Amazon)。
我们让股票分析师去确定他们认为哪个缩写对他们所在的领域最有意义,但对于开发者来说,FAANG 通常被用来指代最大的、薪酬最高的、最适合职业发展的公司。这更多是关于从技术就业的角度来看,哪些公司受到好评。即使它们不是五家列出的公司之一,一些公司仍然可以被认为是“FAANG 公司”。例如,Airbnb、三星、甲骨文、Adobe、思科、高通、eBay 和 Uber 可能被一些人视为 FAANG 公司。
在线,你会看到很多关于加入 FAANG 公司的讨论——无论这个缩写代表的是哪些具体公司——讨论着哪些公司将来会在你的简历上看起来最好。或者讨论着那些最优秀和最聪明的人倾向于工作的公司。或者讨论着那些你将拼尽全力工作但会在财务和声望上得到回报的公司,因为其他开发者几乎会对你抱有敬畏之情。或者以上所有情况!
为了明确:是的,这类公司通常支付得很好。是的,这些公司在简历上看起来也很不错。是的,在那里工作将会具有挑战性但也很值得,因为你将和许多顶尖工程师以及最新的技术一起工作,你将参与一些可能会影响很多人生活的激动人心的产品。但它们并不是对每个人都最好的选择,而且很可能对刚开始的人来说也不是最佳选择。然而,你如何为自己做出这个决定呢?让我们来谈谈这个问题。
调整你的 FAANG 期望
我之前所说的都是很好的内容,很容易理解为什么这么多开发者对在这样公司工作的机会感到兴奋。但我看到很多在网上讨论的人在做关于 FAANG 的一个大错误:他们认为这是成名和致富的唯一途径,他们认为没有其他地方可以工作,你可以做令人兴奋的工作。有些人也犯了一个错误,认为仅仅拥有计算机科学学位就足以让你进入这样的公司。
以上都不是真的。其他地方也有很好的机会,而且大多数机会比在 FAANG 公司更容易获得。
坦白说:作为一名初级开发者,你在任何 FAANG 或类似 FAANG 的公司被雇佣的机会相当低。这些公司在寻找的是顶尖人才,更重要的是,那些能够证明自己是顶尖人才的人。你可能现在是最好的开发者,但如果你没有成绩记录,那么在 FAANG 公司被雇佣可能并不是完全不可能,但这绝对不是你应该依赖的事情。即使是有丰富经验和成功记录的人,往往也发现很难在这样的公司找到工作,因为面试过程非常严格(这个问题我将在本章后面更详细地讨论)。
但不要让这打击了你的热情!正如我所说,FAANG 公司之外还有许多很好的机会——这些职位往往更容易获得。虽然在一个 FAANG 公司工作会增强你的简历,但在非 FAANG 公司工作通常会为你提供一个更温和的进入专业 IT 世界的方式。许多公司也在使用最新的技术,所以你几乎有同样多的机会在工作中学习,但压力和期望会小一些,因为你被安置在 FAANG 公司时的情况。
然而,这并不是严格意义上的 FAANG 与非 FAANG 的对立。除了 FAANG 与非 FAANG 之外,还有另一个需要关注的界限,那就是科技公司与非科技公司之间的界限。
比较科技公司与非科技公司
科技公司是那些生产产品——特别是科技产品——并将其销售给客户的公司。苹果公司就是一个很好的例子。他们创造了 iPhone、iPad 和 MacBook 等许多其他小工具,这些都是科技产品。他们的整个焦点就是制造这些产品并开发其中的技术。因此,技术是他们所做事情的核心,因为本质上就是产品。
相比之下,以富国银行为例。他们是一家金融公司,因此支票账户、信用卡和共同基金等都是他们的产品。但是,为了使这些产品成为可能,需要大量的技术,只是这些技术并不是他们直接销售的产品。技术是他们核心产品提供的基础。在这样的公司,你作为开发者可能会创建一个用于对账日账户活动的系统,但这个系统并不是销售给客户的产品——很可能客户甚至都不知道它的存在。富国银行是一家非科技公司,因为虽然技术是他们所做事情的关键组成部分,但那并不是最终产品。
FAANG 公司往往是科技公司(当然,有些人可能会模糊这条界限,但作为一个广泛的概括,这是正确的)。而且,我认为很明显,科技公司对他们的技术人员的要求往往更高,因为这是由他们生产的产品的需求所决定的。
从所有这些中得出的结论是,在寻找你的第一份工作时,我的建议是首先寻找非技术、非 FAANG 职位。我不想给你一个错误的印象,认为这会很容易得到——无论你有什么样的经验,在 IT 行业找到第一份工作都将是一个挑战——但它将比在科技公司,尤其是 FAANG 公司更容易得多。它们将允许你积累经验,并建立你的记录,到那时,你可以更有效地针对科技公司甚至 FAANG 公司,并提高你获得此类职位的成功机会。
当然,这并不是说你应该完全忽视这样的科技公司。绝对不是!如果出现了一个好机会,那么无论如何,都应该抓住它!但你需要设定现实的目标,并根据这些目标调整你的求职策略,非科技公司往往是更好的选择。
到目前为止,你已经了解了什么是 FAANG,以及科技公司与非科技公司之间的区别。现在,让我们谈谈如何寻找工作机会,无论你想要申请哪种类型的公司。
利用网络成为一位网页开发者!
成为一位网页开发者意味着能够使用网络,那么从寻找网页开发者工作开始可能再合适不过了!今天,在网上找到工作可能比其他任何方式都更常见。幸运的是,因为这种情况很常见,所以它已经变得相当容易。首先,有许多专门从事求职的网站。
使用求职网站
网上列出职位空缺的网站并不缺乏,许多网站都会使申请过程变得简单快捷。当然,作为一般规则,你可能想坚持使用最知名的网站。截至本文撰写时,以下是一些网站,不分先后顺序:
-
Indeed (indeed.com):通常,这是最大的网站,覆盖了大多数行业。
-
CareerBuilder (careerbuilder.com):这个网站,像许多这样的网站一样,提供的不只是职位列表,还包括职业建议博客和薪资信息。
-
ZipRecruiter (ziprecruiter.com):ZipRecruiter 对求职者和雇主都很有帮助,拥有许多出色的功能,包括简历数据库,这样雇主就可以自己搜索合适的候选人。
-
Monster (monster.com):Monster 是目前求职网站中的资深人士之一,但它仍然是一个功能强大、易于使用的职位搜索引擎网站。
-
Glassdoor (glassdoor.com):Glassdoor 有些独特,因为它不仅提供职位列表,还有来自现任员工的匿名反馈,这让你可以很好地了解在特定公司工作的可能情况。
-
LinkedIn (linkedin.com):我稍后会具体谈谈 LinkedIn,但可以这么说,它在寻找工作中可以提供很大的帮助,但方式与其他网站略有不同。
-
Dice (dice.com):Dice 是一个仅限美国的科技特定网站,所以虽然它不像其他网站那样普遍适用,但如果你在美国,它对你作为一个潜在的网页开发者来说非常合适。
-
Upwork (upwork.com):这个网站有些不同:如果你想从事自由职业工作,这个网站将帮助你与那些想要雇佣开发者完成单个项目的人匹配。这可以在赚钱和寻找更稳定的工作的同时积累一些经验。
对于第一次尝试进入这个领域的人来说,找到适合的工作可能会很困难。但我给出的建议是不要过于限制自己。一开始,你会有一种自然的倾向,通过入门级职位或指定较低技能水平的职位进行筛选,因为这样似乎更有可能获得这样的职位。但是,虽然你绝对不应该申请每一个列出的职位,尤其是那些列出很多你一无所知的技能的职位,但不要害怕申请那些似乎略超出你能力范围的职位。通常,你可以在面试中给人留下深刻印象(我们稍后会谈到!),这可能会让人们给你一个机会,知道他们可能需要对你进行一些培训。目前,你真正无法触及的职位和那些如果你有机会就能成长起来的职位之间有一条很细的界限。最终,关键是你不要对自己过于低估。
回到这些网站本身,这些网站提供的不仅仅是职位列表,还有很多其他的东西。它们可以帮助你写一份好的简历,给你提供求职建议,让你对公司有更深入的了解,甚至可以根据你(和潜在的雇主)设定的标准主动为你匹配职位。所有这些都有助于你的求职过程。
但这些网站并不是求职的全部。还有另一种方法,是的,这需要你投入更多的时间和精力,但有时可能会引导你走向你可能会错过的路径,那就是直接访问各种公司的网站。
直接访问公司网站
世界上大多数公司都有自己的网站,其中许多公司在那里发布职位列表。重要的是要记住,公司网站上可能会有不在主要求职网站上发布的职位列表,或者这些职位可能不会在那些网站上出现,直到稍后。因此,你可能会在数百或数千人甚至还没有意识到这个机会之前就开始行动,这让你在竞争中占据优势!
因此,首先要做的是找到你可能想要为其工作的公司。这通常意味着一些在线搜索,通常是在你所在地区或合理的通勤距离内的公司。当然,如果你愿意搬迁,那么你的机会就会增加(我的第一份 IT 工作是在美国的一个不同州,需要搬迁)。你将需要进行一些研究,了解公司的情况,并可能从像 Glassdoor 这样的网站上获取一些“内部”信息,以确定你是否可能想要在那里工作。
作为这项研究的一部分,探索他们的网站。你很可能会在那里找到一些职位发布的链接,也许是在他们的人力资源部(HR)部分。如果找不到,你至少可以找到一些可以让你联系公司并询问空缺职位的电话号码或电子邮件地址。但让我们假设你在网上找到了职位发布。
到那时,你将想要仔细审查列出的职位,并决定哪些适合你。再次强调,虽然你不应该申请每一份工作,但也不要过于害羞。通常情况下,职位列表是由那些可能没有完全或正确理解职位要求的人力资源人员撰写的。例如,职位列表可能列出了大量的技术技能(语言和技术),但实际上可能只需要其中的一小部分。也可能确实不需要这些子集——如果你能证明你的学习能力,那么即使没有列出所有技能,你也可能被雇佣。
这个技巧——说实话,这是一个很难实现的技巧——是绕过人力资源部,直接接触到那些最终决定是否向你发出工作邀请的技术人员。这无论你的初始联系是从公司网站开始还是通过求职网站开始都是适用的。如果你能成功做到这一点,那么你就有机会给人留下深刻印象,而这最终也是目标。
到目前为止,我们已经介绍了两种主要的找工作策略。然而,当你试图在这个领域找到第一份工作时,你可能更难利用第三种策略,尽管你不应该完全放弃它,那就是建立人脉。
建立人脉
进入这个领域可能会很有挑战性,但一旦你进入并积累了一些经验,找到下一份工作或晋升到下一个职位就会变得容易一些。明确地说,这永远不会变得“容易”,但确实会变得“容易”一些。使事情变得更容易的不仅仅是经验,还有你在旅途中遇到的人。有句著名的话说,重要的不是你知道什么,而是你认识谁,虽然这句话可能没有表面上看起来那么绝对,但其中确实有真理。
在你从事行业工作的过程中,你会遇到很多人——同事、经理以及你在其他团队中互动的人。如果你与他们建立关系,那么当你寻找另一份工作时,他们将成为你的资产(希望是自愿的,但你永远不知道)。当这些人跳槽到其他公司,而你与他们保持联系时,他们就成了你的“内线”——你可以去找他们了解工作机会,也许是在它们公开之前,或者只是更快地将你的简历放入人力资源系统。
这就是你的人脉,建立这样的人脉被称为建立人脉。好消息是这不需要特殊技能:你只需要与人交谈!我想这可能对某些人来说可能是一种特殊技能,但如果需要的话,任何人都可以学会。
注意,这并不意味着你需要经常参加球赛和孩子们的独奏音乐会!我的意思是,交朋友也很好,但这是一个专业网络,所以我们的意思是保持联系人的信息,确保它是有效的,也许偶尔发送一封“嗨,你好吗?”的问候邮件来维持关系。
一些网站也可以帮助你做这件事,但其中有一个网站脱颖而出——领英。我之前提到过,但值得进一步讨论,因为它更多地是关于建立人脉,而不是找工作——尽管由于前者,它也证明了对后者非常有用!
一旦你加入领英,你将在那里建立你的个人资料,包括教育背景、工作经验和技术技能等,但有用的地方在于你可以让其他人认可你的技能,从而向世界展示你所知道的一切,并且这不仅仅是你在宣称自己擅长这些事情。然后,你可以与认识的人或熟悉的公司建立联系。领英会根据你的个人资料数据和地理位置为你推荐人脉。然后,你将与这些人建立联系——假设他们也愿意——这开始构建一个你与之相连的人脉网络。
你可以做的最有帮助的事情之一就是与招聘人员建立联系。一旦你开始完善你的领英(LinkedIn)个人资料,你很快就会收到来自招聘人员的连接邀请。这些人工作的目的就是帮助人们找到工作。这并不是出于他们的好心:招聘人员通常是由雇主支付报酬来做这件事,有时也可能是员工支付,但他们确实是为了得到报酬。他们会查看你的个人资料(和简历),并努力寻找与你匹配的工作列表。他们有时可以访问尚未公开的工作列表,这使他们成为宝贵的资产。招聘人员——至少是那些好的招聘人员——通常会帮助你为面试做些准备,给你提供针对你面试的特定工作的建议和指导。如果你没有被录用,他们可以从公司那里得到反馈,然后利用这些反馈帮助你下次表现得更好。
当然,就像大多数事情一样,并不是所有的招聘人员都一样,也不是所有都特别出色。所以,你必须依靠你的直觉来处理他们。除非你真的非常舒服,否则不要签署任何东西!你不想发现自己因为签署了独家合同而只与一个招聘人员绑定,例如(幸运的是,这种策略并不常见)。一个好的招聘人员会像一个好的汽车销售员一样:稳固且有帮助,但不会过于强硬。这是一种“当你看到它时你会知道它是坏的”的情况。
你还可以有一种方法开始构建你的网络,即使你是这个领域的初学者,而且这通常也是免费的(在大多数情况下),同时也很有趣且富有教育意义!我所说的是用户组和聚会这个领域。
用户组和聚会
建立网络在理论上很好,但如果你还没有进入这个行业,这可能是一个挑战。你可以克服这种困难的一种方法就是参加用户组和聚会(用户组通常被视为一个较老的术语,而聚会则是它的更常见的现代版本,但它们的意思是相同的)。
在世界各地,你可以找到技术专业人士,或者只是爱好者,他们会有会议来讨论各种技术话题。你通常可以在这样的会议上找到专业开发者——有时甚至是知名人士——他们会就某个特定主题进行演讲。这可能是一篇关于如何更有效地使用虚拟机的文章,或者可能是关于 React 的介绍。当然,这是一种很好的学习方式,但更好的是有机会与人见面。
这些会议通常会伴随着一些披萨和小吃,几乎总是会有一个前后可以自由交流的时段。如果你是一个外向的人,你可以在会议前简单地走到人们面前自我介绍。不过,在演讲之后会更容易一些,因为你可以开始讨论你刚刚听到的内容。
这也是一个窃听可以非常有用的环境!即使你保持沉默,仅仅听到人们的讨论也能提供有价值的见解。你可能会听到一些新技术正在席卷世界。或者你可能会听到一些你从未听说过的公司中人们有趣的工作方式。甚至你可能会听到一些关于某人在他们的公司里找不出人来填补职位的八卦。这可能对正在找工作的人来说是一块金块般的信息!
你可以用来了解聚会的其中一个主要网站,讽刺的是,它叫做 Meetup (meetup.com)。在这里,你可以搜索你感兴趣的主题,按地理位置搜索,这样你就不必走得太远,你还可以注册不同的群体,以获取即将举行的活动信息。有时在这里会发生对话——通常是在某个特定事件之后——这也可以提供很好的信息。这是一个免费的网站,所以我强烈建议你加入。
然而,你应该意识到,一些聚会可能会有一些(通常非常小的)费用。当你意识到有人必须支付食物、举办地点的空间以及可能还有某位演讲者的费用时,这是可以理解的。顺便说一句,虽然通常会有赞助商(赞助聚会的目的明确是为了寻找他们试图填补职位的候选人!),但这并不总是如此。所以,如果你被要求支付几美元的入场费,请不要感到惊讶——这通常是一小笔钱(我想我在参加的数百次聚会中最多被要求支付过 5 美元)——你可以把它看作是你在求职过程中的一个小投资。
坚持不懈是关键
在你求职的过程中,需要记住的是,在当今的市场中,无论你选择哪条路径寻找机会,事情可能都不会那么容易。
很遗憾,当你提交简历或以其他方式联系潜在雇主时,得不到任何回复的情况也并不少见。你可以理解这一点——当公司收到数百份简历时,不可能回复每个人说“不”——但这是你必须理解的事情;否则,可能会感到沮丧。
很遗憾,即使经历了整个面试过程,也可能得不到任何回复,甚至没有“我们决定选择其他人”的礼貌电话。你可能认为人们至少会告诉你决定是拒绝的,这样你就不会无所事事地等待,但这种情况通常并非如此(实际上,没有回复的情况可能更常见)。
很不幸,在找到合适的工作之前,需要尝试成百上千次的情况也并不少见。尤其是对于那些没有任何工作经验的人来说,找到第一份工作并非易事。
正因如此,保持希望、始终保持积极态度并坚持不懈直到最终出现合适的机会并且所有努力都得到回报是非常重要的。在撰写本文时,市场对新人的进入稍微有些艰难。这并非不可能,但你确实需要耐心并继续努力,即使看起来你似乎在原地踏步。你最终会成功的,但这可能需要一些时间(然后,你可能会一出门就找到理想的工作——你永远不知道,所以坚持不懈是一个重要的品质)。
好吧,所以,你已经去了一些求职网站,直接查看了公司网站,并且通过参加一些聚会建立了人脉。现在,是时候申请职位了。这通常都是从相同的步骤开始的,那就是你的简历,所以我们现在就来谈谈这个。
精炼你的简历,使其光彩照人
简历——或者如果你在美国之外,是简历(CV),可以说是你就业的护照。当你申请工作时,它几乎总是人们首先看到你的东西,而在成功获得这份工作时,它只是众多因素之一,如果它没有很好地展示,它可能会在最初就扼杀机会。
构建一份好的简历在很大程度上取决于遵循一些常见的指南,然后突出那些让你独一无二的事情。让我们首先谈谈这些指南。
构建一份扎实的简历
在线和其他地方有很多关于如何撰写一份优秀简历的建议,但我认为你会发现以下项目几乎是通用的:
-
保持简洁:尤其是当你有很多经验时,你自然想要把所有东西都写进去,最终可能会写出五到六页的简历来描述你做过的所有事情。但你必须抑制这种冲动,因为这几乎总是适得其反,你应该将简历限制在一到两页。记住,简历的目标本质上是打开大门。如果你做到了这一点,你将在面试过程中有时间详细说明。然而,在此之前,你想要避免任何可能让人忽略你的简历的事情,而过于冗长通常被列为其中之一。超过大约两页的简历通常会被直接扔进垃圾桶,甚至没有人看一眼。这可能看起来不公平——而且可能确实不公平——但这却是现实,所以你必须应对它。
-
清晰的布局:你可能会认为越独特的简历越好,所以你可能倾向于玩一些疯狂的设计决策。对于某些职位发布,这可能是有帮助的——例如,如果你申请的是图形设计师。但一般规则是,你应该尽量保持简单。使用清晰、专业、易于阅读的字体。使用清晰、描述性、通常较短的标题来表示简历的主要部分。使用项目符号来表示列表。一般来说,不要在整体布局上过于创新。
-
使用关键词:在许多公司,你的简历将首先通过一个自动筛选过程,它通常会在人类查看之前就被淘汰。这通常是通过寻找关键词来完成的。例如,如果一个空缺职位是为具有 React 技能的前端开发者,那么没有 React 的简历可能会立即被淘汰。因此,在你的简历中投入一些努力,使用与你要申请的特定职位相匹配的关键词,或者如果你没有申请特定职位(也许你只是想将简历投递到你特别想工作的公司,而不知道他们是否有空缺),那么尽量使用尽可能多的准确关键词。这是因为下一个要点。
-
不撒谎或夸张:坚持事实!你可能会侥幸地在这里或那里说谎或夸大其词,但如果被发现,这将摧毁你获得职位的任何机会。即使你被雇佣了,这也可能对你造成伤害,至少在声誉方面。明确地说,描述你所做的事情尽可能积极与对其不诚实之间是有区别的!简历在某种程度上是一项销售工作,所以使用尽可能积极的语言是正常且被鼓励的。但请遵循一个简单的规则:如果某事不是真的,或者如果你在受到质疑时无法证实它,那么你不应该将其放在简历上。
-
校对:在将简历发送给任何人之前,请多次检查语法和拼写,并使用所有可用的工具来帮助这一过程!有些人可能会忽略一些小错误,不会因此对你产生偏见,但其他人可能会因此直接取消你的资格。无论如何,这都是一个不好的印象:当你试图获得工作时,你想要展示的是对细节的关注,而这从一份在拼写和语法方面无懈可击的简历开始。在将简历发送到任何地方之前,也让其他人阅读你的简历,因为第二(或第三或第四)双眼睛总是好事。
-
避免无关信息:过去人们建议在简历上放一些个人爱好之类的信息,因为这表明你工作之外也有生活,你可能不是一个刻板的人。始终记住,人们喜欢与那些周围让人愉快的人共事,他们与工作之外的人有共同点,因为这为他们提供了社交的共同基础和接触点。但简历并不是展示这种信息的地方,所以现在的观点是在面试期间和面试之后,如果一切顺利的话,再展示这类信息。
-
联系方式:你会惊讶地发现,你经常会收到一些看起来不错的简历,但随后发现上面没有关于联系候选人的信息!这是错失机会的明确方式,所以请确保这些信息是存在的,并且是最新更新的。
-
使用专业语言:你正在申请工作,希望一切顺利的话,这将意味着你处于一个专业沟通的语境中。因此,在简历中避免使用俚语或非正式用语。再次强调,这些用语在适当的时间和场合也是有其位置的,因为你的个性和特质在面试过程中也会发挥作用,有时稍微不那么正式可能是个好事。但简历并不是展示这种风格的地方。
-
突出成就:你想要尽可能具体地描述你所做和完成的事情,如果你有支持它们的指标,那就更好了。如果你构建了一个每月能为你雇主节省 10,000 美元的系统,那么你想要说的是这一点。当然,如果你还没有任何工作经验,那么这会变得更加困难。但几乎总是有“一些东西”:你是否为开源项目做出了贡献以获得经验?你是否建立了一个个人网站?你是否编写了一个游戏?你是否在大型零售店担任过库存管理员?成就不一定必须直接与你要进入的领域相关(尽管当然最好是相关的)——你只需要知道如何推销它。我将在下一节中对此进行更多讨论。
-
量身定制:最后,尽可能为你要申请的工作定制你的简历。如果你知道一家公司在寻找在 AWS 进行云开发的人,那么你可能想要强调你在云服务提供商方面的经验,即使它不是 AWS。如果你申请的公司提供共同基金服务,那么你可能想要更详细地描述你在银行担任出纳员的时间,因为这与金融行业相同。再次强调,不要对任何事物撒谎,但考虑到你申请的地方的简历比提交到处都一样的通用简历要好(有时,你将不得不这样做,但每次你可以定制简历时,都这样做)。
你可能还会考虑聘请专业的简历撰写服务,例如 TopResume (topresume.com)。他们将会——以一定的费用——与你合作,为你制作一份顶级的简历。他们知道所有最佳实践,他们知道公司在简历中寻找什么,他们也知道所有能让你的简历脱颖而出,或者至少尽可能降低被忽视的几率的技巧和窍门。如果你不擅长制作一份好的简历,那么这样的服务可能值得考虑。但是,如果你自己能胜任,那么你可以省下一些钱。这类服务通常提供不同的套餐,从对你自己制作的简历进行基本审查到全程指导你完成写作过程,甚至有时帮助你提交简历。只有你自己才能决定这样的服务是否适合你,所以做一些研究,看看你的想法。
现在,让我们谈谈你目前所处的状况,即你在该领域几乎没有或没有任何实际工作经验。
突出你所拥有的
当你能描述过去 20 年在专业网页开发中完成的数十个高调项目时,那当然很棒,但没有人是从这里开始的。在行业内找到第一份工作将需要其他东西。
理论上这个技巧很简单,但在实践中很难:你必须建立你所拥有的任何东西,即使它与直接相关的内容无关。
我的意思就是我之前暗示过的。比如说,你可能还没有做过任何网络开发,至少没有为报酬而做。但也许你曾经是沃尔玛、塔吉特或另一个大型零售商的库存人员。你如何谈论这段经历,使其变得相关?
好吧,库存人员必须具备一定程度的组织能力。他们必须能够确定正确的产品放置在正确的位置上。他们可能需要能够下订单来补充库存,既不过度购买也不短缺购买。他们需要高效地补充货架,无论是从时间上还是从完成后的外观上。
换句话说,他们必须注意细节,并制定一个行动计划以有效地达到既定目标。所以,当你描述那项工作经验时,可以说这样的话!
谈论你所拥有的任何工作经验并不欺骗。同样,如果你建立了一个个人网站,你可以通过描述所使用的技术、你是如何从线框图到原型再到最终产品的过程,以及你如何有效地实施 SEO 来提高搜索排名来谈论它。
或者也许你离开学校的时间并不长。如果是这样的话,你是否完成过一些特别复杂的作业?如果是的话,那么你可以谈论这些,同时注意包括你学到了什么以及你必须克服哪些困难才能成功完成它们。
你应该专注于你培养的技能。这通常在无论你是否拥有工作经验的情况下都是正确的,但如果你没有,它就变得更加重要,因为你在这方面不会有太多的选择。专注于你精通或有过经验的编程语言和技术,无论这种经验的形式或程度如何。
你还应该关注“软技能”,比如团队合作、问题解决和适应性。同样,寻找任何可以突出这些技能的地方。如果你在学校里参与过团队项目,那么这就是团队合作的一个话题。如果你作为库存人员,当别人辞职时不得不接管商店的多个区域,那么这就是适应性的一个好例子,你可以这样谈论它。尽管这可能并不总是以不显得过于牵强的方式与网络开发联系起来,所以请小心处理这一点。
这里的基本观点是,在没有大量的可展示的网页开发工作经验的情况下,你必须寻找自己和工作、生活经历中的积极方面,并以一种方式表达它们,这样它们就能展示出一些潜在雇主可能会觉得吸引人的东西。冒着重复的风险,不要在你说的话中撒谎,因为那根本不值得潜在的负面后果。但是,提升自己和你的能力,用言语推销自己,这并不撒谎——这只是良好的推销技巧!
自然地,拥有一些可以展示的网页开发作品几乎总是比仅仅华丽的语言要好,并且几乎总是比后者更有分量。幸运的是,这是一个你可以在没有专业工作经验的情况下获得此类经验并展示作品的地方,这就是诸如仓库、作品集和样本等东西出现的原因。
拥有可以展示的仓库、作品集和样本
当你第一次出去寻找网页开发工作的时候,你几乎肯定没有一段长长的记录来展示你的能力。你可能有一些可以展示的东西——你可能建立了一个个人网站,或者也许你免费为当地的教堂建立了一个网站。但你没有以前的工作可以指出来并说“看,这展示了我能做什么。”因此,为了给自己提供最好的被雇佣的机会,你将想要制作一些东西,并让人们可以看到它们。
建立网站
正如我刚才提到的,个人网站通常是人们首先创建的东西,他们这样做是为了特别展示他们的能力。这样的项目的优点在于,一切都取决于你!你可以决定它的外观、工作方式、你用来构建它的技术等等。然而,这确实是一个你应该在你准备好时承担的项目。明确地说,它不需要是史上最好的网站,但它确实应该是你当时能构建的最好的网站。你想要能够指向它并说,“这就是我现在能做的。”
虽然这是一个好的开始,但也要寻找为他人建立网站的机会。经常有非营利组织非常愿意免费为他们建立网站。这为他们提供了所需的服务,同时让你有机会学习。但与个人网站不同,现在你必须满足“客户”的需求,这展示了另一组潜在雇主希望看到的能力,因为他们实际上将成为你的客户,如果你被雇佣的话。
对于大多数情况,建立网站不会让人看到你的代码,至少不是源代码,也不是后端代码。这就是 GitHub 仓库可以发挥作用的地方。
建立 GitHub 仓库
另一件你应该做的事情是建立 GitHub 仓库(或几个)。关于这一点,网上有一些争议,因为你会遇到一些人永远不会查看这些仓库,但有些人会——最终,如果你在寻找入门级职位,潜在的雇员可能会查看他们能找到的任何东西来评估你的技能。
如果他们选择查看,拥有 GitHub 仓库可以让面试官看到你是如何编写代码的。他们几乎不会非常严格地看待它,因为任何招聘入门级职位的雇主都必须现实地设定他们的期望。但是,看到一个人编写整洁的代码(格式良好)、注释良好,并且看起来逻辑结构合理,可以让他们对你思考过程有所了解。
你应该在你的仓库中放入什么内容呢?简单的回答是任何东西!当我第一次被雇佣时,我没有大学学位,也没有任何可以指点的经验,除了我做过的一些兼职工作。所以,我提供给面试官查看的代码包括我编写的一些视频游戏,一些我自己编写的的小工具,以及一些为一些旧的公告板系统(BBSs,在互联网之前出现)编写的演示。这是一家金融公司的面试,所以这些内容与我将要做的工作没有任何相关性。但是,展示给面试官的是我能够编写代码,并且能够以他们认为合理的方式编写代码。几个月后,他们直言不讳地告诉我,所有这些都帮助我得到了这份工作。
所以,你编写的任何代码,只要确保它是按照良好的编码实践编写的,就是你仓库中的优质内容。它与你要申请的行业无关——它更多的是对面试过程的输入。
但是,这不仅仅是关于代码,对吧?在一定程度上,它也关乎事物的艺术方面,创造性方面。这通常就是作品集的目的。
构建样本作品的集子
在与艺术相关的职位或其他创造性工作中,在面试期间展示作品集并不罕见。对于艺术家来说,这通常是一本包含你绘画页面的书,展示你能做什么,你能创造什么。
好吧,网络开发是艺术的一部分,尤其是在前端方面!因此,作品集对你来说可能也具有一定的价值。然而,它可能不会像艺术家那样是一本实体书(尽管我敢打赌,现在许多艺术家也不再使用实体书了!)。它可以是展示你构建的网站的 URL。
作品集和 GitHub 仓库之间可能存在一些重叠。你的仓库也可能实际上就是你的作品集,因为 GitHub 提供的一项功能是托管网站。你可以在 GitHub 仓库中放置一个网站,并使其作为一个可工作的网站提供。
不论如何,这里的重点与其说是技术方面,不如说是艺术方面(尽管当然可能存在重叠)。看到你的代码并且写得很好是一回事,但看到屏幕上实际的内容,看到网站是什么,看到你遵循了良好的设计原则,例如,则是另一回事。或者看到你创造的东西在美学上令人愉悦。这正是作品集的作用所在。
所有这些的底线是寻找证明你能做什么的方法,展示你的能力。在努力获得第一份工作时,你可能需要有点创造性,并且需要主动。当你有几年的实际工作经验并可以描述你为其他雇主所做的工作时(尽管通常你不能展示它,因为在大多数情况下它是专有的),但这并不是一个优势,你仍然需要提供尽可能多的证据,用更好的词来说,就是让人愿意冒险雇佣你。制作一些东西,把它们放在网上——包括代码——并利用所有这些来证明你值得雇佣,即使没有那么长的记录!
到目前为止,你可能会找到一些职位发布,提交了你的简历,准备好了你的 GitHub 仓库、作品集和样本,并且在理论上已经准备好面试了。现在,让我们来看看面试以及成功通过面试所涉及的内容。
理解典型的网络开发者面试
实际上并没有一个典型的网络开发者面试——面试之间有很大的差异,人们进行面试的方式也很多,所以一般来说没有两个是完全相同的——但确实有一些常见的模式,一些你更有可能经历的事情。我们可以将这些事情分组,并说它们代表了我们可以称之为典型网络开发者面试的内容。
理解结构
大多数面试都涉及多个环节的活动。这些环节可能由一个人或多人进行,可以是单独的或小组形式。它们可能发生在一天之内,也可能分布在几天内。
初审环节
在第一轮中,你很可能会遇到对你的简历的审查,你将需要回答关于以前项目和经验的问题。这些问题通常会深入询问以确保你简历上列出的内容准确无误(例如,通过要求你描述一个特定项目中所列出的特定技术是如何使用的),并试图判断你对这些主题了解的深度。你可能得到的问题例如:“哦,我看到你在这个项目中使用了 MongoDB,你能告诉我你设计了什么样的数据结构吗?”或者“你是如何在那数据库中实现分片的?”
在这个阶段,直接回答问题,尽量不要过多地发表评论。你可能会发现你的紧张情绪开始发作,开始胡言乱语,或者如果不是因为紧张,那么可能是因为你认为更长的回答必然更好。尽量避免这种情况,但也避免回答得太短。你不想让人觉得你在胡言乱语,但你也不想让人觉得你不知道你所声称知道的东西,或者你对它的了解不够深入(除非你明确地说“是的,我使用过 MongoDB,但我并没有深入研究,所以不能声称自己是这方面的专家”,如果这是真的,这是一个合理的回答)。
技术轮
经过这一轮之后,你可能会被问到一些技术问题。这里我指的是类似于“如果你知道元素的 ID,你可以在 JavaScript 中使用什么函数来获取 DOM 节点的引用?”这样的问题。这些问题是直接的,旨在测试你的知识。这些问题的答案,希望对你申请的工作是相关的,尽管这并不一定有保证。
我处理这类问题的建议是尽可能回答,但如果你确实不知道某件事,那么就说出这一点。尽管如此,也要尽量减轻打击。对于上一个问题,你可能会说:“对不起,那是我以前没有遇到过的事情,但如果有几分钟时间在网上搜索,我知道我能够找到那个答案,因为我知道 DOM 节点是文档对象模型中 HTML 元素的 JavaScript 表示,我使用过 JavaScript,所以这应该只是快速研究的问题。”这告诉面试官你确实拥有他们所寻找的基本知识,但你可能只是对特定的函数感到困惑,或者那是一个你从未使用过的东西,没有人期望知道每一个细节。
在这一轮中,你可能会被要求在白板上编码(这是几乎每个人都讨厌的事情!)。例如,面试官可能会要求你用 Python 在白板上编写一个冒泡排序算法。请放心,大多数面试官并不期望你在这里做到完美!即使你的解决方案接近正确,通常这也足够了。
你可以在这里做的最好的事情是在工作中“大声思考”。在工作过程中,将你头脑中的内部对话用语言表达出来。这样做有两个好处。首先,它让面试官能够洞察你的思维过程,这对你是好事,因为即使你的解决方案最终没有成功,他们也能听到你至少是在正确的轨道上,即使尝试失败,你也能得到一些分数。其次,这有助于缓解你的紧张情绪,因为在这种情况下大声说出来往往能让你的身体释放一些压力。
也不必害怕纠正自己!如果你开始走错路并意识到这一点,说出像“哦,等等,那不会工作,因为X;我需要做Y”这样的话是非常好的。
了解你轮
大多数面试在这一轮之后会变得更加轻松,这时它变得有点更个人化,在性质上更加自由。在这里,你正在被评估是否适合公司文化,坦白说,就是看看你是否是人们愿意与之互动的人。在这里,目标仅仅是友好、开放和诚实。然而,尽量不要过于开放。你在这个阶段不需要透露所有的内心秘密,也不需要谈论你可能有的每一个不寻常的爱好。你无法以任何具体的方式为这一轮做准备,除了尽可能练习成为一个亲切的人。
这也是你通常有机会询问一些关于工作和公司的问题的时候。我将在“处理常见面试问题”部分讨论这个问题,但到目前为止,可以说的是,你应该尝试准备一些问题来提问。一个特别好的问题是简单地问“告诉我一些关于这里的文化;在这里工作的感觉如何?”或者类似的问题。这表明了你的兴趣,会给对方一个机会说一些话,减轻你的压力,并且可以让你获得一些宝贵的见解,了解接受那份工作可能对你日常生活的意义。
作业轮
你可能会遇到另一轮是带回家的作业,有时也可能是现场作业。在两种情况下,你都会被要求做一些事情,一些小项目来展示你的能力。这些并不像我所描述的其他轮次那么常见,但一些公司确实会这样做。你通常会被分配一个与工作相关的任务,然后你会被允许访问工作站(或者你可能被允许使用自己的设备)。在大多数情况下,你将拥有更多或更少的自由来决定你使用什么工具以及如何构建作业(你可能会被给出一些一般性的参数,比如“我们希望你能用 React 来构建这个”)。在大多数情况下,你工作的时候不会被监控,但一些公司可能会有人在你旁边监督。
这里没有真正的技巧——你只需要尽力而为!选择你最熟悉的工具和技术,专注于编写清晰、干净的代码,并且能够很好地展示——大多数时候,这就会发生,你将不得不向面试官展示你的结果。他们可能会问一些关于你为什么这样做的问题,所以请确保你能为每一个决定做出合理的解释。
我必须提醒你注意这些类型的作业:不要让任何人利用你!你的时间是宝贵的,有些公司可能会通过给你不合理的作业来利用这一点,这些作业可能需要几个小时才能完成。一些特别不道德的公司甚至可能会通过给你一个他们需要完成的作业来试图从你那里得到免费的工作!你必须依靠直觉来判断什么合理,什么不合理。我的一个一般性规则是,如果你认为你可以在一个小时内或更短的时间内完成作业,那么这可能是合理的。如果你认为需要更长的时间,那么你可能想这样说:“很抱歉,但我认为完成这个作业将花费我大量的时间,因此我不舒服。你们能给我其他不需要花太多时间的事情吗?”
一些公司可能也愿意为你完成作业所花费的时间付费。这并不常见,我可以说你大概不应该期待这种情况,但一旦发生,那总是令人愉快的。在这种情况下,你可能对所需时间有更大的容忍度。我还要说,除非你感觉面试进行得很好,你对公司提供的机会感到兴奋,否则你不应该接受一个时间较长的作业。如果你对一家公司只有一半的兴趣,那么花 6 个小时编码一个登录页面可能就没有意义了。
既然你对面试可能的样子有了一些了解,让我们谈谈一些你可以做的准备编程面试的一般性事情,除了我在本节中给出的建议。
准备编程面试
编程面试,或者更一般的技术面试,可能是一个非常压力山大的事件。用最简单的话来说,这主要取决于你是否知道这些内容,以及你是否能够证明你确实知道。当我面试人们时,这是我最主要寻找的东西,因为无论他们是否具备该职位所需的技能,我试图填补的空缺才是最重要的。这并不是所有重要的事情,远非如此,但它是最重要的事情。
一个不重要的事情,矛盾的是,就是候选人是否能够脱口而出所有答案。首先,我们工作的领域里这是实际不可能的。其次,这是一个研究和使用参考资料是必须的领域。是的,有一些事情我期望每个候选人都能脱口而出。例如,如果我试图雇佣一个前端开发者,我当然会期望他们能告诉我一些关于 CSS 盒模型的事情。如果我雇佣一个 Java 开发者,我会期望他们能告诉我字符串池化是什么。
但我并不期望的是,例如,他们能告诉我 JavaScript 字符串对象上所有可用的方法,或者他们知道特定数组方法所需的参数。这些是我期望人们在需要时查找的事情(你倾向于只记住你使用最多和最近的东西,所以人们可能会知道一些这类东西,但不会全部知道)。因此,作为候选人,你可以在面试中稍微放松一下,因为面试官不会期望你什么都了如指掌。
但与此同时,了解尽可能多的知识并能够展示你的能力同样重要。
面试官经常尝试通过让你解决编码挑战的网站来评估这一点。其中最著名的两个是 HackerRank (hackerrank.com) 和 LeetCode (leetcode.com)。你可能会在面试中被要求完成这些挑战。这些挑战通常以任务的形式出现,要求你编写一些小段代码,然后平台会确认代码是否正确运行。从理论上讲,你编写代码的方式本身并不重要——重要的是它要有指定的结果,所以他们通常不是在寻找特定的解决方案,只是需要可行的解决方案(然而——遗憾的是——他们有时可能会有些挑剔)。
好消息是,这些网站在面试之外也对你们开放,它们是准备使用它们的面试以及不使用它们的面试的绝佳方式,而且也是一般学习编写代码的好方法。我强烈建议你们使用这些网站。完成挑战,熟悉它们的工作方式以及它们对解决方案的要求。这样,如果你在面试中遇到它们,这不会是你第一次遇到。
更普遍地说,学习数据结构和算法(DS&A)是一个重要的步骤。如果你之前从未听说过这些术语,让我来解释一下。
简单来说,数据结构是存储数据的标准方式。例如,数组就是一种数据结构——几乎每种编程语言都内置了这种结构。你可以使用语言的基本功能来构建更高级的结构——比如列表(本质上是一种更健壮的数组形式)、栈(类似于数组,但元素只能以定义良好的特定方式进入或退出)和映射(将键与值关联起来,允许你通过键查找值)。这些都是一些最简单的例子。
而术语算法只是指一种复杂的方式,即一组用于执行特定任务的编程指令。例如,你可以用许多方法对数据进行排序,可以选择许多不同的算法。有些算法如果数据已经基本有序,效果会更好,而有些算法可能会在排序大量数据时减慢速度,还有一些算法可能会使用更多的 CPU 资源来完成工作。了解这些不同的算法及其权衡,你可能会在面试和工作中被期望知道。
但在特定的面试中,你可能会被要求实现一个双向链表。然而,了解双向链表是什么以及何时何地应用它,比了解如何自己实现它更重要——在大多数情况下,在工作中你不会自己实现它,而是会使用现成的版本。虽然了解如何反转二叉树是有价值的,因为那可能是你会在白板上被要求做的事情,但了解二叉树是什么以及为什么我们使用它们,并能够解释这一点,则更有价值,因为那才是你在工作中需要的东西。像 HackerRank 和 LeetCode 这样的网站在很大程度上是基于要求你实现这样的数据结构与算法解决方案,因此更广泛地研究这些资源将帮助你应对这些网站及其测试。
寻找更多关于 DS&A 的信息
数据结构与算法的主题非常广泛,除了一般性内容外,它超出了这本书的范围。然而,为了更深入地了解,你可以考虑一些资源,比如麻省理工学院的开放课程网站(ocw.mit.edu),它提供了来自著名的麻省理工学院的免费课程和互动内容;哈佛大学的CS50:计算机科学导论材料(pll.harvard.edu/course/cs50-introduction-computer-science);还有老牌的 YouTube!只需搜索“数据结构与算法”,你就能找到大量优质内容(务必选择那些获得大量点赞的内容,因为至少根据观众社区的判断,这往往意味着质量)。
现在,让我们看看你在技术面试中将要面临的最大挑战之一,这不仅仅是技术知识本身,那就是应对整个过程的压力!
应对压力
我已经提到过几次,面试可能会是压力很大的情况。大多数人并不特别适应可能感觉像是在被审问。没有神奇的解决方案能让大多数面试变得容易,但了解进入面试时可以期待什么,并有一些应对策略是有帮助的。现在让我们看看其中的一些:
-
准备:显然,学习和了解你的知识是关键。花些时间复习基础知识——当你被问到基本且简单的问题时,你可能会感到困惑,但你却忘记了,这种情况每个人都会遇到!此外,确保你熟悉工作描述中提到的任何特定技术,即使你对它们没有太多(或没有)经验。我可以告诉你,在我第一次面试时,我被问到了关于 Visual FoxPro 的问题。我以前从未使用过它,但因为它列在了工作描述中,我在面试前买了一本关于它的书并阅读了它。结果,我能够回答一些面试官后来告诉我没有人能回答的问题。
-
进行模拟面试:如果你有朋友或家人愿意这样做,让他们扮演面试官,对你进行一轮考验!一些网站可以帮助你进行模拟面试,包括 Pramp (pramp.com) interviewing.io(这是网站名称和网址!),甚至 HackerRank 和 LeetCode 也提供一些这样的功能。这些可能比你的朋友或家人更好,因为你可能对朋友和家人有点过于放松,而这里的目的是在比实际面试更受控的环境中不要感到舒适。
-
正念技巧:在面试前进行呼吸练习或冥想可以帮助你平静心情,进入良好的心态。
-
转变你的视角:记住,面试是双向的。你也在评估这家公司是否适合你。这可以在情感上给你一种小小的权力感,这可以避免你感到不知所措。
-
保持积极:无论你认为面试进行得如何,从一开始就专注于从经验中学习。如果你坐在那里想“我这次面试砸了!”那么花点时间问问自己到底出了什么问题。显然,在面试过程中,你不可能面无表情地坐着,考虑问题!但你可以潜意识地处理问题,无论这让你能够即时调整还是仅仅考虑下次做得更好的方法,它都可以更积极地重新定义正在发生的事情。
-
面试后分析:无论结果如何,当你完成时,一定要反思哪些做得好,哪些可以改进以备下次使用。这是前一个点的延伸,但它更专注于学习经验和改进,而不是在面试中帮助你。
下一步要考虑的是给面试官留下的印象,以及你可以做些什么来确保这是一个积极的印象。
展现最好的自己
在你通过面试过程,无论它以何种形式进行时,始终记得以专业和友好的方式展示自己。微笑,进行眼神交流,并保持专注。不要让其他事情进入你的脑海,因为在这个时刻,只有面试才是最重要的。
确保你的着装也要成功。即使你知道公司的环境是休闲装,面试时也要穿专业装(我建议即使你被明确告知可以休闲装,也要这样做,尽管这可能有些争议)。我们这里不是在谈论燕尾服,甚至也不是西装领带或昂贵的套装。在大多数环境中,休闲裤和衬衫对男女都是可以接受的。
确保你的仪容整洁无瑕。这关乎向大多数人展示一个积极的自我形象。你永远无法确定别人会如何反应,但你无法控制这一点——你只能控制自己。确保你不显得邋遢是你能够控制的。
这会让你显得专业、专注,并且有良好的细节思考能力,这正是你在争取那些事情重要的职位时想要传达的,就像在软件开发中那样。
但无论你准备得多充分,仍然完全有可能遇到一个糟糕的面试官。鉴于这种情况,让我们谈谈如果你发现自己处于这种不受欢迎的境地时,你可能能做些什么。
保护自己
到头来,接受面试是困难的。但进行面试也同样困难。这可能看起来不应该如此,但事实确实如此。提出好问题很难。评判人很难。确保公平也很难。这是一项需要时间培养和发展的技能,不幸的是,在这一点发生之前,许多人并不擅长。因此,作为候选人,你应该尽你所能采取保护自己的措施。
例如,你可能会遇到一个不想在那里面试的面试官。也许他们自己有很多工作要做,感觉没有时间进行面试。他们可能会分心,比如不断查看手机。结果,他们可能会草率地完成面试,最终没有给你一个公平的机会。在这种情况下,你实际上能做的事情并不多。你最好的选择是保持友好,并尽量提供简洁但仍然有意义的答案。注意不要离题,因为这可能会使情况变得更糟。另一方面,如果他们感觉到你在尊重他们的时间,他们的态度可能会改变。
或者你可能遇到一些人,他们不知道如何构建好的问题。他们可能会问你非常开放性的问题(有时可能是故意为之,但通常不是),或者基于高度主观意见的问题(在某些情况下,这些问题可能是有效的)。他们可能会问一些措辞混乱的问题,或者你认为没有真正有效答案的问题。在这种情况下,你最好能做的事情是请求澄清(当然,要礼貌和尊重)。通过确认你的理解,确保你能尽可能好地理解被问的问题。“好吧,所以我认为你问我的是 JavaScript 中的闭包是如何工作的,对吗?”这是一个很好的问题,可以确保你理解了被问的问题。
你可能会偶尔遇到一个面试官,他们更感兴趣的是用他们所知道的知识来给你留下深刻印象,或者以其他方式让你感到威胁。处理这种情况没有简单的答案,但通常归结为相信自己并保持自信。确保保持良好的眼神交流和稳定的姿势,因为身体语言在面试中确实很重要。如果你感觉到他们试图用他们的知识来给你留下深刻印象,那就让他们这样做吧!他们可能确实有很多可以教你的东西,所以你不妨注意并保持积极的态度,将其视为增长知识的机会。
被问到不相关的问题是在面试中经常遇到的问题之一。对他们来说,当然,这些问题是相关的,但可能并不是以一个有意义的 way。处理这些问题时,首先请求澄清,因为也许这些问题确实以你尚未看到的方式相关。如果不是,尽你所能将对话引导回与该职位相关的你的技能和能力。而且,始终记住,如果一个问题让你觉得冒犯或不合时宜,你有权拒绝回答。如何最好地做到这一点是棘手的,但我会选择像“很抱歉,我不舒服回答这个问题”这样的说法,然后就此打住。
除了压力、可能表现不佳的面试官和需要了解你的技术材料之外,还有一些相当常见的问题你可能会遇到,因此在进入面试前有一个应对策略是个好主意,所以现在让我们来处理一些这些问题。
应对常见面试问题
除了技术角度之外,在面试过程中,一些非技术问题也常常被问到,而且至少其中一些是非常常见的。它们可能以不同的方式被问起,但无论如何,它们基本上归结为同一个基本问题。在这里,我将尽可能地将它们概括,并讨论你应该如何回答这些问题(当然,根本点是你总是想要诚实地回答它们)。
注意,这绝对不是一份详尽无遗的清单。我所做的是,通过一些研究,结合我在桌子的两边积累的个人经验,找出可能是最流行的 10 个这样的问题。自然地,你必须准备好应对其他问题,或者确实没有任何问题!在面试中,你被问到这样的问题的可能性并不是一定的,当然,这些问题也不是一定的。但它们足够常见,我认为它们值得指出,而且如果其他什么都没有,它们至少应该让你开始思考这样的问题,这应该有助于你为可能出现的任何其他问题做好准备。
介绍一下你自己
这是一个完全开放的问题,你可以从很多方面来回答。但主要的是,你应该专注于你的职业旅程,突出相关的经验和成就。当你没有多少(或根本没有)职业经验时,突出你能做到的一切。例如,如果你非常热衷于为开源项目做贡献,那么当然要提到这一点。如果你喜欢编写游戏,也可以稍微谈谈。
通常,尽量传达你对这个领域、技术和学习的热情。你可以稍微谈谈你的个人生活,但尽量与职业生活联系起来。谈论你的爱好本身并没有什么不妥——当然,除非它们可能被认为是不适当的——但可能不是面试官想听的内容。他们想听的是你是什么样的一个人,你将能够胜任你正在面试的职位,所以尽你所能专注于这一点。
你为什么想在这里工作?
这可以看作是一个有点狡猾的问题,因为对我们所有人来说,明显的答案都是“因为我需要赚钱,而你这里有个空缺!”但在现实中,这是一个展示这不仅仅是你(即使如此)的又一次普通工作面试的机会。为了做到这一点,展示你已经对公司进行了一些研究。谈谈公司的使命如何与你的价值观相符,以及你多么希望成为实现这一使命成功的一部分。
正如我多次说过的,这里不要撒谎。如果你的价值观与公司的价值观不一致,你可能一开始就不应该申请那里。但如果公司的使命并没有完全激发你的兴趣,你仍然可以说些像“我喜欢解决问题和构建复杂的事物,这家公司的使命将给我一个机会来运用这些技能。”但如果你对公司一点研究都没有,你就不能说这样的话。
你最大的优势是什么?
回答这个问题的关键是选择与你要申请的职位相关的优势,并且能够给出例子来证明这些优势。
例如,对于一个网页开发者的职位,你应该考虑那些能够展示你解决问题能力、将粗糙的设计转化为成品的能力、你的创造力和学习新事物的能力等优势。这可以通过,例如,讲述一个你在设计和构建个人网站或编写游戏时需要学习新技能的项目故事来实现。
如果你已经做了研究并且知道该公司使用云服务提供商,你可能想在实验 Microsoft Azure 的时间上多加一些关注,即使你构建的东西并不特别。然后,用这个具体的例子说一些更普遍的事情,比如你之前从未使用过 Azure,所以你必须边做边学,在这个过程中,你必须解决如何设置身份验证等问题。
你最大的弱点是什么?
这是一个人们通常喜欢以“狡猾”的方式尝试回答的问题。例如,你可能会说有时你会如此专注于你所做的事情,以至于工作的时间比应该的要长,因为这会让你看起来很投入。换句话说,你可能会试图将负面变成正面。当然,如果做得恰当,这可以有效。然而,有很大可能性你会显得不真诚或狡猾,这是你在面试中无论如何都想避免的事情。
相反,既然我们都不是完美的,试着找到自己真正的弱点,并诚实地展示它。当然,你可能不想选择一个太大的弱点(例如,“我每次有机会都喜欢把办公用品带回家!”可能不是你的最佳选择)。你可能不想说你经常喜欢开车撞进咖啡馆!但通常并不难找到一些不是巨大的弱点。你可能会说“我有时会发现自己不知道某事时会暂时感到恐慌”,这是我们所有人都会经历的事情。
然后——这是关键——谈谈你如何在这个领域努力提升自己。也许你可以说“为了尝试应对那种恐慌,我一直在积极寻找那些会强迫我处理新技术的情况,以便我可以努力克服它。”这表明你是诚实的、内省的,并且愿意根据需要做出改正。
你认为自己五年后会是什么样子?
这又是一个那些没有正确或错误答案的开放式问题。关键在于你必须有一个答案。
你可能不应该在回答中过于夸张。如果你心里想的是“我想成为 CEO!”这完全没问题,但那可能不是最好的回答。
首先,这可能会被认为是不现实的——大多数人通常倾向于在他们雇佣的人身上看重这一特质——甚至可能会让你被嘲笑(即使只是默默地)。设定宏伟的目标是好事,但目标与你现在所在的位置之间有很多步骤,人们希望看到你没有看得太远,也没有不切实际。
其次,这个答案可能意味着你将尽快寻找晋升或甚至离开公司的机会,以实现你的职业目标。从他们的角度来看:为什么投入所有的时间和精力来雇佣你,培训你,让你熟悉并准备好在他们需要填补的职位上投入生产力,只是为了看到你在第一个机会出现时转到另一个职位(无论是转到另一家公司还是转到同一家公司的另一个职位)?从他们的角度来看,那将是大量的浪费时间和精力,这取决于你在那个职位上的时间长短。
因此,你需要在雄心勃勃和不那么雄心勃勃之间找到平衡,这样他们就不会认为你不会想要做他们雇佣的工作,而且在你得到合理的投资回报率(ROI)之前,他们会失去你。因为记住,从他们的角度来看,你是一个非常宝贵的投资!
你还应该尝试将你的职业目标与公司的机会相一致,这同样来自研究。说“5 年后,我希望成为团队领导”是可以的,因为这表明你有雄心,但那些雄心可能会让你在那里待一段时间,而且这也不是一个不切实际的目标。他们可能会欣赏这样的目标,因为这可能表明你将努力工作并产生良好的结果,以促进你的职业发展,但你的努力将一直帮助他们。如果你被雇佣,展示双赢的局面是一个值得记住的目标!
我们为什么要雇佣你?
对于这类问题,你想要解释你如何为公司做出贡献,以及你的技能和经验如何与工作要求相匹配。显然,对于一个网页开发职位,明确说明你如何学习了 HTML、CSS 和 JavaScript,以及你如何将它们用于构建真正的网站,是关键。
你还想要表现出对这个职位的热情,尽管不要过度到听起来像是做作的程度。例如,“我喜欢网页开发中固有的挑战,我相信我的技能将使我能够有效地为你完成这项工作”可能是一个不错的起点。
描述一个你面临的挑战以及你是如何克服它的
这是一个相对容易回答的问题,因为,让我们面对现实,我们都有需要克服的挑战——并且希望我们都克服了它们!在这里,我们谈论的是与工作相关的挑战,无论是在工作中还是在工作之外。
一定要使用具体的例子,并专注于你的问题解决方法和最终结果。你讨论的挑战——一两个就足够了——可以是大的或小的,只要它们能展示你如何有效地处理它们。也许你可以谈谈你花几个小时与某些 CSS 斗争,以使网站布局恰到好处。或者可能是你的 PC 表现异常,你必须进行大量研究和注册编辑来解决问题。任何能展示你能够应对问题、克服困难、研究、学习和推理的事情都是重要的。
注意,你通常不希望在这里讨论任何个人挑战。然而,作为一个寻找入门级职位的人,这可能就是你所拥有的全部。也许你的车坏了,你必须处理这个问题。或者可能有一场糟糕的暴雨,你的屋顶漏水,你必须拼凑一个临时的修补来防止你的房子被淹。只要你能突出你如何克服这些挑战,以及你如何处理它们,以及你处理它们的思考过程,那么即使这是你唯一拥有的答案,它们也可以是合适的。
你如何处理压力或压力?
这个问题与上一个问题紧密相连,因为当我们面对技术挑战时,我们也会面临一定程度的压力和紧张。你如何处理这些情况是潜在雇主想要了解的。
回答这个问题的方式就像上一个问题一样:提供具体困难的情景,并解释它们是如何以及为什么让你感到紧张的。然后,解释你用来应对它们的任何应对机制。也许你经常使用各种呼吸练习。或者你的回答可能是你寻求比你更有知识的人的帮助。任何能展示你知道如何处理压力,不会在压力下崩溃的事情都是这里所寻找的。
你还可能想要提及你使用的任何主动压力管理技巧,在这个领域,稍微透露一些个人信息可能是可以接受的。例如,如果你喜欢钓鱼因为它能放松心情,那么提及这一点是可以的。然而,记住,主要的目标是展示你将在工作中能够做什么以及如何处理,因此尝试将你的回答与工作环境联系起来将是有帮助的。
你对薪资有什么期望?
这始终是一个困难的问题,并引出了更大的谈判话题,这是许多人都不舒服的。为了帮助回答这个问题,确保在面试前研究行业标准和规范,以便你有现实的期望(我在前几章中提到了这一点,但这个数据是不断变化的,所以你将在面试前进行一些研究)。
你需要诚实地表达你想要什么,但也要有一定的谈判空间。在薪酬方面,雇主通常有一定的灵活性,但这将取决于许多因素,其中一些是你无法控制的。所以,避免说“嗯,我知道网页开发者可以赚X,所以我想要这个。”相反,可以说“嗯,根据我的研究,X到Y的范围似乎是常态,所以我正在寻找这个范围内的东西。”只是确保这个范围的低端是一个你会满意的数字,因为雇主自然希望接近这个范围的低端。
最终,是否接受任何工作机会取决于你,你可以对这个答案非常坚定,或者非常灵活。如果你把价格定得过高,超出了他们的舒适区,那么坦白说,你本来就不可能得到这个职位,所以虽然一定的灵活性是好的,但也不允许自己被压价。
你对我们有什么问题吗?
最后,我们来到一个通常在大多数面试中最后提出的问题,你应该通过研究来准备这个问题。你想要准备好两到三个好问题。如果你没有任何问题,那么这通常意味着缺乏兴趣或参与度,这是你绝对不希望在面试中表现出来的。
你可以询问诸如团队结构、公司文化如何、你所在职位的典型工作日会是什么样子,或者如果你有具体的研究结果,可以询问具体的项目。你可以询问工作与生活的平衡(这将在下一部分讨论!)以及公司未来可能参与的项目。
但是,不要过分!如果你问太多问题,或者需要长时间深入回答的问题,那么你可能会被认为不尊重面试官的时间。所以,问两到三个问题是很好的经验法则,假设这些问题都可以在一两分钟内回答。而且,根据答案提问是可以的,但再次提醒,不要占用面试官太多的时间。
假设你通过大量的努力和准备,成功获得了第一份网页开发者工作。恭喜!我想讨论的最后一点是工作与生活的平衡理念。
记住,工作并非一切
本章的重点是帮助你找到工作,让你在网页开发领域获得有偿就业。但正如荷马·辛普森所说,“只工作不玩耍,荷马会变成什么什么。”当然,这是一个幽默的引用,指的是著名的电影《闪灵》,其中主要角色因为过度专注于工作而发疯(嗯,也可能与他所住的旅馆里的鬼魂有关,但在这里我们暂时不考虑这一点!)。
所以,虽然这一节不是关于找到第一份工作,但我认为它关乎一个重要的考虑因素,你应该始终牢记在心,即使在你寻找第一份工作的过程中:工作与生活的平衡。
疲劳过度在任何“知识工作者”行业,包括 IT 行业(可能在任何行业中普遍存在,但对于知识工作者来说似乎是一个更大的问题)都可能是一个大问题。当你热爱解决问题和创造事物时,这一点尤其正确,因为你可能会为了这些目标而工作更长、更努力,因为你完全沉浸在你所做的事情中。你有时会听到“进入状态”或“进入心流”的说法,这确实是一个真实存在的事情。建立你正在工作的心理模型需要时间,任何干扰都会让你退步,但这也意味着一旦你进入心流状态,你往往会稍微忘记周围的世界。
要明确的是,在某种程度上,这并没有什么错!热爱你所做的事情,享受工作,这并没有什么错。全神贯注于你所做的事情,这也没有什么错。
过度热爱你的工作
但很容易走得太远,尤其是在“紧急关头”来临时,那时它就变得危险了。这就是你的工作与生活平衡有时会失衡的地方。
当涉及到开发项目时,你有时会遇到“死亡行军”这个词。这意味着有一个即将到来的截止日期必须满足,你有时会看到人们工作很长时间。你有时会看到人们每天工作 12 小时或 14 小时,每周 7 天,以完成这个日期。
简单来说,这是危险的,而且也是事与愿违的。你不能长时间保持大脑处于高速运转状态,否则你的生产力会急剧下降。即使情况不是这样,人类是情感生物,将如此多的注意力集中在工作上,而忽视其他生活方面,最终会导致不良后果。
当你刚开始时,这是一件很难做的事情,因为你并不完全理解为什么它可能如此具有破坏性,而且你也想表现得很好,换句话说。你不想看起来像那个不付出额外努力的开发者。你被雇佣了,现在你必须产出,如果这意味着加班,那就这样吧——人们自然会有这样的心态。
但是我强烈建议你抵制这种冲动,并在必要时进行反击。除非你明确地想要这样,否则不要让工作成为你的生活。有些人确实想要将生活、饮食和睡眠都投入到工作中。如果你是这样的人,那么就忽略我这里所说的,继续前进!但这种情况通常不是大多数人。大多数人希望在工作和生活之外拥有自己的生活,有时你必须为此而奋斗。
明确一点,偶尔的额外几个小时不应该太大问题。有时,这是必要的。特别是如果你的生产系统出现了重大问题,即世界用来进行商业活动的那套系统,你可能需要投入额外的时间和精力来纠正方向,换句话说。但即使在这种情况下,好的公司也会为你支付时间——如果不是金钱上的,至少会允许你,例如,第二天早点离开来平衡一下。而且你也不应该害羞地要求这样做。
识别不良公司
不幸的是,并非所有公司都很好,所以你不能期望它,但这就是这次谈话的这一部分在求职过程中确实有一些影响:始终记住,你正在面试潜在的雇主,就像他们正在面试你一样!
询问关于工作/生活平衡的问题,并评估他们的回答。有时,可能会有一些不良情况的暗示……比如“嗯,我们希望每个人都在这里付出必要的努力”,或者类似的话,可能表明一个不重视工作/生活平衡的环境。为什么?因为不同的人对“必要”的定义不同。当然,每个雇主都希望员工付出坚实的努力,这是完全合理的。但期望你总是超出预期可能并不合理。所以,你必须弄清楚他们的意思。
你可以通过在类似情况下进行更多探询并自己做出决定来实现这一点,因为这里没有绝对的东西,没有总是会是红旗的明确声明(除了像“我们总是期望每个人都每周工作超过 40 小时”这样的明显事情,但这种情况很少那么明显!)。公司确实经常在面试中说出来他们的工作/生活平衡不足,有时是以微妙的方式——这是关键点——你应该始终寻找这样的线索并进行探询。例如,问“你们的工程师多久需要在周末加班?”可能是一个你可以问的探询问题。如果答案是“非常罕见,只有在极端紧急情况下”,那就与“嗯,这取决于我们的截止日期有多紧张”这样的回答大不相同。前者通常是合理的,但后者,至少在我看来,可能会表明一个管理不善的公司,并期望开发者经常填补空缺。这很可能不会是一个愉快的工作环境。
将这与你自己对该公司的调查结合起来,你可以得出一个有效的结论。像 Glassdoor(glassdoor.com)这样的网站对此很有帮助,因为你可以在这里找到来自公司实际员工的匿名反馈,这可以为你提供关于在那里工作的可能性的可靠见解。
在你求职的早期阶段,你可能会倾向于忽视这些担忧,但我恳求你不要这样做!你不想一年后发现自己讨厌这份工作,因为你总是不得不工作很长时间,这会消耗你的非工作时间。这会导致你迅速过度劳累。所以,不要害怕在面试过程中提出深入的问题——在面试前后做一些研究,并相信你的直觉。如果某个机会让你犹豫,那么无论你多么需要一份工作,可能最好继续寻找。在我看来,工作与生活的平衡太过重要,不能牺牲。
检查路线图
随着我们结束这本书的技术部分,你还有几个项目尚未揭开。在图 9.1中,你可以看到更新后的网页开发者路线图:

图 9.1:包含更多填充框的路标
通过本章,你开始揭开职业路径分支,以及展示能力、面试技巧和代码测试方块。在下一章中,我们应该能够完成整个职业****路径分支!
摘要
在本章中,我们讨论了在网页开发世界中找到第一份工作。我们探讨了 FAANG 这个术语,它的含义以及它如何影响你的求职过程。我们查看了一些在线寻找职位空缺的方法,以及如何准备一份优秀的简历以备申请。我们还讨论了 Git 仓库、作品集和样本,以展示和证明你的能力。然后,我们讨论了面试,它们可能的样子,如何应对它们,以及如何回答一些常见的问题,通常为面试体验做准备。最后,我们讨论了保持工作与你的非工作时间平衡的重要性,以避免过度劳累。
在下一章中,我们将讨论在你找到第一份工作之后会发生什么,以及你可以做些什么来确保你的成功。我们将探讨优秀开发者的良好习惯,以及你在该领域积累经验时应注意的事项,以帮助建立你的职业生涯。
第十章:作为网络开发者,发现高质量工作的关键
学习编码并找到网络开发职位是好事,但一旦你做到了这一点,你就应该开始思考你可以做些什么来确保你表现良好。而且不仅仅是表现良好,还要随着时间的推移继续提高你的表现,如果你愿意,还可以帮助你向上攀登成功的职业阶梯。
我在谈论诸如关注细节、学习有效地使用搜索引擎和其他在线工具、如何有效地使用那些不专注于生成代码而是确保你编写代码的质量的开发者工具,以及你的多任务处理能力。这些都是任何人都可以学习和了解的事情,无论你的经验水平如何,而且随着时间的推移,这些应该成为习惯(即使过了一段时间后你不再有意识地思考它们)。
这些技能之所以重要,是因为目标是始终以高质量的标准工作,并始终产出稳固的结果,但又不至于让自己过度劳累。你的老板会希望你这样做,但如果你对自己的工作感到自豪,那么让老板满意(相对而言)就会变得容易。
因此,在本章中,我们将涵盖以下主题:
-
小事也要上心
-
锻炼你的 Google-Fu
-
识别 MVP 网站
-
工具的使用——其他需要了解和使用的工具
-
多任务处理(就像风中的 CPU 一样!)
小事也要上心
你可能听说过“小事不用太在意”这句话。这里的观点是不要因为担心那些影响较小或你无法做太多的事情而让压力积累。然而,在前面几章中,我提出了关注细节是优秀开发者关键属性的观点。“小事也要上心”,正如我所说的,这正是网络开发的核心。
注重细节是一个重要的技能,需要你在自己身上培养,因为虽然大错误会导致大问题,但小疏忽有时也可能演变成重大问题,尽管不一定有同样的紧迫性。
因此,在这个领域,你确实需要小事也要上心,但这里的短语并不是关于管理压力——而是关于尽可能详尽和准确地完成工作,考虑到尽可能多的因素,无论大小。这是制作尽可能稳健、高效和零缺陷工作的关键技能。
仔细审查代码、深思熟虑系统架构,并确保你的设计满足当前和未来的需求,这是我们最感兴趣做好并给予适当关注细节的事情。
例如,假设你正在构建一个涉及存储用户信息的网站。这涉及到将信息写入关系型数据库。你可能会决定用户表的关键——唯一标识用户的列——应该是每个人的名字和姓氏的组合。这乍一看似乎是合理的,事实上,它至少在一段时间内会运行得很好。但如果两个同名同姓的人——这种情况在现实生活中确实可能发生——尝试注册会发生什么呢?由于数据库设计中相对小的错误,新用户可能因为姓名冲突而无法注册。或者更糟糕的是,根据代码的编写方式,系统可能会将两个人的数据混合在一起。具有讽刺意味的是,当我写下这些话的时候,我正在处理一个情况,我的保险公司不知何故将我和我父亲的账户混合在一起,因为我们有相同的名字和姓氏。你不会认为这样的事情真的会发生,但你大错特错了!
因此,学会关注细节很重要。这也可能很困难,因为,尤其是在你职业生涯的早期,你不会有太多经验可以依据。在那个阶段,你能做的只是尝试思考各种场景,模拟它们,并尽可能多地寻找陷阱。经验并不是成功的保证,也并非总是需要的。逻辑思考——这是我多次提到的一个观点——最终才是关键。当然,你拥有的经验越多,你将能够更好地进行逻辑思考,因为你有更多的信息可以依据,但这并不是关注细节的全部。
如果你过于专注于项目的细节,你也需要稍微小心一点,因为正如俗话所说,你有时可能会因为树木而忽略了森林。你始终需要关注大局,并保持对细节所努力实现的总体目标和目标的视野。找到正确的平衡是必要的,而这主要来自于实践。你必须做这些事情,锻炼这些心理肌肉,以便随着时间的推移正确地建立它们。
当谈到注意力时,最重要的考虑因素可能是专注,以及你如何保持你的思维指向正确的方向,或者换句话说:活在当下。
活在当下
一个可以帮助你提高注意力的技巧是“活在当下”。我们所有人很容易被生活中各种各样的考验和磨难所分散注意力——我们几乎总是有一些事情在脑海中萦绕,我们担心的事情或者我们试图解决的问题——但关键是要学会将这些事情深深埋藏在心底,专注于此时此刻,专注于手头的主要问题。
如果你的思绪飘散,专注于其他事情,比如之前提到的那个数据库——即使你没有立即意识到这一点——你往往会发现你的注意力不在需要的地方,你可能会错过可能导致更好决策的细节,比如如果你使用名称,数据库键的问题。如果你在专注于那个设计任务,不让你的思绪飘散,你可能会想到数据库键的问题(当然,没有任何保证,但你至少想给自己最好的机会)。
虽然保持当下是一个普遍的概念,但你可以通过一些具体的步骤来帮助你实现这一点——我们称之为正念练习。这些练习旨在帮助你将注意力集中在当下,并帮助你更加意识到你的思想、感受和周围环境。它们对于提高专注力、减少压力和提升整体福祉非常有益。
具体来说,以下是一些你可以练习的常见正念练习:
-
正念呼吸:这可能是最基础的正念练习,但同时也可能是最有用的。只需将你的注意力集中在呼吸上。注意吸入和呼出的节奏。这当然是你身体的自主功能,是你很少关注的事情(除非有问题发生!),所以专注于它可能是一种有趣的经验。当你的思绪不可避免地飘散时,轻轻地将其带回到呼吸上,并重新集中注意力在当下。这可以在任何地方、任何时候进行,即使只有几分钟,不仅能帮助你学会关注细节,还可以作为简单放松的绝佳方式,这也有助于提高对细节的关注。
-
身体扫描冥想:这种练习可能不适合在办公环境中进行,但你可以在家中做。你只需躺下或坐在舒适的椅子上,然后缓慢而故意地将你的注意力集中在身体的每一部分,从脚趾到头顶。换句话说,你正在“扫描”你的身体。在这个过程中,注意身体每一部分的任何感觉、紧张或不适,并尝试放松你注意到的任何紧张。
-
正念观察:为了做这个练习,选择你附近的一个物体——任何物体都可以——然后专注于观察它一分钟或两分钟。注意你能注意到的关于它的每一件事,就像你第一次看到它一样去观察。如果你碰巧靠近窗户,找到一只鸟、松鼠或其他动物可以是一个特别有趣的“物体”来用于这个练习。
-
正念倾听:与正念观察不同,这个练习涉及倾听你环境中的声音,无论是鸟儿的鸣叫、树叶的沙沙声,还是远处的交通。
-
正念饮食:虽然这个练习可能不会在执行特定任务时帮助你关注细节,但它可以通过一个常见的例子来锻炼你的技能。这个练习涉及到全神贯注于简单、日常的饮食体验!当你吃饭时,注意食物的颜色、质地和味道。慢慢咀嚼,注意每一口的感觉和味道。你几乎可以把它变成一个游戏!这将训练你的大脑更加专注于当下,让它习惯于这种感受。
-
行走冥想:在类似的方式中,练习行走时的正念。这涉及到意识到你的脚接触地面的感觉,你步伐的节奏,以及你的呼吸。注意这样一个简单的事实:你的腿几乎是在自己移动!这是一个很奇怪的认识!此外,在行走时,注意你周围的环境、声音和气味。
-
正念拉伸或瑜伽:许多人定期练习瑜伽,原因很好:它可以帮助集中注意力!幸运的是,这不需要购买专业的瑜伽垫,也不需要在你的城市找到一个好的瑜伽班。相反,你可以坐一个舒适的姿势,进行缓慢、有目的的轻度拉伸,在移动时注意肌肉和关节的物理感觉。这里的关键是要非常温柔,因为这个练习旨在放松,所以你不想做得太过分,尤其是如果你这不是一个常规活动的话。
最后一个一般性的“练习”就是简单地休息几分钟,让你的思绪随意飘荡一会儿!你会惊讶地发现,仅仅放松一两分钟,反而可以帮助你在之后重新集中精力完成任务。此外,这也给你的大脑留出时间在较低层次上处理事情,这往往是突破棘手问题或挑战所必需的。
当然,关注细节和努力做好每一件小事是我们的目标,但到了某个阶段,你必须知道一些东西才能完成你的工作。但没有人知道所有的事情,包括我自己!然而,我们所有人都必须知道的是如何找到我们需要的信息,并在前进的过程中获得我们所需的知识,而在当今这个时代,实现这一目标的主要方式之一就是通过在线搜索引擎。现在让我们来谈谈这一点。
锻炼你的 Google 技能
正如我之前提到几次的那样,作为一名网页开发者,你不需要一开始就知道所有的事情。知识太多了——太多需要一次性保存在你的“肉脑”中。相反,期望你的是,你在大脑中保留一些一般概念,尤其是关于你目前使用的东西——如果你目前正在大量编写 JavaScript,那么记住数组上可用的方法是很合理(而且幸运的是,很自然)的,因为你将经常使用这些方法。
但始终期望的是,当你需要信息时能够搜索到它,并在行动中将其综合起来。术语“Google-Fu”经常用来描述这种能力,俏皮地将“Google”和“Kung-fu”结合起来,以描述一个人对 Google(或任何搜索引擎)的技能和高效使用。这涉及到快速找到精确信息的能力。
你可能认为这仅仅意味着输入正确的关键词,这确实是其中很大一部分,但它还涉及到理解搜索引擎的工作原理,并使用高级搜索技术,如布尔运算符、过滤器以及特定的搜索命令,以创建更好的搜索词,更快、更准确地获取你需要的信息。
让我们来看看一些关键点,这些点对于培养你的 Google-Fu 技能至关重要:
-
有效的关键词选择:选择精确、相关的关键词对于快速获取你需要的信息是必要的,这并不令人惊讶。例如,搜索
array methods不会提供与javascript array methods相比如此具体的搜索结果。然而,你也必须意识到,过于具体可能会导致错过一些有用的结果。例如,搜索javascript array methods that deal with collections可能会过滤掉一些处理方法如map()和reduce()的网站——这是因为一些网站可能会讨论这些方法,但实际上并没有使用“collections”这个词,所以它们与你的查询匹配程度不如其他网站。这些网站也可能在结果中排名较低,导致你无论如何都会错过它们。这都是在寻找一个合适的平衡点,既要足够具体,又不要过于具体。 -
布尔运算符:Google 允许你使用与大多数编程语言相同的布尔关键词,让你能够构建更精确的搜索词。例如,搜索
javascript array methods for collections not map可以获取你需要的结果,如果你知道map()没有涉及。and、or和not是这种用法中最常见的布尔运算符。 -
引号:当使用谷歌等搜索引擎时,你必须理解使用引号和不使用引号之间的区别。当你使用引号时,你是在告诉谷歌搜索那个确切的短语。搜索
"javascript methods of array"只会找到以那种精确方式说javascript methods of array的页面。没有引号时,包含单词
javascript、methods和array的网站将会出现在结果中(谷歌和大多数其他搜索引擎会丢弃常用词如of和an)。与有效的关键词选择一样,使用引号搜索词可能会使你的搜索过于具体,从而导致你错过好的结果。 -
星号:在搜索词中,星号可以用作通配符。通配符基本上意味着“可以是任何东西”。例如,搜索
*zamm*意味着只要zamm出现在结果中,它之前和/或之后可以是任何东西(或者什么都没有),它都会匹配。所以,例如,包含 Zammetti 的结果会被找到(因为zamm之前没有东西,前面的星号允许,以及之后有etti,后面的星号允许),以及应用数学和力学杂志(其名称为 ZAMM,因此也会满足通配符),ZAMM 食品,Zamm 顾问,以及 Alex Zamm。你应该谨慎使用这个功能,因为它经常会带来很多与你所寻找内容无关的结果,但有时它也可能很有用,尤其是当与其他关键词以及可能的布尔运算符结合使用时。例如,搜索
*zamm* mathematics, not advisors可能会得到更符合技术材料的结果,同时排除那些关于云迁移公司(Zamm Advisors)的结果。 -
特定网站搜索:有时,你可能知道你正在寻找的内容可以在某个特定的网站上找到,但你可能不确定具体在哪里。在这种情况下,谷歌允许你通过在搜索查询前加上
site:来搜索特定网站。例如,为了在流行的电影数据库网站 IMDB 上搜索关于电影《食人鱼》的信息,你可以搜索site:imdb.com predator。作为一个网页开发者,在寻找代码中如何做某事的示例时,专门搜索 GitHub 是很常见的事情,所以site:github.com是一个需要记住的。 -
文件类型搜索:尽管不太常见,但谷歌和其他搜索引擎也允许你搜索特定文档类型。如果你正在寻找 PDF 文件,你可以在搜索查询前加上
filetype:来搜索 PDF 文件。例如,你可以通过搜索filetype:pdf zammetti flutter来找到关于 Flutter 的我的书中的一些章节。 -
基于时间的过滤器:大多数搜索引擎都提供限制结果到特定时间范围的能力。在 Google 中,你可以在搜索框下方找到工具菜单,这是在你进行搜索之后应该看到的。在这里,你可以请求只显示过去一周或一个月的结果,例如。当你需要查找有关最近的安全漏洞的信息时,这可以非常有帮助。
能够通过像 Google 这样的搜索引擎快速有效地搜索答案是一种你可以练习的技能。要做到这一点,只需思考一个主题,然后开始尝试进行一些搜索。一般来说,如果你必须向下滚动到第五个或第十个搜索结果,那么你可能需要改进搜索查询。过了一段时间,你会发现前几个结果通常会包含你需要的内容;否则,它会告诉你你的搜索查询还不够好。这是一个普遍的规则,但总会有例外,但我发现,当你构建好的查询时,这几乎总是正确的。通过使它们更加具体来继续改进那些不能快速给出所需结果的查询。
这又是一个普遍的规则:从一个更一般的查询开始,只有当你没有得到你需要的内容时,才开始使其更加具体。这种策略将帮助你不会因为过于具体而错过好的结果。然而,这个规则的一个大例外是,如果你正在研究一个特定的错误信息。在这种情况下,我鼓励你复制整个错误信息并直接搜索它(尽管我总是建议在它里面不使用引号)。你将是第一个遇到特定错误信息的人的情况极其罕见,因此你很可能会在前一个到三个结果中找到满意的答案。
搜索引擎现在也将人工智能集成到其搜索结果中。例如,在撰写本文时,Google 通常会显示一个生成式人工智能实验部分,位于常规搜索结果之上。这些结果对于某些类型的搜索来说非常好。例如,搜索“如何在 JavaScript 中排序数组”可能会得到一个很好的结果,如图 10.1所示),主要是因为生成式人工智能在您自然提问时通常比您在直接进行网络搜索时搜索一系列关键词时表现得更好:

图 10.1:一个 Google 搜索立即产生有用结果的示例
这些天,我的策略是始终从一个直接的问题开始,就像我是在向另一个人提问一样,看看 AI 的结果如何。如果没有,那么我才会转向查看网络搜索结果。即使如此,写一个直接的问题通常也能得到更好的结果。根据我的经验,这取决于问题的普遍性。例如,如何在 JavaScript 中排序数组是开发者经常问的问题,所以,除了 AI 结果之外,你很可能在第一次网络搜索中就得到一个好的结果。
如果我发现即使采用那种方法也没有得到好的结果,那么我才会转向一个更以关键词为中心的策略。所以,在这个例子中,我可能会尝试搜索sort array javascript。当然,我还会开始查看结果列表的更下方,尽管这些结果的质量在最初几项之后往往会下降。
当然,随着你越来越多地使用搜索引擎,你开始意识到,很多最好的结果都来自少数几个常见的网站。这些就是我所说的最有价值球员(MVP)网站。这些网站是为我们开发者量身定制的,或者为开发者提供了一个聚集的地方,所以有时候,直接访问这些网站会更好。但无论你是直接访问还是通过搜索结果到达,了解它们都是有益的,所以现在让我们来看看其中的一些。
识别 MVP 网站
互联网上有许多有用的网站,你在你的网络开发之旅中会遇到很多,但我认为有两个特别突出——Stack Overflow(SO)和Mozilla 开发者网络(MDN)。你可能发现,你大部分的时间和搜索结果都指向它们,所以让我们来看看这两个。
SO
SO (stackoverflow.com) 已经迅速成为全球开发者景观的一个核心部分。由两位名叫 Jeff Arwood 和 Joel Spolsky 的绅士于 2008 年启动,它为用户提供了一种问答格式,特别是为开发者提供提问和回答关于软件开发及相关主题的问题。
经过几年的发展并积累了大量内容,它现在已经成为一个数据库,包含了各种开发者知识。这就是为什么与软件开发相关的搜索结果经常指向那里。但作为一个独立的网站,它仍然非常活跃,你可以独立于搜索引擎使用它。
一旦你访问 SO 并创建一个免费账户,你将能够以各种类别和标签提问,以标识其内容,然后坐下来等待人们回答!而且这是最好的部分:当你回答问题时,你会获得声誉点,这激励人们回答。虽然在我的经验中并不常见,但能够说你在 SO 上有一个高声誉分数有时在面试中会给人们留下很好的印象,所以当你准备好时回答问题是有价值的。
此外,问题和答案都由 SO 社区进行投票。问题获得更多赞同意味着更高的可见度,这通常意味着更多的答案,赞同有助于回答者获得更多声誉。所有这些都导致了一种基于社区的监管,使得“好”的问题和答案在某种程度上得到提升,而“坏”的问题和答案则被推下(声誉更低,可能在搜索结果中出现的频率更低,等等)。
图 10**.2 展示了一个 SO 问题和对其的回答的例子。注意网站并不花哨,这很好,因为它保持了内容的焦点,这才是最重要的。从这张截图可以看出,这个问题收到了五个回答,其中有一个获得了九个赞同。这个问题被提问者接受,如下面的投票计数下的勾号所示。我很快就会谈到这一点,但请注意这个回答以及提供者如何提供几个替代方案,代码格式良好且解释得当(通过评论)。这就是我认为的“好”答案:

图 10.2:SO,全貌尽收眼底
这自然引出了一个非常公平的问题:什么是构成“好”和“坏”问题与答案的标准。由于是社区驱动的,没有硬性规定。你总是某种程度上受制于构成社区的其它开发者的随意性。但也有一些一般性指南需要记住。
提出好问题
首先,在你提出问题之前,确保花些时间在 SO 上尝试找到合适的答案。正如我之前提到的,你很少是第一个遇到某种情况的人,所以你打算问的问题很可能已经被回答了。在 SO 上发帖提问并立即得到一个(并不特别礼貌)的回答,说它是现有问题的重复,这种情况并不少见。尽量通过自己找到答案来避免这种情况,无论是通过 Google 还是直接在 SO 内部搜索。
接下来,在提问时,友好地开始是有效的方式,这应该不会让人感到惊讶。像“嗨!我在X上遇到问题,并希望得到任何我能得到的帮助”这样的简单事情会让你显得像是一个人们愿意帮助的人。不要显得你理应得到答案——这就是关键。
当你得到答案时,即使有些答案没有帮助,甚至非常无礼(提前警告,有些答案确实会这样),也要继续保持专业、耐心和礼貌。开发者有时可能是一群直言不讳的人,但这并不意味着他们在恶意或不是真正试图帮助。给他们一个好处,如果需要,扩展问题,并始终提供额外信息,同时保持礼貌。当然,如果你遇到那些似乎只是想让你过得艰难的人,忽略他们是完全可以接受的,在这种情况下,这可能是最好的做法。
之后,重要的是要表明你已经做了一些工作。像“我试图让我的应用程序在 Tomcat 中运行,但它不起作用,它给我一些错误,请帮助”这样的问题肯定会被忽视,或者被踩。相反,详细说明你迄今为止为解决问题所尝试的方法,并尽可能提供详细信息:
-
你具体遇到了什么错误?
-
当它发生时,你具体尝试做什么?
-
你在哪里进行了研究,试图自己找到答案?
-
如果那样做不行,你在决定需要帮助之前,自己尝试了什么?
SO 上的用户真的很不喜欢那些看似没有尝试自己解决问题的人提出的问题,所以你想要清楚地表明你已经尝试过,这意味着提供详细信息。
尽量隔离问题。换句话说,尝试从问题中尽可能多地移除内容,以便尽可能集中。如果你有一段不起作用的代码,尝试移除任何你确信是有效的且对理解问题代码不必要的代码。开发者很少需要看到一百个不同的函数来找出为什么某个函数不起作用,所以不要在问题中添加大量无关内容。
此外,务必详细说明你想要得到的精确结果,并为帖子写一个清晰、简洁但尽可能简洁的标题。标题应尽可能用最少的词语描述问题。例如,“帮助,JavaScript 问题”过于笼统,没有价值,但“JavaScript 数组排序未产生正确排序的数组”则更为具体,而且也不算太长。另外,确保你每篇帖子只真正问一个问题;否则,事情可能会变得非常混乱,而且很快就不会有帮助。
随着答案开始陆续到来,你可能需要稍微参与其中,以获取你需要的信息。这可能意味着澄清你的问题,或者提供所需的信息。记住,Stack Overflow 是你与愿意帮助的人之间的对话。有时,你可能会立即得到你需要的答案;其他时候,你可能需要经过一些来回的交流才能得到。当然,如果花费了一些时间或者最终没有得到答案,要抵制住沮丧的冲动。请记住,这些人是在花费他们的时间和精力来尝试帮助你,所以即使他们给出了错误的答案,也不要怪罪他们。他们几乎肯定不是故意这么做的!就随它去吧,记住,即使是错误的答案有时也可能是有帮助的,甚至可能让你看到之前没有看到的东西,并因此找到答案。
最后,当你找到好的答案时,一定要为它们点赞,并确保接受那些帮助你解决问题的正确答案。这对保持声誉系统正常运作并继续为人们提供互相帮助的激励很重要。有些人之所以在 Stack Overflow 上花费很多时间,就是为了回答尽可能多的问题,以获得尽可能高的声誉,而有些人只是偶尔的访客,在他们能帮忙的时候帮忙。无论如何,你想要确保他们得到帮助你的认可,这样他们就会更有可能下次也帮助别人——也许那又是你!
我要提到的最后一件事是,仅仅在 Stack Overflow 上写出一个好的问题,就能帮助你自己发现答案!我无法告诉你我花了多少时间来撰写一个问题,而在 halfway in 我突然意识到答案了。这就像橡皮鸭调试一样发生。橡皮鸭调试是指开发者——有时甚至非常直接——将问题与橡皮鸭讨论。是的,一个真实的、物理的橡皮鸭,就像孩子们在浴缸里经常玩的那样!我知道这听起来很奇怪和愚蠢,但仅仅解释一个问题,讨论你尝试解决它所做的一切,以及详细说明你认为可能的问题,这就是解开你的大脑并得到你需要的“eureka!”时刻的全部。所以,无论你是真的买了一个橡皮鸭,还是只是在 Stack Overflow 上打出一个问题,最终甚至没有发布,有时候这本身就足以完成任务,尽管看起来很奇怪!
提供好的答案
当然,提出好的问题只是方程的一半。特别是在你作为网络开发者旅程的早期,你大部分时间都会处于提问者的位置。但最终,你可能会发现自己信心和经验到了一个点,你想要回答一些问题。你甚至可能会发现,你可以早期回答一些问题,因为你恰好遇到了别人正在努力解决的问题。无论如何,提供好的答案是让 Stack Overflow 正常运作的重要因素。
确保你的回答直接明了。你经常会发现一些回答虽然总体上不错,但却偏离了主题,讨论为什么这项技术或设计选择并不理想——这并不总是坏事,有时回答特定问题非常必要,但有时,这只是某人表达未经请求的观点,对提问者并没有实际的帮助。所以,尽可能专注于提供对特定问题的具体答案。
回答问题时,始终要记住,并非所有开发者都处于同一水平。有些人刚开始,有些人比你有更多经验,以及介于这两者之间的所有水平。尽量减少假设,并始终保持礼貌。无论发生什么,都要尊重和职业。当然,你可能会收到像“不,那是错的,我试过了,下次做得更好”或类似无礼的回复。但最好是忽略它,而不是与某人开始一场口水战(尽管他们确实应该得到这样的回复!)。这根本不值得你浪费时间和精力。
回答时,你希望尽可能清晰简洁,这可以通过提供简单的例子来帮助。有时,一个简单的例子胜过千言万语。你还需要尽可能以最简单的方式解释你的解决方案。再次强调,不要假设其他人知道什么——就像你确信他们不知道一样,解释每一个细节。
确保在适当的地方引用参考文献。这是一种极好的帮助他人学习的方式,通过展示他们可能不知道的信息来源,并允许他们在超出他们提问的问题范围之外学习,而不会显得过于强硬。他们可能会得到你给出的答案,但随后会阅读你用来得出答案的文档,并在这个过程中学习到他们之前没有考虑到的其他东西。
最后,虽然我已经说过几次,但我认为这很重要,所以我想再说一遍:无论提问还是回答问题,无论你的互动如何,始终要选择高尚的道路!SO 有时会因有毒的互动而闻名,但如果你保持积极的态度,我认为你会发现这种情况非常罕见(如果发生,也容易忽略)。记住,SO 是一个村庄,一群人相互交流和合作。就像任何村庄一样,可能有一些不太愉快的人四处游荡,你可能会遇到。尽你所能,永远不要成为他们中的一员,我相信你会发现 SO 是一个宝贵的资源。
当然,SO 是一个社交环境,你在这里与其他人互动。但这并不是唯一的选择。你通常会通过阅读各种网站上的文档来获得答案。但作为一个特定的网络开发者,你可能发现自己经常访问的一个网站就是 MDN。那么,让我们现在就来谈谈它。
MDN
MDN 或 MDN Web Docs,有时也这么称呼,是面向网页开发者的最全面资源之一。该网站提供关于所有网络技术的参考文档、教程、示例和指南。它是一个非常受尊敬的信息来源。您可以在以下链接找到 MDN:developer.mozilla.org。在 图 10.3 中,我选择了一个 JavaScript 对象来展示一些关于它的参考材料:

图 10.3:MDN
MDN 的一些亮点如下:
-
文档:MDN 以其详尽的文档而闻名。它涵盖了主要网络技术,如 HTML、CSS 和 JavaScript,还涵盖了网络开发的其它主题,例如重要的协议、浏览器的网络扩展和可访问性。内容极其详细且组织良好,并包含大量示例。所有这些使其成为初学者和经验丰富的开发者宝贵的资源。
-
社区驱动:虽然不像 SO 那样是一个社交网站,但 MDN 仍然具有其社区驱动的特性。来自世界各地的专业开发者和志愿者定期以更正式的方式为其内容做出贡献,但不如 SO 网站那样。这有助于确保提供的信息始终准确且最新,并考虑到最新的网络标准和最佳实践。MDN 维护者与网络标准机构如 世界万维网联盟(W3C)和 网络超文本应用技术工作组(WHATWG)紧密合作,以确保文档反映了最当前的网络标准。
-
教程和指南:虽然 MDN 提供了大量参考材料,但它还提供了针对新接触网页开发或希望扩展技能的人的教程和学习路径。这些资源旨在实用且易于遵循,并为您提供所需的知识和技能,因此您可以考虑它不仅是一个参考网站,也是一个学习网站。
-
可访问性和国际化:MDN 强调网络可访问性信息,有助于确保您使用其提供的信息构建的网络内容对残疾人士可访问(我们已在 第五章 中讨论了可访问性)。它还提供多种语言的内容,反映了其全球用户基础。
-
兼容性和浏览器支持:MDN 包含了关于不同网络特性浏览器兼容性的详细信息,这对于旨在创建跨平台网络应用的开发者至关重要。有了它,您可以确保给定的特性将按预期在一系列浏览器中工作,从而最大化您构建的网站受众。
-
开发工具:MDN 还提供了关于浏览器开发工具的指南和参考。这有助于开发者通过探索开发工具的使用方式来调试和优化他们的代码。
-
开源和免费: MDN 是一个开源资源,其内容免费提供。这得益于 Mozilla 使命,即保持互联网开放和可访问。他们最知名的产品,Firefox 浏览器,是这个目标的典范,但 MDN 也不遑多让!
与 SO 不同,MDN 不是一个社交网站。在那里,你不会体验到 SO 上的那种与其他开发者的互动。但它是你可以书签并用作参考材料来源的绝对最好的网站之一。它非常准确,始终是最新的,并且在所有重要的方面都非常可靠。
当我们谈论作为开发者为了成功可以做的事情时,另一个考虑因素是明智地使用各种帮助你编写更好代码的工具。IDEs 当然是其中之一,但还有独立的工具(这些工具通常也可以集成到你的 IDE 中)可以检查你的代码并帮助你改进它。我们将在下一节讨论其中的一些。
工具集 – 其他需要了解和使用的相关工具
当你编写代码时,是你与机器对抗。你编写代码,运行它,查看它做得对或不对,然后调试它。你一次又一次地这样做,直到它按预期工作。你就是在数字形式中塑造那块粘土!
但几乎总是有你可以做的事情来让它变得更好。除非代码很小且简单,否则通常是这样的情况。也许其中还有你尚未发现的错误。也许你可以做些事情来提高它的性能。也许存在只有在某些非常具体的情况下才会成为问题的安全问题(不幸的是,威胁行为者非常擅长找到这些问题)。或者,也许它只是没有像它本可以的那样格式化得那么一致。
在所有这些情况下,你不必孤军奋战!有些工具可以帮助你编写更好的代码。它们分为三个广泛的类别:静态代码分析(SCA)工具、代码检查器和格式化工具。它们之间有显著的交叉,通常,你会听到开发者一般地谈论“静态分析工具”,但它们之间有足够的差异,可以创建这三个广泛的类别。
然而,无论哪个类别,它们都涉及分析你的代码、报告问题和提供建议的工具。作为一名专业开发者,几乎肯定会使用这些工具。即使你最终在一个通常不使用这些工具的地方工作,作为一个好的开发者,你也应该自己使用它们,因为它们确实可以给你的工作带来很多好处。
让我们先从 SCA 工具开始讨论,这可能是这个类别中最广泛和最通用的。
SCA 工具
SCA 工具的目的是在不实际执行源代码的情况下分析它,提醒你那些你可能自己看不到的问题。
这些类型的工具通常从命令行运行,你只需告诉它你的源代码在哪里,它就会去执行其任务。其中一些具有 GUI,使得使用它们更容易一些。大多数都可以集成到你的 IDE 中,并从那里执行。
此外,将这些工具集成到 CI/CD 管道中并不罕见,有时甚至非常彻底。例如,构建通常会失败,如果发生任何 SCA 失败,会警告整个开发团队。这有助于确保代码在推送到并部署供人们查看之前处于良好状态,无论是开发环境还是甚至生产环境。
流行示例
虽然远非详尽的列表,但所有这些类别中我都会给出示例,其中一些更广为人知的 SCA 工具如下:
-
SonarQube:这是一个广泛使用的工具,用于查找代码中所有各种问题。例如,它可以指出在给定特定输入时你的代码可能会失败的地方,以及你的代码没有正确编码输出,因此可能导致安全漏洞的地方。它支持质量门的概念,即你可以设置的规则,用于确定审查是否通过或失败。由于它可以集成到 CI/CD 管道中,如果配置为这样做,构建可以根据这些门失败。虽然 SonarQube 通常被认为是一个非常不错的工具,但它的设置和使用可能稍微复杂一些。
-
Veracode:Veracode 与 SonarQube 类似,但几乎专注于安全问题。它是基于云的,这通常使得它比 SonarQube 更容易使用。它通常只需要上传你的源代码并点击一个按钮。你将得到一份详细的报告,其中包含其发现的结果,并直接链接到源代码。它不仅可以检测你代码中的问题,还可以指出你可能正在使用的具有已知漏洞的依赖项。Veracode 还提供动态分析选项,这意味着它可以在运行时扫描你的代码,这允许它检测静态分析无法检测到的缺陷。
-
Coverity:这款工具因其先进的静态分析能力而闻名,尤其是在检测安全漏洞方面。它声称测试每一行代码和潜在的执行路径,并清楚地指出每个缺陷的根本原因,使得问题修复变得容易。它可以检测的问题类型包括资源泄露(当你的代码没有正确清理自身并浪费了像内存这样的资源时)、使用未初始化的数据(一个非常常见的缺陷,通常会导致安全漏洞),以及控制流问题(当你的代码逻辑可能导致执行你不希望执行的代码)。
虽然一些提供了更强大的功能,但所有这些都可以集成到 CI/CD 管道中,也可以集成到 IDE 中,尽管每种情况下提供的能力可能差异很大。例如,虽然 Veracode 可以集成到你的 IDE 中并立即报告问题,但由于云托管模型意味着 Veracode 的全部功能不能在你的机器上运行,因此功能有限。这只是在使用这些工具时需要记住的一点。
此外,这些工具在支持水平上存在一些差异,但所有这些工具都支持多种编程语言和技术堆栈。其他工具针对特定语言(例如,FindBugs 是一个仅针对 Java 的 SCA 工具),因此研究每个工具以确定它是否满足你的技术需求是很重要的。
底线是,使用这些工具将帮助你及早发现问题,在大多数情况下在代码部署或运行之前。这意味着你的输出将更加稳固和健壮,这是最终目标。
代码检查器
代码检查器执行与 SCA 工具几乎相同的功能,但处于不同的层面。虽然 SCA 工具进行的分析可能非常深入,但代码检查器通常不会深入到代码中。SCA 工具通常会通过执行你的代码(不是字面上的执行,而是在它们可以分析代码并确定数据如何通过它的意义上)来更深入地分析,从而发现代码中的深层缺陷。
与之相反,代码检查器(linters)寻找特定的模式,已知容易导致错误的构造,以及不符合既定标准的代码。这意味着代码检查器通常比安全代码分析(SCA)工具要快得多,这意味着定期使用它们干扰性更小。相比之下,SCA 工具通常需要花费相当长的时间来执行其分析。代码检查器可以像 SCA 工具一样找到安全问题,但它们在更高的层面上这样做。
从本质上讲,可以将代码检查器视为将你的代码视为文本的工具,寻找可能表明问题的特定模式,而 SCA 工具则更像你在调试时作为程序员,你在脑海中逐行遍历代码。
代码检查器有助于维护一致的编码风格,使代码更易于阅读和维护,基于你设置的规则。再次强调,这仅仅是模式识别:如果配置了规则来告诉你,例如,如果你忘记在行尾放置分号,代码检查器可以告诉你。
由于不同编程语言之间的差异往往导致不同的查找模式,因此代码检查器几乎总是语言特定的。
流行例子
这里有一些流行的代码检查器例子:
-
ESLint:这是一个 JavaScript 的代码检查器。它可以非常灵活地配置,你可以在非常细粒度的层面上确定它应该使用的规则。它支持最新的 JavaScript 标准,这有助于确保你的代码以现代、健壮的方式编写。
-
Pylint: 这是一个流行的 Python 代码检查工具。它具有灵活性,类似于 ESLint,可以找到各种错误和代码异味(开发者倾向于认为虽然不是明显的错误,但通常不应该以某种方式编写的代码片段)。
-
CheckStyle: 这是一个流行的 Java 代码检查工具。它主要关注代码风格和格式,虽然也包含一些错误检测功能。它具有高度的可配置性,尽管这种配置可能相对复杂。
风格化工具
虽然代码检查工具会指出代码中的问题,但它们不会修改代码。这对于源代码分析(SCA)工具也是如此。另一类工具是风格化工具(有时称为格式化工具),它们确实会修改你的代码。它们可以自动将源代码格式化为特定的风格指南,确保整个代码库的一致性。与代码检查工具不同,它们不仅会标记问题,还会重新格式化代码以满足指定的指南。
风格化工具通常使用简单,并且通常可以配置以适应团队特定的风格和规则。然而,它们也可能存在风险。我并不是说它们会破坏代码——我不记得曾经看到过风格化工具破坏代码——但它们的风险在于它们可以非常快速地改变代码的结构。如果你没有正确设置规则,那么你可能最终会手头上有清理工作(希望这仅仅意味着更改规则并重新运行风格化工具,但这并不总是有效)。
风格化工具的主要目的是维护代码的一致性和适当的格式化风格,但我必须警告您不要过分依赖它们。许多开发者认为他们可以以任何他们想要的方式编写代码,因为他们知道当他们完成时,将会运行风格化工具,这时代码将具有正确的风格。根据我的经验,这是一种不好的工作方式。
我的妻子是一位受过训练的大厨,每次我们一起准备餐点时,她都会强调一个真正的专业厨师会在烹饪过程中不断清理。他们不会简单地做饭时制造一个大混乱,然后在最后清理。他们不断清理自己的工作台,因为这会导致更少的错误(你不想因为在一个混乱的工作台上滑倒而割伤自己)并得到更一致的结果。我已经开始以同样的方式看待编码。
我见过开发者编写代码时,有时用两个空格缩进,有时用四个空格缩进。有时,他们在行尾放置花括号,有时则单独在下一行放置。当然,代码本身并不在乎——它无论如何都能正常工作。但这种随意性会导致更多错误,因为不一致性往往会使代码更难以跟踪,这也是我们为什么关心它的原因。
我相信,就像专业厨师一样,专业开发者会边做边清理。对我们来说,这意味着在编写代码时,要写出格式良好且最重要的是格式一致的代码边写边格式化。是的,你应该在最后使用一个格式化工具来捕捉你犯的错误,因为毕竟,我们都不是完美的。但简单来说:你希望这个格式化工具尽可能少地工作!在你的代码中应该很少有它需要改变的地方,因为你实际上已经自己正确地格式化了。养成这个习惯,让它成为你不用思考就能做的事情,它不会妨碍你的工作。我会争辩说,这会使你更有效率,因为你的大脑在调试时不需要处理你工作的不一致性。
流行示例
你可能会遇到的一些流行格式化工具如下:
-
Prettier:最受欢迎的代码格式化工具之一,它支持许多语言,并与大多数 IDE 以及普通的文本编辑器集成。通常只需按下一个键组合,你的代码就会立即被重新格式化。虽然 Prettier 可以配置,但在这个领域它故意有些限制。相反,Prettier 是我们所说的有“意见”的工具。默认情况下,它使用一组合理且常见的设置,目标是让你根本不需要对其进行配置。
-
Black:一个知名的 Python 代码格式化工具,以其对 Python 代码格式化的坚定方法而闻名。它比 Prettier 更有意见,因此配置选项更少。只要你喜欢它提供的风格,它就非常出色,几乎不需要任何设置成本就能立即为你工作。
-
Gofmt:Gofmt 是 Go 语言编写的代码的默认格式化标准。它几乎被所有 Go 开发者普遍使用。它实现了 Go 社区编码标准,并提供了相对较少的配置选项。然而,它非常快,并且被广泛采用,如果你进行任何 Go 开发,几乎可以肯定你会使用它。
不论是 SCA 工具、linters 还是 stylers,将这些工具融入你的日常工作流程可以显著提高代码质量和可维护性,同时减少错误数量。将它们集成到你的开发流程中,包括 CI/CD 管道,可以简化代码审查,增强团队协作,并强制执行编码标准(这是开发者们通常不太愿意争论的事情!)。它们在大型项目或团队中尤其有益,确保尽管有多个贡献者,代码库仍然保持统一。
当然,没有工具能解决所有问题。有时,一切都取决于你和你的大脑以及你如何使用它。好吧,更具体地说,它总是是这样的!现在让我们看看这方面的某些方面。
使用最好的工具——你自己!
我们一直在讨论的工具都有明确的目的是以某种方式改进你的代码,无论是更无错误、格式良好,还是其他任何东西。这些工具是出色的,并且绝对应该使用。但有一个你应该始终使用,那就是你自己——你和你的大脑!
最好的开发者不会依赖于所讨论的工具;相反,他们会将它们作为备份使用,因为一直以来他们都在编写干净、稳固的代码。当然,没有人是完美的,这就是为什么我们需要这些工具来“监视”我们的后背,换句话说,捕捉我们遗漏的地方。但如果我们做对了,那么这些工具要捕捉的东西应该非常少,因为出于习惯,我们一直以来都在用适当的格式编写代码,并始终遵循稳健的开发原则。
但还有一件事你需要记住,那就是注释代码,因为虽然这些工具可以指出你没有很好地注释的地方,在某种程度上,这主要是一个你必须自己做的领域,因为一般来说,机器不知道你在想什么(一些基于新 AI 的工具开始改变这一点,但我们仍然处于这个领域的早期阶段,所以最好是自己培养良好的习惯)。
记录我们的代码非常重要,这就是为什么在代码中编写注释变得很重要。在你的职业生涯中,你无疑会遇到一些开发者告诉你,如果代码是“自文档”的,那么注释代码是不必要的。他们这是什么意思?
他们这意味着代码应该被编写得清晰且“明显”,其中一部分是良好的命名。例如,看看这段代码:
const ac = getAc("zammetti");
这会做什么呢?你可以看出getAc()函数将返回……某物。它将被存储在ac变量中。这一切都很明显。但getAc()返回什么?ac变量又是什么?
你可以用更清晰的方式重写这段代码:
const checkingAccount = getCheckingAccountFromLastName("Zammetti");
现在,很明显正在发生什么,因为变量的名称和函数的名称本身就清楚地说明了它们的作用。没有猜测的必要。这就是所谓的自文档代码:以某种方式命名事物,以便它们传达它们的目的信息。
然而,即使你这样做,也有一些信息仅仅通过良好的变量和函数名称是无法适当传达的。这就是代码中的注释发挥作用的地方。正如我提到的,关于代码注释有很多争议,一些开发者更喜欢尽可能少地注释,而另一些开发者则会争论说,你的代码中应该有比实际代码更多的注释!
根据我的经验,最好的答案往往介于两者之间。一个需要记住的关键原则是,注释应该解释代码的原因,而不是内容。换句话说,你不应该对每一行代码都进行注释,解释它做什么,因为——假设它像之前描述的那样写得清晰——这一行本身就已经告诉你它在做什么了。代码本身是内容,所以不要这样做:
// Get the checking account.
const checkingAccount = getCheckingAccuntFromLastName("Zammetti");
// Increment its transaction count.
checkingAccount.transactionCount++;
// Create a transaction.
const transaction = new Transaction();
// Add a credit.
Transaction.credit(100);
// Add transaction to account.
account.addTransaction(transaction);
// Save the updated account back to the database.
account.saveToDatabase();
相反,我建议注释这段代码的更好方式可能是如下所示:
// Add a transaction to the account and update it in the database.
const checkingAccount = getCheckingAccuntFromLastName("Zammetti");
checkingAccount.transactionCount++;
const transaction = new Transaction();
Transaction.credit(100);
account.addTransaction(transaction);
account.saveToDatabase();
虽然传达了类似的信息,但良好的命名变量和函数加上一些明智的注释就足够了。如果你在saveToDatabase()调用之后留一个空行,然后开始下一个动作,那么单行注释的好处就是逻辑上分组这些行。你可以轻松地看出这六行代码与更新账户相关,注释就像是一个路标,为它们指路。
但即使这样的注释也可能并非真正必要(除了前面提到的路标之外),因为它并没有解释代码存在的原因。为什么我们要添加交易并更新账户?事情的原因正是注释发挥作用的地方。解释代码各部分是如何协同工作的,非常适合添加注释。描述代码中可能存在的任何陷阱——这也是值得注释的。
例如,你可能会在getCheckingAccountFromLastName()函数上方写一条注释——大致像这样:
/**
* Perform a SQL database query against the MASTER accounts
* table to find a checking account based on a last name.
* The account will consist of the basic account details,
* as well as extended transaction data as a sub-object.
* Note that an exception is never thrown, an Account
* object will ALWAYS be returned. Used by the account
* list screen, as well as the processing subsystem.
*
* @param inLastName The last name to return an account
for.
* @return An Account object. If inLastName is
null, return an empty Account object.
If no account is found for the last
name, return an Account object with
an account number of -1 and all
other fields empty.
*/
不要专注于语法,因为语法会因语言而异(这是针对 Java 的)。但更重要的是,在这里,你可以看到函数的详细描述,以及可能帮助未来开发者(可能就是你!)的一些细节。你还可以看到它接受什么参数,返回什么,以及它的操作条件。
一些开发者说应该避免注释的原因之一是“注释在撒谎”。他们的意思是,如果你修改那个函数,使其在inLastName为null时返回null而不是空的Account对象——这种改变可能出于某种原因在以后是必要的——如果你不更新注释,那么注释实际上就会“撒谎”。它告诉你当inLastName为null时应该期待一个空的Account对象,但情况已经不再是这样了。
虽然有人说你可以通过不写注释来避免这个问题,这无疑是正确的,但这并不是一个好的答案。相反,正确解决这个问题的方法是将注释视为与代码一样重要。当你更改代码时,养成同时更改注释的习惯。一些开发者会先更新注释,从某种意义上说,为自己即将要做的工作提供一个指南。无论如何,不要将注释视为事后之想,这样你就不会倾向于得到“撒谎”的注释。
最后,养成多注释而不是少注释的习惯,但也要留意多余的注释。如果你发现自己更多地注释了正在发生的事情而不是原因,那么你可能做得太过分了,这也可能表明你没有以自文档化的方式编写代码(因为如果代码本身已经很明确,你还需要那么多注释做什么?)
但我可以告诉你,作为一个不得不处理大量代码且注释很少的人,我宁愿选择注释过多的代码库,也不愿选择注释稀疏的代码库,无论它写得多么自文档化。即使是糟糕的注释,也像糟糕的披萨一样:它仍然相当不错!最终,一切都关于平衡。过多的无用注释可能会妨碍,但不足同样糟糕。追求中间地带。
除了注释和养成良好的习惯之外,使用大脑的另一个方面是,在这个行业中,很多时候你需要大脑同时处理多件事情。让我们来探讨这意味着什么。
多任务处理(就像风中的 CPU!)
您电脑中的 CPU 是一项惊人的技术成就。一位名叫本·德里科尔的绅士在一次推文中说得最好:“如果你编写的代码感觉像是一种黑客行为,但又能工作,那就记住,CPU 实际上是一块我们欺骗使其产生思考的石头。”
最令人惊讶的可能就是它(看似)能同时做很多事情。当你使用 Windows、OSX 或基于 Linux 的桌面时,你可以同时打开多个窗口,同时运行多个不同的程序。虽然 CPU 不是唯一负责这一点的因素,但它无疑是关键部分,每当我停下来思考这一点时,都会让我感到震惊。
在一定程度上,作为一名网页开发者,你将不得不像 CPU 一样,同时处理多个任务。当然,人类一次无法像 CPU 那样做那么多,但 CPU 正在对你施展魔法。你看,它也不是一次同时做很多事情(我承认我在这里简化了一些,以便进行比较,但在高层次上这是真实的)。相反,CPU 所做的是在许多任务之间快速切换——快到在人类的时间尺度上保持了所有事情同时发生的错觉。
网页开发者通常必须以相同的方式工作。这是因为一个任务的结果通常会影响到另一个任务,但这个过程变成了循环的,因此是迭代的。
例如,当你为网站编写 HTML 和 CSS 时,你可能会意识到你之前准备好的图片不会完全按计划工作。结果,你必须回去重新做它们。你可以选择等到编码完成后再重新做图片——有时候这完全可行,可能也是最好的方法——但有时重新做图片可能会以某种方式反馈到 HTML 和 CSS 中,导致你必须现在进行调整(也许你决定需要在图片上添加阴影,这意味着你可能需要回去移除也添加阴影的 CSS)。
或者,在你编写代码时,你可能想要在工作过程中部署它,让你的客户看到。所以,你必须在编码和部署之间交替。
应该清楚的是,你必须像 CPU 一样在任务之间切换。希望你不需要像 CPU 那样快,也不需要那么频繁地这样做!理想情况下,作为一个人类,通常在开始另一个任务之前完成一个任务会更好。但这并不总是可能的。
如果你怀疑这可能会很有压力,那么你是对的,这确实可能会。那么,你如何应对这种情况呢?
保持笔记
我发现对我有帮助的是,对正在进行的任务保持明智的笔记。我会不时地写下我在特定任务上的进度,并记录我已经采取的步骤。我还会写下我计划接下来做什么,以及我认为接下来需要做什么。
这样做的优点是,当我必须暂时切换到另一个任务然后再回到第一个任务时,我并不需要从头开始重新构建整个任务的心理模型。相反,我有注释、大纲甚至简单的图表来帮助我更快地继续之前的工作。
我也倾向于将这些笔记保存在我可以随时访问的地方——我目前的选择是 Google Keep,但你可以使用任何让你在任何地方获取信息的东西。我发现这很有益处,因为有时你会在空闲时间或进行其他任务时突然想到一个任务,能够把关于这个想法的笔记记在所有其他关于这个任务的信息所在的地方会很有帮助。这样,当我回到任务时,想法就在那里等待着我(即使在我写下它之后立即从我的脑海中消失,这种情况经常发生!)
保持笔记是好的,但最终,你仍然需要在任务之间切换并管理这些任务,这可能会很有压力,不管有没有笔记。压力的一部分可能来自不良的时间管理。那么,你如何管理分配给每个任务的宝贵时间,以至少有机会减少压力?一种效果相当好的方法是时间盒。
时间盒
时间盒(有时称为时间切片)涉及在开始任务之前定义完成任务所需的时间,并坚持这个限制,无论发生什么。
可能你需要编写一些代码,所以你将其时间框设定为一个小时。然后,你离开去编码一个小时。但如果在 20 分钟后你意识到你需要修改你的图像,那么,而不是立即切换任务,你应该继续编码,直到你达到小时时间框的限制或者比那更早完成(这总是可以的)。只有在你达到小时时间框限制或者更早完成之后,你才会转向处理图像,并且你也会对那个任务进行时间框管理。
可能会有一些情况你必须打破时间框——比如,除非图像正确,否则你无法继续编码——但在大多数情况下,情况并非如此。即使你在工作中发现的任务通常也可以等到你当前的任务完成后再进行。当然,你可能会认为你在工作中发现的事情本身就是当前任务的一部分,在这种情况下,你的时间框仍然有效,但你可以在其中稍微调整你的工作。
当人们在项目上工作时,他们面临的最大问题之一,无论你是否有效地对任务进行时间框管理,都是中断,通常来自其他人。就像一个真实的 CPU 有时可能需要中断当前任务以处理重要的事情一样,人类的 CPU 也会面临这个问题。你该如何应对?
延迟和勿扰
技巧在于以对他人来说合适的方式延迟任何可以延迟的中断。这也需要你意识到中断是通过哪种沟通渠道发生的。
我的意思是,有人走到你的办公桌前和你开始交谈,这比收到一条短信、即时消息或电子邮件具有不同的紧迫性。希望人们使用适当的沟通渠道,但他们并不总是这样做。例如,如果有人有一个不需要立即回答的问题,那么电子邮件通常比电话更好。另一方面,如果他们有一个问题让他们无法继续工作(我们通常称之为阻碍者),那么他们可能别无选择,只能走到你的办公桌前并立即引起你的注意。
你需要意识到的是,除非情况严重,否则你有选择延迟的权利!你可以说:“我现在正忙于某事,等我空下来再给你回复。”是的,如果他们认为自己的需求更加紧急,他们可能不会像平时那样好接受,但到了某个时候,这对你来说就变成了“不是我的问题”的情况。毕竟,你还有工作要做,如果你不能专注于它,因为你必须帮助别人,那就很难完成。
然而,成为那个总是被认为乐于助人、通常有答案的人是一种美好的感觉,但考虑到你正在做的工作,这也很难。如果没有计划,频繁的上下文切换可能会成为生产力的杀手。因此,当事情达到一定程度时,挂上勿扰标志可能是合适的。
在办公室环境中,这可能是一个字面上的标志!如果你有办公室,那么关上门可能就是它的本质。或者当处理即时通讯系统,如 Microsoft Teams 时,将你的状态设置为勿扰一段时间是获得所需专注时间的一个极好的方法。就像我之前说的,成为一个求助者有其好处,但通常不能没有限制;否则,你会发现你的生产力像石头一样下降。
最后的想法
所以,最终,就像 CPU 一样,你必须预料到并处理作为网络开发者的上下文切换。这通常是工作的一部分。但你必须尽你所能来管理它。你不会总是能这样做,但时间分箱和限制或完全关闭沟通渠道——真正的紧急情况除外——是一段时间内你可以实现这一目标的一些方法。
我喜欢把它当作一种挑战!我在工作中经常被打断,因为我就是那些求助者之一。所以,每次发生这种情况,我都喜欢尽力快速地帮助,然后看看我多快能将我的工作重新回到正轨。这几乎变成了一种游戏!
我要留给你的最后一项建议是,当你不可避免地看到你的生产力下降时,注意你是如何帮助他人完成工作的。记住,作为一个团队的一部分,通常整个团队的工作成果才是最重要的。因此,即使你在某一天自己没有做很多工作,意识到你帮助他人完成的工作可以显示出你是如何有生产力的,只是以不同的方式!
检查路线图
我们正迅速接近尾声,因此路线图也在迅速完善!图 10.4展示了更新后的网络开发者路线图:

图 10.4:路线图,填入了一些更多的框
在本章中,揭示了代码质量和软技能的框。关于软技能,下一章将全部关于这个话题,但本章讨论的许多内容也可以在一定程度上被标记为软技能。当然,关注细节和多任务处理会属于这一类别,所以我感觉在这里揭露是合适的,尽管下一章将更深入地探讨这个话题。
摘要
在本章中,我们讨论了关注细节的关键技能,并探讨了一些帮助你实现这一目标的方法,以帮助你保持专注。然后,我们探讨了如何有效地使用搜索引擎,以及你可以获取信息的有帮助的网站——特别是当需要时从其他开发者那里获得帮助。
然后,我们探讨了开发者用来确保代码质量的一些常用工具,包括静态分析工具。
最后,我们讨论了多任务处理的概念——就像 CPU 一样——以及作为网页开发者,你几乎不可避免地需要经常转换档位才能有效工作。我们还讨论了应对由此产生的压力的策略。
大部分讨论可以归结为关注“软技能”,但在下一章中,我们将探讨更多关于软技能的传统术语——比如人际关系和沟通技巧。我们还会稍微谈谈开发方法,特别关注一种称为敏捷方法的东西,因为如今它非常流行,并且从很大程度上说,它是软技能的练习,以便使其有效运作!
第十一章:复习软技能(它们让你难以否认)
我们已经花了很多时间讨论网络开发的各个方面,如何在这个领域找到工作,以及一些有助于你职业成长的发展话题。但还有另一组你需要意识到的事情,以便享受一段富有成效的职业生涯。这些就是我们所说的软技能。
什么是软技能?这些包括你的态度、如何有效地与他人沟通、与同事适当社交、持续增长你的技术知识以及软件开发方法。这些不是技术性质的东西,你通常不会在书籍或课堂上学习它们。你往往是从他人的例子中学习,有时是通过试错。我的目标是,希望尽可能多地从那个方程式中消除错误!
软技能不仅能帮助你获得工作并成为优秀的员工,而且对事业发展至关重要。即使是最优秀的技术人员,如果缺乏软技能,也可能会在职业发展的某个阶段停滞不前。随着你攀登职业阶梯,这些技能变得越来越重要,但它们始终很有价值,从第一级开始就是如此!
在本章中,我们将涵盖以下内容:
-
团队中无“我”
-
学习永无止境
-
超越烟雾信号,用良好的沟通技巧
-
探索敏捷软件开发方法
团队中无“我”
我可以把整个这一节简化成七个字:
成为人们愿意 与之共事的人。
这听起来很简单,猜猜看?实际上确实很简单!当然,简单并不一定意味着容易。但让我们先来谈谈简单的那部分。
成为人们愿意与之共事的人意味着很多,但主要意味着成为一个友好且乐于助人的人,一个可以信赖的人,能够愉快地完成任务。然而,这并不意味着你必须是一个外向、好交际的人。不;你可以保持安静和内向,但仍然被视为一个友好的人可以与之互动。
在面试过程中,这一点很重要,因为那时你正在尝试推销自己。你将被评判的一个方面是,你是否看起来像知道如何与他人良好合作的人。这是面试的文化适应方面。我们几乎总是需要与他人合作,所以我们会希望享受这样做,或者至少不介意这样做。因此,你必须在面试中留下好印象。
尤其要注意,大部分时间要保持微笑,与人交谈时看着他们的眼睛,给予他们应得的关注,因为他们实际上是在评判你,并且通常与他们保持当下。你想要的是专注而不是分心,你应该以热情回答问题。
当然,这一切在你得到这份工作之后也同样适用!你必须记住,正如俗话所说,“团队中没有‘我’”,这意味着你需要确保你周围的人感到舒适。永远不要把工作带回家,因为这可能会影响你的家庭生活;同样,也不要把个人问题带到工作中。人们通常不喜欢有人把所有问题都推给他们。
被视为团队玩家的最好方式之一是,无论何时何地,你都能帮助他人。让我们稍微谈谈这一点。
在你能帮助的地方帮助
当然,做一个团队玩家不仅仅是做一个友好的面孔。始终是人们可以寻求帮助并愿意花时间协助的人。有时候很难记住,如果你帮助某人完成工作,那么你仍然为雇主提供了服务。当然,我们需要找到一个平衡点,因为我们都有自己的任务要完成,所以你必须小心不要给予自己太多。
起初,你可能没有太多可以提供给经验更丰富的人。但你会惊讶地发现,仅仅友好和可接近就能让人们来寻求帮助。这是有道理的:如果你知道角落里那个脾气暴躁的人可能知道答案,你可能会更愿意花更多时间与资历较浅的人一起解决问题,因为那个人并不容易相处。
但也要记住,即使你可能不知道得太多,这并不意味着你不能帮助。有时候,只是那第二双眼睛稍微改变一下你的视角,这就是你解决问题所需要的。即使如此,也要记住我们关于橡皮鸭调试和第十章中 Stack Overflow 的讨论?有时候,仅仅是在那里听某人解释问题,就是他们自己解决问题所需要的,所以你只是通过做一个好的倾听者就为他们提供了帮助!
当然,这不仅仅是关于倾听;有时候你也需要给予反馈。你如何做这一点也是成为一个优秀团队成员的一部分。
在批评时保持善良
最终,在你的职业生涯中,你将不得不对某人进行批评。更确切地说,你将需要批评他们的工作。当这一时刻到来时,你很可能已经有人批评过你的工作,如果你有,记住你的感受。那个人能否以建设性和友好的方式提出批评?希望如此,因为这就是目标。
很不幸,许多技术人员中存在一种常见的态度,认为直言不讳是首选的,如果有人感到冒犯,他们只需要变得更坚强。至少对我来说,我非常不同意这种观点。诚然,诚实至关重要。但诚实并不意味着刻薄,至少它不应该这样。
仔细选择你的用词。与其说“这个课程结构没有意义”,你可以说些像“啊,我明白了你在这里的意图,以及你为什么选择这样做,但我想知道,你有没有考虑过用 X 来做?”这样的话语表达了你对他们的工作的担忧,同时又不那么尖锐。当然,你应该很明显地知道,你永远不应该说出“这个课程结构很愚蠢”这样的话,但大多数人都能做到这一点。更微妙、更严厉的语言更难识别和避免,但保持当下,专注于你所说的话和方式,是达到这一目标的方法。
你有时可能会出错。如果发生了这种情况,不要犹豫,立即或稍后(如果你需要一些时间来意识到你的错误,这将是另一个话题)道歉。
承担责任
几乎没有什么能比我看到我的团队成员不承担他们的行为责任——尤其是他们的错误——更让我感到沮丧的了。在我看来,这根本不专业,甚至可能被视为不成熟的表现。没有人喜欢承认自己犯了错误,但这就是负责任、成熟的职业人士所做的事情。
当你不可避免地犯错——相信我,我们所有人都会犯错——你应该承担责任。站起来,说“是的,我犯了错误”,然后制定一个纠正错误的计划。这满足了人们依赖你的需求。依赖那些犯错并承担责任的人是容易的,但依赖那些推卸责任的人是不可能的。
在这里,你也需要走一条细线。显然,有时你会因为无法预见的情况或他人的行为而犯错,表达这些是合理的事情,但这也可能被视为试图逃避责任。我能给出的最好建议是,只要你在后面跟着说“是的,我犯了 X 错误,但这是因为 Y 和 Z 因素”,这就是可以接受的。这告诉人们,是的,也许你的表现并不完美,但存在外部因素。而且无论如何,你将承担起纠正情况的责任。这表明你不会把自己置于团队之上,并且可以信赖。
另一个让人们愿意与你一起工作的原因是,你不是等待事情发生,而是通过主动行动自己让事情发生。
主动
我的妻子,一位职业厨师,有一句话:“如果你有时间闲站着,那么你就有时间打扫。”这在厨房里很适用,因为保持干净的环境是一个大问题(实际上,对所有的人来说都是一个健康问题!),但稍作调整,它同样适用于网络开发环境!
在那个背景下,这意味着你可以选择成为那种坐着等待别人告诉你该做什么、该建造什么的人,或者你可以主动寻找机会,并积极投入其中。
也许你可以花些时间清理你的 CI/CD 配置文件。或者也许你可以编写一些关于你支持的应用程序配置参数的文档。或者你可能查看一些写得不太好的代码,并对其进行重构,这里的重构意味着重写它,使其更好(更干净、更简单、更健壮),而不改变其功能。你可能查看优化网站上使用的图片以提高加载速度,或者你可能实现一个管理功能,以便能够更好地记录发生的错误。
重点不在于你具体做什么,而仅仅是这样一个想法:你应该四处看看,看看有什么需要完成的事情,然后去做,或者至少,提出去做的意愿。不一定总是最好的主意就是独自去做一堆工作,但你当然可以举手说“嘿,看起来我们应该做 X,我现在有些空闲时间,我可以去做吗?”。为了安全起见,你可以从你的上级那里获得支持,但你很快会发现,大多数人对于这样的请求都非常开放(否则他们会将你引导到更高优先级的工作,这同样很好)。
大多数人都欣赏那些不简单地等待事情发生和被告知该做什么的人,因为这可以减轻他们的负担,无论是完成任务还是确定他们应该做什么。当然,你有时会有空闲时间,显然,在大多数情况下,会有某人指导你的工作。但,正如俗话所说,闲着的手是魔鬼的游乐场。所以,当有时间的时候,总是寻找方法来抵御那只长角的生物,让自己对团队有用和高效。这是赢得人们对你积极感情的一种可靠方式。
当然,如果没有最后一点考虑:诚实,那么几乎这一切都没有意义。
总是诚实,这是不变的真理。
之前我提到了批评,以及有些人给出批评时非常直率和直接。我谈到了这可能会损害团队凝聚力,因为它可能会让人感到不舒服。但残酷的本质中有一个好的原则:诚实。虽然过于直率可能会让你陷入不友善的境地,但诚实是一种在任何时候都应该被重视的品质。
当然,在任何情况下,你都不应该直接撒谎;这一点我希望是显而易见的!你可能认为你在用谎言保护自己,但你所做的实际上只是确保如果你被发现,你将永远不会被信任。你也没有给你的队友提供他们需要的信息来正确处理情况,这往往会产生滚雪球效应:随着时间的推移,问题会变得更糟。
还要记住,正如《星际迷航:下一代》中著名的船长让-吕克·皮卡德所说:“遗漏的谎言仍然是谎言。”有时你必须向高层传达坏消息,你可能不愿意这样做,因为你知道,不幸的是,有时传信者会受到惩罚,正如俗话(某种程度上!)所说。然而,作为一个负责任的团队成员意味着传达这些消息,以便决策者拥有他们做出决策所需的所有信息。但再次强调,学会减轻打击可能会非常有帮助。告诉经理项目有失败的风险可能是准确的,但可能不会受到欢迎。相反,你或许可以说,虽然有一些延误,项目面临的风险水平有所上升,但正在采取措施应对这一风险。只要这个声明是真实的,这可能听起来会更好一些。
但也可能存在一些情况,现实情况不允许你减轻打击。例如,你可能不得不告诉经理,项目无法按时完成,而且没有任何办法可以避免这种情况。这当然不是一件容易告诉负责人的事情,但作为团队的一员和承担责任的一个方面,正如俗话所说,“直言不讳”。你只需勇敢地站出来,传达消息,也许会因此受到责备。
即使如此,也要注意你表达消息的方式。如果你只是脱口而出“这个项目注定要失败!”即使这可能是真的,这可能对任何人都没有帮助。相反,你或许可以说些像“鉴于我们当前的时间表和资源水平,我看不出我们能够将这个项目成功完成的途径”这样的话。
在不过度社交的情况下保持社交
现代世界几乎是由社交媒体定义的。从 X(前身为 Twitter)到 Instagram、Facebook、TikTok 以及许多其他平台,如今人们比以往任何时候都更愿意分享自己的生活。这可以是一件美妙的事情:我们得以接触到不同的观点和其他的生活经历,当然,我们也很容易得到娱乐!
但社交媒体也可以帮助你寻找工作和职业发展。正如在第第九章中讨论的那样,LinkedIn 等网站在寻找工作和建立人脉方面可以提供极大的帮助。但无论是 LinkedIn、Snapchat 还是其他平台,你必须小心分享的内容,因为正如人们所说,互联网永远不会忘记。你发布的内容几乎可以被任何人找到,如果它不是令人满意的,可能会对你的职业前景以及职业发展产生负面影响。
这里没有明确的规则,因为很难确定什么可能对任何其他人构成问题。我认为最好的建议是简单地小心行事,在你发布任何内容之前,问问自己:这会让我看起来正面吗?这是我愿意向雇主解释的事情吗?明确来说,并不是所有公司都会查看你的社交媒体。但如今,你永远无法确定谁会查看,所以最好还是谨慎行事。
把这些放在一边,社交媒体也可以为你带来好处。例如,如果你经常与其他开发者互动,甚至有时与知名开发者互动,你就有机会学习和可能获得一个机会。与某人的积极互动可以在你寻找工作时为你提供帮助。关于特定技术的诚实、开放和富有成效的辩论可能会让你引起注意。
为了提供一些背景,很多很多年前,我被邀请写我的第一本书主要是因为我在社交媒体上的对话。一位出版商注意到了我展示自己的方式,对我进行了一些研究,发现了我的一些文章,认为我可能成为一名优秀的作家。这并不是我追求的,甚至我也没有真正考虑过,但你永远不会知道一次良好的社交互动可能会引导你走向一条意想不到的道路。
最后,记住除非明确授权,否则你在网上不代表你的雇主发言。在讨论中透露关于你的工作情况时要小心。例如,在网上就某个技术职位与他人辩论,甚至用一般性术语使用你的工作经验,是完全可以接受的,但通常在网上过度批评你的雇主并不是一个好主意。透露太多关于公司如何运作的具体信息不仅可能会受到批评,还可能变成法律问题。你的观点是你自己的,你有权表达它们,但当你将这些观点与你的工作生活结合时,你必须小心。一般来说,越少明确与你的雇主相关联的言论越好。如果你必须在公开讨论中涉及工作内容,尽可能将其概括,以便传达你的观点,同时不透露任何你的雇主可能不希望公开的信息。
核心原则是将社交媒体视为对你有益的事物,但同时也需要谨慎对待。就像大多数事情一样,努力在完全不参与和过度分享你整个生活之间找到平衡,可能是一个好的普遍准则。记住,你发布的内容可能随时被任何人看到,所以如果你没有正确使用社交媒体,你可能会发现你的团队关系意外地受到了负面影响。
与团队融合的另一部分涉及继续提升你的技术技能。特别是在这个快速变化的领域,你将别无选择,只能持续学习,所以现在让我们来讨论一下。
学习永无止境
在第一章中,我提到“学会学习”是这个领域的关键要求。然而,在大多数情况下,仅仅依靠实际工作时间是不够的。简单的事实是,软件开发,包括网页开发,变化的步伐如此之快,以至于如果你稍微离开一段时间,你很快就会落后。
在我开始的时候,网站是简单的东西。它只是普通的 HTML、CSS 和 JS。你可以很容易地查看任何网站的源代码,看看事情是如何完成的。没有真正的工具需要学习,你只需要打开记事本或你手头上的任何文本编辑器,然后开始修改。那是在 90 年代早期到中期。
不久之后,像 jQuery 这样的库出现了,然后你必须学习这些。框架开始出现,比如 Struts 和JavaServer Faces(JSF)。尽管这些是 Java 特定的,不是前端库,但它们在相对较小和非常大的程度上改变了你开发应用程序的方式。再次,跟上是一个挑战。
大概在 2010 年左右,我可以说,新的框架、工具包、库、开发者工具和编写前端和后端代码的所有新方法开始爆炸式增长。在某个时候,几乎不可能知道一切。
但你仍然需要尝试并做到这一点:知道一切!
专业化与泛化
自然,你永远不可能知道一切。如果你花时间学习 React,那么你可能对 Angular 不太了解(尽管它们在概念上有相当大的重叠,所以至少了解一个会让你在另一个方面有优势)。如果你专注于 Node 作为后端代码,那么你可能对 Java 不太了解。这始终是一个权衡,因为,与漫威电影宇宙中的奇异博士不同,你可能没有阿加摩托之眼来有效地给自己无限的学习时间!
一些开发者是专家。他们会专注于一个或两个关键技术或技术领域(前端与后端是一个常见的划分),并且对其他方面的了解不多。如果你发现自己对一组特定的技术感兴趣,这是完全可以接受的。只要它们有需求,你很可能能够找到有限范围内的有利可图的就业机会。
即使你选择了专业,仍然非常有帮助,甚至可以说是预期的,你将拥有一些超出你专业范围的知识。所以,虽然你可以专注于几件事,但你仍然需要花时间学习这些事情之外的东西,至少是为了为你的专业提供背景。这意味着你必须找出如何学习新事物。
然而,其他开发者——包括我自己——采取不同的策略,更喜欢成为通才。我们追求广度和深度的结合,对许多事物有合理的了解,但并不一定是任何领域的专家(在现实中,我们通常倾向于有几个“最爱”,我们对这些领域是专家,但随着我们使用它们的程度不同,我们擅长的技能可能会略有变化)。在就业方面,这通常是一条更好的道路,因为你不会根据你的技能集人为地限制你可以申请的工作。这不仅对就业通常更好,而且只要你是一个欣赏挑战并享受更广泛任务混合的人,这也通常会更有趣。
所以,无论你将自己视为通才还是专家,以下两点都是需要记住的:
-
尽可能多地学习尽可能多的知识,至少要了解尽可能多的主题的背景,但如果你想成为专家,可能需要专注于几个领域
-
找出你学习最好、最有效的方法
最后一点很重要,因为我们对学习的方式和效率并不相同。你必须找出你个人的学习风格。
确定你的学习风格
那么,哪种学习方法最适合你,效率最高?是简单地阅读技术新闻网站吗?或者可能是博客文章?自我导向的学习网站呢?你需要视频还是阅读就足够了?你需要动手经验,还是只需通过观察就能像海绵一样吸收知识?
对我来说,我发现阅读比我观看视频更有效,例如,然后立即实验我所学的知识。我认为这可能是因为我在阅读时更能控制节奏,但我不确定。原因并不重要,重要的是理解什么(通常)对我最好,但我必须随着时间的推移来找出这一点。
虽然没有简单的方法来确定你的学习风格,但我可以提供一些一般性的建议,这应该会有所帮助:
-
尝试不同的方法:这一点相当明显,但有时人们认为他们已经知道了自己的学习风格,从而错过了对他们来说更有效的方法。尝试各种学习格式。阅读书籍或文章,观看 YouTube 上的教程视频,参加研讨会或课程,或者简单地做实际的项目,然后注意你在每种方法中的感受以及你在之后保留知识的程度。
-
自我反思:尝试每种方法后,反思几个关键方面。首先,你是否在整个过程中都保持专注,还是你的思绪飘忽不定?显然,能够保持专注对于有效的学习是必要的。接下来是记忆。当你回想起你(据说)学到了什么时,你能回忆起多少?记得越多越好(尽管要记住,很少有人能记住每一个细节,所以在评估时要对自己宽容一些)。你是否能够通过,比如,用新知识编写一个小应用程序等方式,将知识实际应用到实践中?我们获得的信息往往在我们使用它之前不会在我们的脑海中巩固,所以这是一个关键问题。
-
反馈分析:如果可能的话,在学习任何给定方法的过程中,对你的进度进行反馈。例如,如果你是通过参加在线培训课程来学习的,看看是否有评估或测验来衡量你在学习过程中的理解程度。如果你真的在学习,那么你应该通常能够在这样的测验中做得很好。
-
舒适与挑战:确定你是否更喜欢在舒适或具有挑战性的环境中学习。有些人可能在更加轻松、自我调节、独立的环境中表现得更好。但其他人可能需要课堂的结构和互动,例如,通过课堂引导来完成工作。两者本身并没有本质上的优劣,关键是哪种最适合你。
-
实践学习:构建东西是网络开发领域的一个基本要求。除非你是一个真正非凡的学习者,否则不这样做将是必要的。当你学习和构建东西时,无论是工作中的真实项目还是个人项目,关注通过实践学习而不是仅仅消费信息,然后相应地调整你的时间。
-
社区学习:有些人可能更适应群体环境,而且通过这种方式学习效果更好,与他人之间的互动对他们的帮助很大。寻找你可以协作学习的地方,无论是教室、在线聊天室还是讨论论坛,看看这种互动是否能够提升你的学习效果。
-
复杂度变化:看看你的大脑在简单任务和主题与复杂任务和主题之间的表现如何。有些人可能通过阅读教程等方式学习更直接的主题效果更好,但对于更复杂的话题,他们需要亲身体验才能深入理解。
-
一致性及适应性:追踪你随时间对每种方法的坚持程度。你可能一开始用视频教程学得很好,但发现你的兴趣衰减得比阅读要快,因此你需要根据发现的情况进行调整。
-
时间管理:当然,如果你找不到学习的时间,那么这一切都没有太大意义,因此考虑每种学习风格如何融入你的日程安排是很值得的。任何形式的无间断学习几乎总是比不断被打断要好,因此确保你能抽出足够无间断的时间是至关重要的。
-
反馈循环:在选择了方法之后,定期回顾你的选择,不要假设曾经对你最有效的方法现在仍然有效。同时也要注意,学习风格可能会演变,今天有效的方法明天可能就不那么有效了。我之前提到,我最好是在能独自阅读的时候学习,但曾经有一段时间我认为在课堂环境中我表现得更好。
还要记住,拥有多种学习风格偏好而不是单一的最佳选择并不罕见。你当然可以混合各种风格,创建一个既能让你保持参与感又能有效学习的个性化学习策略。
我已经多次提到将知识以实用方式应用于实践,我想在此基础上进一步探讨个人项目这一概念。
构建个人项目
我总是强烈鼓励的一点,无论你想要成为专家还是通才,无论你偏好的学习风格如何,就是花一些个人时间专注于实验你的宠物项目。
无论你建造什么,只要你能建造出点什么。然而,当你选择一个项目时,一个明确的要求是,它必须是你感兴趣的东西。对我来说,编写一个计算汽车燃油压缩比的软件不会引起我的兴趣,因为我从未是汽车爱好者。但一个可以记录我的复古游戏收藏的项目,从开始到结束都会让我感兴趣!任何既能带来一点挑战又不会成为巨大时间陷阱的东西,因为毕竟,我们谈论的是你的个人时间,你应该始终将其视为宝贵。
除了选择你感兴趣的项目外,你还需要选择一个难度适中、既有挑战性又不会让你感到沮丧的项目。当然,这可能会有些棘手,尤其是在早期,你没有很多经验来判断某件事是否困难。
既然如此,我将建议一种我总是推荐,几乎将其视为一种神奇子弹类型的项目!
一种神奇的个人项目类型:游戏!
我几乎推荐给人们的另一种不同类型的项目是编写游戏。当大多数人可能不会成为专业游戏开发者时,这听起来可能有些奇怪。但原因是我自己编写了这么多游戏,我了解这样一个项目能带来什么。
编写游戏需要你涉及计算机科学中的许多主题,包括算法、网络(取决于游戏类型)、人工智能、数据结构、优化、调试等等。当你制作游戏时,无论涉及什么技术,你都会面临在构建任何其他软件、网站或网络应用时可能会遇到的大量相同类型的问题。
游戏也可以用任何技术编写,这使得它们非常适合进行实验。我曾用 BASIC、C/C++、Java、Pascal、PHP、HTML/CSS/JS、React 和 Vue 编写过游戏。甚至在高中时,我还用 COBOL 编写过一款游戏!
也许游戏项目最好的事情就是游戏本身就是,按照设计,是乐趣!因此,它们在构建时也应该是有趣的,对吧?结果证明它们确实是……或者至少可以是。就像任何项目一样,你必须平衡挑战,不要让自己发疯。
就像我的一位经理曾经说过,你不需要一开始就建造泰姬陵。这对游戏项目来说也是真的,或者对任何其他类型的游戏来说也是如此。如果你一开始就试图建造《魔兽世界》,你肯定会失败,因为那是一款高度复杂的游戏。但是,如果你首先只建造一个你只需四处走动的游戏,那么,这就是基础!你可以在那个基础上添加敌人来战斗,施放法术,以及你想象中的任何其他东西。
此外,为了学习而进行的游戏项目不需要原创,甚至不需要优秀!事实上,我经常建议人们简单地复制他们喜欢的游戏,最好是简单的,如果他们真的喜欢复杂的,那么至少将其简化到最基本的部分开始。你可以尝试制作一个模仿 Frogger 或 Pac-Man 的游戏,只用方块和圆形代替真实图形。即使看起来不好,它仍然可以是一个完全功能性的游戏,你可以通过构建它来学习(当然,如果你在练习你的图形设计技能,那么也许你会在图形上花些时间)。
然而,当你制作游戏时,考虑你正在实施的概念以及它们如何与你日常的工作相关。如果你知道你需要学习 React 来工作,那么就用 React 来构建游戏。如果你知道你需要使用 Redux 与 React 进行状态管理,那么即使在游戏中它可能不是你的首选,也要这样做。从某种意义上说,让游戏成为构建游戏的游戏!使用你将在工作中需要的工具、技术和技巧来构建游戏。
即使没有人看到你的游戏,那也完全没问题。它完全可以只为你自己。而且你可能甚至永远不会自己玩它。但再次强调,如果你从经验中学习,它最终不需要很好或很有趣,我保证你将从这些类型的项目中学习,有时甚至没有意识到。
但也许你会想向某人展示。毕竟,你可能会正确地为自己的工作以及从中学到的东西感到自豪,并想炫耀一下。与他人的互动是沟通的领域,这也是我们在本章中关注的另一种软技能,所以让我们接下来谈谈这一点。
超越烟雾信号,用良好的沟通技巧
作为团队的一员工作自然意味着能够有效地沟通,无论是书面形式还是面对面,无论是设计会议、演示还是任何其他我们需要向他人表达想法的情况。当做得好的时候,它会在团队中培养凝聚力,这转化为更好的表现。
良好沟通技巧的一些方面相对明显。例如,保持语气专业是大多数人天生就理解的事情。在专业环境中讲笑话是可以接受的,但引用像艾迪·墨菲、安德鲁“骰子”克莱、戴夫·查佩尔、理查德·普赖尔和里基·杰尔维斯这样的材料可能不太合适(即使你觉得很有趣)。
然而,其他方面可能稍微难以把握。以下是一些需要记住的关键点:
-
清晰和简洁:在这里我要坦白:在办公室里,我以“长邮件”著称。我喜欢给出全面、完整的解释,尽量减少对谈话对象所知内容的假设。作为一个一般概念,这并不是一个坏的方法。然而,简洁也是一种不错的方法。无论是书面还是其他形式的沟通,都应该尽可能清晰和简洁。人们的时间很宝贵,所以你希望快速切入要点。然而,你必须小心不要走得太远,否则你可能会遗漏人们需要的信息。这是我仍在努力提高的技能!
-
积极倾听:当直接与某人交谈时,与他们以及他们所说的话互动,不要立即得出结论或做出回应。这并不总是容易,因为人类是情感动物,而那些情感往往会影响我们的思考大脑。但是,这样做会显示出你对谈话对象的尊重,以及通常会导致双方对讨论的问题或想法有更深入的理解。
-
同理心和理解:在与他人互动的任何形式中,始终都要练习同理心和理解!这其中的一个重要部分是尊重他人的观点。特别是在今天多元化的工作场所,你必须不断地试图了解他人的立场,并在讨论和解决问题的过程中努力找到共同点。这其中的另一个重要部分是当人们提出“愚蠢”的问题时。俗话说,唯一愚蠢的问题是那个你没有问的问题,我完全同意这个观点。永远不要因为觉得自己可能问了一个愚蠢的问题而感到羞愧,相反,当别人向你提出这样的问题时,要友好地对待他们。尽你所能帮助他们,不要评判,即使是对自己也是如此!
-
一致性和开放性沟通:定期的沟通往往更好,因为它促进了人与人之间的关系。毕竟,沟通就像其他任何技能一样,需要练习才能随着时间的推移而提高。它还有助于避免误解,或者更确切地说,当风险不是很高且可以更容易地纠正时,它允许误解发生。
-
书面沟通技巧:具体到书面沟通方法,如电子邮件、报告和文档,由于它们的性质,你有时间把它们做好,所以利用好这个机会!多次阅读和重读你所写的内容。使用你所能获得的任何校对工具,并努力使其尽可能直接和富有信息量。正如小说家常说的,不要害怕“杀死你的宠儿”。这意味着,在你重读时,不要害怕删除那些没有带来价值的词语。仅仅因为花时间写了它们,并不意味着它们最终必须存在!
-
非言语沟通:始终记住,并非所有的沟通都是直接的。注意自己的肢体语言和语调,以及他人的肢体语言和语调,可以产生强大的洞察力。这些往往能传达比言语更多的信息,并在理解对话的完整背景中至关重要。例如,如果你在描述你解决编码问题的方法,而与你交谈的人将双臂交叉抱在胸前,这可能表明他们对你所说的话不感兴趣。然而,肢体语言并不是一门精确的科学,所以你不能将这样的信号视为绝对的,但根据这个线索,你可能会决定稍微改变你的方法。关于肢体语言,有整本书可以提供大量有用的信息。你可以考虑的一本好书是芭芭拉·派斯和艾伦·派斯合著的《身体语言 definitive 书》。
-
反馈(给予和接收):建设性的反馈对于成长和改进至关重要,这双向都适用。愿意接受反馈显然有助于你自我改进,并以建设性和非个人化的方式给予反馈是关键沟通技能。始终记住要批评观点而不是人。例如,“我不确定这种方法会产生积极的结果”比“我不确定你在这里选择了正确的方法”要好一些,因为前者强调了方法,而后者强调了有人犯错的观念。
-
冲突解决:无论你多么努力,你当然有时会遇到与人的分歧,以及那些不太容易相处的人。以冷静的态度和关注解决方案而不是责备来处理这些人,可以维持积极的工作环境。走正道是一项需要练习的技能,因为,再次强调,我们是情感动物,我们的第一反应往往是情绪化地反应。你必须练习对抗这种反应,即使对方没有回应,也要保持理智和礼貌。
-
文化敏感性:在全球化工作环境中——这在 IT 领域尤为常见——对沟通风格中的文化差异有所了解和敏感至关重要。有趣的是,我在写这本书的时候遇到了这个问题。尽管我是美国人,但 Packt 不是一家美国公司,所以有几回,我以一种对美国以外的人来说不太清楚的方式说话,或者使用了无法完全翻译的流行文化引用。你必须始终检查自己,因为虽然不被理解是一回事,但你很容易冒犯那些可能有不同生活观和价值观的人,这是一个更大的问题。
-
正确利用沟通渠道和技术:有效地使用沟通工具,无论是电子邮件、消息应用还是传统的电话。始终选择适合你需求的技术。如果你有一个不需要立即回答的问题,那么使用电子邮件。如果你需要立即回答但不是特别关键,那么使用像 Microsoft Teams 或 Discord 这样的消息应用可能更受欢迎。我认为现在很多人更喜欢除了大量打电话之外的其他技术,所以将其保留在更关键的情况下(除非,当然,你知道你正在给某人打电话,这个人只是喜欢一点对话,在这种情况下,与这个人建立联系可能是有利的)。
-
量身定制信息:始终牢记你的受众!你与技术人员的沟通方式往往与与高级领导沟通的方式不同。首先,他们的关注点不同。高级领导可能只需要了解问题的概要以及未纠正的后果,而技术人员则需要更多细节来提出解决方案。当然,如果你在给与你有更广泛个人关系的人写信,那么你可能比其他人稍微不那么正式(但仍然在公司时间内保持专业界限)。
-
持续改进:始终寻找提高你沟通技巧的方法。利用雇主可能提供的任何培训机会,或者只是寻找 YouTube 上的专家视频,以获取你可以实施的特定建议。没有人是完美的——包括我,正如我之前提到的——但我们所有人唯一不变的是需要不断地评估和改进自己。
最后我想提到的一点是,这是一个相对普遍的想法,我已经稍微提到过,但可能需要更多的关注,那就是思想市场。
在思想市场中购物
思想市场是一个隐喻,描述了关于思想进行开放、公共讨论的需求。目标是确定不同思想、意识形态和观点的真实性和价值。我们不是在谈论法律法庭意义上的真实;相反,这是指人们经常会持有相互冲突的观点,我们的任务是公开、诚实地审查它们,摒弃被认为没有价值的思想,保留被认为具有高价值的思想,或者,正如经常发生的那样,在飞行中将思想融合成新的东西。
思想市场的理念要求人们能够毫无恐惧地表达自己,而且表达的思想将不受不适当的限制地进行辩论和挑战。最终,那些群体认为最“真实”的思想——其中“真实”被定义为大多数群体都认同的思想——将被视为最有益的,并将战胜其他思想,这是一种非常自然且富有成效的方式来完成技术和非技术任务。
为了举一个相对具体的例子,假设你正在启动一个新项目,你正在尝试确定使用哪个前端框架。你可能支持 React,而另一个人可能支持 Angular。你会阐述你的立场并解释为什么你认为 React 是最好的选择。也许你会提出一些论点,比如它的用户基础比 Angular 大,有更多开发者资源支持它,以及它的相对简单性将使项目开发更快。Angular 的支持者可能会就这些论点进行辩论,无论是绝对意义上还是与 Angular 本身相比较。
在某种意义上,思想市场的辩论可能包括你们两个都去创建一个原型来展示你偏好的方法。我以前就遇到过这种情况,当时我支持一种使用当时一些新技术的方法,包括 AJAX 和基于服务的架构。这些想法当时还不常见,但我看到了它们的实用性。然而,当时的另一位架构师更喜欢一个叫做 Flex 的工具,这是 Adobe 的一个现在已经停止生产的产品。
我们编写了一份文档来描述我们的方法和背后的理由。然后我们花了几天时间使用我们选择的技术构建了一个演示应用程序。最终,我们所有人都聚在一起——我自己、另一位架构师、一些其他开发人员和我们的经理——我们展示了我们的成果。那天房间里有些争论,但最终,不说自吹自擂,我的方法胜出,从那时起,它就成了我们公司以及外部开发应用程序的标准方式(而 Flex 则走向了灭绝!)。
这种情况展示了思想市场是如何运作的。我和另一位架构师从未激烈争论,我们从未互相攻击。我们当然会指出对方方法的缺陷以及我们自己的方法如何优于对方,但这就是它应该工作的方式。每个人都得到了表达自己想法和捍卫自己观点的机会,但最终,市场决定了哪个更有价值。
没有任何东西可以在没有相互尊重、承认自己的观点不是唯一,以及愿意说它们可能甚至不是最好的情况下工作。这里没有需要学习的特殊技能,只是拥有一定程度的智力成熟度。没有人通常喜欢犯错或发现自己的观点不如他人,但这就是世界运作的方式,你必须接受它。我能给出的最好建议是尽量保持情绪不参与其中。还有,尽量不要对你的想法产生情感依恋。只要每个人都站在同一起跑线上,以相同的方式处理事情,那么,如前所述,你将会“攻击”事物而不是人,这通常允许大多数人控制自己的情绪。
我想在软技能领域讨论的最后一个话题可能有些牵强,但说实话,我认为它与之前的话题紧密相连,尤其是从沟通技巧开始。这个话题是开发方法,但主要是被称为敏捷(Agile)的东西。
探索敏捷软件开发方法
当我们开发任何类型的软件,包括网站时,我们使用所谓的方法。这是一个华丽的说法,意思是说,我们有一个定义好的方法来完成我们的工作(因为另一种选择就是随意地开始编写代码,这很少会有好结果)。今天,最受欢迎的开发方法是被称为敏捷的方法。
但在我们可以谈论敏捷之前,你必须对之前的情况有所了解——让人们意识到敏捷可能是一个更好的方法的东西——那就是另一个被称为瀑布(Waterfall)的方法。
在桶中翻过瀑布:瀑布方法
在瀑布方法中,在开始构建任何东西之前,你会花费大量时间进行设计。这涉及到花费大量时间与客户交谈,了解他们的需求,并批判性地编写描述将要构建的内容及其构建方式的文档。实际上,文档是区分瀑布模型和敏捷方法的主要因素之一。
在瀑布模型中,你会创建以下类型的文档:
-
业务需求文档(BRD):这是一份概述项目高层次业务目标的文档。这种类型的文档以非技术术语编写,因为它关注的是从业务角度构建的事物应该做什么,其范围是什么,预期的利益是什么。
-
功能需求文档(FRD),有时也称为系统需求规范(SRS):这类文档详细描述了软件将做什么,通常包括用户故事和用例。
-
技术需求文档(TRD),有时也称为软件设计文档(SDD):这是一份包含指定技术方向的文档,例如整体架构、技术栈、数据库设计、要构建的模块、要提供的服务接口等等。这是最终开发者将基于其编写代码的文档。
-
还有其他类型的文档,没有特定的名称(或者至少没有缩写),包括用户手册,用于描述如何使用产品;系统管理手册,解释系统管理员应该如何完成任务;项目计划,描述项目完成所需完成的主要里程碑和任务,包括估计、日期和每个任务的负责人;以及风险管理计划,概述潜在的风险和应对策略。
这些文档,以及可能的其他文档,是瀑布模型的输出,最重要的是,它们是在前期完成的。在所有文档都编写、审查、修订并获得批准之前,不会开始任何开发工作。如果这需要一年的时间,那么在这一年里,除了可能的一些原型和设计样本之外,不会编写任何代码。
初看之下,这似乎是合理的,不是吗?人们在开始构建之前需要知道他们要构建什么,对吧?而且,每个人都应该对最终目标和实现这一目标的步骤有一个清晰的了解,这是不是很有道理?
但问题有两个方面。首先,编写所有这些文档所需的时间在某种意义上是“浪费”的,因为开发通常要等到文档完成才开始。显然,这意味着在过程后期,没有人能看到任何东西,甚至长时间内看不到一个部分构建的系统。其次——这可能是更大的问题——无论你的文档过程有多好,人们往往不知道他们需要什么!或者,随着产品的开发,他们有时会意识到他们最初没有正确指定的事情,因为他们看到了产品的一部分完成。
不幸的是,在瀑布模型中,通常使用产品的人直到产品完成(或者至少大部分完成)之前什么也看不到,这时进行更改,尤其是那些代表对整个产品支撑核心内容进行核心更改的情况,成本非常高,有时甚至不可能。如果需要花费一年来编写所有文档,然后再花费一年来构建它,那么直到接近两年后,没有人会看到文档是否与实际情况相符,这时可能已经无法进行更改,所以现在你花费了所有的时间、金钱和努力,最终得到的东西可能因为各种原因而无法满足需求。
由于这些原因,瀑布模型在很大程度上已经不再受欢迎。它并没有完全消失,一些公司仍然在使用它,并且在某些情况下,如果前期设计更容易进行,它可能是有益的,因为它允许你更好地估计项目的时间表,使事情更加可预测。然而,对于大多数项目来说,它已经让位于敏捷方法论,所以现在让我们来谈谈敏捷。
在你的开发项目中实践敏捷
与瀑布模型是按顺序进行的过程(先文档化然后开发)不同,敏捷方法论是迭代的、增量的,并且通常是并行的。敏捷方法不倾向于一开始就设计好所有内容并花费大量时间编写文档,而是倾向于构建项目的小部分,然后根据需要迭代更新,使其逐渐接近最终所需的状态。然后,项目的下一小部分可以开始(或者根据资源可用性,可能同时开发两个小部分,但无论如何都是一个迭代和增量的过程)。
敏捷的目标
敏捷的概念始于 2001 年的敏捷宣言,由一群软件开发者制定。这份文件描述了敏捷软件开发的关键原则。
敏捷宣言中描述了四个核心价值观:
-
优先考虑个人和交互而非流程和工具:而不是严格遵循特定的工具,强调的是人的技能、团队合作和沟通。这种价值观强调了协作和基于团队的方法的重要性——你知道的,软技能!
-
优先考虑工作软件而非全面的文档:这一价值观将重点放在交付功能软件上,而不是花费大量时间详细记录每个细节,尤其是在瀑布模型中,通常在前期就完成。这并不意味着文档是不必要的。相反,这意味着生产工作软件是优先级更高的任务,是衡量进度更好的标准。
-
优先考虑客户协作而非合同谈判:在整个开发过程中让客户作为积极参与者是这一价值观的核心。它强调了客户反馈和适应他们不断变化的需求(或理解)的重要性,而不是仅仅依赖于最初的合同条款。
-
优先响应变化而非遵循稳固的计划:这一价值观解释了我们应该如何在整个开发过程中拥抱变化,而不是僵化地坚持预先定义的计划。这意味着要灵活和适应性强,以确保最终产品满足客户和市场的不断变化的需求。
除了核心价值观之外,敏捷宣言还描述了十二条原则,这些原则详细阐述了核心价值观:
-
客户满意度:看到持续交付即使是小功能片段的客户往往更满意。
-
欢迎变化的需求:敏捷方法将变化的需求视为一种优势,而不是问题,这使客户能够更快地适应市场,以获得竞争优势。
-
频繁交付工作软件:你越频繁地交付功能片段,对客户就越有利,即使整个产品尚未完成,他们也可以使用这些功能片段。
-
利益相关者和开发者之间的紧密、日常合作:这有助于做出更好的决策,并确保不断变化的目标保持一致。
-
围绕有动力的个人构建项目:当你为团队提供他们所需的环境和支持,并信任他们完成任务时,他们往往表现得更好,并且能够获得更多的个人满足感。
-
传达信息的最高效和最有效的方法:面对面的交流往往是最有效的,但在现代虚拟化的世界中,只要团队拥有良好的沟通技巧,他们也可以有效地远程工作。
-
将正在工作的软件作为衡量进度的首要标准:这一点不言自明,并且已经被讨论过了。毕竟,你可以把其他所有事情都做得很好,但如果交付的产品实际上无法工作,那么这几乎无关紧要,对吧?
-
敏捷过程促进可持续开发:当正确使用敏捷方法时,利益相关者、开发者和用户应该能够无限期地保持恒定的速度。
-
持续关注技术卓越和良好设计:这有助于通过在整个过程中建立坚实的基础来增强敏捷性。
-
简单性:最大化未完成工作的艺术是至关重要的!毕竟,未开发的代码是没有错误的,所以不过度设计是一个关键的技术目标,而不仅仅是做敏捷开发时如此。
-
最佳架构、需求和设计:自我组织的团队往往能产生更好的成果。
-
团队定期反思其表现:团队应定期审视他们所做的工作、如何完成这些工作以及事情是如何进展的,并根据需要调整流程以支持持续的过程改进。
这些价值观和原则,以及敏捷方法,对软件(包括网站)的创建方式产生了深远的影响。它们增强了团队动态,并让利益相关者在整个迭代过程中参与到过程中。
我提到的那次迭代支持敏捷的主要目标之一,即适应变化。这是通过开发小型单元和更大项目的一部分,并且关键的是尽可能快地将它们展示给人们来实现的。这样,如果设计不正确,无论是由于业务所有者不知道他们确切需要什么,开发者误解了需求,还是其他任何原因,只损失了两周的努力(而且在大多数情况下,并不是完全损失)。你可以迭代地重新设计屏幕,直到它满足需求,同时适应任何需求的变化。
这只有在人们之间有持续的合作时才能奏效,这也是为什么我把这种关于开发方法(主要是敏捷方法)的讨论称为“软技能”。我承认我在这里稍微拉伸了定义,但我认为我可以这样做,因为如果你不与人携手合作,敏捷方法就无法发挥作用!如果你不持续沟通,不进行思想上的来回交流,那么敏捷方法就会瓦解,而做这些事情在我的书中无疑是软技能。
同样地,在开发过程中的客户反馈是使这一过程得以顺利进行的关键组成部分。在敏捷开发中,这是通过称为冲刺的迭代来实现的。我将在下一节中详细介绍冲刺,但简而言之,它们是一段时间——比如说两周——在这个时间段内完成一小部分工作。也许在某个特定的冲刺中,你会实现最终需要的五十个功能中的一个或两个,例如。目标是确保在每个冲刺结束时都有可以部署并且理想情况下可以实际使用的成果,这样客户就可以立即从你所构建的内容中获得好处,即使不是整个项目的全部好处。他们可以亲身体验并看到所开发的内容可能并不是他们真正需要的,这可以反馈到过程中并影响你在下一个冲刺中的工作。请注意,在每次冲刺后部署绝对不是敏捷的必要要求,但目标是每个冲刺结束时都可以进行部署。
敏捷开发的另一个好处是,由于必要性,测试在整个过程中进行。当构建一个屏幕时,可以在构建另一个屏幕的同时对其进行测试。与瀑布模型不同,在瀑布模型中,测试几乎是在结束时才完成的,在敏捷开发中,你也不需要像在瀑布模型中那样等到最后才能发现错误。
敏捷开发也将重点——以及很大程度上,控制权——转移到了开发团队。通常情况下,会有一个待办事项列表;也就是说,一个需要完成的任务列表。这个待办事项列表可以通过利益相关者进行整理,这意味着它可以按照优先级顺序排列(通常,待办事项列表顶部的条目被认为是最重要的)。最终,开发团队决定在每个冲刺中完成什么,在冲刺计划会议期间从待办事项列表中提取条目。在那里,他们估算每个条目的工作量,然后决定他们认为在下个冲刺中可以完成多少条目。有时并非所有条目都能完成,有时会有额外的时间,这时可以即时将待办事项列表中的下一个条目添加到冲刺中。有时,由于各种原因,可能会忽略待办事项列表的顺序(例如,如果有一个团队知道在待办事项列表的较后部分容易完成的条目,但他们想将其拉入冲刺以填补一些空白)。
还要注意,在这所有过程中,文档被弱化。当然,在大多数情况下,仍然存在文档,但它是在代码的基础上即时构建的,很大程度上与代码结合。它通常存在于构建每个部分所使用的票据中,而不是作为独立的工件,尽管这取决于特定组织如何实施敏捷。这里有一个常见的线索:敏捷的实施方式不止一种,没有真正标准的“正确”方式。团队会调整其某些方面以满足他们的需求。在我的项目中,我们倾向于选择四周的冲刺,例如。有些团队不做冲刺回顾会议(在冲刺结束后,你向利益相关者展示你的工作,讨论冲刺中做得好和不好的地方以及如何改进下一个冲刺)而有些团队则严格遵守。这一切都是通用的。
所有这些因素加在一起,使得敏捷比瀑布方法更不可预测。你通常不知道开始时需要多少个冲刺,也不会对你正在构建的内容有一个清晰的了解,除非是一些基本内容。如果听起来像是混乱,那它确实如此!但这是可控的混乱,这种混乱允许更早地将工作代码展示给人们,这可能是敏捷的主要好处。
敏捷方法的实施方式可能因组织而异,但大多数实施方式都倾向于共享一些关键概念,从冲刺开始。
理解敏捷的核心概念
如前所述,敏捷使用冲刺的概念,这是小的时间单位——通常是两周——在这里进行开发。通常发生的情况是,人们设计应用程序的一小部分,可能是许多屏幕中的一个。随着设计师和/或分析师仅就冲刺中技术人员需要实施的具体任务编写足够的文档,这产生了一定程度的文档,但远少于瀑布方法中典型的文档。
通常,在冲刺期间,每天都会有一个每日站立会议。在某些情况下,这非常直接——目的是使这次会议非常简短和直接,人们倾向于在站立时这样做。在会议期间,每个人都会快速讨论他们昨天做了什么,今天计划做什么,以及他们是否有任何阻碍或问题阻碍了他们的进展(站立会议的领导者,即Scrum Master,会尝试通过将他们与解决该问题的正确人员联系起来来帮助他们解决问题)。
敏捷与 Scrum 的比较
我在这里省略了一个应该解释的术语。Scrum 是一种特定的敏捷开发形式,并且可能是最普遍的形式,以至于人们倾向于互换使用这两个术语。主要区别在于,虽然敏捷是一套关于软件开发的一般哲学和方法,但 Scrum 将敏捷细化成特定的角色(如 Scrum 大师)、特定的仪式(如每日站立会议)和特定的工件,例如冲刺待办事项,我将在稍后讨论。尽管如此,有时也会使用其他敏捷形式,包括极限编程和精益软件开发。因此,请记住,敏捷并不等同于 Scrum,但也要记住,在大多数情况下,由于它的普及,敏捷和 Scrum 在礼貌的对话中可以互换使用。
但人们如何知道他们应该做什么工作呢?为此,通常会使用像 Jira(www.atlassian.com/software/jira)这样的跟踪系统(参见图 11.1)。对于该屏幕,将开启一个工单并包含设计细节。然后,冲刺开始,开发者“领取”该工单进行处理。在接下来的两周内,他们开发该工单,通常与设计师(有时是业务分析师)来回沟通。但最终,在两周冲刺结束时,目标是屏幕——仅此屏幕——应该处于可部署状态。理想情况下,它应该完全完成且正确无误,但这基本上是目标:部署它并找出问题!

图 11.1:Jira 工单系统
如果进行更改,通常会开启一个新的工单,并在下一个冲刺中进行处理。同时,下一个屏幕可能由另一位开发者以相同的方式进行处理。
随着工作的完成,通常会生成一个燃尽图,其示例可以在图 11.2中看到。X 轴表示时间,通常标记为天数或冲刺,如果需要查看整体进度视图。Y 轴表示剩余的工作量,最常见的是工单或故事点,它们是对工单相对难度和耗时程度的估计(估计为 10 个故事点的工单,一般来说,应该被认为比估计为 5 个故事点的工单难度和时间消耗高出一倍)。

图 11.2:燃尽图示例
此图表通常还会包含一个理想的燃尽线,它从 Y 轴的顶部开始,到冲刺或项目的结束日期结束为零。最后,通常还会有一条实际的燃尽线,它显示了实际完成的工作进度,从完成的总工作量开始,随着项目的完成而向下倾斜。这两条线可以帮助您看到在冲刺期间您计划要做的事情和实际完成的事情之间的关系。
尽管如此,你通常会发现这两种方法,瀑布和敏捷,可以在一定程度上结合,从而实现两者的最佳效果。
混合瀑布和敏捷
当你结合瀑布和敏捷方法时,目标是强调每种方法的优点,同时减轻另一种方法的缺点。由于两种方法都有其优点和缺点,目标是尽可能多地获得优点,尽可能少地获得缺点,这可能导致一种比单独使用任何一种方法都更好的方法。
在混合方法中,有一些前期设计和文档工作——比单纯的敏捷方法要多,但一旦开始编码,它就采取了一种更敏捷和迭代的做法,即你以冲刺为单位工作;你可能在每个冲刺后发布,并且可能比单纯的瀑布方法更能适应变化。
通常,在混合方法中,有一些里程碑是单纯的敏捷方法中不会出现的。例如,你可能会一开始就声明目标是在六个两周冲刺后发布。这个里程碑是一个具体的团队目标,而不是他们只是迭代直到感觉软件可以完全发布,或者在每个冲刺后发布。
虽然敏捷方法倾向于最小化文档,而瀑布方法则强调文档,但混合方法寻求找到一个平衡点。这意味着你可能会看到一些文档,例如 BRD(业务需求文档),但可能没有 TRD(技术需求文档)。被认为最关键的文档可能像瀑布方法一样在前期详细构建,但其他所有内容都可以最小化构建并保留在工单中。
客户参与是另一个不同点。与瀑布方法中在开发接近尾声时才向客户展示你已构建的内容不同,也不像敏捷方法中那样让他们看到每个构建的部分,你可能会只在预定义的检查点展示你已构建的内容。例如,如果你要构建一百个屏幕,你可能将它们分组为离散的功能块,每个组可能有十几个屏幕,并使用每个组作为检查点来展示你已构建的内容。
混合方法还可以更好地管理风险。任何开发项目都涉及某种形式的风险,无论是时间损失还是金钱损失,如果最终产品不符合客户需求。这种风险在敏捷方法中通常更大,因为与瀑布方法不同,在项目开始时并没有那么多的精力用于正确地详细说明一切。但是,在开始敏捷项目之前进行一些前期工作可以帮助降低一些风险,因为你在那个阶段更有可能捕捉到基本问题,这些问题在后期往往更难解决且成本更高。同样,敏捷对变化响应能力更强,这意味着在中间阶段发现的问题不会像瀑布方法那样对整个项目构成风险。
使用混合方法仍然需要一流的沟通技巧(看,那些软技能又出现了!)并允许团队进行定制。事实上,因为它结合了两种方法,所以根据团队的需求和愿望定制方法可能更加重要,这里的团队指的是开发团队加上利益相关者。这种定制的一部分也是确保方法与组织的文化相一致。一些公司本质上是更加灵活的,而另一些则更加僵化,所以你有必要调整你的混合方法以符合整体的企业心态。
最后,无论你使用瀑布模型、敏捷方法还是混合方法,沟通和与人们紧密合作是关键!在敏捷或混合方法中,这些软技能将得到大量的锻炼(在瀑布模型中较少,但确实仍然很多),所以在开始项目之前理解这一点很重要。
检查路线图
现在几乎要结束了;在这之后,只需再有一章!在图 11.3 中,你可以看到更新的网络开发者路线图:

图 11.3:填入更多方框的路线图
好吧,这次只有一个方框——开发方法——但这意味着你知道下一章将要展开什么,因为只剩下三个方框未打开(尽管它们是什么的神秘性令人兴奋,不是吗?)。所以,现在不要减速,只需再有一章就结束了!
摘要
在本章中,我们讨论了所谓的“软技能”——这些技能本质上是非技术性的,但与技术技能一样重要。这包括如何成为一个好的团队成员,在保持开放和诚实的同时对他人友好,即使在提出批评时也是如此。我们讨论了社交可以是一种优势,但你必须谨慎并设定界限,尤其是在社交媒体方面。
我们还讨论了成为一名优秀的网络开发者的一部分是持续学习,但并非每个人都能以相同的方式学习,所以你必须进行自我反思以确定你的首选学习风格。我们讨论了将你的学习应用于个人项目,如果不是必须的,那么至少是一个好主意,特别是游戏尤其是一种很好的——尽管出乎意料——项目类型,可以用来实现这一点。
然后,我们探讨了如何建立有效的沟通技巧;例如,如何简洁但诚实地撰写电子邮件,直接的口头交流有很多层次,包括肢体语言,以及关于思想市场以及如何尊重地成功导航它的内容。
最后,我们探讨了开发方法,特别关注敏捷方法。你看到了敏捷方法如何涉及大量的软技能(尤其是沟通技能),为什么它比瀑布模型更有益,以及为什么混合方法通常被认为是最佳选择。
在下一章——也是最后一章中,我们将在某种程度上扩展持续学习的主题,因为我知道在这个工作领域,这一点尤为重要。希望我能给你提供更多关于如何确保你的技能永不陈旧,并且始终保持最新和最重要的是,具有市场价值的指导。
第十二章:发展你的职业生涯
在上一章中,我们讨论了软技能,那些至少在大部分情况下不是技术性的东西。你学习了如何发展这些技能和品质,以及它们如何能帮助你实现职业抱负。
从某种意义上说,本章是上一章的延伸。然而,我们不会专注于软技能,而是主要关注你可以做些什么来提升你的技术能力。这将着眼于一件事情:发展你的职业生涯。
思考如何提升自己很重要,尤其是如果你有超越仅仅获得初级网络开发者工作的抱负。因此,在本章中,你将了解一些你可能随着成长而寻求的更高级别的职位,以及你可以如何发展技能以达到那里。这包括追求认证、为开源项目做出贡献以学习和展示你的能力,以及侧项目的想法,这也是我在上一章中提到过的(所以如果你在寻找我认为最重要的那件事,那可能是一个很好的指标:在两章中都提到了它!)。我还会讨论你可以如何跟上行业的变化,这是至关重要的。
将所有这些放在一起,我将尝试给你提供一些指导,如果你遵循这些指导,应该能够跟上行业——以及我们中的任何一个人都能做到的——并在追求攀登网络开发者职业阶梯的过程中不断提升你的技能集。
在本章中,我们将涵盖以下内容:
-
ABC – 永远在编码!
-
查看源代码是你的朋友
-
认证你的伟大
-
跟上潮流
-
以回馈来获得回报
-
求之不得的职位
ABC – 永远在编码!
在电影《格伦加里·格伦罗斯》中,这是一部关于销售阴暗、残酷一面的电影,由亚历克·鲍德温扮演的角色布莱克给出了一个关于“永远在成交”这个短语相当著名的演讲。他在黑板上通过解释它为“ABC”来阐述这个短语。在那个电影的背景下,它讨论的是一个优秀的销售人员总是试图成交,总是试图推动最后一点,让潜在买家咬钩并最终达成交易。
在网络开发的背景下,我扮演着布莱克的角色,并将 ABC 重新定义为“永远在编码”!
这是一个足够简单的概念:网络开发是一个发展非常迅速的领域。实际上,快到我们对此有很多笑话。一个星期是聚光灯下的框架,下个星期可能就禁止使用了。好吧,我在这里稍微夸张了一点,但一旦你做了很长时间,你就会开始意识到这并不像你希望的那样夸张,有时甚至更夸张!跟上变化对任何人来说都是一项任务,相信我!
这意味着,如果你不小心,你拥有的技能很快就会变得不那么吸引人。这也意味着,如果你一段时间内不使用特定的技能,它们往往会退化。我在 80 年代花了大约六年的时间只做汇编语言编程,这是一种非常低级的语言,看起来像这样:
JSR $E544
SEI
LDA #$7F
STA $DC0D
LDA #$01
STA $D01A
LDX #>IRQ
STX $0315
CLI
RTS
IRQ INC $D019
LDX #$00
LOOP LDA COLOR,X
STA $D020
LDY DELAY,X
LOOP2 DEY
BNE LOOP2
INX
CPX #$08
BNE LOOP
JMP $EA31
COLOR .BYTE 6,$E,3,1,1,3,$E,6
DELAY .BYTE 1,9,8,9,9,8,8,9
现在,听着,你甚至根本不需要理解那个!但问题是:我也一样!我确实写了那段代码!问题是,我差不多有 30 年没有做过汇编语言编程了,所以那些技能早已从我的大脑中消失。现在对我来说,它几乎就是乱码,因为一些汇编语言的知识仍然在我脑海中飘荡,但它已经很接近乱码了!
如果我在过去几十年里一直在做汇编语言,那么情况就会非常不同。那些技能会在我脑海中保持新鲜,这会像以前一样成为第二本能。所以,窍门是 ABC:始终在编码!
现在,自然的问题是要编写什么代码,而在你的职业生涯早期,简单的答案就是任何东西!我在上一章也说过这样的话,但那里是在学习的大背景下,以及通过实践学习通常是巩固大脑中知识的最有效方式。但即使你不学习新技能,保持旧技能也很重要。虽然汇编语言是我一生中可能永远都不需要在其职业生涯中使用的语言(而且我在自己的项目中做它的可能性也很低),但练习任何类型的编码都能保持更通用的“编程技能”新鲜。
事情就是这样:特定的技能可以允许其逐渐消失,但关于如何编程以及如何编写代码的一般知识,是你不能允许其消失的东西。我知道的避免这种情况的最好方法就是始终不断编码。
在上一章中,我谈到了个人项目和游戏是如何保持你的编码能力强大,甚至可能扩展它们的绝佳选择。虽然我坚持认为游戏是一个极好的选择,但这绝对不是唯一的选择。只要编码任何东西,几乎无关紧要。我建议的另一种可能性是PIM,即个人信息管理器。这是一个允许你存储、管理和利用重要个人信息的应用程序,通常是像联系人、约会和笔记这样的东西。我非常喜欢这个想法,事实上,我在我的许多书中都使用了这样的项目,如图图 12**.1所示:

图 12.1:多年来我编写的几个 PIM 应用的拼贴
这是一个不错的选择,因为,就像游戏一样,你将不得不面对许多不同的编程主题,而且它们可以用任何技术来完成。你可以当然地用 React 和 Node、Angular 和 Java 来构建,或者只用纯 HTML + CSS + JS 加上客户端数据存储。或者,用 Python 来做,拥有一个纯文本界面。如果你喜欢,你可以将其托管在云服务提供商那里,或者在自己的家庭服务器上。它可以在任何技术组合中工作,并且挑战性足够,可以在不过度困难的情况下保持你的技能敏锐。
但再次强调,这只是其中一个想法。希望你已经开始意识到你喜欢编码和构建网站,解决问题,以及所有与之相关的事情。如果你是这样的话,那么找到持续编码的方法,无论是从事一个项目还是另一个项目,可能不会那么困难。而且,这一切都不必公之于众!没有什么规定你必须与任何人分享你的工作,或者在你编写之后使用它。事实上,你甚至不必完成一个项目!我的私人源代码库中充满了半途而废的项目和因为各种原因中途放弃的想法,而大多数开发者都是这样。
但你知道吗?我经常回到那些项目上,因为我意识到其中的一些部分仍然很有价值。即使它们没有完成,我从它们中获得了新技术方面的经验,尝试了新的技术和想法,并且在处理它们的过程中,我的技能也得到了一般性的提升,这就是 ABC 的全部内容。
当然,编写代码只是保持技能敏锐的一部分。阅读代码同样重要,而且我发现许多开发者现在忘记了或忽略了这些,我之前提到过,但值得再次提及,那就是好的“查看源代码”。
查看源代码是你的朋友
有一个常见的说法,对于一个开发者来说,阅读代码和编写代码一样重要。事实上,许多人会说它更重要。也有人说过,大多数开发者花在阅读代码上的时间比编写代码的时间还要多——无论是理解他们必须支持的现有代码库,阅读自己的代码以记住他们是如何做的,还是阅读他人的代码以学习,这是一项独特的技能。
观察他人是如何做事的是一个特别有价值的练习。诚然,这并不总是容易的,因为你不可避免地会遇到你不知道的事情,无论是技术、风格、新库还是其他什么。这可能是一个令人沮丧的练习,但它太有价值了,不能让它阻止你。
之前,我提到了“查看源代码”选项,这是网络浏览器几十年来一直有的功能。这是开发者有时会忘记存在的东西!在一定程度上,这是因为浏览器开发者工具已经取代了查看源代码,但无论你具体如何做,我泛泛地谈论的是查看他人工作的想法。
当你访问一个网站并看到一些酷炫的东西时,在大多数情况下,你可以使用“查看源代码”或开发者工具来检查代码。现在,这并不像以前那么准确,因为现代的开发者技术。在 90 年代末/21 世纪初的早期,查看源代码很棒,因为你得到的是纯净的、未触动的 HTML、CSS 和 JS。没有神秘感;所有这些都展现在你面前,供你检查和学习。
这在意义上仍然是正确的,因为 HTML、CSS 和 JS 是网络上的通用语言。无论你使用什么开发者工具、库、框架或工具包来构建网站,最终都会归结为 HTML、CSS 和 JS。尽管浏览器运行的最终代码可能有许多不同的抽象,但最终仍然归结为这三个。
但如今,浏览器用来显示网页的最终形式可能与开发者所写的内容大相径庭,这要归因于那些抽象。例如,如果他们用 TypeScript 开发了网页,那么最终生成的 JS 代码有时看起来会截然不同。或者,如果他们使用了 Angular 或 React,那么他们所写的代码与在浏览器中运行的代码几乎毫无相似之处。还有被称为混淆器的工具,它们明确地试图使代码看起来尽可能不同,并且明确地更难以阅读。还有压缩器(称为最小化器),它们试图最小化代码的大小,以使页面加载更高效,但这也会使代码更难阅读。因此,代码有时是无意中难以阅读,有时是有意为之,但最终使得“查看源代码”不如以前那么有用。
尽管如此,即使通过查看混淆后的代码,有时也能获得有价值的见解。即使你无法完全确定导致特定效果的精确代码,例如,你仍然可能能够获得关于它是如何实现的概念性理解,而这通常正是你真正想要的。
虽然现代代码可能难以阅读,但有一些需要注意的事项可以帮助你。其中最主要的是所谓的源映射。这些是特殊的文件,可以在压缩或混淆 JS 时,或者在从 TypeScript 转换为 JS 时成为输出的一部分,提供一种将文件内的代码映射回其原始源代码的方法,就像在浏览器中看到的那样。它们通过在代码查看器或调试器中显示原始源代码,使得开发者能够更容易地检查代码,即使浏览器(或 Node,或另一个运行时环境)中运行的代码是经过最小化或转换的版本。这种在运行时代码和原始源代码之间的映射对于调试目的和从中学习至关重要。
很遗憾,源映射是完全可选的,因此无法保证它们一定会存在。但如果有,你最终会得到一个名为example.min.js的文件中的最终代码,它可能看起来像这样:
function a(a,b){return a+b}function s(a,b){return a-b}
//# sourceMappingURL=example.min.js.map
这可以像网页中的任何 JS 文件一样使用,如下面的简单 HTML 所示:
<html>
<head>
<title>Example</title>
<script type="text/javascript" src="img/example.min.js"></script>
</head>
<body>
<script>
document.write(a(2, 2));
document.write("<br>");
document.write(s(10, 5));
</script>
</body>
</html>
对于这次讨论来说,这并不重要,但为了避免你感到困惑,document.write()是一个将内容写入 HTML 文档当前位置的函数。所以,在这种情况下,它写出了调用a(2, 2)的结果,然后是一个<br>标签以跳到下一行,然后是调用s(10, 5)的输出。
现在,example.min.js中的 JS 代码实际上看起来并不糟糕!你可以从中看出你有两个函数,a()和s(),其中a()用于添加两个数字,而s()用于减去两个数字(这就是为什么页面上分别写有 4 和 5 的原因)。在现代开发工具中,你最终得到这样简单且易于理解的东西是很少见的。
但请注意第二行,那是一条注释。这是魔法调料:它为开发者工具提供了一个连接,将这个最小化代码与特定的example.min.js源映射文件之间的链接。源映射文件可能看起来像这样:
{
"version": 3,
"file": "example.min.js",
"sources": ["example.js"],
"names": ["a", "s"],
"mappings": "AAAA,SAASA,IAAT,GAAUA,EAAGA,QAAQC,IAAI,CAACC,CAAD,CAAK,CALZ,CAQA,IAAI,CAACC,CAAD,CAAK,EAAIC,CAAC"
}
不要担心这些意味着什么,因为作为人类,你通常不需要直接查看源映射文件。这不是为我们准备的;它是为工具准备的。当这些工具处理这个文件时,结果就是在浏览器开发者工具中,你现在将能够有效地访问原始源文件。这是因为源映射文件知道如何,在某种意义上,将最小化版本转换为原始源文件,如下所示:
function a(a, b) {
return a + b;
}
function s(a, b) {
return a – b;
}
在图 12.2中,你可以看到为这个示例加载的文件列表,其中包括example.js(你可以通过在本书的代码目录中运行npx http-server 来尝试这个示例;它将启动一个服务器,然后你可以使用显示的 URL 访问它,此时你将能够自己看到开发者工具中的相同内容,如这些页面所示):

图 12.2:浏览器开发者工具中源映射的示例
这让你能够兼得两者之优!你可以使用所有那些花哨的新框架、库和工具包,以及你想要的任何优秀的最小化/混淆工具,同时仍然能够有效地调试你的实时代码。而且它还允许其他人从你的代码中作为学习经验获得价值。
虽然“查看源代码”对于了解他人是如何做的是非常有价值的,但它不仅仅是为了这个目的。它还允许你“像黑客一样思考”,以找出你工作中存在的安全漏洞。
思考像黑客一样
我所说的“像黑客一样思考”是指你可以使用“查看源代码”和浏览器开发者工具来戴上你的“白帽子”,并找出你自己的代码中的漏洞。
黑客的类型
黑客——那些寻找网站和应用程序安全漏洞的人——有多种类型,通常用“帽子颜色”来表示。白帽黑客,也称为“道德黑客”,是指那些在法律范围内,并得到目标方的同意,明确意图寻找和披露漏洞的人。这些人通常被公司雇佣来寻找他们网站中的缺陷。黑帽黑客是指那些将技能用于非法或恶意目的的人。他们的动机通常涉及经济利益、间谍活动,或者仅仅是制造混乱。灰帽黑客介于两者之间,他们寻找漏洞的目的是披露它们,而不是为了恶意目的而利用它们,但他们并不以目标方的同意或事先了解为目标。公司通常对灰帽黑客有着爱恨交加的关系,因为虽然有人像那样发现问题并在黑帽黑客之前通知公司是好事,但接到一个未经请求的报告仍然让人感到不安。
你必须改变你的思维方式来做这件事,看待代码不是看它是什么,而是看你能用它做什么。例如,你能找到在调用你的 REST API 时使用的嵌入在代码中的密码吗?这是一个非常明显的安全问题;然而,这通常不会那么容易,所以你将不得不从侧面开始考虑。比如,你能看到可能能够黑客攻击代码,向服务器调用添加一些恶意的 SQL——换句话说,就是 SQL 注入攻击的地方吗?
只要这是你自己的代码,这一切都无可厚非。但没有什么能阻止你戴上灰帽,看看别人的代码。正如前一个注意事项中提到的,你必须小心,因为并非每个公司都会欣赏你这样做。但这对你的学习经验仍然很有帮助,因为你可以开始看到你应该在自己的工作中避免的模式。看到别人的错误是件有价值的事情!
不要害怕深入研究并看看别人是如何做到的;无论出于安全目的与否,这是底线。查看源代码的功能就是为了让你这样做,现代浏览器中提供的更高级的开发工具也是如此。利用它们,如果你花时间去做,你可以学到很多东西。
你可以增强你的技能以及你的就业能力,另一种方式是通过认证,那些声称告诉别人你所知道的光鲜亮丽的(如今往往是虚拟的)纸张。让我们看看这些是什么!
证明你的伟大
证书,那些(有时)闪闪发光的纸张,挂在墙上(有时),是向世界宣告“我对 X 了如指掌!”的方式,其中 X 是某种技术或技能。它们就像大学学位,但不需要坐在教室里。你看,你可以去参加,比如,网页开发的考试,如果你通过了,你将获得一份证书,意在表明你在网页开发方面已经达到了一定的技能水平。
可用的证书有成百上千种。有些由私人公司(可能大多数都是)管理,针对的是它们主要收入来源的特定技术,有些由政府机构管理,还有些由整个商业模式就是提供认证考试的组织管理。它们涵盖了从云计算到网络安全、网络、人工智能和机器学习、数据管理和分析以及项目管理等各种主题。
有些证书是免费的,但大多数需要付费才能获得。多少钱?从几十美元到几千美元不等。有些证书实际上非常容易;考试相当表面化,不需要深入的知识。其他一些证书——比如思科公司进行的网络测试——因其非常困难且需要实际操作演示你的技能而臭名昭著。
但在你考虑获得任何类型的证书之前,问一个简单的问题是有价值的:它真的值得花费时间、金钱和精力吗?
它们值得获得吗?
技术界的人士与证书的关系一直有点爱恨交加,而且随着时间的推移,对它们的态度也在变化。20-30 年前,几乎任何证书都有价值。它们在求职时使你脱颖而出,因为并不是每个人都拥有它们。在那个时代,花几百甚至几千美元收集几个证书是帮助你的脚进入门槛的好方法,或者可能帮助你加薪或晋升。
现在,情况并不那么明确。有些人对证书几乎不抱任何期望,因为最终,如果你是那种知道如何学习和考试的人,他们认为你可能在没有任何实际经验的情况下获得许多证书。
为了具体说明这一点,大约 25 年前,我获得了 Novell 管理员认证,Novell 是一个已经消失的操作系统,曾经在大数据中心中相当流行。我当然知道 Novell 的基础知识,并且有一些实际操作经验,但我根本不具备成为真实生产 Novell 系统管理员的资格。尽管如此,我还是买了一本学习书籍,翻来覆去地读了几遍,并设法通过了考试,因为我一直很擅长在考试中复述信息。所以,实际上,这个认证从未真正表明我对 Novell 的真实知识水平;它更多地只是显示了我学习和通过考试的能力。
不幸的是,这就是如今一些人看待任何认证的方式。但我认为这是不公平的,因为并非所有认证都是平等的。有些,比如 Novell 测试,只需要基本知识和经验的人就能通过,但像思科测试这样的考试则不行。那些要求你真正了解你的知识,并且能够以实际方式展示出来。
作为一般性的说法,随着时间的推移,认证的重要性会降低,因为你的过去经验,从某种意义上说,成为了你的认证。如果你实际上已经构建了一个或多个复杂的网站,那么这向我——作为招聘经理——展示的你所知道的内容,比任何认证都要好。
所以,这听起来可能有些矛盾,而且从某种意义上说,确实如此!这里存在一种紧张感。
一方面,如果你正在尝试找到第一份工作,并且没有很多经验可以展示,那么我会说认证有一些价值。它们对每个面试官的价值可能都不一样,但对于那些更看重它们的人来说,这可能是被雇佣和不被雇佣的区别。我会说,不要在这些认证上花费过多的时间和金钱;寻找那些(a)不太昂贵,并且(b)你可以根据你所真正知道的内容通过,而不需要大量学习时间的认证。
另一方面,随着你积累了一些实际工作经验,我个人观点——我相信我不孤单——是不要过分重视认证。偶尔有针对性地追求一个认证可能仍然是有意义的,比如如果你正在寻找换工作,并且想要尽可能多的证据来证明你能胜任新工作。但“收集认证”——只是不断地获取它们,认为它们可能对你的职业生涯有好处——可能是一种浪费时间和金钱的行为。可能更好的做法是花时间构建东西,通过实践来积累经验,这样你就有更多展示你技能的证据。
就其价值而言,在我职业生涯的早期,我经历了一段收集认证的阶段。最后一次统计,我拥有大约 90 种不同的认证。虽然其中一些,比如 Novell 的认证,并没有真正反映我当时的知识和能力,但很多确实是如此。但无论如何,简单的事实是,它们对我的职业生涯没有任何实质性的好处。它们没有帮助我得到第一份工作(在那之前我没有任何工作),也没有帮助我在后来获得任何大的薪资增长(尽管证明我在工作中的能力确实做到了)。我是否觉得那些时间和金钱都值得?不,我老实说并不觉得。尽管如此,我认为我确实从为它们所做的学习中学习到了一些东西,从这个意义上说,我可能确实得到了一些好处,但不足以证明时间和金钱的投入是合理的。
因此,如果你决定追求认证,我认为进行有针对性的追求是明智之举;这正是那个轶事的意义所在。
假设你决定追求认证,无论是为了得到第一份工作还是为了后来的某个原因。鉴于现在有如此多的认证,你该如何选择追求哪些呢?让我们来看看目前最受欢迎的一些认证,看看获得它们需要什么,以及哪些可能对你最有益。
要追求的具体认证
确定哪些认证对大多数人来说更有内在价值是一个非常困难的任务。这大部分取决于意见和主观衡量标准。尽管如此,当人们讨论它们时,一些认证确实往往浮出水面。以下是我根据一些在线研究认为最有价值获得、按类别划分的认证列表。
通用网络开发
这些认证显示了网络开发者可能需要具备的多种领域的技能:
-
Adobe 认证专家(ACE)网络专家:由 Adobe 管理,它专注于 Adobe 的网页设计和开发工具,非常适合前端开发者(
learning.adobe.com/certification.html) -
认证网络专业人士(CWP):由国际网络协会认证,它提供网页设计、电子商务和网页开发的专业化(
iwanet.org/profdevel) -
全栈网络开发者纳米学位:由 Udacity 这个培训平台管理,它涵盖了前端和后端技能、数据库管理和 API 使用(
www.udacity.com/course/full-stack-web-developer-nanodegree--nd0044) -
Oracle 认证专业 Java SE 程序员:由 Oracle 管理,它验证了在 Java(后端开发的一种常见语言)方面的专业知识(
education.oracle.com/oracle-certification-path/pFamily_48) -
认证响应式网页设计师 (CRWD) (Certified Responsive Web Designer):由 freeCodeCamp,一个学习平台,管理,确认在创建响应式、用户友好型网页设计方面的技能(
www.freecodecamp.org/learn/2022/responsive-web-design)
云计算
云计算认证通常是坚实的,对很多人来说具有相当的意义:
-
AWS 认证开发者助理 (AWS Certified Developer Associate):由亚马逊管理,涉及在 AWS 上开发和维护应用程序(
aws.amazon.com/certification/certified-developer-associate) -
亚马逊网络服务认证解决方案架构师 (AWS Certified Solutions Architect):由亚马逊管理,专注于在 AWS 上设计分布式系统(
aws.amazon.com/certification/certified-solutions-architect-associate) -
微软认证 Azure 开发者助理 (Microsoft Certified Azure Developer Associate):由微软管理,针对开发者设计、构建、测试和维护 Azure 上的云解决方案(
learn.microsoft.com/en-us/credentials/certifications/azure-developer) -
谷歌认证专业云架构师 (Google Certified Professional Cloud Architect):由谷歌管理,专注于设计和管理谷歌云上的解决方案,包括人工智能和机器学习解决方案(
cloud.google.com/learn/certification/cloud-architect)
网络安全
网络安全是当今一个关键话题,无论你是直接寻找网络安全工作,还是只想表明,作为一名网页开发者,你对这个重要话题有很好的掌握:
-
认证信息系统安全专业员 (CISSP):由 ISC2 管理,是针对对信息安全职业认真负责的 IT 专业人士的高级认证(
www.isc2.org/certifications/cissp) -
认证信息系统安全管理员 (CISM) (Certified Information Security Manager):由 ISACA 管理,专注于信息安全的管理和治理(
www.isaca.org/credentialing/cism) -
认证网络应用安全测试员 (C-WAST) (Certified Web Application Security Tester):由 Udemy,一个教育平台,管理,专注于识别和减轻网络应用漏洞(
www.udemy.com/course/certified-web-application-tester/?couponCode=24T5FS31824) -
GIAC 网络应用渗透测试员 (GWAPT) (GIAC Web Application Penetration Tester):由 GIAC 认证管理,验证网络应用安全测试和渗透测试的技能(
www.giac.org/certifications/web-application-penetration-tester-gwapt)
网络
正如你所知,网络对于成为一名网页开发者至关重要,尽管这些认证对于网页开发者来说可能过于高级,但它们确实是获得大量认可的认证示例:
-
思科认证网络助理 (CCNA):由思科管理,是一个入门级认证,涵盖基本的网络安全概念 (
www.cisco.com/c/en/us/training-events/training-certifications/certifications/associate/ccna.html) -
思科认证网络专业人士 (CCNP):由思科管理,涵盖更高级的网络安全主题,需要更深入的知识和理解网络解决方案 (
www.cisco.com/c/en/us/training-events/training-certifications/certifications/professional/ccnp-enterprise.html)
数据管理和分析
处理数据通常是软件开发的一个重要部分,因此处理这些技能的认证具有一定的内在价值:
-
认证数据专业人士 (CDP):由职业信息网站 O*NET 管理,是一个针对数据专业人士的全面认证项目 (
www.onetonline.org/link/certinfo/12605-B) -
微软认证数据分析师助理:由微软管理,专注于在微软 Power BI 中转换、分析和可视化数据所需的技能 (
learn.microsoft.com/en-us/credentials/certifications/power-bi-data-analyst-associate)
人工智能和机器学习
正如你所知,人工智能和机器学习是庞大且不断发展的领域,因此,这些领域的认证自然具有一些内在意义:
-
微软认证 Azure AI 工程师助理:由微软管理,专注于使用认知服务、机器学习和知识挖掘来设计和实施微软人工智能解决方案 (
learn.microsoft.com/en-us/credentials/certifications/azure-ai-engineer) -
IBM 数据科学专业人士证书:由 IBM 和 Coursera(一个学习平台)管理,涵盖数据科学、机器学习和人工智能,使用各种工具和技术 (
www.coursera.org/professional-certificates/ibm-data-science)
项目管理
就像网络一样,这些认证通常不是作为网页开发者所希望追求的。但你知道吗?有些人最终会意识到他们更喜欢项目管理方面而不是开发方面,所以了解在那个时刻可能感兴趣的认证,或者只是如果你想拥有更广泛的专长基础,这是很好的:
-
项目管理专业 (PMP):由 PMI 管理并在全球范围内得到认可,它表明你有能力领导项目团队并实现项目目标(
www.pmi.org/certifications/project-management-pmp) -
认证 ScrumMaster (CSM):由 Scrum Alliance 管理,它专注于敏捷项目管理,特别是在软件开发中,以及如何作为 Scrum Master,即领导每日站立会议和其他敏捷项目方面的活动(
www.scrumalliance.org/get-certified/scrum-master-track/certified-scrummaster)
我会说,获得这些认证中的任何一个都会让简历看起来很好,有助于帮助你找到第一份工作,并且可能在将来提升你的职业生涯中带来一些好处。我肯定会说,截至写作之时,这些认证可能比大多数其他认证更能得到更多人的认可,所以如果你要获得任何认证,我建议你应该把它们放在你的清单的前列或接近前列。
因此,你获得了一些认证,找到了你的第一份工作,你知道你应该始终如一地编码以保持你的技能敏锐。你还能做些什么来确保和提升你的职业生涯?这全部关乎关注行业中的动态,关注他人正在做什么,并尽可能从他们那里学习。让我们来谈谈一些实现这一目标的策略。
跟随潮流
IT 领域,尤其是网络开发,是一个不断变化、不断扩大的领域,每次转身都有新事物要学习。跟上所有这些实际上是一项全职工作。我们在前几章中讨论了几种你可以用来跟上潮流的方法,比如聚会和会议,但好消息是,网络本身提供了大量的资源,让你可以在家中舒适地完成这些。
现在有很多网站你可以经常访问,以获取最新的技术新闻、趋势、观点,以及介绍最新和最好的产品。当然,有些网站比其他网站更好,所以试图找到最好的可能很困难。但不用担心,这正是我在这里的原因!
以下是我个人认为最有价值的网站之一。毫无疑问,还有其他好的网站,但如果你经常访问这些网站——我个人喜欢几乎每天浏览它们——我认为你会对自己有所帮助,并对行业的发展有一个良好的感觉。关注整个行业的重要性不容忽视,因为它能让你深入了解他人正在做什么,以及你应该关注哪些技术,所以这绝对是你时间的宝贵利用,尤其是有这些好网站的情况下。
A List Apart
A List Apart (alistapart.com) 是一个专门为那些制作网站的人——无论是设计师还是开发者——提供新闻的网站。它的文章侧重于网络标准和最佳实践,对任何网络开发者来说都很有价值。在 A List Apart 上,你可以找到从编码到设计、用户体验、内容策略以及行业和流程新闻的各种主题。

图 12.3:A List Apart
Daily.dev
Daily.dev (app.daily.dev) 是另一个“聚合”网站,它提供了一个易于使用、简洁的界面,链接到其他人的内容。你将找到各种主题和文章类型,但所有内容都侧重于软件开发。虽然这里对 Web 开发给予了大量关注,但并非唯一。

图 12.4:Daily.dev
作为额外的好处,Daily.dev 使提交内容变得容易,所以如果你有灵感,这是一个向世界介绍你可能会在某个时候撰写的任何文章的好网站!
dZone
截至撰写本文时,已有超过一百万的开发者成为 dZone (dzone.com/) 的成员,这意味着它是今天专业开发者的一个显赫的编程新闻中心。该网站涵盖了从敏捷到云技术、DevOps、大数据、ML/Al、性能调优等各个方面。它是从行业各个角落获取最新洞察的有价值来源,对初学者和经验丰富的开发者都同样有益。

图 12.5:dZone
Hacker News (Y Combinator)
Hacker News,有时被称为 Y Combinator (news.ycombinator.com),类似于 Reddit,但专门针对开发者!Hacker News 是开发者、技术和科学新闻中最知名和最受尊敬的网站之一。它是一个非常简洁的网站,可能看起来不是那么美观,但速度非常快,导航也很容易。它是保持最新信息的最优秀资源之一,并且经常是行业讨论的场所。例如,数百名开发者参与了 2017 年 2 月 28 日大规模 AWS S3 故障的实时讨论。虽然其中一些文章可能更偏向理论性和教育性,但其他文章,比如那篇,会变成任何人都可以阅读并参与的实时讨论。

图 12.6:Hacker News
InfoQ
InfoQ (www.infoq.com)的口号是“促进专业软件开发中知识和创新的传播”,从其主页上一瞥可以看出,它非常认真地遵循这一信条,为你提供了一系列优秀的文章。虽然我认为我可能会说 InfoQ 倾向于更多经验丰富的开发者,但它仍然为其他技能水平的人提供了可以从中获得的东西。它还提供了指向演示文稿和播客的链接,其中许多在占用你一些时间的同时提供了巨大的价值。

图 12.7:InfoQ
SD Times
SD Times (sdtimes.com),即软件开发时报,是一种为各种开发者制作的数字报纸。其内容涵盖包括 DevOps、测试、安全、团队和文化以及系统架构在内的多个主题。它当然提供新闻,但除此之外,还提供特定主题的网络研讨会、白皮书和播客。你可能会因为主页看起来似乎没有太多内容而被误导,但稍微向下滚动并探索菜单,你会发现实际上有很多内容。

图 12.8:SD Times
技术雷达
最后,如果你在寻找一个关于你应该关注的正在兴起的热门技术的优秀图形表示,那么技术雷达(www.thoughtworks.com/radar)是一个很好的选择。这是 Thoughtworks 的产品,它是一家知名的技术咨询公司,技术雷达展示了语言和框架、技术、工具和平台,以及你是否应该——在专家的意见中——继续使用它们(因为它们可能正在从开发者的脑海中消失),评估它们是否适合你的使用,以试验模式使用它们,或者直接采用它们。换句话说:它试图给你一个关于当前热门、即将到来或正在消失的技术概念。鉴于这个网站的明显主观性质,你当然需要自己评估它所说的内容。但一个知名且普遍受到尊重的、肯定知道自己在做什么的群体提供这种指导,可以是一个非常宝贵的资源。

图 12.9:技术雷达
阅读新闻网站非常有价值,在这个领域你几乎必须这样做。但你可以做的一件事——而且在我看来,只要可能,你就应该这样做——是寻找将你的技能用于实际的方式,以构建和展示你的能力。虽然这对你有明显的益处,但它也对他人有益,所以这是一个双赢的局面!现在让我们看看你可以如何实现这一点。
回馈以获得回报
在我多年的开发者/架构师/领导/技术经理生涯中,我逐渐明白的一点是,当你帮助他人时,你几乎总是会在过程中帮助到自己。这可以以多种形式出现。帮助同事解决问题往往能让你获得之前没有的洞察力。教导他人有助于巩固你自己的知识。而且当你为项目做出贡献时,你会在获得技术利益的同时,获得成就感。
因此,我总是寻找可以“回馈”他人的方式。我说“回馈”,因为我们所有人都依赖他人,无论是他们教导我们、帮助我们解决问题,还是仅仅创建了我们所依赖的软件和工具。寻找机会自己去做同样的事情是提升你的技能集和其他无形品质的绝佳方式。
让我们谈谈一些你可能考虑的回馈方式。当然,哪种方式适合你,你需要自己发现,这绝对不是一份详尽的清单。但希望这能给你一些关于你可以做的事情的想法。
开源贡献
最好的——也是最容易的——回馈方式之一是向开源项目(通常使用缩写FOSS,代表free/open-source software,我将在下面这样做)做出贡献。你当然可以开始自己的项目;这没有什么错,而且它有很多好处。但在这里,我专注于寻找你喜欢的项目并向它们做出贡献。
许多开发者对参与项目感到紧张,尤其是在他们自己的学习初期,因为他们认为他们可能没有任何可以贡献的东西。让我向你保证,这是错误的!开源项目通常需要人们做一些不需要高技术技能的事情。例如,文档是许多项目所缺乏的,而且项目的维护者都乐于有人帮助他们。
事实上,你的相对缺乏经验实际上可能是一种祝福:编写更简单的“入门”文档,就像一个新用户需要的那样,你可能可以处理,因为你必须自己经历那个学习过程才能写出这样的文档。你可能会节省其他人通过这样的贡献来熟悉情况的时间,这肯定是一件有价值的事情。
没有获取权限的必要——你只需开始写作!你很可能会需要克隆项目的仓库,正如在第四章中讨论的那样。这将在你的机器上创建一个本地副本,你可以开始修改而不会对现有项目的代码造成任何风险。如果事情不顺利,那也无关紧要;你总是可以克隆一个新的副本并重新开始,无损无损。
但最终,当你取得了良好的进展时,当你认为文档准备就绪时,你可以发送一个 pull request(假设项目正在使用 GitHub)。当然,没有保证它会被接受,或者他们不会回送一些关于他们希望看到的内容的评论。但很多时候,你会得到一个巨大的“谢谢!”以及立即的接受。
查看项目的现有错误并提交修复方案是另一种伟大的贡献方式。显然,对于项目来说,解决错误是件好事,但对于你来说也是极好的,因为它不仅帮助你学习项目的代码库,以便你将来可以做出更多贡献,而且意味着你在阅读代码并从中学习,所以自私地说,这将极大地对你有益。而且你不需要一开始就解决最棘手的错误!找到那些看起来比较简单的错误,甚至“愚蠢”的事情,比如屏幕上的标签可能有拼写错误,也可以让你入门。
最终,你可以看看一个项目,想想“好吧,我能添加什么功能,我认为人们会喜欢?”然后就去实现它。当你达到这个阶段时,你可能想要在论坛或邮件列表或 Discord 的消息列表上进行一些讨论——无论项目成员如何沟通——以获得一定程度的支持,这样你就不会在最终没有机会被接受的事情上浪费很多时间(再次强调,构建这个功能的经验本身也可能对你有益,所以也要记住这一点)。了解你不会开始做某个项目成员已经在做的事情也是好的。
可能项目的网站有点不足。在这种情况下,你可能会提出建立一些更稳健的东西。在这方面,事情与过去略有不同,因为大多数开源项目都托管在 GitHub 等网站上,它们可能没有自己独特的网站,就像几年前那样常见。但事实仍然是,项目通常会想要有自己的“面孔”,即使它们托管在 GitHub 上,提出建立这样的网站——以及可能提出一些设计理念或原型来证明这一点——也可能是有价值的。
你也可以仅仅通过成为SME(行业专家)或subject matter expert(主题领域专家)来参与开源项目。如果你对项目非常了解,你可以开始帮助在论坛上遇到问题的用户。这种贡献不仅会被项目的用户所欣赏,也会被维护者所欣赏,因为他们可以更多地专注于编写代码而不是帮助人们。
从根本上说,开源(FOSS)的一切都是关于社区,所以找到你可以帮助社区的方式就是关键。无论是文档、编码还是仅仅回答问题,这些都算数。
展示
另一种回馈的方式是在用户组、聚会等活动上进行展示。这将帮助你巩固你的技术知识,因为当然,你需要学习和准备你的展示,并准备好回答问题。但这也带来了一个好处,那就是让你在公众面前讲话的经验,这是一个非常有价值的技能。
你真正需要做的只是选择一个你自信的题目,并找到一个合适的群体进行展示。网站 meetup.com 是一个很好的资源。你可能想要从小规模开始,所以找到处理你想要展示的题目的较小、本地群体,并直接联系组织者。这些人通常很乐意有人愿意进行展示,因为否则他们必须自己付出努力去寻找人。让人们的任务变得更简单总是好事!
如果你以前从未做过演讲,一开始可能会非常紧张。在大众面前讲话,无论大小,都会让人感到害怕。就像大多数事情一样,你做得越多,就会越习惯,所以我建议,全力以赴!面对紧张,克服它!
话虽如此,如何做一个好的演讲呢?以下是一些一般性的指导原则,可以帮助你做到这一点:
-
假设你已经在像 Microsoft PowerPoint 这样的软件中制作了幻灯片,每张幻灯片只包含一个主题。你想要确保不要一次性给观众提供过多的信息,过于密集的幻灯片正是如此。
-
展示,而不仅仅是讲述。这不仅是演讲的建议,也是虚构写作的建议。如果你能展示一段代码的结果,那么就展示,而不是简单地告诉你的观众它能做什么。
-
不要简单地阅读你的幻灯片!这是一个非常普遍的问题,而且很快就会被注意到。你的观众自己完全可以看懂幻灯片;你的工作是当他们阅读时,对幻灯片上的内容进行扩展。
-
与前一点相辅相成:每张幻灯片限制在一句话或两句话以内。再次强调,过于密集的幻灯片只会分散观众的注意力。他们是在听你讲话,而不是阅读一大堆文字。你只需要一或两句话来概括你要讲的内容就足够了。
-
一般而言,每张幻灯片应该对应一到两分钟的时间。演讲,就像故事一样,应该保持一个合理的节奏。当然,会有例外,但保持这个速度是一个很好的通用准则。
-
当可能的时候,使用图形。正如人们所说,一张图片胜过千言万语。一个好的图像、图表或截图可以比一大堆文字(无论是打印的还是口头的)更好地讲述一个故事,尽管有时——每个实例都必须根据其自身优点来审视——但这是一个普遍的准则。
-
如果可能的话,在你的演讲中设置一到两个“哇!”的时刻。这可能是接近结尾,你以某种方式将你所说的内容综合在一起,或者它可能是正好在开始时,你展示了一些令人印象深刻的内容,你将讨论这些内容。或者它可能是在中间的一两个时刻。无论如何,这是一个很好的方式来吸引和保持观众的兴趣,让他们感觉自己的时间花得很值得。
当然,练习!许多人喜欢在镜子前做演讲;其他人则更喜欢让他们的伴侣听他们演讲。甚至在网上有地方可以让你在“人”面前练习,甚至在 VR 中!这样一个网站是 virtualorator.com,它允许你在与真实环境非常相似的模拟环境中练习,前提是你有适当的 VR 设备。
博客
你还可以做的是写独立文章——就像我们今天所说的博客。例如 medium.com 这样的网站使得这一过程变得非常简单,并且在这个过程中你可能会赚到一些钱。
开始使用就像注册一个账户一样简单,然后开始输入!你需要对你所写的任何主题进行所有适当的研究,如果需要的话,创建示例,并学会在发布之前编辑和审查你的作品。但除了你自己的动力和动机之外,实际上没有真正的入门障碍。
Medium 并不是唯一这样的网站,你甚至不必使用这样的网站。你可以自己托管你的文章,也许使用 WordPress 这样的东西,或者从头开始创建自己的网站,这有一个好处,就是可以展示你能做什么。最大的区别是,使用像 Medium 这样的网站可能会更快地将你的作品展示给更多的人,因为技术读者在那里聚集,从某种意义上说,而如果你有自己的网站,你需要自己进行广告和宣传。你可以从 Medium 开始,建立一些了解你作品的观众,然后迁移到你的网站。
在撰写此类文章时,以下是一些需要注意的事项:
-
无论你认为你做得有多好,都不可避免地会有某人会批评你的作品。这就是我们生活在这个世界中的现实。所以,当它来临时,做好准备,培养出厚脸皮,看看批评中是否有任何有价值的东西。可能有一些真理被一些不友善的话语所包裹,如果你能找到它(当然,总会有一些人只是为了恶意而恶意,所以你还需要学会如何和何时简单地忽略人们)。
-
记住,你的文章不一定需要很大或宏伟才能给人们带来价值。有时,仅仅几段文字就足以让某人有一个“啊哈!”的时刻,他们会像感谢你写了一本完整的书一样感谢你。
-
选择你热爱的主题。人们通常能感觉到一个作家是在例行公事还是在真正地兴奋于他们所写的内容。做后者,因为这样总是表现得更好。
技术审稿人为作者
另一种回馈的方式是成为像这本书一样的书籍的技术审稿人!
每当一位作者撰写技术书籍时,他们需要有人审阅并确保他们没有犯任何技术错误。写一本书并不容易,相信我,无论你多么努力,通常总有一些事情做得不对。也许有你不了解的操作系统之间的差异。也许有你在编写示例代码时没有使用的库的不同版本,这破坏了那些示例。也许你并没有像你认为的那样清楚地解释某些事情。所有这些就是技术审稿人发挥作用的地方。
担任技术审稿人需要你阅读、测试并寻找读者可能遇到的问题。有时,你可能需要成为书中主题的专家,但通常,如果你不是专家可能反而更好。这是因为从逻辑上讲,大多数技术书籍的读者都是在尝试学习,而不是那些已经在某个特定主题上成为专家的人。考虑到这一点,因此,一个技术审稿人可能也不是(尽管,当然,你总是会被期望在进入时拥有一些知识——这很大程度上取决于特定出版社如何处理事情)。
如何成为一名技术审稿人?嗯,基本上有三种方式。一种是你可能是一个被发现并接触的人。例如,如果你已经写了很多关于 React 的文章,那么正在编写 React 书籍的出版社可能会注意到你,并接触你,认识到你是一个知道自己在做什么的人。另一种方式是认识作者。作者经常被问到是否认识可以作为技术审稿人的人,所以如果你有一个强大的网络,那么你可能会被你认识的人询问,他们正在写书。第三种方式是直接接触出版社!访问他们的网站,寻找编辑或可能是收购人员的联系方式,并发送一封邮件说明你感兴趣成为技术审稿人。你可能不会立即成功,但你也可能发现自己被添加到一份名单中,稍后会被联系。
我还会给你一个额外的第四种方法:阅读现有的书籍,寻找错误,并将错误数据发送给出版社。这不仅会磨练你的技术审稿人技能,并帮助作者和出版社,而且如果你提交了很多,可能会引起注意,这可能会带来未来的机会。
自己写书
当然,在某个时候,你可能会超越博主或技术审稿人的角色,决定自己写一本书可能是一件你想做的事情。
例如,Packt 这样的出版社对新作者非常开放,所以如果你认为你可能想尝试,那么绝对值得一试。如果你已经有一些写作,出版社可能会注意到你,并接触你,但你也可以自己主动接触他们。大多数,如果不是所有出版社,都在他们的网站上提供了指导,说明如何向他们提交书籍提案。通常,这涉及到提交一些关于你想写什么的概述信息,而且通常,他们还会想要一个粗略的大纲,以展示这本书将是什么样的。
实际上,这有点像钓鱼:你把你的提案当作诱饵扔进水里,然后等待咬钩!一旦有反应,那么艰苦的工作就开始了!
虽然这是一项艰巨的工作,但它对你有很大的好处。首先,鉴于这是任何书籍的关键要求之一,学习和练习如何清晰地表达自己是一个好处。另一个好处是,作为作者,人们期望你非常了解你写的内容,但由于没有人知道一切,你可能需要在写作过程中进行一些研究并学习相关知识。
此外,在你的简历上写上你写过一本书或两本书,看起来也不算太差!
此外,如果你最终写出一本畅销书,还可以获得一些非常不错的经济收益。自从互联网成为今天这个样子以来,出版行业已经变得有些艰难,但人们仍然在阅读,甚至经常更喜欢书籍,所以这些山丘上仍然可能蕴藏着金子。但即使你不通过写书致富,从书中获得的除了金钱之外的好处也可以使它成为一种值得的经历。只是记住,这将是一大堆艰苦的工作,所以要做好准备。但只要你有这个准备,就去尝试吧,我说!
导师
你可以回馈社会的最后一种方式是成为导师。我更专注于成为导师,因为成为教师通常需要标准化的资格证书、学位和其他认证。然而,成为导师通常不需要。
你可以辅导谁呢?嗯,实际上可以是任何人,但一个很好的可能性是你镇上的老年人。也许你附近有一个老年人中心,或者一个活跃的老年人中心。这些地方有时会有夜间的课程,供想学习技术技能的人参加。现在,可能对网络开发学习的需求不会太多,但你永远不知道!即使没有,教人们如何一般地使用电脑,以及如何使用万维网,对他们来说可能非常有价值,同时也能帮助你确保自己掌握了基础知识。
你有时可以通过频繁访问在线社交网站,这些网站上有的人在讨论网络开发,并寻找那些自己也在寻求帮助的人,来进入导师的角色。有时,你会看到有人直接寻求导师,有人指导他们的学习。加入他们并主动提出帮助他们!
还有一些专门用于寻找和成为导师的网站。一个例子是mentorcruise.com。虽然你可以在该网站上成为导师并赚取一些钱,但通常这只是一个次要的考虑因素,因为帮助人们才是首要的。然而,成为导师需要投入时间,所以你必须确保自己准备好了。这可能需要很多时间,也可能不需要太多时间;这完全取决于你达成的安排。
这一部分的主旨是,当你帮助他人时,你以各种方式帮助自己。是的,这可能以额外收入的形式出现,但这并不是全部。这实际上关于帮助他人带来的积极感受,以及帮助他人带来的技能和知识的好处。
但无论你选择是否指导他人,这些都可能随着时间的推移推动你建立自己的职业生涯,通过不断晋升到各种职位来提升自己。让我们来谈谈你可能渴望的一些职位。
值得追求的高级职位
成为网页开发者本身就是一个很好的目标。你可能会发现它完全值得并令人满意。多年前,我有一位经理愿意接受工资削减,只是为了能回到只做程序员的岗位,因为他喜欢这份工作的编程部分,但发现他真的很讨厌管理部分。
另一方面,有些人发现他们喜欢管理他人,指导项目,以及做经理和高层通常做的事情。也许在你成为网页开发者一段时间后,你会环顾四周,开始思考,“我可能已经准备好迎接新的挑战了。”
幸运的是,如果网页开发者想要的话,他们可以在职业生涯中通过几个高级阶段,一直晋升到像首席信息官这样的高管职位。为了给你一个关于你潜在职业发展路径中可能遇到的工作类型的想法,让我们来看看你可能会遇到的一些最常见的职位名称。
明确地说,在这个领域(或实际上任何其他领域)取得进步并没有一个确定的指南!但鉴于对各种角色及其要求的普遍了解,我们可以总结出一些一般性的建议,这些建议应该有助于你晋升到每个新的职位。
高级开发者
在获得足够的实践经验后,网页开发者可以晋升到高级职位,在那里他们将专注于更复杂的项目,并开始成为初级开发者的导师,从技术角度一般性地领导开发工作。
这个职位的要求可能包括以下内容:
-
在各种语言和框架中拥有更高级的编程技能。
-
对软件开发方法论的深刻理解,并能够应用它们。
-
除了编程之外,对行业工具的实践经验,如数据库管理和性能优化,是必须的。
为了晋升到这个职位,可能需要采取以下一些行动:
-
开始指导初级开发者,以展示你的领导力和沟通技巧。寻找你可以帮助他人的机会。
-
承担更复杂的工作,以展示你的问题解决和项目管理能力。当系统中的某个挑战性部分出现时,主动提出去做,并掌控这项工作。
-
展示对新技术的持续学习,以保持最新并增加对组织的价值。当关于如何做事的决定正在被做出时,提出新的想法和技术,在它们合理的地方提出。
技术负责人
在这个角色中,你将负责监督项目在广泛的技术方面的技术细节,确保开发团队能够满足项目的技术需求和标准。在此阶段,你还将作为开发团队和管理层之间的一种桥梁。高级开发人员和首席开发人员之间的区别通常非常微小,通常在某个组织中可能只存在其中一个角色。
这个角色的要求可能包括以下内容:
-
在高级角色中领导开发项目的证明经验
-
在多个项目中展示解决问题的能力、技术能力和团队管理技能
-
能够将业务需求转化为技术解决方案的能力是必要的
要晋升到这个角色,可能需要采取以下一些行动:
-
努力证明你能够在不同部门之间工作,与其他团队沟通以促进项目执行的顺畅。展示你能够将所有部件组合在一起是关键。
-
倡导代码质量、测试和维护的最佳实践。成为提升你参与项目质量的一名领导者。
-
有效地管理项目时间和交付成果,以展示组织能力。你不必亲自担任项目经理,但你确实需要与项目经理紧密合作,帮助组织你们共同工作的项目,以展示你能够履行这些职责。
架构师
无论软件、Web 还是系统架构师,架构师负责项目的整体设计和架构,关注可扩展性、安全性和与其他系统的集成等问题。他们做出高级设计选择,并设定技术标准,包括要使用的工具、要构建的平台以及要遵守的编码标准。通常,在这个阶段,你将停止日常编码,尽管许多架构师仍然会抽出一些时间进行少量编码,但你将花费更多时间在图表、文档和会议等方面。
这个角色的要求可能包括以下内容:
-
对系统设计、架构模式和最佳实践的深入理解
-
在多个项目中展示设计可扩展、安全、稳健系统的经验
-
了解各种架构风格、框架以及其他项目构建的基础技术
要晋升到这个角色,可能需要采取以下一些行动:
-
设计和实施与业务目标一致的系统,证明有战略思维。在项目工作中,寻找可以提出建议的机会,这些建议将主动让业务利益相关者感到高兴,解决他们可能甚至没有意识到的问题。
-
领导架构审查和决策过程。让自己参与到决策会议中,并且不要害怕发表意见!
-
为组织的科技路线图和创新战略做出贡献。展示你理解业务及其对技术的需求是关键所在。
开发经理
这通常是开发者达到的第一个纯粹的管理职位,涉及监督多个开发团队,管理预算,并确保项目时间表得到遵守。开发经理处理他们团队的技术和人员管理,但依赖其他人处理许多日常技术细节。然而,一个开发经理必须有一个良好的技术基础,以便能够有效地与团队沟通。
本职工作的要求可能包括以下内容:
-
强有力的领导力和有效的人员管理技能
-
预算和资源配置经验
-
精通项目管理方法和工具
为了晋升到这个职位,可能需要采取以下一些行动:
-
发展和指导团队,建立一个强大的部门。寻找机会,提出重组团队以提高效率的方法。
-
与其他部门建立关系,以使开发工作与业务需求相一致。是的,我们在这里实际上是在谈论建立人脉,因为拥有一个可以求助并能良好合作的人脉网络表明你已经准备好担任这个职位。
工程总监
在这个层面,一个人负责监督组织内的所有工程或开发活动,为开发部门设定战略方向,并与其他高管紧密合作,确保技术项目与业务目标一致。这通常不是一个高级管理职位,但在这个职位上的人肯定会花很多时间与高管交流。
本职工作的要求可能包括以下内容:
-
战略规划和执行技能
-
在管理大型和多元化的技术团队方面拥有丰富的经验
-
对核心业务有深入的理解,并能够将技术与公司目标对齐
为了晋升到这个职位,可能需要采取以下一些行动:
-
制定支持业务目标的长远科技战略。这表明你在全局范围内,从高层次思考整个业务及其运作。
-
促进技术实践中的创新和持续改进。你需要不仅提出建议,而且要抓住每一个机会推动事情发生。
-
管理利益相关者关系,并有效地沟通科技战略。这意味着确保业务和技术人员都满意,并且朝着同一个方向努力。
技术副总裁
技术副总裁(VP of technology)的角色更为广泛,可能涵盖几个部门、技术领域或业务线。这个角色需要领导大型团队,并对公司的技术方向和愿景负责。在这里,你肯定已经脱离了技术实施的角色。相反,你是在决定大量人群将做什么。
该角色的要求可能包括以下内容:
-
在高级技术领导角色中的经验(这一点不容置疑)
-
不仅对业务有深入理解,还对业务运营的行业和市场趋势有深刻理解
-
在长时间内领导大规模技术项目和转型的能力
为了晋升到这个角色,可能需要采取以下一些行动:
-
推动技术愿景并确保其与整体商业战略相结合。在这个阶段,你是思想领袖,当人们决定将资源投向何处时,他们会向你寻求意见。
-
领导并激励不同领域的庞大技术团队。你不仅仅是象征性的角色,激发人们是这份工作的一部分!
-
建立强大的技术管理治理和流程。例如,监管问题成为你工作中的一大重要部分,这取决于行业。
首席技术官(CTO)
这是我们的首次进入 C 级管理层,这是对那些头衔以首席或 C 开头的任何人的俗称。他们是公司的执行之一。CTO 负责公司的技术路线图,专注于整合新技术,监督工程部门,并做出与公司战略目标一致的决定。在某种程度上,这更像是一个商业角色,而不是技术角色,尽管组织的整体规模是决定这一程度的重要因素。
该角色的要求可能包括以下内容:
-
创新性技术解决方案的业绩记录
-
强大的商业和技术战略规划
-
在战略层面领导技术的经验
为了晋升到这个角色,可能需要采取以下一些行动:
-
旨在定义和推动公司的技术议程,以促进创新和增长。现在是时候制定宏伟愿景了,这些目标将影响整个公司。
-
建立并维护与合作伙伴、供应商和行业领导者的高级关系。在某种程度上,像 CTO 这样的高管职位全在于关系,无论是在公司内部还是外部。
-
在高级别确保技术职能与商业目标一致。任何不利于公司整体目标的事情,你都应该努力去解决。
首席信息官(CIO)
与 CTO 相比,CIO 显然有更多的业务导向。他们管理组织的整体 IT 基础设施和为支持业务目标提供的服务。CIO 在全球层面上参与战略规划,决定技术投资,并确保公司的技术基础设施支持其整体业务目标。
此角色的要求可能包括以下内容:
-
丰富的 IT 管理和战略规划经验
-
深入理解技术如何影响商业的各个方面
-
强大的领导能力、财务知识和战略业务规划技能
为晋升到这一角色应采取的行动包括以下内容:
-
监督公司的技术基础设施,确保其符合战略业务目标。此时你很可能已经达到 CTO 级别,所以这实际上是一个在那一职位上做得好的问题。
-
制定和实施与公司目标一致的 IT 政策和系统。你现在真正掌舵了,所以你必须有一个清晰的愿景,知道事情应该如何进行,并推动你下面的人实施它。
-
领导数字化转型项目并管理组织的 IT 投资。寻找可以从技术角度彻底改变做事方式的机会,并在合理的情况下进行转型(当然,不要只是为了做事而做事!)。
通过这些角色过渡需要不断发展的技术专长、领导能力、战略思维以及对技术和商业的深入了解,尤其是在职位越高时这一点越重要。在技术和管理各个方面积累经验、建立人脉、持续学习和紧跟行业变化对于晋升到这些高级职位至关重要,同样重要的是坚实的沟通和人际交往能力。
注意,这些头衔在不同的组织中可能并不相同,有些可能根本不存在,而有些可能没有在这里展示。但这些相当典型,应该能为你提供可能经历的进步的好主意。
要通过这些工作,你必须不仅在技术能力上出类拔萃,还要培养强大的领导力、战略思维和商业技能。建立人脉、追求持续教育机会和紧跟行业变化在从网络开发者到 CIO 的职业道路上也发挥着关键作用。
审查路线图
就这样!你做到了!你已经发现了所有的方块,如图12.10 所示:

图 12.10:完成的路线图
最后三个部分——持续成长、开源贡献和认证——标志着路线图的结束。你现在可以看到成为现代网络开发者所需的所有要素,以及你需要培养的所有技能和能力,以便在这个行业中拥有一个有成效的职业生涯。恭喜!这是一段漫长的旅程,但也是收获颇丰的!
摘要
在本章中,我们探讨了一些你可以用来提升职业生涯的方法。我们讨论了 ABC 理念——始终在编码——对于保持你的技能敏锐和新鲜的重要性。我们讨论了认证可能(或可能不)在说服人们相信你的知识方面具有价值。我们还介绍了一些你可以经常访问的网站,以保持你对所有最新行业新闻和事件的了解。
我们讨论了你可以通过帮助他人来“回馈”社会,在这个过程中,帮助自己学习和保持敏锐的方法。最后,我们探讨了你可能有的职业目标,你可能会追求的角色,以及它们的要求,以及(一些)实现它们所需的东西。
呼——亲爱的读者,你做到了!你已经完成了整本书!我希望,到这一点,你对作为一名有抱负的网络开发者需要具备的素质有了清晰的认识,并且我希望我已经能够与你一起建立起一个坚实的知识基础,让你能够构建一个成功的职业生涯。最重要的是,我希望我至少在你心中培养了一种“嘿,这实际上可能很有趣,也很充实!”的感觉,因为我相信,如果你决定这是你的道路,那可能就是真的。
祝你在旅途中一切顺利,希望不久的将来能与你并肩作战!


浙公网安备 33010602011771号