loading

The Odin Project翻译+笔记

The Odin Project

AI翻译+笔记

项目来自https://www.theodinproject.com/

基础课程

概述

这正是你旅程的起点!这是一门动手实践的入门课程,你将学习构建真实、可用的网站所需的所有核心工具。你将了解网页开发者实际从事的工作——这些基础知识是你后续课程所必需的。

介绍

本课程如何运作

简介

奥丁计划(The Odin Project)是一个开源社区,致力于提供最佳的信息资源,引导您从零开始成为一名全栈开发者。

在本单元中,我们将学习网络的工作原理,并开始思考计算机和网络编程的基础知识。

接下来的每个部分和课程都涵盖了重要的基础知识。即使您无意成为网络开发者,这些内容也应有助于您理解创建网络内容并将其提供给用户所涉及的各个组成部分。

我们将从熟悉互联网和您自己的计算机开始。接下来,我们将设置开发环境,学习 Git 和 GitHub。然后,我们将学习前端技术(如 HTML、CSS 和 JavaScript)的基础知识,之后会简要涉足后端,介绍后端技术的基本概念。

在本单元结束时,您不仅应该理解网络如何运作,还应能够识别和区分您将用于构建自己网络应用程序的所有技术。您将能够构建一个基本的网页、对其进行样式设计、添加少量的交互元素,并能在命令行环境中舒适地工作。

本单元特意涵盖了非常广泛的主题。在还没有了解什么是服务端编程、为什么它有用(以及为什么您应该学习它!)的情况下就直接深入进去是很不明智的!

课程概览

本节包含您将在本课中学习的主题的总体概述。

  • 了解奥丁计划的课程体系如何运作。
  • 了解“基础课程”(Foundations course)如何运作。

运作方式

本课程通过整合互联网上关于特定主题的最佳内容来进行教学。在每节课中,我们会介绍该主题并尝试提供一些有用的背景信息,然后将您指向由他人制作的外部资源。

大多数课程都会包含一些您应该在继续学习前能够回答的问题。有些课程会包含需要您完成的练习。此外,我们在整个课程体系中提供了多个项目,通过实际构建东西来帮助您加深理解。

不要将奥丁计划或编程视为学校里的课程。它不是您为了参加考试而一次性学完,然后通过或失败的材料。您可以把它想象成一个雪球。您自己就是一个雪球。您正在一个满是积雪的山坡上向下滚动;滚得越远,粘附在您身上的雪就越多。当然,雪也会从您身上掉下来,您会经常忘记东西,但这只是过程的一部分。如果您在做一个项目时感觉自己什么都没记住,请不要担心。这很自然,每个人都会遇到这种情况。项目并不是对您目前记忆内容的测试。 它们是实践点,当您开始一步一步解决问题时,依靠谷歌和奥丁社区寻求帮助,这些信息会重新回到您的脑海中。

关于工具的一点说明

我们经常收到关于为什么本课程不涵盖某种语言或工具的问题。

事实是,一旦您完成本课程,您将不再需要像这样的课程或任何形式的“教程”。奥丁计划的目标不是教您任何孤立的技能,它也不是一个教程。主要理念是教您为什么并改变您的思维方式。

本课程最困难的部分之一就是采用正确的心态。一个更大的挑战是学习编程的核心思想。这不仅仅是关于写代码,而是关于解决问题、提出正确的问题以及进行足够的研究。达到对这些技能感到舒适的位置,也意味着您将能够自如地扩展您的技能组合。一旦您有了坚实的基础,在此基础上扩展就会变得简单得多。这正是奥丁计划的核心所在:培养这些技能

完成本课程后,您将处于这样的位置:您可以直接跳入您试图学习的任何东西的文档中。阅读文档是学习任何工具的最佳方式。但这需要技巧才能掌握。许多人会被专业术语吓到,这是可以理解的。编写文档的人通常都假设读者具备编程背景。这正是为什么我们为您提供大量需要您自行阅读的材料。

您通过完成奥丁计划所获得的技能将是您在今后数年乃至数十年内赖以发展的基础。如果您在完成课程后,认为需要另一个像这样的课程来学习像 Python 这样的东西,那么要么是您不相信自己,要么就是您还没有领会本课程所涵盖的重要思想。

您在这里学到的技能可以迁移到各种情境中。一旦您知道了某个想法并且知道做某事是可能的,那么实际找出如何做那件事就会变得容易得多,大多数情况下只需谷歌搜索一下即可。

关于人类语言的一点说明

奥丁计划吸引了来自世界各地渴望学习如何成为开发者的人们。请注意,本课程是用英语编写的,并由讲英语的人维护,他们无法或不应期望为您翻译。随着您成长为一名程序员,您会发现您正在进入的世界深深植根于英语语言。这意味着您所用编程语言的语法、教您如何使用它的文档以及社区中的大多数人,都期望用英语与您交流。

如果您不是以英语为母语的人,或者英语不是您的主要语言,这个事实并非要打击您,而是为了让您做好准备。

作为此准备的另一部分,我们强烈建议您在遇到不能立即理解的主题和术语时多花些时间。我们也鼓励您寻找用您自己母语教授这些主题的额外资源,以便您能更全面地理解它们。

除此之外,您还可以考虑在阅读我们的课程时使用您自己语言的翻译词典,以便随时查阅。我们没有具体的推荐,因为世界上使用的语言种类繁多,找到一个合适的词典可能是学习如何通过搜索在线找到有用工具的一个很好的第一步。这是您在成长为开发者的学习过程中将会运用和提高的一项技能。

后续内容

完成本课程后,您应该会对网络编程的构建模块感到得心应手,但渴望更深入地挖掘。尽管我们在本课程中花了不少时间深入探讨每个主要主题,但这实际上只是对后续内容(以及您可以用它做的所有很酷的东西)的一个初步体验。

本课程的最后一课将让您有机会在“全栈 JavaScript”和“全栈 Rails”路径之间做出选择,这两条路径都旨在进一步发展本课程中建立的基础。每条路径都将专注于将这些原始的构建模块锤炼成高度实用的技能组合。

奥丁计划由专业人士维护。我们精选了一些可用的最佳资源,并策划了一份如何学习它们的指南。如果没有好的资源,我们就自己编写。话虽如此,请知道课程中的所有内容都是有意包含的,并且对您成为一名成功的程序员至关重要。

随着您在课程中不断前进,每个部分都建立在之前所有内容的基础上,因此跳过内容会在您的知识中留下理解空白,这些空白将开始影响您解决问题的能力和理解当前任务的能力。

附加资源是唯一被视为可选的内容,除非明确说明。这些资源的存在是为了在您感觉需要或想要更深入地探讨某个主题以获得更好理解时使用。

不要跳过任何内容!

作业

  1. 阅读奥丁计划的关于页面,了解更多信息。
  2. 查看常见问题解答部分。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答某个问题,请点击它以复习相关材料,但请记住,您不需要记忆或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这不是必需的,因此请将其视为补充材料。

  • 看起来本课目前没有任何附加资源。请通过为我们的课程做贡献来帮助我们扩展此部分。

Web开发入门

简介

Web开发者是做什么的?简而言之,他们构建和维护网站。

Web开发者通常为试图将其产品或服务推向网络的客户工作。工作通常是项目导向的,涉及与团队协作,将客户的需求整合到最终产品中。客户可能是科技公司、组织或政府。工作可能涉及前端、后端或全栈Web开发。

如果您喜欢解决逻辑问题、构建有用的东西以及尝试新技术,那么Web开发可能是一个适合您的职业。Web开发人员需求旺盛,通常拥有良好的工作与生活平衡,并且薪水可观。在谷歌上搜索您所在的具体位置,以更好地了解当地的Web开发职位机会。

Web开发者的类型

之前我们提到,Web开发工作可以是前端的、后端的或全栈的。这些术语具体是什么意思?

  • 前端 (Front end) 是您在浏览器中在网站上看到的内容,包括内容的呈现和用户界面元素(如导航栏)。前端开发人员使用 HTML、CSS、JavaScript 及其相关框架来确保内容有效呈现,并为用户提供卓越的体验。
  • 后端 (Back end) 指的是应用程序的核心,它运行在服务器上。后端存储并提供程序数据,以确保前端有所需的内容。当网站拥有数百万用户时,这个过程会变得非常复杂。后端开发人员使用 Java、Python、Ruby 和 JavaScript 等编程语言来处理数据。
  • 全栈开发者 (Full-stack developers) 能够熟练地同时处理前端和后端工作。在 The Odin Project,我们专注于教授全栈开发,涵盖Web开发的各个方面。

职业类型

现在您了解了不同类型的Web开发者,让我们来详细介绍一下之前提到的您可能合作的不同类型的客户和雇主。

  • 大型科技公司,如谷歌 (Google)、Meta 和亚马逊 (Amazon),有非常严格的招聘要求。如果您成功达到这些期望,他们会提供丰厚的薪酬、福利和机会。
  • 初创公司有点像狂野西部 (Wild West)。对于初级开发者来说,由于开发节奏快,可能会感觉像一场严峻考验 (trial by fire)。初创公司通常提供的薪水稍低,工作时间更长,但也可能提供公司股权 (equity) 和非常独特的工作环境。
  • 作为自由职业者 (Freelancer),您可以获得较高的时薪,并享有安排时间和设计自己产品的自由。然而,您需要自己寻找工作(这意味着编码时间更少)、管理客户账单(这可能很困难)并独自承担责任。这条道路需要出色的人际交往能力。
  • 作为网络咨询公司的顾问 (Consultant for a web consultancy),您会牺牲一些自由职业者的薪酬潜力,但能够更专注于代码本身,而较少操心业务拓展。这个选项也提供了良好的工作与生活平衡以及薪酬。
  • 最后,大型的老牌公司仍然需要Web开发人员。这些公司提供良好的工作与生活平衡、薪酬和福利,但通常比高度专注于技术的公司发展更慢。

常用工具

以下是一些您将经常使用的基本工具。您现在可能不知道它们是什么,但将来您肯定会了解。

动力

学习编程回报丰厚,但也可能充满困难和挫折。作为一名学生,您能拥有的最强资产是构建的欲望、解决问题的头脑以及在挫折面前坚持不懈的精神。

Web开发行业有着众多背景各异的成功开发者的悠久历史,因此人们往往更关心您实际构建了什么,而不是您是如何到达那里的。

为什么选择奥丁 (Odin)?

我们希望您明白,这并非易事。

网上有很多面向初学者的其他课程,但它们通常在一个极其孤立和受控的环境中授课,并且只涵盖特定主题。

The Odin Project 对您需要了解的内容采取现实的态度,并让您在您自己的环境中设置和工作,这非常类似于您找到工作时要做的事情。

它承认您需要掌握广泛的技能组合和语言才能达到可就业的水平。

The Odin Project 之所以不断发展,正是因为像您这样的人在课程中取得进展,并通过逐步改进我们的内容来回馈社区 (pay it forward)。

一旦您开始对这些工具感到得心应手,就开始为开源项目做贡献吧,比如为 The Odin Project 做贡献!您贡献得越多,您对自身能力的了解就越深,距离被雇佣也就越近。这些项目在您的简历上也会非常亮眼!

结语

希望您对Web开发者实际做什么以及如果您决定将其作为职业后生活可能的样子有了更好的了解。这只是对Web开发世界的一个初步了解。

在这个“基础 (Foundations)”课程中,您将踏上一段旅程,遍览您最终需要了解的所有主题。本课程会涉足您可能完全陌生的各种主题,让您对每个主题稍作体验,然后继续前进。

后续课程将深入探讨这些主题。您将构建数十个脚本、项目和网站,以巩固那些能让您获得雇佣的技能。

是的,这将充满挑战。

但它也将充满乐趣。

甚至可能改变人生。

您还在等什么?

作业

  1. 阅读奥丁计划创始人撰写的文章《为什么学编程 如此之难。这篇文章对未来的学习旅程给出了现实的视角。
  2. 阅读维基百科关于网页设计的条目,该条目描述了Web开发专业的广度。
  3. 阅读 Udacity 关于前端、后端和全栈开发者的博客文章。

附加资源

本节包含相关内容的实用链接。这不是必需的,因此请将其视为补充材料。

学习动力与心态

简介

学习编程回报丰厚,但也可能充满困难和挫折。如同任何值得掌握的技能一样,它需要时间来习得,无法在一个周末甚至一个月内学会。话虽如此,我们相信只要有时间和精力的投入,任何人都能学会编程。

因此,在我们深入课程核心内容之前,我们将探讨以下几个方面,帮助您充分利用 The Odin Project:那些有助于您成功学习编程的因素,以及您在学习过程中应该尽量避免的陷阱。

课程概览

本节包含您将在本课中学习的主题的总体概述。

  • 识别促进健康学习的明智心态。
  • 理解有助于学习和应对困难的有益方法。
  • 在 TOP 学习旅程中需要避免的事情。

学习动力

花点时间思考一下您决定学习编程的原因。

  • 您是否想要一份报酬丰厚且充实的工作?
  • 您是否对编程提供的创造力出口感到兴奋?
  • 您是否决心培养能够构建任何您能想到的应用程序的技能和能力?
  • 您是否想通过将应用创意变成现实来创办自己的公司?

您的动力可能是这些原因的组合,也可能是完全不同的东西。无论是什么,请紧紧抓住您的动力——这将是拉动您走完这段旅程的动力,为您提供一个明确的目标去努力。

成长型思维 (Growth Mindset)

在自学任何新技能(不仅仅是编程)时,您的心态都非常重要。您的心态对您成功机会的影响,几乎超过其他任何因素。

拥有固定型思维 (Fixed Mindset) 的人认为,如果他们在第一次尝试时没能掌握某样东西,他们就永远也掌握不了。他们相信自己不够聪明,无法做到或理解某些事情。

然而,有大量研究表明,智力并非固定不变,而是可以发展的。拥有成长型思维 (Growth Mindset) 的人相信,通过努力和坚持,他们可以在任何事情上做得更好。

这对您意味着什么?这意味着您可以通过坚持和毅力 (Persistence and Grit) 来学习新技能和发展新才能。

在 The Odin Project 的学习过程中,您将会有很多次卡在某个概念或编程问题上,并可能开始质疑自己学习编程的能力。当您发现自己处于这种境地时,请提醒自己:您可能暂时还没掌握,但凭借坚持和毅力,您终将掌握。为某件事而挣扎就是成长。您在一个概念或项目上挣扎多久并不重要;重要的是您有毅力和韧性坚持到底。这才是真正的学习发生的方式。

在学习课程时,拥抱您遇到的关于困难概念和复杂项目的挣扎。一定要庆祝您克服这些挣扎的坚持精神!

当您发现自己质疑自身能力时,反思一下您在学习编程过程中已经取得的成功:您完成的项目,以及那些您曾经不理解但现在理解的概念。这些都是您能做到的最好证明。

学习过程

学习概念然后进行实践,将帮助您更全面地理解事物如何运作以及如何相互配合。项目是确保您的理论理解与编程概念和技术实际运作方式相一致的终极方法。

在学习过程中,您的大脑会持续在专注模式 (Focus Mode) 和发散模式 (Diffuse Mode) 之间切换。

  • 专注模式 (Focus Mode) 发生在您有意识地专注于学习、阅读、观看视频或做项目时。
  • 发散模式 (Diffuse Mode) 发生在潜意识层面,当您没有主动学习时,例如洗碗、锻炼或睡觉时。在这种状态下,您的大脑会处理将您所学内容与您已知的其他事物联系起来的工作。突破往往就发生在这个阶段。

了解您的大脑在学习时会经历这两种状态非常重要,因为您可以利用这一点让学习更高效。当您在一个概念或项目上卡住时,休息一下以恢复精力,让您的潜意识去建立联系,往往能带来问题的解决方案。诀窍是先努力解决问题,然后再休息。

简而言之,理解它 (Understand it),实践它 (Practice it),最后教授它 (Teach it)。

将您所知道的知识教给他人是巩固所学内容的绝佳方式,并且常常能暴露出您知识中的漏洞,否则您可能无法发现这些漏洞。

您可以通过在我们的社区中帮助他人来实践这种学习方法(即费曼学习法)。

当您卡住时该怎么办

在学习过程中,您不可避免地会在某个时刻卡住,可能是因为某个概念难以理解,也可能是因为项目中的某些东西无法正常工作。无论是什么原因,请使用以下工具来摆脱困境:

  • 谷歌搜索 (Google it):您可以肯定,在某个时候,其他人也遇到过与您相同的问题。快速的谷歌搜索通常能找到解决方案。
  • 休息一下 (Take a break):让您的发散学习状态来处理问题。
  • TOP Discord 服务器 中寻求帮助 (Ask for help);准备好您的研究成果。当人们看到您已经付出努力尝试自己寻找解决方案时,他们会更愿意帮助您。

此外,您可以参考下图来帮助您解决可能遇到的问题:

diagram to help learners go through problems when they are stuck

48d6ec5f76b5f

关于AI代码生成的说明

随着技术的进步,我们看到了一些令人惊叹的工具出现,它们可以帮助加速编码能力。最近特别流行的一个领域是使用大型语言模型(LLMs)和生成式AI进行代码补全(如GitHub Copilot)和代码生成(如ChatGPT)。

虽然这些工具非常棒,但学习者应该意识到使用此类工具可能对核心能力产生的影响。计算机科学教授David Humphrey撰写了一篇关于ChatGPT及其对核心学习潜在负面影响的文章。这是一篇很好的读物,阐述了在教育环境中使用生成式AI的陷阱。

对于编程新手来说,像ChatGPT或GitHub Copilot这样的工具可能会导致知识盲点和漏洞。

  1. 错过探索机会: 使用生成式AI,学习者可能错过探索某物如何工作以及为何那样做的机会。
  2. 提问技能发展受阻: 提出好问题是一项重要技能,依赖生成式AI而不是向他人(如我们乐于助人的Discord社区)提问,会延迟这项技能的发展。
  3. 解释代码能力的缺失: 学习谈论自己编写的代码是另一项重要技能。在专业环境中,你很可能需要进行代码审查,解释你所呈现内容的“如何”和“为何”。在需要帮助时,利用Odin社区就你的代码提出好问题,也有助于培养这项技能。
  4. 缺乏判断输出质量的能力: 正如David Humphrey提到的,生成式AI的输出必须仔细审查,而编程新手通常缺乏足够的整体理解力来判断输出是好是坏。
  5. 提示工程是辅助技能: 虽然学习如何为AI工具提供好的提示(Prompt Engineering)是一项技能,但它对发展基础编程技能是补充性的。Odin课程致力于教授的是基础技能。
  6. AI不培养研究能力: AI工具旨在回答问题,而非帮助学习者培养研究和解决问题的能力。如果你向AI询问信息,它会提供信息给你。如果你向人类请教,他们可能会邀请你分享对问题的理解,并指导你如何探索解决方案。
  7. 面试挑战: 缺乏研究、解决问题和批判性思维的实践,面试可能会很困难,因为求职者很可能不被允许使用AI工具。

我们不建议在学习过程中使用AI工具。

管理学习时间

在Odin上取得成功的秘诀在于投入持续的时间,而不是一周只学一次。养成在特定时间、设定特定目标每天学习的习惯,将确保你取得持续的进步。

理解概念可能比其他人花的时间长,也可能更短。这并不意味着你比别人更聪明或更笨,只意味着你拥有不同的生活经历,这些经历可能为你学习这些概念做了准备,也可能没有。在工程师身边长大的人可能比没有这种经历的人有一些优势,但这并不意味着你不能学会这些技能。

The Odin Project不像大学,它是自定进度的,允许你在掌握扎实的基础上再前进。在学校里,你必须跟上进度,否则就会不及格。The Odin Project的不同之处在于,它不要求你有很多先验知识;没有先决条件。我们见证过只会用电脑查邮件的人在这里取得成功,也见证过计算机科学学位持有者在这里取得成功。将The Odin Project视为一个静态的时间线是可以理解的,但这表明期望错位了。你还不知道你不知道什么,这没关系!The Odin Project的课程没有截止日期,所以你可以花足够的时间学好它,并讨论相关主题。

截止日期会造成不必要的压力。由于The Odin Project是一个免费开放的平台,你不受任何截止日期的约束。自设截止日期可能导致仓促学习本不该仓促的概念。本课程非常注重研究,意味着你需要进行研究才能完成任务和项目。无法保证你能足够快地找到正确的文章或帖子来满足你的截止日期,但你很可能会在此过程中学到大量未来可用的知识。进行这种研究并努力写出更好解决方案的人,未来往往能成为更好的开发者。无法预知你需要多长时间才能学会如何查询概念来找到答案。对此没有硬性规定。如果你做The Odin Project是因为你现在就需要一份高薪工作,那么在你设定的时间内不太可能成为一名扎实的开发者。压力和焦虑也绝对不利于学习。放松下来,享受这段旅程吧。

长话短说:别担心,只管去学!

需要避免的陷阱

以下是初学者学习编程时常遇到的一些陷阱。请尽力避免它们。

拖延症 (Procrastination)

拖延症将是你在试图取得进展时的最大敌人。

解决方案:番茄工作法 (Pomodoro Technique) 是一种管理时间以保持专注的方法。其核心是设定一个25分钟的计时器,并专注于一项任务直到计时结束。如果在25分钟内分心或被干扰,则重新开始这25分钟的工作。一旦你成功专注工作了25分钟,休息5分钟。休息结束后,重复25分钟工作和5分钟休息的循环。完成四个25分钟的工作块后,进行一个更长的15-30分钟的休息。

番茄工作法通过强制你在无干扰状态下工作来有效避免拖延。因为工作时间只有25分钟就休息一次,所以不会让人感到不堪重负,从而更难为拖延找借口。

如果你想试试,Pomofocus 是一个可在桌面和移动浏览器上使用的可定制番茄钟计时器。

不休息

当你沉浸于学习材料时,你可能会忍不住长时间连续学习。起初看似效率更高,但这常常导致倦怠(burnout),进而降低生产力。

这似乎违反直觉,但定期退一步让大脑和身体充电,实际上会让你完成更多工作。研究表明,各种时长的休息后表现都会提升:从长时间的休假到30秒的微休息。多伦多大学管理学副教授John Trougakos说,精神专注力类似于肌肉。持续使用后我们的注意力会疲劳,需要一段休息时间来恢复,就像健美运动员在健身房组间休息一样。

解决方案: 使用前面提到的番茄工作法来安排休息的频率和时长。可以尝试不同的休息频率和时长,找到最适合你的方式。

休息时可以做什么:

  • 听音乐。
  • 写日记。
  • 涂鸦。
  • 冥想。
  • 快速玩个小游戏。
  • 到外面散个短步。

查看这篇文章了解更多关于休息与生产力的信息

数字干扰 (Digital distractions)

数字干扰包括电子邮件、Facebook通知以及社交媒体等浪费时间网站。这些干扰会打断你的专注力,并诱使你拖延。因此,在学习时间应避免它们。

解决方案: 关闭通知,并在你的浏览器中添加屏蔽器以限制你在干扰性网站上的时间。

物理干扰 (Physical distractions)

物理干扰是来自环境的干扰,如背景电视声或他人交谈声。这些干扰对专注力的破坏性与数字干扰不相上下。

解决方案: 在家里找一个安静的地方专注学习。如果不可行,可以使用降噪耳机来屏蔽环境中的噪音干扰。此外,也有免费开放的公共图书馆和大学图书馆,环境宁静舒适。有些图书馆甚至24/7不间断开放。除了提供宜人的学习空间外,周围其他人的学习状态也能营造一种高效氛围。

钻牛角尖/无谓的钻研 (Rabbit holes)

由于The Odin Project涵盖了大量材料并链接了许多高质量的课程和工具,学生很容易被“兔子洞”吸引,花费时间试图学习他们尚未准备好或对他们益处不大的主题的所有知识。我们在构建课程结构上付出了巨大努力,以便所有你需要了解的关于Web开发的重要知识都在你需要的时候被覆盖到。

解决方案: 尽可能遵循既定路径。尽量限制钻牛角尖的时间,因为这些偏离会严重破坏你的学习势头。

与他人比较 (Comparing yourself to others)

学生常常将自己与那些在编程道路上走得更远或经验更丰富的人比较。这是导致沮丧和挫败感的根源。

解决方案: 只与过去的自己比较。你的能力和知识相比上周、上个月或去年有提升吗?为你已经取得的进步感到自豪吧!

低效笔记法 (Counterproductive note-taking)

The Odin Project不建议在你的Web开发学习旅程中做大量笔记,因为这很耗时,并且常常导致努力白费。

解决方案: 不要做用于直接参考的笔记,而是做能提示你进行进一步研究的笔记。习惯阅读文档非常重要,文档本质上就是别人预先写好的笔记。

结语

学习任何新技能都是一段充满坎坷和障碍需要克服的旅程。我们希望这里阐述的原则能让你处于一个更有利的位置,在The Odin Project中取得成功并收获最大价值。

作业

  1. 加入TOP Discord服务器后,给你的动力加把劲!阅读TOP Discord服务器的成功故事论坛中他人的成功经历。
  2. 想了解更多关于成长型思维的内容,请学习以下资源:
  3. 想了解更多关于最佳学习方式的内容,观看这个Coursera "Learning How to Learn" 课程摘要。完整课程链接在附加资源中。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答某个问题,请点击它以复习相关材料,但请记住,您不需要记忆或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这不是必需的,因此请将其视为补充材料。

寻求帮助

引言

在你的编程学习之路上,你不可避免地需要在像我们这样的在线编程社区或与同事交流时提问。虽然提问看似简单,但在编程环境中,提出表述清晰、包含足够上下文的问题至关重要,这样才能让你在寻求帮助时获得最大收益。本质上就是,“帮助他人来帮助你!” 本课程将为你提供在 Discord 聊天、Stack Overflow 以及工作场所等社区中有效提问的工具!

课程概览

本节概述了你将在本课程中学到的主题。

  • 解释提出编程问题的技巧。
  • 解释并避免“糟糕”问题的特征。
  • 提出有效且表述清晰的问题。

获取最佳帮助的提示

始终提供你的代码和周边上下文

提问时,必须提供你的代码、错误信息、终端命令、服务器输出以及其他相关细节。你应该提供尽可能多的上下文,并聚焦于具体问题,例如指出代码中的特定函数或行号。提供这些相关信息能让他人更容易帮助你。

如果你的问题没有提供任何代码或其他相关信息,他人将很难帮助你。为了充分理解你的问题,将需要大量不必要的来回对话。这对你来说可能很令人沮丧,因为任何信息不完整的回答都无法解决你的问题。这个过程也会让帮助者感到沮丧,因为随着你添加上下文,你最初问题的含义会发生变化。如果你确实想提出一个概念性的问题,你应该在问题中明确指出这一点。

询问手头的问题,而不是解决方案本身

许多学习者会直接询问如何完成本课程中的特定任务或作业,例如:

  • “我该如何完成石头剪刀布作业的第 5 步?”

请记住,你应该自己想办法解决作业,想出解决方法是学习旅程中必不可少的部分。一个更好的提问方式可能是这样的:

  • “你好,我正在尝试在石头剪刀布游戏中返回一个显示获胜者的字符串,但在第 12 行遇到了语法错误。我该如何修复?这是我的代码。”

通过分享你的尝试,他人知道你尝试过什么,就不会建议那些对你可能无效的方法。这也使他们能够调试你当前的代码版本,而不是让你重新开始(当你可能已经很接近解决方案时)。

当然,如果你完全不知道从哪里开始,让别人知道你卡住了是完全没问题的。询问可以从哪里开始以及可以研究什么来走上正轨,能让你在未来有能力独立解决大部分问题,甚至可能让你以后能帮助遇到相同问题的其他人。也建议你分享你的伪代码(pseudo code),这样人们可以将你推向正确的方向或纠正你可能存在的误解。

不要对他人要求更多上下文耿耿于怀

在编程社区提供帮助的人是来帮忙的!如果你被要求提供更多上下文,可能意味着你自认为连贯且“显而易见”的问题其实并不如此。对初学者来说“显而易见”的事情,对专家来说可能远非如此。专家知道可能导致某人遇到问题的更多情况,并且会希望避免将学习者引向错误的道路。人们是抽出自己的时间来帮助你的,所以请让他们尽可能容易地帮助你!他们要求更多信息很可能有充分的理由,所以当他们询问时,请相信他们的判断和经验。许多在编程聊天中提供帮助的人是无偿志愿者,他们完全没有义务回答你的问题。但正因为他们真心想帮助你,他们才会在需要时要求更多信息!

作业

  1. 不要问‘能不能问’,直接问 说明了直接提出你的问题,而不是先问能不能提问的重要性。
  2. 阅读关于 “XY问题” 的介绍,这是新老程序员在提问时都会陷入的常见陷阱。
  3. 阅读并收藏我们的社区指南 如何提出技术问题。当你需要寻求帮助时,这是一个很好的参考,而且在思考文章中列出的要点时,你甚至可能发现自己就解决了问题。

知识检查

以下问题是反思本课程关键主题的机会。如果你无法回答某个问题,可以点击它回顾相关材料,但请记住,你不需要死记硬背或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这些内容不是必需的,因此仅供参考。

加入 Odin 社区

引言

与他人协作是成为一名 Web 开发人员工作的重要组成部分。因此,我们在 The Odin Project 鼓励你参与我们下面将详细介绍的在线聊天社区。加入社区后,你可以与其他 Odin 学员共同成长,并互相帮助学习 Web 开发。同时,你还可以查看我们的 Facebook 页面在 X 上关注我们 并在我们的 Instagram 页面上了解最新动态。使用 #TheOdinProject 标签来分享你在 The Odin Project 的进展、更新、想法,并看看其他 Odin 学员都在做什么!

课程概览

本节概述了你将在本课程中学到的主题。

  • 了解 The Odin 社区以及如何加入。
  • 解释如何提出优质且详尽的问题。
  • 解释帮助他人解决问题的良好实践。

为什么社区对你来说很棒

学习 Web 开发将是一段漫长而艰辛的旅程,但通过参与我们的 Discord 社区,你可以让这场“马拉松”变得有趣得多。无论你以什么进度学习我们的课程,总会有人比你领先几步并愿意提供帮助。此外,帮助那些比你落后几步的人是加深你自己理解并巩固所学知识的绝佳方式。

当你陷入“绝望沙漠”,代码无法运行甚至让你感到困惑时,你可以在我们的社区中找到知识和鼓励的“绿洲”。The Odin Project 的老手们乐于帮助填补知识空白,并就如何改进你的代码提供新的见解和视角。我们都曾经历过!

还记得那个你费尽心思才搞懂、并为完成它而感到无比自豪的项目吗?通过我们的社区,你将可以与那些完全理解其中付出了多少努力的人分享你的作品和进展。

为什么社区对 Odin 来说很棒

我们正在努力更新现有课程并制作新内容,因此我们非常乐意听取你对课程和项目的反馈。我们希望你觉得课程有趣、吸引人且信息丰富,项目具有挑战性但可完成。所以请告诉我们你的想法!

在寻求帮助之前

由于大多数项目旨在将你推向极限,请记住你总有一个可以求助的社区!你不需要立刻知道如何解决每个问题,但你确实需要对目标方向有一个大致的了解。这在提问时变得尤为重要,因为有时问题出在你的方法而不是代码上。

如果你感到卡住了,是时候停下来喘口气了。将问题分解成小块,然后确定真正阻碍你的是什么。我们称这种技术为小黄鸭调试法 (Rubber Duck Debugging)

你还应该使用 Google 查找与你的问题相关的信息。阅读如何使用 Google 解决你的编程问题。你也可以回顾之前的课程,寻找可以应用到当前任务中的工具。

如果这些方法都没有为你找到解决方案,那么是时候联系 Odin 社区寻求帮助了。

寻求帮助

你已经花了一些时间尝试自己解决问题,现在是时候启动 Odin Discord 寻求帮助了。首先要记住的是不要问“能不能问”,直接问。虽然这是一个简单的理念(还有一个相当朗朗上口的座右铭!),但它可以帮助你更快地得到问题的答案,并让其他人更愿意轻松地为你提供帮助。

此外,当你提问时,你应该通过构建一个优质的问题来帮助社区帮助你。

提问时,请记得包含以下上下文

  • 你认为问题是什么?
  • 你期望发生什么?
  • 实际发生了什么?
  • 你是怎么走到这一步的?
  • 到目前为止你尝试了什么?

如果你无法精确定位问题,可以分享一张截图。这对于显示命令行中的命令输出特别有用。在 Discord 中,将截图图像文件拖放到聊天框中即可上传,或者使用 PrtScn (Print Screen) 键和粘贴快捷键。如果你不知道如何在电脑上截图,这是问 Google 的好时机。

截图非常适合显示命令的输出或命令行的错误信息。截图也适用于显示代码的输出,例如网页上的视觉呈现效果或浏览器中的控制台输出。然而,你始终应该包含包含错误信息的相应文件。即使代码量很短,在 Discord 服务器中以正确的格式提供代码以及输出截图,也比仅仅提供截图更有助于调试。当你分享输出截图或视觉效果时,请确保将你的项目推送到 GitHub 或使用合适的在线 REPL(如 CodePen)分享你的相应代码,以便其他人可以仔细检查和调试代码。输出截图和能够重现问题的相关代码将有助于帮助你的人更容易理解问题。你很快就会学到关于 GitHub 的所有知识。

有时可能周围没有人能帮助你解决你的问题。这是熟悉 Discord 搜索功能的理想时机!搜索特定的关键词或错误信息,看看之前是否有人遇到过类似问题以及他们是如何解决的!

格式化你的问题

以可读的格式提问有助于大家更好地进行调试。以下是一些方法:

  • 如果你在命令行遇到问题: 确保包含你的输入和你收到的错误信息。

  • 在聊天室中: 可以使用反引号 (```) 使代码与普通句子区分显示。反引号位于美式和英式键盘的 Tab 键上方。反引号不同于单引号 (‘’),单引号位于 Enter 键左侧。

    • 单行代码: 在代码前后各使用一个反引号。

      `你的单行代码`
      
    • 多行代码: 在代码上方和下方单独一行使用三个反引号。

      ```
      你的多行代码
      第二行
      第三行
      ```
      
    • 你还可以使用代码高亮为多行代码添加颜色,通过指定语言:

      ```js 或 ```JavaScript
      function hello() {
        console.log("Hello, colorful world!");
      }
      ```
      

聊天功能

  • 使用 Giphy 动图增添乐趣: 输入 /gif hi 向大家问好。
  • 输入 / 查看所有聊天命令。
  • 向帮助你的人表达感谢: 使用 @用户名 ++
  • 别忘了访问所有可用的房间!

如何帮助他人解决编程问题

不仅知道如何提出有效的问题很重要,知道如何有效地帮助他人也同样重要。请花点时间回顾这些准则,以便对你将在我们的 Discord 社区获得的帮助有正确的期望。此外,当你准备好开始帮助他人时,也请回来复习这些准则。

1. 引导而非直接给出答案

除非问题是简单的拼写错误或语法错误,否则引导他们自己找到答案更有益。这种方法将教授良好的调试技能,并提高他们解决未来问题的能力。
首先提出探究性问题,例如“你已经尝试了什么?”、“你期望这个函数做什么?”或“你认为那个错误信息是什么意思?”。

2. 只在确定答案时提供帮助

如果你不是 100% 确定答案,你可能会弊大于利,所以请让其他人来回答。
不用担心别人需要等多久才能得到答案。正确的答案值得等待。

3. 只在没有其他人正在帮助时介入

如果有人已经在接受帮助,请不要打断对话。我们知道你是好意,但对于接受帮助的人来说,同时跟进多个对话会让他们不知所措。

4. 只在你有充足时间时提供帮助

如果你没有太多时间提供帮助,请让别人来回答这个问题。

5. 根据对方的水平调整你的期望

如果问题没有透露他们在课程中的位置,请询问他们,以便根据他们的知识水平调整你的期望。

6. 请求澄清

如果问题看起来令人困惑或含糊不清,请要求更清晰的说明,或者礼貌地引导他们使用我们的机器人命令 /question,该命令链接到文章如何出色地提出编程问题

7. 要求提供实时代码

如果问题需要实时代码才能完全理解或调试,请要求他们使用合适的在线 REPL(如 CodePen)来提供。如果问题难以隔离,他们应该用隔离的代码重现该问题。

8. 不要回答能通过 Google 找到答案的问题

学习如何研究这些问题对开发者来说是一项非常重要的技能,因此我们需要赋能他们自己找到答案。当我们回答这些问题时,会阻碍他们的个人成长,并使他们过度依赖我们的社区。
与其回答这些问题,不如礼貌地请他们去 Google 搜索问题,或者使用我们的机器人命令 /search google 加上搜索关键词。

9. 不要回答我们课程中已涵盖的问题

如果你知道答案在我们的课程中提供,请问他们在课程中的进度。
如果他们还没有学到课程的那部分内容,告诉他们将来会学到。
如果他们已经学过那部分课程,请礼貌地引导他们回顾那节课。

10. 在指出其他问题之前先解决原始问题

在帮助他人时,很容易发现他们代码中的其他问题。先解决原始问题,然后再指出需要注意的其他问题。

11. 鼓励学生使用调试器

学生常常不理解使用调试器查看程序在不同点变量值的重要性。当学生得到意外值时,请礼貌地鼓励他们使用调试器,可以用我们的机器人命令 /debug

12. 留意需要退一步的学生

学生常常会过度专注于一个问题,无法清晰地看清全局。当这种情况出现时,请礼貌地鼓励他们从问题中退一步,休息一下。通常,暂时离开问题会帮助他们看清大局以及如何解决它。

13. 留意力不从心的学生

学生跳过课程/项目或高估自己知识水平的情况很常见。当这种情况出现时,请礼貌地鼓励他们回去重新阅读课程的相关部分以获得更深的理解。

14. 承认问题超出你当前知识范围的情况

实际的问题超出最初提问范围的情况很常见。如果它超出了你当前的知识范围,重要的是承认你不确定正确答案,并让别人来帮助。
在深入挖掘问题后,他们可能能够继续自行排除故障,或者可以等待更有经验的人来帮忙。

15. 保持耐心

帮助他人解决问题并非总是易事。请记住在他们努力解决问题的过程中保持耐心。

16. 感到沮丧时请退出对话

有时会产生误解,互动会变得不顺利。你是一名志愿者,当事情失控时没有义务继续提供帮助。请礼貌地退出对话,让别人来接手。

作业

  1. 首先,创建一个免费的 GitHub 帐户 正如你将发现的,GitHub 是开发工作流程中不可或缺的一部分。
  2. 现在,登录我们的 Discord 服务器 进来打个招呼吧!我们创建了一个“自我介绍”频道,这是介绍你自己的好地方,我们总是很高兴欢迎新的社区成员。我们为课程涵盖的每个开发主题都设立了聊天室。登录聊天并开始探索吧!
    • (可选)将你的 GitHub 关联到 Discord 个人资料: 点击用户名右侧的齿轮图标打开 用户设置。转到 连接,然后点击 GitHub 图标。在打开的新标签页中点击“允许访问”,然后回到 Discord 确保“在个人资料上显示”开关已打开。这将允许其他人看到你正在做什么,反之亦然!
  3. 以下是你深入参与前的一些准则:
    • 阅读规则和常见问题解答: 请花时间阅读并理解我们的规则和常见问题解答。在 Discord 左侧边栏中,导航到 TOP META -> rulesfaq
    • 记住对方也是人: 每个用户名背后都是一个有感情的人!友善一点!如果你没什么好话要说,那就什么都别说。
    • 如果你不会大声说出来,那就别打字: 简单明了。
    • 有目的地提及 (@user): 仅在必要时提及 (@) 其他用户。在消息中包含你的问题或评论。等待他们回复后再提及。
    • 不要“轰炸”聊天: 不要连续发送多条消息;输入完整的消息后再发送。
    • 不要排斥任何人: 这些是公共聊天;如果有人加入对话,请接纳他们!例外情况是当有人在帮助学习者时。这些需要是 1 对 1 的对话,以免让学习者感到困惑。
    • 不要在寻求代码帮助后立刻消失: 如果你发布了一个问题,请确保你有时间留下来与试图帮助你的人讨论!
    • 在加入之前花些时间观察服务器: 这有助于你了解我们社区的互动和交流方式。

知识检查

以下问题是反思本课程关键主题的机会。如果你无法回答某个问题,可以点击它回顾相关材料,但请记住,你不需要死记硬背或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这些内容不是必需的,因此仅供参考。

先决条件

计算机基础知识

引言

毋庸置疑,在学习编程时,您大部分时间都会花在计算机上。因此,了解如何操作您的计算机至关重要。在本课中,我们将介绍一个资源,它能帮助您快速掌握一些计算机基础知识。

课程概述

本节概述了您将在本课中学习的主题。

  • 计算机软件和硬件。
  • 如何截屏。
  • 强密码的重要性。

作业

善意社区基金会 (Goodwill Community Foundation, GCF) 的资源是让您熟悉基础知识的绝佳选择。它们配有视频和插图说明,易于阅读。以下阅读材料应该足够,当然您也可以自行探索 GCF 的更多资源。

  1. 什么是计算机?
  2. 什么是操作系统?
  3. 什么是应用程序?
  4. 开源软件 vs 闭源软件
  5. 截屏
  6. 创建强密码

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,可以点击它回顾相关材料,但请注意您不需要记忆或完全掌握这些知识。

补充资源

本节包含相关内容的实用链接。这些内容不是必需的,仅供参考。

网络是如何工作的?

引言

在理解如何为网络编程之前,你需要对网络本身有一个比你目前可能拥有的更严谨的理解。这些概念能让你对你即将工作的生态系统有更全面的认识,并使你能够与其他开发者就你的工作进行有见地的交流。

课程概述

本节概述了您将在本课中学习的主题。

  • 描述什么是互联网。
  • 描述什么是数据包 (packets) 以及它们如何用于传输数据。
  • 理解网页 (web page)、网络服务器 (web server)、网页浏览器 (web browser) 和搜索引擎 (search engine) 之间的区别。
  • 简要解释什么是客户端 (client)。
  • 简要解释什么是服务器 (server)。
  • 解释什么是 IP 地址。
  • 解释什么是 DNS 服务器。

作业

  1. 观看 BBC 的短片,了解 互联网工作原理概述
  2. 阅读 Mozilla 的文章 “互联网是如何工作的?”
  3. 观看 5分钟了解互联网工作原理
  4. 阅读关于 网页、网站、网络服务器和搜索引擎之间的区别
  5. 观看 Google 的短片,解释 什么是网页浏览器。然后,使用这个网站 查找你当前使用的网页浏览器及其版本
  6. 阅读关于 网络的不同部分如何相互交互 并阅读这篇 MDN 文章,了解 DNS 请求如何工作。或者,这里有一个 关于 DNS 请求如何工作的视频

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,可以点击它回顾相关材料,但请注意您不需要记忆或完全掌握这些知识。

补充资源

本节包含相关内容的实用链接。这些内容不是必需的,仅供参考。

安装概述

引言

构建任何网站的第一步是拥有合适的工具。对我们来说,这意味着设置一个用于编写优质代码的开发环境。

许多在线开发课程使用浏览器内代码编辑器或“沙盒”,它们只提供完成手头任务所需的工具和程序,别无其他。在 The Odin Project 的早期阶段,你会使用一些这样的沙盒,因为它们非常适合快速上手。然而,为长期成功做好准备的最佳方式是在真实的开发环境中操作。

我们不会骗你:安装软件包、编辑器,甚至整个操作系统可能会非常令人沮丧。但是,拥有设置开发环境来运行你将编写的代码的经验,是一项宝贵的、现实世界的技能,它将伴随你的整个职业生涯。

课程概述

本节概述了您将在本课中学习的主题。

  • The Odin Project 支持哪些操作系统 (OS)。

操作系统选项

关于发行版/版本的具体信息以及操作系统安装说明都在下一课中。你现在还不需要去安装任何东西。

macOS

如果你使用的是 Mac,那你的条件就很好了。The Odin Project 的说明假设是基于 Unix 的系统。只需安装几个程序,你就能立即开始你的学习之旅!

Linux (官方 Ubuntu 变体)

Linux 是一个免费且开源的操作系统,与所有编程语言都能良好配合。大多数开发工具都是为原生在 Linux 上运行而编写的。你的工具在 Linux 上更新可能更频繁、有更多的故障排除信息可用,而且运行效果就是更好。我们将使用 Ubuntu,这是最流行且用户友好的发行版之一,或者其更轻量级的替代品 Xubuntu。如果你不使用 Mac,我们建议你使用 Linux。 就这么简单。

Windows

Windows 本身 不被 The Odin Project 或我们的 Discord 服务器原生支持。因为你将使用的许多工具是为 Linux 环境编写的,所以即使你计划使用 Windows 作为开发操作系统,也需要拥有一个 Linux 环境。如果你当前正在使用 Windows,你可以使用以下选项之一来创建你的开发环境(我们在下一课中为这些选项提供了完整的分步说明):

  • VirtualBox 虚拟机 (VirtualBox Virtual Machine)
  • 双系统启动 Ubuntu 安装 (Dual-boot Ubuntu installation)
  • Windows Subsystem for Linux (WSL2)

虚拟机 是在你现有操作系统内模拟的一台计算机。它允许你在当前操作系统的一个程序中运行另一个操作系统(例如,在 Windows 中运行 Linux)。虚拟机像安装其他任何程序一样简单直接,并且没有风险。如果你不喜欢 Linux,可以轻松删除虚拟机。虚拟机是新开发者快速入门的好方法。

双系统启动 意味着在你的计算机上安装两个操作系统,在计算机启动时让你可以选择启动 Linux 或 Windows。双系统启动相对于虚拟机的优势在于,操作系统可以使用你计算机的所有资源,从而运行速度更快。安装双系统存在一定风险,因为你需要更改硬盘分区,但只要你花时间仔细阅读说明就不会有问题。

双系统启动可以像插入 U 盘并点击几个按钮一样简单。双系统启动的好处怎么说都不为过。你将让 Linux 能够充分利用你的硬件性能,拥有一个干净、无干扰的编码环境,并学习世界各地许多高级开发者和服务器所使用的平台。

Windows Subsystem for Linux (WSL) 让你可以在现有的 Windows 安装中运行一个成熟的 Linux 环境,通过简化的安装过程为你提供 Linux 的所有优势。在课程中,我们将使用 WSL 的第 2 版,通常称为 WSL2。

Chromebook

对于 Chromebook 用户,你可能可以在设备上运行 Linux 终端。更多细节将在下一课中提供。

担心安装新操作系统?

“哇,哇,哇!我很满意我现在的操作系统!”

如果你没有苹果电脑,很可能正在使用 Windows。别担心!上面的选项并不意味着你需要卸载 Windows。Linux 会很乐意与 Windows 共享硬盘。我们知道你可能已经为你的最爱操作系统学了很多技巧,不想丢失电脑上的所有东西。然而,大多数操作系统是为非技术人员开发的,因此它们会隐藏或难以使用我们将要安装的许多语言和框架。不得不克服这些困难导致许多新开发者在开始通往全栈理想境界的旅程之前就放弃了。

修改计算机或安装双系统以配合你所需的工具,将使开始编程变得容易得多,有助于创建一个无干扰的环境,并且会让你的简历看起来更棒。深呼吸,让我们看看你的选择。

还是不信服?以下是安装 Linux 的几个重要理由:

  • 经过测试 - 我们已经在 macOS、Ubuntu(及官方变体)和 WSL2 上测试过我们的指导说明。我们做了研究,以便你可以尽可能少遇到问题地安装工具,让你更快地进入编码状态。花在跟操作系统较劲上的时间,就是从学习编码中夺走的时间。
  • 社区支持 - 使用我们推荐的工具,在你遇到麻烦时我们能更容易地提供帮助。
  • 开发工具是为 Linux 构建的 - Ruby (on Rails) 和 Node.js 是 The Odin Project 涵盖的流行后端技术,在更大的 Web 开发社区中也被广泛使用,它们是开源项目,明确期望在像 Linux 这样的开源(基于 UNIX)平台上运行。
  • 像专业人士一样工作 - 许多开发者使用基于 Unix 的操作系统。
  • 性能 - 你是否因为机器慢、旧或空间有限而担心安装 Linux?当性能是优先考虑因素时,Linux 是一个绝佳的选择。它比 Windows 使用更少的系统资源并占用更少的硬盘空间。

许多学习者来到我们的 Discord 频道询问是否需要遵循本页的指导。我们 Discord 服务器的版主们撰写了你刚才阅读的关于安装计划的所有内容。在我们 Discord 服务器上支持学习者的成员们都同意本页的指导,并将给出你在此处读到的相同建议。

在我们继续之前,必须强调一个重要细节:

我们只能支持我们课程范围内提供的内容。我们无法支持原生 Windows 作为开发环境。 使用 Windows 的问题已被多次讨论,目前并不可行。请不要要求我们支持 Windows,也不要在 Discord 中提出这个问题。我们不断评估我们的课程,以保持内容尽可能新鲜和易于访问,而 Windows 尚未被证明是一条低阻力路径。有关 The Odin Project 和 Windows 的更多信息,我们有一份说明为什么 The Odin Project 不支持 Windows 操作系统的原因列表

说完这些,我们需要设置一个合适的开发环境了!

补充资源

本节包含相关内容的实用链接。这些内容不是必需的,仅供参考。

安装

引言

如果您已经在使用 MacOSUbuntuUbuntu 官方版 作为您的操作系统,并且已安装了 Google Chrome 浏览器,那么您可以跳过本课程。否则,请点击下方您想要使用的安装方法左侧的小箭头以展开该部分,然后按照安装说明进行操作。

请注意您使用的操作系统

我们仅支持上面提到的操作系统。我们的说明已在 MacOS、Ubuntu 和 Ubuntu 官方风味版上经过测试。我们不建议安装仅基于 Ubuntu 的发行版(例如 Mint、Pop!_OS、ElementaryOS 等)。

课程概述

本节包含您将在本课程中学到主题的概要。

  • 如何设置合适的环境以学习 The Odin Project 课程。
  • 在您的环境中安装 Google Chrome。

任务

  1. 如果您尚未运行受支持的环境,请确定您将要设置哪种环境。
    • 通读安装说明,以便了解需要做什么。
    • 从下方选择并遵循其中一种安装说明。
  2. 在您设置好环境后,请继续按照 Google Chrome 的安装说明进行操作。

操作系统安装

重要提示

本课程仅支持使用笔记本电脑、台式机或受支持的 Chromebook。我们无法帮助您在树莓派(RaspberryPi)或任何其他设备上设置开发环境。如果您已经在使用 MacOSUbuntuUbuntu 官方版 作为您的操作系统,则只需遵循以下其中一组说明,或者无需遵循任何说明

请从下方选择您的安装方法:

虚拟机(推荐)

安装虚拟机 (VM) 是创建 Web 开发环境最简单、最可靠的方法。虚拟机是在您当前操作系统(如 Windows)内运行的完整计算机模拟。虚拟机的主要缺点是速度可能较慢,因为您本质上是在同时运行两台计算机。我们会采取一些措施来提高其性能。

步骤 1:下载 VirtualBox 和 Xubuntu

安装虚拟机是一个直接的过程。本指南使用 Oracle 的 VirtualBox 程序来创建和运行虚拟机。该程序是开源、免费且易于使用的。夫复何求?现在,让我们确保我们已经下载好所有东西并准备好进行安装。

重要提示

一旦您完成这些说明, 您将完全在虚拟机内工作。 最大化窗口,如果您有额外的物理显示器,可以添加更多虚拟显示器,然后从桌面左上角的 鼠形菜单 (Whisker Menu) 蓝白色的鼠形菜单图标 启动互联网浏览器。在使用 The Odin Project 时,您不应使用虚拟机之外的任何东西。如果您在使用虚拟机一段时间后感觉理解良好,并且/或者想要改善体验,我们推荐双启动 Ubuntu,下面有相关说明。

步骤 1.1:下载 VirtualBox

下载适用于 Windows 主机的 VirtualBox

步骤 1.2:下载 Xubuntu

有成千上万的 Linux 发行版,但 Xubuntu 无疑是最流行且用户友好的发行版之一。在虚拟机上安装 Linux 时,我们建议下载 Xubuntu 22.04。这里列出了几个文件,下载以 .iso结尾的那个文件。Xubuntu 使用与 Ubuntu 相同的基础软件,但具有一个需要较少计算机资源的桌面环境,因此非常适合虚拟机。如果您发现下载速度慢,可以考虑使用不同的 Xubuntu 22.04 镜像 (之前链接的是美国镜像)。如果您到达下载页面但不确定选择哪个版本,建议您选择长期支持 (LTS) 版本 22.04 (Jammy Jellyfish)。您可能想选择更新的版本,但这个版本经过 The Odin Project 社区的测试和验证,因此是本课程最可靠的选择。

步骤 2:安装 VirtualBox 并设置 Xubuntu

步骤 2.1:安装 VirtualBox

安装 VirtualBox 非常简单。它不需要太多技术知识,过程与在 Windows 计算机上安装任何其他程序相同。双击下载的 VirtualBox 文件将开始安装过程。如果您收到关于需要 Microsoft Visual C++ 2019 Redistributable Package 的错误,您可以在 官方的 Microsoft Learn 页面上找到它。您很可能需要 X64 架构(即 64 位)的版本 - 下载并安装它,然后再次尝试安装 VirtualBox。

在安装过程中,您会看到各种选项。我们建议取消勾选 Python 支持(您不需要它),方法是点击带有箭头的驱动器图标并选择 整个功能将不可用

Python 选项在列表底部

关闭 Python 支持后,您的安装窗口应如下所示:

您希望 Python 选项上有一个猩红色的 "X"

确保将应用程序安装在 C: 盘上,否则可能会出错。虚拟机本身可以安装在任何地方,我们很快就会讲到。软件安装时,进度条可能看起来卡住了;只需等待它完成即可。

步骤 2.2:在 VirtualBox 中为 Xubuntu 做准备

现在您已安装 VirtualBox,启动该程序。打开后,您应该会看到开始屏幕。

VirtualBox 开始屏幕

单击 新建 按钮来创建一个虚拟操作系统。给它起个名字 Xubuntu。如果您希望虚拟机安装在默认 C: 以外的位置,请在 文件夹 选项中相应更改。这是您的虚拟磁盘所在的位置,因此请确保您至少有 30GB 的空间。在 ISO 映像 下选择 其它 - 您会看到一个窗口打开,供您在 PC 上查找 .iso 文件。它很可能在 Downloads 文件夹中。将 跳过自动安装 (Skip Unattended Installation) 保持原样。

部分选项变灰是正常的。不用担心。

 下一步 继续并按照后续步骤操作:

步骤 2.2.1:自动访客操作系统安装设置

您现在应该看到一个像这样的窗口:

无需担心产品密钥。

您需要勾选 增强功能 (Guest Additions)  在后台安装 (Install in Background) 选项,并根据您的喜好更改 用户名 (Username)  密码 (Password) 字段。请注意,您的用户名必须全部小写且不超过 32 个字符。如果您忘记更改默认密码,它将是 changeme。将 增强功能 ISO (Guest Additions ISO) 主机名 (Hostname)  域名 (Domain Name) 保持原样。按 下一步继续。

步骤 2.2.2:硬件

您可能想给虚拟机分配超过 2 个处理器。别这样做。

在安装的 硬件 部分,您需要将 基本内存 (Base Memory) 设置为至少 2048 MB 或更多(如果可能) - 上限是您总 RAM 的一半,但根据我们的建议设置 4096 MB 应该能提供流畅的体验。

例如,如果您有 8 GB(即 8192 MB)的 RAM,您最多可以为虚拟机的操作系统分配 4096 MB(1024 MB 等于 1 GB)。如果您不知道您有多少可用 RAM,可以 Google 一下如何查找。如果虚拟机运行有点慢,尝试分配更多内存!

将千兆字节 (GB) 转换为兆字节 (MB) 有困难?1 GB RAM 等于 1024 MB。因此,您可以认为 8 GB = 8 x 1024 = 8192 MB。

至于 处理器 (Processors) 您希望设置为 2,不要更多。将 启用 EFI(特殊操作系统)(Enable EFI (special OSes only)) 保持原样 - 即 未勾选 - 然后点击 下一步 继续。

步骤 2.2.3:虚拟硬盘

不要预分配全部大小。

现在,除了 磁盘大小 (Disk Size),其他设置都保持原样。我们建议给虚拟机 至少 30GB 的空间。提醒一下,这个磁盘将创建在您虚拟机创建过程第一步指定的文件夹中,但尽管如此,如果需要,该磁盘将来可以移动和调整大小。

步骤 2.2.4:开始自动安装

单击 下一步 进入 摘要 页面,在该页面上您可以单击 完成 以开始自动安装过程。它的妙处在于?它会在无需您输入的情况下,自行安装操作系统和 GuestAdditions!让它做自己的事情,当您在 预览 (Preview) 部分看到像这样的登录屏幕时,您就知道它完成了:

预览部分在 VirtualBox 窗口的右上角。

只需单击名为 显示 (Show) 的绿色箭头,您将看到一个虚拟机窗口和登录屏幕。使用您在安装过程中设置的密码登录,我们只剩下一项配置要做。

单击 完成后,您可能会收到类似这样的错误:

错误显示在 VirtualBox 窗口的右侧,措辞可能略有不同。

这意味着您必须在计算机的 BIOS/UEFI 设置中启用虚拟化 在 BIOS/UEFI 设置中启用虚拟化的替代说明。如果您有 AMD CPU,您可能要找的是名为 SVM 的选项来启用;对于 Intel CPU,则是 Intel Virtualization Technology。错误应该会告诉您它正在寻找什么。处理完这个问题后,只需 启动 机器并让事情发生,当您看到登录屏幕时,您就知道该过程已完成:

您已经可以将虚拟机全屏或最大化窗口。

步骤 3:设置正确的 sudo 权限

由于 VirtualBox 配置自动安装的方式,您的帐户没有正确的 sudo 权限。可以将它们视为 Windows 机器上 以管理员身份运行 (Run as administrator) 的等效项 - 您可以想象为什么拥有它们很重要。

步骤 3.1:导航到用户和组

首先,如果您尚未登录,请使用之前创建的用户名和密码登录,然后单击窗口左上角的 蓝白色的鼠形菜单图标 ,之后输入 Users ,您应该会看到 Users and Groups 出现。点击它。

它应该是您看到的顶部选项。由于本地化,它可能被称为不同的名称 - 尝试使用您语言中的术语。

步骤 3.2:管理组

在刚刚弹出的窗口中,您需要单击 管理组 (Manage Groups),在列表中的某处单击并键入 sudo 。这应该会将您带到 sudo 条目,如下图:

您会在 Xubuntu 的许多角落找到这样的搜索功能。

步骤 3.2:将自己添加到 sudo 组

选中 sudo 后,单击 属性 (Properties) ,在显示的窗口中勾选您的用户名,如下所示:

无需触碰其他任何东西。

然后点击 确定。您将看到一个密码提示 - 它与您登录时使用的密码相同。

步骤 3.3:重启您的虚拟机

现在这一切都完成了,您可以关闭这些窗口并重启您的虚拟机。您可以通过按 Ctrl + Alt + T 打开一个 终端 (Terminal) 窗口,然后输入 reboot ,然后按 Enter 执行该命令。或者,您可以单击 蓝白色的鼠形菜单图标,然后单击右下角的电源图标并选择 重启 (Restart)

您可能想注意此菜单中的其他选项。

不是最令人兴奋的菜单,但请注意关机选项。

步骤 3.4:测试您新获得的 sudo 权限

现在您有权访问 sudo,我们可以使用它通过 终端 更新我们的 Xubuntu。打开 终端 并使用以下命令,一个接一个:

关于在终端中输入密码的说明

当在终端中使用需要输入密码进行身份验证的命令时(例如 sudo),您会注意到输入字符时它们不可见。虽然看起来终端没有响应,但别担心!

这是一项安全功能,用于保护机密信息,就像网站上的密码字段使用星号或圆点一样。通过不显示您输入的内容,终端可以保证您的密码安全。

您仍然可以正常输入密码并按 Enter 提交。

sudo apt update sudo apt upgrade 

使用第一个命令后,系统会要求您输入密码 - 输入并按 Enter 将密码提供给终端。您输入的内容没有视觉反馈,但您确实在输入。

sudo apt upgrade 运行一段时间后,系统会询问您是否要安装东西 - 安装以更新您的机器。如果您有任何问题,请随时访问我们的 Discord 服务器 并在 #virtualbox-help 频道寻求帮助。

步骤 4:了解您的新虚拟机

步骤 4.1:关闭 Windows 浏览器中的 TOP 并在您的虚拟机中打开网站

从现在开始,坚持在您的虚拟机内使用 The Odin Project (TOP) 网站,并遵循提供的 Linux 说明。课程通常要求您在课程、您的编码空间以及终端之间复制和粘贴代码。如果您在虚拟机和 Windows 之间切换,这将无法顺利进行,因为它们是两个完全独立的环境。

记住: 在 TOP 课程的其余部分,仅参考 Linux 说明。

以下是一些帮助您在虚拟环境中入门的提示:

  • 在您的虚拟机设置中启用工具栏 - 那里有一些您可能想尝试的有用选项,特别是关于全屏或多显示器的选项。为此,请单击 设置 (Settings) ,然后导航到 用户界面 (User Interface) ,最后勾选 在屏幕顶部显示 (Show at Top of Screen)总体查看设置以了解可能的功能是个好主意。
  • 您所有的工作都应该在虚拟机中进行。您将在虚拟机内安装编码所需的一切,包括您的文本编辑器、语言环境和各种工具。虚拟机中的 Xubuntu 安装也预装了网络浏览器,但我们稍后会安装 Chrome。
  • 要在虚拟机上安装软件,您将遵循 Xubuntu 虚拟机内部的 Linux (Ubuntu) 安装说明。
  • 当在我们的 Discord 上寻求帮助时,您可能需要截图,具体取决于您使用它的位置:
    • 虚拟机内: 您可以使用 Shift + PrtSrc 截取屏幕部分区域的截图。或者,您可以单击 鼠形菜单 并输入 Screenshot,在其中您可以选择截取整个屏幕、当前窗口或选择特定区域的截图。
    • 在您的主机 (Windows) 上: 您可以使用主机键的快捷键(右 Ctrl + E)或单击 视图 (View) -> 截取屏幕截图 (Take Screenshot) 进行全屏截图。另一种方法是单击虚拟机窗口外部以取消其焦点,然后使用常规的 Windows 快捷键 Win + Shift + S 截取屏幕部分区域的截图。
  • 记住: 您将进行的与 TOP 相关的所有开发都应在虚拟机中完成。
  • 我们建议全屏(视图 (View) -> 全屏模式 (Full-screen Mode))并忘记您的主机操作系统 (Windows)。为了获得最佳性能,在运行虚拟机时关闭主机操作系统内的所有程序。
  • 要添加额外的显示器,请关闭虚拟机并转到虚拟机 VirtualBox 设置中的 显示 (Display) 选项卡并增加显示器数量。现在,在虚拟机运行时,单击 视图 (View) -> 虚拟屏幕 2 (Virtual Screen 2) -> 启用 (Enable)。您可以在多个显示器上全屏运行,但它可能会要求更多的 视频内存 (Video Memory),您在添加更多显示器时应该已经增加了。 确保在进入全屏模式之前,在窗口模式下启用您的虚拟屏幕,否则它们将无法工作。 退出全屏后,您的辅助显示器可能会关闭。您可以按照这些说明重新打开它。

常见问题/疑问

  • 如果尝试启动虚拟机时只出现黑屏,请关闭并 关闭电源 (power off) 虚拟机,在 VirtualBox 中单击 设置 (Settings) -> 显示 (Display) 并确保 启用 3D 加速 (Enable 3D Acceleration) 未勾选的,并且视频内存设置为 至少 128MB
  • 空间不足?查看这些 来自 TOP Discord 服务器的增加虚拟机磁盘空间的说明
  • 您在使用触摸屏吗? 观看有关如何为 VirtualBox 启用触摸屏控件的视频

  • 虚拟机性能提示
    • 运行虚拟机时,最小化您的 Windows 活动。如果您使用的是笔记本电脑,您可能还需要连接电源。
    • 确保您的处理器仅设置为 2 个,并且您分配给虚拟机的内存最多是您总 RAM 的一半,但至少 2GB。如果您无法腾出 2GB,请双启动。
    • 如果虚拟机中的视频卡顿,请确保将视频内存最大化到您能分配的最大值,或者如果您的机器能够处理,可以在 Windows 上播放它们。如果启用了 3D 加速,请禁用它。
    • 请注意虚拟机窗口右下角的 带有绿色乌龟和 V 的图标 图标。这表示名为 Hyper-V 的功能已开启。VirtualBox 论坛上的一个帖子描述了 如何完全关闭 Hyper-V。您希望看到的是一个带有 V 的芯片图标 带有 V 的芯片图标 。如果您在右下角没有看到这些图标中的任何一个,您必须退出全屏模式才能看到它们。
    • 如果您的性能仍然不足,请选择双启动,因为这将确保您所有的硬件规格只用于一个操作系统,从而显著改善您的体验。
  • 如果您的滚轮在 Google Chrome 和/或其他应用程序中行为异常,并且您已按照 VM 性能提示确保您的 VM 正常工作,请查看 您的增强功能 (Guest Additions) 版本是否正确

步骤 5:安全关闭您的虚拟机

您不会拔掉日常使用计算机的电源插头,对吧?为什么要对您的虚拟计算机做同样的事情呢?当您单击 X 按钮并关闭虚拟机时,您可能就得跟您的文件说再见了。在本节中,您将了解三种关闭虚拟机的方法。

选项 1 - 在虚拟机内部通过图形界面 (UI) 关机

点击 Whisker 菜单 蓝白啮齿动物图标 Whisker 菜单 ,然后点击电源图标,会提供几个修改会话的选项,其中包括关机。是的,就是之前你可能用来重启的那个地方!

选项 2 - 在虚拟机内部通过终端 (Terminal) 关机

在终端中输入 poweroff 命令即可。您的系统将立即关机。

选项 3 - 在虚拟机外部关机

安全关闭虚拟机的最后一种方法是使用虚拟机管理器(VM)界面。点击文件 (File) 选项卡并点击关闭按钮(该按钮也有一个电源图标),将弹出一个标题为 关闭虚拟机 (Close Virtual Machine) 的对话框。该对话框询问您是要保存虚拟机状态 (Save the machine state)发送关机信号 (Send the shutdown signal),还是强制关闭虚拟机电源 (Power off the machine)

虚拟机管理器文件菜单

关闭虚拟机对话框

为了安全起见,请选择 发送关机信号 (Send the shutdown signal) 的单选按钮,然后点击确定 (OK)。这将安全地关闭您的虚拟机,并且您的文件不会损坏。

Ubuntu/Windows 双系统

开始前请完整阅读本节内容

双系统 (Dual-booting) 让您的计算机上同时拥有两个操作系统,只需简单重启即可在它们之间切换。一个操作系统不会修改另一个,除非您明确要求它这样做。在继续操作之前,请务必备份所有重要数据,并确保有寻求帮助的途径。如果您迷失方向、感到害怕或遇到困难,我们随时在 Odin 技术支持聊天室 为您提供帮助。来打个招呼吧!

步骤 1:下载 Ubuntu

首先,您需要下载要在计算机上安装的 Ubuntu 版本。Ubuntu 有不同的版本(“风味”),但我们建议使用标准的桌面版 Ubuntu。如果您使用的是较旧的计算机,我们推荐 Xubuntu。请务必下载 Ubuntu 或 Xubuntu 的 64 位版本。如果您访问下载页面不确定要选择哪个版本,建议您选择长期支持 (LTS) 版本 22.04 (Jammy Jellyfish)。您可能想选择更新的版本,但此版本经过 Odin Project 社区的反复测试验证,因此对于本课程目的来说是最可靠的选择。

步骤 2:创建可启动 U 盘

接下来,按照 在 Windows 中创建 Ubuntu Live USB 的指南 创建一个可启动 U 盘,以便您可以在硬盘上安装 Ubuntu。如果您没有 U 盘,也可以使用 CD 或 DVD。

注意:您可以使用此方法尝试 不同的 Ubuntu 风味版(如果您愿意)。这些镜像允许您尝试不同的风味版而无需实际安装。请注意,从 U 盘运行操作系统会导致系统运行缓慢,并可能缩短 U 盘的使用寿命。

步骤 3:安装 Ubuntu

步骤 3.1:从 U 盘启动

首先,您需要从 U 盘启动 Ubuntu。具体步骤可能有所不同,但通常您需要执行以下操作:

  • 将 U 盘插入计算机。
  • 重新启动计算机。
  • 选择 U 盘而不是硬盘作为启动设备。

例如,在戴尔 (Dell) 计算机上,您需要插入 U 盘,重启计算机,并在计算机首次启动时按 F12 键调出启动菜单。在那里,您可以选择从 U 盘启动。您的计算机可能不完全相同,但可以通过 Google 搜索找到方法。

步骤 3.2:安装 Ubuntu

如果您想试用 U 盘上的 Ubuntu 版本,请点击“试用”(Try me)。当您找到喜欢的 Ubuntu 风味版后,点击“安装”(Install) 并继续下一步。

安装 Ubuntu 是计算机真正开始发生变化的地方。默认设置基本完美,但请务必选择 “将 Ubuntu 与 Windows 并排安装”(Install Ubuntu alongside Windows),并将分配给 Ubuntu 的磁盘空间更改为 30 GB(如果可能,可以更大)。

有关分步说明,请按照 如何在 Windows 10 旁安装 Ubuntu 22.04 并设置双系统

Intel RST(快速存储技术)

如果您在尝试安装 Ubuntu 时遇到要求您禁用 Intel RST 的错误,请按照此 在 Intel RST 系统上安装 Ubuntu 22.04 的解决方法 进行操作,特别是其中的 方案二 (Choice #2)。此过程在您将主板存储驱动程序切换为与 Ubuntu 兼容后,会强制 Windows 启动进入安全模式。一旦它启动进入 Windows,强制开启的安全模式就会被禁用,您就可以再次尝试安装 Ubuntu。

ChromeOS/ChromeOS Flex

随着近期支持运行 Linux 终端的功能加入,ChromeOS 平台已具备安装原生 Linux 应用程序的能力。如果您希望使用 Chromebook 完成 The Odin Project 课程,需要确保满足以下要求:

  1. 您的 Chromebook 在支持列表中:
  2. 您能够在 Chromebook 上成功设置 Linux 环境

成功满足以上两个要求后,您应该能够遵循整个课程中针对 Linux 的安装和操作说明。

WSL2 (高级)

使用 WSL2 是开始使用 Linux 的一种快速简便的方法,它允许您在 Windows 内部运行 Linux 发行版。WSL2 适用于 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)和 Windows 11。

需要明确的是:您将使用不同的操作系统,这不是避免使用 Linux 的方法。由于 WSL2 与 Windows 的集成方式,它常常让新学习者感到困惑。如果您希望在 Windows 和 Linux 之间有清晰的分离以便更轻松地遵循课程,请使用虚拟机。

WSL2 和 Linux 说明

因为 WSL2 是一个功能完备的 Linux 发行版,所以课程中几乎所有关于 Linux 的教学内容同样适用于 WSL2。在未来的课程中,每当有因操作系统而异的说明时,您应遵循 Linux 的说明,除非课程包含 WSL2 的特定说明。

步骤 1:安装

步骤 1.1:安装 WSL2

  • 在应用程序中搜索 PowerShell,右键单击顶部选项,然后选择"以管理员身份运行",以管理员模式打开 PowerShell。您可能会收到一个提示,询问是否允许 Windows PowerShell 对您的设备进行更改:单击"是"。
  • 输入以下命令:

    wsl --install 
  • 几分钟后,系统会提示您重新启动计算机;请照做。
  • 您应该会看到一个打开的 PowerShell 窗口,提示您输入用户名和密码。您的用户名应为小写,但可以是您喜欢的任何名称。您还需要输入一个新密码。
  • 输入密码时,您可能会注意到没有任何视觉反馈。这是 Linux 中的标准安全功能,将来所有需要输入密码的情况下都会发生。只需输入密码并按 Enter 键。

步骤 1.2.1:安装 Windows Terminal (仅限 Windows 10)

Windows Terminal 是一个终端应用程序,可让您更轻松地自定义和运行终端,并支持多个选项卡,每个选项卡可以运行各自不同的终端。

步骤 1.2.2:将 WSL2 设为默认 (可选)

除非您经常在计算机上使用其他终端,否则我们建议您在打开 Windows Terminal 时将 WSL2 设置为默认终端程序。

  • 通过在应用程序中搜索 terminal 来打开 Windows Terminal。
  • 单击新选项卡按钮旁边的下拉菜单(位于窗口顶部),然后选择"设置"(Settings)。
  • 您应该会看到一个"默认配置文件"(Default Profile)选项,旁边有一个下拉菜单。
  • 在下拉菜单中选择"Ubuntu"。
  • 单击页面底部的"保存"(Save)。

步骤 2:打开 WSL2

在 Windows 上,有三种主要方式可以打开 WSL2。

  • 如果您将 Windows Terminal 设置为默认打开 Ubuntu 终端,可以通过打开终端应用程序来启动新的 WSL2 会话。
  • 否则,您可以打开 Windows Terminal,单击新选项卡按钮旁边的下拉菜单(位于窗口顶部),然后选择"Ubuntu"。
  • 如果在应用程序搜索栏中搜索 Ubuntu,您应该会看到一个名为"Ubuntu"的应用程序;打开它以启动新的终端会话。

您可能会注意到,当您通过 Windows Terminal 打开 WSL2 时,您会看到一个具有不同配色方案和不同图标的窗口,与通过应用程序中的 Ubuntu 打开终端相比。这是因为 Windows Terminal 为 Ubuntu 提供了默认的配色方案,旨在模拟真实 Ubuntu 终端的外观。这种差异纯粹是外观上的,两者之间没有实际区别。

打开 WSL2 终端时,请确保在行首看不到 /mnt/c/mnt/c 是您在 WSL2 内工作时 Windows 安装所在的位置,在此处操作可能会产生意想不到的后果。

Google Chrome 安装

为什么选择 Google Chrome?

因为我们的课程使用 Google Chrome,并且 Chrome/Chromium 被开发者和用户压倒性地使用,我们给出的建议是经过深思熟虑的。查看这份网络浏览器使用比例(链接指向原文提到的数据来源),看看其他人最常用的是什么。

请选择您的操作系统:

Linux

步骤 1:下载 Google Chrome

  • 打开你的 终端 (Terminal)
  • 运行以下命令下载最新的 Google Chrome .deb 安装包
 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 

复制粘贴快捷键说明

你可能已经注意到,常用的键盘快捷键 Ctrl + V 在终端中无法粘贴内容。要在终端中粘贴文本,请改用 Ctrl + Shift + V 组合键。另外,知道 Ctrl + Shift + C 组合键会复制终端中任何选中的文本(之后可以粘贴)也非常有用。

步骤 2:安装 Google Chrome

  • 在终端中输入以下命令安装 Google Chrome .deb 安装包

     sudo apt install ./google-chrome-stable_current_amd64.deb 
  • 如果需要,请输入你的密码

关于在终端中输入密码的说明

当在终端中使用需要输入密码进行身份验证的命令时(例如 sudo),你会发现输入字符时不会显示出来。虽然看起来终端没有响应,但别担心!

这是一个安全功能,用于保护机密信息,就像网站上的密码字段使用星号或圆点一样。通过不显示你输入的字符,终端可以确保你的密码安全。

你仍然可以正常输入密码,然后按 Enter 提交。

你可能会看到一个以 N: Download is performed unsandboxed (...) 开头的通知。你无需担心。你可以阅读这篇 Reddit 帖子了解更多信息。

步骤 3:删除安装程序文件

 rm google-chrome-stable_current_amd64.deb 

步骤 4:使用 Google Chrome

你可以通过两种方式启动 Chrome:

  • 在应用程序菜单中点击 Google Chrome
  • 或者,在终端中使用 google-chrome 命令
 google-chrome 

Chrome 将使用这个终端输出各种消息,你将无法在此运行其他命令。不用担心这些消息。如果你想在运行 Chrome 的同一个终端中运行其他命令,请改用 google-chrome &

macOS

步骤 1:下载 Google Chrome

步骤 2:安装 Google Chrome

  • 打开 下载 文件夹
  • 双击文件 googlechrome.dmg
  • 将 Google Chrome 图标拖拽到 应用程序 文件夹图标上

步骤 3:删除安装程序文件

  • 打开 访达 (Finder)
  • 点击侧边栏中 Google Chrome 旁边的 箭头
  • 进入 下载 文件夹
  • googlechrome.dmg 拖拽到 废纸篓

步骤 4:使用 Google Chrome

  • 进入你的 应用程序 文件夹
  • 双击 Google Chrome
WSL2

WSL 不像 Windows 那样拥有图形用户界面 (GUI),因此此步骤将指导你在 Windows 中安装 Google Chrome。稍后,我们将介绍如何使用 Windows 安装的 Chrome 来预览你在 WSL 中的工作成果。

步骤 1:下载 Google Chrome

步骤 2:安装 Google Chrome

  • 打开 下载 文件夹。
  • 双击文件 ChromeSetup.exe 开始安装。

步骤 3:使用 Google Chrome

  • 开始菜单 中搜索 Google Chrome
  • 点击 Google Chrome 启动应用程序。

可选:删除安装程序文件

  • 打开 下载 文件夹。
  • 选中 ChromeSetup.exe 文件并按 Delete 键,或将其拖到回收站。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,可以点击它回顾相关材料,但请注意您不需要记忆或完全掌握这些知识。

补充资源

本节包含相关内容的实用链接。这些内容不是必需的,仅供参考。

文本编辑器

简介

文本编辑器是迄今为止最常用的开发者工具,无论您是什么类型的开发者。一个好的文本编辑器可以通过实时代码检查、语法高亮和自动格式化等功能帮助您编写更好的代码。

为什么不能使用 Microsoft Word?

富文本编辑器(如 Microsoft Word 和 LibreOffice Writer)非常适合撰写论文,但它们创建格式精美文档的特性使其不适合编写代码。这些富文本编辑器创建的文件不仅包含文本,还包含如何显示文本的信息以及嵌入文档的图形数据。相比之下,纯文本编辑器(如 VSCode 和 Sublime)不会保存任何额外信息。仅保存文本允许其他程序(如 Ruby 解释器)将文件作为代码读取和执行。

代码编辑器

您可以将代码编辑器视为专业的 Web 开发工具。它们高度可定制,并提供许多让您工作更轻松的功能。没有什么比花两个小时试图找出程序为什么无法运行,结果发现只是漏了一个右括号更糟糕的了。插件支持、语法高亮、括号自动闭合和代码检查只是使用代码编辑器众多好处中的一小部分。市面上有许多文本编辑器可供选择,但我们建议从 Visual Studio Code 开始。

Visual Studio Code(通常简称为 VSCode)是一款出色的免费代码编辑器。它具有出色的插件支持和优秀的 Git 集成功能。VSCode 是 Odin 学生和导师中最受欢迎的代码编辑器,因此在社区中很容易获得支持。

选择哪个编辑器通常是个人偏好问题,但就本课程而言,我们将假设您使用 VSCode,因为它免费、易用,并且在每个操作系统上的工作方式基本相同。请注意,这意味着如果您在课程中使用 VSCode 以外的其他文本编辑器,将无法获得帮助。

提醒:如果您使用虚拟机,应在虚拟机内安装 VSCode。当然您也可以在主机(即您的 Windows 主操作系统)上安装,但请确保在虚拟机内拥有这个关键工具。

VSCode 安装

选择您的操作系统:

Linux

步骤 1:下载 VSCode

  • 打开您的 终端
  • 运行以下命令下载最新的 VSCode .deb 安装包:
wget -O code-latest.deb 'https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64' 

步骤 2:安装 VSCode

  • 在终端中输入以下命令以安装 VSCode .deb 包:
sudo apt install ./code-latest.deb 
  • 如果提示,请输入您的密码。

关于在终端中输入密码的说明

当在终端中使用需要输入密码进行身份验证的命令时(例如 sudo),您会注意到输入时字符不可见。虽然看起来终端好像没有响应,但别担心!

这是一个安全特性,用于保护机密信息,就像网站上的密码字段使用星号或圆点一样。通过不显示您输入的字符,终端可以保证您的密码安全。

您仍然可以正常输入密码,然后按 Enter 键提交。

您可能会看到一个以 N: Download is performed unsandboxed (...) 开头的通知。您无需担心它。您可以阅读这篇 Reddit 帖子获取更多信息。

步骤 3:删除安装程序文件

rm code-latest.deb 

步骤 4:使用 VSCode

您可以通过两种方式启动 VSCode:

  • 从应用程序菜单中点击 Visual Studio Code
  • 或者,在终端中使用 code 命令。
code 
MacOS

步骤 1:下载 VSCode

步骤 2:安装 VSCode

  • 打开下载文件夹。
  • 双击文件VSCode-darwin-universal.zip
  • Visual Studio Code.app图标拖拽到应用程序文件夹图标上。

步骤 3:删除安装文件

  • 打开访达(Finder)
  • 进入下载文件夹。
  • VSCode-darwin-universal.zip拖拽到废纸篓。

步骤 4:使用 VSCode

  • 进入您的应用程序文件夹。
  • 双击Visual Studio Code
WSL2

步骤 1:安装 VSCode

步骤 2:删除安装程序文件

  • 打开 文件资源管理器
  • 转到 下载 文件夹。
  • VSCodeUserSetup-{版本号}.exe 文件拖到回收站。

步骤 3:安装 WSL 扩展

  • 打开 Visual Studio Code。
  • 导航到扩展选项卡。
  • 查找并安装 WSL 扩展

步骤 4:确保 WSL2 能正确打开 VSCode

  • 打开一个新的 WSL2 终端。
  • 运行以下命令以打开一个新的 VSCode 窗口。

     code  
  • 稍等片刻,一个新的 VSCode 窗口应该会打开,并且 VSCode 会弹出一个通知,表明它正在 WSL2 中打开。

任务

  1. 熟悉 VSCode 将帮助您节省时间并提高工作效率。通过观看这个 VSCode 初学者教程视频,您将了解 VSCode 提供的所有功能。不必跟着实际编码,只需观察视频中如何使用 VSCode。
  2. 禁用默认启用的 Copilot AI 代码补全功能。点击 VSCode 窗口右下角的小机器人图标,取消勾选代码补全框。要了解 The Odin Project 建议您禁用此功能的原因,请回顾动机与思维课程章节中的 关于 AI 代码生成的说明

附加资源

本部分包含相关内容的实用链接。这些不是必需的,仅供参考。

  • VSCode 文档是解决任何 VSCode 相关问题的好地方。
  • 这些便捷的 PDF 文件包含 VSCode 的快捷键参考:LinuxmacOSWindows/WSL2,能帮助您更流畅高效地使用 VSCode。

命令行基础

简介

害怕命令行?你并不孤单。我们脑海中总浮现这样的画面:开发者全神贯注地盯着一个黑色屏幕,白色或绿色的文字飞速闪过,他们疯狂地输入难以理解的命令来入侵公司的主机(毫无疑问,同时还在狂灌汽水,擦掉键盘上沾着的霓虹橙色奇多碎屑)。

那个带有提示符和闪烁光标的空白屏幕或窗口就是命令行界面 (CLI),你可以在其中输入命令,让计算机为你执行。虽然你无需重现上述场景,但作为开发者,掌握命令行是一项关键技能。命令行就像我们的作战基地,从这里我们可以启动其他程序并与之交互。它有自己的语法需要学习,但由于你会重复输入相同的命令数十次,你很快就会掌握最需要的那些命令。

在这堂关于命令行的入门课中,你将学习如何直接从命令行(既方便又高效)来导航你的计算机以及操作文件和目录(也称为文件夹)。你很快就会发现这并不像你想象的那么难。本课中你将学到的命令都非常简单直接,所以不要让第一次使用命令行的前景吓倒你。

课程概览

本节概述了你将在本课中学到的主题。

  • 描述什么是命令行。
  • 在你的计算机上打开命令行。
  • 使用命令行来导航目录和显示目录内容。
  • 使用命令行创建新目录和新文件。
  • 使用命令行重命名或删除目录和文件。
  • 使用命令行在程序中打开文件或文件夹。

试用你的终端

在你的计算机上打开一个终端。

  • Linux:打开程序菜单并搜索“Terminal”。你也可以按键盘上的 Ctrl + Alt + T 来打开终端。
  • macOS:打开你的 应用程序 (Applications) > 实用工具 (Utilities) 文件夹,找到“终端 (Terminal)”。你也可以使用 Spotlight 搜索来打开终端。按 Cmd + Space 打开 Spotlight,搜索“Terminal”,然后按 Enter 打开它。

打开的窗口将大部分是空白的,只有一些文本会根据你的操作系统而有所不同。在 Linux 和较旧的 Mac 上,行尾是 $;在较新的 Mac 上,行尾是 %。这个符号——称为提示符 (prompt)——表示终端正在等待你输入命令。我们现在来试试。输入 whoami 并按 Enter

它会返回你的用户名。很酷!

通常,使用终端的指南和说明会先放上这个符号来表示命令,比如 $ whoami。这是告诉你在终端中输入该命令,但不要输入 $。记住,如果你使用的是较新的 Mac,% 等同于 $

为什么现在要学这个?

在整个课程中,你将大量使用命令行,并且即将开始的安装项目将需要你使用命令行安装许多不同的软件程序。此外,你将主要在命令行中使用 Git(稍后会详细介绍)。从更宏观的角度看,在你作为软件开发人员的职业生涯中,很可能每天都要使用命令行,这使它成为你工具集中不可或缺的一项技能。

关于在终端中输入密码的说明

当在终端中使用需要输入密码进行身份验证的命令时(例如 sudo),你会注意到输入字符时它们是不可见的。虽然看起来终端没有响应,但别担心!

这是一个安全特性,用于保护机密信息,就像网站上的密码字段使用星号或圆点一样。终端通过不显示你输入的字符来保护你的密码安全。

你仍然可以正常输入密码,然后按 Enter 提交它。

像专业人士一样使用命令行

有件重要的事情你需要了解程序员。程序员很懒。真的非常懒。当被迫一遍又一遍地做某件事时,他们很可能会想方设法将其自动化。好消息是,你可以利用他们一路上创造的许多快捷方式。是时候学习如何像专业人士(也就是说,以一种非常懒的方式)使用命令行了。

首先,你可能已经注意到在命令行中复制和粘贴的工作方式与你预期的不一样。在命令行内部,使用 Ctrl + Shift + C (Mac: Cmd + C) 来复制,使用 Ctrl + Shift + V (Mac: Cmd + V) 来粘贴。例如,要从浏览器复制命令并粘贴到命令行中,先高亮命令文本并像往常一样使用 Ctrl + C,然后在你的终端中使用 Ctrl + Shift + V 粘贴它。试试看吧!

其次,你需要了解 Tab 键自动补全 (tab completion)。说真的,这个技巧会为你节省大量时间和减少挫败感。假设你在命令行中,需要进入一个很深的文件夹,比如 ~/Documents/Odin-Project/foundations/javascript/calculator/。要完整敲出这个命令太长了,而且必须完全准确才能工作。不行,我们实在太懒了!基本上,通过按 Tab 键,一旦你开始输入的内容只剩下一个匹配选项时,命令行会自动补全命令。例如,在用户主目录 (home directory) 下有一个 Documents 文件夹和一个 Downloads 文件夹是很常见的。如果你输入了 cd D 然后按 Tab,命令行会显示与你目前输入匹配的不同选项,让你知道它不确定你想要哪个:

$ cd D
Documents/ Downloads/
$ cd D

但是,一旦你多输入一些字符,它就会为你补全名称。这样,通过输入像 cd Doc[tab]O[tab]f[tab]j[tab]cal[tab] 这样少的字符(具体取决于你计算机上存在的其他文件夹),就可以写出上面完整的文件路径。测试一下,熟悉它的工作原理。你会爱上它的。

第三,有一个非常方便的快捷方式可以打开项目目录内的所有内容:.(点)。安装好文本编辑器后,你可以使用这个快捷方式一次性打开整个项目及其所有文件。这个快捷方式也常与 Git 一起使用(后面会详细介绍),比如命令 git add . 可以将目录中的所有文件添加到 Git 的暂存区。例如,如果你安装了 VS Code,你可以 cd 进入项目目录,然后输入 code .(带点号)。它将启动 VS Code 并在侧边栏中打开该项目文件夹。有关更详细的示例,请参阅本课的下一节。

从命令行在 VSCode 中打开文件

  • Linux:可以通过输入 code 从命令行打开 VSCode。你也可以通过在后面加上位置名称来打开文件夹或文件:code my_awesome_project/
  • macOS:需要一些设置。安装 VSCode 后,用你习惯的方式启动它。运行后,用 Cmd + Shift + P 打开命令面板 (Command Palette)。在出现的小对话框中,输入 shell command。出现的选项之一将是 Shell Command: Install 'code' command in PATH(Shell 命令:在 PATH 中安装 'code' 命令)。选择该选项,如果终端已打开,请重启终端。
  • WSL2:在 WSL2 的命令行中打开 VSCode 和在 Linux 中一样简单。只需输入 code,这将在 WSL2 中打开 VSCode。

作业

以下部分资源假设你的系统中有一个名为“桌面 (Desktop)”的文件夹。但是,如果你系统中的任何地方都没有这个文件夹,请执行以下操作:打开终端,通过输入 cd ~ 确保你位于用户主目录 (home directory)。然后,输入 mkdir Desktop 并按 Enter。为确保命令生效,输入 ls 并检查你的 Desktop 文件夹是否显示在其他目录中。

WSL2 用户注意:在 Download files 部分,使用 wget 命令和提供的链接在你的 WSL2 安装中获取 zip 文件 (wget https://swcarpentry.github.io/shell-novice/data/shell-lesson-data.zip)。你还需要使用命令 sudo apt install unzip 安装 unzip,然后使用 unzip shell-lesson-data.zip 来解压文件。请记住,在下面第一步链接的课程中,你的终端输出可能与课程中显示的内容略有不同。每当课程要求你进入 Desktop 时,你将改为进入用户主目录 (home directory),这可以通过 cd 命令 (cd ~) 完成。

许多资源假设你使用的是 Mac 或 Linux 环境。如果你完成了我们之前的安装课程,你应该已经在双启动或虚拟机中安装了 Linux。或者,你正在使用 macOS。如果你没有 macOS 或任何官方 Ubuntu 发行版安装,请返回 安装课程 (Installations lesson)

  1. 访问由 Software Carpentry Foundation 设计的 Unix Shell 课程。在那里,你会找到关于使用 CLI 的完整课程,但现在只专注于完成以下课程:

  2. 运用你新发现的 CLI 超能力,使用上一步介绍的 mkdirtouchcd 命令练习创建一个文件夹和几个文件。例如,一个基本的网站可能有一个主 index.html 文件、一个名为 style.css 的 CSS 样式表文件以及一个用于存放 images 的文件夹。思考如何使用这些命令来创建这些文件,并付诸实践!

  3. 让我们练习创建文件和目录并删除它们!你需要在终端中输入以下步骤的命令。如果不记得如何打开终端,请向上滚动查看提示。

    1. 在你的用户主目录 (home directory) 中创建一个名为 test 的新目录。
    2. 导航到 test 目录。
    3. 创建一个名为 test.txt 的新文件。提示:使用 touch 命令。
    4. 在 VSCode 中打开你新创建的文件,进行一些更改,保存文件,然后关闭它。
    5. 导航回 test 目录之外(即返回其父目录)。
    6. 删除 test 目录。

    就这样——练习完成了!如果你承诺从现在开始大部分任务都从命令行完成,这些命令对你来说就会变得自然而然。移动和复制文件在命令行中完成效率要高得多,即使目前感觉有点麻烦。

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它以查看相关材料,但请记住,并不要求你死记硬背或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这些内容并非必需,仅供参考。

设置 Git

简介

Git 是一个非常流行的版本控制系统。在 TOP (The Odin Project) 的学习过程中,你会非常熟悉这款软件,所以现在不必过于担心理解它。在课程的后半部分有许多专门讲解 Git 的课程。

GitHub 是一项服务,允许你使用 Git 配合其简洁的 Web 界面来上传、托管和管理你的代码。

尽管 GitHub 和 Git 听起来相似,但它们并不相同,甚至不是由同一家公司创建的。

第 1 步:安装 Git

点击下方你选择的操作系统:

Linux

步骤 1.1:更新系统

在终端中运行以下命令来更新 Linux 系统:

sudo apt update sudo apt upgrade 

关于在终端输入密码的说明

在终端中使用需要输入密码进行身份验证的命令时(例如 sudo),你会发现输入时字符不会显示出来。虽然看起来终端没有响应,但别担心!

这是一个安全功能,用于保护机密信息,就像网站上的密码字段使用星号或圆点一样。通过不显示你输入的字符,终端保证了密码的安全。

你仍然可以正常输入密码,然后按 Enter 键提交。

步骤 1.2:安装 Git

你可能已经安装了 git ,但为了确保我们拥有最新版本的 git,请运行以下命令:

sudo add-apt-repository ppa:git-core/ppa sudo apt update sudo apt install git 

步骤 1.3:验证版本

确保你的 Git 版本 至少 是 2.28,运行此命令:

git --version 

如果版本号低于 2.28,请重新遵循这些说明。

MacOS

步骤 1.0:安装 Homebrew

首先,你需要安装 Homebrew。安装前,请确保你满足 Homebrew 的 macOS 要求。满足要求后,将以下内容复制粘贴到你的终端:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 

在 Apple Silicon Mac 上,你还需要执行一个额外的步骤。查看安装 Homebrew 后的终端输出,你会看到“Installation Successful!”(安装成功!)。在终端输出更下方的位置,会有一个名为“Next steps”(后续步骤)的部分。阅读终端可能有点令人畏惧,但这是一个克服这种感觉的好机会。按照终端中陈述的后续步骤(复制粘贴给定的命令)将 Homebrew 添加到你的 PATH 中,这样你就可以使用 brew 命令前缀了。

步骤 1.1:更新 Git

macOS 本身自带一个 Git 版本,但你应该更新到最新版本。在终端中输入:

brew install git 

这将安装最新版本的 Git。很简单,对吧?

步骤 1.2:验证版本

如果你刚刚通过上一步安装和/或更新了 Git,请先关闭该终端窗口。

打开一个新的终端窗口 ,然后确保你的 Git 版本 至少 是 2.28,运行此命令:

git --version 

如果版本号低于 2.28,请重新遵循这些说明。如果你遇到 no formulae found in taps 错误:

  1. 运行 brew doctor
  2. 你将看到类似下面的输出。注意:运行 brew doctor 的实际输出可能因你运行的 macOS 版本以及你自己安装中可能存在的其他问题而异。最终,你必须运行 Homebrew 在运行 brew doctor 后提供的每一条命令行片段,以修复你的 Homebrew 安装,包括最后运行 brew cleanupBrew Doctor 示例输出
  3. 运行 brew install git 打开一个新的终端窗口,然后再次检查你的 Git 版本,它现在应该是最新的了。
ChromeOS

按照 Digital Ocean 上的从源代码安装 Git的说明操作。

第 2 步:配置 Git 和 GitHub

步骤 2.1:创建 GitHub 账户

访问 GitHub.com 并创建一个账户!在账户设置过程中,它会要求你提供电子邮件地址。这需要是真实的电子邮件地址,并将默认用于标识你的贡献。如果你注重隐私,或者只是不希望你的电子邮件地址公开,请确保在登录后访问 电子邮件设置 页面,并勾选以下两个复选框:

nihao

启用这两个选项可以防止在使用 Git 和 GitHub 时意外泄露你的个人电子邮件地址。

你可能还会在 Keep my email addresses private(保持我的电子邮件地址私密)选项下看到一个电子邮件地址。这是你的 GitHub 私有电子邮件地址。如果你打算使用它,现在请记下它,因为在下一步设置 Git 时你会需要它。

步骤 2.2:设置 Git

为了让 Git 正常工作,我们需要让它知道我们是谁,这样它才能将本地的 Git 用户(你)链接到 GitHub。在团队协作中,这允许人们查看你提交的内容以及谁提交了每一行代码。

下面的命令将配置 Git。请确保在引号内输入你自己的信息(但保留引号!)——如果你在步骤 2.1 中选择在 GitHub 上保持电子邮件私密,请使用来自步骤 2.1 的 GitHub 私有电子邮件。

git config --global user.name "你的名字"
git config --global user.email 你的邮箱@example.com

例如,如果你在 GitHub 上设置了电子邮件为私有,第二条命令将类似这样:

git config --global user.email 123456789+odin@users.noreply.github.com # 请记住在这里使用你自己的 GitHub 私有邮箱。

GitHub 最近将新仓库的默认分支从 master 改为了 main。使用此命令更改 Git 的默认分支:

git config --global init.defaultBranch main

你可能还想将你的默认分支协调行为设置为合并(merging)。你将在课程的后半部分学到这些术语的含义,但现在只需知道,我们建议在参加 The Odin Project 时,在 Git 设置过程中运行以下命令。

git config --global pull.rebase false

要验证一切是否正常,输入这些命令并检查输出是否与你的姓名和电子邮件地址匹配。

git config --get user.name
git config --get user.email

对于 macOS 用户

运行以下两个命令,告诉 Git 忽略 .DS_Store 文件,这些文件是当你使用 Finder 查看文件夹时自动创建的。.DS_Store 文件对用户不可见,存储着文件夹的自定义属性或元数据(如缩略图),如果不配置 Git 忽略它们,烦人的 .DS_Store 文件会出现在你的提交中。记得将每条命令复制粘贴到你的终端。

echo .DS_Store >> ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global

步骤 2.3:创建一个 SSH 密钥

如果你在开始 The Odin Project 之前已经在其他机器上设置了与 GitHub 配对的 SSH 密钥,GitHub 允许你将多个密钥对关联到你的账户。你可以再次遵循这些说明来设置另一个密钥对并在 GitHub 上注册它。

SSH 密钥是一个加密的安全标识符。它就像一个非常长的密码,用于标识你的机器。GitHub 使用 SSH 密钥允许你上传到仓库,而无需每次都输入用户名和密码。

首先,我们需要检查你是否已经安装了 Ed25519 算法的 SSH 密钥。在终端中输入以下命令,并根据下面的信息检查输出:

ls ~/.ssh/id_ed25519.pub

如果控制台中出现包含文本“No such file or directory”(没有这样的文件或目录)的消息,那么你还 没有 Ed25519 的 SSH 密钥,需要创建一个。如果控制台输出中 没有 出现这样的消息,你可以继续步骤 2.4。

要创建新的 SSH 密钥,请在终端内运行以下命令。

ssh-keygen -t ed25519

当它提示你保存生成密钥的位置时,只需按 Enter(回车键)。

接下来,它会要求你输入一个密码。这个密码用于加密存储在你计算机上的私钥,每次使用这些 SSH 密钥时都需要输入此密码。如果不使用密码,任何有权访问你计算机的人都可以读取私钥,并能够修改你所有的 GitHub 仓库。如果需要,请设置一个密码,但这不是必需的。如果选择不使用密码,只需直接按 Enter 而不输入任何内容。

步骤 2.4:将你的 SSH 密钥与 GitHub 关联

现在,你需要告诉 GitHub 你的 SSH 密钥是什么,这样你就可以在不每次都输入密码的情况下推送代码。

首先,导航到 GitHub 接收我们 SSH 密钥的位置。登录 GitHub,点击右上角的个人资料图片。然后,在下拉菜单中点击 Settings(设置)。

接下来,在左侧,点击 SSH and GPG keys(SSH 和 GPG 密钥)。然后,点击右上角的绿色按钮 New SSH Key(新建 SSH 密钥)。给你的密钥取一个描述性的名称,让你能记住这个 SSH 密钥来自哪台设备,例如 linux-ubuntumacbook-pro。执行下一步操作时请保持此窗口打开。

现在你需要复制你的公钥(public SSH key)。为此,我们将使用一个叫 cat 的命令将文件内容读取到控制台。(注意,.pub 文件扩展名在这种情况下很重要。)

cat ~/.ssh/id_ed25519.pub

突出显示并复制命令的 全部 输出。如果你遵循了上面的说明,输出很可能会以 ssh-ed25519 开头,并以你的 username@hostname 结尾。

现在,回到你浏览器中的 GitHub 窗口,将你复制的密钥粘贴到密钥字段中。保持密钥类型为 Authentication Key(认证密钥),然后点击 Add SSH key(添加 SSH 密钥)。完成了!你已成功添加了你的 SSH 密钥!

步骤 2.5:测试你的密钥

遵循 GitHub 的测试 SSH 连接说明。确保终端中输出的指纹(fingerprint)与 GitHub 的四个公共指纹 中的一个匹配。

你应该在终端中看到这样的响应:Hi username! You’ve successfully authenticated, but GitHub does not provide shell access.(嗨,用户名!你已成功通过身份验证,但 GitHub 不提供 shell 访问权限。)不要因为 GitHub 不提供 shell 访问而困扰。如果你看到这条消息,说明你已成功添加了 SSH 密钥,可以继续了。如果输出不匹配,请尝试再次完成这些步骤,或者到 Discord 聊天室 寻求帮助。

第 3 步:让我们知道进展如何!

你已经完成了基础安装部分,干得好!在学习路径(Paths)的过程中,还会有其他工具需要安装,所以请保持关注!

你可能觉得自己远超自己的能力范围,也可能不太理解自己在做什么。这完全正常。坚持下去。你能做到!我们会支持你的。

额外资源

本节包含相关内容的补充链接。非必读,仅供参考。

  • 理解 SSH 密钥对 SSH 是一种安全的网络协议,使用公钥加密(也称为非对称加密)的实现。对其工作原理有基本了解可以帮助你理解 SSH 密钥的全部意义。
  • 非对称加密 - 简单解释 一个解释非对称加密的短视频。

Git 基础

Git 简介

引言

Git 就像是文件和目录的一个超强的保存按钮。官方定义,Git 是一个版本控制系统。

文本编辑器中的保存操作会将文档中的所有单词记录为一个单独的文件。除非你制作副本(这很难记住并跟踪),否则你只会拥有该文件的一个记录,例如 essay.doc

essay-draft1.doc`, `essay-draft2.doc`, `essay-final.doc

然而,Git 中的保存操作会记录文件和文件夹中的差异,并保留每次保存的历史记录。这个功能改变了游戏规则。作为个人开发者,Git 使你能够回顾项目的成长过程,并轻松查看或恢复过去某个时刻的文件状态。一旦连接到网络,Git 允许你将项目推送到 GitHub 或其他替代平台(如 Bitbucket、Beanstalk 或 GitLab),以便与其他开发者共享和协作。

请注意,在我们的课程中支持 GitHub,不会帮助解决其他替代平台的问题。

Git 在你的本地机器上运行,而 GitHub 是一个位于网络上的远程存储设施,用于存放你所有的编码项目。这意味着通过学习 Git,你将能够在 GitHub 上展示你的作品集!这非常重要,因为几乎所有软件开发公司都认为使用 Git 是现代 Web 开发人员的必备技能。拥有 GitHub 作品集将为你未来的潜在雇主提供你能力的证明。

在本课中,我们将简要探讨 Git 的历史、它是什么以及它的用途。

在下一课中,我们将介绍使用 Git 的基本工作流程,这应该能加深你的理解并展示 Git 为何如此有用。

最后,你将使用 Git 设置一个项目,该项目将作为你未来项目的模板。

现在,让我们来了解 Git 是什么以及它为何如此强大!

课程概述

本节包含了你将在本课中学到主题的总体概述。

  • 解释 Git 和 GitHub 是什么以及两者之间的区别。
  • 描述 Git 和文本编辑器在保存内容和记录保存方面的区别。
  • 描述 Git 对个人开发者和开发团队为何有用。

任务

  1. 阅读 Pro Git 入门部分的第 1.1 到 1.4 章,了解本地、集中式和分布式版本控制系统的区别。
  2. 观看 2 分钟解释“什么是 Git?” - 一个关于 Git 是什么以及它如何改善个人和开发团队工作流程的视频。
  3. 阅读 “关于 GitHub 和 Git”,简要介绍 GitHub 是什么以及 Git 和 GitHub 如何协同工作。你可以跳过最后的“我从哪里开始?”部分。
  4. 如果你还没有安装 Git,请访问 设置 Git 课程。
  5. 看看 The Odin Project 的 GitHub 仓库。所有课程都存储在这里!在那里时,看看我们的贡献者,体会 Git 如何记录所有协作努力以及 GitHub 如何直观地展示这一点。

知识检查

以下问题是反思本课关键主题的机会。如果你无法回答某个问题,请点击它查看相关材料,但请记住,你不需要记忆或掌握这些知识。

  • Git 是一种什么类型的程序?
  • Git 和文本编辑器在保存内容和记录保存方面有什么区别?
  • Git 是在本地还是远程层面工作?
  • GitHub 是在本地还是远程层面工作?
  • 为什么 Git 对开发者有用?
  • 为什么 Git 和 GitHub 对开发团队有用?

补充资源

本节包含相关内容的补充链接。非必读,仅供参考。

  • Git 和 Github 的简史
  • 什么是 Git 和 GitHub?
  • 什么是版本控制?
  • 什么是 Git?

Git 基础

介绍

在本课中,我们将介绍管理项目并将作品上传到 GitHub 时常用的 Git 命令。我们将这些命令称为基本 Git 工作流程。使用 Git 时,这些命令将占到你 70%–80% 的使用量。如果你掌握了这些,就算 Git 已经过半了!

课程概览

本节包含本课中你将学习的主题一览:

  • 如何在 GitHub 上创建仓库。
  • 如何在本地和 GitHub 之间同步文件。
  • 如何为代码拍“快照”。

作业

开始之前!

  • GitHub 最近更新了默认分支的命名方式,确保你使用的 Git 版本为 2.28 或更高。可运行:

    git --version
    
  • 如果尚未设置,将本地默认 Git 分支改为 main

    git config --global init.defaultBranch main
    
  • 有关从 mastermain 的更改,请参阅 GitHub 重命名仓库

创建仓库

  1. 你应已在设置 Git课程中创建了 GitHub 账号。

  2. 在 GitHub 首页,点击右上角的“+”按钮,选择“New repository”(新建仓库)。

    如果视口较小,该按钮可能隐藏,此时可点击右上角头像,按钮会出现在你的用户名旁边。

  3. 在“Repository name”字段中输入 git_test,勾选 “Add a README file”,然后点击底部的“Create repository”。

  4. 创建完成后,你会跳转到新仓库页面。点击绿色的“Code”按钮(通常在当前分支名称右侧),在“Clone”区域选择 SSH,然后复制下面显示的 URL。注意:一定要选 SSH

  5. 回到本地终端,在你的主目录(~)下创建一个 repos 文件夹,并进入该文件夹:

    mkdir repos
    cd repos/
    
  6. 使用 git clone 命令将仓库克隆到本地,格式如下(将 USER-NAME 和 REPOSITORY-NAME 替换为你的信息):

    git clone git@github.com:USER-NAME/REPOSITORY-NAME.git
    

    如果你的 URL 以 https:// 开头,说明你选错了协议,请重新选 SSH。

  7. 运行:

    cd git_test
    git remote -v
    

    你应看到类似:

    origin  git@github.com:USER-NAME/git_test.git (fetch)
    origin  git@github.com:USER-NAME/git_test.git (push)
    

    其中 origin 是远程仓库的默认名称(也可以叫别的名字,但通常用 “origin”)。

使用 Git 工作流程

  1. git_test 文件夹内创建新文件:

    touch hello_world.txt
    
  2. 运行 git status。你会看到 hello_world.txt 在 “Untracked files”(未跟踪文件)列表中,且以红色显示,表示尚未暂存。

  3. 运行:

    git add hello_world.txt
    

    再次 git status,你会看到该文件出现在 “Changes to be committed”(将提交的更改)列表中,以绿色显示,表示已暂存。

  4. 运行:

    git commit -m "Add hello_world.txt"
    git status
    

    你应该看到 “nothing to commit, working tree clean”,表示已提交。若出现 “upstream is gone” 或“Your branch is ahead of ‘origin/main’ by 1 commit”,都是正常现象,稍后推送到远程即可解决。

  5. 运行 git log,查看提交历史,你会看到刚才的 “Add hello_world.txt” 提交条目。若终端进入分页界面,在底部显示 (END),按 q 退出。

修改文件

  1. 用你喜欢的编辑器打开 README.md。例如在 VS Code 中可运行:

    code .
    

    macOS 用户若遇到 “command not found: code”,请参考命令行基础 中的说明。

  2. README.md 中新增一行:“Hello Odin!” 然后保存。

  3. 回到终端(或在 VS Code 内置终端),运行 git status,你会看到 README.md 列在 “Changes not staged for commit”(未暂存的更改)中。

  4. 运行:

    git add README.md
    
  5. 再次 git status,你会看到 README.md 以绿色显示在 “Changes to be committed” 列表中。

  6. 打开 hello_world.txt,添加内容,保存并暂存。你也可运行 git add . 一次性暂存当前目录及子目录的所有更改。

  7. 提交所有暂存更改:

    git commit -m "Edit README.md and hello_world.txt"
    git status
    
  8. 再次运行 git log,你应看到三条提交记录。

推送到 GitHub

  1. 运行:

    git push origin main
    

    (也可简写为 git push

    注意:如果出现 “Support for password authentication was removed on August 13, 2021...” 错误,说明你用 HTTPS 而非 SSH 克隆,请参照切换远程 URL 从 HTTPS 到 SSH 更改后再重试。

  2. 运行 git status,应显示 “Your branch is up to date with 'origin/main'. nothing to commit, working tree clean”。

  3. 刷新 GitHub 仓库页面,即可看到刚推送的 README.mdhello_world.txt

避免直接在 GitHub 上编辑

虽然在 GitHub 网页端修正文档或 README 很方便,但请暂时避免在网页上直接编辑,这会引入更高级的 Git 问题,后续课程会讲解。现在建议在本地修改,再用 Git 提交并推送。

备忘表(Cheatsheet)

远程仓库相关:

  • 克隆:

    git clone git@github.com:USER-NAME/REPOSITORY-NAME.git
    
  • 推送:

    git push
    git push origin main
    

工作流程相关:

  • 暂存更改:

    git add .
    
  • 提交:

    git commit -m "描述本次快照变更的消息"
    

状态与日志相关:

  • 查看状态:

    git status
    
  • 查看历史:

    git log
    

基本 Git 语法格式为 program | action | destination,例如:

  • git add .git | add | .
  • git commit -m "message"git | commit -m | "message"
  • git statusgit | status | (无 destination)

Git 最佳实践

掌握 Git 有很多内容值得深究。在团队协作或独立开发时,良好的使用习惯都能提高效率。以下两点建议尤其实用:

  1. 原子性提交(Atomic commits):一次提交仅包含一个功能或任务的更改。这样若出现问题,可轻松回退单独的提交;同时也能写出更清晰的提交说明。
  2. 有意义的提交消息:利用原子性提交,写出对未来协作者有帮助的详细消息。后面课程将会讲解如何撰写优秀的提交消息。

更改 Git 提交消息编辑器

如果你使用 VS Code,可以设置让 git commit(无 -m)时自动在 VS Code 中编辑,而不是卡在 Vim 里。运行:

git config --global core.editor "code --wait"

此命令不会有任何输出或确认。设置完成后,你既可继续使用 git commit -m "消息",也可直接运行 git commit 在 VS Code 中编辑消息,保存并关闭标签页后提交生效。

知识检测

以下问题可帮助你回顾本课重点,点击问题可跳转查看答案所在章节:

附加资源

本节暂无额外资源,欢迎贡献扩充课程内容!

HTML 基础

HTML 和 CSS 简介

引言

现在开始:是时候真正动手制作东西了。本节将教你 HTML 和 CSS 的基础知识,它们是构成网络上几乎所有内容的两大基础构建块。

课程概览

本节概述了你将在本课中学到的主题。

  • 了解 HTML、CSS 及其如何协同工作的基本概述。

HTML 和 CSS

HTML 和 CSS 是两种协同工作的语言,共同创建你在互联网上看到的一切内容。HTML 是网页构建的原始数据。所有文本、链接、卡片、列表和按钮都是用 HTML 创建的。CSS 则是为这些纯元素添加样式。HTML 将信息放在网页上,而 CSS 则定位这些信息、赋予颜色、改变字体,使其看起来美观!

许多优秀资源将 HTML 和 CSS 称为编程语言,但严格来说,这样标记它们并不完全准确,因为它们只关注呈现信息,并不用于编程任何逻辑。JavaScript(你将在下一节学习)才是编程语言,因为它用于让网页执行操作。然而,仅用 HTML 和 CSS 也能完成相当多的工作,而且你绝对需要两者。接下来的课程将重点为你提供工具,帮助你在继续学习我们的课程并接触到 JavaScript 内容时取得成功!

作业

  1. 阅读 HTML vs CSS vs JavaScript。这是对 HTML、CSS 和 JavaScript 之间关系的快速概述。

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它以查看相关材料,但请记住,并不要求你死记硬背或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这些内容并非必需,仅供参考。

  • FreeCodeCamp 文章 比指定文章更深入一些。它涵盖了我们将在后续课程中明确教授的内容,所以不必担心记忆任何细节。
  • DevDocs.io 添加为书签以供将来参考。这是一个由 FreeCodeCamp 维护的大规模 API 文档集合。阅读 "Welcome" 消息以获取更多信息。

元素与标签

引言

HTML(超文本标记语言,HyperText Markup Language)定义了网页的结构和内容。我们使用 HTML 元素来创建构成典型网页的所有段落、标题、列表、图像和链接。在本课中,我们将探讨 HTML 元素的工作原理。

课程概览

本节概述了你将在本课中学到的主题。

  • 解释什么是 HTML 标签。
  • 解释什么是 HTML 元素。

元素与标签

HTML 页面上的几乎所有元素都只是包裹在开始标签 (opening tag) 和结束标签 (closing tag) 中的内容片段。

HTML 标签是定义网页结构和内容的基本构建块。

  • 开始标签 (Opening tags) 告诉浏览器这是某个 HTML 元素的起始点。它们由一个包裹在尖括号 <> 中的关键字组成。例如,一个段落的开始标签看起来像这样:<p>
  • 结束标签 (Closing tags) 告诉浏览器一个元素在何处结束。它们与开始标签几乎相同;唯一的区别是它们在关键字前有一个正斜杠 /。例如,一个段落的结束标签看起来像这样:</p>

一个完整的段落元素看起来像这样:

html

<p>一些文本内容</p>

让我们分解一下:

  • <p> 是开始标签。
  • 一些文本内容 代表包裹在开始和结束标签之间的内容。
  • </p> 是结束标签。

你可以将元素视为内容的容器。开始和结束标签告诉浏览器该元素包含什么内容。然后浏览器就可以利用这些信息来决定如何解释和格式化内容。

HTML 有一个庞大的预定义标签列表,你可以使用它们来创建各种不同的元素。为内容使用正确的标签非常重要。使用正确的标签会对你的网站产生两大影响:它们在搜索引擎中的排名如何;以及它们对于依赖辅助技术(如屏幕阅读器)来使用互联网的用户的可访问性如何。

为内容使用正确的元素称为语义化 HTML (semantic HTML)。我们将在课程后面更深入地探讨这一点。

空元素

一些 HTML 元素没有结束标签。这些元素只有一个单独的标签,例如:<br><img>。它们被称为空元素 (void elements),因为它们内部是空的 (void of any content),里面没有任何内容。没有结束标签意味着它们不能像其他标签那样包裹内容。

你可能也会看到它们被称为自闭合标签 (self-closing tags)。但这只是指那些末尾带有正斜杠 / 的空元素,例如:<br /><img />。出于历史原因,你很可能会经常看到自闭合标签的使用。浏览器能够正常渲染它们,但最新版本的 HTML 规范不鼓励使用它们,并认为它们是无效的。

作业

  1. 观看 Kevin Powell 的视频 HTML & CSS for Absolute Beginners: What is HTML?

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它以查看相关材料,但请记住,并不要求你死记硬背或完全掌握这些知识。

附加资源

本节包含相关内容的实用链接。这些内容并非必需,仅供参考。

HTML 基础结构

引言

所有 HTML 文档都有一个相同的基本结构或称为“模板”(boilerplate),在开始做任何有用的事情之前,这个结构需要就位。在本课中,我们将探讨这个模板的不同部分,看看它们是如何组合在一起的。

课程概览

本节概述了你将在本课中学到的主题。

  • 如何编写 HTML 文档的基本模板。
  • 如何在浏览器中打开 HTML 文档。

创建 HTML 文件

为了演示 HTML 模板,我们首先需要一个 HTML 文件来操作。

在你的计算机上创建一个新文件夹,命名为 html-boilerplate。在该文件夹内创建一个新文件,命名为 index.html

你可能已经熟悉许多不同类型的文件,例如 doc、pdf 和图像文件。

为了让计算机知道我们要创建一个 HTML 文件,我们需要在文件名后附加 .html 扩展名,就像我们在创建 index.html 文件时做的那样。

值得注意的是,我们将 HTML 文件命名为 index。我们应该始终将包含网站主页的 HTML 文件命名为 index.html。这是因为当用户访问我们的网站时,Web 服务器默认会寻找一个 index.html 页面——没有这个文件会导致大问题。

DOCTYPE(文档类型声明)

每个 HTML 页面都以一个 DOCTYPE 声明开始。DOCTYPE 的目的是告诉浏览器它应该使用哪个版本的 HTML 来渲染文档。HTML 的最新版本是 HTML5,其 DOCTYPE<!DOCTYPE html>

旧版本 HTML 的 DOCTYPE 稍微复杂一些。例如,这是 HTML4 的 DOCTYPE 声明:0000

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

然而,我们可能永远都不想使用旧版本的 HTML,因此我们将始终使用 <!DOCTYPE html>

打开之前创建的 index.html 文件,在你的文本编辑器中,将 <!DOCTYPE html> 添加到第一行。

HTML 元素

声明了 DOCTYPE 之后,我们需要提供一个 <html> 元素。这就是所谓的文档的根元素(root element),意味着文档中的每个其他元素都将是它的后代。

当我们学习用 JavaScript 操作 HTML 时,这一点变得更加重要。现在,只需要知道每个 HTML 文档都应该包含 <html> 元素。

回到 index.html 文件,让我们添加 <html> 元素,输入其开始和结束标签,像这样:

<!DOCTYPE html>
<html lang="en">
</html>

注意到 lang 这个词了吗?它代表一个 HTML 属性(HTML attribute),与给定的 HTML 标签(即本例中的 <html>)相关联。这些属性提供关于 HTML 元素的附加信息。(更多关于 HTML 属性的内容将在下一课介绍。)

lang 属性是什么?
lang 指定该元素中文本内容的语言。此属性主要用于提高网页的可访问性(accessibility)。它允许辅助技术(例如屏幕阅读器)根据语言进行调整并调用正确的发音。

Head 元素

<head> 元素是我们放置关于我们网页的重要元信息(meta-information) 以及网页在浏览器中正确渲染所需内容的地方。在 <head> 内部,我们不应使用任何在网页上显示内容的元素。

回到我们的 index.html 文件,让我们添加一个 <head> 元素,里面包含一个 <meta> 元素和一个 title<head> 元素位于 <html> 元素内部,并且应始终是 <html> 开始标签下的第一个元素:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>我的第一个网页</title>
  </head>
</html>

Meta 元素

我们应该始终在 <head> 元素中包含带有网页字符集编码的 <meta> 标签:<meta charset="UTF-8">

设置编码非常重要,因为它确保网页能在浏览器中正确显示特殊符号和不同语言的字符。

Title 元素

我们应该始终包含在 HTML 文档头部的另一个元素是 <title> 元素:

<title>我的第一个网页</title>

<title> 元素用于给网页一个人类可读的标题,它会显示在我们网页的浏览器标签页上。例如,如果你查看浏览器当前标签页的名称,它会显示“HTML Boilerplate | The Odin Project”;这就是当前 .html 文件的 <title>

如果我们不包含 <title> 元素,网页的标题将默认为其文件名。在我们的例子中,那将是 index.html,这对用户来说意义不大;如果用户打开了许多浏览器标签页,这将使找到我们的网页变得非常困难。

还有许多其他元素可以放在 HTML 文档的头部(head)。然而,目前只需要了解我们在这里介绍的两个元素。我们将在课程的其余部分介绍更多放在头部的元素。

Body 元素

完成 HTML 模板所需的最后一个元素是 <body> 元素。这是所有将显示给用户的内容所在之处——文本、图像、列表、链接等等。

为了完成模板,在 index.html 文件中添加一个 <body> 元素。<body> 元素也位于 <html> 元素内部,并且始终在 <head> 元素下方,像这样:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>我的第一个网页</title>
  </head>

  <body>
  </body>
</html>

在浏览器中查看 HTML 文件

此时 index.html 文件中的 HTML 模板已经完成,但你如何在浏览器中查看它呢?有几种不同的选择:

  1. 使用 Google Chrome:为了避免因不同浏览器之间的差异而调整课程说明,在本课程的剩余部分,我们将使用 Google Chrome 作为我们的主要浏览器。所有对浏览器的引用都将特指 Google Chrome。我们强烈建议你在今后的所有测试中都使用 Google Chrome。
  2. 拖放:你可以将 HTML 文件从文本编辑器拖放到浏览器地址栏。
  3. 双击文件:你可以在文件系统中找到 HTML 文件,然后双击它。这将在你系统默认的浏览器中打开该文件。
  4. 使用终端打开
    • Ubuntu:导航到包含文件的目录,然后输入 google-chrome index.html
    • macOS:导航到包含文件的目录,然后输入 open ./index.html
    • WSL:导航到包含文件的目录,然后输入 explorer.exe index.html。注意,这将在你系统默认的浏览器中打开文件。

使用上述方法之一,打开我们一直在处理的 index.html 文件。你会注意到屏幕是空白的。这是因为我们的 body 中没有任何要显示的内容。

回到 index.html 文件,让我们在 body 中添加一个标题(稍后会详细介绍),然后保存文件:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>我的第一个网页</title>
  </head>

  <body>
    <h1>Hello World!</h1>
  </body>
</html>

现在,如果你在浏览器中刷新页面,你应该会看到更改生效,标题 “Hello World!” 将被显示出来。

VSCode 快捷方式

VSCode 有一个内置的快捷方式,你可以用它一次性生成所有模板。请注意,此快捷方式仅在编辑具有 .html 扩展名的文件或已选择 HTML 语言的文本文件时才有效。要触发快捷方式,请删除 index.html 文件中的所有内容,然后在第一行输入 !。这将弹出几个选项。按 Enter 键选择第一个选项,瞧,所有模板代码都应该为你自动填充好了。

你可能会注意到,此快捷方式生成的模板包含一行我们尚未提及的内容:

html

<meta name="viewport" content="width=device-width, initial-scale=1.0">

这是我们讨论响应式设计(responsive design)(一个涉及不同屏幕尺寸的高级主题,我们将在课程后面详细讲解)之前不需要了解的内容。现在,你可以保留该行不变。

不过,最好还是知道如何自己编写模板,以防你发现自己使用的是像记事本(但愿不会)这样没有此快捷方式的文本编辑器。在最初的几个 HTML 项目中,尽量不要使用快捷方式,这样你可以为编写模板代码建立一些肌肉记忆。

作业

  1. 观看并跟随 Kevin Powell 的优秀视频 Building Your First Web Page
  2. 删除 index.html 文件的内容,尝试仅凭记忆再次写出所有模板代码,以建立肌肉记忆。如果前几次卡住了,不必担心,可以查看课程内容。坚持下去,直到你能凭记忆做几次。
  3. 将你的模板代码通过 W3 HTML 验证器运行。验证器确保你的标记是正确的,并且是一个极好的学习工具,因为它们会反馈你可能经常犯但没意识到的语法错误,例如缺少结束标签和 HTML 中的多余空格。

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它以查看相关材料,但请记住,并不要求你死记硬背或完全掌握这些知识。

  • DOCTYPE 声明的目的是什么?
  • HTML 元素是什么?
  • head 元素的目的是什么?
  • body 元素的目的是什么?

附加资源

本节包含相关内容的实用链接。这些内容并非必需,仅供参考。

  • 在浏览器中打开 HTML 页面的另一个选项是使用 VSCode 的 Live Preview 扩展。这将打开你的 HTML 文档,并在每次保存文档时自动刷新它。然而,我们建议最初避免使用此扩展。相反,在浏览器中手动打开并刷新你的 HTML 页面。这有助于你在依赖工具和扩展之前,对基本的开发工作流程建立扎实的理解。
  • 如果你愿意,可以在整个网页的各个元素中添加 lang 属性。
  • 如果你对字符编码感到好奇,W3C 关于字符编码的文章做了很好的解释。

处理文本

简介

网络上的大部分内容都是基于文本的,因此你会发现经常需要处理 HTML 文本元素。

在本课中,我们将学习你最可能经常使用的基于文本的元素。

课程概述

本节包含了你将在本课中学习主题的总体概述。

  • 如何创建段落。
  • 如何创建标题。
  • 如何创建粗体文本。
  • 如何创建斜体文本。
  • 嵌套元素之间的关系。
  • 如何创建 HTML 注释。

段落

你期望以下文本在 HTML 页面上输出什么?

<body>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  incididunt ut labore et dolore magna aliqua.

  Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
  nisi ut aliquip ex ea commodo consequat.
</body>

它看起来像是两段文本,所以你可能会期望它那样显示。然而,事实并非如此,正如你在下面的输出中看到的那样:

当浏览器在 HTML 中遇到这样的新行时,它会将它们压缩成一个单独的空格。这种压缩的结果是所有文本都被挤在一起,变成了一长行。

如果我们想在 HTML 中创建段落,我们需要使用段落元素(paragraph element),它会在每个段落后面添加一个新行。段落元素是通过用 <p> 标签包裹文本内容来定义的。

将我们之前的例子改为使用段落元素可以解决这个问题:

标题

标题与其他 HTML 文本元素不同:它们显示得比其他文本更大、更粗,以表明它们是标题。

标题有 6 个不同的级别,从 <h1><h6>。标题标签中的数字代表该标题的级别。<h1> 是最大且最重要的标题,而 <h6> 是级别最低的最小标题。

标题的定义方式与段落非常相似。例如,要创建一个 <h1> 标题,我们将标题文本包裹在 <h1> 标签中。

使用正确的标题级别很重要,因为级别为内容提供了层次结构。<h1> 标题应始终用作整个页面的标题,而较低级别的标题应作为页面较小部分内容的标题。

Strong 元素

<strong> 元素使文本变为粗体。它在语义上也标记文本为重要;这会影响工具,比如屏幕阅读器,视力障碍用户将依赖这些工具来使用你的网站。某些屏幕阅读器的语调会发生变化,以传达 <strong> 元素内文本的重要性。要定义一个 <strong> 元素,我们将文本内容包裹在 <strong> 标签中。

你可以单独使用 <strong>

但你可能会发现自己在与其他文本元素结合时更多地使用 <strong> 元素,像这样:

有时你会想让文本变粗而不赋予它重要的含义。你将在课程后面关于 CSS 的课程中学习如何做到这一点。

Em 元素

<em> 元素使文本变为斜体。它在语义上也会强调文本,这同样可能影响屏幕阅读器等工具。要定义一个强调元素,请将文本内容包裹在 <em> 标签中。

单独使用 <em>

同样,像 <strong> 元素一样,你会发现自己主要是在与其他文本元素一起使用 <em> 元素:

嵌套和缩进

你可能已经注意到,在本课的所有示例中,我们对位于其他元素内的任何元素都进行了缩进。这被称为嵌套元素(nesting elements)。

当我们将元素嵌套在其他元素内时,我们在它们之间创建了父子关系。被嵌套的元素是子元素(children),它们被嵌套在其中的元素是父元素(parent)。

在以下示例中,<body> 元素是父元素,<p> 元素是子元素:

就像人类关系一样,HTML 父元素可以有许多子元素。处于相同嵌套级别的元素被认为是兄弟元素(siblings)。

例如,以下代码中的两个 <p> 元素是兄弟元素,因为它们都是 <body> 标签的子元素,并且彼此处于相同的嵌套级别:

我们使用缩进来使嵌套级别清晰易读,方便我们自己以及将来使用我们 HTML 的其他开发人员。在我们的示例中,我们将任何子元素按每个嵌套级别缩进两个空格。

元素之间的父、子和兄弟关系在稍后我们开始使用 CSS 设置 HTML 样式以及使用 JavaScript 添加行为时将变得更加重要。不过,就目前而言,重要的是了解元素之间如何关联以及用于描述它们关系的术语之间的区别。

HTML 注释

HTML 注释在浏览器中不可见;它们允许我们为代码添加注释,以便其他开发人员或未来的自己可以阅读它们,并获取代码中可能不明确的内容的上下文信息。

为了编写 HTML 注释,我们只需用 <!----> 标签将注释括起来。例如:

VS Code 键盘快捷键

如果你觉得输入注释语法很麻烦,以下快捷键将帮助你快速创建新注释、将任何行转换为注释或取消注释任何行:

  • Mac 用户:Cmd + /
  • Windows 和 Linux 用户:Ctrl + /

作业

  1. 观看 Kevin Powell 的 HTML 段落和标题视频
  2. 观看 Kevin Powell 的 HTML 粗体和斜体文本视频
  3. 为了练习在 HTML 中使用文本,创建一个纯文本博客文章页面,使用不同的标题、段落,并使段落中的一些文本加粗和斜体。你可以使用 Lorem Ipsum (乱数假文) 来生成虚拟文本,在构建网站时用它代替真实文本。VS Code 包含一个生成乱数假文的快捷方式。要触发快捷方式,在你想要虚拟文本的行上输入 lorem,然后按 Enter 键,看吧,你就毫不费力地生成了虚拟文本。

知识检查

以下问题提供了反思本课关键主题的机会。如果无法回答某个问题,可点击链接复习相关内容(请注意无需死记硬背这些知识):

附加资源

本节包含相关内容的实用链接(非必读,可作为补充材料):

列表

简介

无论是 IMDB 的 Top 250 电影榜单还是 FBI 通缉要犯名单,列表在网络上无处不在。在构建网页时,你终将需要用到它们。

幸运的是,HTML 提供了几种不同类型的列表供你使用。

课程概述

本节包含了你将在本课中学习主题的总体概述:

  • 如何创建无序列表
  • 如何创建有序列表

无序列表

如果你想创建顺序无关紧要的项目列表(例如可随意购买商品的购物清单),可以使用无序列表。

无序列表使用 <ul> 元素创建,列表中的每个项目使用列表项元素 <li> 创建。

无序列表中的每个列表项以项目符号开头:

有序列表

如果你需要创建顺序至关重要的项目列表(例如食谱的分步说明或个人十大最爱剧集),则可以使用有序列表。

有序列表使用 <ol> 元素创建。其中的每个项目同样使用列表项元素 <li> 创建。但有序列表中的每个项目以数字开头:

作业

为练习使用列表,请创建新的 HTML 文档并构建以下列表:

  1. 你最爱食物的无序购物清单
  2. 今天待办事项的有序清单
  3. 你希望有朝一日游览的无序地点列表
  4. 你心目中 Top 5 电影/游戏的有序榜单

知识检查

以下问题提供了反思本课关键主题的机会。如果无法回答某个问题,可点击链接复习相关内容(请注意无需死记硬背这些知识):

附加资源

本节包含相关内容的实用链接(非必读,可作为补充材料):

链接与图像

简介

链接是 HTML 的核心功能之一,它们使我们能够在网页间相互连接——这正是"万维网"名称的由来。

本课程将学习如何创建链接,并通过嵌入图像为网站增添视觉吸引力。

课程概述

本节包含你将学习的关键主题概览:

  • 如何创建指向其他网站的链接
  • 如何创建指向自身网站页面的链接
  • 绝对链接与相对链接的区别
  • 使用 HTML 在网页上显示图像

准备工作

请按以下步骤创建练习项目:

  1. 新建名为 odin-links-and-images 的目录
  2. 在该目录中创建 index.html 文件
  3. 在 VS Code 中打开文件并添加标准 HTML 模板
  4. 在 body 中添加标题:
<h1>Homepage</h1>

锚元素

创建链接需使用锚元素(<a> 标签)。在 index.html 中添加:

<a>About The Odin Project</a>

此时点击无反应,因为缺少目标地址。需添加 href 属性指定链接目标:

<a href="https://www.theodinproject.com/about">About The Odin Project</a>

默认情况下,带 href 的链接显示为蓝色下划线文本。

新标签页打开链接

添加 target="_blank" 在新标签页打开链接,同时强烈建议添加安全属性:

<a href="https://www.theodinproject.com/about" target="_blank" rel="noopener noreferrer">
  About The Odin Project
</a>

安全说明:

  • rel="noopener":防止新页面操控原页面(防范标签劫持
  • rel="noreferrer":隐藏来源信息(隐私保护)
  • 现代浏览器虽提供基础防护,但遵循最佳实践应始终同时添加这两个属性

绝对链接与相对链接

绝对链接

包含完整协议和域名,指向外部网站:

<!-- 示例:-->
<a href="https://www.theodinproject.com/about">外部链接</a>

相对链接

指向站内资源,只需文件相对路径:

  1. 在项目目录创建 about.html
  2. 添加基础链接:
<a href="about.html">关于</a>
  1. 创建子目录优化结构:
    • 新建 pages 目录
    • 移动 about.htmlpages
  2. 更新相对路径:
<!-- 推荐添加 ./ 前缀 -->
<a href="./pages/about.html">关于</a>

概念隐喻

将网站结构比作城镇:

  • 域名 town.com = 城镇
  • 目录 /museum = 博物馆
  • 页面 /museum/movie_room.html = 展厅
  • 相对链接 ./shops/coffee_shop.html = "从当前展厅到咖啡馆的指示"
  • 绝对链接 https://town.com/museum/shops/coffee_shop.html = "包含城镇名的完整地址"

图像

使用 <img> 空元素嵌入图像,通过 src 指定路径:

<!-- 绝对路径示例 -->
<img src="https://www.theodinproject.com/mstile-310x310.png" alt="Odin项目标志">

相对路径使用方式相同:

<!-- 相对路径示例 -->
<img src="./images/photo.jpg" alt="描述文本">

关键注意事项:

  • 空元素无需闭合标签
  • 必须包含 alt 属性提供替代文本
  • src 路径规则与链接一致

通过掌握链接和图像的应用,您已获得构建基础网站的核心能力。下一步可探索图像格式优化、响应式图片等进阶技巧。

Linux, macOS, ChromeOS
  1. odin-links-and-images 项目目录中,创建一个名为 images 的新目录。
  2. 接下来,下载我们的练习图片,并将其移动至我们刚刚创建的 images 目录中。
  3. 将该图片重命名为 dog.jpg
WSL2

当您从互联网下载文件时,Windows 有一项安全功能会创建一个隐藏的 Zone.Identifier 文件,其文件名与您下载的文件相同,格式如 mypicture.jpg:Zone.Identifier。此文件无害,但我们希望避免复制它以免弄乱目录。

  1. odin-links-and-images 项目内创建一个名为 images 的新目录。

  2. 接下来,下载示例小狗图片

  3. 在 Chrome 窗口底部右键点击新下载项,选择“在文件夹中显示”。

    1. 或者,如果 Chrome 窗口底部未显示任何内容,请打开“自定义及控制 Google Chrome”三点菜单并选择“下载”项。这将显示您的所有下载,每个下载都有其“在文件夹中显示”按钮。
  4. 将图片重命名为 dog.jpg

  5. 将文件从您的下载文件夹拖拽到 VSCode 文件浏览器中新建的 images 目录。

    1. 或者,使用 Ubuntu 终端,导航到要复制图片的目标文件夹(例如 cd ~/odin-links-and-images

    2. 输入 cp <空格>

    3. 从 Windows 资源管理器窗口拖拽 dog.jpg 图片并放置到终端窗口,它将显示为 "/mnt/c/users/username/Downloads/dog.jpg"

    4. 输入 <空格> . 表示要将文件复制到当前工作目录。完整命令类似如下:

      cp "/mnt/c/users/username/Downloads/dog.jpg" .  
    1. Enter 键执行命令,并使用 ls 确认文件已存在。

从 Windows 拖拽文件到 VSCode 文件浏览器可防止复制 Zone.Identifier 文件。今后任何需要将图片或其他下载文件复制到 WSL2 时,均可采用此方法。如果不小心将这些 Zone.Identifier 文件复制到 WSL2 中,您可以安全地删除它们而不会产生任何问题。

最后将图像添加到 index.html 文件

<body>
  <h1>主页</h1>
  <a href="https://www.theodinproject.com/about">关于 The Odin Project</a>
  
  <a href="./pages/about.html">关于我们</a>
  
  <img src="./images/dog.jpg">
</body>

保存 index.html 文件并在浏览器中打开,欣赏 Charles(小狗)的英姿。

使用的图像应符合您的使用目的

网上有许多免费图像资源,但请确保在项目中注明图像创作者的来源。

提供来源的简单方法是在仓库的 README 文件中包含创作者姓名和联系信息,或提供署名

查找方法:在 Google 图片搜索 -> 工具 -> 使用权限 -> "知识共享"。选择图片后点击"许可详情"。

我们建议您始终审查计划使用的任何图像的许可要求。

父目录路径

如果要在 about 页面使用小狗图像怎么办?我们需要先向上返回一级(从 pages 目录到其父目录),才能访问 images 目录。

要访问父目录,需要在相对路径中使用两个点:../。在 about.html 文件主体中,在之前添加的标题下方添加以下图像:

<img src="../images/dog.jpg">

路径解析:

  1. 首先从 pages 目录返回其父目录 odin-links-and-images
  2. 然后从父目录进入 images 目录
  3. 最后访问 dog.jpg 文件

使用之前的比喻:在文件路径中使用 ../ 就像从当前房间走到主走廊,以便进入另一个房间。

Alt 属性

除了 src 属性,每个图像元素都应具有 alt(替代文本)属性。

alt 属性用于描述图像:

  • 当图像无法加载时会显示替代文本
  • 供屏幕阅读器向视障用户描述图像内容

以下是我们之前使用的 The Odin Project 徽标示例(包含 alt 属性):

作为练习,请为 odin-links-and-images 项目中的小狗图像添加 alt 属性。

图像尺寸属性

虽然不是强制要求,但在图像标签中指定高度(height)和宽度(width)属性有助于浏览器布局页面,避免页面跳动和闪烁。

建议养成习惯始终为每个图像指定这些属性,即使图像尺寸正确或您使用 CSS 调整尺寸。

以下是包含高度和宽度属性的 The Odin Project 徽标示例:

请更新 odin-links-and-images 项目,为小狗图像添加宽度和高度属性。

作业任务

  1. 观看 Kevin Powell 的 HTML 链接视频
  2. 观看 Kevin Powell 的 HTML 图像视频
  3. 观看 Kevin Powell 的 文件结构视频
  4. 阅读并实践 Interneting is Hard 的 链接与图像 文章,特别注意图像格式部分

知识检查

以下问题可帮助您回顾本课关键概念。如无法回答,请点击问题查看相关内容(无需死记硬背):

补充资源

此部分包含相关参考内容(可选):

提交消息规范

引言

本课程将解释优秀提交消息的重要性、如何编写提交消息、何时提交,以及为何良好的提交历史记录至关重要!

课程概览

本节概述您将在本课程中学到的主题:

  • 如何编写有意义的提交消息
  • 为何有意义的提交消息如此重要
  • 何时进行提交

提交消息值得单独开设课程吗?

是的!以下是几个关键原因:

  • 求职优势:雇主会查看您在 GitHub 上的项目提交历史,良好的提交记录能让初级开发者脱颖而出
  • 高效协作:清晰的提交历史便于您或团队成员快速理解代码变更原因,这对修复 Bug 尤为重要
  • 项目回溯:当您时隔数月重返项目时,提交消息能帮您回忆当时的编码思路

糟糕 vs 优秀的提交对比

糟糕的提交示例

修复了一个 Bug

这种描述过于模糊,团队成员无法理解具体内容。

优秀的提交应包含两个部分

  1. 主题行(Subject):简要概括代码变更(推荐 ≤72 字符)

    我对代码库进行了以下修改
    
  2. 正文(Body):清晰描述变更原因和方式

    说明提交解决的问题及解决方案
    

优化后的提交示例

为公司徽标添加缺失链接和替代文本

屏幕阅读器需要此信息才能向视障用户描述图像内容

优势解析:

  • 主题行明确操作内容
  • 正文解释变更必要性
  • 主题与正文用空行分隔(提升可读性)

如何编写多行提交消息

  1. 使用命令:git commit不要加 -m 参数)
  2. 系统会自动在 VSCode 中打开编辑器(需提前设置 VSCode 为 Git 编辑器
  3. 删除注释行后编写:
    • 首行:主题行
    • 空一行
    • 后续行:正文
  4. 保存关闭后自动完成提交

何时提交?

将提交视为代码的"快照",最佳实践包括:

  • 实现关键功能时 🎯
  • 修复重要 Bug 时 🐞
  • 纠正拼写错误时 ✏️
  • 完成逻辑模块时 ✅

实际价值:当代码意外出错时,您可以通过提交历史:

  1. 回退到正常版本 ⏪
  2. 对比历史版本差异 🔍

作业任务

  1. 精读 如何编写 Git 提交消息,重点掌握"优秀提交的七项原则"

核心技巧

  • 活用 VSCode:多行编辑 + 拼写检查扩展
  • 主动语态:使用"修复卡片生成器"而非"卡片生成器被修复"
  • 避免模糊词:禁用"已保存"/"已更新"等空洞描述
  • 高频提交:小步快跑,定期存档进度

知识检查

(点击问题可回顾相关内容,无需死记硬背)

扩展资源

项目:食谱网站

引言

现在是将所学 HTML 知识付诸实践的时刻!在本项目中,您将构建一个基础的食谱网站。网站将包含一个主页(index page),提供多个食谱的链接。完成时网站可能不够精美,但请牢记本项目的核心目标是夯实 HTML 基础——后续我们将用 CSS 为其添加样式。

GitHub 仓库设置

Git 入门课程所述,请将所有项目整理成作品集并关联至 GitHub。

GitHub 文件创建注意事项

在 GitHub 网站直接创建文件后,若本地已克隆仓库,会导致本地版本落后于远程版本。现阶段请严格遵循操作指南:

  1. 在 GitHub.com 创建名为 odin-recipes公开仓库(非默认的私有仓库)
  2. 在本地 repos 目录(Git 基础课程中创建)执行克隆命令:
    git clone git@github.com:用户名/odin-recipes.git(使用 SSH)
  3. 进入本地项目目录:cd odin-recipes
  4. 创建 README.md 文件,简要说明项目内容及完成后将展示的技能(也可在项目结束时补充)

常见问题排查:

提交时机建议

遵循提交消息规范课程的最佳实践:

  • 采用多次 git add + git commit 循环
  • 高频提交:代码产生有意义变更时立即提交
  • 最终使用 git push origin main 推送至 GitHub
  • 在浏览器中查看仓库确认文件

现在开始正式构建项目!

重要警告:勿提前查看他人代码

项目底部设有"学员解决方案"区供展示成品代码。但请注意:

  • 这些方案来自同样处于学习阶段的开发者,未必代表最佳实践
  • 提前查看会剥夺您培养问题解决能力的机会
  • 编码方案具有多样性,没有唯一标准答案
  • 类比:"通过成品披萨学习做面团"无法真正掌握制作过程

遇到困难时:

  1. Discord 服务器寻求帮助(需先加入服务器
  2. 切勿偷看解决方案!

项目任务

迭代 1:基础结构

  1. odin-recipes 目录创建 index.html
  2. 添加标准 HTML 模板,在 body 内创建标题:<h1>Odin 食谱</h1>

迭代 2:食谱页面

  1. 在项目目录新建 recipes 子目录

  2. 在子目录创建食谱 HTML 文件(如 lasagna.html),包含:

    • 标准 HTML 模板
    • 标题:<h1>食谱名称</h1>
  3. 在主页添加食谱链接:
    <a href="recipes/recipename.html">食谱名称</a>

  4. 在食谱页面添加返回主页的链接(顶部/底部均可):

    html

    <a href="../index.html">返回主页</a>
    

迭代 3:食谱内容

食谱页面需包含:

  1. 成品图(位于标题下方)
  2. "描述"标题 + 介绍段落
  3. "食材"标题 + 无序列表(食材清单)
  4. "步骤"标题 + 有序列表(制作步骤)

迭代 4:扩展食谱

  1. 新增两个相同结构的食谱页面
  2. 在主页以无序列表整合所有链接:

html

<ul>
  <li><a href="recipes/食谱1.html">食谱1名称</a></li>
  <li><a href="recipes/食谱2.html">食谱2名称</a></li>
  <li><a href="recipes/食谱3.html">食谱3.html</a></li>
</ul>

在线部署项目

通过 GitHub Pages 发布网站(免费账户需使用公开仓库):

  1. 确保主文件名为 index.html
  2. 在 GitHub 仓库点击 SettingsPages
  3. Branchnone 改为 main 并保存
  4. 等待部署完成(最长约 1 小时),访问地址:
    用户名.github.io/仓库名
  5. 若超时未部署:
    • 检查根目录是否有 index.html
    • Settings/Pages 中切换 Branch:mainnonemain

关于作品提交的合理预期

看到"学员解决方案"区的精美作品时,请注意:

  1. 这些多出自有经验的开发者或返场学员之手
  2. 作为初学者,您的重点应是完成基础要求
  3. 深入阅读:成为 TOP 成功案例之"学习编码"

保持专注,享受构建过程!您的进步比完美呈现更重要。

CSS 基础

CSS 入门

简介

在之前的课程中,您学习了如何编写决定网页结构的 HTML。下一步是通过 样式 让这个结构看起来更美观,这正是 CSS 的作用。在接下来的几节课中,我们将重点讲解一些基础的 CSS 概念——无论您是初学者还是需要复习,这些都是每个人从一开始就应该掌握的知识。

课程概述

本节概述了您将在本课程中学到的主题:

  • 使用 CSS 为 HTML 添加样式
  • 理解如何使用 class 和 ID 属性
  • 使用正确的选择器为特定元素添加样式

基本语法

在最基础的层面上,CSS 由各种规则组成。每条规则由一个选择器(稍后详述)和一个分号分隔的声明列表组成,每个声明由一个属性-值对构成。

Basic CSS syntax

注意

<div> 是基本的 HTML 元素之一。它是一个空容器。在项目中最好使用其他标签(如 <h1><p>)来放置内容,但随着您深入学习 CSS,您会发现很多情况下只需要一个容器来包裹其他元素。为简化起见,我们的许多练习使用普通的 <div>。后续课程将更详细地介绍何时适合使用各种 HTML 元素。

选择器

选择器指的是 CSS 规则所应用的 HTML 元素;它们实际上是被"选择"的对象。以下小节并未涵盖所有可用的选择器,但它们是最常见且您应该首先熟悉的选择器。

通用选择器

通用选择器会选择所有类型的元素(整个文档),语法是一个简单的星号。在下面的示例中,每个元素都会应用 color: purple; 样式:

* {
  color: purple;
}

类型选择器

类型选择器(或元素选择器)会选择给定类型的所有元素,语法就是元素的名称:

<!-- index.html -->

<div>Hello, World!</div>
<div>Hello again!</div>
<p>Hi...</p>
<div>Okay, bye.</div>
/* styles.css */

div {
  color: white;
}

这里,所有三个 <div> 元素都会被选中,而 <p> 元素则不会。

类选择器

类选择器会选择所有具有给定类的元素,类就是您放置在 HTML 元素上的属性。以下是如何在 HTML 标签中添加类并在 CSS 中选择它:

<!-- index.html -->

<div class="alert-text">请同意我们的服务条款。</div>
/* styles.css */

.alert-text {
  color: red;
}

注意类选择器的语法:一个点号后紧跟类属性值(区分大小写)。类不需要特定于某个元素,因此您可以在任意多个元素上使用相同的类。

如果类名以数字开头,类选择器将不起作用。例如,如果您给元素一个类名 .4lert-text,使用 .4lert-text 作为选择器将无法匹配它。

类属性的另一个功能是向单个元素添加多个类,作为空格分隔的列表,例如 class="alert-text severe-alert"。由于空格用于分隔类名,因此切勿在多个单词的名称中使用空格,而应使用连字符。

ID 选择器

ID 选择器类似于类选择器。它们选择具有给定 ID 的元素,ID 是您放置在 HTML 元素上的另一个属性。类和 ID 之间的主要区别在于一个元素只能有一个 ID。它在单个页面上不能重复,且不应包含任何空格:

<!-- index.html -->

<div id="title">我的超赞 90 年代网页</div>
/* styles.css */

#title {
  background-color: red;
}

对于 ID,我们使用井号后紧跟 ID 属性值(区分大小写)。一个常见的误区是在不需要时过度使用 ID 属性,而类已足够。虽然有使用 ID 有意义或必要的情况(例如利用特异性或让链接重定向到当前页面的某个部分),但您应该尽量少用 ID(如果可能的话)。

与类选择器一样,ID 选择器也不能以数字开头。例如,如果您给元素一个 ID 7itle,选择器 #7itle 将不起作用——它不是有效的 CSS 选择器。

分组选择器

如果我们有两组元素共享某些样式声明怎么办?

.read {
  color: white;
  background-color: black;
  /* 几个独特的声明 */
}

.unread {
  color: white;
  background-color: black;
  /* 几个独特的声明 */
}

我们的 .read.unread 选择器都共享 color: white;background-color: black; 声明,但各自还有其他独特的声明。为了减少重复,我们可以将这两个选择器分组为逗号分隔的列表:

.read,
.unread {
  color: white;
  background-color: black;
}

.read {
  /* 几个独特的声明 */
}

.unread {
  /* 几个独特的声明 */
}

上面的两个示例(带分组和不带分组)将产生相同的结果,但第二个示例减少了声明的重复,并使得同时为两个类编辑 colorbackground-color 更加容易。

链式选择器

使用选择器的另一种方式是将它们作为列表链接而不加任何分隔。假设我们有以下的 HTML:

<div>
  <div class="subsection header">最新帖子</div>
  <p class="subsection preview">这里是帖子预览的位置。</p>
</div>

我们有两个带有 subsection 类的元素,它们有某种独特的样式,但如果我们只想将单独的规则应用于同时具有 header 作为第二个类的元素怎么办?我们可以在 CSS 中将两个类选择器链接在一起,像这样:

.subsection.header {
  color: red;
}

.subsection.header 的作用是选择同时具有 subsectionheader 类的任何元素。注意 .subsection.header 类选择器之间没有任何空格。此语法基本上适用于链接任何选择器组合,除了链接多个类型选择器。

这也可以用于链接类和 ID,如下所示:

<div>
  <div class="subsection header">最新帖子</div>
  <p class="subsection" id="preview">
    这里是帖子预览的位置。
  </p>
</div>

您可以使用以下方式组合上面的两个元素:

.subsection.header {
  color: red;
}

.subsection#preview {
  color: blue;
}

通常,您不能链接多个类型选择器,因为一个元素不能同时是两种不同类型。例如,链接两个类型选择器如 divp 将得到选择器 divp,这将不起作用,因为选择器会尝试查找字面上的 <divp> 元素,而该元素并不存在。

后代组合器

组合器允许我们以不同于分组或链接的方式组合多个选择器,因为它们显示了选择器之间的关系。总共有四种类型的组合器,但现在我们只介绍后代组合器,它在 CSS 中由选择器之间的单个空格表示。后代组合器仅当元素匹配最后一个选择器且其祖先(父级、祖父级等)匹配前一个选择器时才会被选择。

因此,像 .ancestor .child 这样的选择器会选择具有类 child 的元素,前提是它有一个具有类 ancestor 的祖先。另一种理解方式是,仅当 child 嵌套在 ancestor 内部时才会被选择,无论嵌套有多深。快速查看下面的示例,看看您是否能根据提供的 CSS 规则判断哪些元素会被选择:

<!-- index.html -->

<div class="ancestor">
  <!-- A -->
  <div class="contents">
    <!-- B -->
    <div class="contents"><!-- C --></div>
  </div>
</div>

<div class="contents"><!-- D --></div>
/* styles.css */

.ancestor .contents {
  /* 一些声明 */
}

在上面的示例中,前两个带有 contents 类的元素(B 和 C)会被选择,但最后一个元素(D)不会。您猜对了吗?

规则中添加的组合器数量没有限制,因此 .one .two .three .four 是完全有效的。这将选择具有类 four 的元素,前提是它有一个具有类 three 的祖先,并且该祖先有自己的具有类 two 的祖先,依此类推。不过,您通常应避免尝试选择需要这种嵌套级别的元素,因为它可能会变得相当混乱和冗长,并且在特异性方面可能引起问题。

入门属性

有些 CSS 属性您会一直使用,或者至少经常使用。我们将向您介绍其中几个属性,尽管这绝不是一个完整的列表。学习以下属性将足以帮助您入门。

color 和 background-color

color 属性设置元素的文本颜色,而 background-color 设置元素的背景颜色。我想我们就到这里了?

差不多。这两个属性都可以接受几种值。常见的一种是关键字,例如像 red 这样的实际颜色名称或 transparent 关键字。它们还接受 HEX、RGB 和 HSL 值,如果您曾经使用过 Photoshop 程序或可以自定义个人资料颜色的网站,您可能对这些值很熟悉。

p {
  /* hex 示例: */
  color: #1100ff;
}

p {
  /* rgb 示例: */
  color: rgb(100, 0, 127);
}

p {
  /* hsl 示例: */
  color: hsl(15, 82%, 56%);
}

快速查看 CSS 合法颜色值,了解如何通过添加 alpha 值来调整这些颜色的不透明度。

排版基础和 text-align

font-family 可以是单个值或以逗号分隔的值列表,用于确定元素使用的字体。每种字体都属于以下两类之一:"字体家族名称"如 "Times New Roman"(由于单词之间有空格,我们使用引号)或"通用家族名称"如 serif(通用家族名称从不使用引号)。

如果浏览器无法找到或不支持列表中的第一种字体,它将使用下一种,依此类推,直到找到支持且有效的字体。这就是为什么最佳实践是为此属性包含一个值列表,以最希望使用的字体开头,以通用字体家族作为后备结尾,例如 font-family: "Times New Roman", serif;

font-size 将设置字体的大小。为此属性提供值时,值不应包含任何空格,例如 font-size: 22px 在 "22" 和 "px" 之间没有空格。

font-weight 影响文本的粗细(假设字体支持指定的粗细)。此值可以是关键字,例如 font-weight: bold,也可以是 1 到 1000 之间的数字,例如 font-weight: 700(相当于 bold)。通常,数值将以 100 为增量直至 900,但这取决于字体。

text-align 将在元素内水平对齐文本,您可以使用在文字处理程序中常见的关键字作为此属性的值,例如 text-align: center

图像高度和宽度

图像并非我们唯一可以调整高度和宽度的元素,但在这种情况下我们想特别关注它们。

默认情况下,<img> 元素的 heightwidth 值与实际图像文件的尺寸相同。如果您想调整图像大小而不导致其失去比例,可以为 height 属性使用 "auto" 值并调整 width 值:

img {
  height: auto;
  width: 500px;
}

例如,如果图像的原始尺寸为高度 500px 和宽度 1000px,使用上面的 CSS 将导致高度为 250px。

这些属性与 HTML 中的高度和宽度属性协同工作。即使您不打算从图像文件的原始值调整这些值,最好也为图像元素同时包含这些属性和 HTML 属性。当这些值未包含时,如果图像加载时间比页面其他内容长,它最初不会占用页面上的任何空间,但一旦加载完成,会突然导致其他页面内容发生剧烈位移。显式声明 heightwidth 可以防止这种情况发生,因为空间将被"保留"在页面上,在图像加载前显示为空白。

将 CSS 添加到 HTML

现在我们已经学习了一些基本语法,您可能想知道如何将所有 CSS 添加到我们的 HTML 中。有三种方法可以实现。

外部 CSS

外部 CSS 是您将遇到的最常见方法,它涉及为 CSS 创建一个单独的文件,并使用 void <link> 元素将其链接到 HTML 的 <head> 标签内:

<!-- index.html -->

<head>
  <link rel="stylesheet" href="styles.css">
</head>
/* styles.css */

div {
  color: white;
  background-color: black;
}

p {
  color: red;
}

首先,我们在 HTML 文件的 <head> 标签内添加一个 void <link> 元素。href 属性是 CSS 文件的位置,可以是绝对 URL,或者您将使用的相对于 HTML 文件位置的 URL。在上面的示例中,我们假设两个文件位于同一目录中。rel 属性是必需的,它指定了 HTML 文件和链接文件之间的关系。

然后在新创建的 styles.css 文件中,我们有选择器(divp),后跟一对花括号,它们创建了一个"声明块"。最后,我们将任何声明放在声明块内。color: white; 是一个声明,color 是属性,white 是值,background-color: black; 是另一个声明。

关于文件名的说明:styles.css 只是我们在此使用的文件名。只要文件类型是 .css,您就可以随意命名文件,尽管最常用的是 "style" 或 "styles"。

此方法的几个优点是:

  1. 它使我们的 HTML 和 CSS 保持分离,从而使 HTML 文件更小且看起来更整洁。
  2. 我们只需要在一个地方编辑 CSS,这对于具有许多共享相似样式的页面的网站特别方便。

内部 CSS

内部 CSS(或嵌入式 CSS)涉及将 CSS 直接添加到 HTML 文件本身,而不是创建完全独立的文件。使用内部方法,您将所有规则放在一对 <style> 标签内,然后将其放在 HTML 文件的 <head> 标签内。由于样式直接放在 <head> 标签内,我们不再需要外部方法所需的 <link> 元素。

除了这些差异外,语法与外部方法完全相同(选择器、花括号、声明):

<head>
  <style>
    div {
      color: white;
      background-color: black;
    }

    p {
      color: red;
    }
  </style>
</head>
<body>
  ...
</body>

此方法可用于为网站的单个页面添加独特样式,但它不像外部方法那样保持分离,并且根据规则和声明的数量,它可能导致 HTML 文件变得相当大。

内联 CSS

内联 CSS 可以直接向 HTML 元素添加样式,尽管此方法不推荐:

<body>
  <div style="color: white; background-color: black;">...</div>
</body>

首先要注意的是,我们实际上没有使用任何选择器,因为样式是直接添加到 <div> 标签本身。接下来,我们有 style= 属性,其值在引号内的是声明。

如果您需要为单个元素添加独特样式,此方法可以正常工作。但是,通常不推荐使用此方法将 CSS 添加到 HTML,原因如下:

  • 一旦您开始向单个元素添加大量声明,它会很快变得混乱,导致 HTML 文件不必要地臃肿。
  • 如果您希望许多元素具有相同的样式,则必须将相同的样式复制并粘贴到每个单独的元素,导致大量不必要的重复和更多臃肿。
  • 任何内联 CSS 都将覆盖其他两种方法,这可能导致意外结果。(虽然我们不会深入探讨,但这实际上可以加以利用。)

作业

  1. 访问我们的 CSS 练习仓库并阅读 README 文件。

  2. 然后,一旦您知道如何使用练习,导航到 CSS 练习仓库的 foundations/intro-to-css 目录。在完成以下练习之前,请先查看每个 README 文件:

    • 01-css-methods
    • 02-class-id-selectors
    • 03-grouping-selectors
    • 04-chaining-selectors
    • 05-descendant-combinator

    注意:这些练习的解决方案可以在每个练习的 solution 文件夹中找到。

知识检查

以下问题让您反思本课程的关键主题。如果您无法回答问题,请点击它复习材料,但请记住,您不需要记忆或掌握这些知识。

  • 类和 ID 选择器的语法是什么?
  • 如何将单个规则应用于两个不同的选择器?
  • 给定一个 ID 为 title 且类为 primary 的元素,如何同时使用这两个属性创建单个规则?
  • 后代组合器的作用是什么?
  • 将 CSS 添加到 HTML 的三种方式分别是什么?
  • 这三种将 CSS 添加到 HTML 的方式之间有哪些主要区别?

附加资源

本节包含相关内容的实用链接。这不是必需的,因此仅供参考。

  • Mozilla CSS 值和单位可用于了解绝对或相对术语中可能的各种值类型。
  • 一个交互式 Scrim,以交互形式涵盖课程中的大部分内容。

CSS层叠机制

引言

在上一课中,我们学习了CSS基础语法和选择器。现在,我们将结合选择器知识来探讨CSS中的C——层叠(Cascade)。

课程概览

本节概述了本课将学习的关键主题:

  • 层叠机制的作用原理
  • 选择器优先级及组合使用
  • 继承性如何影响特定属性

CSS层叠机制

当多个规则相互冲突时,可能出现预期之外的结果。"为什么这些段落变成红色了?我明明想设置成蓝色!" 虽然令人沮丧,但需要理解:CSS不会无故违背指令。唯一例外是浏览器提供的默认样式(不同浏览器默认样式不同),这也是某些元素产生额外间距或按钮显示特定样式的原因。

意外结果通常源于:

  1. 浏览器默认样式
  2. 对属性理解不足
  3. 未掌握层叠机制

层叠机制决定了哪些规则最终生效,主要依据三个关键因素:

优先级

优先级更高的CSS声明将覆盖低优先级声明。优先级排序如下(从高到低):

  1. ID选择器(最高优先级)
  2. 类选择器
  3. 类型选择器

仅当元素存在冲突声明时,优先级才作为"决胜局"生效:

  • 1个ID选择器 > 任意数量类选择器
  • 1个类选择器 > 任意数量类型选择器
  • 同类型选择器数量多的规则胜出
示例分析
<!-- 案例1:类选择器数量决定 -->
<div class="main">
  <div class="list subsection">红色文本</div>
</div>

<style>
.subsection { color: blue; }  /* 1个类 */
.main .list { color: red; }   /* 2个类 → 生效 */
</style>
<!-- 案例2:ID选择器优先 -->
<div class="main">
  <div class="list" id="subsection">蓝色文本</div>
</div>

<style>
#subsection { color: blue; }  /* ID > 类 → 生效 */
.main .list { color: red; }   /* 2个类 */
</style>
<!-- 案例3:混合选择器计算 -->
<div class="main">
  <div class="list" id="subsection">红字黄底</div>
</div>

<style>
#subsection {                 /* 1个ID */
  background-color: yellow;   /* 无冲突,生效 */
  color: blue;
}
.main #subsection {           /* 1个ID + 1个类 → 更高优先级 */
  color: red;                 /* 生效 */
}
</style>
不参与优先级计算的符号
  • 通用选择器 (*)
  • 组合器 (+, ~, >, 空格)
/* 相同优先级:均含2个类 */
.class.second-class { font-size: 12px; }  /* 链式选择器 */
.class > .second-class { font-size: 24px; } /* 子组合器 */

/* 类型选择器 > 通用选择器 */
* { color: black; }   /* 无优先级 */
h1 { color: orange; } /* 生效 */

继承性(Inheritance)

某些CSS属性会由父元素自动传递给子元素(无需显式设置),主要涉及排版属性:

  • 继承属性:color, font-size, font-family
  • 非继承属性:大多数其他属性
直接目标优先原则
<div id="parent">
  <div class="child">蓝色文本</div>
</div>

<style>
#parent { color: red; }   /* ID选择器更高优先级 */
.child { color: blue; }   /* 但直接目标子元素 → 生效 */
</style>

规则顺序

当前两个机制无法决断时,最后定义的规则生效:

.alert { color: red; }    /* 先定义 */
.warning { color: yellow; } /* 后定义 → 对同时有alert/warning类的元素生效 */

任务练习

  1. 完成CSS练习库中 foundations/cascade 目录的练习:
    • 01-cascade-fix
      注:参考答案在各练习的solution文件夹中

知识检测

  • 问题:包含1个类选择器的规则 vs. 包含3个类型选择器的规则,哪个优先级更高?
    答案:类选择器优先级更高(类选择器 > 类型选择器)

扩展资源

HTML与CSS检查指南

引言

掌握HTML/CSS的检查与调试能力是前端开发的核心技能。本课将详解Chrome开发者工具,助您查看元素详情、分析CSS规则,并快速定位修复代码问题。

课程概览

本节涵盖以下核心内容:

  • 开发者工具的开启方式
  • 特定元素的检查方法
  • HTML/CSS实时调试技巧

开发者工具

开启检查器:

  1. 网页任意位置右键点击选择"检查"(Inspect)
  2. 或直接按键盘 F12键
    打开后您将看到当前页面的完整代码结构,请聚焦于元素面板(Elements)样式面板(Styles)

元素检查

元素面板中:

  • 浏览整个HTML文档结构
  • 点击任意元素可选中目标
  • 或使用元素选择器(蓝色箭头图标)悬停查看页面元素

nihaoma

选中元素后,样式面板将显示:

  • 当前生效的样式
  • 被覆盖的样式(带删除线效果)
    例如检查TOP首页的主标题时:

nihaobu

实时样式调试

通过样式面板可直接修改CSS:

  1. 在现有选择器中点击属性/值进行编辑
  2. 或点击空白区域添加新规则
    所有更改将实时反馈在页面上(注意:此操作仅临时生效,不会修改源代码文件)

实践任务

完成Chrome开发者工具官方文档学习:

  1. 工具概览:了解功能模块
  2. 开启工具:掌握多种启动方式
  3. 查看修改DOM:跳过JavaScript控制台部分
  4. CSS调试指南:跟随教程实操练习

知识检测

  • 如何选择页面特定元素?
    右键点击元素选择"检查"或使用元素选择器
  • CSS声明中的删除线含义?
    表示该样式被更高优先级规则覆盖
  • 如何实时修改元素CSS?
    在样式面板中直接编辑属性值

扩展资源

CSS盒模型详解

引言

掌握HTML/CSS基础后,我们将深入核心技能:定位与布局。调整字体颜色固然重要,但精确控制元素位置才是网页设计的精髓。试问:您见过所有元素简单堆叠的网页吗?

布局技能虽关键却易掌握,但许多初学者急于学习JavaScript而忽略这些基础,导致后期陷入无尽调试困境(甚至诞生搞笑动图)。毕竟若无法控制元素位置,JavaScript再强也毫无意义。现在让我们正式启程!

课程概览

本节将系统学习:

  • 盒模型核心原理
  • 通过margin, padding, border精准控制元素尺寸

盒模型解析

理解盒模型是CSS成功的基石。虽然概念简单,但跳过它将导致后续开发严重受阻。

核心原则:网页所有元素都是矩形盒子。这些盒子可嵌套、并排。通过以下CSS可直观查看:

* {
  outline: 2px solid red; /* 为所有元素添加红色轮廓 */
}

nihaohao

您可直接在浏览器检查器的"样式面板"添加此代码实时验证。即使是圆形元素,布局时仍按矩形盒子处理——网页布局本质就是盒子的嵌套与堆叠。

nihaohuai

尺寸控制三要素

  • padding:内容与边框间距(内边距)
  • border:内边距与外边距间的分隔线(边框)
  • margin:盒子与相邻元素的间距(外边距)

nihuai

学习任务

  1. 8分钟掌握CSS盒模型:快速理解盒模型与边距
  2. border-box详解:深入解析box-sizing属性
  3. MDN盒模型权威指南:通过在线编辑器实操练习(重点学习行内盒子)
  4. CSS Tricks外边距指南:掌握auto居中与外边距折叠

知识检测

  • 盒模型属性从内到外顺序?
    内容(content) → 内边距(padding) → 边框(border) → 外边距(margin)

  • box-sizing作用?
    控制元素尺寸计算方式(是否包含内边距与边框)

  • 标准盒模型 vs 替代盒模型?
    标准模型:width/height仅含内容
    替代模型(border-box):width/height包含内容+内边距+边框

  • 元素间距用margin还是padding?
    margin(元素间距离)

  • 内容与边框间距用哪个?
    padding(盒内空间)

  • 元素重叠用哪个?
    margin(负外边距可实现重叠)

  • 如何全局启用替代盒模型?

    * {
      box-sizing: border-box;
    }
    
  • 如何水平居中元素?

    .center {
      margin: 0 auto; /* 块级元素有效 */
    }
    

扩展资源

CSS块级与内联元素精解

引言

在上一课中,我们认识到不同显示类型具有独特的盒模型,可通过box-sizing属性调整尺寸计算。CSS包含两种基础盒类型:块级(block)内联(inline) 盒子,它们决定了元素的交互行为。本课将深入探索display属性如何控制HTML元素的显示方式。

课程概览

本节将系统掌握:

  • 常规流(Normal Flow)布局原理
  • 块级与内联元素的本质区别
  • 常见元素的默认显示类型
  • div与span容器的核心作用

块级 vs 内联元素

块级元素(block)

  • 默认样式:display: block
  • 核心特征:垂直堆叠排列(每个元素独占一行)
  • 典型元素:<div><h1>-<h6><p><ul>
  • 支持完整盒模型:可自由设置宽高/边距

内联元素(inline)

  • 默认样式:display: inline
  • 核心特征:水平流动排列(不强制换行)
  • 典型元素:<span><a><strong><img>
  • 特殊限制:
    • 设置宽高无效
    • 垂直边距不影响布局
    • 示例:文本中的链接保持行内流动特性

混合形态:内联块级(inline-block)

  • 特性:保持内联流动特性,但支持块级盒模型
  • 应用场景:水平排列元素(但现代布局更推荐Flexbox)

通用容器:div与span

<div> 块级容器

  • 本质:无语义的块级通用容器
  • 核心用途:
    • 创建布局分区
    • 组合相关元素
    • CSS样式挂钩
  • 示例:创建彩色内容区块
<div class="content-block">内容区块</div>

<span> 内联容器

  • 本质:无语义的内联通用容器
  • 核心用途:
    • 包裹文本片段
    • 精细控制行内样式
  • 示例:标记重点文本
<p>这是<span class="highlight">重点内容</span>的示例</p>

学习任务

  1. 掌握常规流布局原理:理解浏览器默认排版机制

  2. 查阅块级/内联元素完整列表

  3. 通过对比实例理解inline与inline-block差异

  4. 完成CSS练习库任务:

    • 01-margin-and-padding-1
    • 02-margin-and-padding-2
      注:参考答案在solution文件夹
  5. 实战项目:美化HTML基础篇的食谱页面

    • 使用外部CSS文件链接

    • 实践颜色/背景/字体等属性

    • 字体设置方案:

      /* 优先使用安全字体,最后设置通用字体族 */
      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      }
      
    • 参考资源:CSS字体指南安全字体列表

知识检测

  • 块级与内联元素核心区别?
    块级垂直堆叠/支持完整盒模型,内联水平流动/尺寸受限
  • 内联 vs 内联块级关键差异?
    内联块级支持设置宽高和垂直边距
  • 元素类型识别
    • h1 → 块级
    • button → 内联块级
    • div → 块级
    • span → 内联

扩展资源

Flexbox

Flexbox弹性布局入门指南

引言

在网页布局中,元素定位方法历经多次革新。Flexbox的出现彻底改变了布局方式——虽然它是相对较新的技术,但如今已成为开发者的首选布局工具。让我们先了解Flexbox发展史,再掌握这项核心技能!

课程概览

本节将掌握:

  • Flexbox元素定位技术
  • 弹性容器与弹性项目的概念
  • 创建超越简单堆叠的高级布局

学习准备

Flexbox布局涉及较多概念,请善用浏览器开发者工具进行调试。当布局效果不符预期时,检查工具应是您的第一选择。

虽然Flexbox不比其他概念更难,但包含更多组成部分。建议您:

  1. 动手实践所有代码示例
  2. 随时调整参数观察变化
  3. 完成章节练习时回看参考资料

Flex布局实战

Flexbox通过弹性伸缩机制实现行列布局。通过交互示例直观感受:

操作指引

  1. 在CodePen中删除/**/注释符号
  2. 观察三个div变为水平排列
  3. 点击"1x"/"0.5x"调整视图尺寸
  4. 注意元素自动伸缩填满空间

扩展操作

  • .flex-container内添加新div → 自动加入弹性布局
  • 点击"在CodePen上编辑"获得完整操作空间

核心概念:弹性容器 vs 弹性项目

  • 弹性容器:设置display: flex的元素
  • 弹性项目:直接位于容器内的子元素

嵌套机制
弹性项目可同时成为新容器,实现复杂布局:

<!-- 三级嵌套示例 -->
<div class="container1"> <!-- 一级容器 -->
  <div class="item1">   <!-- 一级项目 & 二级容器 -->
    <div class="item2"></div> <!-- 二级项目 -->
  </div>
</div>

布局能力展示
仅用Flexbox即可构建复杂界面:

知识检测

  • 弹性容器与项目的区别?
    容器控制布局流向,项目接受容器规则
  • 如何创建弹性项目?
    将元素直接置于display: flex的容器内

扩展资源

Flexbox弹性伸缩机制解析

引言

上节课我们通过flex: 1实现了元素的等分效果,现在将深入探究其背后的伸缩原理。

课程概览

本节将掌握:

  • flex简写属性分解的3个子属性
  • 各子属性的独立使用方法

flex简写属性剖析

flex实际上是三个属性的复合简写:

flex: [flex-grow] [flex-shrink] [flex-basis];

当仅指定单值时(如flex: 1),等价于:

  • flex-grow: 1
  • flex-shrink: 1
  • flex-basis: 0

flex-grow(增长因子)

  • 定义:元素在容器剩余空间的分配比例
  • 特性:值越大占据空间越多
  • 示例:三个元素设为flex:1时等分空间,若其中一元素设为flex:2,则占据双倍空间
<div class="flex-container">
  <div style="flex: 1">1</div>
  <div style="flex: 2">2 (双倍空间)</div>
  <div style="flex: 1">3</div>
</div>

flex-shrink(收缩因子)

  • 定义:容器空间不足时元素的收缩比例
  • 默认值:1(等比例收缩)
  • 特殊值:0(禁止收缩)
  • 示例:中间元素设置flex-shrink:0后,缩小浏览器窗口时保持250px宽度
<div class="flex-container">
  <div style="flex: 1 1 250px">1 (可收缩)</div>
  <div style="flex: 1 0 250px">2 (禁止收缩)</div>
  <div style="flex: 1 1 250px">3 (可收缩)</div>
</div>

重要提示:当使用flex-grow/shrink时,元素的width属性可能失效

flex-basis(基准尺寸)

  • 作用:定义伸缩计算的起始尺寸
  • 关键值:
    • 0:忽略元素原始宽度
    • auto:参考width声明(如上例需设为auto才能保持250px)
  • 默认值冲突:
    • flex-basis独立属性默认:auto
    • flex简写flex:1等价于:flex-basis: 0

flex: auto的特殊含义

flex: auto 是特殊简写,等价于:

flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;

注意与flex: 1(隐式flex-basis:0)的区别

实践建议

实际开发中最常用模式:

.item {
  flex: 1; /* 等分伸缩 */
}

.fixed-item {
  flex-shrink: 0; /* 禁止收缩 */
}

复杂比例布局(如2:1分栏)较少使用

学习任务

  1. 阅读W3C弹性盒规范掌握基础值
  2. 查看MDN flex文档了解完整语法

知识检测

  • flex简写的三个值代表什么?
    flex-grow | flex-shrink | flex-basis
  • flex: auto等价于什么?
    flex: 1 1 auto

扩展资源

Flexbox布局轴线解析

引言

本课将探索如何通过flex-direction属性控制弹性容器内项目的排列方向。

课程概览

本节将掌握:

  • 弹性容器的双轴线概念
  • 实现垂直布局(列排列)的核心方法

轴线机制

Flexbox布局的核心特性是支持水平垂直双方向排列,默认方向为水平(row)。通过flex-direction可切换方向:

css

.flex-container {
  flex-direction: column; /* 切换为垂直排列 */
}

主轴与交叉轴

无论方向如何,弹性容器始终存在两条轴线:

  1. 主轴(Main Axis):项目排列方向
  2. 交叉轴(Cross Axis):垂直于主轴方向

方向切换时轴线关系:

  • row(默认):主轴水平(左→右),交叉轴垂直
  • column:主轴垂直(上→下),交叉轴水平

垂直布局实践

通过CodePen实例观察方向切换效果:

操作说明

  1. 取消注释flex-direction: column
  2. 观察div变为垂直堆叠
  3. 注意:若使用flex: 1简写会导致高度塌陷

垂直布局高度问题解析

塌陷原因
flex: 1等价于flex-basis: 0,而空div默认高度为0。解决方案:

/* 方案1:显式声明基准值 */
.item {
  flex: 1 1 auto; /* 参考height值 */
}

/* 方案2:单独使用flex-grow */
.item {
  flex-grow: 1;
}

/* 方案3:容器设置固定高度 */
.flex-container {
  height: 300px;
}

方向与基准值关系

flex-basis参照维度随方向变化:

  • row模式 → 参照宽度(width)
  • column模式 → 参照高度(height)

注意:对于从右到左(如阿拉伯语)或从上到下(如中文传统排版)的语种,主轴方向可能不同,但此特性超出基础范围

知识检测

  • 如何实现垂直排列?
    flex-direction: column
  • column布局中flex-basis参照什么?
    元素高度(height)
  • row布局中flex-basis参照什么?
    元素宽度(width)
  • 为何基准参照不同?
    主轴方向决定基准维度(水平→宽度,垂直→高度)

扩展资源

Flexbox对齐机制精解

引言

虽然flex:1能实现空间均分效果,但实际布局中常需控制固定尺寸元素的精确排列。Flexbox的对齐功能将解决此类需求。

课程概览

本节将掌握:

  • 弹性容器内元素的双向对齐技术(水平/垂直)

对齐机制实践

基础示例分析

操作步骤

  1. 移除.itemflex:1(预测元素恢复原始宽度)
  2. .container添加justify-content: space-between → 元素两端分布

主轴对齐(justify-content)

控制元素沿主轴排列:

  • center:主轴居中 → 添加justify-content: center
  • 其他常用值:
    • flex-start:起始端对齐(默认)
    • flex-end:末端对齐
    • space-around:均匀分布(含两侧间距)
    • space-evenly:绝对均匀分布

交叉轴对齐(align-items)

控制元素沿交叉轴排列:

  • 添加align-items: center → 垂直居中效果

方向变化影响:当flex-direction: column时,justify-content控制垂直对齐,align-items控制水平对齐

间距控制(gap)

现代间距解决方案:

.container {
  gap: 8px; /* 元素间添加8px间距 */
}

学习任务

  1. Flexbox交互式指南:通过创意案例巩固概念
  2. Flexbox实战应用:MDN场景化教程(务必操作交互模块)
  3. Flexbox终极指南:经典速查手册(重点1-3节和5节)
  4. 完成CSS练习库任务(按顺序):
    • 01-flex-center
    • 02-flex-header
    • 03-flex-header-2
    • 04-flex-information
    • 05-flex-modal
    • 06-flex-layout
    • 07-flex-layout-2
      注:参考答案在solution文件夹

知识检测

  • justify-content vs align-items区别?
    justify-content控制主轴对齐,align-items控制交叉轴对齐

  • 如何实现元素完全居中?

    .container {
      display: flex;
      justify-content: center; /* 主轴居中 */
      align-items: center;     /* 交叉轴居中 */
    }
    
  • space-between vs space-around区别?
    space-between:首尾贴边,中间等距
    space-around:每个元素两侧等距(元素间距离=边缘距离×2)

扩展资源

项目:登录页面 (Landing Page)

引言

在本项目中,您将根据我们提供的设计创建一个完整的网页。如果您一直跟着课程学习,您应该已经具备了完成此项目所需的技能,但这可能并不容易!

我们提供给您的设计以两张图片的形式呈现:一张是完整网站的图片,另一张包含我们使用的一些字体和颜色的详细信息。

不要害怕使用谷歌(Google)或回顾之前的课程来查找资料。在现实中,专业开发人员会*不断地*使用谷歌搜索他们做了多年的事情。 现阶段,并不期望您记住所有内容,所以不必担心。此外,设计中可能包含一些您在课程中尚未遇到的小细节。这是有意为之。 这些细节是次要的,并且很容易搜索到(例如,在谷歌搜索 css 圆角)。

尽量让您的项目接近设计效果,但不必追求像素级精确。不需要拿出尺子或数像素来确定各个部分之间的精确边距。本作业的目的是从头开始创建一些东西,并使各种元素相对于其他元素大致处于正确的位置。即使您使用了 margin: 24px 而设计上实际是 margin: 48px,也没关系。

最后,欢迎您将您自己的内容替换到此设计中。图片包含了一些无意义的虚拟内容,但如果您想虚构一个企业并个性化这个页面,请随意!在占位符位置插入实际的图片,也欢迎您稍微调整一下颜色和字体。

关于网络图片的说明

您没有法定权利使用在网络上找到的任何图片。网上有许多免费图片,但请确保您使用的图片确实是您可以免费使用的,并且务必在您的项目中注明图片创作者的来源。一个简单的注明方式是在您的代码仓库(repository)的 README 文件中写上创作者的名字和联系信息。

网络上一些可以找到免费使用图片的好地方包括 Pexels, Pixabay, 和 Unsplash

设置项目的 GitHub 仓库

如同我们在“菜谱项目”中所做的那样,请在开始此项目之前设置好您的 git 仓库。Git 和 GitHub 将成为您未来的作品集,所以您需要把所有内容都保存在那里!如果您需要提醒如何设置,请回顾 菜谱项目

作业要求

不要忘记尽早并经常提交(commit)!您可以参考 提交信息课程

  1. 下载设计图片并查看您将要创建的内容。
    • 图片一(完整设计)
    • 图片二(颜色和字体)
      • 主标题文本 (Hero text) 是出现在网页顶部的声明。
  2. 处理此类项目的方法有很多种,面对一个空白的 HTML 文档却不知从何开始可能会让人不知所措。我们的建议是:一次只处理一个部分。您要创建的网站有 4 个主要部分(和一个页脚),所以选择一个部分,在它基本成型后再继续下一个部分。从顶部开始总是一个稳妥的计划。
  3. 对于您正在处理的部分,先获取所有内容到页面上,然后再开始设计样式。换句话说,先做 HTML,然后做 CSS。一旦开始设计样式,您可能还需要回过头来修改 HTML,但从一开始就在两者之间来回切换会花费更多时间,并可能导致更多的挫败感。(注意:您不需要使用多个样式表。仅使用一个 CSS 文件对于此项目就足够了)。
  4. 此页面上的许多元素与您在 flexbox 练习中看到的内容非常相似……如果需要复习,请随时回顾那些内容。
  5. 不要担心让您的项目在移动设备上看起来美观。我们稍后会学习这个。
  6. 完成后,别忘了将其推送到 GitHub!

在网络上查看您的项目

如果您想向他人展示您的工作(项目),或在下方提交解决方案,您将需要发布您的网站,以便其他人可以从网络上访问它,而不仅仅是在您的本地机器上。好消息是,如果您的项目在 GitHub 上(如上所述),那么做到这一点很简单。

GitHub 允许您直接从 GitHub 仓库发布 Web 项目。这样做将使您能够通过 您的-github-用户名.github.io/您的-github-仓库名 访问您的项目。

有几种方法可以发布您的项目,但最简单的是以下步骤:

  • 确保您项目的主 HTML 文件名为 index.html。如果不是,您需要重命名它。
  • 在网页上进入您的 GitHub 仓库,并点击 Settings(设置)按钮,如下方截图所示。
  • 点击左侧边栏上的 Pages(页面)。
  • Branch(分支)从 None(无)更改为 main(主分支)并点击 Save(保存)。
  • 可能需要几分钟(GitHub 网站说最多 10 分钟),但您的项目应该可以通过 您的-github-用户名.github.io/您的-github-仓库名 这个网址在网络上访问了(显然,在链接中替换为您自己的信息)。

JavaScript 基础

变量与运算符

简介

在之前的章节中,你学习了如何使用 HTML 构建网页结构以及使用 CSS 为其添加样式。下一步是让网页具有交互性,而这正是 JavaScript 的用武之地。

在本节中,我们将重点介绍 JavaScript 的基础知识,以及你如何用它来操作网页与用户之间的各种交互。

课程概览

本节概述了你在本课中将学习到的主题。

  • 使用 HTML 文件运行 JavaScript 代码。
  • 使用 letconst 声明变量。
  • 执行数字运算。
  • 执行字符串操作。
  • 使用逻辑和数学运算符。

如何运行 JavaScript 代码

在 Foundations 课程的大部分内容中,我们将编写的所有 JavaScript 都将通过浏览器运行。Foundations 后续课程以及 NodeJS 路径将向你展示如何在浏览器环境之外运行 JavaScript。这意味着我们将让浏览器执行我们的代码,即使这些代码来自我们创建的文件。

在这些课程之外,目前你应始终默认在浏览器中运行 JavaScript(除非另有说明),否则可能会遇到意想不到的错误。

最简单的入门方法是创建一个包含 JavaScript 代码的 HTML 文件。在你计算机的某个位置创建一个文件,使用 VS Code 的代码片段 ! + TAB 来生成基本的 HTML 骨架。确保包含 <script> 标签:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <script>
    // 你的 JavaScript 代码放在这里!
    console.log("Hello, World!")
  </script>

</body>
</html>

保存此文件并在 Web 浏览器中打开它,然后打开浏览器的控制台(Console):

  1. 在空白网页上右键单击。
  2. 点击“Inspect”(检查)或“Inspect Element”(检查元素)打开开发者工具(Developer Tools)。
  3. 找到并选择“Console”(控制台)选项卡,你应该能看到我们 console.log 语句的输出。

实时预览 (Live Preview)

你可以使用 Visual Studio Code 中的 Live Preview 扩展 在保存文件时自动更新浏览器,而无需在编辑代码后手动刷新页面即可看到更改。尝试将文本编辑成其他内容吧!

console.log() 是将内容打印到浏览器开发者控制台的命令。你可以使用它将以下任何文章和练习的结果打印到控制台。我们鼓励你跟着本课以及未来课程中的所有示例进行编码。

另一种在网页中包含 JavaScript 的方式是通过外部脚本。这与你将外部 CSS 文档链接到网站的方式非常相似。

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

JavaScript 文件的扩展名是 .js,类似于样式表的 .css。外部 JavaScript 文件用于更复杂的脚本。

我们将文件命名为 javascript.js,但也可以选择任何名称,如 my-script.js 甚至没有名称 .js。真正重要的是 .js 扩展名。

变量 (Variables)

这些是任何程序的构建基石。你可以将变量视为代码中数据的“存储容器”。

你可以使用 let 关键字声明变量。我们来试试吧!(不是双关语 😉)。

let name = "John"; // 名字
let surname = "Doe"; // 姓氏

console.log(name);
console.log(surname);

console.log 会输出什么?试试看!

你也可以重新赋值变量:

let age = 11; // 年龄
console.log(age); // 在控制台输出 11

age = 54; // 重新赋值

console.log(age); // 现在会输出什么?

注意第 4 行没有 let —— 我们不需要它,因为变量已经在之前声明过了,我们这里只是对它进行重新赋值!

重新赋值很酷,但如果我们希望它发生呢?例如,我们可能有一个常量 pi,它永远不需要被重新赋值。我们可以使用 const 关键字来实现这一点。

const pi = 3.14; // 圆周率常量
pi = 10; // 尝试重新赋值

console.log(pi); // 会输出什么?

你的直觉可能会告诉你将输出 3.14。试试看!

会抛出一个错误。它甚至不会执行到 console.log!你可能会想为什么我们希望代码中出现错误。说实话,错误在告诉我们代码出了什么问题以及问题具体出在哪里方面非常有用。没有它们,我们的代码仍然可能无法按我们的意愿运行,但要找出问题所在将会非常痛苦!

因此,总结一下,有两种声明变量的方法:

  • let,我们可以重新赋值。
  • const,我们不能重新赋值,如果我们尝试会抛出错误。

还有第三种方式,var,这是 JavaScript 中最初声明变量的方式。varlet 类似,用这种方式赋值的变量可以重新赋值,但它有其他一些在语言引入 letconst 时已被解决的怪癖。总的来说,它不再被使用了。然而,你可能会在某个时候遇到使用 var 的代码,所以知道它的存在是有用的。

数字 (Numbers)

数字是编程逻辑的基石!事实上,很难想象任何有用的编程任务不涉及至少一点基础数学……所以了解数字如何工作显然非常重要。幸运的是,它也相当简单明了。

如果你上过学,你可能不会觉得这个概念太难掌握。例如,数学表达式 (3 + 2) - 76 * (1 + 1) 在 JavaScript 中也是有效的。如果你把它放进 console.log,它会计算表达式并输出正确的数字。试试看!

练习 (Assignment)

在你的 HTML 文件的脚本标签 (<script>) 中添加代码,尝试以下练习:

  1. 将两个数字相加! 在你的脚本中,输入 console.log(23 + 97)。运行它应该会输出 120
  2. 做同样的事情,但将 6 个不同的数字相加。
  3. 现在记录以下表达式的值: console.log((4 + 6 + 9) / 77)。控制台应该输出大约 0.24675
  4. 让我们使用一些变量!
    1. 在脚本标签中添加此语句:let a = 10
    2. 在它下面,添加 console.log(a)。运行此代码时,浏览器控制台应输出 10
    3. 之后,用另一个数字值重新赋值 a(例如 a = 20;)。再次记录 a,它应该显示更新后的值(之前的 console.log 仍应显示 a 重新赋值前的旧值 10)。
    4. 现在在脚本底部添加 let b = 7 * a
    5. 记录 b 是什么。它应该输出 7 乘以你重新赋值给 a 的数字的结果。
  5. 尝试这个序列:
    1. 声明一个常量变量 max,值为 57
    2. 声明另一个常量变量 actual,并将其赋值为 max - 13
    3. 再声明一个常量变量 percentage,并将其赋值为 actual / max
    4. 现在如果你记录 percentage,你应该会在控制台中看到一个类似 0.7719 的值。
  6. 花几分钟时间继续在你的脚本标签中尝试各种东西。 最终,我们将学习如何让这些东西真正显示在网页上,但所有这些逻辑都将保持不变。在继续之前,请确保你对此感到满意。

阅读以下文章以加深你的知识。

  1. 阅读 JavaScript.info 上关于 JavaScript 中的变量 的文章(包括变量命名规则)。
  2. 这篇 W3Schools 关于 JavaScript 算术 的课程,以及这篇关于 JavaScript 数字 的课程,是介绍在 JavaScript 中能用数字完成什么任务的好材料。
  3. 这篇 MDN 关于 JavaScript 数学 的文章从稍微不同的角度涵盖了相同的信息,同时还教你如何在 JavaScript 中应用一些基础数学。关于数字你还能做更多的事情,但目前你只需要了解这些。
  4. 阅读(并跟着代码操作!)这篇关于 JavaScript 运算符 的文章。别忘了做页面底部的“Tasks”(任务)!它将让你很好地了解在 JavaScript 中能用数字(以及其他东西!)完成什么任务。

知识检查 (Knowledge check)

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,点击其链接可回顾相关材料,但请记住你不需要死记硬背或完全掌握这些知识。

附加资源 (Additional resources)

本节包含相关内容的实用链接。这些内容不是必需的,因此仅供参考。

安装 Node.js

简介

Node.js 是一个 JavaScript 运行时环境,让您能在浏览器外运行 JavaScript。在接下来的课程中,部分练习需要使用它。在开始之前,我们需要先安装一些必需工具才能将 Node 安装到您的系统中。

我们将使用 nvm(Node 版本管理器)进行安装,因为它可以轻松切换 Node 版本和升级 Node。另一个工具 npm 我们稍后会用于安装 JavaScript 环境中的各种库和工具。这两个工具容易混淆,请仔细阅读!

使用 nvm 安装 Node 也非常简单,因此整个过程会很快完成 😃

课程概述

本节概述了您将在本课中学习的主题:

  • 学习如何安装 nvm(Node 版本管理器)和 npm
  • 学习如何运行 Node 控制台

安装 NVM

在 Linux 上安装

步骤 0:先决条件

要正确安装 nvm,您需要安装 curl。运行以下命令安装 curl

  sudo apt install curl  

注意:您可能需要将 Ubuntu 软件包列表更新到最新版本才能完成 Curl 安装。如果是这样,请运行以下命令:

  sudo apt update && sudo apt upgrade  

步骤 1:下载并安装 NVM

运行此命令安装 nvm

  curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash  

步骤 2:初始化 NVM

在终端中,应该会显示有关如何初始化 nvm 的说明。如果没有(或者您不想从终端复制),请运行以下命令:

  export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm  

您可以通过运行以下命令来验证 nvm 是否已安装:

  command -v nvm  

如果此命令返回 nvm: command not found,请关闭终端并重新打开它。

在 macOS 上安装

在 macOS 10.15 及更高版本中,默认 shell 现在是 zsh。安装过程中,nvm 会在您的用户主目录中查找 .zshrc 文件。默认情况下此文件不存在,因此我们需要创建它。

要创建 .zshrc 文件并开始 nvm 安装,请运行以下命令:

touch ~/.zshrc 
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash 

重启终端,或者将以下内容复制粘贴到终端并按 Enter

export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion 

运行以下命令测试 nvm 安装:

nvm --version 

更多信息请阅读 NVM GitHub 上的安装和更新说明

安装 Node

现在我们已经安装了 nvm,接下来可以安装 Node.js。

步骤1:安装

运行:

nvm install --lts

这将安装最新的 Node.js 长期支持 (LTS) 稳定版本,终端会显示大量输出。如果一切正常,您会在输出信息中看到类似这样的内容(X 会被实际数字替代):

Downloading and installing Node vXX.xx.x...

如果没有看到,请关闭终端,重新打开后再次运行 nvm install --lts

步骤2:设置 Node 版本

我们需要告诉 nvm 运行 node 命令时使用哪个版本。很简单,只需运行以下命令:

nvm use --lts

这样我们就告诉 nvm 使用计算机上安装的最新 Node.js LTS 版本。必须使用 Node.js 的 LTS 版本,以避免与后续课程中将安装的软件包出现兼容性问题。Node.js 的 LTS 版本在首次发布后保证提供 30 个月的支持,它比非 LTS 版本更稳定,与各种软件包的兼容性也更好。

现在运行 node -v,您应该会看到 vXX.xx.x 或类似内容(X 会被实际数字替代)。

如果看到这个提示,说明您已成功安装 Node.js!

使用 Node 控制台

为了方便使用,Node 提供了交互式控制台,让您可以直接在终端中运行和编辑 JavaScript 代码,类似于 Ruby 的 IRB。这非常有助于快速调试或测试小段代码,无需每次都打开浏览器。

要运行 Node 控制台,请打开终端并输入 node。输入 .exit 可退出控制台。

补充资源

本部分包含相关内容的实用链接,非必读内容,供您参考:

数据类型和条件语句

简介

在 JavaScript 中,您会遇到几种极其常见的数据类型,这些基础课程将为您全面奠定坚实基础。不过在深入学习之前,请先阅读这篇 JavaScript 最常见数据类型概述

课程概述

本节概述了您将在本课中学习的主题:

  • 列举 JavaScript 中的八种数据类型
  • 理解单引号、双引号和反引号的区别
  • 在字符串中嵌入变量/表达式
  • 理解什么是方法
  • 列举三种逻辑运算符
  • 理解比较运算符的作用
  • 理解条件语句的概念
  • 理解嵌套的概念
  • 理解真值(truthy)和假值(falsy)

字符串

根据工作内容不同,您可能处理文本多于数字。字符串就是一段文本...也是这门语言的基础构建块。

  1. 阅读并实践 MDN 关于 JavaScript 字符串的教程。跳过 Concatenation in context 部分,这部分内容我们将在后续 DOM 操作课程中讲解
  2. 学习 W3Schools 关于字符串方法的课程,了解字符串的更多用法
  3. 术语时间:方法是语言或特定数据类型内置的功能片段。在 W3Schools 关于字符串方法的课程中,您已经学习了几个字符串方法,如 replacesliceMDN 字符串文档提供了更全面的内置字符串方法参考。您无需记忆这些方法,但这份文档将是宝贵的参考资料,请务必收藏!

条件语句

现在是趣味时间...到目前为止,我们的编程内容用基础数学就能完成。确实,我们教会了计算机如何做数学计算,这提高了效率,但编程的本质是教会计算机如何做决策以完成更复杂的任务。条件语句就是实现这一目标的方式。

  1. 学习条件语句的第一步是确保您掌握 比较操作
  2. W3Schools 也有 关于 JavaScript 条件语句的课程
  3. JavaScript.info 提供了 关于逻辑运算符的优秀教程。关于此阅读任务的提示:您会遇到 alert() 中包含数字或字符串的问题,括号内的情况将在课程后期讨论。有些答案现在可能不太明白,但它们是准确的,随着课程深入您会理解。现在不必担心!
  4. MDN 关于条件语句的文章强化了这一概念,并提供了多个有趣的网站构建应用示例
  5. JavaScript.info 的 if/else 课程涵盖相同基础概念(可作为复习阅读!),更重要的是在页面底部提供了常规"任务"!
  6. 学习 switch 语句,这在处理多条件时非常方便

练习任务

为了提供充分实践,我们创建了 JavaScript 练习供您操作。这些练习包含测试用例,用于确保您的代码按预期工作。当您看到 return 时,它表示函数运行结束后将返回 return 后的内容。在后续课程中,我们将更详细地讲解这些概念,现在请坚持学习!

务必按此处顺序操作。阅读所有说明,观察终端输出,并仔细阅读所有错误信息。

  1. 按照我们 javascript-exercises 仓库的 README 说明设置本地环境。完成仓库 fork、克隆和 Jest 安装后,按顺序完成以下练习前请先阅读各 README 文件:
    • 01_helloWorld(此练习特别适合初学者,确保您设置正确!)
    • 02_addNumbers
    • 03_numberChecker
    • 04_mathEquations
    • 05_joinStrings

注意:这些练习的解决方案可在每个练习的 solution 文件夹中找到

知识检查

以下问题帮助您反思本课关键主题。若无法回答,可点击问题回顾内容,但请注意您无需记忆或完全掌握这些知识:

补充资源

本部分包含相关内容的实用链接,非必读内容,供您参考:

  • 正则表达式(通常称为 regex)是用于字符串验证的模式匹配工具。但了解何时不应使用正则表达式同样重要。字符串处理有多种方法,正则表达式有时可能较慢

  • Web Dev Simplified 的 20 分钟正则表达式教程

  • JavaScript 开发者工具

    简介

    掌握浏览器内置的开发者工具是任何 Web 开发者的必备技能。开发者工具可用于运行 JavaScript 代码、编辑 HTML 和 CSS 样式(无需刷新页面)以及查看性能数据,能为您节省大量时间。上手操作非常简单。您应已熟悉使用它们进行调试和处理 HTML/CSS,现在让我们看看它们在编写 JavaScript 时如何发挥作用。

    课程概述

    本节概述了您将在本课中学习的主题:

    • 使用开发者工具调整网站屏幕尺寸
    • 查看并修改 DOM
    • 调试 JavaScript
    • 使用断点
    • 在"元素"面板查看和编辑 HTML
    • 通过"资源"面板检查网站运行脚本
    • 为类添加 CSS 伪状态
    • 按字母顺序查看 CSS 属性
    • 在 Chrome 开发者工具中查看和编辑元素的盒模型
    • 以打印模式查看页面
    • 启用或禁用 CSS 类
    • 在设备模式下模拟媒体查询

    打开开发者工具

    三种方式打开 Chrome 开发者工具菜单:

    1. 通过 Chrome 菜单 > 更多工具 > 开发者工具
    2. 网页任意位置右键点击选择 检查
    3. 使用快捷键 F12 或 Ctrl + Shift + C (Mac: Opt + Cmd + C)

    练习任务

    Google 已更新下方教程的部分内容,某些元素可能变更或不存在,但您仍可使用相同的功能和工具进行操作。例如,教程可能要求检查页面上已移除的按钮——您仍可检查现有元素完成学习。

    1. 前往 Google 的 Chrome 开发者工具文档。以下子章节涵盖您 95% 的使用场景,请选择阅读(熟悉部分可跳过):
    2. 学习 Chrome 开发者工具控制台概述,熟悉控制台功能及用法

    知识检查

    以下问题帮助您反思本课关键主题(无需记忆,点击问题可回顾内容):

    补充资源

    本部分包含相关内容的实用链接(选学):

函数基础

简介

内容将变得非常精彩!迄今为止,您已编写了大量代码来解决各种问题,但这些代码尚未充分发挥其潜力。

想象一下:将您的脚本打包成小巧的模块,无需重写或修改代码即可重复使用。这就是函数的强大之处,它们在 JavaScript 中被广泛使用

课程概述

本节概述了您将在本课中学习的主题:

  • 如何定义和调用不同类型的函数
  • 如何使用返回值
  • 什么是函数作用域

函数详解

以下面示例函数为背景,讨论参数(parameters)与实参(arguments)的区别:

function favoriteAnimal(animal) {
    return animal + " is my favorite animal!"
}

console.log(favoriteAnimal('Goat'))

在 JavaScript 中:

  • 参数是函数声明括号 () 内列出的占位符
  • 实参是实际传递给函数的值

上例中:

  • 第一行 function favoriteAnimal(animal) 定义函数,括号内的 animal 是参数(可替换为 petx 等名称)
  • 最后一行 favoriteAnimal('Goat') 调用函数,'Goat' 是实参
  • 参数 animal 是未来值的占位符,实参 'Goat' 是实际传入的值

下图直观展示参数传递和返回值的过程:

注意:

  • 通过在 console.log() 中调用 favoriteAnimal('Goat'),函数返回值被打印到控制台
  • 函数调用 favoriteAnimal('Goat') 本身作为实参传递给 log() 函数
  • 若直接调用函数而不使用 console.log,控制台不显示内容,但函数仍会返回字符串

请随意实验代码:将 'Goat' 替换为您喜欢的动物,或修改函数声明中的 animal 参数,观察变化。

练习任务

注意:文章 #1 和 #2 附带的练习暂不完成(涉及未学知识)

  1. 阅读 MDN 函数指南,重点学习"函数作用域"(作用域是常见难点)
  2. 学习返回值详解
  3. 阅读 JavaScript.info 函数基础,了解"默认参数"新特性(跳过"函数注释"部分和结尾涉及循环的任务)
  4. 学习函数表达式箭头函数基础(箭头函数了解即可)
  5. 了解 JavaScript 调用栈,理解链式调用中的 return 机制(不必完全掌握)

编码实践(在 HTML 文件 <script> 标签中编写,使用 console.log 测试):

  1. 函数 add7:接收一个数字,返回该数字加 7 的结果
  2. 函数 multiply:接收两个数字,返回它们的乘积
  3. 函数 capitalize:接收字符串,返回首字母大写的版本(需处理小写/UPPERCASE/混合大小写)
  4. 函数 lastLetter:接收字符串,返回最后一个字母
    • 示例:lastLetter("abcd") 返回 "d"

知识检查

以下问题帮助您反思关键概念(点击问题可回顾内容,无需记忆):

补充资源

选读内容:

问题解决

简介

在深入学习 JavaScript 的精妙特性前,我们需要先探讨问题解决——这是开发者最重要的核心能力。

问题解决是软件开发者的本质工作。编程语言和工具只是辅助这项基本技能的次要因素。

V. Anton Spraul 在其著作《像程序员一样思考》中对编程中的问题解决如此定义:

问题解决是编写一个原创程序,执行特定任务集并满足所有既定约束条件。

任务范围涵盖从小型编码练习到构建 Facebook 这样的社交网络或 Google 这样的搜索引擎。每个问题都有其约束条件,例如性能与可扩展性在编码练习中可能不重要,但在 Google 这类日处理数十亿查询的应用中至关重要。

新手程序员常发现问题解决是最难培养的技能。许多初学者能轻松掌握语法和编程概念,但在独立编码时却对着空白编辑器无从下手。

提升问题解决能力的最佳途径是通过大量编程实践积累经验。练习越多,解决实际问题的准备就越充分。

本节课程将带您了解问题解决流程中的几种实用技巧。

课程概述

本节概述了您将在本课中学习的主题:

  • 解释问题解决流程的三个步骤
  • 理解伪代码的概念并应用于问题解决
  • 学会将复杂问题分解为子问题

理解问题

问题解决的第一步是准确理解问题本质。若未理解问题,您将无法判断解决方案是否正确,并可能在错误方案上浪费大量时间。

为明确理解问题:

  • 在纸上写下问题
  • 用通俗语言重述直至清晰
  • 必要时绘制示意图
    当能用通俗语言向他人解释问题时,您才算真正理解它

规划阶段

明确目标后,切勿直接编码。先规划解决方案:

  • 程序是否有用户界面?如何设计?包含哪些功能?纸上草图规划
  • 程序需要哪些输入?用户输入还是外部数据源?
  • 期望输出是什么?
  • 根据输入,实现期望输出需要哪些步骤?

最后的问题是编写解决问题的算法。算法可视为解决问题的"食谱",用伪代码定义计算机需执行的步骤。

伪代码

伪代码是用自然语言而非编程语言描述程序逻辑。它帮助您放慢节奏,逐步思考程序解决问题的流程。

示例:打印所有小于输入数字的伪代码

当用户输入数字时
初始化计数器变量并设为零
当计数器小于用户输入数字时:
    计数器加一
    打印计数器值

这只是基础示例,后续练习将包含更多伪代码案例。

分而治之

规划阶段应已识别出大问题的若干子问题。算法中的每个步骤都是子问题。选择最简单或最小的子问题开始编码。

需注意:

  • 您可能无法预知所有步骤,算法可能不完整——这很正常
  • 解决一个子问题常会揭示下一个待解子问题
  • 即使已知后续问题,先解决当前子问题也会简化流程

许多初学者试图一次性解决大问题——切勿如此。复杂问题会使您陷入困境。将问题分解为更易解决的子问题才是上策。分解是应对复杂性的主要方式,使问题更易解决和理解。

简言之:分解大问题,逐个解决小问题,最终解决大问题。

Fizz Buzz 实战演示

通过解决 Fizz Buzz 问题演示此流程

理解问题

编写程序:接收用户输入,打印从 1 到输入数字的所有数字。但 3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数则打印 FizzBuzz

通过重述明确需求:
编写程序让用户输入数字,打印 1 到该数字间的所有数。若数字能被 3 整除则打印 Fizz,能被 5 整除则打印 Buzz,同时被 3 和 5 整除则打印 FizzBuzz

规划

  • 交互界面:控制台程序,无需界面,用户通过弹窗输入数字
  • 输入来源:用户通过提示框输入数字
  • 期望输出:1 到输入数字的列表,其中:
    • 被 3 整除的数字输出 Fizz
    • 被 5 整除的数字输出 Buzz
    • 同时被 3 和 5 整除的数字输出 FizzBuzz

编写伪代码

实现期望输出的步骤:

当用户输入数字时
从 1 循环到输入数字
  如果当前数字能被 3 整除则打印 "Fizz"
  如果当前数字能被 5 整除则打印 "Buzz"
  如果当前数字同时被 3 和 5 整除则打印 "FizzBuzz"
  否则打印当前数字

分而治之

从伪代码可见,第一个子问题是获取用户输入。先解决此问题并通过打印输入值验证:

使用 JavaScript 的 prompt 方法:

let answer = parseInt(prompt("请输入要进行 FizzBuzz 的最大数字: "));

此代码创建弹窗获取用户输入,并将数值存储在变量 answer 中。parseInt 确保输入转为数字类型。

接下来解决第二个子问题:"从 1 循环到输入数字"。JavaScript 有多种循环方式,常见的是 for 循环

let answer = parseInt(prompt("请输入要进行 FizzBuzz 的最大数字: "));

for (let i = 1; i <= answer; i++) {
  console.log(i);
}

此循环:

  1. 初始化变量 i = 1
  2. i <= answer 时继续循环
  3. 每次迭代 i 增加 1
    用户输入 10 时,控制台将打印数字 1-10

现在解决第三个子问题:如果当前数字能被 3 整除则打印 Fizz

let answer = parseInt(prompt("请输入要进行 FizzBuzz 的最大数字: "));

for (let i = 1; i <= answer; i++) {
  if (i % 3 === 0) {
    console.log("Fizz");
  } else {
    console.log(i);
  }
}

使用取模运算符 % 检查整除性(余数为 0 表示可整除)。用户输入 10 时输出:

1
2
Fizz
4
5
Fizz
7
8
Fizz
10

解决第四个子问题:如果当前数字能被 5 整除则打印 Buzz

let answer = parseInt(prompt("请输入要进行 FizzBuzz 的最大数字: "));

for (let i = 1; i <= answer; i++) {
  if (i % 3 === 0) {
    console.log("Fizz");
  } else if (i % 5 === 0) {
    console.log("Buzz");
  } else {
    console.log(i);
  }
}

用户输入 10 时输出:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz

最后解决:如果同时被 3 和 5 整除则打印 FizzBuzz

let answer = parseInt(prompt("请输入要进行 FizzBuzz 的最大数字: "));

for (let i = 1; i <= answer; i++) {
  if (i % 3 === 0 && i % 5 === 0) {
    console.log("FizzBuzz");
  } else if (i % 3 === 0) {
    console.log("Fizz");
  } else if (i % 5 === 0) {
    console.log("Buzz");
  } else {
    console.log(i);
  }
}

关键调整:

  • 优先检查同时被 3 和 5 整除的条件
  • 若顺序错误,程序可能在检查 3 的倍数后就跳过后续检查
    用户输入 20 时完整输出:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

练习任务

  1. 阅读 Richard Reis 的 《如何像程序员一样思考——问题解决课程》
  2. 观看 Coding Tech 的 《如何开始像程序员一样思考》(1小时干货,强烈推荐)
  3. 阅读 Built In 的 《伪代码:概念与编写指南》

知识检查

以下问题帮助反思关键概念(点击问题可回顾内容,无需记忆):

补充资源

选读内容:

理解错误

简介

阅读和理解错误信息是开发者的必备技能。许多初学者初看错误信息时会感到"恐惧",觉得难以理解其中的术语。然而,错误信息实际上是知识宝库,它们准确指出了问题所在及解决方法!掌握解析错误信息的能力将使您能够:

  • 有效调试应用程序
  • 获取有意义的帮助
  • 在遇到错误时继续推进开发

课程概述

本节概述您将学习的内容:

  • 列举至少三种 JavaScript 错误类型
  • 识别错误信息中定位问题源头的两个关键部分
  • 掌握研究并解决错误的方法

错误剖析

错误是 JavaScript 内置的对象类型,包含名称/类型和消息。错误信息提供关键信息帮助您:

  1. 定位错误代码位置
  2. 理解错误原因
  3. 找到解决方案

运行以下代码示例:

const a = "Hello";
const b = "World";
console.log(c);

此代码会抛出错误(技术上称为"抛出错误")。错误的第一部分显示错误类型(本例为 ReferenceError):

引用错误(ReferenceError) 在引用当前作用域未声明/初始化的变量时发生。此处错误消息明确说明:c is not defined(c未定义)。

不同的引用错误有不同消息,例如:ReferenceError: can't access lexical declaration 'X' before initialization(无法在初始化前访问词法声明'X')。理解错误类型和消息是解决问题的关键。

错误信息的下一部分显示:

  • 文件名(本例为 script.js
  • 行号(如 script.js:4
  • 列号/字符位置(如 script.js:4:13

点击行号链接可跳转到开发者工具的 Sources 面板查看具体代码。

堆栈追踪(stack trace) 显示错误在应用程序中的传播路径:

const a = 5;
const b = 10;
function add() { return c; }
function print() { add(); }
print();

对应错误信息:

堆栈追踪显示:

  1. add() 函数内 c 未定义(第5行)
  2. add()print() 调用(第9行)
  3. print() 在12行被调用

常见错误类型

语法错误(SyntaxError)

当代码不符合 JavaScript 语法规则时发生:

function helloWorld() {
  console.log "Hello World!" // 缺少括号
}

错误信息:

引用错误(ReferenceError)

引用不存在的变量或拼写错误时发生(前文已详述)

类型错误(TypeError)

在以下情况发生:

  • 传递给函数的参数类型不符
  • 尝试修改不可变值
  • 不恰当使用值

示例:

const str1 = "Hello";
const str2 = "World!";
const message = str1.push(str2); // push()是数组方法

错误信息:

解决:将 .push() 改为字符串方法 .concat()。遇到类型错误时,检查数据类型与操作/方法的兼容性。

错误解决技巧

  1. 拥抱错误信息:错误信息是朋友而非敌人,它精准指出问题所在
  2. 善用搜索引擎:Google 错误信息常能找到 StackOverflow 解决方案或文档说明
  3. 使用调试器(debugger)
    • 设置断点(breakpoints)
    • 查看变量值
    • 逐行执行代码
  4. 控制台工具
    • console.log() 快速调试
    • console.table() 表格展示数据
    • console.trace() 输出堆栈追踪

错误 vs 警告

特征 错误(Error) 警告(Warning)
影响 终止程序执行 不终止程序
严重性 高(必须解决) 低(潜在问题提示)
显示颜色 通常红色 通常黄色
处理优先级 立即解决 适时处理

练习任务

  1. 阅读 MDN 文档:
  2. 实践 "出了什么问题?JavaScript 故障排除"(下载含错误的示例代码)

知识检查

以下问题帮助反思关键概念(点击问题可回顾内容):

补充资源

选读内容:

项目:石头剪刀布游戏

简介

在这个项目中,您将创建一个石头剪刀布游戏,该游戏完全在浏览器控制台中运行。

注意:虽然部分学生解决方案包含按钮和文本界面(称为图形用户界面 GUI),但您将在后续课程中学习 GUI 开发。当前请专注于控制台实现,并确保将代码提交到 GitHub。

问题解决方法

作为首个从头构建的 JavaScript 项目,请牢记问题解决课程的智慧箴言。每个步骤遵循:

  1. 规划或编写伪代码
  2. 编写代码
  3. 测试代码确保功能正常

任务说明

重要:请频繁提交代码!复习提交信息规范

步骤1:建立项目结构

  1. 为项目创建新的 Git 仓库
  2. 创建包含 <script> 标签的空白 HTML 文件
  3. 验证 JavaScript 链接:
    • 在 JavaScript 中写入 console.log("Hello World")
    • 打开网页后检查浏览器控制台是否显示 "Hello World"

最佳实践:在 <script> 标签中链接外部 JavaScript 文件,保持 HTML 文件整洁。游戏完全通过控制台进行,无需在 HTML 中添加额外代码。

步骤2:实现计算机选择逻辑

创建 getComputerChoice 函数,随机返回 "rock"、"paper" 或 "scissors":

function getComputerChoice() {
  const choices = ["rock", "paper", "scissors"];
  const randomIndex = Math.floor(Math.random() * 3);
  return choices[randomIndex];
}
// 测试代码
console.log("计算机选择:", getComputerChoice());

提示:使用 Math.random() 生成 0-1 的随机数实现选择逻辑(无需数组也可实现)

步骤3:实现玩家选择逻辑

创建 getHumanChoice 函数,通过弹窗获取玩家选择:

function getHumanChoice() {
  return prompt("请选择:rock, paper 或 scissors").toLowerCase();
}
// 测试代码
console.log("玩家选择:", getHumanChoice());

注意:暂不处理无效输入,假设玩家始终输入正确选项

步骤4:声明分数变量

在全局作用域声明玩家和计算机的分数变量:

let humanScore = 0;
let computerScore = 0;

步骤5:实现单轮游戏逻辑

创建 playRound 函数:

function playRound(humanChoice, computerChoice) {
  // 统一转换为小写
  humanChoice = humanChoice.toLowerCase();
  
  // 胜负判定逻辑
  if (humanChoice === computerChoice) {
    console.log(`平局!双方都选择了 ${humanChoice}`);
    return;
  }
  
  const winConditions = {
    rock: "scissors",
    paper: "rock",
    scissors: "paper"
  };
  
  if (winConditions[humanChoice] === computerChoice) {
    humanScore++;
    console.log(`你赢了!${humanChoice} 击败 ${computerChoice}`);
  } else {
    computerScore++;
    console.log(`你输了!${computerChoice} 击败 ${humanChoice}`);
  }
}

// 测试单轮
const humanSelection = getHumanChoice();
const computerSelection = getComputerChoice();
playRound(humanSelection, computerSelection);

步骤6:实现完整游戏逻辑

创建 playGame 函数组织5轮游戏:

function playGame() {
  // 重置分数
  let humanScore = 0;
  let computerScore = 0;
  
  // 进行5轮游戏
  for (let round = 1; round <= 5; round++) {
    console.log(`\n=== 第 ${round} 轮 ===`);
    const humanChoice = getHumanChoice();
    const computerChoice = getComputerChoice();
    playRound(humanChoice, computerChoice);
  }
  
  // 最终结果
  console.log("\n=== 游戏结束 ===");
  console.log(`最终比分:玩家 ${humanScore} - 计算机 ${computerScore}`);
  
  if (humanScore > computerScore) {
    console.log("恭喜!你赢得了游戏!");
  } else if (humanScore < computerScore) {
    console.log("很遗憾,计算机获胜!");
  } else {
    console.log("平局!势均力敌!");
  }
}

// 启动游戏
playGame();

项目建议

  • 避免添加额外功能,保持项目简洁
  • 将创意想法留到后续作品集项目
  • 阅读成为TOP成功案例了解学习心态与作品集策略

编写整洁代码

引言

你可能以为程序员大部分时间都在写代码。但现实中,大量时间其实花在阅读代码上——包括团队成员写的代码、已离职同事的代码,甚至是自己两周前写但已经记不清的代码。

不必把这些原则视为必须立即掌握的东西。每个人都会写出混乱的代码,专业程序员也不例外。我们的目标是提供一些指南,帮助你在编码过程中逐步提升代码可读性。写得越多,你的代码在可读性等各方面都会变得更好。

尝试将这些理念融入你的编码思维,但不必苛求自己每次都写出优雅完美的代码。专注持续改进,而非追求一步到位。

现在,让我们开始吧!

课程概览

本节概述你将学到的内容:

  • 区分难读代码与易读代码
  • 运用编程原则使代码更整洁
  • 编写优质注释

什么是整洁代码?

请看以下示例:

示例 A:

const x= function (z){
   const w = "Hello ";
return w +  z

 }

x("John");

示例 B:

const generateUserGreeting = function (name) {
  const greeting = "Hello ";
  return greeting + name;
};

generateUserGreeting("John");

哪个示例更易读?显然后者更有意义。有趣的是,两个函数功能完全相同(实现方式也完全一致!),且都是有效代码。但第二个版本可读性高得多,为什么?

第一个示例使用了单字母变量,缩进和空格也不一致,导致代码混乱难懂。想象你与写这段代码的人合作:你需要多久才能理解其含义继续工作?或者这是你自己写的代码但已完全遗忘?无论哪种情况,你最终都能理解,但过程绝不愉快。

示例 B 展示了整洁代码的特点:函数和变量命名清晰,缩进和空格遵循一致逻辑。即使不了解每个部分的作用,也能轻松推测代码意图。

循环或回调函数中可使用单字符变量名,但其他场景应避免。

关于驼峰命名法(camelCase)

驼峰命名法允许无空格或标点连接多个单词。采用 camelCase 时(如 setTimeout),首单词全小写,后续单词首字母大写。

本课程中大多数变量和函数将使用 camelCase。虽然并非所有语言都采用此规范,但在 JavaScript 中非常常见,值得学习。

规范只是规范

本课程虽提供代码优化示例,但实际工作中不同组织可能有不同规范。没有绝对标准。

关键在于所有方法都服务于共同目标——提升代码可读性和可维护性。在需要遵循特定规范前,保持自身规范的一致性即可。

函数与变量命名

第一个示例已说明有意义命名的重要性。下面进一步解析优质命名要素。

好名称应具描述性

在优质示例中,变量 greeting 与参数 name 拼接,函数名 generateUserGreeting 准确描述其功能。简洁易懂。

反观劣质示例:函数名为 x,变量是 zw。糟糕透顶。

保持词汇一致性

同类变量应采用统一命名体系。参考游戏代码示例:

// 一致命名
function getPlayerScore();
function getPlayerName();
function getPlayerTag();

均采用"获取XX"的命名结构。再看反面案例:

// 不一致命名
function getUserScore();
function fetchPlayerName();
function retrievePlayer1Tag();

不一致示例使用了三个不同动词。虽然含义相似,但容易让人误解存在特殊区别(例如"get"与"fetch"在特定语境下可能不同)。此外,UserPlayerPlayer1 有何区别?若无实质差异,应统一使用如 Player。一致性带来可预测性。

变量代表"事物"(字符串/数字等),建议以名词或形容词开头。函数代表动作,建议以动词开头。

// 推荐
const numberOfThings = 10;      // 数量
const myName = "Thor";          // 名称
const selected = true;          // 状态

// 不推荐(以动词开头易与函数混淆)
const getCount = 10;            // 像函数名
const showNorseGods = ["Odin", "Thor", "Loki"];  // 像函数名

// 推荐(动词开头)
function getCount() {
  return numberOfThings;
}

// 不推荐(函数名未体现动作)
function myName() {
  return "Thor";
}

使用可搜索且直观的名称

有时会忍不住使用"魔法值"(magic values)——即裸露的数字或字符串。看示例:

setTimeout(stopTimer, 3600000);

问题很明显:魔法数字 3600000 代表什么?倒计时多久会触发 stopTimer?即使知道 JavaScript 以毫秒计时,也需要计算器或谷歌才能换算成分钟。

引入描述性变量提升可读性:

const ONE_HOUR = 3600000; // 也可写作 60 * 60 * 1000

setTimeout(stopTimer, ONE_HOUR);

这样是否清晰得多?变量名直观描述,读者无需自行计算。

你或许疑惑为何此处使用全大写命名(之前推荐 camelCase)。这是表示绝对常量的约定,尤其适用于代表固定概念(如时间单位)。我们知道一小时的毫秒数永不改变,故适用此约定。需注意这只是规范,并非绝对标准。

缩进与行宽

现在进入更"有争议"的话题(关于空格派与制表符派的战争是个经典梗)。

关键在于一致性。选择一种缩进方式并坚持使用。不同 JavaScript 风格指南有不同建议,并无绝对优劣。后续课程将详细探讨风格指南及相关工具。

行宽限制

各风格指南对此建议不同,但几乎所有指南都建议限制代码行长度。

通常,手动将超过 80 字符的行断开会显著提升可读性。多数代码编辑器会显示阈值线。手动换行时,建议在运算符或逗号后断开。

续行有多种格式化方式,例如:

// 超长行示例
let reallyReallyLongLine = something + somethingElse + anotherThing + howManyTacos + oneMoreReallyLongThing;

// 格式方案A
let reallyReallyLongLine =
  something +
  somethingElse +
  anotherThing +
  howManyTacos +
  oneMoreReallyLongThing;

// 格式方案B
let anotherReallyReallyLongLine = something + somethingElse + anotherThing +
                                  howManyTacos + oneMoreReallyLongThing;

不同格式无绝对对错,依个人偏好选择并保持一致性即可。

分号使用

JavaScript 中分号基本可选,因为解释器会自动补全。但某些情况下可能出错导致 bug,建议养成添加分号习惯。

无论是否添加,保持一致性最重要。

关于注释

注释虽好,但使用不当反受其害。尤其对编程新手,容易过度注释解释每行代码。这通常不是好习惯。下面分析常见注释误区及原因。

该用 git 时别用注释

可能想用注释记录代码变更:

/**
 * 2023-01-10: 移除导致混淆的冗余代码 (RM)
 * 2023-03-05: 简化代码 (JP)
 * 2023-05-15: 移除引发生产环境 bug 的函数 (LI)
 * 2023-06-22: 新增合并值的函数 (JR)
 */

问题在于你本就有追踪变更的工具——git!维护这些注释会成为负担,且信息不完整。文件也会包含无关内容。

使用 git 可让这些信息整齐存储在仓库中,通过 git log 轻松查阅。

同理,不再使用的代码也应直接删除。测试时临时注释可以接受,但确定无用后务必删除。避免残留如下代码:

theFunctionInUse();
// oldFunction();
// evenOlderUselessFunction();
// whyAmIEvenHereImAncient();

解释原因而非过程

优质注释应避免用伪代码重复代码逻辑,而应解释代码背后的原因。有时甚至完全不需要注释!

假设要从方括号包裹的字符串中提取内容:

// 提取文本的函数
function extractText(s) {
  // 返回"["之后、"]"之前的内容
  return s.substring(s.indexOf("[") + 1, s.indexOf("]"));
}

这些注释只是复述代码内容。稍好的注释可解释代码动机:

// 提取方括号内的文本(不含括号)
function extractText(s) {
  return s.substring(s.indexOf("[") + 1, s.indexOf("]"));
}

但通常我们可以让代码自解释而无需注释:

function extractTextWithinBrackets(text) {
  const bracketTextStart = text.indexOf("[") + 1;
  const bracketTextEnd = text.indexOf("]");
  return text.substring(bracketTextStart, bracketTextEnd);
}

第一例的注释重复了代码功能,但阅读代码本身即可理解,注释冗余。

第二例注释明确了函数目的:从字符串提取方括号间文本(而非笼统的"提取文本")。这很有用,但还能更好。

第三例完全无需注释,描述性的函数和变量名已说明一切。很优雅吧?

但并非好代码就要避免注释。 看一个注释发挥价值的例子:

function calculateBMI(height, weight) {
  // BMI公式 = 体重(kg) / 身高(m)^2
  const heightInMeters = height / 100;
  const bmi = weight / (heightInMeters * heightInMeters);
  return bmi;
}

此注释用自然语言解释 BMI 计算逻辑,帮助读者理解为何转换身高单位及后续计算。命名虽已较清晰,注释仍提供额外价值。

许多情况下,精准注释极其珍贵。它们可能解释反直觉代码的必要性,或说明为何在此处调用特定函数。课后阅读材料将深入探讨。

结语

学习完这些理念后,请重温开篇提醒:勿追求完美代码,这只会导致挫败感。写出"意大利面条式代码"不可避免——每个人都会经历。只需牢记这些原则,保持耐心,你的代码将逐渐变得整洁。

编写整洁代码是持续精进的过程,甚至在你完成Odin项目后仍将继续。本课程旨在为你开启这段旅程。

卓越代码源于经验,经验来自不那么卓越的代码。

课后任务

  1. 阅读保持代码整洁的十大原则获取实用技巧
  2. 深入理解注释实践:

知识自测

以下问题帮助你反思核心概念(无需记忆):

扩展资源

循环与数组

引言

编程的挑战之一在于重复操作。虽然少量手动重复可以接受,但有时我们需要优化重复指令以提升可读性。另一挑战是处理大量数据。例如,如何存储全班学生姓名?为每个姓名创建变量不仅繁琐低效,后续管理和更新也很困难。

幸运的是,这些问题都有解决方案。本课你将学习:

  • 循环:控制结构,允许重复执行代码块
  • 数组:数据结构,可在单个变量中存储多个值,便于组织操作大量数据
  • 测试驱动开发(TDD):先写测试再写代码的开发实践

循环与数组常结合使用,便于对数组元素执行统一操作。

课程概览

本节概述你将学到的内容:

  • 循环的应用
  • 数组的使用
  • 数组方法(mapfilterreduce
  • TDD实践练习

循环

计算机永不疲倦且速度极快!因此特别适合解决需要重复计算的问题。计算机可在数秒内执行数千甚至数百万次操作,而人类可能需要数小时(实际速度取决于计算复杂度与计算机性能)。

使用循环可重复执行指令集:

  1. 阅读 MDN循环指南(含底部实践练习)
  2. 学习 JavaScript.info循环教程(完成章节练习)

注:部分示例使用数组(即项目列表),下节将详细讲解

数组

字符串和数字是基础构件,但复杂脚本需处理大量数据。数组正是存储有序集合(字符串/数字等)的理想结构。某些数组方法功能类似循环,有时数组方法可更优雅地完成任务。

学习资源:

  1. JavaScript数组速成课(6分钟概览)
  2. JavaScript.info数组基础(无需做练习)
  3. JavaScript.info数组方法详解(稍后处理练习)
  4. 收藏 MDN数组文档(无需记忆,随用随查)

数组的魔法

数组不仅是高效存储工具,其方法还能实现强大数据操作。以下通过sumOfTripledEvens函数示例说明:

// 功能:1.筛选偶数 2.数值乘3 3.结果求和
function sumOfTripledEvens(array) {
  let sum = 0;
  for (let i = 0; i < array.length; i++) {
    if (array[i] % 2 === 0) {          // 步骤1:判断偶数
      const tripleEvenNumber = array[i] * 3; // 步骤2:数值乘3
      sum += tripleEvenNumber;          // 步骤3:累加结果
    }
  }
  return sum;
}

使用数组方法可使代码更简洁优雅:

map方法

对每个元素执行转换:

// 所有元素加1
const arr = [1, 2, 3, 4, 5];
const mappedArr = arr.map(num => num + 1);
console.log(mappedArr); // [2, 3, 4, 5, 6] (原数组不变)

filter方法

筛选符合条件的元素:

// 筛选奇数
const arr = [1, 2, 3, 4, 5];
const oddNums = arr.filter(num => num % 2 !== 0);
console.log(oddNums); // [1, 3, 5] (原数组不变)

reduce方法

累积计算结果:

// 计算乘积(初始值1)
const arr = [1, 2, 3, 4, 5];
const product = arr.reduce((total, current) => total * current, 1);
console.log(product); // 120 (原数组不变)

方法图解

视觉解析:filter(筛选食材)→ map(加工处理)→ reduce(组合成品)

实践挑战

使用数组方法重构sumOfTripledEvens函数。完成后可参考下方答案:

解决方案
function sumOfTripledEvens(array) {
  return array
    .filter((num) => num % 2 === 0)
    .map((num) => num * 3)
    .reduce((acc, curr) => acc + curr);
}

测试驱动开发

测试驱动开发(TDD)是开发领域经常听到的术语。它指的是在实际编写代码之前,先编写描述代码应如何工作的自动化测试的实践。例如,如果要编写一个数字相加的函数,应该先编写使用该函数并提供预期输出的测试。在编写代码前测试会失败,当测试通过时,就能确认代码正确运行。

在许多方面,TDD 比无测试编写代码高效得多。如果不对加法函数进行测试,就需要反复手动运行代码,输入不同数字进行验证... 对于简单的 add(2, 2) 这不算什么,但想象一下对更复杂的功能(如检测井字棋获胜状态)进行测试:game_win(["o", null,"x",null,"x",null,"x", "o", "o"])。如果不采用 TDD,可能需要自己玩多局游戏来测试函数是否正确工作!

我们将在课程后期教授编写这些测试的技巧。现在,请继续完成之前的 JavaScript 练习。

作业要求

  1. 前往 JavaScript.info 数组方法文章末尾的数组方法练习,完成以下题目:

    • 将 border-left-width 转换为 borderLeftWidth
    • 过滤范围
    • 原地过滤范围
    • 降序排序
    • 复制并排序数组
    • 数组洗牌
    • 过滤唯一数组成员
  2. Fork 并克隆 Wes Bos 的 JavaScript30 仓库。观看以下两个视频练习时,请打开对应的 index-START.html 文件:

  3. 返回基础 JavaScript 练习仓库,按顺序完成以下练习(开始前请阅读各练习的 README):

    • 06_repeatString(重复字符串)
    • 07_reverseString(反转字符串)
    • 08_removeFromArray(从数组移除)
    • 09_sumAll(求和运算)
    • 10_leapYears(闰年判断)
    • 11_tempConversion(温度转换)

    注意:各练习的 solution 文件夹包含参考答案

知识检查

以下问题帮助回顾本课核心概念,如无法回答可点击链接复习(无需死记硬背):

补充资源

本节包含相关扩展内容(可选):

  • 当前课程暂无补充资源,欢迎贡献内容扩充本版块

DOM 操作与事件

简介

JavaScript 最独特且强大的能力之一就是操作 DOM 的能力。那么什么是 DOM?我们如何改变它?让我们深入探索...

课程概览

本节概述了您将在本课中学到的主题:

  • 解释 DOM 与网页的关系
  • 解释"节点"与"元素"的区别
  • 说明如何使用"选择器"定位节点
  • 掌握查找、添加、删除和修改 DOM 节点的基本方法
  • 解释"NodeList"与"节点数组"的区别
  • 说明"事件冒泡"机制及其工作原理

文档对象模型(DOM)

DOM(文档对象模型)是网页内容的树状表示 - 由不同关系的"节点"组成的树结构,这些关系取决于它们在 HTML 文档中的排列方式。节点有多种类型,本课我们重点关注用于操作 DOM 的"元素"节点。

<div id="container">
  <div class="display"></div>
  <div class="controls"></div>
</div>

在上例中,<div class="display"></div><div id="container"></div> 的"子元素",也是 <div class="controls"></div> 的"兄弟元素"。可将其视为家谱:<div id="container"></div>父元素,其子元素位于下一层级,各自位于自己的"分支"上。

使用选择器定位节点

操作 DOM 时,使用"选择器"定位目标节点。可结合 CSS 样式选择器和关系属性进行定位。以上例中的 <div class="display"></div> 为例,可使用以下选择器:

  • div.display
  • .display
  • #container > .display
  • div#container > div.display

也可以使用节点特有的关系选择器(如 firstElementChildlastElementChild 等):

// 选择 #container div(暂时忽略语法)
const container = document.querySelector("#container");

// 选择 #container 的第一个子元素 => .display
const display = container.firstElementChild;
console.log(display);  // <div class="display"></div>

// 选择 .controls div
const controls = document.querySelector(".controls");

// 选择前一个兄弟元素 => .display
const display = controls.previousElementSibling;
console.log(display); // <div class="display"></div>

这是基于节点与周围节点的关系来定位特定节点的方法。

DOM 方法

当浏览器解析 HTML 代码时,会将其转换为 DOM。主要区别在于这些节点是具有众多属性和方法的 JavaScript 对象。这些属性和方法是我们用 JavaScript 操作网页的主要工具。

查询选择器

  • element.querySelector(selector) - 返回与选择器匹配的第一个元素的引用
  • element.querySelectorAll(selectors) - 返回包含所有匹配元素的"NodeList"

querySelectorAll 的返回值不是数组。它看起来像数组,行为也类似数组,但实际上是"NodeList"。关键区别在于 NodeList 缺少一些数组方法。解决方案是使用 Array.from()展开运算符将其转换为数组。

元素创建

  • document.createElement(tagName, [options]) - 创建指定标签类型的新元素
const div = document.createElement("div");

此函数不会直接将新元素放入 DOM - 而是在内存中创建。这样您可以在元素添加到页面前进行操作(添加样式、类、ID、文本等)。可使用以下方法将元素放入 DOM。

追加元素

  • parentNode.appendChild(childNode) - 将子节点添加为父节点的最后一个子元素
  • parentNode.insertBefore(newNode, referenceNode) - 在父节点的参考节点前插入新节点

移除元素

  • parentNode.removeChild(child) - 从 DOM 中移除父节点的子元素,并返回子元素的引用

修改元素

获得元素引用后,可修改其属性:添加/移除/修改属性、更改类、添加内联样式等。

// 创建新 div 并存储在变量 'div' 中
const div = document.createElement("div");

添加内联样式

// 为 div 添加指定样式规则
div.style.color = "blue";

// 添加多个样式规则
div.style.cssText = "color: blue; background: white;";

// 添加多个样式规则
div.setAttribute("style", "color: blue; background: white;");

访问带连字符的 CSS 属性(如 background-color)时:

  • 使用驼峰式点表示法:div.style.backgroundColor
  • 或使用带引号的字符串:div.style["background-color"]div.style["backgroundColor"]

编辑属性

div.setAttribute("id", "theDiv"); // 设置/更新 ID
div.getAttribute("id");           // 获取属性值
div.removeAttribute("id");        // 移除属性

查看 MDN 的 HTML 属性部分了解更多可用属性。

操作类

div.classList.add("new");       // 添加类
div.classList.remove("new");    // 移除类
div.classList.toggle("active"); // 切换类(存在则移除,不存在则添加)

切换 CSS 类通常比添加/移除内联 CSS 更规范(且更简洁)。

添加文本内容

div.textContent = "Hello World!"; // 创建文本节点并插入

添加 HTML 内容

div.innerHTML = "<span>Hello World!</span>"; // 在 div 内渲染 HTML

注意:添加文本首选 textContent 而非 innerHTML,后者应谨慎使用以避免安全风险。了解使用 innerHTML 的风险,请观看防止最常见 XSS 攻击的视频

练习示例

<!-- HTML 文件 -->
<body>
  <h1>网页标题</h1>
  <div id="container"></div>
</body>
// JavaScript 文件
const container = document.querySelector("#container");

const content = document.createElement("div");
content.classList.add("content");
content.textContent = "这是荣耀的文本内容!";

container.appendChild(content);

运行 JavaScript 后,DOM 树将变为:

<body>
  <h1>网页标题</h1>
  <div id="container">
    <div class="content">这是荣耀的文本内容!</div>
  </div>
</body>

重要提示:JavaScript 修改的是 DOM 而非 HTML 文件。为确保 DOM 操作生效:

  • 将 JavaScript 放在 HTML 文件底部
  • 或在 <head> 中使用 defer 属性:
    <script src="js文件.js" defer></script>
    了解更多:script 的 defer 属性

动手练习

将上述示例复制到您的文件中。确保整个 HTML 结构完整并正确链接 JavaScript 文件。验证无误后,仅使用 JavaScript 和上述 DOM 方法向容器添加以下元素:

  1. 红色文本的 <p>:"Hey I'm red!"
  2. 蓝色文本的 <h3>:"I'm a blue h3!"
  3. 带黑色边框和粉色背景的 <div>,内含:
    • <h1>:"I'm in a div"
    • <p>:"ME TOO!"
      提示:创建 div 后,先添加子元素再将其加入容器

事件处理

掌握了 DOM 操作后,下一步是学习如何动态地实现交互!事件让您的页面产生神奇变化。事件是网页上发生的动作(如鼠标点击或按键)。通过 JavaScript,我们可以让网页监听并响应这些事件。

有三种主要方法实现事件处理:

方法 1:HTML 属性

<button onclick="alert('Hello World')">点击我</button>

缺点:JS 与 HTML 混杂,且每个元素只能设置一个 onclick 属性。

方法 2:DOM 属性

<button id="btn">点击我</button>
const btn = document.querySelector("#btn");
btn.onclick = () => alert("Hello World");

改进:JS 与 HTML 分离,但仍受限于单个 onclick 属性。

方法 3:事件监听器(推荐)

<button id="btn">也点我</button>
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
  alert("Hello World");
});

优势:关注点分离,支持多个事件监听器,更灵活强大。

使用命名函数

function alertFunction() {
  alert("恭喜!你做到了!");
}

// 方法 2
btn.onclick = alertFunction;

// 方法 3
btn.addEventListener("click", alertFunction);

命名函数可大幅简化代码,特别适用于多处复用的功能。

事件对象

btn.addEventListener("click", function (e) {
  console.log(e);            // 事件对象
  console.log(e.target);     // 目标元素
  e.target.style.background = "blue"; // 修改目标元素样式
});

参数 e 包含引用事件的事件对象,可访问触发事件的目标元素、按键等有用信息。

批量添加监听器

<div id="container">
  <button id="one">按钮1</button>
  <button id="two">按钮2</button>
  <button id="three">按钮3</button>
</div>
const buttons = document.querySelectorAll("button");

buttons.forEach((button) => {
  button.addEventListener("click", () => {
    alert(button.id); // 显示按钮 ID
  });
});

常用事件类型:

  • click(点击)
  • dblclick(双击)
  • keydown(键按下)
  • keyup(键释放)

完整事件列表参考:W3Schools JavaScript 事件参考

任务清单

  1. 完成 MDN 的 DOM 操作实践章节
  2. 阅读 JavaScript 教程的 DOM 事件系列:

知识检查

  • 什么是 DOM?
  • 如何定位目标节点?
  • 如何在 DOM 中创建元素?
  • 如何添加元素到 DOM?
  • 如何从 DOM 移除元素?
  • 如何修改 DOM 元素?
  • 添加文本时应使用 textContent 还是 innerHTML?为什么?
  • 操作 DOM 节点时,JavaScript 标签应放在 HTML 的什么位置?
  • 事件和监听器如何工作?
  • 三种事件处理方式是什么?
  • 为什么事件监听器是首选方式?
  • 在监听器中使用命名函数有什么好处?
  • 如何批量添加节点监听器?
  • querySelectorquerySelectorAll 返回值有何区别?
  • NodeList 包含什么内容?
  • 解释"捕获"与"冒泡"的区别

扩展资源

重构石头剪刀布游戏

引言

掌握 DOM 操作后,是时候为石头剪刀布游戏添加用户界面了。在修改项目前,需要学习 Git 的分支概念,这样可以在不破坏现有代码的前提下进行开发。

Git 分支允许仓库同时保存多个"平行宇宙"版本的文件。实际上,从首次提交开始您就已经在使用分支了!在配置 Git 课程中运行 git config --global init.defaultBranch main 时,您设置了仓库的默认分支名称(当前标准为 main)。

如同树枝从树干分叉(因此得名),项目所有分支都源于主干(main 分支)或其他分支。在特定分支提交的更改仅存在于分支,其他分支保持原样。

这意味着您可以将 main 分支作为仅存放已完成功能的稳定版本,通过专用分支(称为功能分支)添加新功能。

课程概览

本节概述您将学习的主题:

  • 如何创建新分支
  • 如何合并分支
  • 分支的最佳实践与应用场景

使用分支

您可以使用命令 git branch <分支名> 来创建新分支。然后,您可以使用 git checkout <分支名> 切换到您的新分支。您也可以通过使用带有 -b 标志的 checkout 命令,以 git checkout -b <分支名> 的形式,一次性创建新分支并切换到它。

您可以使用不带任何其他参数的 git branch 命令查看您当前所有的分支。您当前所在的分支会用一个星号(*)在终端中标出。如果您想从任何其他分支切换回 main 分支,您可以使用 git checkout main 命令,就像切换到其他分支一样。

当您完成在特性分支上的工作,并准备将您在该分支上所做的提交记录合并到您的 main 分支时,您将需要执行一个称为 合并(merge) 的操作。

合并操作通过使用命令 git merge <分支名> 来完成,该命令会将您在 分支名 中所提交的更改,添加到您当前所在的分支中。您可以在下面的示意图中看到一个创建 develop 分支、向其提交更改、然后将其合并到 main 分支的示例。

有时,同一个文件中的相同行会被两个不同的分支修改。当这种情况发生时,您在尝试合并这些分支时将会遇到合并冲突(merge conflict)。为了完成分支的合并,您必须先解决冲突,这将在未来的课程中介绍。

当您不再需要一个分支时,如果该分支已经被合并到 main 分支中,可以使用 git branch -d <分支名> 将其删除;如果它尚未被合并,则使用 git branch -D <分支名> 删除。通常,在完成分支的工作后,您会希望删除它们,否则它们会堆积起来,在您需要时更难找到所需的分支。

代码共享

分支的另一个重要用途是共享代码:
当新功能存在难以解决的 BUG 时,不希望将问题代码提交到主分支或功能分支。此时可:

  1. 创建临时分支:git checkout -b temp-fix
  2. 提交问题代码
  3. 推送到 GitHub:git push origin temp-fix
    这样即可在不污染主分支的前提下分享代码寻求帮助。

作业要求

  1. 在石头剪刀布仓库新建分支

    1. 创建 UI 分支:git checkout -b rps-ui
    2. 推送新分支:git push origin rps-ui(GitHub 仓库将显示两个分支)
    3. 确认当前分支:git branch(带*号分支为当前分支)
      注意:在此分支的操作与主分支相同,但推送时使用:git push origin rps-ui
  2. 实现游戏 UI

    1. 移除原"五局三胜"逻辑

    2. 创建三个按钮(石头/布/剪刀),添加点击事件监听器:

      button.addEventListener("click", () => playRound("剪刀")); // 调用 playRound 函数
      
    3. 添加结果显示区域(如 <div id="results">),将 console.log 替换为 DOM 操作

    4. 显示实时比分,当某方达 5 分时宣布胜者

    5. 重构旧代码是程序员日常,放心修改!

  3. 提交分支更改
    完成 UI 后:git status 检查变更 → 提交到 rps-ui 分支

  4. 合并分支到主分支

    1. 切换主分支:git checkout main
    2. 合并分支:git merge rps-ui
    3. 验证合并:git log(查看功能分支提交记录)
    4. 推送主分支:git push origin main(GitHub 将更新)
    5. 清理分支:
      • 本地删除:git branch -d rps-ui
      • 远程删除:git push origin --delete rps-ui
  5. 发布游戏
    通过 GitHub Pages 发布,在项目页添加在线预览链接

知识检查

  • Git 中的分支是什么?
  • 如何创建新分支?
  • 如何将分支合并到主分支?
  • 分支的一个应用场景?

扩展资源

项目:蚀刻素描 (Etch-a-Sketch)

简介

在本项目中,您将创建一个相当酷炫的玩具来锻炼您的 DOM 操作技能。您将构建一个介于素描板和蚀刻素描 (Etch-A-Sketch) 之间的浏览器版本。

这个项目不应该很简单。您可能需要经常通过 Google 查找要使用的正确 JavaScript 方法和 CSS——事实上,这正是重点!您可以使用已经学过的工具来构建它。如果您确定需要用到我们尚未涵盖的知识,网络上也有大量资源可供学习。我们将引导您完成基本步骤,但具体实现将由您来完成。

如果您完全卡住了,请访问 我们的 Discord 服务器 寻求帮助。会有人为您指明正确方向的。

任务要求

不要忘记尽早并经常提交(commit)!您可以参考 提交信息课程

  1. 设置 GitHub 仓库:按照我们关于 设置项目 GitHub 仓库 的说明进行操作。
  2. 创建网格:创建一个包含 16x16 个正方形 div 的网页。
    • 使用 JavaScript 创建这些 div。不要尝试在 HTML 文件中手动复制粘贴来创建它们!
    • 最好将这些网格方块放在一个“容器(container)” div 内。这个 div 可以写在您的 HTML 文件中。
    • 使用 Flexbox 使这些 div 显示为网格(而不是每行只有一个)。尽管名字叫“网格(Grid)”,但请不要尝试研究或使用 CSS Grid,因为它将在基础路径之后的课程中教授。本项目是专门练习 Flexbox 的机会!
    • 注意边框(border)和外边距(margin),因为它们会调整正方形的大小!
    • “天啊,为什么我的网格没被创建出来???” 排查步骤:
      • 您链接了 CSS 样式表吗?
      • 打开浏览器的开发者工具。
      • 检查 JavaScript 控制台中是否有任何错误。
      • 检查您的“元素(Elements)”面板,看看元素是否实际已出现但被隐藏了。
      • 大胆地在您的 JavaScript 中添加 console.log 语句,看看代码是否实际被加载了。
  3. 悬停效果:设置一个“悬停(hover)”效果,以便当您的鼠标经过网格 div 时,它们会改变颜色,像笔一样在网格中留下(像素化的)轨迹。
    • 提示:“悬停(Hovering)”是指鼠标进入 div 开始到离开 div 结束的过程。您可以为这两个事件中的任何一个设置事件监听器作为起点。
    • 有多种方法可以更改 div 的颜色,包括:
      • 向 div 添加一个新的类(class)。
      • 使用 JavaScript 更改 div 的背景颜色(background color)。
  4. 网格重置按钮:在屏幕顶部添加一个按钮,该按钮将向用户弹出一个提示框(popup),询问新网格每边所需的方格数量。输入后,应移除现有网格,并在与之前相同的总空间内(例如,960 像素宽)生成一个新网格,这样您就得到了一个新的素描板。
    • 提示:将用户输入限制在最大 100。方格数量越大,使用的计算机资源越多,可能导致我们想要防止的延迟、冻结或崩溃。
    • 研究 HTML 中的 button 标签以及如何在点击按钮时运行 JavaScript 函数。
    • 同时了解 prompt(提示框)。
    • 您应该能够输入 64,然后弹出一个全新的 64x64 网格,而使用的总像素量不变。
  5. 推送至 GitHub:将您的项目推送到 GitHub!

附加挑战

通过引入一系列修改,改变鼠标交互时方格的行为。

  1. 随机颜色:不是让整个网格中的方格都是同一种颜色,而是在每次交互时随机化方格的 RGB 值。
  2. 渐进变暗:此外,实现一个渐进变暗效果,每次交互使方格的暗度增加 10%。目标是在仅十次交互内使方格达到完全黑色(或完全着色)。

您可以选择完成其中一个或两个挑战,这取决于您。

对象基础

简介

恭喜你坚持到基础课程的最后一课!至此,你已经掌握了 JavaScript 的许多基础知识。在本课中,你将学习对象(Objects)——一种键值对的集合——以及一些更强大且常用的数组方法。

在你还没意识到的时候,你就会更深刻地理解对象和数组的强大之处,以及它们如何成为你 JavaScript 工具包中不可或缺的部分!

课程概览

本节概述了你将在本课中学到的主题。

  • 创建对象 (Creating objects)。
  • 访问对象属性 (Accessing object properties)。
  • 使用多个对象操作符 (Using multiple object operators)。
  • 理解对象和基本数据类型之间的区别 (Understanding the differences between object and primitive data types)。

对象

对象是 JavaScript 语言中非常重要的一部分。虽然大部分情况下,即使不涉及对象,你也能完成简单甚至中级的任务,但任何你将要尝试的实际项目都将会用到对象。JavaScript 中对象的用途可以相对快速地深入,因此目前我们只介绍基础知识。稍后将会有深入的探讨。

  1. JavaScript.info 的这篇关于对象的文章是入门的最佳选择。
  2. MDN 的对象基础教程也不错,如果你需要从另一个角度理解这个主题,也可以查阅它。

对象与基本类型的区别

在课程的前期,你已经学习了基本数据类型 (primitive data types)。现在你已经见过对象数据类型 (object data type),它包括但不限于对象({key: value})、数组 (arrays) 和函数 (functions)。两者之间的主要区别在于,基本类型只能包含单个内容(字符串、数字等)对象数据类型用于存储数据集合和更复杂的实体

除了形式上的区别,还有一些技术差异会影响我们如何使用每种数据类型。当你定义一个基本类型变量时,它会包含所提供信息的副本 (copy)

let data = 42;
// dataCopy 将存储 data 所包含内容的副本,即 42 的副本
let dataCopy = data;

// 这意味着对 dataCopy 的更改不会影响 data
dataCopy = 43;

console.log(data); // 42
console.log(dataCopy); // 43

另一方面,当你定义一个对象变量时,它会包含指向所提供对象的引用 (reference)

// obj 包含一个指向我们右边定义的对象的引用
const obj = { data: 42 };
// objCopy 将包含一个指向 obj 所引用的对象的引用
const objCopy = obj;

// 对 objCopy 的更改会影响到它所引用的那个对象
objCopy.data = 43;

console.log(obj); // { data: 43 }
console.log(objCopy); // { data: 43 }

这种行为对你来说并不陌生。在你上一个项目(Etch-A-Sketch 画板)中,你通过使用引用来更改网格中单元格的样式。让我们以下面这段代码片段为例:

const element = document.querySelector("#container");
element.style.backgroundColor = "red";

我们正在改变(突变,mutate)我们声明的变量 (element),而这个更改却影响了 DOM 中对应的节点。为什么会这样?这是因为我们代码中的节点是 DOM 所使用的节点的引用 (reference)。如果它不是引用,而是像基本数据类型那样的副本 (copy),我们的更改将没有任何效果!因为更改只会发生在我们拥有的本地副本上。

当我们向函数传递参数时,也需要考虑这种行为。让我们以下面的函数为例:

function increaseCounterObject(objectCounter) {
  objectCounter.counter += 1;
}

function increaseCounterPrimitive(primitiveCounter) {
  primitiveCounter += 1;
}

const object = { counter: 0 };
let primitive = 0;

increaseCounterObject(object);
increaseCounterPrimitive(primitive);

花点时间猜一下,在我们调用这些函数之后,object 会发生什么变化,primitive 又会发生什么变化。

如果你回答对象的计数器会增加 1,而基本类型的计数器不会改变,那么你是对的。请记住,参数 objectCounter 包含一个指向与我们传递的 object 变量相同的对象引用 (reference),而 primitiveCounter 只包含基本类型值的副本 (copy)

重新赋值对象数据类型变量

虽然突变 (mutate) 我们持有的引用所指向的对象会影响所有引用该对象的其他变量,但重新赋值 (reassigning) 一个变量不会改变其他变量所引用的对象。例如:

let animal = { species: "dog" };
let dog = animal;

// 用一个全新的对象重新赋值 animal 变量
animal = { species: "cat" };

console.log(animal); // { species: "cat" }
console.log(dog); // { species: "dog" }

任务

  1. 既然你已经接触了对象,请回到几课前的 JavaScript.info 数组方法文章末尾的数组方法练习只做以下练习
    • Map to names (映射到名称)
    • Map to objects (映射到对象)
    • Sort users by age (按年龄对用户排序)
    • Get average age (获取平均年龄)
    • Create keyed object from array (从数组创建键控对象)
  2. 回到 JavaScript 练习仓库 (JavaScript exercises repository)。在完成以下练习之前,请先按顺序查看每个练习的 README 文件:
    • 12_calculator (计算器)
    • 13_palindromes (回文)
    • 14_fibonacci (斐波那契)
    • 15_getTheTitles (获取标题)
    • 16_findTheOldest (查找最年长者)
      注意:这些练习的解决方案可以在每个练习的 solution 文件夹中找到。

如果你感到不知所措或卡住了,不要害怕回头复习或在 TOP Discord 服务器 中寻求帮助!

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击,此处保留链接位置)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

项目:计算器

简介

你做到了!至此,你应该对 JavaScript 的基础知识有了非常扎实的掌握。当然,还有很多东西要学,但此时你应该能够创建相当多的东西了。我们的最终项目将结合你到目前为止所学的所有知识:你将使用 JavaScript、HTML 和 CSS 制作一个屏幕上的计算器。

和往常一样,这个项目中的某些元素对你来说并非轻而易举,但如果你一直跟着课程学习,你绝对拥有完成它所需的一切。我们将引导你完成可以采取的各种步骤,但再次强调,如何实际实现取决于你自己!

关于 eval() 和 new Function() 的警告

在你开始项目之前,我们需要提醒你注意一点。当你在研究如何在 JavaScript 中计算复杂的数学表达式时,你很可能会遇到诱人的 eval() 函数。然而,这个函数可能非常危险,MDN 很好地记录了为什么你永远不应该使用 eval!作为本项目的一部分,你需要构建自己的函数来评估表达式。同样地,在研究如何计算表达式时,你可能会遇到建议你返回一个 new Function() 来评估字符串的解决方案。与 eval() 类似,由于评估不安全数据可能带来的隐患,这也不应使用。更何况,让解决方案为你完成所有工作有什么乐趣呢?让我们开始吧!

任务要求

不要忘记尽早并经常提交(commit)!你可以在这里参考 提交信息课程

以下是一些用例(对你的项目的期望):

  1. 创建基本运算函数:你的计算器将包含通常计算器上所有基本数学运算符的函数。因此,首先为以下运算创建函数,并在浏览器的控制台中进行测试:
    • add (加法)
    • subtract (减法)
    • multiply (乘法)
    • divide (除法)
  2. 定义操作变量:一个计算器操作将由一个数字、一个运算符和另一个数字组成(例如,3 + 5)。创建三个变量,分别代表操作的每一部分。稍后你将使用这些变量更新显示。
  3. 创建 operate 函数:创建一个新函数 operate,它接受一个运算符和两个数字,然后调用上述相应的函数对数字进行运算。
  4. 构建基本 HTML 界面:创建一个基本的 HTML 计算器界面,包含每个数字和运算符(包括 =)的按钮。
    • 暂时不必担心使它们具有功能。
    • 还应该有一个计算器的显示屏。先用一些虚拟数字填充它,使其看起来正确。
    • 添加一个“清除”(clear)按钮。
  5. 实现数字按钮功能:创建函数,使得点击数字按钮时能够填充显示屏。你应该将显示屏的内容(数字)存储在一个变量中,供下一步使用。
  6. 实现核心计算逻辑:让计算器工作起来!你需要存储用户输入的第一个和第二个数字,然后当用户按下 = 按钮时,根据在数字之间选择的运算符对它们调用 operate()
    • 你应该已经有可以填充显示屏的代码,所以在调用 operate 之后,用操作的结果更新显示屏。
    • 这是项目中最难的部分。你需要弄清楚如何存储所有值以及如何用它们调用 operate 函数。如果你花了点时间才理清逻辑,别担心,这很正常。
  7. 陷阱与注意事项:如果你的代码中出现以下错误,请留意并修复它们:
    • 一次只计算一对数字:你的计算器一次不应计算超过一对数字。
      • 示例流程
        1. 输入一个数字 (12)。
        2. 输入一个运算符 (+)。
        3. 输入第二个数字 (7)。
        4. 输入第二个运算符 (-)。此时,它应该计算初始的数字对 (12 + 7),然后显示结果 (19)。
        5. 输入另一个数字 (1)。
        6. 输入另一个运算符或等号 (=)。此时,它应该使用之前的结果 (19) 作为第一个数字、运算符 (-) 和新数字 (1) 来计算新公式 19 - 1。你应该在显示屏上看到结果 (18)。
      • 在线参考:要查看实际操作效果,可以将我们刚刚解释的公式 12 + 7 - 1 = 输入到这个在线计算器中。
    • 处理长小数:你应该对带有长小数的答案进行四舍五入,以免它们溢出显示屏。
    • 避免提前按 = 导致错误:在输入所有数字或运算符之前按下 = 可能会导致问题!
    • 确保“清除”功能彻底:按下“清除”按钮应清除所有现有数据。确保用户在按下“清除”后真正重新开始。
    • 处理除以零错误:如果用户尝试除以 0,显示一个略带讽刺意味的错误消息…并且不要让它使你的计算器崩溃!
    • 仅在条件满足时才运算:确保你的计算器仅在用户提供了两个数字和一个运算符时才运行运算。示例:你输入一个数字 (2),然后按运算符按钮 (+)。如果你连续第二次按运算符按钮 (+)。你的计算器不应将其计算为 (2 + 2),也不应显示结果 (4)。如果连续按下运算符按钮,你的计算器不应执行任何计算,它只应将最后输入的运算符用于下一次运算。
    • 显示结果后的新输入行为:当显示结果时,按下新的数字键应清除结果并开始新的计算,而不是将数字附加到现有结果上。检查你的计算器是否符合这一点!

附加挑战

  • 添加小数点支持:用户可以通过进行必要的数学运算获得浮点数,但他们还不能手动输入。添加一个 . 按钮,让用户可以输入小数!确保不允许他们输入多个小数点,例如:12.3.56.5。如果显示屏中已经存在小数点分隔符,则禁用 . 按钮。
  • 添加“退格”按钮:添加一个“退格”(backspace)按钮,这样如果用户点错了数字,可以撤销他们最后一次的输入。
  • 添加键盘支持:添加键盘支持(让用户可以使用键盘操作计算器)!

结语

选择你的前进道路

简介

这是一段相当精彩的旅程。花点时间反思一下,你刚刚至少掌握了三种不同语言的基础知识,而且如果必须的话,你可能已经可以自己拼凑出一个网页了。

你现在应该能比较自如地在电脑命令行中输入命令,并且理解在浏览器中输入网址后返回一个功能页面的过程。你应该能够创建一个基本的网页,并用 JavaScript 让它实现一些基本功能。最后,你应该清楚地理解了所有这些知识如何在你未来成为 Web 应用程序构建者的道路上相互融合。

凭借你目前掌握的知识,你可以开始搭建一些基础网站了,但我们的目标是帮助你走得更远。我们希望将你培养到能够自信地构建一个功能全面且可扩展的 Web 应用程序的程度,无论是独立完成,还是作为开发者在顶级科技公司工作。

你在学习曲线上已经走了很长的路,但还有更多酷炫的东西等着你去学习掌握。现在是时候决定你想选择哪条道路了:是选择 Ruby 搭配 Ruby on Rails,还是选择 JavaScript 搭配 Node.js/Express。两条路径都将引导你培养技能、积累项目组合,并成为一名优秀的开发者。关于哪条路径是最好的,这里没有标准答案。学习多种语言可以帮助你成为更全面的开发者,但专注于一门语言可以使你在特定语言上更专业。两条路径都能帮助你成为一名优秀的开发者。重要的是不要半途而废切换路径;那样只会让你落后。

学习编程远不止是学习一门语言。它关乎学习如何研究和解决难题,培养工程思维,处理复杂问题和代码库,与其他开发者有效沟通,以及更多更多。幸运的是,所有这些技能都可以迁移到其他语言和软件领域,并且因为大多数语言在概念和结构上相似,转向不同的语言将比你学习第一门语言容易得多。

关于学习你所在地区常用语言的论点可能听起来很有说服力,但因为一个优秀的开发者可以轻松迁移技能,你所学的具体语言并不会对求职结果产生巨大影响。不过,如果你无法摆脱这种担忧,那就做你需要做的任何事情——即使这意味着研究你所在地区看看本地有什么工作机会(不过别忘了远程工作)。一门好的课程远比你先学哪门语言重要得多。

为了帮助你做出决定,以下是每种技术的一些特点:

Ruby 和 Ruby on Rails

  • 约定俗成 (Opinionated)
    • 你无需为项目的结构做决定。这有助于你接手他人构建的项目。
    • 你将学习“Ruby 之道”,这也有助于你在其他语言中编写整洁的代码。
  • 优雅 (Elegant)
    • 提供了大量工具,帮助你以易于阅读的方式开发。
    • Ruby 的解决方案通常比其他语言更简洁。
  • 快速开发 (Rapid development)
    • Rails 能帮助你非常快速地构建应用程序。这意味着在相同时间内,你可以完成比其他人更多数量的有趣项目。
  • 单一范式 (Single paradigm)
    • 面向对象编程 (OOP) 是 Ruby 的方式;许多其他语言也主要基于 OOP。

JavaScript - Node.js & Express

  • 无约定俗成 (Un-opinionated)
    • 没有标准的做事方式。好坏参半,你将不得不自己决定项目的结构。
  • 速度非常快 (Very fast)
    • 因为 JavaScript 利用了 JIT (即时) 编译,它可以非常快速地进行计算。
  • 详细 (Verbose)
    • 这些技术没有“魔法”。你需要理解一切才能理清头绪。
    • 帮助你更接近计算机实际读取代码的方式。
  • 前后端语言统一 (Same language on both front end and back end)
    • 保持语言一致性有助于在整个应用程序中编写一致的代码。
    • 允许你做一些很酷的事情,例如编写可以在浏览器和服务器上运行以实现优化的代码。

归根结底,选择哪条路径并不重要。重要的是你坚持下去,并在整个职业生涯中始终保持学习心态。还不信服?阅读这篇关于选择正确编程语言的文章

还是无法决定?让转盘来决定你的命运吧!

欢迎在聊天室分享结果!

做好准备吧,因为这仅仅是个开始。现在是时候深入探索 Foundations 课程所涵盖的领域了。接下来的课程中有一些精彩的项目,它们会挑战你的极限,但当你完成时,你将准备好去编程世界留下自己的印记。

旅程才刚刚开始!

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

  • 如果你仍然感到困惑,可以查看这篇文章,获取关于这两种语言的更多事实、优缺点:Ruby on Rails vs JavaScript

JavaScript 全栈开发

概述

本学习路径将引导你完成我们完整的 JavaScript 课程体系。课程应按照其显示的顺序进行学习。你将学习使用 JavaScript 和 NodeJS 从零开始构建精美、响应式的网站所需的一切知识。

中级 HTML 和 CSS 课程

概述

让我们进一步学习 HTML 和 CSS 的更多功能。

中级 HTML 概念

简介

介绍

这是包含两部分内容课程的第一部分,我们将深入探讨 HTML 和 CSS。

我们的基础课程设计上只是浅尝辄止了可能实现的功能。我们希望为你提供快速入门所需的一切必要基础,让你能尽快开始实践并有所产出。但现在,是时候放慢脚步,深入学习了!

到目前为止,你可能已经意识到 HTML 元素远不止我们在基础内容中提到的那些。在本课程中,我们将涵盖其余重要的元素,例如表单 (forms) 和表格 (tables)。

你还会在本课程中学到 CSS 的更多功能,例如变量 (variables)、函数 (functions)、阴影 (shadows),当然还有网格布局 (grid layouts)!所以,系好安全带!当你完成本课程时,你将能够重现你在互联网上找到的几乎任何网页设计……这是一项重要的、需要持续精进的技能。即使你的目标不是专门从事前端工作,能够让你的作品集项目看起来美观,对于让你自己脱颖而出也是至关重要的。

你将在本课程的第二部分(稍后在课程体系中)学习动画 (animations)、可访问性 (accessibility) 和响应式设计 (responsive design)。

任务

  1. 浏览 HTML 元素参考:通读这份 HTML 元素参考,大致了解还有哪些 HTML 元素可供你使用。现在无需刻意记忆,因为我们后续会学习重要的部分,但现在浏览一下有助于之后内容的理解。
  2. 预览 CSS 速查表:快速浏览一下这份看起来有点让人眼花缭乱的 CSS 速查表。再次强调,我们不需要你从这里学习任何具体内容或记忆任何东西。只需用它来感受一下你还有哪些内容需要学习!

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

Emmet

简介

Emmet 是一个内置于 VS Code 的插件,它通过提供一系列巧妙的快捷键,帮助你更高效地编写 HTML 和 CSS。到目前为止,你已经编写了不少 HTML 和 CSS 代码,如果你一直在使用 VS Code,那么很可能已经以某种方式接触过 Emmet 了。

课程概览

本节概述了你将在本课中学到的主题。

  • 使用一些 Emmet 最有用的快捷键。
  • 在 VS Code 中设置自定义 Emmet 按键绑定。

Emmet 详解

Emmet 对于经常处理 HTML 和 CSS 的人来说是一个非常实用的工具。幸运的是,Emmet 的学习曲线并不陡峭,如果你已经知道如何编写 HTML 和 CSS,那么掌握 Emmet 缩写将毫无困难。

让我们从使用 Emmet 生成一个 HTML 样板文件开始。在 VS Code 中打开一个空的 HTML 文件并输入 !,应该会触发 Emmet 建议,如下图所示:

按 Enter 键后应生成以下文本:

我们刚刚使用了众多 Emmet 缩写中的一个。还有很多有用的 Emmet 缩写你应该了解一下,比如用缩写包裹 (Wrap with Abbreviation)删除标签 (Remove Tag)。在继续之前,一定要先看看这两个。

考虑到这两个功能非常实用,我们将为它们在 VS Code 中设置快捷键。首先打开键盘快捷键窗口。你可以通过点击左下角的齿轮图标并选择键盘快捷键,或者在 Mac 上按 Cmd + K 再按 Cmd + S,在 Windows/Linux 上按 Ctrl + K 再按 Ctrl + S 来打开。

进入键盘快捷键窗口后,你应该可以通过搜索 Emmet:<action> 来访问所有的 Emmet 操作。在我们的例子中,将是 Emmet:Wrap With Abbreviation(用缩写包裹)。

现在你可以通过点击加号并输入你想要的任意按键组合来为它添加快捷键。你可以通过搜索 Emmet:Remove tag(删除标签)为 Remove Tag 做同样的设置。

任务

  1. 观看 Emmet 视频教程:这个关于 Emmet 的视频展示了一些我们可以使用 Emmet 来提高效率的方法。请观看至 11:37,因为它演示了一些我们尚未涉及的内容(我们稍后会学到)。
  2. 查阅 Emmet 速查表:看一下这份 Emmet 速查表。你不需要死记硬背,但熟悉它的不同用法是很有益的。

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

  • Emmet Keybindings (作者:Andrés Gutiérrez):这是一套用于 Visual Studio Code 的 Emmet 按键绑定预设。如果你不知道如何映射按键,它可以作为一个预定义的按键组使用。
  • Emmet Live (作者:Yurii Semeniuk):是 Visual Studio Code 的另一个扩展。它可以在你编辑 Emmet 缩写时持续生成相应的 HTML 结构。为了达到预期效果,尝试在调用此扩展之前选中一段文本。

SVG (可缩放矢量图形)

简介

SVG 是网络上一种非常常见的图像格式。它们起初可能有点令人困惑,但一旦你掌握了使用方法,它们就成为一种极其强大的工具,可以为你的网站创建高质量的动态图像。

在本课中,我们将学习 SVG 究竟是什么、它们的用途以及如何将它们嵌入到你的网站中。

课程概览

本节概述了你将在本课中学到的主题。

  • SVG、矢量图形 (Vector Graphics) 和 XML 是什么。
  • 如何创建简单的 SVG 并将其添加到你的网站中。
  • 何时使用 SVG,以及何时其他图像格式会更合适。

什么是 SVG?

SVG 是一种可缩放 (scalable) 的图像格式,这意味着它们可以轻松缩放到任何尺寸并保持质量,而不会增加文件大小。如果你需要通过编程方式创建或修改图像,它们也非常有用,因为你可以通过 CSS 和 JavaScript 更改它们的属性。

SVG 通常用于:

  1. 图标 (Icons)
  2. 图表/图形 (Graphs/Charts)
  3. 大型、简单的图像 (Large, simple images)
  4. 图案背景 (Patterned backgrounds)
  5. 通过 SVG 滤镜将效果应用于其他元素 (Applying effects to other elements via SVG filters)

好吧,但它们到底是什么?

“SVG” 代表 “可缩放矢量图形 (Scalable Vector Graphics)”。矢量图形 (Vector graphics) 是由数学定义的图像,这与传统的“栅格图形 (raster graphics)”相反,后者的图像由像素网格定义。对于栅格图形,细节受限于该像素网格的大小。如果你想增加图像的大小(缩放它),你就必须增大该网格的大小。如何决定所有那些新像素应该是什么样子?没有简单的解决方案。此外,网格越大,你的文件大小增长得也越大。

另一方面,对于矢量图形,没有网格。相反,你有不同形状和线条的公式。由于这些只是公式,所以无论你想让它们显示得多大或多小都无关紧要——它们可以缩放到你想要的任何尺寸,并且这不会影响质量或文件大小。

SVG 还有另一个有趣的方面:它们是使用 XML 定义的。XML(又名“可扩展标记语言 (Extensible Markup Language)”)是一种类似 HTML 的语法,用于许多事情,从 API,到 RSS,再到电子表格和文字处理软件

SVG 源代码是 XML 这一事实有几个关键好处。

首先,这意味着它是人类可读的 (human-readable)。如果你在文本编辑器中打开一个 JPEG,它看起来就像乱码 (gobbledygook)。然而,如果你打开一个 SVG,它会看起来像这样:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <rect x=0 y=0 width=100 height=50 />
  <circle class="svg-circle" cx="50" cy="50" r="10"/>
</svg>

它可能仍然令人困惑,但是嘿——这些都是单词!标签!属性!与像 JPEG 这样的二进制文件格式相比,我们绝对是在熟悉的领域。

XML 的第二个好处是它设计为可与 HTML 互操作 (interoperable),这意味着你可以将上述代码直接放入 HTML 文件中,无需任何更改,它就应该能显示图像。并且因为这些可以像 HTML 元素一样成为 DOM 中的元素,你可以用 CSS 定位它们,并使用你已经用过的 Element WebAPI 来创建它们!

缺点

那么,显然 SVG 非常棒!是时候把我们所有的图像都转换成 SVG 了,对吧?嗯,不完全是这样。SVG 非常适合相对简单的图像,但由于图像的每一个细节都需要写成 XML,它们在存储复杂图像时效率极低。如果你的图像需要是照片级逼真的,或者它有精细的细节或纹理(“粗糙纹理”就是一个很好的例子),那么 SVG 就是错误的工具。

SVG 结构剖析

通常,你不会想在代码中从头开始创建 SVG。最常见的情况是,你会从网站或能够创建它们的图像编辑器(Adobe Illustrator 和 Figma 是两个可以创建 SVG 的流行应用程序)下载文件或复制代码。然而,下载一个 SVG 并想要稍微调整或修改它的情况相当常见,因此了解所有部分是什么以及它们如何工作非常有用。

*(上方是一个可交互的 CodePen 示例,尝试修改代码并观察变化)*
  1. xmlns - 代表 “XML 命名空间 (XML NameSpace)”。这指定了你正在使用的 XML 方言 (dialect)。在我们的例子中,该方言是 SVG 语言规范。没有它,一些浏览器将无法渲染你的图像或会渲染不正确。
  2. viewBox - 定义 SVG 的边界。当你必须定义 SVG 中元素的不同点位置时,参考的就是这个坐标系。它还定义了 SVG 的宽高比 (aspect ratio)原点 (origin)。所以它做了很多事情!一定要在上面的示例中尝试不同的值,感受它如何影响形状。
  3. class, id - 这些属性的功能与它们在 HTML 中的功能完全一样。在 SVG 中使用这些可以让你轻松地通过 CSS 或 JavaScript 定位元素,或者通过 use 元素 重用元素。
  4. 元素 (Elements)<circle> (圆), <rect> (矩形), <path> (路径), 和 <text> (文本) 由 SVG 命名空间定义。这些是我们的基本构建块。虽然你可以用 SVG 制作极其复杂的图像,但它们大多只用十几个左右的基本元素创建。这里是 SVG 元素的完整列表
  5. 样式属性 (Styling Attributes):许多 SVG 属性,如 fill (填充) 和 stroke (描边),可以在你的 CSS 中更改。在此关于 SVG 属性和 CSS 的文章中了解更多。

尝试修改上面的代码,感受一下发生了什么。当你改变 viewBox 的尺寸时会发生什么?或者改变元素的属性时呢?

嵌入 SVG (Embedding SVGs)

在决定如何实际将 SVG 放入你的文档时,主要有两种方法:链接 (linked)内联 (inline)

链接 SVG 的方式基本上与链接任何其他图像的方式相同。你可以使用 HTML 图像元素如 <img>,或者在 CSS 中使用 background-image: url(./my-image.svg) 链接它。它们仍然会正确缩放,但 SVG 的内容将无法从网页访问。

另一种方法是内联 (inline) 你的 SVG,将其内容直接粘贴到你网页的代码中,而不是将其作为图像链接。它仍然会正确渲染,但 SVG 的属性将对你的代码可见,这将允许你通过 CSS 或 JavaScript 动态地更改图像。

内联 SVG 可以让你解锁它们的全部潜力,但它也带来一些严重的缺点:它使你的代码更难阅读,使你的页面更难缓存,如果它是一个大型 SVG,可能会延迟 HTML 其余部分的加载。

一旦你学习了像 React 这样的前端 JavaScript 库或像 webpack 这样的构建工具,内联 SVG 代码的一些缺点可以得到避免。我们还没有准备好讨论这些,所以只需记住这一点。

就目前而言,只需选择最适合你用例的方法。链接通常更简洁、更简单,因此除非你需要与 HTML 一起调整 SVG 代码,否则优先选择链接方式。

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

表格

简介

HTML 表格 (Tables) 允许你创建由行和列组成的二维表格。在本课中,你将学习关于 HTML 表格所需了解的一切知识。

课程概览

本节概述了你将在本课中学到的主题。

  • 创建高级 HTML 表格。

HTML 表格详解

某些数据确实需要通过表格来展示。HTML 表格的使用频率可能低于按钮、链接、列表以及你目前学过的其他元素,但在某些情况下,它们却是完美的工具。一些更高级的功能在正确设置时可能有点棘手,但表格入门非常简单。你可以使用 <table></table> 标签创建一个表格,然后将行 (rows)、列 (columns)、表头 (headers) 或其他任何可能的元素放在这些表格标签内。

例如:

*(上方是一个简单的表格示例 CodePen)*

在这个 CodePen 示例中,我们创建了一个 <table> 元素,包含两个表格行 (<tr>) 元素。第一行有两个表头 (<th>),而第二行有两个数据单元格 (<td>)。

任务

  1. 学习 MDN 表格教程
    • 阅读 MDN 的 表格基础表格进阶 教程。它们会教你 HTML 表格背后的所有语法,相当直观易懂。务必跟着教程编码练习 (code along)!
  2. 查阅 CSS Tricks 表格指南
    • 通读 CSS Tricks 的 表格元素完全指南。它非常深入地探讨了你已经学到的内容,并涉及一些额外的知识点。跳过涉及 jQuery、高级 JavaScript 或 SASS 的部分,因为它们超出了本课范围。
  3. 完成 MDN 表格评估练习
    • 完成 MDN 的这个 表格评估练习 (构建行星数据表)。将你新学到的技能付诸实践是很好的巩固方式!

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

  • Pencil & Paper 发表了一篇很棒的关于表格可实现功能的文章(如果你愿意花点时间好好研究它们的话)。他们提出了一些关于如何格式化数据的良好习惯,这些习惯会带来巨大的改变。这不是一份表格的必做清单,而是值得在下次为表格外观困扰时牢记的想法。

中级 CSS 概念

默认样式

简介

浏览器会对每个网页应用一套默认样式。你可能没有直接思考过这个问题,但你肯定遇到过它。

课程概览

本节概述了你将在本课中学到的主题。

  • 你将了解浏览器的默认样式。
  • 你将学习如何使用 CSS 重置 (CSS reset) 来移除或更改这些默认样式。

什么是默认样式?它们来自哪里?

在你进行项目开发时,你可能观察到某些元素被应用了默认样式,例如 h1 元素更大更粗的标题,以及 a 元素蓝色带下划线的链接。你也很有可能曾与诸如默认的外边距 (margin) 和内边距 (padding) 之类的问题斗争过。这些样式是用户代理样式表 (user-agent stylesheets) 的一部分,它们确保在没有 CSS 的情况下网页也能有基本的样式。每个浏览器都有自己的一套用户代理样式表,因此默认样式在不同的浏览器之间会略有差异。

如果我不喜欢默认样式怎么办?

在极少数例外情况下,你只需编写自己的 CSS 规则即可。你在样式表中编写的规则比用户代理规则的优先级 (precedence) 更高,因此会覆盖默认样式。不过,还有另一个选择。

为了解决不同浏览器之间的不一致性,并为样式设计建立一个一致的起点,一些开发者开始使用 CSS 重置 (CSS resets)。这些重置是包含 CSS 规则的样式表,旨在更改或移除由用户代理样式表设置的默认样式。使用它们有助于实现一致性,并为开发者提供一个干净的起点(“白板”),以便在不受干扰的情况下应用自己的样式。

虽然 CSS 重置仍然被广泛使用,但它们并非强制性的 (not mandatory)。一些开发者选择不使用它们,而另一些开发者则创建自己的重置或使用预构建的重置。重要的是要理解,重置是主观的 (subjective)体现开发者偏好的 (opinionated),它们反映了创建它们的开发者的偏好。你可以自行决定你希望如何处理。

任务

  1. 阅读 CSS Tricks 文章:阅读 Reboot, Resets, and Reasoning(重置、重启与缘由),这是一篇关于重置历史的精彩文章,并解释了重置为何是体现开发者偏好的。
  2. 阅读关于 Normalize 和 Reset 的文章:阅读 Making the case for CSS normalize and reset stylesheets in 2023(为 2023 年使用 CSS normalize 和 reset 样式表辩护)。它出色地讨论了各种重置之间的差异以及你为何可能选择使用它们。
  3. 学习 Josh Comeau 的自定义重置:阅读 Josh Comeau’s custom CSS reset(Josh Comeau 的自定义 CSS 重置)。它也很好地分解了他所使用的每条规则背后的思考过程,让你了解如何思考这些问题。

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

  • Browser Default Styles (浏览器默认样式):允许你查看用户代理应用于每个元素的规则。
  • The Fabulous Styleboard (神奇的样式板):包含几个重置选项,你可以选择和取消选择它们,以查看它们如何影响各种 HTML 元素(交互式演示)。

CSS 单位 (CSS Units)

简介

CSS 中有许多不同的单位可以用来定义尺寸。本课将向你介绍最重要的单位,并告诉你可以在哪里学习其他单位。

课程概览

本节概述了你将在本课中学到的主题。

  • 你将了解相对单位 (relative units) 和绝对单位 (absolute units) 的区别。
  • 你将学习何时适合使用不同的单位。

绝对单位

绝对单位是指在任何上下文中都保持不变的单元。px (像素) 是一个绝对单位,因为一个像素的大小不会相对于页面上的其他任何东西而改变。事实上,px 是你在 Web 项目中唯一应该使用的绝对单位。其余的单位(如 in (英寸) 和 cm (厘米))在打印设置中更有意义,因为它们与物理单位相关。

相对单位

相对单位是指可以根据其上下文而变化的单位。你可能会遇到并想使用其中的几种。

em 和 rem

emrem 都指的是字体大小,尽管它们也常用于定义 CSS 中的其他尺寸。你会经常看到两者,所以我们将解释两者,但根据经验法则,优先使用 rem

  • em1em 是元素当前的 font-size(如果你用它来设置 font-size,则基于其父元素的 font-size)。例如,如果一个元素的 font-size16px,那么将其宽度设置为 4em 将使其宽度为 64px (16 * 4 == 64)。
  • rem1rem 是根元素(:roothtml)的 font-sizerem 的计算方式与 em 相同,但无需跟踪父元素的字体大小,因此更简单。依赖 em 意味着特定尺寸可能会随着上下文的变化而改变,而这很可能不是你期望的行为。

推荐:使用像 rem 这样的相对单位来定义你网站上的字体大小。许多浏览器允许用户更改基准字体大小以提高可读性。如果可能,建议尊重用户关于字体大小的意愿。你将从阅读材料中了解更多信息。

视口单位

单位 vh (视口高度) 和 vw (视口宽度) 与视口 (viewport) 的大小相关。具体来说:

  • 1vh 等于视口高度的 1%
  • 1vw 等于视口宽度的 1%

当你希望某些内容相对于视口调整大小时,这些单位非常有用。示例包括:

  • 全屏高度的英雄区域 (full-height heroes)
  • 类应用的全屏界面 (full-screen app-like interfaces)

任务

  1. CSS 值和单位:涵盖所有可用的单位(MDN)。
  2. CSS 单位:深入探讨你可能想要使用 emrempx 的方式和时机(Cody Loyd 的文章)。
  3. 视口单位的趣味应用:展示一些你可以用 vhvw 实现的有趣效果(CSS-Tricks)。

知识检查

以下问题旨在反思本课的关键主题。如果你无法回答某个问题,请点击它以复习相关材料,但请记住,你并不需要死记硬背或完全掌握这些知识(原文档中链接可点击)。

附加资源

本节包含相关内容的实用链接。不是必须的,因此仅供参考。

更多文本样式

引言

您已经在我们的基础课程中学过一些文本操作。本课将介绍在处理文本时可以使用的更多有用的 CSS 属性。

课程概览

本节概述了您将在本课中学到的主题。

  • 您将学习如何在 Web 项目中使用自定义字体。
  • 您将学习更多与文本相关的 CSS 属性。

字体

在我们的基础课程中,我们介绍了更改元素的 font-family(字体系列),但当时我们省略了一些细微之处和细节。

系统字体堆栈 (System Font Stack)

如果您使用 font-family 属性更改为像 ImpactTimes New Roman 这样的字体,而这些字体碰巧没有安装在用户的计算机上,那么将显示后备字体。如果您没有定义后备字体,则将使用默认的 HTML 字体,这通常不太美观。因此,在项目中看到列出较长的字体堆栈是很常见的。

一个流行的堆栈是 CSS Tricks 的系统字体堆栈

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

这一连串有些冗长的字体系列的目的是尝试使用系统用户界面的默认字体。它会遍历这些字体,直到找到系统上已安装的一个,然后使用它。使用这样的堆栈通常会产生令人满意的结果,特别是如果您追求某种“中性”的字体风格时。

网络字体 (Web Fonts)

如果您想使用用户设备上不可用的字体,则需要从在线源(字体库或您网站上的资源)导入该字体。这两种方法都会导入字体并使其可在您的 CSS font-family 属性中访问。

请记住,添加后备字体非常重要。如果您链接到外部 API,您无法保证 URL 不会更改,或者外部 API 不会在某个时候宕机。拥有合理的后备意味着如果出现问题,至少您的网站看起来不会完全崩溃。

使用导入的文件存在性能问题,您将在作业中阅读更多相关内容。因此,如果您的设计允许,最好使用字体堆栈而不是导入的字体。但是,有时您会需要使用导入的字体。

在线字体库 (Online Font Libraries)

一种流行且简单的方法来获取用户设备上安装的字体是使用在线字体库。

要使用这些库中的字体,请访问网站,选择一种字体,然后从网站复制一个代码片段,将该字体从他们的服务器导入到您的网站中。您将获得一个要放入 HTML 的 <link> 标签,如下所示...

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">

...或者一个可以放在 CSS 文件顶部的 @import 标签。

@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

使用字体库时的一个重要考虑是该库的隐私政策以及您可能需要遵守的任何法规。例如,使用 Google Fonts API 违反了欧洲 GDPR(通用数据保护条例)。如果您担心遵守此类法规,可以从库下载字体并自行托管。

自托管字体 (Self Hosted Fonts)

也可以使用从网上下载的字体。在您的 CSS 文件中,使用 @font-face 规则导入并定义自定义字体,然后像使用任何其他字体系列一样使用它。有多种字体文件格式,您可以在 fileinfo.com 的字体文件格式页面 上更深入地了解它们。但是,在选择字体文件格式时请务必小心,因为并非所有格式都得到浏览器的普遍支持。浏览器及其支持的字体格式列表可以在 W3 Schools 的 CSS Web 字体页面 上找到。

@font-face {
  font-family: my-cool-font;
  src: url(../fonts/the-font-file.woff);
}

h1 {
  font-family: my-cool-font, sans-serif;
}

此方法可能比依赖第三方字体 API 更可靠,但包含后备字体始终是明智之举。

文本样式

您在我们的基础课程中学到了操作字体的基础知识,但在使用 CSS 操作文本样式方面,还有更多可以做的事情。这些规则都相对简单且不言自明。您可以查阅文档解决可能遇到的任何问题。

font-style

通常用于使字体变为斜体。您学习了 HTML <em> 标签,它使用斜体字体,但 <em> 表示它包裹的文本很重要或应以某种方式强调。一个很好的经验法则是:如果您只想让文本变为斜体(或粗体、带下划线、高亮等),请使用 CSS 属性。否则,如果文本应该具有某种语义上的强调,请使用正确的 HTML 元素。

例如,如果您希望所有标题文本都是斜体,您应该使用 font-style 来实现。如果您希望句子中间的一些文本显示为斜体以强调该文本,则使用 em 元素是合适的。MDN 关于强调元素的文档 强调了我们的上述观点。

如果斜体是出于样式目的而需要的,我们应该使用 font-style: italic;

h1 {
  font-style: italic;
}

如果斜体是出于强调目的而需要的,我们应该使用 em 元素。

<p>我 <em>从未</em> 说过他偷了你的钱</p>
<p>我从未说过 <em>他</em> 偷了你的钱</p>
<p>我从未说过他偷了 <em>你的</em> 钱</p>

letter-spacing

字母间距的作用如您所料...它改变单词中字母之间的间距。这对于调整您觉得间距过大或过小的自定义字体非常有用。在某些情况下,比如标题中,它也可能具有美观效果。

显然,请谨慎且有节制地使用此属性。不要使您的网站难以阅读!

line-height

行高调整换行文本行之间的间距。增加一点行高可以提高可读性。

text-transform

文本转换更改给定文本的大小写。例如,您可以使用此属性强制您的标题标签全部大写,或者将每个单词首字母大写。

用法可以在 MDN Web 文档 上的清晰示例中看到。

text-shadow

如您所料,text-shadow 为所选元素中的文本添加阴影。此属性最好有节制地使用,但在标题或其他展示性文本中可以产生很好的效果。

MDN 关于 text-shadow 的参考页面 上的示例展示了如何使用它。

省略号 (Ellipsis)

这个不是一个单一属性,但它是您工具箱中一个有用的技巧。使用 text-overflow 属性,您可以用省略号截断溢出的文本。然而,使溢出发生需要使用其他几个属性,因为文本默认打印在其容器外部的行为在技术上不被视为 overflow(我们知道这很令人困惑。抱歉。)

完整的代码片段是:

css

.overflowing {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

您可以在 CSS Tricks 关于省略号溢出的文章 中查看更多细节和示例。(准备好每次想使用此技巧时去查阅那篇文章。)

作业

  1. 阅读 MDN 关于 Web 字体的文章 并完成练习。
  2. 阅读 字体最佳实践 以了解导入字体的性能考虑因素以及如何缓解相关问题。
  3. 阅读 web.dev 排版 以了解开发者对于排版的一些重要考虑因素。

知识检查 (Knowledge Check)

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

更多 CSS 属性

引言

到目前为止,您可能已经牢固掌握了 CSS 重要的基础概念,但 CSS 还有很多功能可以做出真正引人注目的网站。现在是时候介绍那些有用的 CSS 小功能了,您可以使用它们为您的项目增添光彩。

CSS 属性非常多。幸运的是,您不必全部记住:您日常实际使用的属性数量要少得多。本课将涵盖您最终会定期使用的大部分属性。本课的格式有点不同,因为它本质上只是一个 CSS 属性列表。我们会给出属性的简要描述,然后链接到一些文档,您可以在其中查看所有可用的选项。

课程概览

本节概述了您将在本课中学到的主题。

  • 您将学习许多有用的 CSS 属性!

background (背景)

您可能已经尝试过设置元素的背景颜色,但 background 属性实际上可以做得更多。background 属性实际上是 8 个不同背景相关属性的简写,所有这些属性您都可以在链接的文档中阅读。除了更改背景颜色之外,您还可以指定背景图像、更改背景图像的位置和大小,以及如果背景图像太小无法填充其容器,可以更改它们如何重复或平铺。还可以设置多个背景层。

需要注意的一点是,可以单独使用这些属性,在某些情况下,这可能比默认使用简写更容易、更清晰。这与某些其他简写属性形成对比,那些属性几乎总是优先使用简写形式(如 flex、margin、padding 等)。

关于这个简写及其所有相关属性的文档中有大量信息。正如我们之前提到的,您不需要记住每个属性的确切顺序和语法。知道它们的存在并对其功能有一个大致的了解就足够了。

再提一点,这里的形式语法 (Formal Syntax) 部分很复杂。别让它吓倒你。基本语法有点难以定义,因为构成简写的许多属性是可选的,或者可以出现在定义中的不同位置。阅读 MDN 关于 background 的文档

borderborder-radius (边框和边框半径)

至此,您可能已经遇到过 borderborder-radiusborder 属性是另一个简写,但它比 background 简写简单得多。对于边框,基本上您只需要定义大小(size)、样式(style)和颜色(color)。

border-radius 是用于在元素上创建圆角的属性。正如您将在文档中看到的,可以为元素的每个角定义不同的半径以获得花哨的效果,但这很少有用。将该信息存储在“如果我需要它我会去查阅”的类别中。

阅读 MDN 关于 borderborder-radius 的文档。

box-shadow (盒阴影)

正如属性名称所示,box-shadow 在元素周围添加阴影效果。这对于在页面上营造深度感以及为元素之间添加微妙的分离非常有用。

使用起来很简单,但要记住,最好有节制且微妙地使用它。与较深或较亮的颜色相比,更倾向于使用较浅、几乎看不见的阴影。

阅读 box-shadow 文档

overflow (溢出)

可以使用 overflow 来定义当元素的内容太大而无法容纳时会发生什么。最常见的用法可能是在网页内的某个元素(例如,一个具有可滚动内容的 card 样式元素)上添加滚动条。

查看 overflow 文档

opacity (不透明度)

不透明度是另一个简单的属性,在某些情况下非常有用。

查看 opacity 了解定义和一些示例。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

  • CSS Tricks 有一些非常棒的内容。其中一些感觉不如 MDN 文档那么正式和官方,但这意味着它们可能更容易理解。有时他们的示例可能更有用。例如,查看他们关于 background 简写overflow 的页面。
  • W3 Schools 是另一个不错的资源。我们(Odin 的作者)倾向于更喜欢 MDN,但 W3 没有任何问题。

高级选择器

引言

到目前为止,您应该已经熟悉了基本的 CSS 选择器,并且能够轻松地通过元素的类型、类或 ID 来获取它们。但要成为一名真正的 CSS 外科医生,有时您需要更专业的工具。在本课中,我们将探讨高级 CSS 选择器,并向您展示如何以更具体、更精细的方式定位元素。

当您无法(或不想)更改 HTML 标记时,这些选择器尤其有用。

高级选择器非常多,因此在此课程中逐一介绍超出了范围。但是,我们将介绍一些最有用和最常用的选择器,并为您提供概念和词汇,以便您自行学习更多内容。

一如既往,欢迎打开您的代码编辑器并使用这些选择器运行您自己的实验——熟能生巧!

课程概览

本节概述了您将在本课中学到的主题。

  • 理解如何使用父选择器和兄弟选择器。
  • 识别伪类 (pseudo-classes) 和伪元素 (pseudo-elements) 之间的区别。
  • 了解一些最有用和最常用的伪元素和伪类。
  • 了解选择属性或其部分的不同方式。

子代组合器和兄弟组合器

让我们看看更多无需引用类名就能访问不同元素的方法。这里有三个新的选择器可以实现这一点:

  • > - 子代组合器 (child combinator)
  • + - 相邻兄弟组合器 (adjacent sibling combinator)
  • ~ - 通用兄弟组合器 (general sibling combinator)

我们将使用以下示例标记来处理一些实际例子:

<main class="parent">
  <div class="child group1">
    <div class="grand-child group1"></div>
  </div>
  <div class="child group2">
    <div class="grand-child group2"></div>
  </div>
  <div class="child group3">
    <div class="grand-child group3"></div>
  </div>
</main>

到目前为止,您应该非常熟悉使用在 CSS 入门 中学到的后代组合器 (descendant combinator) 编写规则。例如,如果我们想选择 main 内所有带有 childgrand-child 类的 div,我们可以这样写:

main div {
  /* 我们很酷的 CSS */
}

但如果我们想更具体,选择 childgrand-child 类的 div 呢?这就是子代组合器 > 派上用场的地方。与后代组合器不同,它只选择直接子元素。

/* 带有 "child" 类的 div 会被这个选择器选中 */
main > div {
  /* 我们很酷的 CSS */
}

/* 带有 "grand-child" 类的 div 会被这个选择器选中 */
main > div > div {
  /* 更酷的 CSS */
}

换句话说,子代选择器会选择缩进一级的元素。要选择与我们目标元素相邻(紧跟在后面)或处于相同缩进级别的元素,我们可以使用相邻兄弟组合器 +

/* 只有带有 "child group2" 类的 div 会被这个选择器选中 */
.group1 + div {
  /* 我们很酷的 CSS */
}

/* 只有带有 "child group3" 类的 div 会被这个选择器选中 */
.group1 + div + div {
  /* 更酷的 CSS */
}

最后,如果我们想选择一个元素的所有后续兄弟元素,而不仅仅是第一个,我们可以使用通用兄弟组合器 ~

/* .group1 的所有 div 兄弟元素——在此例中是第 2 个和第 3 个 .child div,会被这个选择器选中 */
.group1 ~ div {
  /* 我们很酷的 CSS */
}

就像后代组合器一样,这些选择器没有特殊的特异性规则——它们的特异性分数仅由其组成部分构成。

如果您想了解更多关于组合器的信息,这篇 MDN 关于组合器的文章 提供了很好的概述。您不必做文章底部的“测试您的技能!”部分,因为它涵盖了尚未讨论的概念。别担心,本课后面会详细介绍!

伪选择器 (Pseudo-selectors)

在深入探讨伪选择器之前,先快速了解一下 伪类 (pseudo-classes) 和伪元素 (pseudo-elements) 之间的区别。伪类选择器以单个冒号 : 为前缀,是定位 HTML 中已有元素的不同方式。伪元素以两个冒号 :: 为前缀,用于定位标记中通常不存在的元素。如果这还不能立即理解,别担心——我们将在下面探讨一些例子。

伪类 (Pseudo-classes)

伪类为我们提供了多种方式来定位 HTML 中的元素。伪类有很多种,并且有几种不同的类型。一些伪类基于元素在 HTML 中的位置或结构。另一些则基于特定元素的状态,或者用户当前如何与其交互。这里无法详细涵盖所有内容,但我们将了解一些最有用的伪类。伪类的特异性与常规类相同(0, 0, 1, 0)。就像常规类一样,大多数伪类可以链接在一起。

上面的 (0,0,1,0) 是计算特异性的符号。要了解更多关于它如何工作的信息,请浏览这篇 关于 CSS 特异性的文章 中的“计算 CSS 特异性值”部分。

一如既往,别忘了查看 MDN 关于伪类的文档 以了解完整的情况。

动态和用户操作伪类

这些类型的有用伪类可以使您的页面感觉更加动态和交互。

:focus 应用于当前被用户选中的元素,无论是通过光标选择还是使用键盘选择。

:hover 会影响用户鼠标指针下的任何元素。它可以用来给按钮和链接增添额外的活力,以突出显示它们是可交互的,或者用来触发下拉菜单。

:active 应用于当前正被点击的元素,对于向用户反馈他们的操作产生了效果特别有用。这是给您的按钮和其他交互元素更多“触觉”反馈的一个好方法。

您是否想过为什么链接是蓝色的,但在未添加样式的 HTML 中点击后会变成紫色?这是因为浏览器默认实现了这种样式。要实现您自己的自定义链接样式,可以利用 :link:visited 伪类。默认浏览器样式的简化版本可能如下所示:

  /* 此规则将应用于所有链接 */
  a {
    text-decoration: underline;
  }

  /* 这将应用于未访问过的链接 */
  a:link {
    color: blue;
  }

  /* 您猜对了,这应用于用户点击过的所有链接 */
  a:visited {
    color: purple;
  }

结构伪类

结构伪类是一种强大的方式,可以根据元素在 DOM 中的位置来选择它们。

:root 是一个特殊的类,代表您文档的最顶层——即没有父元素的元素。通常在 Web 开发中,这相当于 html 元素,但 :roothtml 之间有一些细微差别

:root 通常是您放置“全局”CSS 规则的地方,您希望这些规则在任何地方都可用——例如您的自定义属性和 CSS 变量,或者诸如 box-sizing: border-box; 之类的规则。

:first-child:last-child 将匹配作为第一个或最后一个兄弟元素的元素。

类似地,:empty 将匹配没有任何子元素的元素,而 :only-child 将匹配没有任何兄弟元素的元素。

为了更动态的方法,我们可以使用 :nth-child。这是一个灵活的伪类,有几种不同的用法。

  .myList:nth-child(5) {/* 选择第 5 个带有类 myList 的元素 */}

  .myList:nth-child(3n) { /* 选择每第 3 个带有类 myList 的元素 */}

  .myList:nth-child(3n + 3) { /* 选择每第 3 个带有类 myList 的元素,从第 3 个开始 */}

  .myList:nth-child(even) {/* 选择所有带有类 myList 的偶数元素 */}

伪元素 (Pseudo-elements)

伪类为我们提供了根据状态或结构与 HTML 元素交互的替代方式,而伪元素则更加抽象。它们允许我们影响根本不是元素的 HTML 部分。这些特殊元素的特异性与常规元素相同(0, 0, 0, 1)。有许多有用的伪元素可以以各种创意方式使用。

::marker 允许您自定义 <li> 元素的项目符号或数字的样式。

::first-letter::first-line 允许您(您猜对了!)为文本的第一个字母或第一行赋予特殊样式。

::selection 允许您更改用户在页面上选择文本时的高亮显示样式。

::before::after 允许我们使用 CSS 而不是 HTML 向页面添加额外的元素。用它以各种方式装饰文本是一种常见的用例。

<style>
  .emojify::before {
    content: '😎 😄 🤓';
}

  .emojify::after {
    content: '🤓 😄 😎';
}
</style>

<body>
  <div> 让我们 <span class="emojify">emojify</span> 这个 span!</div>
</body>

使用这些伪元素会得到以下结果:
让我们 😎 😄 🤓 emojify 🤓 😄 😎 这个 span!

还有更多!快速浏览一下 伪元素文档 以查看可能性的完整列表。

属性选择器 (Attribute Selectors)

我们要添加到工具箱中的最后一个工具是属性选择器。回想一下,属性是 HTML 元素开始标签中的任何内容——例如 src='picture.jpg'href="www.theodinproject.com"

由于我们为属性编写自己的值,因此需要一个稍微更灵活的系统来能够定位特定值。

属性选择器的特异性与类和伪类相同(0, 0, 1, 0)。

让我们看一些基本用法的示例。

  • [attribute] - 此通用选择器将选择给定属性存在的任何元素。其值无关紧要。
  • selector[attribute] - 可选地,我们可以将属性选择器与其他类型的选择器(如类选择器或元素选择器)组合使用。
  • [attribute="value"] - 为了非常精确,我们可以使用 = 来匹配具有特定值的特定属性。
  [src] {
    /* 这将针对任何具有 src 属性的元素。 */
  }

  img[src] {
    /* 这只针对具有 src 属性的 img 元素。 */
  }

  img[src="puppy.jpg"] {
    /* 这将针对 src 属性恰好是 "puppy.jpg" 的 img 元素 */
  }

有时我们需要更通用地访问这些属性。例如,也许我们只对 src 属性值以 .jpg 结尾的 img 元素感兴趣。对于这种情况,我们有一些属性选择器允许我们匹配属性值的一部分。如果您曾经遇到过 正则表达式,这些属性使用类似的语法。

  • [attribute^="value"] - ^= 将从开头匹配字符串。
  • [attribute$="value"] - $= 将从结尾匹配字符串。
  • [attribute*="value"] - *= 通配符选择器将在字符串内部的任何位置匹配。
[class^='aus'] {
  /* 类也是属性!
    这将针对任何以 'aus' 开头的类:
    class='austria'
    class='australia'
  */
}

[src$='.jpg'] {
  /* 这将针对任何以 '.jpg' 结尾的 src 属性:
  src='puppy.jpg'
  src='kitten.jpg'
  */
}

[for*='ill'] {
  /* 这将针对任何在属性值中包含 'ill' 的 for 属性:
  for="bill"
  for="jill"
  for="silly"
  for="ill"
  */
}

要了解使用属性选择器可以实现的其他功能,例如不区分大小写的搜索或由连字符分隔的子字符串,请浏览 MDN 关于属性选择器的文档

作业

  1. 完成 CSS Diner。您应该熟悉前几个练习中的大部分内容,但练习和复习总没有坏处!别忘了阅读右侧的示例和解释。
  2. 阅读 Shay Howe 关于复杂选择器的文章。这更详细地涵盖了本课的大部分内容。如文章中所述,他们有时对伪元素使用单冒号而不是双冒号。请记住,双冒号现在是标准。
  3. 完成 MDN 的这个 选择器评估。它将帮助您将新学到的选择器知识付诸实践!

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

定位 (Positioning)

引言

到目前为止,您已经通过 margin、padding 和 flexbox 等技术进行了大量练习来在屏幕上移动元素。这些技术都依赖于 CSS 默认的“定位模式 (positioning-mode)”。这种默认定位模式很直观,您将继续在几乎所有的布局需求中使用它。然而,还有其他方法可供您使用,这些方法在某些情况下非常有用。

课程概览

本节概述了您将在本课中学到的主题。

  • 您将学习如何使用绝对定位 (absolute positioning)。
  • 您将学习如何使用固定定位 (fixed positioning)。
  • 您将学习如何使用粘性定位 (sticky positioning)。
  • 您将了解每种属性的区别以及如何组合使用它们。

静态定位和相对定位 (Static and relative positioning)

您已经习惯的默认定位模式是 position: static。静态定位 (static) 和相对定位 (relative) 之间的区别相当简单。静态定位是每个元素的默认位置,属性 toprightbottomleft 不会影响元素的位置。而相对定位与静态定位几乎相同,但属性 topright(等)会使元素相对于其在文档流 (flow of the document) 中的正常位置发生偏移。

绝对定位 (Absolute positioning)

position: absolute 允许您在屏幕上的精确点定位某个元素,而不会干扰其周围的其他元素。更具体地说,对元素使用绝对定位会将该元素从正常的文档流中移除,同时相对于某个祖先元素 (ancestor element) 进行定位。换句话说:从文档正常流中移除的元素不会影响其他元素,也不会受到其他元素的影响。使用绝对定位允许您使用 toprightbottomleft 属性将元素定位在屏幕上的任何位置。当您想在屏幕上的精确点定位某些内容而不干扰其他任何元素时,此属性非常有用。绝对定位的一些良好用例包括:

  • 模态框 (modals)
  • 带有标题 (caption) 的图像
  • 位于其他元素顶部的图标

在以下示例中,我们使用绝对定位在图像上方显示文本。

免责声明:绝对定位有非常具体的用例,如果可能,应优先使用 flexbox 或 grid。绝对定位不应用于进行整个页面的布局。

固定定位 (Fixed positioning)

固定定位的元素也会从文档的正常流中移除,并相对于 视口(viewport) 定位。您基本上使用 toprightbottomleft 属性来定位它,并且当用户滚动时,它将固定在该位置。这对于导航栏 (navigation bars) 和浮动聊天按钮 (floating chat buttons) 等元素特别有用。

粘性定位 (Sticky positioning)

粘性元素在您滚动经过它们之前表现得像普通元素,然后它们开始表现得像固定元素 (fixed elements)。它们也不会从文档的正常流中移除。这听起来可能令人困惑,因此请查看这个 粘性定位示例,它可能会为您澄清问题。它对于章节标题 (section-headings) 之类的内容非常有用。还记得在浏览商店时滚动过程中仍然能看到您正在查看的类别吗?就是这样实现的!

作业

  1. Web Dev Simplified 的 学习 CSS 定位 视频节奏较快,但很好地展示了不同定位行为。请观看它。
  2. MDN 关于 position 的文档 涵盖了所有关于定位的概念性细节。
  3. CSS Tricks 的页面 绝对、相对、固定定位 应该能让您对该主题有不同角度的见解。您也应该阅读它。
  4. 最后,Kevin Powell 的文章讨论了 固定定位与粘性定位的区别。这是一篇帮助您更好地理解两者差异的好文章。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

CSS 函数

引言

您可能已经注意到,在 CSS 中使用的一些属性值具有稍微不同的语法。当值是一个单词后跟一对括号 (),括号中包含信息时——例如 background-color: rgb(0, 0, 0)——您就是在使用 CSS 函数。

在本课中,我们将介绍函数的基本概念以及它们在 CSS 中的一些常见用法。

课程概览

本节概述了您将在本课中学到的主题。

  • 识别 CSS 函数的基本组成部分。
  • 了解 calc()min()max()clamp() 函数以及如何使用它们。

什么是函数?它们在 CSS 中如何使用?

与编程语言类似,CSS 中的函数是执行特定任务的可重用代码片段。函数在括号 () 之间接收“参数 (arguments)”,每个参数被函数以特定方式使用。一些常见的例子是:

color: rgb(0, 42, 255); /* 使用 rgb() 函数 */
background: linear-gradient(90deg, blue, red); /* 使用 linear-gradient() 函数 */

在这里,color 的值是函数 rgb(),它接受数字形式的参数。它处理这些数字以计算与给定三个值对应的 RGB 颜色。类似地,background 属性的值是 linear-gradient(90deg, blue, red)linear-gradient 使用给定的参数生成渐变图像。它至少需要两个颜色参数:要过渡的颜色。此外,您可以设置渐变线的角度方向(如我们在示例中所做),添加更多颜色值等。

与您在 TOP 中将使用的编程语言不同,CSS 不允许我们创建自己的函数。相反,该语言附带了一组预制的函数列表,这些函数将帮助您解决最常见的样式问题。

除了定义颜色之外,在设计网站布局和尺寸时,还有几个 CSS 函数非常有用。在考虑响应式设计时,这些函数变得尤为重要。

让我们深入了解其中的几个函数:calc()min()max()clamp()

calc()

calc() 最强大的用例包括:

  • 混合单位(如 px, rem, vh 等)
  • 能够嵌套 calc( calc () - calc () )

看看这里如何使用 calc()

:root {
  --header: 3rem; /* CSS 变量 --header */
  --footer: 40px; /* CSS 变量 --footer */
  --main: calc(100vh - calc(var(--header) + var(--footer))); /* 使用 calc() 和变量 */
}
  • --header--footer--main 都是 CSS 变量的示例。您将在下一课中学习这些。

main 设置为等于:100vh - (3rem + 40px) 的结果。换句话说:main = 100vh - (header + footer)。尽管我们混合使用了 vh、rem 和 px 单位,calc() 也在为我们处理数学计算。结合 CSS 变量,calc() 可以让我们免于重复编写 CSS 规则的烦恼。

您应该能够理解上面 CodePen 嵌入中如何使用 calc()。我们鼓励您在继续学习之前,尝试使用不同的单位和元素大小,看看会得到什么结果。

上面只是 calc() 如何影响布局的一个示例,但请记住,calc() 可能不是实现它的最佳方式。我们将在未来的课程中更多地讨论布局。

min()

min() 在帮助我们创建响应式网站方面表现出色。看看这个例子:

#iconHolder {
  width: min(150px, 100%); /* 使用 min() 函数 */
  height: min(150px, 100%); /* 使用 min() 函数 */
  box-sizing: border-box;
  border: 6px solid blue;
}

关注这行代码 width: min(150px, 100%);,我们可以得出几个观察结果:

  • 如果有 150px 可用空间给图像,它将占据全部 150px
  • 如果没有 150px 可用空间,图像将切换到父元素宽度的 100%
  • 在第一种情况下,min() 选择 150px,因为 150px150px 和父元素宽度的 100% 之间较小的(最小值)那个。
  • 在第二种情况下,它选择 100%

min() 表现为属性的最大允许值的边界,在此示例中,该边界是 150px

您可以在 min() 中进行基本的数学运算 => 例如:width: min(80ch, 100vw - 2rem);

max()

max() 的工作方式与 min() 相同,只是方向相反。它会从括号中选择最大可能的值。您可以将 max() 视为确保属性的最小允许值。

考虑给定元素的以下属性:

width: max(100px, 4em, 50%); /* 使用 max() 函数 */

从这个给定的尺寸列表中,max() 将选择最大的那个。只要 4em50% 的结果长度大于 100pxmax() 就会选择(较大的)那个。如果它们小于 100px(可能是由于用户的字体大小偏好、浏览器窗口大小或缩放级别造成的),那么 100px 将作为最大的值胜出。在此示例中,您可以将 100px 视为一个保护值:这里的 width 永远不会被设置为小于 100px

当浏览窗口特别小,或者用户通过使用浏览器的缩放功能增大内容大小时,max() 函数最有用。一开始您可能不会发现 max() 有很多用途,但对于可访问性很重要的项目来说,它是一个值得了解的好工具。

clamp()

clamp() 是使元素流畅且具有响应性的绝佳方式。clamp() 接受 3 个值:

h1 {
  font-size: clamp(320px, 80vw, 60rem); /* 使用 clamp() 函数 */
}
  1. 最小值 (320px)
  2. 理想值 (80vw)
  3. 最大值 (60rem)

clamp() CSS 函数使用这些值来设置最小值、理想值和最大值。在上面的示例中,这意味着最小的可接受字体大小为 320px,最大的为 60rem。理想的字体大小是 80vw。

作业

  1. 浏览一下 CSS 函数的完整列表 及其用法,以便了解可以实现的功能。
  2. 更深入地了解 minmaxclamp CSS 函数 的实际应用。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

自定义属性 (Custom Properties)

引言

自定义属性(也称为 CSS 变量)在编写 CSS 文件时可以成为非常有用和强大的工具。简而言之,它们允许我们在整个文件中多次引用 CSS 值。通过使用自定义属性,我们无需更新特定值的每一个实例("这种红色太浅了,让我们改变这七个选择器上的所有色调"),只需更新一个实例:自定义属性本身。不仅如此,自定义属性还可以帮助我们在整个项目中保持颜色一致性,这对于大型项目将非常有帮助。

我们甚至可以在不同上下文下重新定义自定义属性,这对于创建主题(例如如今在许多网站上看到的深色和浅色主题)非常有用。

课程概览

本节概述了您将在本课中学到的主题。

  • 如何声明自定义属性。
  • 如何在规则声明中使用自定义属性。
  • 自定义属性的作用域。
  • :root 选择器。
  • 使用媒体查询。

使用自定义属性

声明和访问自定义属性的语法与编写普通规则声明没有太大区别:

.error-modal {
  --color-error-text: red; /* 声明自定义属性 */
  --modal-border: 1px solid black;
  --modal-font-size: calc(2rem + 5vw);

  color: var(--color-error-text); /* 使用自定义属性 */
  border: var(--modal-border);
  font-size: var(--modal-font-size);
}

就是这样!首先,我们使用双连字符 (--) 后跟一个区分大小写、用连字符分隔的属性名称来声明自定义属性(color-error-textColor-Error-Text 不同),名称可以是任意的。这里使用短横线命名法 (Kebab case,用连字符分隔单词) 非常重要,因为空格无效(--color error text 不起作用)。然后,我们可以在新声明的自定义属性中存储任何有效的 CSS 值,无论是颜色值、简写值,还是更复杂的函数等等。

当我们想访问自定义属性时,我们使用 var() 函数作为 CSS 属性的值,然后将自定义属性放在括号内(包括开头的双连字符)。

回退值 (Fallback values)

var() 函数实际上接受两个参数。第一个参数我们已经介绍过,即我们要分配的自定义属性。第二个参数是可选的回退值。当提供回退值以及自定义属性时,如果自定义属性无效或尚未声明,则将使用回退值。我们甚至可以传递另一个自定义属性作为回退,该自定义属性也可以有自己的回退值!

.fallback {
  --color-text: white; /* 声明自定义属性 */

  /* 使用回退值:如果 --undeclared-property 未定义,则使用 black */
  background-color: var(--undeclared-property, black);
  
  /* 嵌套回退:先尝试 --undeclared-again,然后尝试 --color-text,最后使用 yellow */
  color: var(--undeclared-again, var(--color-text, yellow));
}

在上面的示例中,如果 --undeclared-property 未定义,我们的 background-color 属性将具有值 black。对于 color 属性,由于 --undeclared-again 未定义,它将回退到 --color-text,其值为 white。如果 --color-text 自定义属性无效或不存在,则回退到回退值,color 属性将具有值 yellow

作用域 (Scope)

在第一个示例中,您可能已经注意到我们在同一个声明块中声明并访问了自定义属性。这是因为自定义属性的作用域由选择器决定。此作用域包括声明自定义属性的选择器以及该选择器的任何后代。如果您熟悉 JavaScript 中的作用域工作原理,这种行为应该感觉有些相似。

在下面的示例中,只有带有 cool-paragraph 类的元素会获得红色背景,因为它是声明自定义属性的元素的后代。

<div class="cool-div">
  <p class="cool-paragraph">看看我酷炫的红色背景!</p>
</div>

<p class="boring-paragraph">我不在作用域内,所以我不酷。</p>
.cool-div {
  --main-bg: red; /* 在 .cool-div 上声明自定义属性 */
}

/* 在作用域内:可以访问 --main-bg */
.cool-paragraph {
  background-color: var(--main-bg);
}

/* 不在作用域内:无法访问 --main-bg */
.boring-paragraph {
  background-color: var(--main-bg); /* 无效,没有值 */
}

:root 选择器

虽然有时您可能希望限制自定义属性的作用域,但您可能希望能够在许多不相关的选择器上使用其他自定义属性。一种解决方法是在许多选择器上声明相同的自定义属性,但这违背了使用自定义属性的目的之一(轻松一次性更改多个实例的值)。

更好的解决方案是在 :root 选择器上声明这些自定义属性,该选择器本质上与 html 选择器相同,但具有更高的特异性。

<p class="cool-paragraph">Lorem ipsum dolor sit amet.</p>

<p class="exciting-paragraph">Lorem ipsum dolor sit amet.</p>
:root {
  --main-color: red; /* 在根元素上声明全局自定义属性 */
}

.cool-paragraph {
  color: var(--main-color); /* 可以访问全局属性 */
}

.exciting-paragraph {
  background-color: var(--main-color); /* 可以访问全局属性 */
}

通过在上面的示例中的 :root 选择器上声明自定义属性,我们可以在 CSS 文件中的任何其他有效选择器上访问它,因为任何其他选择器都将被视为 :root 选择器的后代。

使用自定义属性创建主题

除了允许我们更全局地访问自定义属性外,:root 选择器为我们提供了一种向页面添加主题的方法:

首先,由于我们在 CodePen 的 HTML 选项卡中没有直接访问根元素的权限,我们使用设置菜单应用了默认的 dark 类。请随意打开笔并更改此设置以查看其行为!接下来,在我们的 CSS 中,我们在 :root 选择器上为自定义属性创建了两个作用域:一个用于当 html(或根)元素具有 dark 类时,另一个用于当它具有 light 类时。然后,我们的其他选择器根据根元素上当前存在的类使用自定义属性的值。

媒体查询 (Media queries)

让用户自己切换主题很棒,但设置主题还有另一种选择,您可能在某些网站或应用程序中遇到过:使用用户的操作系统或用户代理(如浏览器)中的主题设置。这可以使用 prefers-color-scheme 媒体查询完成,该查询允许您根据用户的设备或设置(如屏幕大小或主题偏好(浅色/深色模式))应用不同的样式。

prefers-color-scheme 查询检查用户是否在其操作系统或浏览器上选择了主题。别担心,我们稍后将更详细地介绍媒体查询。现在,尝试更改您的操作系统或浏览器主题以查看示例实时更新!

我们首先在媒体查询之外的 :root 元素上添加了自定义属性。这为我们提供了默认主题,以防用户在其操作系统或用户代理上没有设置偏好,或者浏览器不支持媒体查询。在这种情况下,我们使用“浅色”主题颜色作为默认值。然后,我们为当用户在偏好设置中设置了深色主题时添加了 prefers-color-scheme 媒体查询。

使用 prefers-color-scheme 媒体查询对用户非常有帮助,因为它不需要他们手动更改主题到他们喜欢的主题。也就是说,在使用此媒体查询时,您需要注意以下几点:

  1. 只有 darklight 是媒体查询的有效值,因此您不能使用它来实现超出这两个基本主题的任何主题。
  2. 媒体查询的 light 值实际上是当用户指定了浅色主题未设置偏好时。
  3. 它不允许用户自己更改主题,在用户可能希望使用与其操作系统/用户代理首选主题相反的主题的情况下,这仍然很重要。

作业

  1. 这个关于 CSS 自定义属性的视频是一个很好的介绍。请观看它。
  2. 阅读 MDN 的使用 CSS 自定义属性页面,从“自定义属性的继承”部分开始。
  3. 阅读 CSS Tricks 文章,了解自定义属性的全面概述以及您可以用它们做什么
  4. 在此页面上打开检查器(开发者工具)以检查样式,并查看 Odin 如何使用一些自定义属性。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

浏览器兼容性 (Browser Compatibility)

引言

随着您在 Web 开发领域的不断深入,请务必记住,您作品的最终用户可能使用各种浏览器:Chrome、Microsoft Edge、Firefox 和 Safari 等等。同时,使用移动操作系统的用户数量正在快速增长,因此您还应该考虑不同浏览器的移动版本。

课程概览

本节概述了您将在本课中学到的主题。

  • 理解浏览器兼容性及其历史。
  • 了解新的 CSS 功能如何进入浏览器。
  • 了解如何检查兼容性。

浏览器历史

现代浏览的历史始于 1990 年 12 月 WorldWideWeb 浏览器的发布。它由 Tim Berners-Lee 在欧洲核研究机构 CERN 工作期间编写。后来为了避免与万维网 (World Wide Web) 混淆,它被重命名为 Nexus。

Nexus 是同类产品中的第一个,允许用户查看基本的样式表、阅读新闻组,甚至还有拼写检查功能!今天看来可能不算什么,但在当时确实是开创性的。

然而,Nexus 的发布只是一个开始,在接下来的十年里,人们见证了诸如 Mosaic 浏览器等浏览器的首次发布,它迅速流行起来并成为全球最受欢迎的浏览器。从那时起,随着 Opera 和 Netscape Navigator 浏览器的发布,万维网的发展呈爆炸式增长。

1995 年,世界迎来了 Internet Explorer 的第一个版本,它成为市场的主导者。在某个时期,超过 90% 的用户都在使用 Internet Explorer。为了对抗这种主导地位,Netscape 发起了后来成为 Mozilla 基金会的项目,该基金会开发并维护 Firefox。不久之后,苹果在 2003 年推出了 Safari,谷歌在 2008 年推出了 Chrome。

您很可能熟悉所有这些名字(即使不是全部)。尽管 Chrome(以及 Chromium)是当前市场的主导者,但浏览器之间的竞争至今依然激烈。

什么是浏览器兼容性?

今天,很难想象没有浏览器的互联网。我们已经见证了从独立应用程序向 HTML5 和渐进式 Web 应用 (Progressive Web Apps) 的转变,这些应用允许在浏览器内完全运行。例如,Microsoft Word 和 Excel 长期以来只能通过独立应用程序执行。现在,您可以通过任何浏览器使用这些应用程序,而无需安装任何文件。

随着公司争夺市场份额,不同的浏览器使用不同的引擎来显示网页信息。例如,Chrome 和 Chromium 使用 Blink 引擎,而 Safari 使用 WebKit。

由于这些差异,您的应用程序在不同浏览器中的行为可能不同。由于 Chrome 的主导地位,绝大多数应用程序都设计为在 Chromium 中流畅运行,而在其他浏览器中提供同等水平的性能则是次要的。

为了让您的 Web 开发项目覆盖更广泛的用户,您必须确保您的 Web 应用程序在用户最可能使用的浏览器上进行测试。Chrome、Safari、Firefox 和其他基于 Chromium 的浏览器(Microsoft Edge、Brave 等)在普通用户中更为常见。但根据用户群体或您工作的公司,您可能还需要支持不太常见的浏览器。对于基于 Chromium 的浏览器,如果它在 Chrome 中有效,那么在其他相关浏览器中也应该有效。

浏览器发布和新的 CSS 功能

W3C 万维网联盟是制定 Web 标准的权威机构,旨在最大化 Web 体验的可访问性和一致性。W3C 也是开发 CSS 新功能的权威机构。这是一个与 Web 社区以及开发 Web 浏览器的公司密切合作的过程。

当像 Nexus 和 Netscape 这样的 Web 浏览器发布时,还没有像 W3C 这样的组织来帮助提高兼容性。您的应用程序在每个浏览器中的外观和功能都可能不同。更糟糕的是,您的应用程序可能完全无法使用。Web 开发人员必须为每个浏览器进行特定的调整,而且并非每个开发人员都有足够的资源来为所有人实现这一点。

如今,随着 Web 标准的演变和变化,Web 开发人员开始在其代码库中实现新功能,浏览器必须提供对这些新功能的支持。如果浏览器缺乏支持而影响用户体验,用户可能会转向竞争对手的产品。

何时安全地使用新功能?

实现新功能虽然令人兴奋,但也存在急于求成的风险。例如,如果您的应用程序过去在 Firefox 中运行良好,但由于代码库的更改,现在在 Firefox 中无法使用却在 Safari 中运行良好,这对您的用户来说不会是一个积极的体验。幸运的是,有一个工具可以帮助您防止这种情况。

"Can I Use" 是一个很好的资源,可以帮助您验证新功能是否被浏览器支持。它提供了关于哪些浏览器和平台支持新技术的统计数据,甚至包括这些浏览器的哪些版本支持特定功能。

通常的好建议是,当新功能被大多数常见浏览器支持时才实施它们。这样您不太可能遇到大量用户都会遇到的问题。

移动浏览器

传统上,Web 是桌面优先的。如果应用程序在桌面浏览器上运行良好,它就是成功的。但随着智能手机的普及,每年有越来越多的用户将移动设备作为他们主要的 Web 访问设备。在世界某些地区,移动用户占绝大多数。

移动设备主要包括智能手机和平板电脑。最流行的移动操作系统是 Android 和苹果的 iOS

在开发应用程序时,您还必须考虑您的应用程序是否应完全兼容移动设备。关于移动浏览器,有几个具体事项需要牢记:

  1. 在 iOS 和 iPadOS 上,从技术上讲 Safari 是唯一支持的浏览器。是的,您可以安装 Chrome 或 Firefox,甚至可以将其设置为默认浏览器,但它们不是完整的浏览器。它们仍然使用 Safari 的渲染引擎 (WebKit)。因此,为了让您的 Web 应用程序适用于苹果用户,您必须确保支持 Safari 中使用的 WebKit 和其他技术。重要的是要记住,移动浏览器与其桌面版本并非一一对应。在桌面版 Safari 中运行良好的项目可能仍需要调整才能在同一个浏览器的移动版本上正常工作。
  2. 移动浏览器的另一个考虑因素是屏幕尺寸的多样性。实际上不可能拥有每一台物理设备进行测试,幸运的是浏览器提供了模拟其他设备的方法。需要记住的重要一点是,例如当您在 Chrome 中模拟 iPhone 时,您模拟的只是屏幕尺寸。请记住,操作系统的任何特定考虑因素将无法重现。这意味着即使模拟设备时在 Chrome 中一切功能正常,但在实际的手机或平板设备上行为可能会不同。

作业

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

框架与预处理器 (Frameworks and Preprocessors)

引言

至此,您已经编写了大量原生(vanilla)HTML 和 CSS,并学习了许多设计技巧,随着您的成长,这些技巧将继续使用。在您的经历中,您可能已经接触到有关 CSS 框架和预处理器(也称为预编译器)的信息。这两类工具都可以使编写 CSS 更高效、更少繁琐。

了解 CSS 框架和预处理器的一个实用原因是它们在工作场所中很常见。虽然初级职位的面试官可能更关注 CSS 基础(即使工作使用特定的框架或预处理器),但了解这些工具是什么,以及一旦确定需要学习它们时去哪里查找,对您是有帮助的。

您应该意识到,在您当前的学习阶段,建议在项目中继续使用原生 CSS。所有这些框架和预处理器都是围绕 CSS 构建的,因此打下坚实的基础会使得将来学习和切换任何框架或预处理器变得容易得多。在此课程期间尝试学习一个框架或预处理器,从长远来看,不如提高您的 CSS 基础技能来得高效和有价值。

课程概览

本节概述了您将在本课中学到的主题。

  • 了解什么是 CSS 框架。
  • 了解一些可用的框架。
  • 了解什么是预处理器。
  • 了解一些可用的预处理器。

框架概述

不同的框架有不同的目标。像 Bootstrap 这样的框架为您完成了许多繁重的工作,打包了常用的 CSS 代码,甚至包括图标和交互(如下拉菜单)。它们旨在抽象化编码直观、可复用和响应式元素的过程。像 Tailwind 这样的框架则旨在通过提供预命名的类(通常每个类只应用单行 CSS)来改变我们应用 CSS 的方式。CSS 框架本质上只是一个 CSS 包,您可以使用框架定义的类来访问和应用它。例如,许多框架提供一个名为 .btn 的类,它会为您的按钮添加所有需要的样式,而无需您编写任何 CSS。通常,要使用一个框架,您需要理解它期望您如何构建网站,以及它使用哪些类来应用其特定的样式集。

您应该知道有相当多的框架可用。您可能遇到的其他两个框架是 BulmaFoundation,但市面上还有更多。

框架的缺点

框架非常适合快速生成最终用户可以轻松交互的网站界面。然而,当您浏览过一些较流行的框架后,您会开始注意到由于类似地使用框架,许多您遇到的网站之间存在着惊人的相似性。除了这个问题之外,太多新开发人员在其教育过程中过早地跳入学习框架;不必练习编写原生 CSS 的前景非常诱人。结果,许多开发人员没有足够的 CSS 实践来巩固这门非常重要的语言的基础知识。

此外,如果您的基础 CSS 知识较弱,覆盖框架样式或调试页面上的样式问题会变得非常困难。理解框架在“底层”做什么至关重要,这样您才能在未来处理这些问题(请相信我们,您肯定会遇到)。

最终,框架可以帮助您快速启动和运行 - 但从长远来看,它们可能会限制您。一旦您使用框架启动了一个项目,可能很难移除它。将来,您(或您的雇主!)可能必须决定是否在项目中使用框架,以及如果使用,选择哪一个。

预处理器概述

预处理器(也称为预编译器)是帮助您更轻松地编写 CSS 的语言。它们可以减少代码重复,并提供各种节省时间和代码的功能,例如允许您编写循环和条件语句,以及合并多个样式表。

CSS 预处理器本质上是原生 CSS 的扩展,提供了一些额外的功能。当您运行预处理器时,它会获取您的代码并将其转换为原生 CSS,然后您可以将其导入到您的项目中。

预处理器确实有一些独特且有用的工具,但它们许多最有用的功能已经在原生 CSS 中实现,因此除非您认为确实需要这些功能,否则可能不值得花费精力去学习一个。例如,您已经学习了自定义属性(custom properties),这曾经是只有预处理器才能实现的功能。CSS 嵌套也曾是一些预处理器的常见优势,但现在已进入原生 CSS,并且最近开始获得更多浏览器的支持。

一些常用的标准预处理器包括 SASSLESSStylus

作业

  1. 阅读这篇 CSS 框架简要概述
  2. 浏览这篇 SASS、LESS 和 Stylus 概述
  3. 阅读关于 使用 CSS 预处理器的一些缺点。请注意,自该文章撰写以来,CSS 现在通过自定义属性实现了变量,并通过原生语法实现了嵌套。

知识检查

以下问题是反思本课关键主题的机会。如果您无法回答问题,请点击它以复习材料,但请记住,您不需要记忆或精通这些知识。

其他资源

本节包含相关内容的实用链接。它不是必需的,因此仅供参考。

Forms 表单

表单基础 (Form Basics)

引言

表单是您网站最关键的部分之一。它们是用户与后端交互的入口 - 用户在表单中提供数据,您根据这些数据执行操作。

您需要为每个可能的数据项指定适当的输入类型,因为通常有多种方式收集一条数据,但只有一种方式对用户来说最便捷。

在本课中,我们将探讨 HTML 表单的基础知识以及可用的不同类型的输入控件。

课程概览

本节概述了您将在本课中学到的主题。

  • 使用 HTML 创建表单。
  • 对表单样式设计有基本了解。

form 元素

<form> 元素是一个容器元素,类似于课程早期学过的 <div> 元素。表单元素包裹了用户在表单上将与之交互的所有输入控件。

表单元素接受两个基本属性:

  • action 属性:接受一个 URL 值,告诉表单应将其数据发送到哪里进行处理。
  • method 属性:告诉浏览器应使用哪种 HTTP 请求方法提交表单。GET 和 POST 请求方法是您最常用的两种。

创建表单元素的标记如下:

<form action="example.com/path" method="post">

</form>

表单控件 (Form controls)

要开始收集用户数据,我们需要使用表单控件元素。这些是用户在表单上交互的所有元素,如文本框、下拉框、复选框和按钮。

input 元素

<input> 元素是所有表单控件元素中最通用的。它接受 type 属性,告诉浏览器应期望什么类型的数据以及如何渲染输入元素。

文本输入如下:

<form action="example.com/path" method="post">
  <input type="text">
</form>

标签 (Labels)

单独一个输入控件用处不大,因为用户不知道应提供什么数据。我们可以为输入添加标签来告知用户期望输入的数据类型:

<label for="first_name">First Name:</label>
<input type="text" id="first_name">

placeholder 属性

为了指导用户在表单元素中输入什么,我们可以在输入字段中包含占位符文本:

<input type="text" id="first_name" placeholder="Bob...">

name 属性

我们需要让数据发送到的后端知道每条数据代表什么:

<input type="text" id="first_name" name="first_name">

name 属性用作表单提交后输入数据的引用。可以将其视为输入的变量名。

在表单外部使用表单控件

您可以在 <form> 元素外部使用 HTML 提供的任何表单控件,即使没有后端服务器可以发送数据。

type 属性

  • Email 输入:专用于电子邮件地址
<input type="email" id="user_email" name="email" placeholder="you@example.com">
  • Password 输入:掩码显示输入数据
<input type="password" id="user_password" name="password">
  • Number 输入:仅接受数字值
<input type="number" id="amount" name="amount">
  • Date 输入:提供日期选择器日历
<input type="date" id="dob" name="dob">

textarea 元素

文本区域元素提供一个可接受多行文本的输入框:

<textarea rows="20" cols="60"></textarea>

选择元素

下拉选择框 (Select dropdown)

<select name="Car">
  <option value="mercedes">Mercedes</option>
  <option value="tesla">Tesla</option>
  <option value="volvo" selected>Volvo</option>
</select>

单选按钮 (Radio buttons)

<input type="radio" id="adult" name="ticket_type" value="adult" checked>
<label for="adult">Adult</label>

复选框 (Checkboxes)

<input type="checkbox" id="pepperoni" name="topping" value="pepperoni">
<label for="pepperoni">Pepperoni</label>

按钮 (Buttons)

提交按钮 (Submit buttons)

<button type="submit">Submit</button>

重置按钮 (Reset button)

<button type="reset">Reset</button>

通用按钮 (Generic button)

<button type="button">Click to Toggle</button>

组织表单元素

字段集 (Fieldset)

<fieldset>
  <legend>Contact Details</legend>
  <label for="name">Name:</label>
  <input type="text" id="name" name="name">
</fieldset>

图例 (Legend)

<fieldset>
  <legend>What would you like to drink?</legend>
  <input type="radio" name="drink" id="coffee" value="coffee">
  <label for="coffee">Coffee</label>
</fieldset>

关于表单样式设计的说明

表单样式设计面临两大挑战:

  1. 浏览器默认样式:不同浏览器对表单控件有不同默认样式
  2. 难以/无法自定义样式的控件
    • 单选按钮和复选框需要特殊技巧实现自定义样式
    • 日期选择器等控件无法直接自定义样式

作业

表单基础

  1. 阅读并实践 MDN 表单入门指南 - 完成您的第一个表单如何构建网页表单
  2. 阅读并实践 MDN 不同表单控件指南

表单样式设计

  1. 阅读并实践 MDN 表单样式教程
  2. 阅读并实践 HTML & CSS is Hard 表单指南

知识检查

以下问题是反思本课关键主题的机会:

其他资源

表单验证 (Form Validation)

引言

验证允许我们设置特定的约束或规则,决定用户可以在输入框中输入什么数据。当用户输入违反规则的数据时,会出现一条消息,提供关于输入数据问题所在以及如何修复的反馈。

验证是精心设计表单的关键组成部分。它们有助于保护我们的后端系统免受错误数据的影响,并帮助用户与表单的交互体验尽可能简单到极致。

本课将探讨 HTML 表单中可用的一些内置验证。我们还将深入探讨如何使用 CSS 设置验证样式。

课程概览

本节概述了您将在本课中学到的主题。

  • 解释什么是表单验证。
  • 了解如何使用一些基本的 HTML 内置验证。
  • 了解如何构建自定义验证。

必填验证 (Required validation)

我们通常希望在提交表单前确保特定字段已填写,例如登录表单中的电子邮件和密码。

要使字段成为必填项,我们为其添加 required 属性:

为确保良好的用户体验并满足无障碍指南,我们应始终标明哪些字段是必填的。这通常通过像示例中那样在必填字段标签旁添加星号 () 来实现。您也可以添加一条消息,向用户阐明星号 () 的含义。

文本长度验证 (Text length validations)

有时我们希望用户在一个字段中输入最少或最多数量的文本。现实世界的例子包括 X(前身为 Twitter)状态字段中曾经使用的 140 个字符限制,或者用户名字段的最小和最大长度限制。

最小长度验证 (Minimum length validation)

<textarea minlength="3" ...></textarea>

最大长度验证 (Maximum length validation)

<textarea maxlength="140" ...></textarea>

组合验证 (Combining validations)

<textarea minlength="3" maxlength="140" ...></textarea>

数字范围验证 (Number range validations)

就像我们经常需要控制基于文本的表单控件的长度一样,在许多情况下,我们希望控制用户可以在基于数字的表单控件中输入的值范围。

我们可以使用 minmax 属性来实现这一点,它们允许我们设置表单控件可接受值的下限和上限。

最小值验证 (Min validation)

<input type="number" min="1" ...>

最大值验证 (Max validation)

<input type="number" max="6" ...>

模式验证 (Pattern validations)

为了确保从用户那里获取正确的信息,我们经常需要确保数据符合特定模式。现实世界的应用包括检查信用卡号或邮政编码格式是否正确。

要添加模式验证,我们为表单控件提供一个 pattern 属性,其值是一个正则表达式

<input type="text" pattern="\d{5}(-\d{4})?" ...>

模式属性只能用于 <input> 元素。一些输入元素已经验证符合特定模式的数据,例如:

  • email 输入字段确保输入有效的电子邮件
  • url 输入元素确保 URL 以 http 或 https 开头

验证样式设计 (Styling validations)

我们可以使用 :valid:invalid 伪类来定位通过或未通过验证的表单控件。

/* 有效输入 */
input:valid {
  border-color: green;
}

/* 无效输入 */
input:invalid {
  border-color: red;
}

结论

内置验证在确保用户输入正确数据方面效果显著。它们添加起来快速简便。然而,它们也有局限性。

有时您需要包含内置验证无法完成的验证。例如,验证密码输入和密码确认输入是否具有相同的值,或验证用户名是否已被使用。我们在验证消息的样式设计和内容方面也受到限制。

在这种情况下,我们需要发挥创意,使用 JavaScript 和 CSS 制作自定义验证。我们将在未来的课程中深入探讨如何通过 JavaScript 实现验证。

同样值得注意的是,客户端验证并非确保用户输入正确数据的万能药。为确保进入我们系统的任何用户数据的完整性,我们还应该有服务器端验证。我们将在课程后面介绍验证的这一方面。

作业

  1. 阅读并实践 MDN 的客户端表单验证指南 - 跳过“使用 JavaScript 验证表单”部分(将在后续课程中涵盖)
  2. 学习 SitePoint 的 HTML 表单和约束验证完整指南 - 跳过“JavaScript 和约束验证 API”以及“创建自定义表单验证器”部分
  3. 阅读 Silo Creativo 的文章 改善表单用户体验

知识检查 (Knowledge check)

以下问题是反思本课关键主题的机会:

其他资源 (Additional resources)

项目:注册表单

简介

本项目旨在让你有机会实践最近学到的新知识。你将为一个虚构的服务创建一个注册表单。

任务要求

步骤一:环境搭建与规划

  1. 设置你的 Git 仓库(如有需要可参考以往项目)
  2. 创建 HTML 和 CSS 文件并添加基础内容,确保文件正确关联
  3. 下载设计稿高清版本,规划 HTML 文档布局结构

步骤二:资源准备

  1. 设计稿包含大幅背景图,请自行下载所需图片(设计稿原图可在unsplash获取,务必注明图片来源)
  2. 为"LOGO"区域选择外部字体(示例使用Norse Bold字体)
  3. 侧边栏图片使用Odin图标

步骤三:关键提示

  1. 建议先搭建页面框架,再分区块实现
  2. "ODIN"标志后方需使用半透明深色背景层(增强文字在复杂背景中的可读性)
  3. "创建账户"按钮颜色取自背景图色调(色值 #596D48)
  4. 输入框默认边框色为浅灰色(#E5E7EB),注意两个特殊状态:
    • 密码无效时显示红色边框(使用 :invalid 伪类实现)
    • 选中状态的输入框显示蓝色边框和轻微阴影(使用 :focus 伪类)
  5. 暂无需考虑移动端适配(响应式设计将在后续课程讲解)
  6. 密码一致性验证需使用 JavaScript(后续课程将涉及),目前仅需单独验证各字段

Grid 网格

CSS网格布局入门

简介

在接下来的课程中,我们将学习CSS网格布局技术,它将大幅简化页面布局设计。我们将首先回顾弹性盒子(还记得Flexbox吗?),然后深入学习网格布局。

后续课程将指导您如何创建网格、定位网格项目以及使用高级属性。之后我们将深入比较弹性盒子与网格布局的差异。最终目标是通过网格布局构建一个仪表板项目。

课程概览

本节概述您将在本课学习的主题:

  • 对比弹性盒子与网格布局的基础特性
  • 分析何时应选用网格布局替代弹性盒子

弹性盒子回顾

在基础课程中您已深入学习过弹性盒子。如果您一直在使用弹性布局,本节将带您快速回顾,为学习网格布局做准备。若您对弹性盒子完全陌生,建议重新学习弹性盒子课程

弹性盒子课程涵盖沿两条轴线(主轴和交叉轴)的项目定位,以及如何设置弹性对齐。您还学习了如何让弹性项目扩展、收缩或调整尺寸,这正是弹性盒子的精髓所在——项目能够"弹性"伸缩。

您可能还记得如何创建整齐的弹性行布局:

<!-- 行布局代码示例 -->
<iframe allowfullscreen="true"...></iframe>

或弹性列布局:

<!-- 列布局代码示例 -->
<iframe allowfullscreen="true"...></iframe>

对于一维布局,弹性盒子提供了便捷方案,无需依赖浮动或CSS技巧即可精准定位项目。

对于二维布局,您已了解flex-wrap属性,它允许弹性项目自动换行(行换行或多列换行)。还记得这个卡片布局练习的挑战吗?

虽然弹性盒子支持构建行列组合的布局,但实现过程往往较为复杂。而使用CSS网格创建相同的卡片布局则简单得多:

<!-- 网格布局代码示例 -->
<iframe allowfullscreen="true"...></iframe>

什么是网格布局?

尽管网格是CSS较新的模块,但其布局理念早在1996年就由CSS联合创始人Bert Bos博士提出(灵感源于报刊杂志的网格布局)。经过多年深入演示和开发,CSS网格于2017年获得所有主流浏览器支持。

如前所示,网格布局因简化二维项目定位而备受赞誉,但它同样适用于一维布局。其优势在于:开发者可以先构建单行布局,后续再扩展为多行。

您会注意到网格与弹性盒子的诸多相似之处:两者都采用父容器包裹子项目的模式,具有类似的对齐和定位属性名。但二者也存在显著差异,业界对其适用场景有不同见解。例如,当您难以在弹性盒子中实现等尺寸项目时,网格布局会简单得多。

查阅旧资料时请注意:随着模块更新,弹性盒子与网格的差异可能变化。CSS网格的重要特性gap属性(下节课将讲解)最初仅适用于网格,现已在弹性盒子中同样可用。

尽管有人认为CSS网格将取代弹性盒子,但学完本课程您将理解:网格只是布局工具箱中的另一种工具。实际上,这两个模块各有适用场景,结合使用效果更佳——我们将在总结课中详解此点。现在让我们开始学习如何构建网格!

知识检验

以下问题帮助您回顾本课关键概念,如无法回答可点击链接复习:

扩展资源

本节提供补充学习资料(非必学):

78%Complete

CSS网格布局基础

简介

在了解CSS网格布局的概念后,本节将指导您创建网格容器、定义轨道、理解显式与隐式网格的区别,并掌握网格间距设置方法。

课程概览

本节涵盖的核心知识点:

  • 创建网格容器
  • 定义网格轨道
  • 解析显式网格与隐式网格的区别
  • 设置网格单元间距

网格构建基础

网格容器

CSS网格布局基于容器-项目模型。将元素设为网格容器后,其直接子元素自动成为网格项目:

/* 块级网格容器 */
.container {
  display: grid; 
}

/* 行内网格容器 */
.container-inline {
  display: inline-grid;
}

注意:仅直接子元素会成为网格项目。嵌套元素需单独设置才能成为网格容器。

轨道定义

通过以下属性定义网格行列轨道:

/* 定义三列(每列50px)两行(每行50px) */
.container {
  grid-template-columns: 50px 50px 50px;
  grid-template-rows: 50px 50px;
}

/* 简写形式(行定义/列定义) */
.container {
  grid-template: 50px 50px / 50px 50px 50px;
}

/* 非对称轨道(首列250px,其余50px) */
.container {
  grid-template-columns: 250px 50px 50px;
}

显式 vs 隐式网格

类型 定义方式 特性 尺寸控制
显式网格 grid-template-* 明确定义 按预设轨道布局 直接设置尺寸
隐式网格 自动生成未定义轨道 容纳超出预设的项目 通过grid-auto-*控制
/* 显式网格:2x2布局 */
.container {
  grid-template-columns: 50px 50px;
  grid-template-rows: 50px 50px;
}

/* 控制隐式行高为50px */
.container {
  grid-auto-rows: 50px;
}

/* 横向填充隐式列(默认纵向) */
.container {
  grid-auto-flow: column;
  grid-auto-columns: 50px;
}

网格间距

使用间隙属性(gap)控制轨道间距:

/* 行列独立间距 */
.container {
  column-gap: 10px; /* 列间隙 */
  row-gap: 30px;    /* 行间隙 */
}

/* 简写形式 */
.container {
  gap: 30px 10px; /* 行间隙 列间隙 */
}

知识检验

  • 如何使HTML元素成为网格项目?
    作为网格容器的直接子元素自动转换
  • 网格线之间的空间称为什么?
    网格轨道(grid track)
  • 如何设置网格沟槽(间隔)?
    使用gap/row-gap/column-gap属性
  • 当内容超出预设轨道时会发生什么?
    自动生成隐式网格轨道
  • 如何控制隐式轨道的尺寸?
    通过grid-auto-rows/grid-auto-columns设置

扩展资源

创建网格

简介

现在你知道了什么是 CSS Grid 布局,接下来你将学习如何创建自己的网格。本课将涵盖创建网格容器、添加列和行、网格背后的显式 (explicit) 和隐式 (implicit) 概念,以及如何设置网格单元格之间的间隙 (gap)。

课程概览

本节包含你将在本课中学到主题的总体概述。

  • 创建网格容器 (Make a grid container)
  • 定义网格轨道 (Define grid tracks)
  • 解释显式网格和隐式网格的区别 (Explain the difference between an explicit and implicit grid)
  • 设置网格单元格之间的间隙 (Set gaps between grid cells)

设置网格

本课将向你展示只需少量工作就能轻松创建网格布局。在接下来的课程中,你将了解关于定位和创建复杂网格的更多内容,但现在我们将从一个基础示例开始。

网格容器 (Grid container)

我们可以将 CSS Grid 理解为一个容器 (container) 和多个项目 (item)。当你将一个元素设为网格容器时,它将"包含"整个网格。在 CSS 中,通过属性 display: griddisplay: inline-grid 可以将元素转变为网格容器。

在这个例子中,标记为 class="container" 的父元素成为网格容器,其下的每个直接子元素自动成为网格项 (grid item)。CSS Grid 的便捷之处在于你不需要为每个子元素单独分配属性。

请注意,只有直接子元素才会成为这里的网格项。如果我们在这些子元素之一下面还有另一个子元素,它不会成为网格项。在下面的例子中,段落元素就不是网格项:

<!-- index.html -->

<div class="container">
  <div>项目 1</div>
  <div>项目 2
    <p>我不是网格项!</p>
  </div>
  <div>项目 3</div>
  <div>项目 4</div>
</div>

但正如你在 Flexbox 课程中学到的,网格项也可以是网格容器。所以如果你愿意,你可以在网格中创建网格。

网格中的线和轨道 (Lines and tracks in grids, oh my!)

既然你正在跟着我们的示例敲代码(对吧?),你会注意到它看起来还不像一个网格。很多关于 CSS Grid 的资源喜欢一开始就向你展示带边框和轮廓的网格表格。但是,如果你的网格容器和网格项没有任何边框,你在页面上实际上看不到这些线。所以别担心,它们仍然存在!

如果你在网页上使用开发者工具检查这些元素,你会在代码中的网格元素上注意到网格徽章。开发者工具的布局 (Layout) 选项允许你选择一个覆盖层 (overlay),可以显示这些不可见的网格线、轨道和区域。你将在下面的作业中阅读关于使用浏览器开发者工具的内容,并在下一课中了解更多关于线 (lines)、轨道 (tracks) 和区域 (areas) 的知识。

列和行 (Columns and rows)

现在我们有了包含几个网格项的网格容器,是时候指定我们的列和行了。这将定义网格轨道 (grid track)(网格上线与线之间的空间)。因此,我们可以设置一个列轨道来提供列之间的空间,设置一个行轨道来提供行之间的空间。我们将在下一课中详细讨论轨道和线,但现在让我们先创建一些列和行。

属性 grid-template-columnsgrid-template-rows 使得定义列和行轨道变得容易。在本课中,我们将坚持使用像素 (pixels) 来定义列和行。在接下来的课程中,你还将学习使用百分比 (percentages) 和分数单位 (fractional units) 进行定义。

回到我们上面的网格容器,让我们定义两列两行来放置我们的四个网格项:

如果我们想向网格添加更多列或行,我们可以定义这些值来创建额外的轨道。假设我们想在示例中添加第三列:

CSS Grid 还包含一个用于定义行和列的简写属性 (shorthand property)。在我们之前的例子中,我们可以用简写属性 grid-template 替换 grid-template-rowsgrid-template-columns 属性。在这里,我们可以一次性定义行和列。对于这个属性,斜杠 (/) 前面定义行,斜杠后面定义列。让我们保持相同的列和行值,但改用简写属性:

/* styles.css */

.container {
  display: grid;
  grid-template: 50px 50px / 50px 50px 50px; /* 行 / 列 */
}

列和行也不必共享所有相同的值。让我们改变列的属性值,使第一列的宽度是其他列的五倍:

显式 vs 隐式网格 (Explicit vs implicit grid)

让我们回到最初包含四个网格项的 2x2 布局示例。如果我们不改变 grid-template-columnsgrid-template-rows 属性,而向容器添加第五个项目会发生什么?

你会注意到我们的第五个项目被放置在网格上,并且它被放入了一个我们未定义的第三行。这是因为隐式网格 (implicit grid) 的概念,这也是 CSS Grid 能够在我们未显式定义布局时自动放置网格项的方式。

当我们使用 grid-template-columnsgrid-template-rows 属性时,我们是在显式地定义网格轨道来布局我们的网格项。但是当网格需要更多轨道来容纳额外内容时,它会隐式地定义新的网格轨道。此外,从我们的 grid-template-columnsgrid-template-rows 属性建立的大小值不会延续到这些隐式网格轨道中。但我们可以为隐式网格轨道定义值。

我们可以使用 grid-auto-rowsgrid-auto-columns 属性来设置隐式网格轨道的大小。这样,我们可以确保隐式网格为额外内容创建的任何新轨道都设置为我们定义的值。

假设我们希望任何新行保持与我们显式行轨道大小相同的值:

/* styles.css */

.container {
  display: grid;
  grid-template-columns: 50px 50px; /* 显式定义:2列 */
  grid-template-rows: 50px 50px;    /* 显式定义:2行 */
  grid-auto-rows: 50px;             /* 隐式行的大小也为50px */
}

默认情况下,CSS Grid 会将额外内容添加为隐式行 (implicit rows)。这意味着额外的元素将不断以垂直方式向下添加到网格中。希望额外内容在网格上水平添加的情况会少见得多,这可以通过设置 grid-auto-flow: column 属性来实现,并且可以使用 grid-auto-columns 属性定义这些隐式轨道的大小。

间隙 (Gap)

网格行和列之间的间隙被称为沟槽 (gutter) 或通道 (alley)。可以使用 column-gaprow-gap 属性分别调整行和列的间隙大小。此外,我们可以使用简写属性 gap 来同时设置 row-gapcolumn-gap

在添加网格间隙属性之前,让我们让内容更容易看清,而不是依赖开发者工具。我们将在网格项周围添加边框,以便更好地理解它们彼此之间的位置:

接下来,我们将使用较小的列间隙 (column gap) 来稍微间隔开我们的两列:

最后,我们将为行添加较大的间隙以突出差异:

你也可以在上面的 CodePen 中尝试使用简写属性 gap 来同时设置 row-gapcolumn-gap

完成我们的第一个网格 (Wrapping up our first grid)

现在你已经创建了一个网格,可以开始看到使用 CSS Grid 控制元素布局是多么容易。你可能也意识到 CSS Grid 如何解决常见的布局问题。在接下来的几课中,我们将涵盖元素定位和高级网格属性。但首先,请查看下面的链接,这些链接更详细地介绍了创建网格的基础知识。

作业 (Assignment)

知识检查 (Knowledge check)

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它复习相关材料,但请记住,你不需要记忆或精通这些知识。

附加资源

本节包含相关内容的实用链接。非必读,仅供参考。

定位网格元素

简介

在本课中,我们将研究网格的不同部分,并探索用于定位网格项的常用属性。

课程概览

本节包含你将在本课中学到主题的总体概述:

  • 描述轨道 (tracks)、线 (lines) 和单元格 (cells) 之间的区别
  • 通过定义起始线和结束线来定位项目
  • 使用简写符号 (shorthand notation)

回顾轨道

在深入探讨定位之前,我们先建立一些术语以更好地理解网格的不同部分。在上一课中,你了解到使用 grid-template 定义网格时,就是在定义网格将具有的轨道 (tracks)。你可以将网格轨道视为网格上的任何单一行或列。

例如,如果我们想创建一个具有 100 像素行和 100 像素列的 3x3 网格,需要定义 3 个高度为 100 像素的水平轨道和 3 个宽度为 100 像素的垂直轨道:

你会注意到此 CodePen 中有两行 CSS 被注释掉了。取消注释 .first-row 类选择器中的属性以查看第一行和第二行网格线之间的轨道。

接着,取消注释 .last-column 类选择器中的属性以查看第三列和第四列网格线之间的轨道。

线 (Lines)

每当我们创建网格轨道时,网格线会被隐式创建。这一点很重要。网格线只有在定义网格轨道后才会创建。我们无法显式创建网格线。

每个轨道都有起始线和结束线。这些线从左到右、从上到下编号,从 1 开始。因此,我们上面的 3x3 网格有 1 到 4 的垂直列线和 1 到 4 的水平行线。

网格线是我们用来定位网格项的工具。我们稍后会讲到这一点,但首先让我们使用开发者工具更深入地了解网格线。

如果你在 Chrome 中打开开发者工具,可以导航到布局 (Layout) 面板并找到网格覆盖显示设置 (Grid overlay display settings)。确保启用显示行号 (show line numbers)。从网格覆盖中选择正确的元素(例如,如果你正在检查我们的 CodePen,这可能是我们的 div.container)。你现在应该能看到网格线的覆盖层。

注意开发者工具还会显示与正线相对的负线。你现在不必担心负线,但要知道这为你定位网格项提供了另一个选项。

单元格 (Cells)

网格中由单个行轨道和单个列轨道共享的空间称为网格单元格 (cell)。你可以将网格单元想象成电子表格中的单元格:由行、列坐标定义的空间。

默认情况下,网格容器的每个子元素将占据一个单元格。在上面的示例中,我们的网格中有 9 个单元格(3 行 x 3 列),每个单元格内都有一个自动定位的子元素。标记为字母 "A" 的元素占据位于行轨道 1(在行网格线 1 和 2 之间)和列轨道 1(在列网格线 1 和 2 之间)的单元格。标记为字母 "H" 的项目位于行轨道 3(在行网格线 3 和 4 之间)和列轨道 2(在列网格线 2 和 3 之间)的单元格中。

但如果我们想更改网格项的顺序怎么办?或者如果我们希望项目占据多个单元格怎么办?

定位 (Positioning)

为了感受如何定位项目,我们将创建一个公寓的模拟平面图。让我们从一个 5x5 网格的公寓总面积(网格容器)开始。为了使这个示例更清晰,我们将使用背景色来区分容器空间。注意我们还在此处使用了 display: inline-grid,这样我们的容器就不会像块级框那样拉伸占用空间。这将帮助我们更好地可视化空间。

目前这是一个相当简陋的单元。为了让它不再是一个空盒子而更像一个家,我们将在网格容器中添加一些代表不同房间的项目。

我们的大多数房间代表一个网格单元格。但我们给了自己一个宽敞的客厅。(耶!)我们使用 grid-column-startgrid-column-end 定位了这个项目。它们的属性值代表我们希望它开始和结束的列网格线。

你还会注意到我们注释掉了此项目的网格行定位的属性值。取消注释 grid-row-startgrid-row-end 属性,看看我们的客厅如何通过占据第一行和第三行网格线之间的轨道变得更大。

这些属性允许我们使用现有的网格线来告诉每个项目应该跨越多少行和列。花点时间在这里尝试不同的属性值。如果线编号令人困惑,请使用开发者工具检查容器以显示网格覆盖层。

接下来,我们需要更有效地利用空间。我们将让其余房间跨越多个网格单元格并填满公寓的其余部分。

现在我们有了整个公寓的蓝图。如果你查看 #kitchen 选择器,你会看到我们在此处使用了简写属性。grid-column 只是 grid-column-startgrid-column-end 的组合,两个值之间用斜杠分隔。而 grid-row 是设置项目行定位的简写版本。

我们的平面图有一个问题:浴室和厨房位于公寓的两端。如果我们将这两个房间背靠背放置,可以节省管道费用。现在花点时间尝试更改浴室、卧室和壁橱的起始和结束位置,使浴室紧邻厨房。你可以使用完整或简写属性。

grid-area

你现在知道如何使用行线和列线定位网格项了。但还有其他定位项目的方法,这可能会让人有点困惑。

有一种更短的简写方式可以使用起始线和结束线定位网格项。你可以将 grid-row-start / grid-column-start / grid-row-end / grid-column-end 合并为一行,使用 grid-area

我们的客厅可以这样写:

/* styles.css */

#living-room {
  grid-area: 1 / 1 / 3 / 6; /* 行起始 / 列起始 / 行结束 / 列结束 */
}

grid-area 也可以指代其他几种用法。

除了使用网格线定位网格中的所有项目外,我们还可以用文字创建网格的可视化布局。为此,我们使用 grid-area 为网格上的每个项目命名。

因此我们的客厅可以这样写:

/* styles.css */

#living-room {
  grid-area: living-room; /* 命名为"客厅" */
}

我们可以为所有网格项执行此操作,并为每个房间赋予一个 grid-area 值作为名称。然后我们可以使用 grid-template-areas 在网格容器上映射整个结构。

哇!你可能需要打开 CodePen 浏览器并将其放大到足以逐行阅读 grid-template-areas 布局。但这个工具为我们提供了一种完全不同的定位项目的方式。

我们甚至可以使用 . 表示空单元格。假设我们的公寓可能会安装热水器和洗衣机/烘干机。我们可能不确定确切的布局,但可以通过在浴室和厨房中留出更多空间轻松可视化一些空间:

现在你知道了在网格项上使用 grid-area 的两种截然不同的方式。你甚至可能会看到术语 "grid area" 指代一组单元格。例如,客厅的所有网格单元一起构成一个网格区域。公寓类比应该会有所帮助。一个网格项可以占据多个单元格,形成网格的一个区域,就像公寓中一个有四面墙的房间。

总结

当你完成作业时,会遇到更多术语,如 spanauto,用于在轨道上定位网格项。还有类似于 Flexbox 的用于对齐网格项的属性。学习所有这些术语以及如何定位项目的最佳方法是通过大量练习!

作业

  1. 阅读 MDN 的 使用 CSS Grid 进行基于线的定位

  2. 完成我们 CSS 练习仓库中 intermediate-html-css/positioning-grid 目录的练习(不要忘记说明在 README 中):

    • 01-basic-holy-grail

    注意:完成此练习时,请使用所有需要的文档和资源来完成它。此时你不需要记住任何内容。查阅文档、使用 Google,做你需要做的事情(除了查看解决方案)来完成它们。

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它复习相关材料,但请记住,你不需要记忆或精通这些知识:

附加资源

本节包含相关内容的实用链接。非必读,仅供参考:

高级网格属性

简介

到目前为止,你已经学会了如何创建网格、调整轨道大小以及在特定行和列中定位网格元素。现在你已经拥有了创建任何静态网格布局所需的工具,但是当你希望网格更具响应性、动态性或可复用性时,该怎么办呢?

在本课中,你将学习一些更高级的 Grid 属性来实现这些目标。

课程概览

本节包含你将在本课中学到主题的总体概述:

  • 使用 repeat 函数更轻松地创建多个网格轨道
  • 使用 fr 单位而非固定尺寸创建网格轨道
  • 设置最小、最大和理想的轨道尺寸边界
  • 使用 auto-fitauto-fill 创建具有动态行/列数的网格
  • 结合使用 auto-fit/auto-fillminmax() 创建响应式网格

设置

我们将以实践方式学习本课。让我们设置一个包含五列两行的网格,并应用一些样式以便更清晰地观察:

在这个例子中,HTML 非常简单,但让我们快速讨论一些与课程无关的 CSS,以便你理解为什么使用它们:

grid-item, p, img 选择器

这里的所有属性都是为了美化网格项,都比较直接,无需深入细节。

grid-container

对于容器,显然使用 display: grid 将其渲染为 CSS Grid。但下一个属性可能不熟悉:resize: both。这个属性允许用户通过点击并拖动右下角来调整容器大小。当我们开始使用根据网格大小调整轨道尺寸的属性时,这将非常有用。

调整缩放级别:确保以 0.5x 或 0.25x 查看 CodePen 嵌入,以便有空间调整容器。

我们使用 overflow: auto 以便在容器调整得比网格能容纳的更小时启用滚动。

使用 gappadding 属性建立"沟槽"区域,以便更好地可视化网格项。

borderbackground-color 用于美化容器外观。

css

grid-template-rows: 150px 150px;
grid-template-columns: 150px 150px 150px 150px 150px;

这是我们本课的重点。为了创建两行五列,我们手动定义了每个行和列的轨道尺寸。

唉,太繁琐了!

好吧,也许没那么繁琐。但在这个例子中,我们只创建了 2x5 网格,最多容纳十个项。想象一下为一个能容纳数百个项的网格手动定义每个列和行!

这时 repeat() 登场了。

repeat 函数

repeat() 是一个可用于 CSS Grid 模板属性的 CSS 函数,允许我们定义行或列的数量及其尺寸,而无需手动输入每个轨道的尺寸。例如,我们上面的设置:

.grid-container {
  grid-template-rows: 150px 150px;
  grid-template-columns: 150px 150px 150px 150px 150px;
}

可以改写为:

.grid-container {
  grid-template-rows: repeat(2, 150px);
  grid-template-columns: repeat(5, 150px);
}

自己试试看:

分数单位 (fractional units)

现在我们知道如何快速创建多个网格轨道,是时候学习如何让它们变得动态。这里的动态意味着"灵活的"或"以某种方式响应"。动态的反面是静态,即固定在特定定义的高度,如 150px(我们在示例网格中使用的)。

使网格项动态的最基本方法是使用分数单位(fractional units),也称为 fr

fr 单位是一种分配网格中剩余空间的方式。例如,如果我们有一个总宽度为 400px 的四列网格,每列轨道分配 1fr 作为大小,所有网格项将被分配 400px 空间的一个分数,即 100 像素。

让我们看看在示例网格中,将列和行轨道从静态宽度 150px 改为动态宽度 1fr 会发生什么:

注意我们的网格项现在如何填满整个网格的宽度和高度?很酷吧?现在尝试调整此示例的大小,看看会发生什么。更酷!

我们还可以告诉网格项按比例分配剩余空间。例如,如果我们将前两列轨道大小设为 2fr,其余三列设为 1fr,前两轨道将获得其他轨道的两倍剩余空间。将此示例与前一个进行比较:

在此示例中,grid-template-columns 中有很多内容,但花点时间理解其中的内容:

grid-template-columns: repeat(2, 2fr) repeat(3, 1fr);

我们继续在此使用 repeat() 函数,但这也可以用传统方式编写!

关键是前两列分配了 2fr 单位,其余三列分配了 1fr。这意味着随着网格动态增大和缩小,空间将按不同比例在这些列之间分配,具体来说,前两列获得的像素数是其余三列的两倍。

请注意,调整大小时,网格项会按分配的 fr 单位数量成比例增长。

你还可以混合使用静态单位(如 px)和动态单位(如 fr):

此时你可能已经注意到,当将网格调整到最大时,网格项的大小没有上限。但是,当将其调整到最小时,网格允许其项达到的"最小"尺寸是明显的。在这种情况下,这是 <p><img> 元素在不溢出的情况下能达到的最小尺寸。这个断点是项的 min-content 值。这个 CSS 关键字非常有用,但超出了本课的范围。更多信息,请查看文档

最小和最大轨道尺寸:min() 和 max()

当我们将网格调整得非常小时,浏览器会阻止项缩小到 min-content 值以下,这让人放心。但在大多数情况下,我们真的不想依赖于此。作为开发者,明确决定内容的最小和最大尺寸要好得多,即使在最极端的情况下也是如此。

我们在之前的CSS 函数课程中学习了 min()max(),但复习一下也无妨。这两个函数将根据提供的参数返回值。min() 将返回传入值中最小的,max() 将返回最大的。例如,min(100px, 200px) 每次返回值 100px,而 max(100px, 200px) 每次返回值 200px

你可以向这些函数提供任意数量的参数:

.grid-container {
  grid-template-rows: repeat(2, min(100px, 200px, 300px, 400px, 500px));
  grid-template-columns: repeat(5, max(100px, 200px, 300px, 400px, 500px));
}

当然,向这些函数提供静态单位是愚蠢的,因为计算毫无意义:总是返回最小或最大值。在上面的示例中,网格行大小始终为 100px(五个值中最小的),网格列大小始终为 500px(五个值中最大的)。但是当我们提供一个动态值作为这些参数之一时,特别是在 Grid 的上下文中,我们解锁了这些函数的真正潜力:

.grid-container {
  grid-template-rows: repeat(2, min(200px, 50%));
  grid-template-columns: repeat(5, max(120px, 15%));
}

在这种情况下,网格行大小将根据值 200px 和网格容器高度的 50% 计算。浏览器会实时比较这两个值,并将最小的值应用到我们的网格行大小上。本质上,这告诉网格轨道大小应为网格总垂直空间的 50%(因为我们定义的是行大小),除非该数字超过 200px。实际上,你是在设置轨道的最大高度。

相反,网格列大小将根据 120px 和网格容器宽度的 15% 这两个值中较大的一个计算。这样,我们实际上将网格列大小的最小宽度设置为 120px。查看此示例,并尝试点击并拖动以更改网格尺寸,看看网格项如何响应:

动态最小和最大尺寸

minmax()

minmax() 是一个专门用于 Grid 的 CSS 函数。它只能用于以下 CSS 属性:

  • grid-template-columns
  • grid-template-rows
  • grid-auto-columns
  • grid-auto-rows

这是一个相对简单的函数,只接受两个参数:

  1. 网格轨道可以达到的最小尺寸
  2. 网格轨道可以达到的最大尺寸

min()max() 不同,为两个参数使用静态值有意义的。以下是我们一直使用的网格示例,使用 minmax() 和一些静态值编写:

.grid-container {
  grid-template-rows: repeat(2, 1fr);
  grid-template-columns: repeat(5, minmax(150px, 200px));
}

通过 grid-template-columns 使用 minmax() 值设置,每个网格项的宽度将随着网格容器水平调整大小而增长和缩小。但是,随着网格缩小,列轨道将在 150px 处停止缩小;随着网格增大,它们将在 200px 处停止增大。多么灵活!亲自试试下面的例子:

clamp()

minmax() 不同,clamp() 是一个可以在任何地方使用的 CSS 函数,不仅限于网格容器内。与 min()max() 一样,我们在之前的课程中学习过它,但让我们快速复习一下。语法如下:

clamp(minimum-size, ideal-size, maximum-size)

这样做是允许我们的项调整大小,直到达到最小或最大阈值。

由于 clamp() 的目的是创建具有约束的灵活尺寸轨道,我们希望为"理想尺寸"参数使用动态值,而通常为最小和最大尺寸使用静态尺寸,尽管也可以在此处使用动态值。

这是一个非网格示例。我们稍后再看网格:

.non-grid-example {
  width: clamp(500px, 80%, 1000px);
}

这个元素(可以想象只是一个 div)将以等于其父元素宽度 80% 的宽度渲染,除非该数字低于 500px 或高于 1000px,此时它将使用这些数字作为宽度。

好的,现在回到我们的网格:

.grid-container {
  grid-template-columns: repeat(5, clamp(150px, 20%, 200px));
}

注意轨道如何保持在容器宽度的 20%,直到达到最小或最大阈值?

使用 clamp()minmax() 是使网格更具响应性的绝佳方法,同时确保我们不会达到使网站外观糟糕的关键断点。当使用图像和元素时,这一点至关重要,因为它们在被推到极端尺寸时可能会溢出或以不理想的方式渲染。

auto-fit 和 auto-fill

这两个值实际上是 repeat() 函数规范的一部分,但它们被保留到课程结束,因为在理解 minmax() 函数之前,它们的用途并不明显。以下是使用场景:你希望为网格提供基于网格大小的灵活列数。例如,如果我们的网格只有 200px 宽,我们可能只需要一列。如果是 400px 宽,我们可能需要两列,依此类推。值得庆幸的是,auto-fitauto-fill 来拯救我们了!

根据 W3 关于 auto-fill 和 auto-fit 的规范,这两个函数都将返回"最大可能的正整数",而不会使网格项溢出其容器。以下是一个示例:

.example {
  display: grid;
  width: 1000px;
  grid-template-columns: repeat(auto-fit, 200px);
}

对于这个网格,我们设置了 1000px 的宽度,并告诉它用每个 200px 的轨道填充列。只要至少有五个网格项,这将始终产生 5 列布局。在这种情况下,auto-fill 实际上会做同样的事情。我们很快就会讨论区别。

当我们结合 minmax() 时,auto-fitauto-fill 的真正魔力就显现出来了。使用 minmax(),我们可以告诉网格我们希望拥有尽可能多的列,使用 minmax() 函数的约束来确定每列的大小,而不会溢出网格。看看我们现在调整大小时网格有多酷!

.grid-container {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

注意当我们调整大小时,列如何自动知道可以容纳多少列。如果你觉得这酷,最好检查一下你的脉搏!

那么 repeat(auto-fit, minmax(150px, 1fr)); 具体发生了什么?记住 auto-fit 将返回最大的正整数而不使网格溢出。因此,首先,浏览器必须知道我们的网格有多宽:在这种情况下,它只是窗口的宽度(减去边距),因为我们没有明确设置它。为了这个示例,假设我们的窗口当前是 500px 宽。其次,浏览器需要知道在该宽度内可以容纳多少个网格列轨道。为此,它使用 minmax() 函数中的最小值,因为这将产生最多的项数,即 150px。如果我们的窗口是 500px 宽,这意味着我们的网格将渲染 3 列。但等等,还有更多!一旦浏览器确定了我们可以容纳多少列,它就会将列的大小调整到 minmax() 函数允许的最大值。在这种情况下,我们的最大尺寸是 1fr,因此所有三列将被分配相等的可用空间。当我们调整窗口大小时,这些计算会实时发生,结果就是你在上面示例中看到的!

auto-fill 怎么样?

在大多数情况下,auto-fill 的工作方式与 auto-fit 完全相同。区别仅在有少于一次填满整个网格行的项时才会显现。当网格扩展到可以容纳另一个网格项的大小,但没有更多项时,auto-fit 会将网格项保持在其 max 大小。使用 auto-fill,一旦有空间添加另一个网格项(即使没有要渲染的项),网格项将立即回落到其 min 大小。随着网格扩展并有更多空间用于新网格轨道,它们将继续其增长到 max 然后回落到 min 的模式。

要查看实际操作,请看以下两个示例,第一个使用 auto-fit,第二个使用 auto-fill,看看当水平调整网格大小时会发生什么:

就是这样!恭喜,如果你已经学到这里,你正走在成为网格大师的路上!

作业

  1. 复习 CSS-Tricks 的Grid 属性第 4 部分

  2. 按顺序完成我们 CSS 练习仓库中 intermediate-html-css/advanced-grid 目录的练习(记住说明在 README 中):

    • 01-responsive-holy-grail
    • 02-holy-grail-mockup

    注意:完成这些练习时,请使用所有需要的文档和资源来完成它们。此时你不需要记住任何内容。查阅文档、使用 Google,做你需要做的事情(除了查看解决方案)来完成它们。

知识检查

以下问题提供了反思本课关键主题的机会。如果你无法回答某个问题,请点击它复习相关材料,但请记住,你不需要记忆或精通这些知识:

附加资源

本节包含相关内容的实用链接。非必读,仅供参考:

posted @ 2025-07-30 16:38  Super_Snow_Sword  阅读(204)  评论(0)    收藏  举报