JHU-Web-开发者的-HTML-CSS-JavaScript-笔记-全-
JHU Web 开发者的 HTML、CSS、JavaScript 笔记(全)
001:开发环境配置 第1部分

在本节课中,我们将学习如何为后续的Web开发课程配置开发环境。我们将介绍完成本课程所需的核心工具,并指导你完成初步的准备工作。
课程所需工具概述
以下是完成本课程需要准备的工具列表。我们将逐一介绍它们的作用和获取方式。
- Github账户:你需要在Github.com上注册一个账户。我们将在整个课程中频繁使用Github。
- Google Chrome浏览器:我们将重度依赖Google Chrome浏览器。请下载并安装它。该浏览器自带Chrome开发者工具,我们也会广泛使用这些工具。
- 代码编辑器:你需要一个代码编辑器。我推荐使用Sublime Text 3。虽然它技术上仍处于测试版,但开发者推荐下载此版本,且其稳定性和插件生态都非常出色。其他可选的编辑器包括Visual Studio Code和Brackets。
- Git:我们需要版本控制工具Git。
- Browser Sync:这是一个开发工具,用于实现浏览器实时同步刷新。
- Node.js:为了安装Browser Sync,我们需要先安装Node.js。
工具安装详解(第一部分)
上一节我们列出了所有必需工具,本节中我们来看看其中几项的简单安装步骤。本讲座将分为两部分,第一部分(即本节)将演示一些初步设置,第二部分将分别针对Windows和Mac系统,详细讲解Git、Browser Sync和Node.js的安装。
1. Google Chrome浏览器
关于如何安装Google Chrome浏览器,这里不做详细演示。你可以通过搜索引擎找到下载链接,或者你可能已经在使用它了。Chrome开发者工具已集成在浏览器中,无需单独安装。
2. Github账户注册
我们将在课程中大量使用Github。注册过程非常简单:访问Github.com,提供用户名、邮箱并创建密码即可完成注册。
3. Sublime Text 3 代码编辑器
接下来,我们需要获取代码编辑器Sublime Text 3。
以下是下载步骤:
- 打开浏览器,搜索“Sublime Text 3”。
- 确保进入的是Sublime Text 3的页面,而不是Sublime Text 2。
- 在下载页面,你可以看到针对Mac、Windows(32位/64位)和Linux系统的不同版本。
- 根据你的操作系统选择对应的版本进行下载。
- 下载完成后,根据你的系统进行安装(双击安装程序或拖拽安装)。
本节总结与下节预告
本节课中我们一起学习了开发环境配置的初步工作,包括了解所需工具、注册Github账户以及下载Sublime Text 3编辑器。


我将结束本讲座的第一部分。在第二部分,我们将分为Windows和Mac两个版本,详细演示如何安装Git、Browser Sync以及Node.js。
002:开发环境配置 (macOS版) - 第2部分

概述
在本节课中,我们将继续在macOS系统上配置Web开发环境。我们将安装Git版本控制系统、Node.js运行环境以及Browser-sync工具,并验证它们是否安装成功。最后,我们会创建一个简单的测试网站来初步体验Browser-sync的实时刷新功能。
安装Sublime Text编辑器
上一节我们介绍了课程的基本情况,本节中我们来看看如何在macOS上安装并配置开发工具。
首先,我已经下载了Sublime Text编辑器。以下是安装步骤:
- 双击下载的安装文件。
- 在弹出的窗口中,将Sublime Text图标拖拽到“应用程序”文件夹。
- 安装完成后,可以通过
Command + 空格键打开聚焦搜索,输入“sublime”来启动它。 - 首次打开时,系统会询问是否允许,点击“是”即可。
现在,Sublime Text已成功安装并可以随时使用。
安装Git版本控制系统
接下来,我们需要安装Git。Git是一个分布式版本控制系统,对于代码管理至关重要。
以下是安装Git的步骤:
- 打开浏览器,搜索“download Git for Mac”并访问官网。
- 下载适用于Mac的最新版本安装包。
- 下载完成后,双击打开
.dmg文件。 - 在打开的窗口中,双击
.pkg安装包文件。 - 如果系统提示“无法打开,因为来自未识别的开发者”,需要进入“系统偏好设置” > “安全性与隐私”,点击“允许从以下位置下载的应用程序”下的“任何来源”。(完成安装后,建议将此设置改回以增强安全性)
- 解锁设置后,再次双击安装包,按照提示输入管理员密码完成安装。
为了验证Git是否安装成功,我们需要打开终端(Terminal)应用程序。可以通过 Command + 空格键 搜索“terminal”来打开它。
在终端中输入以下命令并回车:
git --version
如果终端显示类似 git version 2.6.3 的版本信息,则说明Git已成功安装。
安装Node.js与Browser-sync
为了安装Browser-sync这个能实时刷新浏览器的工具,我们首先需要安装它的运行环境——Node.js。
以下是安装Node.js的步骤:
- 在浏览器中搜索“download Node.js for Mac”并访问官网。
- 下载Mac安装程序(.pkg文件)。
- 下载完成后,双击打开并按照安装向导的提示进行操作,同意许可协议并输入管理员密码。
安装完成后,最好重启一下终端应用程序,以确保环境变量生效。然后,我们可以验证Node.js及其包管理工具npm是否安装成功。
在终端中依次输入以下两个命令:
node --version
npm --version
这两个命令应分别返回Node.js和npm的版本号,例如 v6.9.1 和 3.10.8。
现在,我们可以安装Browser-sync了。在浏览器中搜索“browser sync”找到其官网。根据官网说明,我们需要使用npm进行全局安装。
由于macOS系统的权限设置,建议使用 sudo 命令来获取管理员权限进行安装。在终端中输入以下命令:
sudo npm install -g browser-sync
输入命令后,系统会提示你输入当前用户的管理员密码。输入密码(输入时不会显示字符)后按回车。
安装过程中可能会出现一些警告信息,这通常是正常的。如果系统提示需要安装“Command Line Developer Tools”,请点击“安装”,这是开发所必需的工具包。
安装完成后,输入以下命令验证Browser-sync是否可用:
browser-sync --version
如果终端返回版本号,则表明安装成功。
创建测试网站并体验Browser-sync
现在,让我们创建一个简单的测试网站,并体验Browser-sync的实时预览功能。
首先,我们在终端中创建一个名为“test_site”的目录,并进入该目录:
mkdir test_site
cd test_site
接着,打开Sublime Text,创建一个新的HTML文件。输入 html 然后按 Ctrl + 空格键 可以快速生成HTML文档模板。
在模板的 <body> 标签内,我们添加一个标题:
<h1>Hello, Coursera.</h1>
将这个文件保存到刚才创建的 test_site 目录下,命名为 index.html。
现在,回到终端,确保当前目录是 test_site,然后启动Browser-sync服务器。我们使用以下命令,让它以目录模式启动,并监视所有文件的变化:
browser-sync start --server --directory --files "*"
命令解释:
start --server:启动一个本地服务器。--directory:如果请求的是目录,则显示目录文件列表(方便我们选择index.html)。--files "*":监视当前目录下所有文件的变动。
执行命令后,终端会显示服务器已在 http://localhost:3000 运行。打开浏览器(如Chrome),访问这个地址。
在打开的页面中,点击 index.html 链接,你就能看到“Hello, Coursera.”的页面了。
此时,如果你用Sublime Text修改 index.html 文件并保存(例如将“Coursera”改为“World”),Browser-sync会自动检测到文件变化,并立即刷新浏览器页面,无需你手动操作。
总结
本节课中我们一起学习了在macOS上配置Web开发环境的后续步骤。我们成功安装了代码编辑器Sublime Text、版本控制工具Git、JavaScript运行环境Node.js以及实时预览工具Browser-sync。我们还创建了一个简单的测试网站,并实践了如何使用Browser-sync来实现代码修改与浏览器显示的同步更新。在下一部分,我们将更深入地演示Browser-sync的使用,并学习Git的基础操作以及如何将网站部署到GitHub上进行托管。
😊




003:开发环境配置 第2部分 Windows版

在本节课中,我们将要学习如何在Windows操作系统上配置完整的Web开发环境。我们将安装代码编辑器、版本控制工具以及用于实时预览的本地服务器工具。
🖥️ 设置默认浏览器
首先,我们需要设置一个默认浏览器。这里以Chrome为例。
- 打开Chrome浏览器。
- 在系统设置中,将其设置为默认浏览器。
📝 安装代码编辑器:Sublime Text
上一节我们设置了浏览器,本节中我们来看看如何安装代码编辑器。Sublime Text是一款流行的代码编辑器。
以下是安装步骤:
- 在浏览器中搜索“Sublime text”。
- 下载Sublime Text 3的安装程序。根据你的系统选择64位版本。
- 运行下载的安装文件,并接受所有默认设置完成安装。
- 安装完成后,你可以在开始菜单中搜索“sublime”,找到程序图标。
- 右键点击图标,选择“固定到任务栏”,以便快速访问。
🔧 安装版本控制工具:Git
接下来,我们需要安装Git,这是一个用于跟踪代码更改的版本控制系统。
以下是安装步骤:
- 在浏览器中搜索“download Git for Windows”。
- 点击下载链接,开始下载Git for Windows安装程序。
- 运行安装文件,在安装向导中点击“Next”。
- 当出现“Adjusting your PATH environment”窗口时,选择“Use Git from the Windows Command Prompt”选项,这允许你在命令行中使用Git。
- 在“Choosing the default terminal emulator”步骤中,选择“Use Windows‘ default console window”。
- 继续点击“Next”并完成安装。
- 为了验证安装是否成功,打开命令提示符(CMD),输入以下命令并按回车:
如果看到类似git --versiongit version 2.6.4.windows.1的输出,说明Git已成功安装。
💡 关于命令行的重要说明

在继续之前,需要澄清一个可能引起混淆的概念。本课程主要使用Mac的“终端”(Terminal)进行演示。在Windows上,与之对应的是“命令提示符”(Command Prompt或CMD)。
它们都是命令行界面(Command Line Interface),即通过输入文本命令来操作计算机的程序。
我们安装的软件(如Git、Node.js)的命令在Windows和Mac上是相同的。但是,文件系统相关的命令则因操作系统而异。

一个常见的区别是:
- 在Mac上,使用
ls(list的缩写)命令来列出目录中的文件。 - 在Windows上,需要使用
dir(directory的缩写)命令来达到相同目的。
🚀 安装Node.js与npm
现在,我们来安装Node.js。Node.js是一个JavaScript运行环境,我们将使用它附带的npm(Node Package Manager,Node包管理器)来安装其他工具。
以下是安装步骤:

- 在浏览器中搜索“download Node.js for Windows”。
- 进入官网,根据你的系统(例如64位)下载对应的
.msi安装程序。 - 运行安装文件,接受许可协议。
- 在安装选项中,确保勾选了“Add to PATH”,这样可以在任何命令行位置使用Node.js。
- 完成安装。
- 为了验证安装,请关闭并重新打开一个新的命令提示符窗口,然后分别输入以下两个命令:
node --version
如果两个命令都输出了版本号(例如npm --versionv10.15.3和6.4.1),则说明Node.js和npm均已安装成功。


🔄 安装实时预览工具:BrowserSync

最后,我们将使用npm来安装BrowserSync。这个工具可以创建一个本地服务器,并在你修改代码时自动刷新浏览器页面,极大提升开发效率。
以下是安装步骤:

- 在浏览器中搜索“browser sync”。
- 进入其官网,你会看到安装命令。
- 在命令提示符中,输入以下命令并回车:
参数npm install -g browser-sync-g表示全局安装,这样你可以在任何项目目录中使用它。 - 安装完成后,同样建议关闭并重新打开命令提示符,然后输入以下命令验证:
如果显示了版本号,说明BrowserSync已准备就绪。browser-sync --version
📋 总结
本节课中我们一起学习了在Windows 10系统上配置Web开发环境的完整流程。我们完成了以下核心工具的安装与验证:
- Sublime Text:作为我们的代码编辑器。
- Git:用于版本控制,并通过
git --version命令验证。 - Node.js 和 npm:提供了JavaScript运行环境和包管理工具,通过
node --version和npm --version验证。 - BrowserSync:用于创建具备实时重载功能的本地开发服务器,通过
browser-sync --version验证。

现在,你的开发环境已经搭建完毕。在接下来的部分,我们将学习如何使用BrowserSync来启动本地服务器并开发网站,同时也会探讨如何将你的网页部署到Github Pages上进行托管。
004:3_开发环境配置 第3部分 GitHub与浏览器同步 🚀

在本节课中,我们将学习如何创建GitHub仓库,将其配置为托管网站,并使用Git命令将本地代码同步到GitHub。此外,我们还将介绍一个名为Browser Sync的强大工具,它能在你修改代码时自动刷新浏览器页面,从而极大地提升开发效率。
创建GitHub仓库并配置页面托管
上一节我们介绍了本地开发环境的搭建,本节中我们来看看如何将我们的代码与GitHub同步,并利用GitHub Pages功能托管我们的网站。
首先,你需要登录到你的GitHub账户。接下来,我们将创建一个新的仓库(Repository),这是你提交作业和存储网站代码的地方。
以下是创建新仓库的步骤:
- 点击页面右上角你的头像旁边的加号(+)图标。
- 在下拉菜单中选择“New repository”。
- 在“Repository name”输入框中,为你的仓库命名,例如
coursera-test。 - 系统会检查名称是否可用,显示绿色对勾则表示可用。
- 你可以添加一个描述,例如“Coursera test repository”。
- 保持仓库为“Public”(公开)。如果你想创建私有仓库,则需要付费。
- 勾选“Initialize this repository with a README”选项,这通常是个好习惯。
- 最后,点击“Create repository”按钮。
现在,你的仓库已经创建好了。为了将这个仓库变成一个可以对外访问的网站,我们需要启用GitHub Pages功能。
以下是配置GitHub Pages的步骤:
- 进入你刚创建的仓库。
- 点击顶部的“Settings”(设置)选项卡。
- 向下滚动到“GitHub Pages”部分。
- 点击“Launch automatic page generator”(启动自动页面生成器)按钮。
- 你可以选择页面布局,但为了演示,我们直接滚动到底部。
- 点击“Continue to layouts”(继续选择布局),然后点击“Publish page”(发布页面)。
完成后,GitHub会为你生成一个网站,并提供一个专属URL,格式通常为 https://[你的用户名].github.io/[仓库名]。你可以在“Settings”页面的“GitHub Pages”部分再次找到这个链接。
需要注意的是,GitHub Pages功能实际上创建了一个名为 gh-pages 的特殊分支。只有这个分支里的内容才会被发布到你的网站链接上。对于初学者,建议你直接在这个分支上进行所有操作。
将GitHub仓库克隆到本地
现在,我们已经有了一个在线的仓库和网站。接下来,我们需要将这个仓库“克隆”(Clone)到本地计算机,以便在本地进行代码编辑。
首先,在GitHub仓库页面找到仓库的URL(通常以 .git 结尾),并复制它。
然后,打开你的终端(Terminal),使用 git clone 命令将仓库下载到本地。
git clone [你复制的仓库URL]
命令执行后,会在当前目录下创建一个与仓库同名的文件夹。使用 cd 命令进入这个文件夹。
cd coursera-test
默认情况下,你处于 master 分支。由于我们的网站托管在 gh-pages 分支,我们需要切换到这个分支。

git checkout gh-pages


切换后,使用 ls 命令查看目录,你会看到GitHub自动生成的网站文件。
在本地编辑并同步代码到GitHub
现在,我们可以在本地进行开发了。为了避免覆盖自动生成的首页,我们可以在仓库内创建一个子文件夹来存放我们的代码。
在终端中,创建一个名为 site 的文件夹并进入。
mkdir site
cd site
然后,你可以使用Sublime Text或其他编辑器在此文件夹中创建你的网页文件,例如 index.html。
编辑并保存文件后,我们需要使用Git命令将这些更改提交(Commit)到本地仓库,然后推送(Push)到GitHub。
以下是同步代码的基本Git命令流程:
-
添加更改到暂存区:使用
git add命令标记你想要提交的文件。git add .(
.表示添加当前目录下的所有更改) -
提交更改到本地仓库:使用
git commit命令并附上描述信息。git commit -m “我的第一个页面” -
推送更改到GitHub:使用
git push命令将本地提交上传到远程仓库(GitHub)。git push首次推送时,可能会要求你输入GitHub的用户名和密码。
完成推送后,稍等片刻,刷新你的GitHub Pages网站链接(记得在URL后加上 /site/),就能看到你刚刚创建的页面了。
之后任何代码修改,只需重复 add -> commit -> push 这个流程,即可更新你的网站。
使用Browser Sync实现实时预览
在开发过程中,频繁地手动刷新浏览器来查看更改效果非常低效。Browser Sync工具可以监听文件变化,并自动刷新浏览器,实现实时预览。
首先,确保你通过Node.js的包管理器npm全局安装了Browser Sync。
npm install -g browser-sync
安装完成后,你可以在你的项目目录(例如 site 文件夹)中启动Browser Sync。
以下是启动Browser Sync服务器的命令:
browser-sync start --server --directory --files “**/*”
这个命令做了以下几件事:
start --server:启动一个本地服务器。--directory:如果访问根目录,显示文件列表。--files “**/*”:监听当前目录下所有文件的变化。
执行命令后,Browser Sync会自动在浏览器中打开一个标签页,显示你的本地网站。现在,当你用编辑器修改并保存HTML、CSS或JavaScript文件时,浏览器中的页面会自动刷新,显示最新的修改结果。
这个工具能显著提升前端开发的体验和效率。


本节课中我们一起学习了如何利用GitHub进行代码版本控制和免费网站托管,掌握了基本的Git命令(clone, checkout, add, commit, push)来同步本地与远程代码。此外,我们还配置了Browser Sync工具,实现了开发时的浏览器实时自动刷新。现在,你的开发环境已经全部配置完毕,可以高效地开始Web开发之旅了。
005:问题咨询资源

概述
在本节课中,我们将学习在Web开发过程中遇到问题时,如何有效地寻求帮助。课程将介绍几个核心的在线资源平台,并讲解如何通过分享可运行的代码片段来更清晰地描述问题,从而获得更快、更准确的解答。
寻求帮助的在线资源
在开发过程中,尤其是Web开发,你必然会遇到需要向他人求助的情况。本节将为你介绍几个可以寻求帮助的资源平台。
Stack Overflow
一个首要的网络资源是 stackoverflow.com。这个网站拥有庞大的开发者社区,随时准备为你提供帮助。当你未来知识增长后,也可以通过这个网站回馈社区。
请务必记住这个网站。
如何有效地提问
当你提出编程问题时,如果解答者能够查看你的代码,这对他们会有很大帮助。直接将代码复制粘贴到问题中是一种方法,但提供一段实际可运行的代码通常更有助于他人诊断和解决问题。这样做能显著提高你获得快速、准确解答的几率。
以下是几个在Web开发领域特别有用的网站,它们可以帮助你分享可运行的代码。
JSFiddle
一个网站叫做 jsfiddle.net。这个网站允许你编写HTML、CSS和JavaScript代码,并通过链接分享这些代码。
操作示例:
- 在HTML面板中输入:
<h1>Hello Coursera</h1> - 点击“Run”按钮,结果面板会显示“Hello Coursera”。
- 在CSS面板中输入:
h1 { color: red; } - 再次点击“Run”,文字颜色变为红色。
- 点击“Save”按钮,你会获得一个唯一的URL。你可以复制并分享这个链接。
优势:
他人通过这个链接可以查看你的全部代码和运行结果。他们还可以“Fork”(即基于你的代码创建新副本)来修正或补充代码,方便你对比查看解决方案。

jsfiddle.net 是一个极佳的资源。
CodePen
另一个我非常喜欢的网站是 codepen.io。这个网站由Web开发社区的知名人物Chris Coyier参与创立。他创办的 css-tricks.com 本身也是一个关于CSS和HTML的优质教程与答案资源库。
codepen.io 的功能与jsfiddle类似,但它围绕网站构建了更庞大的社区。你可以查看“精选作品”,这些解决方案可能会让你惊叹,并且你能学习他们是如何通过HTML、CSS和JavaScript实现这些效果的。
操作示例:
- 创建一个新Pen(无需注册,但注册后可以保存所有作品)。
- 在HTML部分输入:
<h1>Hello Coursera</h1>,结果会自动显示。 - 在CSS部分输入:
h1 { color: red; },颜色会自动更新。 - 它还有一个类似浏览器的控制台功能。例如,在JS面板输入:
控制台会输出“Hello Coursera”。var x = "Hello Coursera"; console.log(x); - 点击“Save”后,你可以复制URL并分享给他人以寻求帮助。
我强烈建议你查看这些网站,并将它们作为资源储备。 它们不仅能用于查找资料,也能在提问时派上用场,长远来看会对你大有裨益。


总结
本节课我们一起学习了在Web开发中寻求帮助的方法。我们介绍了 Stack Overflow 这个核心问答社区,并重点讲解了如何通过 JSFiddle 和 CodePen 这两个在线代码编辑与分享平台来创建和分享可运行的代码片段,从而更清晰、高效地描述问题,获得社区的帮助。掌握这些工具将极大地提升你解决问题的效率。
006:5_第1讲 HTML简介 🧱

在本节课中,我们将要学习HTML的基础概念,了解它在Web开发中的核心作用,并认识驱动现代网页的三大核心技术:HTML、CSS和JavaScript。
什么是HTML?
HTML是超文本标记语言的缩写。我们来逐一解析这个术语的含义。

超文本指的是包含指向其他文本链接的文本。这构成了整个万维网的基础:一个文档指向另一个文档,如此不断延伸,有时还会链接回原始文档,最终形成一个巨大的网络。

如今,超媒体在网络中扮演着重要角色。你可以观看视频、收听音乐,超媒体本质上是超文本的延伸。
标记意为对内容进行标注或注释。网络的核心是内容。假设你有一篇名为《我为何热爱这门课程》的文档,这就是内容。HTML的作用就是用标记语言(如标签)来包裹这些内容,以告知浏览器或其他程序这些内容的含义。
例如,<title>我为何热爱这门课程</title> 这段代码中,内容被一个<title>标签包裹,这告诉软件这是文档的标题。这引出了第一个要点:HTML是人类可读的。这些标签看起来就像文档结构的指令,无需通过解释器就能理解文档的输出结构。
语言意味着HTML拥有自己的语法,即有正确和错误的编码方式。例如,在下面的错误代码中,</div>标签出现在</h1>标签之后,关闭顺序颠倒,这会导致解析和潜在的渲染错误。
<!-- 错误的语法示例 -->
<h1>标题</div></h1>
HTML也有自己的语义,这意味着标签名称对机器或人类都具有特定含义。
驱动Web的三大技术
接下来,我们谈谈驱动Web的三大技术。每一项都有其独特的目的,并且三者能完美地协同工作。
HTML 提供结构。它定义HTML文档包含哪些组件,例如一个标题、两个段落和一个页脚。
<h1>主标题</h1>
<p>第一个段落。</p>
<p>第二个段落。</p>
<footer>页脚信息</footer>
请注意,HTML并不告诉你这些组件在视觉上如何布局、它们看起来是什么样子、是什么颜色或字体大小。它只告诉你由哪些组件构成,就像一栋房子,你只知道有三个房间和一个厨房,但不知道厨房的颜色或墙壁的样式。
CSS 负责样式。颜色、布局、字体大小等所有样式相关的内容都由CSS处理。例如,如果我们的HTML文档中有一个标题,CSS会定义它的颜色和字体大小。
JavaScript 提供行为和功能。它为页面添加交互功能。例如,当HTML文档加载完成时会发生什么,或者点击标题时会发生什么,这些都是JavaScript的工作。

概念实例演示
让我们通过一个快速示例来理解这些概念的实际应用。

我们将查看位于examples/lecture01文件夹中的两个文件。

第一个文件是structure-only-before.html。它只是一个简单的HTML文件,包含一些HTML结构和虚拟文本数据。它有一个标题、一个主标题、几个段落和一个包含技术支持邮箱的页脚。
第二个文件是structure-only-after.html。它拥有完全相同的HTML结构,但我们插入了一些样式和行为。例如,我们将标题变为绿色并居中对齐,为第二个段落添加了边距,并且点击页脚中的技术支持邮箱时会弹出一个警告框。

在浏览器中查看这两个文档,区别显而易见。第一个文档只有朴素的结构。第二个文档中,标题变成了绿色的大号居中字体,第二个段落也居中且字体更大,点击邮箱地址会触发一个警告弹窗。
这里需要注意的关键点是:两个HTML文档的结构完全相同,区别仅在于我们添加的样式和行为。
本节总结
本节课中我们一起学习了:
- HTML是超文本标记语言,其核心作用是注释内容和定义文档结构。
- 作为一种语言,HTML有正确的语法规则需要学习。
- Web开发的三大核心技术HTML、CSS和JavaScript各司其职:HTML负责结构,CSS负责样式,JavaScript负责行为。它们分工明确,协同工作。
在下一节中,我们将简要回顾一下HTML标准的相关历史。




007:HTML发展简史

在本节课中,我们将简要回顾HTML的发展历程。了解这段历史不仅有助于我们理解这项技术是如何演进的,还能让我们认识到HTML中至今仍然适用和相关的某些方面。
早期:标准缺失的“狂野西部”时代
在1997年之前,网络世界没有统一的标准。浏览器厂商各自为政,他们随意发明新标签,对相同标签的实现方式也各不相同。这可以说是网络的“狂野西部”时代。用户访问一个网站时,可能会被告知其浏览器不兼容,必须更换浏览器才能正常浏览。
标准的诞生:W3C与HTML 4
大约在1997年,万维网联盟(W3C)制定了第一个标准——HTML 4,并很快更新为HTML 4.01。然而,这个标准较为宽松,浏览器厂商在如何实现和渲染页面方面仍有很大的自由发挥空间。
转向严格:XHTML的尝试
大约在2000年,W3C推出了另一个基于XML的规范,称为XHTML 1.0。XML是一种非常严格但清晰的标记语言。W3C希望沿用这一思路,并随后推出了XHTML 2.0。
分歧与对抗:WHATWG的成立
此时,浏览器厂商已有不完全遵循标准的历史。他们认为W3C的进程过于缓慢,且规范的发展方向是错误的。因此,几家主要的浏览器厂商联合起来,成立了另一个制定规范的组织——WHATWG。
- WHATWG 是 Web Hypertext Application Technology Working Group 的缩写。
- 与W3C相比,WHATWG的决策过程不那么民主。它设有一位总编辑,拥有最终决定权。
- WHATWG是推动现代HTML5发展的主要力量。
合作与统一:HTML5的诞生
很长一段时间里,W3C和WHATWG两个组织各行其道,互不合作。但W3C最终意识到,WHATWG是由真正重要的公司(即浏览器开发商)驱动的。大约在2007-2009年间,双方开始尝试合作。他们最终共同产出的成果,就是我们今天所知的 HTML5。
现状:双轨制与“常青”浏览器
那么,这段历史对我们有何影响?关键在于,目前有两个组织在某种程度上共同负责HTML:W3C和WHATWG。它们的职责划分如下:
- W3C 负责制定和维护官方的 HTML5标准。这是他们的核心工作。
- WHATWG 则不再为其HTML规范设定版本号。他们认为HTML是持续演进的,没有所谓的最终版本。WHATWG负责推动新特性的实现,而W3C则会将其中较为成熟、已被浏览器开始实现的部分,逐步纳入官方标准。
这种双轨制使得情况有些复杂,开发者需要跟踪哪些特性是浏览器应该支持的。这在当今“常青”浏览器的时代尤为重要。
“常青”浏览器意味着浏览器会在用户电脑上静默自动更新,无需用户手动干预。这带来了安全补丁和最新HTML5特性的自动获取。所有主流浏览器(Chrome、Firefox、Safari、Edge等)现在都是常青浏览器。
开发者实用资源
为了帮助大家更好地跟踪这些变化,以下是一些非常有用的网络资源。
W3C官方标准文档
第一个应该分享的资源是W3C的HTML5标准文档。这是一份学术性较强的阅读材料,但你可以从中查找信息。如果你想了解各方都同意的官方标准是什么,这是一个很好的起点。
Can I use... 特性兼容性查询
另一个你必须查看并纳入自己工具箱的优秀网站是 Can I use...。这个网站本身就在跟踪HTML5、CSS、JavaScript API等各种Web标准,告诉你哪些浏览器支持什么特性。
例如,如果我搜索一个较新的属性 srcset(我们稍后会讨论),它会告诉我这是一个什么属性,以及目前哪些浏览器支持它,哪些(如旧版IE)不支持。你可以据此决定是否要使用某个特定特性。
W3C标记验证服务
另一个用于检查你的HTML是否能在浏览器中正常工作的好资源是使用验证器。W3.org网站提供了一个很棒的验证器。你可以直接粘贴你网站的URL,上传HTML文件,或者复制粘贴HTML代码进行验证。如果验证通过,那么它在浏览器中正常工作的几率就非常高。
浏览器市场份额统计
在实际开发中,你不可能声称要支持网络上存在的每一个浏览器,因为浏览器种类繁多,有些已经非常过时。因此,查看浏览器统计数据总是一个好主意。W3Schools网站提供了浏览器市场份额统计。
例如,查看2015年9月的数据,你会发现Chrome占据了绝对主导地位。了解这些数据可以帮助你做出明智的决定,确定你的Web应用应该主要针对哪些浏览器进行开发和测试。
搜索引擎:日常开发的必备工具
最后但同样重要的是,像Google这样的搜索引擎。在Web开发中,几乎没有一天不需要上网查找资料。这是日常工作的一部分。如果你要从事Web开发,就必须习惯不断查找资料,因为技术一直在变化,没有人能仅凭自己就知道一切。
总结
本节课我们一起回顾了HTML的发展简史,并了解了这段历史如何影响我们今天的开发工作。我们介绍了一些用于验证HTML的资源(如W3C验证器),这能增加你的HTML在浏览器中正常工作的信心。我们还介绍了用于特性调查的资源(如Can I use...网站),它可以告诉你特定特性在特定浏览器版本中的支持情况。最后,我们了解了在哪里可以查看浏览器统计数据,以便你能明智地决定你的Web应用应该主要支持哪些浏览器。


下一节,我们将讨论HTML标签的构成,将其拆解开来,看看HTML标签是什么以及如何正确编写它。
008:HTML标签结构解析

在本节课中,我们将要学习HTML的核心组成部分——HTML标签。我们将详细解析标签的语法结构、属性规则以及一些重要的书写规范,帮助你打下坚实的HTML基础。
HTML标签的构成
HTML的核心是标签。理解HTML标签的构成以及如何正确编写其语法非常重要。
通常,HTML标签由一个开始标签和一个结束标签组成,它们将一些内容包裹起来。例如,标签P代表段落,它告诉浏览器灰色区域内的内容应被视为一个段落。从技术上讲,P本身被称为元素,而加上尖括号<p>和</p>后,整体被称为标签。但在实际使用中,这两个术语经常互换使用。
大多数HTML标签都有与其开始标签匹配的结束标签,但并非全部如此。例如,<br>(换行)和<hr>(水平线)标签只有开始标签,没有结束标签。
HTML标签的属性
每个HTML元素都可以拥有预定义的属性。随着课程的深入,我们将学习一些最常见的属性。以下是关于标签属性你需要了解的一般知识:
属性是一个名值对,它是应用于元素本身的元数据。在下面的例子中,我们将值myID赋给了id属性。
<p id="myID">这是一个段落。</p>
每个属性对其值的含义都有自己的规则。例如,被赋值为myID的id属性在整个HTML文档范围内必须是唯一的。换句话说,网页中任何其他元素都不允许将其id属性设置为字符串myID。如果存在另一个具有相同id值的元素,则意味着该网页包含无效的HTML,这可能会破坏页面的某些样式甚至功能。
基本间距规则
现在,让我们回顾一些基本的间距规则。
- 在开始标签的尖括号
<和标签名之间不允许有空格。 - 同样,在结束标签的
</和标签名之间也不允许有空格。 - 但是,在标签本身与其任何属性之间必须至少有一个空格。
- 其他地方的额外空格(包括回车符)都会被浏览器完全忽略。
例如,在开始标签的p元素后有额外空格,或者在属性名、等号、属性值之间有额外空格,这些都会被忽略。
还有一个规则:属性只能在开始标签上指定,不能在结束标签上指定。
属性值的引号规则
接下来,让我们谈谈属性值。在HTML5中,从技术上讲,并非在所有情况下都必须用引号将属性值括起来。然而,最佳实践是始终用单引号或双引号将属性值括起来。使用单引号还是双引号在HTML中没有区别。
一个更有趣的情况是当属性值本身包含引号时。在这种情况下,唯一需要注意的是确保以与打开引号相反的顺序关闭引号。
例如:
<p title='约翰说:"你好,世界!"'>一个包含引号的标题。</p>
或者:
<p title="这是一个'嵌套'的例子">另一个例子。</p>
你从哪种引号开始并不重要,也可以根据需要嵌套任意多层引号,只要确保以正确的顺序关闭它们即可。但在实践中,很少需要超过两层引号,否则代码会变得混乱且难以阅读。
HTML5中的空标签
如果你接触过早期版本的HTML,特别是XHTML,你可能熟悉自闭合标签的概念。自闭合标签是XHTML中一种简写表示法,用于表示不包含任何内容的标签。
然而,在HTML5中,标签的语义更强。如果一个HTML5标签有能力(即它可能在未来拥有内容),那么即使当前没有内容,将其设为自闭合也是不合法的。相反,你必须提供一个开始标签和一个结束标签,并且它们之间没有空格,以表示该标签没有包裹任何内容。
例如,在HTML5中,一个没有内容的段落应该这样写:
<p></p>
而不是XHTML风格的:
<p />
课程总结
本节课中,我们一起学习了HTML标签的核心结构。我们介绍了开始标签、结束标签,以及标签可以拥有描述其自身的属性。我们学习了可以使用双引号和单引号,但如果属性值本身包含引号,则需要以相反的顺序交替使用它们。我们还讲解了在HTML5中如何指定一个没有内容的标签,其规则与早期HTML版本有所不同。


下一节,我们将讨论如何创建基本的HTML文档结构。
009:HTML基本文档结构 📄


在本节课中,我们将学习如何创建一个基本的HTML文档结构。我们将直接在编辑器中编写代码,然后使用W3C验证器来检查我们的代码是否符合标准。
概述
我们将从零开始构建一个HTML页面,了解其核心组成部分,包括文档类型声明、<html>、<head>和<body>等标签。理解这些基础结构是学习Web开发的第一步。
创建基本HTML文档
首先,我们打开一个文本编辑器,创建一个名为doc-structure-before.html的新文件。我们将从一个空白页面开始构建。
文档类型声明
每个HTML页面都应该以文档类型声明开始。其作用是告诉浏览器准备渲染HTML内容。
在HTML5中,这个声明非常简单:
<!DOCTYPE html>
<!DOCTYPE html>这几个单词可以是大写或小写。唯一需要注意的是,在<!和DOCTYPE之间不能有空格,其他地方的空格数量则没有严格限制,但为了美观,通常只保留一个空格。
这个声明在历史上更为复杂。过去,网络上有许多不符合标准的页面。为了帮助浏览器正确渲染这些旧页面,浏览器会使用文档类型声明来区分“怪异模式”和“标准模式”。如今,如果你省略这个声明,浏览器会将你的页面视为不遵循HTML标准,这可能导致布局和样式出现问题。因此,请始终使用这个简单的HTML5文档类型声明。
HTML标签结构
声明之后是<html>标签,它包含了整个HTML文档。
在<html>标签内部,首先是<head>标签。<head>标签包含了描述页面主要内容的元数据,例如字符编码、页面标题以及渲染页面所需的外部资源。
以下是<head>部分的一个例子:
<head>
<meta charset="UTF-8">
<title>页面标题</title>
</head>
我们使用<meta charset="UTF-8">来指定页面的字符编码为UTF-8,这是最常用的编码。请注意,<meta>是一个自闭合标签,没有单独的闭合标签。<title>标签是<head>中必须存在的标签,没有它HTML将是无效的。
<head>标签之后是<body>标签。<body>标签是所有对用户可见内容的根容器,通常被称为“视口”。我们在这里编写页面的主要内容。
完整的文档示例
将以上部分组合起来,一个最基本的有效HTML文档如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Coursera很酷</title>
</head>
<body>
<p>课程很棒,我学到了很多。</p>
</body>
</html>
在浏览器中打开这个文件,你将看到标题为“Coursera很酷”,并且页面上显示段落文字“课程很棒,我学到了很多。”。

验证HTML代码
编写完代码后,最好使用W3C验证器进行检查。将我们的代码复制粘贴到验证器中,如果一切正确,它会显示“文档验证成功”。这能确保我们的代码遵循Web标准。



HTML标签的嵌套规则
你可能已经注意到,HTML标签是相互嵌套的。例如,<head>标签包含了<title>标签。
嵌套HTML标签时有一条重要规则:必须先关闭最后打开的标签,然后才能关闭其父标签。如果顺序错误,HTML将是无效的。

例如,以下代码是错误的,因为<span>标签在<p>标签关闭之后才关闭:
<p>这是一段<span>文本</p></span>
正确的嵌套顺序应该是:
<p>这是一段<span>文本</span></p>
如果将错误的代码放入验证器,它会提示错误,要求我们先关闭最后打开的<span>标签。

浏览器的渲染顺序
在继续学习之前,还有一点需要注意:浏览器在打开HTML页面时,总是从上到下顺序地渲染和解释HTML代码。它首先解释文档类型声明,然后是<html>标签,接着是<head>标签,依此类推,直到遇到最后一个闭合的</html>标签。记住这个顺序对我们后续课程的学习非常重要。

总结
本节课我们一起学习了HTML文档的基本结构。我们回顾了构成一个HTML页面的最小必要元素:
- 我们学习了必须使用的HTML5文档类型声明:
<!DOCTYPE html>。 - 我们使用了第一批HTML标签:
<html>、<head>、<meta>、<title>、<body>和<p>。 - 我们了解到浏览器是从上到下顺序渲染HTML代码的。
- 我们掌握了HTML标签的嵌套规则:后开的标签必须先闭合。
- 我们实践了使用W3C验证器来检查代码的有效性。


下一节课,我们将讨论HTML的内容模型。
010:HTML内容模型

在本节课中,我们将要学习HTML内容模型的概念。内容模型定义了浏览器如何处理特定类型的元素,以及这些元素之间如何嵌套。理解内容模型对于编写结构良好、符合标准的HTML代码至关重要。
概述:什么是内容模型? 📖
内容模型这一术语,指的是浏览器应用于属于该内容模型的元素的默认行为,以及这些元素的嵌套规则。换句话说,它规定了哪些元素可以嵌套在其他哪些元素内部。
在HTML5规范之前,HTML元素要么是块级元素,要么是内联元素。HTML5将这两种内容模型拆分成了七种模型,因此情况变得稍微复杂了一些。
传统内容模型:块级与内联 🧱

尽管如此,我们仍然需要回顾一下两种传统模型。我稍后会解释为什么这样做。在传统的内容模型结构下,所有元素基本上分为两类:块级元素或内联元素。
块级元素
块级元素默认会在新的一行开始渲染。你可以用CSS改变这一点,但我们目前还不讨论CSS。这意味着,每次在HTML中指定一个块级元素时,浏览器会自动将该元素放置在文档流的新行上。
块级元素允许在其内部包含内联元素或其他块级元素。
内联元素
这与内联元素形成对比。内联元素默认在同一行上渲染。同样,你可以改变这一点,但默认情况下,它们在同一行渲染。这意味着,如果你将一堆内联元素彼此相邻放置,它们都会显示在同一行上,就像没有换行符一样。
内联元素还有一个限制:它们只能包含其他内联元素。换句话说,一个内联元素不能将块级元素作为其内容的一部分。
为何仍要学习传统模型? 🤔

我提到过,HTML5实际上用一组更复杂的内容类别取代了这些定义。那么,为什么我们现在还要学习它们呢?原因是,块级元素和内联元素之间的这种区分仍然非常实用,因为它与仍然存在的CSS规则非常吻合。
因此,尽管HTML5提出了新的内容模型名称、新的子类别和新的分类方式,但就你的编码而言,你仍然可以将所有这些视为块级元素和内联元素。这显然有点过于简化,但它是有效的。
为了更完整地说明:块级元素大致对应于HTML5新的流内容类别,而内联元素大致对应于HTML5的措辞内容类别。
代码演示:<div> 与 <span> 💻
为了演示块级元素和内联元素之间的区别,我们将查看一个名为 div_and_span.html 的HTML文档。它位于示例的 lecture05 文件夹中。

也许每个类别中最通用的元素就是 div 和 span 元素,这也是我们将在本文档中介绍的元素。
div代表“分区”,是你的最通用的块级元素。span代表“跨度”,是你的超级通用的内联元素。
让我们快速浏览一下这个文档。我们有几个 div 依次排列:div1 和 div2。然后,在 div2 后面跟着一个 span 元素。第三个 div 稍微复杂一些,因为它内部包含了一个 span 元素。
浏览器中的效果
在浏览器中查看时,你可以看到:
div1元素独占一行。div2元素也独占一行。- 即使
span1是一个内联元素,并且紧跟在div2之后,但由于div2要求独占一行,它也将下一个内联元素推到了自己的行上。 - 对于
div3也是如此:即使span1是内联元素,理论上下一个标签应该紧跟在span1后面,但由于div3是块级元素,它要求独占一行,因此被推到了下一行。 span2标签位于div3内部,由于它是内联元素,不会引起任何额外的格式变化,它只是位于div3内部,不需要新行。

关于换行符的说明
需要明确的是,div 标签后面的换行符对HTML页面的渲染方式绝对没有影响。我可以删除所有换行符,保存文档并在浏览器中再次预览。你会看到,在格式化和要求新行方面,没有任何变化。


验证嵌套规则 ✅

作为最后一步,让我们尝试从页面中复制代码,并在W3C验证器中进行验证。
如你所见,页面是有效的。但是,如果我在验证器中,直接在 span 标签内添加另一个带有内容的 div 标签并关闭它,然后再次检查页面,会发生什么呢?
现在你会看到它报错了:在此上下文中,不允许将 div 元素作为 span 元素的子元素。它告诉你 div 元素可以使用的上下文是流内容,而 span 元素的内容模型是措辞内容。再次强调,措辞内容大致对应于内联内容,流内容大致对应于块级内容。
进一步探索 🔍
我提供几个URL供你进一步探索HTML5内容模型的不同类别。我想快速演示其中一个,即W3C的“内容种类”部分,它基本上列出了HTML5定义的七种内容类型。这个页面的酷之处在于,你可以将鼠标悬停在图形的不同部分,看到属于该特定HTML5内容模型类别的所有不同元素。
总结 📝
本节课中,我们一起学习了:
- 内容模型的概念,它定义了元素的默认行为和嵌套规则。
- 传统的块级元素和内联元素的区别:块级元素独占一行,可包含多种内容;内联元素同行排列,只能包含其他内联内容。
- 尽管HTML5引入了更精细的分类(如流内容和措辞内容),但传统模型因其与CSS的紧密关联,在实践中仍然非常有用。
- 通过
<div>和<span>的代码示例,直观地看到了这两种元素在浏览器中的渲染差异。 - 使用W3C验证器验证了元素嵌套规则,并理解了违反规则(如在
span内嵌套div)会导致验证错误。
总而言之,我们比较了块级和内联内容类型,我们知道它们不是官方HTML5规范的一部分,但在文献和常规编码中仍然经常使用,并且它们分别大致等同于流内容和措辞内容。


接下来,我们将讨论标题,并探索一些新的HTML5语义元素。
011:标题元素与HTML5新语义标签


在本节课中,我们将要学习HTML中的标题元素以及HTML5引入的一些新的语义化标签。我们将了解“语义化”的含义,探讨这些元素的重要性,并通过实例来理解它们如何使我们的网页结构更清晰、更易于理解。
概述:什么是语义化?
首先,我们来理解“语义化”这个词的含义。语义化指的是与语言或逻辑中的意义相关。换句话说,它本身带有某种固有的含义。
当这个概念应用于HTML时,一个语义化的HTML元素意味着该元素对其所包含的内容赋予了某种意义。它告诉阅读者(无论是人类还是机器)关于这段内容的一些信息,例如其重要性或描述。这比仅仅使用一个不传达任何具体含义的通用标签(如<div>)要好得多。
使用语义化标签可能有助于提升搜索引擎排名(即SEO,搜索引擎优化)。但需要指出的是,专家们对此观点存在争议。一些专家认为,现代搜索引擎非常复杂,除了内容本身,几乎没有其他因素能显著影响页面排名。尽管如此,在标题元素(尤其是<h1>)的使用上,大家普遍认为它对SEO至关重要。

标题元素
上一节我们介绍了语义化的概念,本节中我们来看看最基础、最重要的语义化元素之一:标题元素。
标题元素从<h1>到<h6>,用于表示文档中不同层级标题的重要性。<h1>代表最重要的标题,<h6>则代表最不重要的标题。

以下是所有可用标题元素的示例代码:
<h1>这是最重要的标题 (Heading 1)</h1>
<h2>次级标题 (Heading 2)</h2>
<h3>再次级标题 (Heading 3)</h3>
<h4>标题 4 (Heading 4)</h4>
<h5>标题 5 (Heading 5)</h5>
<h6>最不重要的标题 (Heading 6)</h6>

在浏览器中,这些元素默认具有不同的样式(如字体大小、粗细),以视觉方式区分其重要性。
关于标题元素,有几点非常重要:
- 不应仅用于样式:虽然浏览器默认赋予了它们视觉上的区分,但这些元素的本意是传达HTML页面的结构,而非样式。任何元素都可以通过CSS被设计成类似标题的样子。
- 使用
<h1>的原因:如果我们用<div>代替<h1>,就会失去“这是一个最重要标题”的语义含义。 <h1>的重要性:被标记为<h1>的内容显然是整个页面最重要、最概括的描述。尽管专家对语义标签的SEO效果有争议,但一致认为<h1>标签的使用至关重要,它应包含能准确传达页面核心主题的词语。
HTML5 新语义标签
了解了基础的标题元素后,我们现在来探索HTML5引入的一系列新的语义化标签,它们能帮助我们构建更具描述性的页面结构。
以下是一个使用了多种HTML5语义标签的示例文档结构:
<header>
<nav>
<!-- 导航链接 -->
</nav>
<h1>网站主标题</h1>
</header>
<section>
<article>
<!-- 文章内容 1 -->
</article>
<article>
<!-- 文章内容 2 -->
</article>
<article>
<!-- 文章内容 3 -->
</article>
</section>
<section>
<article>
<!-- 文章内容 4 -->
</article>
<article>
<!-- 文章内容 5 -->
</article>
</section>
<aside>
<!-- 侧边栏内容,与主内容相关但不直接 -->
</aside>
<footer>
<!-- 页脚信息 -->
</footer>
以下是这些新标签的简要说明:
<header>:通常包含页面的页头信息,如公司Logo、标语,有时也包含导航。<nav>:表示用于网站导航的内容部分。<section>:定义文档中的一个章节或区域。<article>:定义独立、完整的内容块,如博客文章、新闻故事。<aside>:表示与页面主内容相关但不直接属于主内容的部分,如侧边栏、引用框。<footer>:包含页脚信息。
需要理解的关键点是,所有这些标签在默认情况下都是块级元素,在视觉上每个都会独占一行。从纯样式角度看,我们本可以全部使用<div>标签。

然而,使用这些语义标签后,代码的结构清晰度大大提升。例如,你可以轻松看出文章1、2、3是相关的,并且与文章4、5、6分属不同的章节。这就是语义化元素的力量——它们天生就传达出结构意义。
总结
本节课中我们一起学习了HTML的语义化概念及其应用。
我们首先探讨了标题元素(<h1> 到 <h6>),理解了它们用于定义内容层级和重要性,并且<h1>元素的内容选择对SEO至关重要。
接着,我们认识了HTML5的新语义标签,如<header>、<nav>、<section>、<article>、<aside>和<footer>。这些标签本身不提供比<div>或<span>更多的功能,但它们允许我们以更具意义的方式来表达HTML代码的结构,使代码对人类阅读者和机器都更加友好。


记住,精心设计的语义化结构是编写高质量、可维护网页的基础。下一讲,我们将讨论如何使用列表来组织内容。
012:第7讲 列表


📋 概述
在本节课中,我们将要学习HTML中的列表。列表是一种非常有用的HTML结构,它允许你将相关的内容进行分组。我们作为普通人,通常会以列表的形式来思考事物,例如待办事项列表、购物清单等。因此,列表作为一种组织工具,对我们来说非常自然。接下来,我们将直接进入代码,看看如何在HTML中实现这些列表。
📝 无序列表
上一节我们介绍了列表的基本概念,本节中我们来看看如何创建无序列表。无序列表适用于项目之间没有特定顺序的情况,例如购物清单。
首先,我们来看一个典型的晚餐购物清单示例,包含牛奶、甜甜圈、饼干和胃药。在HTML中,如果我们只是简单地分行书写这些项目,浏览器会忽略换行符和多余的空格,最终只会显示为用空格分隔的一行文本。为了创建带项目符号的列表,我们需要使用无序列表。

创建无序列表的方法是使用 <ul> 标签(unordered list的缩写)包裹整个列表内容,列表中的每一个项目都需要用 <li> 标签(list item的缩写)包裹。

以下是创建无序列表的步骤:
- 使用
<ul>标签开始一个无序列表。 - 使用
<li>标签包裹列表中的每一个项目。 - 使用
</ul>标签结束无序列表。
有时,列表项内部可能包含子列表。例如,“饼干”这个项目下可能还有更细的分类。处理方法是,在“饼干”这个 <li> 标签的内部,再创建一个新的 <ul> 列表来包含其子项目。

完成代码编写后,在浏览器中刷新页面,就能看到一个带项目符号的无序列表了。


最后,确保我们编写的HTML代码是有效的非常重要。我们可以使用W3C验证器来检查代码。有效的HTML意味着 <ul> 标签内部只能直接包含 <li> 标签,不能包含其他文本或元素。如果违反此规则,例如移除了某个项目的 <li> 标签,验证器就会报错,提示“在此上下文中不允许在UL内部使用文本”。
🔢 有序列表
上一节我们学习了无序列表,本节中我们来看看有序列表。有些列表项目之间有严格的顺序要求,例如操作步骤说明,这时就需要使用有序列表。

有序列表与无序列表的创建方式非常相似,唯一的区别是将外层的 <ul> 标签替换为 <ol> 标签(ordered list的缩写)。列表项依然使用 <li> 标签。
例如,一个“吃奥利奥饼干的步骤”列表就必须是有序的,以确保第一步和第二步不会混淆。在代码中,我们只需将包裹整个步骤的标签改为 <ol>。
处理有序列表中的子列表(嵌套列表)的方法与在无序列表中完全一致。如果一个步骤(如“制作双层奥利奥”)内部包含多个子步骤,我们就在该步骤的 <li> 标签内部再创建一个新的 <ol> 列表。

在浏览器中查看时,有序列表的每个项目前会显示数字序号(如1, 2, 3),而不是项目符号,清晰地表明了步骤的先后顺序。


🎯 总结
本节课中我们一起学习了HTML中的两种列表:无序列表和有序列表。列表为内容提供了一种自然且常用的分组方式,在网页中常用于组织导航菜单等内容。我们掌握了如何使用 <ul> 和 <ol> 标签来创建列表,以及如何使用 <li> 标签定义列表项。我们还学习了如何处理嵌套列表,并了解了编写有效HTML代码的重要性。
013: HTML字符实体引用 🧩

在本节课中,我们将学习HTML字符实体引用。这是一种特殊的语法,用于在网页中显示那些在HTML语法中具有特殊含义的字符,例如 <、> 和 &。我们还将了解如何使用它们来插入键盘上没有的符号,以及在某些特定场景下确保文本正确显示。
概述
HTML使用某些字符(如 < 和 >)来定义标签。如果我们想在网页内容中显示这些字符本身,而不是让浏览器将它们解释为代码,就需要“转义”它们。HTML字符实体引用就是解决这个问题的工具。本节将介绍其核心用途和常见示例。
为什么需要字符实体引用?
由于HTML将特定字符用于其语法,我们需要一种方法来区分这些字符是作为HTML代码使用,还是作为普通内容显示。如果我们希望浏览器将特殊的HTML字符解释为常规内容,就需要一种方式来“转义”它们。换句话说,我们需要告诉浏览器不要将它们解释为HTML。
具体来说,有三个字符应始终进行转义,以确保它们不会立即或日后引发渲染问题。
以下是这三个字符及其对应的实体引用:
- 小于号
<: 应使用<。 - 大于号
>: 应使用>。 - 和号
&: 应使用&。
因此,如果你在HTML中输入 <,浏览器会将其解释为一个小于字符 <,而不是一个标签的开始。
实践示例:修复问题标题
让我们通过一个例子来看看这个概念的实际应用。
我们查看一个名为 html-entities-before.html 的文档,它位于示例文件夹中。这个文档包含一段引文和一个看起来有些奇怪的标题。
标题内容原文是:
Don't be afraid to be <100% success> more.
在浏览器中查看时,标题显示不完整,只显示了“Don't be afraid to be more”。这是因为浏览器将 < 解释为一个新标签的开始,并试图寻找与之匹配的 >,导致中间的内容被忽略。
我们可以通过将特殊字符替换为对应的HTML实体引用来修复这个问题。将 < 替换为 <,将 > 替换为 >,将 % 旁边的 & 替换为 &。

修复后的代码如下:
Don't be afraid to be <100& success> more.

保存并重新加载网页后,整个标题内容就能正确显示了。
其他常用实体引用
HTML包含大量不同的实体引用。虽然无法逐一介绍,但有几个特别常见。
1. 版权符号 ©
版权符号在键盘上不常见,但可以轻松使用实体引用插入。其代码是 ©。
例如,在年份后添加 ©,就会显示版权符号 ©。
2. 不换行空格
有时我们希望几个单词始终在同一行显示,不被拆开。这时可以使用不换行空格实体引用 。
例如,将句子中的“victory nor defeat”之间的普通空格替换为 :
victory nor defeat
这样,当调整浏览器窗口宽度时,这几个单词会作为一个整体一起换行或保持在同一行。
⚠️ 注意: 很多人误用 来在单词间添加多个空格以达到排版效果(例如 critic who)。这是不正确的做法。如果需要调整间距,应该使用CSS(例如为元素添加 margin 或 padding)。 应仅用于需要阻止换行的特定词组。
3. 引号和其他符号
在某些环境下(例如一些电子邮件客户端),字符编码可能受限,导致引号等符号显示为乱码。这时可以使用实体引用来确保正确显示。
例如:
- 左双引号:
“ - 右双引号:
” - 左单引号:
‘ - 右单引号:
’
使用这些实体引用可以替代直接输入键盘引号,在某些编码设置下提供更可靠的显示。
总结
本节课我们一起学习了HTML字符实体引用。它们主要帮助避免渲染问题,特别是针对 <、> 和 & 这三个容易被浏览器误解为代码的字符。我们还看到,实体引用可以用于防范字符编码受限的环境,并能提供键盘上没有的字符(如版权符号©)。正确使用实体引用是编写健壮、兼容性好的HTML的重要一环。


接下来,我们将讨论如何通过链接让文本变得互联。
014:创建超链接 🔗


在本节课中,我们将学习超链接。超链接是构成万维网的核心元素。我们将探讨不同类型的链接,并学习如何在HTML页面中创建它们。
内部链接

首先,我们来看内部链接。内部链接指向同一网站或Web应用程序内的其他页面。
创建链接的方法是使用<a>元素,并为其设置href属性。href代表“超文本引用”。其值可以是相对URL或绝对URL。对于内部链接,我们通常使用相对URL。
以下是创建内部链接的要点:
href属性值指向目标文件。如果不提供目录信息,浏览器会假设目标文件与当前HTML文件位于同一目录。- 始终为
<a>标签设置title属性是一个好习惯。title属性会被屏幕阅读器使用,帮助视障用户浏览网页。 - 位于
<a>标签开始和结束标记之间的内容是网页中显示并可点击的部分。
在HTML5中,<a>标签既是流内容也是短语内容。这意味着它可以同时作为内联元素和块级元素使用。因此,我们可以将<a>标签包裹在<div>这样的块级元素外面,从而创建一个可点击的整个区域。这在创建可点击的Logo或公司名称区域时非常有用。

外部链接
接下来,我们看看外部链接。外部链接指向其他网站或不同域名的页面。
外部链接的创建方式与内部链接类似。其特殊之处通常在于href值以http://或https://开头。

<a>元素有一个常用属性target。当target属性值设置为_blank时,浏览器会在新标签页或新窗口中打开链接页面。这有助于防止用户完全离开你的网站。
片段标识符
另一种极其重要的链接类型是片段标识符。它用于链接到同一页面内的特定部分。
片段标识符链接的href属性格式为#后跟一个名称,例如#section1。
要标识页面中的一个部分,有两种主要方法:
- 为任何标签(如
<div>、<h2>等)设置一个id属性,其值与链接中的片段名(不含#)相同。 - 使用一个带有
name属性的<a>标签来命名该部分。
当用户点击这样的链接时,页面会自动滚动到对应的部分。片段标识符的URL可以被复制和分享,其他人打开该URL时会直接跳转到指定的页面部分。这在单页面应用程序的导航中变得尤为重要。
总结
本节课我们一起学习了如何创建超链接。我们了解了三种主要类型:指向同一站点内其他页面的内部链接、指向其他网站的外部链接(可使用target="_blank"在新标签页打开),以及链接到同一文档特定部分的片段标识符。掌握这些链接的创建方法是构建可导航网页的基础。


接下来,我们将讨论如何显示图像。
015:第10讲 图像显示

🖼️ 概述
在本节课中,我们将学习如何在HTML文档中插入图像。图像是增强网站视觉效果的关键元素,我们将了解其基本语法、属性以及最佳实践。
📝 图像基础语法
上一节我们介绍了课程概述,本节中我们来看看如何在HTML中使用图像标签。
图像在HTML中通过<img>标签显示。该标签使用src属性来指定图像文件的URL。这个URL可以是相对路径,也可以是包含外部链接的绝对路径,这与之前学过的<a>标签的href属性类似。
以下是<img>标签的基本结构:
<img src="图片路径" width="宽度" height="高度" alt="替代文本">
🔧 图像标签属性详解
了解了基础语法后,我们进一步探讨<img>标签的几个重要属性。
src:这是必需的属性,用于指定图像资源的路径。width与height:这两个属性用于定义图像的显示尺寸。虽然技术上不是必需的,但强烈建议始终指定它们。alt:此属性为图像提供替代文本。它对于辅助技术(如屏幕阅读器)至关重要,能帮助视觉障碍用户理解图像内容,同时在图像无法加载时也会显示。
🧪 实践:在页面中添加图像
现在,让我们通过一个例子来实践。我们有一个包含西奥多·罗斯福名言的HTML页面,任务是在引文前添加一张图片。
首先,我们使用一张本地图片:
<img src="images/quote.png" width="100" height="100" alt="Inspirational icon">
保存文档并在浏览器中查看,可以看到图像像文本中的一个字符一样显示在引文旁边。这直观地表明<img>标签是一个行内元素。
接下来,我们尝试使用一张来自网络的外部图片:
<img src="https://example.com/ants.jpg" width="300" height="200" alt="Ants on grass">
⚠️ 指定宽高的重要性
在网页视觉布局中,图像常作为锚点。如果图像加载缓慢,页面可能会因为不知道预留多少空间而出现跳动。
以下是未指定图像宽高时可能引发的问题:
- 布局跳动:浏览器在图像加载完成前不知道其尺寸,导致内容重排。
- 布局失效:如果图像因链接失效而无法加载,为其预留的视觉空间将消失,破坏页面布局。
我们可以通过浏览器开发者工具模拟慢速网络来观察第一种情况。即使图像未加载,只要指定了width和height,浏览器也会预留出相应的空间,保持布局稳定。
<!-- 即使此图片链接失效,布局空间仍会保留 -->
<img src="https://broken-link.com/image.jpg" width="300" height="200" alt="示例图片">
📚 总结
本节课中我们一起学习了如何在HTML中使用图像。
- 图像通过
<img>标签插入,其src属性指向图片资源。 width和height属性对于维持稳定的页面布局至关重要,应尽可能指定。alt属性提供了必要的文本描述,兼顾了可访问性和用户体验。


正确使用图像可以极大地提升网站质量,而遵循上述最佳实践能确保你的页面在各种情况下都能保持良好的结构和外观。
016:模块1总结 🎉
在本节课中,我们将对模块1的学习内容进行回顾与总结,并展望后续的学习路径。
概述

你已经完成了模块1的学习,做得很好。模块1为我们后续的编程实践奠定了重要的基础。接下来,我们将继续进入模块2的学习,而模块3则会开始为一个真实的客户编写一个真实的网站。为了从中获得最大的乐趣,我们需要模块1和模块2的基础知识作为支撑。请继续前进,我保证这会很有趣。
学习回顾与展望
上一节我们完成了模块1所有知识点的学习,本节中我们来总结一下收获并了解接下来的学习安排。
以下是模块1的核心要点回顾:
- 我们学习了Web开发的基本概念与工作流程。
- 我们掌握了HTML的基本结构、常用标签及其语义。
- 我们初步接触了CSS,了解了如何为HTML元素添加样式。
掌握了这些基础知识后,我们便为后续更深入的学习做好了准备。
后续学习路径
现在,你已经准备好进入下一个阶段。
以下是接下来的学习模块:
- 模块2:我们将深入学习CSS的布局、选择器以及响应式设计等核心概念,例如使用
flexbox或grid进行页面布局。 - 模块3:我们将综合运用HTML、CSS和JavaScript,开始动手为一个真实的客户构建一个功能完整的网站。
请保持学习的劲头,一步步扎实前进。
总结

本节课中我们一起学习了模块1的总结。我们回顾了HTML与CSS的基础,并明确了模块2和模块3的学习目标。持续学习是掌握Web开发的关键,每一步积累都至关重要。
如果你已经想给我们一个五星好评,我怎么会阻止你呢?继续加油吧!
017:CSS的强大功能

在本节课中,我们将要学习CSS(层叠样式表)技术的重要性。我们将探讨为何仅靠HTML结构不足以构建出色的网站,以及CSS如何通过改变内容的视觉呈现来极大地影响用户体验。
我之前提到过,在网络上,内容是王道。一切都关乎内容。
我们讨论过HTML定义了该内容的结构。
如果你只想要原始内容,你可以获取你的HTML文件,将它们上传到服务器,然后就完成了。然而,这将是一个巨大的错误,因为虽然内容是王道,但用户如何吸收和理解该内容,很大程度上取决于我们消费其呈现方式的体验。如果我周围有很多噪音,你可能几乎听不到我,课程的内容是相同的,但消费该内容呈现方式的体验将大不相同。
用房子来类比,没有人喜欢住在没有漂亮粉刷的石膏板、绝缘层和支撑梁的房子里。这就像是感觉只是躲避外部元素和感觉像在家里的区别。
我想说明的观点是,在现实世界的网站中,仅有结构是远远不够的。
仅仅在服务器上留下一个框架,提供一个开箱即用的网站框架来展示数据是不够的。你需要以对用户来说既美观又有用的方式来设计你的内容。颜色的使用、定位、大小以及其他方面都是其中的一部分。
层叠样式表(CSS)是提供样式化能力的技术。CSS是一种极其强大的技术。
它可以采用相同的HTML结构,并以如此截然不同的方式呈现,以至于你甚至猜不到其底层内容结构是完全相同的。让我向你展示一个网站,它不仅说明了这一点,而且应该也会让你感到相当惊讶。
我想向你展示的网站是CSS Zen Garden。这个网站的理念很简单:来自世界各地的设计师和Web开发人员可以为网站CSS Zen Garden提交他们自己的设计。他们可以重新设计网站本身。只有一个限制:他们不能更改任何HTML。HTML保持完全不变,他们提供的是CSS文件。所以他们唯一改变的是样式,他们完全不改变HTML的结构或内容。
然后,该网站会在CSS Zen Garden上展示这些设计。目前他们已经收到了超过200个设计,并托管在这个网站上。所以我想做的就是让你体会一下,样式化能对同一个网站、相同的内容、相同的HTML结构做些什么。我将演示并展示其中的几个设计。
我预先在浏览器中打开了几个标签页,我想展示给你看。这是同一个网站,你正在看的这个网站,是应用了特定外观的HTML和自定义CSS。让我上下滚动一下,展示它的样子。
让我们看看这个。这又是完全相同的网站,唯一的区别是应用了不同的CSS样式。
这又是同一个网站,应用了不同的CSS样式。当然,设计师也能够应用他们自己的图像,但布局、样式与原始版本相比是如此截然不同。
同样,这里有超过200个不同的网站、不同的样式、不同的设计师提交的不同风格,你当然可以去看看。
这个非常有创意,实际上是以漫画书的形式制作的,文字是真实的,可以选中,所以这仍然是完全相同的HTML。
这个可能会让你大吃一惊。看这个,太酷了,实际上有动画效果,这全部是用CSS完成的。再次强调,这是完全相同的HTML结构和完全相同的内容,你只是看到了应用在它们上面的不同样式。
这难道不酷吗?
所以,这节简短讲座的重点是,CSS是一种极其强大的技术,它不仅可以为你的内容添加样式,还可以创造你希望用户拥有的特定用户体验。
第二点是,内容之上的用户体验很重要。记住,听一些几乎听不清的东西和听一些你真正能理解并享受的东西之间的区别。所以,内容的用户体验很重要。
剩下的就是弄清楚如何编写CSS代码。接下来,我们将开始探索CSS的语法,并研究CSS规则的构成。


本节课中,我们一起学习了CSS在Web开发中的核心作用。我们了解到,HTML提供了内容的结构,而CSS则负责内容的视觉呈现和用户体验。通过CSS Zen Garden的例子,我们看到了相同的HTML结构如何通过不同的CSS样式呈现出完全不同的视觉效果,这充分证明了CSS的强大能力。最后,我们明确了下一步将开始学习CSS的具体语法和规则构成。
018:CSS规则结构解析

在本节课中,我们将要学习CSS规则的基本结构。我们将解析一条CSS规则的组成部分,并通过一个简单的例子来理解这些部分如何协同工作,以控制网页元素的样式。
概述:CSS如何工作
CSS通过将规则与HTML元素关联来工作。这些规则控制着指定元素的内容应如何显示。虽然为整个网页设计样式可能是一个相当复杂的过程,但定义一个简单的CSS规则是相当直接的。
CSS规则的基本结构
一条CSS规则由两个主要部分组成:选择器和声明块。
选择器 (Selector)
选择器用于指定这条规则将应用于哪些HTML元素。例如,选择器 p 表示规则将应用于HTML页面中的每一个段落标签(<p>)的内容。
声明块 (Declaration Block)
选择器后面跟着一对大括号 {},大括号内部包含一个或多个CSS声明。
每个CSS声明又由两部分组成:
- 属性 (Property):一个由CSS规范预定义的样式名称(如
color,font-size)。 - 值 (Value):为属性指定的具体设置。每个属性都有一组预定义的值,或者特定类型的数值。

属性和值之间用一个冒号 : 分隔,每个声明以分号 ; 结束。从技术上讲,分号并非总是必需,但遵循最佳实践,应始终使用它。
一条CSS规则可以包含多个声明。例如,以下规则为段落设置了颜色、字体大小和宽度:
p {
color: blue;
font-size: 20px;
width: 200px;
}
这条规则的含义是:页面中所有段落的内容应为蓝色,字体大小为20像素,并且段落本身占据的宽度为200像素。
什么是样式表 (Style Sheet)
这些CSS规则的集合被称为样式表。从技术上讲,一个空的样式表也是样式表,只是不太有用。通常,一个样式表中会包含多条CSS规则。
实践:在HTML中应用CSS

上一节我们介绍了CSS规则的抽象结构,本节中我们来看看一个具体的例子。
以下是一个HTML文档的结构示例,其中包含一个一级标题(<h1>)、几个二级标题(<h2>)和一些段落(<p>)。我们的目标是为这些元素添加自定义样式。
为了方便起见,我们将样式表直接放在HTML文档的 <head> 部分(在实际网站中,CSS样式的放置位置可能不同,这将在后续课程中讨论)。
<!DOCTYPE html>
<html>
<head>
<style>
/* 针对段落标签的规则 */
p {
color: blue;
font-size: 20px;
width: 200px;
}
/* 针对一级标题的规则 */
h1 {
color: green;
font-size: 36px;
text-align: center;
}
</style>
</head>
<body>
<h1>这是一个主标题</h1>
<h2>子标题一</h2>
<p>这是第一个段落。</p>
<h2>子标题二</h2>
<p>这是第二个段落。</p>
</body>
</html>
在这个例子中:
- 我们只指定了一次
p选择器,但文档中的每一个段落都应用了相同的样式(蓝色,20像素字体,200像素宽)。这体现了CSS在保持样式一致性和编写代码效率方面的强大能力。 - 我们为
h1元素设置了绿色、36像素字体大小和居中对齐。
浏览器默认样式
你可能注意到,示例中的 <h2> 元素(“子标题一”和“子标题二”)显示为粗体且比普通文本稍大。这个样式并非来自我们编写的CSS规则。
这个样式来自于浏览器本身的默认样式表。每个浏览器都会为不同的HTML元素(如 <h1>, <h2>, <p> 等)应用一些默认样式。很多时候,网页开发者的工作之一就是重置或覆盖这些默认样式,以应用自己设计的样式。
总结
本节课中我们一起学习了CSS规则的核心语法。我们讨论了:
- 选择器:用于指定样式应用的目标元素。
- 声明块:包含一个或多个属性-值对,用于定义具体的样式。
- 样式表:CSS规则的集合。
- 浏览器默认样式:理解浏览器自带的初始样式是进行网页设计的基础。


下一节,我们将深入探讨CSS规则中的一个关键部分——选择器。我们将详细介绍不同类型的选择器,特别是元素选择器、类选择器和ID选择器。
019:2_第13讲 元素、类与ID选择器

概述
在本节课中,我们将要学习CSS选择器的三种基本类型:元素选择器、类选择器和ID选择器。我们将了解它们各自的语法、用途以及如何在实际的HTML文档中应用它们来精确地控制样式。
元素选择器
元素选择器是最基础的选择器,它通过HTML元素的标签名来选取元素。
语法:element { property: value; }
例如,规则 p { color: blue; } 意味着文档中所有的段落(<p>标签)文本都将显示为蓝色。这个规则不会影响其他包含文本的元素,例如<div>标签。
类选择器
上一节我们介绍了元素选择器,本节中我们来看看类选择器。类选择器允许你为具有相同class属性值的多个元素定义样式,提供了更高的复用性。
语法:.classname { property: value; }
以下是使用类选择器的关键步骤:
- 在CSS中,使用一个点号(
.)后跟类名来定义样式规则。 - 在HTML中,为需要应用该样式的元素添加
class="classname"属性。
例如,CSS规则 .highlight { background-color: yellow; } 将应用于所有 class="highlight" 的HTML元素。

注意:定义类时,点号和类名之间不能有空格。在HTML中使用时,只需写出类名本身,不需要加点号。
ID选择器
我们已经了解了可以复用的类选择器,现在来学习ID选择器。ID选择器用于选取具有特定id属性值的唯一元素。

语法:#idvalue { property: value; }
以下是使用ID选择器的关键步骤:
- 在CSS中,使用一个井号(
#)后跟ID值来定义样式规则。 - 在HTML中,为需要应用该样式的元素添加
id="idvalue"属性。
例如,CSS规则 #main-point { font-weight: bold; } 将仅应用于 id="main-point" 的那个HTML元素。
注意:根据HTML规范,一个ID值在整个文档中只能出现一次。因此,ID选择器的可复用性最低,通常用于选取特定的单个元素。
选择器分组
为了编写更高效的CSS,我们可以将多个共享相同样式声明的选择器分组在一起。
语法:selector1, selector2, selector3 { property: value; }
例如,规则 h1, p, .intro { color: gray; } 会将所有<h1>、所有<p>以及所有类名为intro的元素的文本颜色设置为灰色。
总结
本节课中我们一起学习了CSS的三种基本选择器:
- 元素选择器:通过标签名选取元素,例如
p。 - 类选择器:通过类名选取元素,定义时使用
.classname,具有高复用性。 - ID选择器:通过ID值选取唯一元素,定义时使用
#idvalue,复用性最低。


我们还学习了如何通过逗号分隔来分组选择器,以提高代码效率。记住,在定义类和ID选择器时,点号或井号与名称之间不能有空格。合理地使用这些选择器是构建样式表的基础。在接下来的课程中,我们将学习如何组合这些选择器,以实现更精确的目标元素选取。
020:组合选择器 🎯

在本节课中,我们将学习如何组合CSS选择器。这是一种强大的技术,能让我们更精确地定位页面上的元素。我们将探讨几种最常见的组合方式,并通过实例来加深理解。
概述
组合选择器允许我们通过将多个简单选择器连接起来,创建更具体、更强大的选择规则。本节将重点介绍三种核心的组合方式:元素与类选择器组合、子选择器和后代选择器。掌握这些技巧是编写高效、可维护CSS的关键。
元素与类选择器组合
上一节我们介绍了基本的选择器,本节中我们来看看如何将它们组合起来。第一种组合方式是元素与类选择器。
其语法是:一个元素选择器直接后跟一个类选择器,中间没有空格。这表示选择所有具有指定类名的特定元素。
语法公式:
element.className
例如,p.big 会选择所有 class="big" 的 <p> 元素。如果在元素和类选择器之间加了空格,含义将完全不同,它会变成一个后代选择器。
以下是使用此组合方式的一个关键点:
- 确保元素选择器和类选择器之间没有空格。
- 这种组合常用于当某个CSS类应用于多种元素,但您希望它在特定元素上呈现不同样式时。
在右侧示例中,只有 class="big" 的 <p> 元素会获得20像素的字体大小,而其他具有 class="big" 的元素则不会。
子选择器
接下来,我们学习子选择器。它用于选择作为某个元素直接子元素的元素。
其语法是:一个选择器后跟一个右尖括号 >,再跟另一个选择器。我们从右向左阅读这个组合:选择所有作为左侧选择器直接子元素的右侧选择器元素。
语法公式:
parentSelector > childSelector
例如,article > p 会选择所有作为 <article> 元素直接子元素的 <p> 元素。
在示例中,作为 <article> 直接子元素的 <p> 会变成蓝色。而另一个 <p> 元素,虽然也在 <article> 内部,但它是 <div> 的子元素,因此不是 <article> 的直接子元素,所以其样式不受影响。
后代选择器

现在,让我们看看后代选择器。它与子选择器类似,但范围更广,选择的是某个元素内部任意层级的后代元素。


其语法是:一个选择器后跟一个空格,再跟另一个选择器。同样从右向左阅读:选择所有位于左侧选择器元素内部(无论嵌套多深)的右侧选择器元素。
语法公式:
ancestorSelector descendantSelector
例如,article p 会选择 <article> 元素内部的所有 <p> 元素,无论它们是直接子元素,还是嵌套在更深层的其他元素中。
在示例中,第一个 <p> 是直接子元素,会变蓝;第二个 <p> 完全不在 <article> 内,不受影响;第三个 <p> 虽然不是直接子元素(中间隔着 <div>),但由于它是 <article> 的后代,所以也会变蓝。
选择器组合的灵活性
需要指出的是,这些组合不仅限于元素选择器。我们可以使用任何类型的选择器进行组合。
例如:
.colored p是一个后代选择器,表示选择所有位于class="colored"的元素内部的<p>元素。article > .colored是一个子选择器,表示选择所有作为<article>直接子元素的、class="colored"的元素。
代码示例与实践
让我们通过几个代码示例来看看这些组合选择器如何实际工作。
示例1:元素与类选择器
我们有一个简单的HTML文件。其结构包含一个带有 class="highlight" 的 <h1>,两个 <p> 标签(其中一个有 class="highlight"),以及一个带有 class="main point highlight" 的 <div>。
以下是我们要编写的CSS规则:
首先,我们想选中所有 class="highlight" 的元素。
.highlight {
background-color: green;
}
其次,我们想选中所有同时是 <p> 元素且具有 class="highlight" 的元素。注意,p 和 .highlight 之间没有空格。
p.highlight {
font-style: italic;
}
最后,我们想选中所有同时具有 class="highlight" 和 class="main point 的元素。写法是将两个类选择器直接连在一起。
.highlight.mainpoint {
color: red;
background-color: black;
}
应用这些规则后,只有同时拥有两个类的 <div> 元素会变成红字黑底。
示例2:子选择器
这个HTML文件的结构包含两个 <section>。第一个 <section> 内有一个 <div>,<div> 内有一个 <article>。第二个 <section> 内直接有一个 <article> 子元素。此外,还有一个 <h2> 在 <section> 外部。
以下是我们要编写的CSS规则:
首先,选中所有作为 <section> 直接子元素的 <article> 元素。
section > article {
color: blue;
}
只有第二个 <section> 下的 <article> 会变蓝。
其次,选中所有作为 <section> 直接子元素的 <h2> 元素。
section > h2 {
color: red;
}
只有第一个 <section> 下的 <h2>(“subheading 1”)会变红。
示例3:后代选择器
这个HTML文件包含两个无序列表 <ul>。第一个 <ul> 在 <section> 元素内部,第二个 <ul> 在 <article> 元素内部。
我们的目标是:选中所有位于 <section> 元素内部(任何层级)的 <li> 元素。
section li {
color: green;
}
应用此规则后,第一个 <ul> 中的所有 <li> 都会变成绿色,即使它们与 <section> 之间还隔着一个 <ul> 元素。
总结与延伸
本节课中我们一起学习了三种核心的CSS选择器组合方式:
- 元素与类选择器 (
element.className): 用于选择具有特定类名的特定元素。 - 子选择器 (
parent > child): 用于选择作为某元素直接子元素的元素。 - 后代选择器 (
ancestor descendant): 用于选择位于某元素内部任何层级的后代元素。
记住,这些组合中的“选择器”可以是任何有效的CSS选择器,不限于元素标签。
此外,还有两种不太常见但有时很有用的组合选择器未在本课详述:
- 相邻兄弟选择器 (
element + sibling): 选择紧接在另一个元素后的兄弟元素。 - 通用兄弟选择器 (
element ~ sibling): 选择指定元素之后的所有兄弟元素。


您可以查阅CSS规范或在线资料来了解它们的详细用法。下一讲,我们将探讨伪类选择器,它允许我们根据元素的状态或位置来应用样式。
021:伪类选择器 🎯

在本节课中,我们将要学习CSS伪类选择器。伪类选择器允许我们根据元素的状态(如用户交互)或其在文档结构中的位置来应用样式,而不仅仅是基于元素类型、类或ID。我们将通过实例来了解几个最常用和强大的伪类选择器。
上一节我们介绍了基础的选择器,本节中我们来看看如何利用伪类选择器实现更动态和精细的样式控制。
理解伪类选择器

伪类选择器用于定位两种目标:一是可以通过常规选择器组合定位的结构;二是基于用户与页面交互状态(例如鼠标悬停)来应用样式的能力。

指定伪类选择器的方式是:在一个已知的选择器后加上冒号和一个预定义的伪类名称。例如:selector:pseudo-class。
在本讲中,我们将涵盖五个伪类选择器。前四个——:link、:visited、:hover 和 :active——非常基础且使用频率极高。最后一个 :nth-child() 则功能强大,虽然可以变得相当复杂,但其基本用法相当直观。
现在,让我们直接进入代码,看看这些选择器如何工作。
实战:将列表样式化为菜单
我们从一个包含标题和多个<div>的HTML页面开始。标题内有一个无序列表,其中包含多个链接。我们的第一个任务是将这个无序列表样式化为一个菜单按钮栏。
首先,我们需要定位这些列表项。我们将使用后代选择器 header li,并通过 list-style: none; 移除默认的项目符号。
header li {
list-style: none;
}
接下来,我们需要样式化<li>元素内的链接,使它们看起来像按钮。然而,样式化链接并不像样式化常规元素那样直接,因为链接具有不同的状态,这些状态可以通过伪类来表达。
以下是链接的几种状态,我们经常将其中两种组合在一起定义样式:
:link:链接的默认状态。:visited:链接被访问后的状态。:hover:用户鼠标悬停在元素上时的状态。:active:用户点击元素但尚未释放鼠标按钮时的状态。
我们首先将 :link 和 :visited 状态组合起来,为它们设置相同的基础样式。这通常是一种常见做法,表示我们不区分未访问和已访问的链接。
a:link, a:visited {
text-decoration: none; /* 移除默认下划线 */
background-color: green;
border: 1px solid blue;
color: black;
display: block; /* 将行内元素改为块级元素 */
width: 200px; /* 限制按钮宽度 */
text-align: center; /* 文字居中 */
margin-bottom: 1px; /* 按钮间添加间距 */
}
默认情况下,<a>元素是行内元素。为了创建块级按钮效果,我们将其 display 属性设置为 block,并指定宽度和对齐方式。
现在,我们有了三个基础的菜单按钮。为了增加交互性,我们需要定义用户悬停和点击时的样式。我们将 :hover 和 :active 状态组合起来。
a:hover, a:active {
background-color: red;
color: purple;
cursor: pointer; /* 鼠标悬停时变为手形指针 */
}
这样,当用户将鼠标悬停在按钮上或点击按钮时,背景色会变为红色,文字变为紫色,光标也会变成手形指针。
使用 :nth-child() 伪类
:nth-child() 伪类选择器允许我们根据元素在其父元素中的位置(序号)来选择目标元素。
其基本形式是 :nth-child(n),其中 n 可以是数字、关键词(如 odd 奇数、even 偶数)或公式(如 2n 表示所有偶数项)。
例如,如果我们想单独放大菜单中的第三个链接(假设是“Facebook粉丝页”链接),可以这样做:
header li:nth-child(3) {
font-size: 24px;
}
这只会影响标题中无序列表的第三个<li>子元素。
:nth-child() 的功能远不止于此。例如,我们有一系列单调的<div>,我们希望让列表更易读,可以为所有奇数项设置灰色背景。
section div:nth-child(odd) {
background-color: gray;
}
现在,所有位于<section>内的奇数序号<div>都会拥有灰色背景。
组合伪类选择器
就像常规选择器可以组合一样,伪类选择器也可以组合使用。这允许我们创建更具体、更动态的样式规则。
例如,我们希望上面<div>列表中的第四项在鼠标悬停时有不同的行为。我们可以这样写:
section div:nth-child(4):hover {
background-color: green;
cursor: pointer;
}
这个选择器表示:选择<section>内第四个<div>子元素,并且仅当它处于悬停状态时,应用绿色背景和手形指针样式。
注意事项与最佳实践
通过以上示例,你已经看到了选择器,尤其是伪类选择器的强大功能。然而,有一个重要的警告:请确保你的选择器仍然易于阅读和理解。
这些规则可以很快变得非常复杂。如果你写的选择器让其他开发者(甚至一个月后的你自己)都难以快速理解其目标,那么它可能会变成维护的噩梦。

因此,简单、可读的代码绝对优于复杂、取巧的代码。你或许能用复杂的技巧让人印象深刻,但让代码超级复杂绝不是良好的编程实践。
课程总结
本节课中我们一起学习了CSS伪类选择器。我们了解了如何利用 :link、:visited、:hover 和 :active 来根据链接状态应用样式,从而创建交互式UI元素如菜单按钮。我们还探索了强大的 :nth-child() 伪类,它允许我们基于元素在父容器中的位置来定位和样式化元素,并学习了如何组合伪类以实现更精细的控制。


记住,强大的工具需要负责任地使用。在追求功能强大的同时,始终保持代码的清晰和可维护性。
022:样式放置

在本节课中,我们将要学习CSS样式可以放置的不同位置,以及每种方式的优缺点。理解样式放置的位置不仅影响样式的可重用性,也决定了样式声明的优先级。
概述
CSS样式可以通过多种方式应用到HTML元素上。我们将探讨三种主要方法:内联样式、内部样式表和外部样式表。每种方法都有其特定的使用场景和影响。

内联样式
上一节我们介绍了样式放置的基本概念,本节中我们来看看第一种方法:内联样式。
内联样式是直接在HTML元素的style属性中编写CSS规则。这种方式的特点是缺少选择器,因为样式直接应用于该元素本身。样式声明的格式与常规CSS相同,每条声明以分号结尾。
以下是内联样式的一个例子:
<p style="text-align: center;">这段内容居中</p>
在这个例子中,<p>元素的文本被设置为居中对齐。正如你在页面中看到的,它确实在页面中居中了。
内联样式是可重用性最低的样式方法。例如,如果你想在同一个页面中为另一个元素应用相同的样式,你必须将样式代码复制粘贴到另一个元素上。因此,这是最不推荐的样式方法,应尽量避免使用。个人建议仅将其用于快速测试,以预览样式效果。
内部样式表
接下来,我们看看第二种方法:内部样式表,这也是我们在课程中常用的方法。
内部样式表是将CSS规则写在HTML文档<head>部分的<style>标签内。这种方法比内联样式更好,因为我可以在这里指定类选择器,并用选择器同时为多个元素设置样式。
然而,如果我的网站有多个页面,并且我希望它们看起来一致,那么在一个特定HTML页面的<head>标签内指定样式,对其他页面是无效的。这意味着我需要另一种方法来指定独立于HTML页面外部的样式,并将其链接回我的HTML页面。
外部样式表
这正是外部样式表的作用。你可以看到我们在这里使用了一个名为<link>的标签来指定它。

我们告诉浏览器这是一个样式表,并通过href属性指定它的位置。在这个例子中,我们指定了一个相对URL,因此style.css文件应该与我们的style-placement.html文件位于同一目录下,事实上它确实存在。
外部样式表就是一个包含一系列CSS规则的文件,周围不需要任何特定的HTML标签。正如你在style.css文件中看到的,我们指定了整个body(即整个视口)的背景颜色应为灰色,字体大小应为默认字体大小的130%。如果你看我们的页面,会发现字体比通常稍大,并且背景确实是灰色的,这些样式都来自style.css。
总结与最佳实践
本节课中我们一起学习了CSS样式放置的三种方式。
在课程中,为了更方便地展示样式,我们将继续使用<head>标签内的<style>标签。然而,在现实世界的网站开发中,你几乎总是希望将所有样式都放在HTML页面外部。这意味着将你的样式写入一个外部的style.css文件是正确的方法。
总结来说:
- 内联样式适用于快速测试,但在实际编码真实网站时应尽量避免。
- 真实世界的网站几乎总是使用外部样式表。这意味着你取出所有样式,将它们放置在一个外部文件中,然后通常将其链接回多个HTML页面。
- 这种技术不仅能重用你的CSS样式,还能促进整个网站外观的一致性。
- 此外,由于你将CSS样式外部化到一个文件中,网站中的每个后续页面都无需反复重新下载相同的样式。


虽然我们为了演示目的使用了内部样式(<head>中的样式),但在现实中,它们通常用于覆盖某些外部样式。然而,即使这种做法也有些值得商榷。如果你试图覆盖你无法控制的、来自HTML页面外部的样式,更好的做法是创建你自己的外部CSS文件,并将其指定在(你无法控制的)那个外部CSS文件之后。不过,这就会涉及到样式之间的冲突解决规则,而这正是我们接下来要讨论的内容。
023:第17讲 第1部分 样式冲突解决 🎨

在本节课中,我们将要学习CSS的一个核心机制——层叠(Cascading)。我们将探讨当多个CSS规则试图控制同一个元素时,浏览器如何决定最终应用哪个样式。理解这个过程是掌握CSS的关键。
什么是层叠? 🔄
层叠是CSS的一个基本特性。它是一种算法,定义了如何组合来自不同来源的属性值。正如其名称“层叠样式表”所暗示的,层叠算法是理解和使用CSS的核心。该算法通过结合样式声明的重要性、来源、特异性和源代码顺序,来确定最终应用于任何给定元素的声明,并在存在冲突时解决这些冲突。
核心概念:来源与合并 📝
层叠算法涉及许多术语和概念。然而,要掌握其工作原理,你需要理解以下四个概念:来源(Origin)、合并(Merge)、继承(Inheritance)和特异性(Specificity)。本节我们先来看前两个:来源与合并。
当两个声明发生冲突时,即它们为同一个目标元素指定了相同的属性,来源优先规则就会生效。这是一个非常简单的规则:最后声明的规则胜出。
在判断哪个是“最后声明”时,你需要记住HTML是按顺序(从上到下)处理的。因此,声明在页面中出现的位置越靠下,其优先级就越高。同时,对于外部CSS文件,可以将其视为在HTML中<link>标签所在的位置被声明的。为了判断声明的先后顺序,你可以想象将外部CSS文件的全部内容直接剪切并粘贴到HTML文件<head>部分中该CSS链接声明的位置。
当不同的CSS声明没有冲突时,即它们仍然针对同一个元素,但设置的CSS属性不同,规则则更为简单:声明会合并。例如,一个针对字体大小的声明和一个针对颜色的声明,由于它们是两个不同的属性,即使它们来自两个不同的来源,当它们应用于同一个元素时,也会合并为一个。该元素将同时获得字体大小和颜色的样式。
让我们通过代码快速看一个例子。
以下是代码示例的解析:
在HTML文件的<head>部分,首先通过<link>链接了一个外部CSS文件。该文件规定所有<p>标签的字体大小为130%,背景为灰色,文字颜色为白色。
紧接着,在同一个<head>部分,有一个<style>标签,它覆盖了<p>元素的文字颜色,将其设置为栗色(maroon)。
在页面主体中,有两个段落。第一个是普通段落,第二个段落通过style属性内联声明了文字颜色为黑色。
结果分析:
- 第一个段落:背景为灰色,文字颜色为栗色。这是因为外部CSS的声明(灰色背景、白色文字)首先被应用,但随后
<style>标签中的声明(栗色文字)覆盖了颜色属性。字体大小和背景颜色没有冲突,因此被合并应用。 - 第二个段落:背景为灰色,文字颜色为黑色。这是因为内联样式(
style="color: black;")是最后声明的颜色规则,因此它覆盖了之前所有的颜色声明。同样,字体大小和背景颜色被合并应用。
这演示了来源优先规则的实际应用。同时,对于没有冲突的属性(如background-color和font-size),它们被合并到了最终的样式中。
核心概念:继承 🌳
接下来我们要看的概念是继承,这是一个相当简单的概念。其基本思想是:文档对象模型(DOM)是一个树形结构。如果你在某个元素上指定了某个CSS属性,那么该元素的所有子元素、孙元素等都将继承该属性,而无需你为每个元素单独指定。
例如,如果我在<body>标签上指定了一个属性,那么<body>标签的每个子元素,甚至子元素的子元素等,都将继承该属性。同样,如果我在HTML页面中的某个元素上指定了一个属性,那么该元素的每个子元素和孙元素等也将继承该属性。但显然,该元素的父元素不会继承此属性,因此完全不受其影响。
让我们看看代码中的继承是什么样子。


在CSS中,继承是一个非常简单易懂的概念,同时也是一种非常强大的技术。它允许我指定一条规则,并让目标元素的所有子元素都继承该规则。
以下是代码示例的解析:
在HTML中,一个常见的做法是为<body>标签指定一些声明。原因很明显:就视口而言,<body>是我们HTML中的顶层元素。我可以在其中为整个HTML文档指定一些规则,然后利用我们的层叠来源优先规则,在文档靠下的位置覆盖一些特定的规则,同时保持其他所有内容的一致性。
在这个例子中,我指定了文档中出现的任何文本颜色应为红色,并且所有内容都应居中对齐。同时,指定了一种将在整个HTML文档中保持一致的字体(这里是Helvetica)。这是一种非常常见的做法。

然后,我仅为段落(<p>)指定了特定的文本颜色(蓝色)。这样,即使每个段落都会继承红色的文本颜色,我们在这里用蓝色覆盖了它。
最后,在更靠下的<h2>标签中,我再次使用来源优先规则,将这个特定二级标题的文本颜色覆盖为绿色。
结果分析:
在浏览器中,<h1>和第一个<h2>显示为红色,尽管我从未明确指定<h1>或<h2>为红色。我只是说<body>内的任何内容颜色都应为红色。
同时,你会注意到这里的每个块级元素都是居中的,原因是我在<body>上指定了text-align: center。
字体家族也是如此,整个文档都是Helvetica。我从未为页面中的每个元素单独指定text-align或font-family,但我为它们的父元素<body>标签指定了这些属性。
正如你所见,这非常强大。
本节总结 📚
本节课中,我们一起学习了CSS层叠机制的前半部分。我们了解了当样式规则发生冲突时,浏览器如何通过来源优先规则来决定胜出者,即最后声明的样式生效。我们也看到了当规则不冲突时,样式会合并应用。此外,我们还探讨了继承这一强大特性,它允许样式从父元素自动传递给子元素,从而简化了代码并保持了一致性。

由于本讲内容稍长,我们将在第二部分完成最后一个核心概念——特异性(Specificity)的学习。特异性是解决样式冲突的另一个关键因素,它决定了当多个选择器指向同一个元素时,哪个规则具有更高的优先级。
024:第17讲 第2部分 样式冲突解决
在本节课中,我们将要学习CSS中一个至关重要的概念:特异性。我们将了解浏览器如何决定当多个CSS规则同时应用于同一个元素时,哪一个规则最终生效。理解特异性是掌握CSS并避免常见样式问题的关键。

特异性概念
上一节我们介绍了层叠算法的基本概念,本节中我们来看看特异性。特异性也有一条简单的规则:最具体的CSS选择器组合胜出。虽然这条规则听起来简单,但开发者最常遇到的问题往往就是忽略了特异性的概念及其在CSS中的实现方式。
特异性计算方法
以下是一种直观的技巧,可以帮助你判断哪个选择器组合更具体。

你可以将选择器的特异性想象成一个计分系统,分数最高的选择器获胜。换句话说,分数最高的选择器被认为是最具体的。
为了方便计算分数,我们将影响分数的选择器类型从左到右排列,左侧的特异性值最高。然后,只需查看你的CSS规则,在相应的位置填入该类型选择器出现的次数,最终得到的数字就是你的分数。
让我们看看屏幕上的四个方框:
- CSS中最具体的定位方式根本不使用任何选择器,那就是元素的
style属性。当你直接在元素上使用style属性定义CSS声明时,就属于这种情况。这非常合理,因为指定style属性就像用手指着那个元素说“就是这一个”,没有比这更具体的了。这就是为什么style属性位于最左侧的方框。 - 特异性值其次的是 ID选择器。
- 然后是 类选择器或伪类选择器。
- 最后是你的选择器组合中使用的 元素选择器数量。
例如,对于规则 h2 style="color: green;",我们使用了style属性,这意味着第一个方框得1分,其余方框得0分。这几乎是你能得到的最高分数:1,0,0,0。
再看另一个选择器,例如 div p { color: green; }。它没有在style属性中定义,所以第一个方框得0分。没有ID选择器,第二个方框得0分。也没有类选择器,第三个方框得0分。但这里有两个元素选择器(div和p),所以最后一个方框得2分。分数是 0,0,0,2。
特异性比较示例
现在让我们看看如何通过比较来决定哪个选择器组合会胜出。
假设你正在为一个特定的段落标签设置样式。一种情况使用屏幕左侧显示的选择器组合,另一种情况使用屏幕右侧显示的选择器组合。哪一个会胜出?换句话说,该段落的文本颜色会是蓝色还是绿色?
让我们快速计算一下分数:
- 左侧选择器:没有使用
style属性,得0分。使用了一个ID属性,得1分。没有使用类,得0分。元素数量为1,得1分。所以最终分数是0,1,0,1。 - 右侧选择器:没有使用
style属性,得0分。没有使用ID属性,得0分。使用了一个类,得1分。使用了两个元素,得2分。所以分数是0,0,1,2。
因此,左侧的选择器胜出,段落的文本颜色将是蓝色,而不是绿色。
观察左侧的分数和我们为获得该分数所选择的选择器,我们可以看到,其实我们一开始可以不指定div元素。仅使用ID选择器表达,我们仍然会赢得这场“选择器之战”,因此段落的文本颜色仍将是蓝色。
代码实践
让我们进入代码编辑器,看看这个概念的实际应用。
这里我们在Sublime Text中查看 specificity.html 文件。这是一个非常简单的文档,基本上只有一个段落标签,它位于一个类名为 navigation 的 header 标签内。
如果你查看为这个文档指定的样式,有两个相互竞争的规则:一个试图将文本颜色设置为蓝色,另一个试图将文本颜色设置为红色。显然,正如你在右侧浏览器中看到的,文本颜色最终是蓝色的。这是为什么?




记住我们刚刚学到的知识,这里起作用的正是特异性规则。
- 两条规则都有一个类选择器,所以各得10分(注:此处讲师将类/伪类的权重计为10,是一种简化教学方式,实际计算中通常按位比较
0,0,1,0和0,0,1,0)。 - 然而,第一条规则指定了两个元素(
header p),而另一条只指定了一个元素(.navigation)。所以第一条规则的分数更高(例如0,0,1,2vs0,0,1,1),因此这条规则胜出。
!important 规则
我想向你展示另一个概念:使用 !important 来覆盖所有规则。
这里我们有第三种方式来定义这个段落标签的文本颜色为绿色。这次我将使用 !important 关键字来定义。!important 基本上是说:无论特异性如何,我都想覆盖一切,并按照我定义的方式设置这个属性。这里我们定义颜色为绿色。
所以,如果我们刷新浏览器,即使这条规则(p { color: green !important; })的特异性(例如 0,0,0,1)远低于另一条规则(例如 0,0,1,2),但由于我们指定了 !important,文本颜色将变为绿色。
关于使用 !important 的警告:虽然跳过理解所有这些层叠规则和特异性规则,每当样式不生效时就在各处随意添加 !important 非常诱人,但这在真实世界的项目中会迅速演变成一场维护噩梦。你将不得不用一个 !important 声明去覆盖另一个 !important 声明,最终会建立一套自己的、混乱不堪的规则体系。因此,除非绝对必要,否则请避免使用 !important。
总结
本节课中我们一起学习了层叠算法中的特异性。我们探讨了如何计算选择器的特异性分数,并通过比较分数来决定哪个样式规则最终生效。我们还了解了强大的 !important 规则及其潜在风险。
层叠算法通过对目标内容提供精确的控制,同时允许你在网站中最大程度地重用样式,这基本上就是CSS如此强大的原因。
接下来,我们将讨论文本样式的设置。




025:第18讲 第1部分 文本样式设计

在本节课中,我们将要学习如何使用CSS来设计和控制网页上文本的显示样式。我们将从最基础的字体属性开始,逐步介绍一系列关键的文本样式属性,并通过实例演示它们的效果。
有许多CSS属性可以影响文本的显示方式。我们不会试图覆盖每一个属性,而是通过查看几个关键属性来理解其背后的样式设计概念。让我们直接进入代码编辑器开始学习。
现在,我正使用Sublime Text查看一个名为“Styling-text-before.html”的文件。首先,让我快速调整一下屏幕布局,以便你能同时看到浏览器和代码编辑器。
我们的第一个任务是为HTML文件中的第二个段落应用样式。该段落被赋予了一个名为.style的类。目前,浏览器中显示的两个段落都使用的是浏览器的默认样式。我们的目标是只对第二个段落进行样式设置,以便进行对比。
通常,我们首先要指定的属性是font-family(字体系列)。
font-family属性的值可以变化。事实上,我已在HTML页面中提供了一个链接,点击该链接可以查看常用的字体组合。通常,你会为font-family指定一个由多个字体名称组成的值。这样做的原因是,你依赖于用户计算机上已安装的字体。如果用户电脑上没有安装你指定的第一种字体,浏览器会尝试列表中的下一个字体。你至少应该指定是使用衬线字体(serif)还是无衬线字体(sans-serif),因为每台电脑都会有某种默认的这两种字体。衬线字体在线条的末端有装饰性笔画,而无衬线字体则线条非常笔直。
我个人偏好特定的字体组合,因此我会复制它并粘贴到代码中。
.style {
font-family: Arial, Helvetica, sans-serif;
}
保存文件并刷新浏览器后,可以看到段落字体已变为Arial。
接下来,让我们改变文本颜色。虽然测试时使用如red、green、blue这样的预定义颜色名是可以的,但在实际网站中,通常使用十六进制值来指定精确的颜色。颜色的选择完全取决于你的设计。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
}
十六进制颜色值的表示方法是:前两位数字代表红色,中间两位代表绿色,最后两位代表蓝色。FF是蓝色分量的最大值。这本质上是RGB颜色规范的一种十六进制表达方式。保存并刷新后,文本变成了蓝色。
font-style属性用于指定文本是否倾斜。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
font-style: italic;
}
保存并刷新后,字体变为斜体。
font-weight属性用于设置字体的粗细。它可以从normal到bold,也可以用数字(如100到900)来指定。bold关键字通常就足够使用,它大约等同于数值900。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
font-style: italic;
font-weight: bold;
}
接下来,我们指定font-size(字体大小)。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
font-style: italic;
font-weight: bold;
font-size: 24px;
}

这会使字体比当前默认的16像素更大。请注意,px(像素)是屏幕上的绝对尺寸单位,而pt(点)通常用于印刷品。像素相对于观看设备:对于低DPI设备,一个CSS像素对应一个设备像素点;对于打印机和高分辨率屏幕,一个CSS像素可能对应多个设备像素点。
在介绍了字体大小之后,让我们再了解几个你可能想知道的文本属性。
text-transform属性允许你控制文本的大小写形式。
以下是text-transform属性的几个常用值:
capitalize:将每个单词的首字母转换为大写。lowercase:将所有字母转换为小写。uppercase:将所有字母转换为大写。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
font-style: italic;
font-weight: bold;
font-size: 24px;
text-transform: uppercase;
}
保存并刷新后,整个文本变为大写。
另一个有用的属性是text-align,它允许你在其块级元素内对齐文本。
以下是text-align属性的几个常用值:
left:左对齐(默认)。center:居中对齐。right:右对齐。justify:两端对齐。
.style {
font-family: Arial, Helvetica, sans-serif;
color: #0000FF;
font-style: italic;
font-weight: bold;
font-size: 24px;
text-transform: uppercase;
text-align: justify;
}
保存并刷新后,文本在容器内实现了两端对齐。

本节课中我们一起学习了CSS文本样式的基础知识。我们探讨了如何设置字体系列、颜色、样式、粗细和大小,并介绍了控制文本大小写和对齐方式的属性。在下一部分,我们将继续本讲座,讨论相对字体尺寸单位。
026:第18讲 第2部分 文本样式设计

概述
在本节课中,我们将要学习CSS中相对字体大小的概念,特别是百分比(%)和em单位的使用方法。我们将通过一个具体的例子来理解它们如何工作,以及如何在实际项目中应用它们来创建灵活、可缩放的文本布局。
相对字体大小介绍
上一节我们介绍了基础的文本样式,本节中我们来看看如何使用相对单位来设置字体大小。相对单位意味着字体大小不是固定的像素值,而是相对于其他元素(如父元素或根元素)的尺寸来计算的。
我们首先打开一个名为 font-size.html 的示例文件,它位于与上一节文件相同的文件夹中。
两种相对单位:百分比与em
我们将讨论两种相对度量单位:百分比(%)和 em。
使用百分比设置字体大小
首先,我们尝试使用百分比。我们指定 body 标签的字体大小为 120%。这意味着我们希望字体大小是浏览器默认大小的 120%。
公式:目标大小 = 默认大小 × 百分比
大多数浏览器的默认文本大小是 16px。因此,120% 对应的像素值计算如下:
16px × 120% = 19.2px
如果我们刷新页面,会看到整个HTML文档的字体大小都增加了。
使用em单位设置字体大小
接下来,我们使用 em 单位。em 是一个相对单位,它等于当前字体中字母“M”的宽度。更简单地说,1em 等于其父元素的当前字体大小。
为了测试,我们为第一个显示“2em text”的文本添加一个内联样式,将其字体大小设置为 2em。
代码:
<div style="font-size: 2em;">2em text</div>
由于我们之前将整个 body 的字体大小设置为 120%,这个 div 也继承了该大小。现在,2em 意味着我们希望此 div 的字体大小是其当前字体大小的两倍(即 120% 的两倍)。
保存并刷新文件后,可以看到“2em text”的字体大小大约是“regular text”的两倍。
相对单位的累积效应
你可能会感到困惑:我们不是用 2em 覆盖了 120% 吗?实际上,在相对尺寸设置中,后续元素的指定不会产生覆盖效果,而是会产生累积效应。
当我们说 font-size: 2em; 时,意思是“将当前字体大小乘以2”。因此,它是在继承的 120% 基础上再次翻倍。
进一步调整字体大小
现在,我们想让显示“4em text”的文本大小变为 4em。如果我们直接设置 style="font-size: 4em;",这将是错误的。因为该元素已经是 2em 文本的子元素,4em 会基于当前的 2em 再次计算,最终效果相当于 8em。
为了正确实现“4em text”是“2em text”的两倍,我们需要在其父元素(即“2em text”的 div)的基础上设置 2em。
代码:
<div style="font-size: 2em;">
2em text
<div style="font-size: 2em;">4em text</div>
</div>
保存并刷新后,可以看到“4em text”的字体大小确实是“2em text”的两倍。同时,“2em text”本身也变大了,因为它是我们刚设置样式的 div 的子元素。
如何恢复字体大小
如果我们想将“4em text”内部的文本恢复成与“2em text”相同的大小,不能直接覆盖,而是需要减小其相对大小。
代码:
<div style="font-size: 2em;">
2em text
<div style="font-size: 2em;">
4em text
<div style="font-size: 0.5em;">Back to 2em</div>
</div>
</div>
这里,我们设置 font-size: 0.5em;,这意味着将当前字体大小(4em)减半,从而使其恢复到与“2em text”相同的大小。刷新页面后,可以看到效果。
最佳实践与注意事项
在结束之前,有几个要点需要说明。
以下是关于使用相对字体大小的一些建议:
- 全局字体调整:通常,如果你想增加整个文档的字体大小,会使用百分比,并将其应用于最顶层的父标签(通常是
body标签)。 - 单位选择:技术上,你也可以在这里使用百分比,因为
2em实际上等同于200%。但在文档内部设置相对大小时,更常见的做法是使用em单位。 - 保持一致性:强烈建议不要混合使用百分比、
em以及绝对单位(如px)。这会使代码变得混乱且难以维护。最好保持单位使用的一致性。 - 用户可访问性:即使用CSS将字体大小设置为
120%,网站用户仍然可以通过浏览器缩放功能(例如放大到200%)来调整页面大小。使用相对单位的好处在于,即使整体放大,各元素之间的视觉大小关系(如“此文本是彼文本的一半”)也能保持不变,这体现了响应式设计的价值。


总结
本节课中我们一起学习了CSS中相对字体大小的核心概念。我们重点探讨了百分比(%)和 em 单位的工作原理,理解了它们的累积效应,并学习了如何通过它们创建可灵活缩放的文本布局。记住,在大多数情况下,使用 em 进行文档内部的相对调整,并使用百分比进行全局基准设置,是一种清晰且有效的实践方式。
027:盒子模型 📦


在本节课中,我们将要学习CSS盒子模型。盒子模型是网页布局的核心概念,它描述了每个HTML元素在页面上如何被看作一个矩形盒子,以及这个盒子的各个组成部分如何影响其尺寸和与其他元素的关系。
在HTML中,每个元素都被视为一个盒子。然而,这个盒子不仅仅包含元素所包裹的内容。让我们快速浏览一个简单的示意图。


除了实际内容外,每个盒子都由内边距、边框和外边距组成。盒子模型指的是构成HTML盒子的组件,以及管理这些盒子组件如何影响布局、如何计算盒子宽度和高度的规则。让我们进入代码,看一些例子。

现在,我在Sublime Text中查看一个名为 box-model-before.html 的文件,它位于 examples/lecture19 文件夹中。让我们快速检查一下这个文档的HTML结构,它实际上非常简单。它只有一个 h1 标题,一个ID为 box 的 div,在这个 div 内部,有另一个ID为 content 的 div,里面包含一些虚拟文本。此外,我将 box 的背景色设置为蓝色,将 content 的背景色设置为浅绿色。为了让背景色更有趣,我将 body 的背景色设置为灰色。

这是我们的页面在浏览器中的呈现。由于 div 是块级元素,它会尝试在宽度上填满整个父元素。所以它试图填满浏览器的整个宽度。但这里有些奇怪的现象,如果你观察一下,在内容文本的开头和结尾处有一些间距。让我们快速查看一下Chrome开发者工具,弄清楚这些间距是怎么回事。
我们选择 box 元素,看看发生了什么。box 本身似乎没有设置任何间距。但 body 标签本身呢?看,我们发现 body 标签四周有8像素的外边距。这个8像素从哪里来的?它来自用户代理样式表,也就是浏览器本身的默认样式。浏览器这样做是很常见的。为了不影响我们的操作,我们需要重置它。网上有很多重置浏览器默认样式的方法。我们将设置 margin: 0 和 padding: 0,这样就不会受到任何默认样式的影响。保存并刷新后,现在我们的内容与浏览器窗口边框对齐了。
继续,我们看到内容是绿色的。但我们没有看到 box 的背景色,因为内部的 content div 完全覆盖了外层的 box div。它们都试图填满它们的父元素(即 body 标签),由于内部的 div 位于外部的 div 内部,它覆盖了蓝色的背景。
现在,让我们给 box 设置一些内边距。我们设置 padding: 10px 10px 10px 10px。顺便说一下,这显然有一个简写方式,你可以只写 padding: 10px,意思完全相同。我建议你查阅所有不同的简写方式,但这里我先保留完整写法。要点是,顺序总是上、右、下、左。保存并刷新浏览器,现在我们看到了内边距。内边距的作用是在内容周围提供一些空间。我们的内容(即带有文本的 content div)被挤压在中间,内边距显示在四周,现在你可以看到蓝色背景了,那是我们 box 的背景色。
接下来,让我们给 box 添加边框。设置 border: 3px solid black。这意味着边框将是3像素厚、实线、黑色。保存并刷新浏览器,我们可以看到边框也显示出来了。实际上,让我们把它加粗一点,改成 5px。最后,让我们也给它添加一些外边距。使用简写符号 margin: 40px,这意味着我希望四周都有40像素的外边距。刷新后,你可以看到现在每个边都有40像素的外边距。
到目前为止,我们让内容和一些边距等来决定盒子的大小。但如果我们自己设置它的宽度呢?


让我们设置它的宽度为300像素。保存并刷新后,我们看到盒子变小了。让我们在Chrome开发者工具中检查这个盒子。
它显示我们的盒子确实是300像素宽。由于我们没有限制高度,它会自动调整以包裹文本。除非我们手动指定高度,否则高度会根据需要调整。如果我们把它调小一点,比如100像素,刷新后你会发现高度变大了,变成了72像素,因为内容被挤压得需要换行,增加了额外的行。让我们把它改回300像素并刷新。
现在,如果我们看这个分解图,我们遇到了第一个非常有趣的点。首先,让我们浏览所有组件:绿色高亮部分是纯粹的内容;再往外一点,会高亮显示内边距;再往外是边框;外边距在鼠标悬停时可能不太明显,但四周的40像素依然存在。
现在有趣的问题是:这个盒子到底有多大?当我们开始放置更多盒子、制作不同HTML组件的布局时,为了正确设计UI,我们需要知道每个盒子的大小。我们指定了盒子宽度为300像素,但问题在于它实际上并不是300像素宽。我们忽略了它周围还有边框和内边距。内边距一边是10像素,另一边也是10像素。不仅如此,还有边框,一边是5像素,另一边也是5像素。所以实际上是 300 + 10 + 10 + 5 + 5 = 330 像素。如果你看这个盒子的实际可见边框,这个距离不是300,而是330。
一个有趣(或者说有点烦人)的部分是,根据你设置的边框和内边距,这个宽度会改变。让我们看看,如果我把边框设得非常大,比如20像素,刷新后,边框变成了20像素,现在我的盒子边界延伸到了那里。如果我把内边距也设得更大,比如右边和左边都设为30像素,再看这个盒子,它又变大了。
所以,尽管我们说希望盒子的宽度是300,结果我们设置的并不是盒子的宽度,而是内容的宽度(即绿色部分)。对于布局和一般意义上的合理性来说,这有点烦人。你确实想改变这一点,而CSS3实际上确实改变了它。
原来,默认情况下,每个HTML元素都有一个 box-sizing 属性,默认值设置为 content-box。这意味着当你指定高度和宽度时,你指定的是内容框的高度和宽度,而不是整个盒子。CSS3为这个属性引入了一个新值,叫做 border-box。让我们在这里设置它:box-sizing: border-box。
刷新后,看看,它变小了。现在,如果你看分解图,你会发现尽管我们指定宽度为300,但这个宽度现在保证是从这个边框边缘到那个边框边缘的整个盒子的宽度。计算一下:200 + 30 + 30 = 260,再加上 20 + 20 = 40,总共是300。如果我们改回默认的 content-box,它会跳回原来的大小。
但事实是,所有现代框架(如Bootstrap等)都使用 border-box 作为其尺寸模型。当你规划和设计布局时,这是明智的选择。所以请确保你始终使用 box-sizing: border-box。


本节课中我们一起学习了CSS盒子模型。我们了解了每个HTML元素如何被视为一个由内容、内边距、边框和外边距组成的盒子。我们探讨了默认的 content-box 模型如何导致实际元素尺寸与设定宽度不符,并学习了CSS3引入的 border-box 模型如何解决这个问题,确保设定的宽度就是元素可见框的总宽度。掌握盒子模型是进行精确网页布局的基础。
028:11_第19讲 第2部分 盒子模型

📦 概述
在本节课中,我们将深入学习CSS盒子模型的两个重要概念:如何全局应用box-sizing: border-box属性,以及理解垂直方向上外边距(margin)的折叠现象。我们将通过实例和代码来阐明这些概念。
🌐 全局应用 box-sizing: border-box
上一节我们介绍了应尽量使用box-sizing: border-box作为盒子模型。当然,我们不希望为HTML中的每个元素单独指定此属性,而是希望设置一次,然后让它被所有元素继承。让我们尝试实现这一点。
首先,我们将从.box类中移除box-sizing: border-box。<body>元素似乎是一个自然的放置位置,因为它是顶级元素,其属性应该被所有子元素继承。因此,如果我将box-sizing: border-box设置在body上并刷新页面,效果应该保持不变。
然而,它没有生效。盒子又跳回了content-box模式。其原因是,box-sizing是CSS中不被继承的属性之一。你不能在父元素上设置它并期望子元素继承该属性。
那么,我们如何解决这个问题呢?解决方法是学习一个新的选择器:星号选择器。让我们回到代码顶部,使用星号*,然后将border-box属性粘贴在这里。
* {
box-sizing: border-box;
}
现在刷新页面,它生效了。星号选择器的作用是:选择页面上的每一个元素,并将指定的CSS属性应用给它们。它与在父元素上设置属性的关键区别在于:星号选择器不是请求继承,而是直接为每个元素应用该属性值,就像它遍历HTML并为每个元素单独添加了box-sizing: border-box一样。这就是将类似box-sizing这样的属性应用到整个页面的方法。
我曾提到border-box是CSS3的新属性。当你听到这类信息时,应该警醒:我使用的所有浏览器都支持它吗?我们可以通过查询“Can I use”网站来确认。查询结果显示,所有主流浏览器都完全支持box-sizing: border-box这个属性值,因此可以放心使用。
⬇️ 理解垂直外边距折叠

接下来,让我们更深入地理解外边距(margin),特别是垂直方向上的行为。
请看这张示意图。我们有两个并排的盒子(即两个元素),灰色部分代表它们的外边距。左右外边距是累加的。如果左边盒子的margin-right是40像素,右边盒子的margin-left是50像素,那么它们之间的总间距就是90像素(40+50)。这很简单,也符合直觉。
但是,如果一个元素在另一个元素上方呢?假设这是两个<div>,每个都指定了外边距。下方元素的margin-top是30像素,上方元素的margin-bottom是20像素。你可能会认为总间距是50像素。
然而,答案是否定的。这里会发生外边距折叠,并且较大的外边距会胜出。因此,两个元素边框之间的实际距离,在这个例子中将是30像素。较大的外边距(30px)生效,较小的那个(20px)则被“折叠”消失了。
让我们在代码编辑器中查看一个实例。

回到之前的HTML文件,我们可以看到这里有一个<h1>标题,我们为其定义了margin-bottom: 30px。同时,我们的.box类移除了所有外边距。现在,让我们为.box类定义一个margin-top: 20px。
.box {
/* 其他样式... */
margin-top: 20px;
}
刷新页面后,你会发现没有任何变化。原因是,盒子定义了20像素的上外边距,但<h1>已经定义了一个30像素的下外边距。当它们相遇时,会发生折叠,最终间距取两者中较大的值,即30像素。
但是,如果我将盒子的margin-top改为更大的值,比如50像素:
.box {
/* 其他样式... */
margin-top: 50px;
}
现在你会看到盒子向下移动了,因为此时盒子的上外边距(50px)大于<h1>的下外边距(30px)。所以,<h1>和盒子之间的实际距离变成了50像素。
另外,你可能注意到“Box Model”这几个字并没有紧贴浏览器窗口的左上角。这是为什么呢?让我们检查一下元素。可以看到<h1>有一个大约21.44像素的上外边距。这个外边距来自浏览器的用户代理样式表。这是因为一些浏览器特定的样式(如margin-before)为<h1>预设了外边距。
我们之前在body上设置的margin: 0之所以在这里没生效,原因很简单:body的样式会被其内部元素继承,但前提是该元素自身没有更具体的样式覆盖它。在这里,用户代理样式表通过h1选择器专门设置了外边距,这个更具体的规则胜出了。
那么,如何覆盖它呢?既然我们知道了星号选择器,这就很简单了。我们可以将重置margin和padding的规则也移到星号选择器中。
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
现在,星号选择器选中每一个元素,并明确地将其margin和padding覆盖为0。刷新页面,你会看到文字现在紧贴顶部了。我们在页面中不一定会这样做,但至少你现在知道了:如果你想重置浏览器的默认CSS样式(这被称为CSS重置),大多数时候都可以使用这个星号选择器来实现。
📝 总结
本节课我们一起学习了两个关键内容:
- 使用星号选择器
*来全局应用box-sizing: border-box等不被继承的CSS属性。 - 理解了垂直外边距折叠的规则:当两个垂直相邻的块级元素的外边距相遇时,它们会合并(折叠),最终的距离等于两个外边距中较大的那个值。
掌握这些概念有助于你更精确地控制网页元素的布局和间距。
029:盒子模型第三部分

在本节课中,我们将要学习CSS盒子模型的最后一个重要概念:如何处理当元素内容超出其指定尺寸的情况。我们将探讨overflow属性的不同取值及其效果。
在上一节中,我们讨论了box-sizing属性,它决定了元素尺寸的计算方式。本节中,我们来看看当元素内容过多,超出其设定的宽度或高度时会发生什么,以及如何控制这种情况。
默认情况下,如果我们为一个元素设置了固定的height,但内容超出了这个高度,内容会“溢出”到盒子之外。这可能会影响页面布局,导致内容重叠。
以下是处理内容溢出的几种方法,主要通过overflow属性来控制。
overflow属性选项:
visible(默认值):内容会溢出到盒子外部并显示出来。hidden:溢出的内容会被直接裁剪掉,用户无法看到超出部分。scroll:无论内容是否溢出,都会在元素内显示滚动条。auto:只有当内容溢出时,才会在需要的方向上显示滚动条。
例如,对于一个高度固定的div,我们可以这样设置:
div {
height: 50px;
overflow: auto; /* 或 hidden, scroll, visible */
}

使用auto是一个常见的解决方案,它只在必要时显示滚动条。然而,需要注意,在浏览器主窗口滚动条之外再出现内部滚动条(即“双重滚动”),可能会影响用户体验。但在某些场景下,例如在一个固定区域展示长篇条款或协议,使用内部滚动是合理且预期的做法。


本节课中我们一起学习了CSS盒子模型的收尾部分。我们总结了盒子模型的核心概念,并重点探讨了overflow属性的用法,它帮助我们管理内容超出容器边界的情况。下一节,我们将开始学习background属性。
030:第20讲 背景属性 🎨
在本节课中,我们将要学习CSS背景属性的更多用法。到目前为止,我们主要使用background属性来设置背景颜色,但实际上,背景属性功能非常丰富,可以设置背景图片、控制图片重复方式、定位图片等。本节我们将逐一探索这些功能。



设置背景颜色与图片
上一节我们介绍了背景的基础概念,本节中我们来看看如何设置背景图片。
首先,我们有一个简单的HTML结构,包含一个<div>元素,其ID为bg。我们通过CSS将其设置为一个500x500像素的方块。
#bg {
width: 500px;
height: 500px;
}
我们可以使用background-color属性为其设置一个纯色背景,例如蓝色。
#bg {
width: 500px;
height: 500px;
background-color: blue;
}
然而,背景不仅可以是纯色,还可以是图片。设置背景图片需要使用background-image属性。
以下是设置背景图片的语法:
background-image: url("图片路径");
图片路径可以是相对路径或绝对路径。需要注意的是,路径是相对于CSS文件的位置而言的。例如,如果我们的图片文件名为yakov.png,并且与CSS文件在同一目录下,我们可以这样写:
#bg {
width: 500px;
height: 500px;
background-image: url("yakov.png");
}
设置后,图片会默认在元素内平铺显示。
控制背景图片的重复
默认情况下,背景图片会水平和垂直方向重复铺满整个元素。我们可以使用background-repeat属性来控制这一行为。

background-repeat属性有以下几种常用值:

repeat: 默认值,在水平和垂直方向都重复。no-repeat: 不重复,只显示一张图片。repeat-x: 只在水平方向重复。repeat-y: 只在垂直方向重复。
例如,如果我们希望背景图片只显示一次,可以这样设置:
#bg {
width: 500px;
height: 500px;
background-image: url("yakov.png");
background-repeat: no-repeat;
}
此时,图片默认会显示在元素的左上角。
定位背景图片
我们可以使用background-position属性来精确控制背景图片在元素内的位置。
该属性接受两个值:第一个值控制水平位置,第二个值控制垂直位置。值可以是关键词(如left, center, right, top, bottom)或具体的长度单位(如像素px)。
以下是几个例子:
/* 图片位于右上角 */
background-position: right top;
/* 图片位于右下角 */
background-position: right bottom;
/* 只指定水平方向为右,垂直方向默认为居中 */
background-position: right;
/* 只指定垂直方向为底部,水平方向默认为居中 */
background-position: bottom;
使用简写属性
前面我们分别使用了background-color、background-image、background-repeat和background-position等属性。实际上,CSS提供了一个简写属性background,可以将所有这些设置合并到一行声明中。
简写属性的语法如下,属性值的顺序通常为:颜色、图片、重复方式、位置。
background: <color> <image> <repeat> <position>;
例如,我们可以将之前的设置合并为:
#bg {
width: 500px;
height: 500px;
background: blue url("yakov.png") no-repeat right center;
}
一个重要注意事项:当使用简写属性background时,它会重置所有未在简写中声明的单个背景属性(如background-color)。这意味着,如果你之前单独设置了background-color: blue;,然后又使用了background: url(...) no-repeat;但没有包含颜色,那么蓝色背景将会被清除。因此,在简写属性中需要包含所有你希望保留的设置。

总结
本节课中我们一起学习了CSS背景属性的强大功能。我们了解到:
- 除了设置纯色背景(
background-color),还可以设置图片背景(background-image)。 - 可以使用
background-repeat属性控制图片是否重复以及如何重复。 - 可以使用
background-position属性精确定位背景图片在元素中的位置。 - 可以使用简写属性
background来一次性设置所有背景相关的值,但要注意它会覆盖未在简写中声明的单个属性。
背景属性是网页视觉设计的基础工具,通过组合使用,可以创造出丰富的视觉效果。在后续课程中,我们还会学习如何结合媒体查询,让背景在不同屏幕尺寸下自适应变化。


接下来,我们将进入新的主题:学习如何使用浮动(float)来定位页面元素。
031:浮动定位 🎯


在本节课中,我们将学习如何使用CSS的float属性来定位元素。浮动是一种将元素从常规文档流中取出,并使其向左或向右移动,直到其外边缘碰到包含块或另一个浮动元素的边缘的技术。掌握浮动对于创建多列布局至关重要。
浮动基础


上一节我们介绍了盒模型,本节中我们来看看如何使用浮动来改变元素的布局方式。浮动元素会脱离正常的文档流,这会影响其周围元素的排列。
浮动元素
以下是浮动一个段落元素到右侧的示例代码:
p {
float: right;
}
应用此样式后,该元素会移动到其包含块的右侧顶部边缘。同时,文档流中的后续元素会向上移动,占据该元素原先的位置,因为浏览器认为该位置现在是“空”的。
边距与浮动
浮动元素的垂直边距不会发生折叠。这意味着,即使两个相邻元素的边距在常规流中会合并,一旦其中一个浮动,它们的边距将保持独立并完全显示。
清除浮动
当容器内的所有子元素都浮动时,容器的高度可能会“坍塌”,因为它不再包含在常规文档流中的内容。为了解决这个问题,我们需要使用clear属性。
使用 clear 属性
clear属性指定元素的哪一侧不允许出现浮动元素。这可以迫使该元素移动到浮动元素的下方,从而恢复正常的文档流。
以下是clear属性的三个值:
clear: left;:清除左侧浮动。clear: right;:清除右侧浮动。clear: both;:清除左右两侧的浮动。
例如,要确保一个section元素不被左侧的浮动元素影响,可以这样写:
section {
clear: left;
}
创建两列弹性布局
现在,让我们应用所学知识来创建一个经典的两列布局,该布局能够随着浏览器窗口的缩放而灵活调整。
布局结构

假设我们的HTML结构包含一个div,其内部有两个p元素和一个section元素。我们希望两个p元素并排形成两列。
关键样式步骤
以下是实现该布局的核心CSS步骤:

- 设置宽度:将每个段落的宽度设置为
50%,使其各占容器宽度的一半。 - 应用浮动:将两个段落都设置为
float: left;,使它们并排排列。 - 修正盒模型:使用
box-sizing: border-box;。这至关重要,因为它确保元素的width属性包含了内边距和边框的宽度。如果没有它,50% + 边框 + 内边距的总宽度会超过100%,导致第二列被挤到下一行。 - 添加内边距:为内容添加一些内边距(如
padding: 10px;)以改善可读性。 - 清除浮动:在
section元素上使用clear: both;,确保它出现在两列的下方,而不是被浮动元素环绕。
核心样式代码示例如下:
p {
width: 50%;
float: left;
box-sizing: border-box; /* 关键属性 */
padding: 10px;
}
section {
clear: both;
}
应用这些样式后,你将得到一个灵活的两列布局。当调整浏览器窗口大小时,两列的宽度会按比例变化,始终保持各占50%。
总结
本节课中我们一起学习了CSS浮动定位的核心概念。我们了解到,浮动元素会脱离正常文档流,可用于创建灵活的布局(如多列设计)。浮动元素的垂直边距不会折叠。当需要让后续元素脱离浮动影响、恢复正常流时,必须使用clear属性进行清除。通过结合百分比宽度和box-sizing: border-box,我们可以构建出自适应浏览器宽度的弹性布局。


下一节,我们将探讨相对定位和绝对定位。
032:第22讲 第1部分 相对与绝对定位

概述
在本节课中,我们将要学习CSS中两种重要的定位方案:相对定位和绝对定位。这两种方案允许你通过指定精确的偏移量,将目标元素移动到页面的不同位置或区域。
静态定位
上一节我们介绍了浮动定位,它是一种替代正常文档流的定位方式。本节中,我们来看看另外两种定位方案。
首先,让我们谈谈静态定位。静态定位本质上是“正常文档流”的另一种说法。实际上,除了HTML元素外,它是所有元素的默认设置。如果你尝试对position属性设置为static的元素应用定位偏移量,这些偏移量将被忽略。
相对定位
一种不同的定位方案称为相对定位。当你对一个元素应用position: relative;时,该元素将相对于其在正常文档流中的原始位置进行定位。换句话说,如果你对该元素应用偏移量,这些偏移量将基于该元素在正常文档流中的原始位置进行计算。
以下是CSS偏移属性:
topbottomleftright
当你将一个元素设置为相对定位时,你基本上是在为偏移创建一个锚点。元素的top、bottom、left和right边就成为了你偏移该元素的边界。
关于相对定位,需要了解的重要一点是:设置为相对定位的元素不会被移出正常文档流。事实上,即使它通过偏移量移动了,其原始位置仍然被保留。对于HTML页面上的其他元素而言,该元素仍然位于其原始位置,尽管在视觉上它被偏移到了其他地方。
让我们看一个简单的视觉示例。这里有一个段落,用这个橙色方框表示。一旦我们对该元素设置position: relative;,它的边缘就成为了未来偏移的锚点。现在,当我们应用偏移量时,元素相对于其在正常文档流中的原始位置移动。在这个例子中,它从顶部移动50像素,从左边缘移动50像素。
这些值可能有点令人困惑,因为我们说left: 50px;,但元素却向右移动。理解这些偏移量的方式不是“向左”或“向上”,而是“从左边”和“从顶部”。如果你从左边取走50像素,元素就向右移动50像素;从顶部取走50像素,元素就向下移动50像素。
还要注意,除了这个元素移动之外,文档的其他部分没有发生任何变化。该元素的原始空间仍然保留,其周围的元素布局也保持不变,因为它们认为该元素仍在其原始位置。
你也可以为这些偏移量使用负值。至于使用bottom/right还是top/left,这更多取决于你编码时的便利性,根据你试图实现的具体用例。在这个例子中,我们通过使用负的bottom和right值,达到了与使用正的top和left值相同的效果。
绝对定位
现在,我们来探讨绝对定位的概念。绝对定位的核心思想是:所有偏移量(top、bottom、left、right)都是相对于最近的、定位属性不是static的祖先元素的位置来计算的。换句话说,某个父级、祖父级或更高级的祖先元素必须将其定位设置为static以外的值,绝对定位才会真正开始工作。
默认情况下,HTML元素是唯一一个定位属性不是static的元素,它实际上被设置为relative。
与相对定位不同,如果一个元素的定位被设置为absolute,它会被移出其正常的文档流。
让我们假设我们有一个灰色方框,它是一个容器元素,其定位设置为relative。我们有一个编号为1的方框(这是我们的目标元素)和一个编号为2的方框(其他元素)。如果我们对1号元素设置position: absolute;,首先,它会被移出正常的文档流,并且在没有任何其他偏移量的情况下,它会停留在原来的位置。正如你在图示中看到的,元素2向上移动到了元素1的下方,因为元素1不再是文档流的一部分,元素2认为它根本不存在。
现在,如果我们应用一些偏移量(在这个例子中,我们使用bottom和right偏移),我们将相对于我们的容器元素进行定位,因为该容器元素的定位设置为relative。我们设置bottom: 10px;和right: 10px;,这样我们就得到了1号方框位于容器元素右下角的结果。

这种组合的一个非常酷的特性是:如果你的容器元素本身被偏移了,那么该容器内的所有内容都会随之偏移。这意味着你可以在一个容器元素内设置特定的布局,然后移动或偏移该容器元素到屏幕的任何位置,而无需担心容器内每个元素的特定偏移值。
代码示例
让我们跳转到代码编辑器,看看这些概念的一些具体例子。
好的,现在我正在Sublime Text中查看一个名为positioning_before.html的文件,它位于examples/lecture22文件夹中。让我们快速检查一下HTML的结构,它非常简单:我们只有一个H1标题,用来宣布我们在这里做什么;然后是一个ID为container的div;接着是四个段落,每个段落都有ID(p1、p2、p3、p4)。我们的初始样式也很简单:首先,我们重置了margin和padding,并将box-sizing设置为border-box。由于重置了边距,我们给第一个H1标题添加了15像素的底部边距,以推下页面的其余内容。

我们给div容器设置了熟悉的浅蓝色。每个<p>标签都是50像素乘50像素,有一个1像素的黑色边框,并且每个都有15像素的底部边距。每个段落都有不同的颜色,以便区分我们正在看的是哪一个。让我们在浏览器中看看它的样子。
正如你所看到的,这里有我们的四个段落方框,它们位于这个浅蓝色的容器div内。我们要做的第一件事是处理第一个方框。我们将把它的定位设置为relative。当我们这样做并保存刷新后,我们看不到任何明显的变化。实际上,这里发生的是我们现在设置了一个锚点,所以现在这个元素的所有偏移都将相对于其在正常文档流中的位置(它仍然在这里)进行计算。让我们给它一些偏移量,模拟它移动到第二行的位置。我们设置top: 65px;(从顶部向下65像素)和left: 65px;(从左边缘向右65像素),然后刷新页面。
好了,我们把这个元素从这里移到了这里。正如你所看到的,这个元素没有被移出文档流,因为其余元素仍然位于它们的原始位置,而这个空间仍然完全空着。实际上,浏览器认为这个空间仍然被我们刚刚移走的这个元素占据着。这就是相对定位的基本原理。
接下来,我们将看一个绝对定位的例子。
总结


本节课中,我们一起学习了CSS中的相对定位和绝对定位。我们了解到,相对定位允许元素相对于其原始位置进行偏移,同时保留其在文档流中的空间。而绝对定位则将元素移出文档流,并相对于最近的已定位祖先元素进行定位。掌握这两种定位方式是实现复杂页面布局的关键。
033:相对与绝对定位(第二部分)

在本节课中,我们将继续探索CSS定位,重点通过实际操作来理解绝对定位的行为,特别是它与祖先元素的关系。我们将通过移动一个盒子来演示关键概念。
上一节我们介绍了相对定位的基本概念,本节中我们来看看绝对定位在实际布局中如何工作。
实验绝对定位
让我们实际操作一下绝对定位。我们选择第三个盒子,它的颜色是军蓝色(虽然看起来有点偏绿)。我们将尝试使用绝对定位将它移回原来栗色盒子所在的位置。
为此,我们选中代表第三个盒子的段落(P3),并为其添加以下CSS样式:
position: absolute;
top: 0;
left: 0;
保存并刷新页面后,我们发现盒子确实被移出了正常的文档流,但它并没有出现在我们预期的位置(即原栗色盒子处),而是紧贴在了浏览器的边缘。
理解定位的“锚点”
为什么会出现这种情况?让我们再次审视文档结构来找出原因。
代表第三个盒子的元素是<p id="p3">。我们回忆一下,绝对定位的元素需要一个设置了position: relative或position: absolute的父级或祖先元素作为其定位的参照物(“锚点”)。
该元素会向上查找其祖先链:
- 首先检查其直接父容器(
div.container)是否设置了非static的定位。答案是否定的。 - 于是它继续向上查找
<body>元素。答案同样是否定的。 - 最终,它找到了
<html>元素。默认情况下,<html>元素的position被设置为relative。因此,这个绝对定位的盒子就以整个浏览器视口(viewport)的左上角为原点进行定位了。
修复定位:设置相对容器
如何修复这个问题?很简单:我们需要为绝对定位元素的容器设置一个相对的定位上下文。
既然我们希望这个盒子相对于其外层的div容器进行定位,我们就需要将这个容器的position设置为relative。
让我们为div.container添加样式:
position: relative;
保存并刷新后,第三个盒子现在准确地跳转到了原栗色盒子的位置。这是因为现在#p3找到了一个设置了position: relative的最近祖先(div.container),并以其边界为基准进行定位。
移动整个容器组

最后,让我们尝试将整个容器元素向下移动一些像素,观察其内部所有元素的定位是否会随之整体移动。
为此,我们为div.container添加一个top偏移值:
top: 60px;
保存并刷新页面,可以看到整个容器连同其内部所有定位好的元素一起向下移动了60像素,并且它们彼此之间的相对位置关系保持不变。
这个特性为我们提供了巨大的灵活性,可以轻松地移动一整组元素,而不仅仅是单个元素。
本节总结

本节课中我们一起学习了绝对定位的实践应用。以下是核心要点的回顾:
- 静态定位:是除
<html>外所有元素的默认定位方式。<html>元素的定位默认为relative。 - 相对定位:使元素相对于其在正常文档流中的原始位置进行偏移。移动相对定位的元素不会影响其他元素的正常文档流布局。
- 绝对定位:使元素相对于其最近的一个设置了非
static定位(即relative或absolute)的祖先元素进行定位。 - 关键影响:绝对定位的元素会被完全移出正常文档流,后续元素在布局时会表现得如同这个元素不存在一样。
- 容器控制:通过为容器设置
position: relative,可以控制其内部绝对定位子元素的定位基准。移动这个相对定位的容器,其内部所有定位元素会作为一个整体随之移动。

下一节,我们将开始学习媒体查询,这是实现响应式网页设计的关键技术。
034:17_第23讲 第1部分 媒体查询

概述
在本节课中,我们将要学习CSS媒体查询的基础知识。媒体查询是响应式网页设计的核心技术,它允许我们根据用户设备的特性(如屏幕宽度、高度或方向)来应用不同的CSS样式。
媒体查询简介
媒体查询允许你将样式分组,并根据特定条件将其应用到不同的设备上。例如,你可以根据设备的宽度、高度或方向(横向或纵向)来应用样式。在桌面浏览器和手机上查看网站时,最明显的区别之一就是屏幕尺寸。请记住,使用CSS,你有能力从相同的HTML代码生成截然不同的网页布局。因此,为不同屏幕尺寸的设备提供不同的样式,是调整页面样式和布局最常见的方式。这就是为什么学习如何使用媒体查询至关重要,没有它们,我们即将讨论的响应式设计就无法实现。

接下来,让我们探索媒体查询的基本语法。
媒体查询基本语法
一个媒体查询以 @media 关键字开始,后面跟着一个或多个媒体特性,最后用花括号 {} 包裹样式规则。它基本上就像一个样式表中的样式表。
每个媒体特性会解析为 true 或 false。你可以使用逻辑运算符将多个媒体特性组合在一起。如果媒体特性解析为 true,则花括号内的样式将被应用。在编写媒体查询时,请确保正确关闭整个媒体查询的花括号,以及每个单独的样式规则。
以下是媒体查询的基本语法结构:
@media (media-feature) {
/* 你的样式规则 */
}
常用媒体特性
有许多可用的媒体特性。例如,你可以使用 max-width(最大宽度)、min-width(最小宽度)、height(高度,此处未列出),甚至可以针对设备的朝向(纵向或横向)。你还可以指定 only screen(仅屏幕)而非 only print(仅打印)。如果这些条件中的任何一个评估为 true,媒体查询花括号内的样式就会生效。
如前所述,尽管有许多媒体特性可用,但最常用的是 max-width 和 min-width。这主要是因为针对不同设备最常见的方式就是根据设备的宽度。
组合媒体特性
此外,媒体特性可以使用逻辑运算符进行组合。最常见的逻辑运算符之一是 and 运算符。
以下是一个代码片段示例,它针对一个宽度范围:
@media (min-width: 768px) and (max-width: 991px) {
/* 样式规则 */
}
在这个例子中,我们针对的是宽度在768像素到991像素之间的任何设备。如果设备的宽度小于或大于这个范围,此媒体查询内的样式将不会应用。
另一种组合媒体特性的方法是在它们之间使用逗号 ,,这基本上等同于逻辑上的 or 运算符。
以下代码片段展示了我们如何针对宽度不超过767像素或宽度至少为992像素的任何设备:
@media (max-width: 767px), (min-width: 992px) {
/* 样式规则 */
}
在实际应用中,当你处理响应式设计和布局时,最常用的逻辑运算符是 and 运算符。所以,如果你掌握了这个运算符,就基本够用了。
媒体查询的组织结构
在深入代码示例之前,我想展示一种非常常见的方法,即如何在样式表中组织媒体查询。通常,你会有几个这样的媒体查询,但你几乎总是从一些基础样式开始。基础样式将普遍适用,无论你实际在何种屏幕尺寸上查看网站。然后,你开始针对特定的屏幕尺寸,通过更改基础样式的某些属性、添加新样式或移除某些样式来实现调整。
这里需要指出一个重点,尤其是在使用设备屏幕尺寸的宽度时,你必须非常小心,不要让范围边界重叠。在这个例子中,你会注意到第一个查询的最小宽度是1200像素,而第二个查询是一个范围,其最大宽度是1199像素。如果我写成1200像素,就意味着两个样式集都会应用,这很可能会导致样式混乱且难以维护。因此,在构建这些媒体查询时,保持清晰、独立的边界非常重要。
在本讲座的第二部分,我们将进入代码编辑器,看看这些概念的实际应用。


总结
本节课中我们一起学习了CSS媒体查询的基础知识。我们了解了媒体查询的语法结构,认识了最常用的媒体特性(如 max-width 和 min-width),并学习了如何使用 and 和 ,(或)运算符来组合多个条件。我们还探讨了在样式表中组织媒体查询的常见模式,即从基础样式开始,再针对不同屏幕尺寸进行覆盖或增强,并强调了避免范围边界重叠的重要性。掌握这些基础知识是进行响应式网页设计的关键第一步。
035:媒体查询实践

在本节课中,我们将学习如何使用CSS媒体查询来创建响应式网页设计。我们将通过一个简单的例子,了解媒体查询的基本语法、如何设置断点,以及如何使用浏览器开发者工具进行调试。
上一节我们介绍了媒体查询的概念,本节中我们来看看如何在实际代码中应用它。

我们首先查看一个名为 media queries before 的HTML文件。它的结构非常简单,包含一个H1标题和两个段落。

以下是该HTML文件的基本结构:
<h1>媒体查询示例</h1>
<p id="p1">第一个段落</p>
<p id="p2">第二个段落</p>
在应用任何媒体查询之前,我们为段落设置了一些基础样式。这些样式将作为所有屏幕尺寸的默认样式。
以下是应用于段落的基础CSS规则:
p {
border: 1px solid black;
margin-bottom: 15px;
}
#p1 {
background-color: lightblue;
width: 300px;
height: 300px;
}
#p2 {
background-color: lightcoral;
width: 50px;
height: 50px;
}
在浏览器中查看,我们可以看到两个不同大小的盒子。当我们缩小浏览器窗口时,它们的样式不会改变。接下来,我们将使用媒体查询来根据屏幕宽度调整它们的样式。

现在,让我们开始编写我们的第一个媒体查询。我们将针对“大”设备,即宽度至少为1200像素的屏幕。
以下是为大屏幕设备(≥1200px)编写的媒体查询:
@media (min-width: 1200px) {
#p1 {
width: 80%;
}
#p2 {
width: 150px;
height: 150px;
}
}
应用此查询后,当屏幕宽度达到或超过1200像素时,第一个段落的宽度将变为屏幕宽度的80%,第二个段落将变为150x150像素的正方形。我们可以使用Chrome开发者工具(按 Cmd+Opt+I 或 Ctrl+Shift+I 打开)来拖动窗口边缘,观察在1200像素断点处样式的切换。

上一节我们为大型设备设置了样式,本节中我们为中型设备添加另一个媒体查询。为了避免样式冲突,我们需要确保断点不重叠。
以下是为中型屏幕设备(992px - 1199px)编写的媒体查询:
@media (min-width: 992px) and (max-width: 1199px) {
#p1 {
width: 50%;
}
#p2 {
width: 100px;
height: 100px;
}
}
现在,我们的样式规则如下:
- 基础样式:
#p1为300x300像素,#p2为50x50像素。 - 中型屏幕(992px-1199px):
#p1宽度变为50%,#p2变为100x100像素。 - 大型屏幕(≥1200px):
#p1宽度变为80%,#p2变为150x150像素。
在浏览器中调整窗口大小,可以清晰地看到在992像素和1200像素这两个断点处,样式发生了切换。

Chrome开发者工具提供了一个强大的功能来模拟不同设备和屏幕方向,这对于测试响应式设计至关重要。
以下是使用Chrome开发者工具测试响应式设计的步骤:
- 打开开发者工具(
Cmd+Opt+I/Ctrl+Shift+I)。 - 点击工具栏上的切换设备工具栏图标(手机形状)。
- 在顶部可以选择预设的设备(如iPad),或手动调整窗口尺寸。
- 工具还会在CSS面板中高亮显示当前生效的媒体查询规则。
通过这个工具,我们可以方便地观察网站在不同断点下的表现,并进行调试。
本节课中我们一起学习了媒体查询的实践应用。
我们回顾了媒体查询的基本语法:@media (媒体特征) { ... }。在响应式布局中,我们通常使用 and 逻辑运算符来组合多个特征,例如 @media (min-width: 992px) and (max-width: 1199px)。
我们强调了设置断点时不要重叠的重要性,通常的做法是:
- 先编写适用于所有设备的基础样式。
- 然后通过媒体查询,针对越来越大的屏幕尺寸,逐步添加或覆盖样式规则。
这种方法使CSS代码更清晰、更易于维护。


接下来,我们将探讨响应式布局。
036:19_第24讲 第1部分 响应式设计

📱 概述
在本节课中,我们将要学习响应式设计的基本概念、其重要性以及实现它的核心原则。我们将了解为什么响应式设计是现代网页开发不可或缺的一部分,并初步探索其背后的布局思想。
🌍 响应式设计的诞生背景
响应式设计主要源于应对移动设备爆炸式增长的需求,这些设备开始能够像桌面浏览器一样浏览网页。
我在网上找到了这张折线图,它源自摩根士丹利研究所的一些研究。我无法确认这些数字的精确性,但其核心观点是众所周知的:使用移动设备浏览网页的用户数量已经超过了使用常规桌面浏览器的用户。观察这张图表,这个转变大约发生在2013至2014年间,这看起来是合理的。这件事已经发生了一段时间了。
因此,关键在于你无法忽视移动端。你必须也为移动设备设计你的网站。
📱 目标设备的选择
当涉及到移动设备时,设备种类繁多。我们想要针对和支持哪些设备呢?答案实际上是所有设备。你无法承担忽视任何设备的后果。市面上的设备种类如此丰富,忽视其中一部分就等于忽视了你的一部分用户群。

那么,响应式设计如何帮助我们做到这一点呢?我认为答案在于响应式设计本身是什么。
💧 什么是响应式网站?
响应式网站是指通过使用流式、基于比例的网格、灵活的图片和CSS3媒体查询,来使其布局适应观看环境的网站。
当我们说“基于比例的网格”时,我们的意思是,这些网格或列的宽度(我们稍后会看到)应该使用百分比来指定。
🥤 内容如水
我非常喜欢有人在搜索响应式设计时整理并放在维基百科上的这个可视化比喻。这是该页面上的图片之一,它基本上转述(或者可能是直接引用)了著名武术专家李小龙的一句话:“把水倒入杯中,它就成为杯的形状;把水倒入瓶中,它就成为瓶的形状;把水倒入茶壶中,它就成为茶壶的形状。”
这个理念很简单:内容应该像水一样。无论你把它放入哪种不同的设备中,它都应该能够显示。内容本身应该是相同的,所传达的信息也应该是相同的。可能唯一不同的是内容的形状,即它的显示方式。

🔧 响应式设计的实践含义
运用前一张图的理念,我们现在可以理解,网站的布局应该适应设备的大小。在实践中,大多数时候(如果不是所有时候),这意味着网站应该根据设备的宽度进行调整。
我还应该指出,内容的呈现方式或其视觉传达可能会改变。
以下是响应式设计可能带来的具体变化:
- 元素重要性的调整:例如,对于一个餐厅网站,在桌面版本中,电话号码可能被显示在网站的某个位置,并不特别引人注目。而在移动版网站中,它可能被置于最前面、最中心的位置,成为页面上最突出的项目。这可能是因为我们希望人们在手机上能直接点击拨打电话下单,或者仅仅是因为移动设备的屏幕空间有限,无法让他们去其他地方寻找那个电话号码。
- 内容的隐藏:网站的某些部分甚至可能在移动版本中被隐藏。因为我们现在必须非常仔细地衡量哪些部分是最关键的。
🔄 响应式设计的替代方案
响应式设计原则有替代方案吗?实际上是有的。你可以使用服务器端技术来检测用户的用户代理(换句话说,就是他们使用的浏览器类型),从而判断他们是在移动设备还是桌面设备上。然后,基于这个信息,要么提供常规的桌面版网站,要么向客户端提供该网站的移动版本。
在响应式设计成为主流之前,那些也想拥有移动版本的网站基本上就是这样做的。但问题是,这种方法存在几个问题:
- 功能版本不一致的风险很高:因为从技术上讲,你现在确实有两个不同的应用程序在运行你的网站,一个是移动版本,一个是桌面版本。
- 移动设备尺寸差异太大:很难制作一个能满足所有客户需求的移动网站。最终的结果是,你牺牲了用户体验,因为你试图制作一个适合所有人的移动网站,而这基本上从未成功过。
因此,如今由于这些问题,人们基本上都避免使用这种方法。
📐 响应式布局的核心:12列网格
响应式设计中最大的部分显然是布局。目前最常见或最流行的响应式布局是12列网格响应式布局。这是Bootstrap(Twitter Bootstrap)使用的布局,现在几乎所有响应式框架都在使用。
之所以使用12,是因为12的因数。12可以很好地被1、2、3、4、6和12本身整除。这意味着我们可以将页面均匀地分割成多个部分,并很好地布局。
例如,我们可以将内容分割成12列中的3列(即3.333...),这样加起来总共是12列。如果你感到困惑,我在这里留了一些内边距,只是为了可视化,让你能更容易地看到不同的列。
显然,在这种布局中,你可以有6和6、4、4和4,或者任何其他你想要的组合,比如8和4、9和3等等。
这种响应式布局的方法是:我们知道浏览器窗口的宽度是100%。这意味着我们可以计算出在这个12列网格布局中,一列的宽度是多少。那就是100%除以12,结果大约是8.33%。
这意味着当我们在我们的框架(如Bootstrap或你正在使用的任何框架)中指定某一列应该有多宽时,我们可以将这些列数转换为百分比。例如:
- 3列变成12列的25%。
- 6列变成12列的50%。
- 4列变成12列的33.33%。
显然,我们不仅限于只有一个12列网格布局。我们完全可以在内部嵌套网格。如果你有4、4和4的布局,在每个4列内部,我们可以将其视为100%,然后在其中再放置另一个12列网格布局。
因此,在左边第一个例子中,我们看到6和6;在中间的例子中,我们看到一堆3;在最右边的例子中,我们看到4、4和4。这完全可以接受,事实上也经常这样做。
📝 总结
本节课中,我们一起学习了响应式设计的起源和必要性,理解了“内容如水”的核心哲学。我们探讨了响应式网站的定义及其三个关键技术:流式网格、灵活图片和媒体查询。我们还分析了响应式设计与传统服务器端检测方法的优劣,并重点介绍了目前最主流的12列网格布局系统及其数学原理。


在下一部分,我们将进入代码编辑器,亲眼看看这些概念是如何在实践中应用的。
037:响应式设计实战

在本节课中,我们将学习如何构建一个简单的响应式网格框架,并理解其核心原理。我们将从编写CSS样式和媒体查询开始,最终实现一个能在不同屏幕尺寸下自动调整布局的网页。
概述
响应式设计的目标是让网页在不同设备上都能提供良好的浏览体验。本节我们将手动创建一个基于12列网格的响应式布局系统,并解决移动设备上常见的视口缩放问题。
第一步:基础样式设置
首先,我们为段落标签设置一些基础样式,它将作为我们布局中的视觉参考元素。
p {
background-color: #A9A9A9;
border: 1px solid black;
height: 150px;
width: 90%;
margin-left: auto;
margin-right: auto;
}
这段代码为段落设置了背景色、边框和固定高度。宽度设为90%是为了模拟内边距效果,使其不占满整个网格单元格。通过设置左右外边距为auto,我们实现了在块级元素(div)内的水平居中。
第二步:构建响应式网格框架

上一节我们设置了基础样式,本节中我们来看看如何构建核心的网格框架。我们的框架将基于浮动布局,并通过媒体查询来适应不同屏幕尺寸。
以下是框架的核心CSS代码:
首先,定义行容器,它占据父元素的全部宽度。
.row {
width: 100%;
}
接着,我们为大屏幕设备(宽度大于1200像素)定义列样式。我们使用lg(large)作为类名前缀。
@media (min-width: 1200px) {
.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4,
.col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8,
.col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
float: left;
border: 1px solid green;
}
.col-lg-1 { width: 8.33%; }
.col-lg-2 { width: 16.66%; }
.col-lg-3 { width: 25%; }
.col-lg-4 { width: 33.33%; }
.col-lg-5 { width: 41.66%; }
.col-lg-6 { width: 50%; }
.col-lg-7 { width: 58.33%; }
.col-lg-8 { width: 66.66%; }
.col-lg-9 { width: 75%; }
.col-lg-10 { width: 83.33%; }
.col-lg-11 { width: 91.66%; }
.col-lg-12 { width: 100%; }
}
核心概念:在12列网格中,每列的宽度是总宽度的1/12。计算方式为:
单列宽度 = 100% / 12 ≈ 8.33%
N列宽度 = (100% / 12) * N
然后,我们为中屏幕设备(宽度在950像素到1199像素之间)定义另一组列样式,使用md(medium)作为前缀。
@media (min-width: 950px) and (max-width: 1199px) {
.col-md-1, .col-md-2, .col-md-3, .col-md-4,
.col-md-5, .col-md-6, .col-md-7, .col-md-8,
.col-md-9, .col-md-10, .col-md-11, .col-md-12 {
float: left;
border: 1px solid red;
}
.col-md-1 { width: 8.33%; }
.col-md-2 { width: 16.66%; }
/* ... 其余列宽度定义与.lg类似 ... */
.col-md-12 { width: 100%; }
}
布局逻辑:通过为同一元素分配不同的类(如col-lg-3和col-md-6),我们可以确保在不同屏幕尺寸下应用不同的样式。由于媒体查询的范围不重叠,同一时间只有一个类会生效。

第三步:编写HTML结构
框架定义好后,我们需要编写HTML来使用它。以下是页面结构:
<h1>响应式设计演示</h1>
<div class="row">
<div class="col-lg-3 col-md-6"><p>项目 1</p></div>
<div class="col-lg-3 col-md-6"><p>项目 2</p></div>
<div class="col-lg-3 col-md-6"><p>项目 3</p></div>
<div class="col-lg-3 col-md-6"><p>项目 4</p></div>
<div class="col-lg-3 col-md-6"><p>项目 5</p></div>
<div class="col-lg-3 col-md-6"><p>项目 6</p></div>
<div class="col-lg-3 col-md-6"><p>项目 7</p></div>
<div class="col-lg-3 col-md-6"><p>项目 8</p></div>
</div>
布局说明:
- 在大屏幕上(>1200px),每个
div使用.col-lg-3,即每行放置4个项目(3+3+3+3=12)。 - 在中等屏幕上(950px-1199px),每个
div使用.col-md-6,即每行放置2个项目(6+6=12)。 - 在小屏幕上(<950px),没有媒体查询生效,
div失去浮动属性,变为块级元素并垂直堆叠。


第四步:移动设备视口问题与解决
在浏览器中测试时,布局切换正常。但在真实手机(如iPhone 6)上查看时,中屏布局(两列)却意外地出现在小屏幕上。
通过Chrome开发者工具的移动设备模拟器,我们发现问题的根源:手机浏览器为了适配非响应式网站,默认会进行缩放(viewport zoom out),导致媒体查询判断的屏幕宽度与实际不符。
解决方法是添加一个特殊的meta标签,告诉浏览器使用设备的实际宽度作为视口宽度,并禁止初始缩放。
<meta name="viewport" content="width=device-width, initial-scale=1">

将此标签放入HTML文档的<head>部分后,移动设备浏览器就会正确识别屏幕尺寸,我们的单列布局得以在小屏幕上正常显示。
总结
本节课中我们一起学习了响应式设计的核心实践:
- 构建了12列网格系统:使用百分比宽度实现流体布局,通过浮动排列元素。
- 应用了媒体查询:针对不同屏幕尺寸范围(大屏、中屏)定义不同的列宽规则,实现布局自动切换。
- 理解了移动视口:认识到移动浏览器默认缩放行为对响应式设计的影响。
- 使用了Viewport Meta标签:通过
<meta name="viewport">确保网页在移动设备上以正确尺寸渲染,这是响应式设计的必要步骤。



通过这个简单框架的搭建,我们掌握了响应式布局的基本原理。接下来,我们将开始学习一个更成熟、功能更强大的工具——Twitter Bootstrap框架。
038:21_第25讲 第1部分 Twitter Bootstrap入门

概述
在本节课中,我们将要学习Twitter Bootstrap,这是一个非常流行的CSS框架。我们将了解Bootstrap是什么、它的核心特点、优势与劣势,并初步了解如何开始使用它。
什么是Bootstrap?🚀
Bootstrap是最流行的HTML、CSS和JavaScript框架,用于在Web上开发响应式、移动设备优先的项目。这是Bootstrap官网的直接引用。
让我们来详细解析一下。首先,它是最流行的框架。Github是一个拥有社交功能的代码仓库网站,Bootstrap项目在那里获得了超过90,000个星标或点赞,是整个网站上最受欢迎的项目,并且被分叉了超过38,000次。
Bootstrap主要由CSS构成。它为你预定义了许多CSS类。很多时候,特别是对于比单个HTML元素更复杂的用户界面,CSS需要针对特定的HTML结构。因此,Bootstrap通常要求你在HTML页面中拥有特定的HTML结构。
然而,在实践中,这并不像听起来那么可怕。它并不会真正侵入你的HTML代码,迫使你编写一些奇怪的HTML契约。它只是要求你在这里或那里添加一个额外的<div>,并赋予它一个特定的类,以便Bootstrap定义的CSS能够正确应用。
Bootstrap也是一个JavaScript框架,这一点不容忽视。它有一个非常酷的JavaScript框架,实际上基于jQuery的插件架构,允许你或其他人添加不同的插件,从而极大地增强你的网站。
什么是“移动设备优先”框架?📱
Bootstrap也被称为“移动设备优先”框架。那么,这是什么意思呢?
一般来说,这个术语意味着你首先处理用户界面在移动设备上的布局。根据我的观察,对于这个含义有两种理解方式。
第一种是纯粹主义者的方法,即首先编写移动版本。其主张是这确实有助于你的内容策略,帮助你规划内容的布局和内容本身,迫使你思考即将呈现的内容中真正重要的是什么。
第二种是对“移动设备优先”含义的理解,我称之为非100%纯粹主义者的方法,我自己也属于这种思维方式。其理念是你在样式中为移动设备做规划,但如果感觉更舒适或对你的项目更有意义,你可以先从桌面版本开始编码。
就编码而言,无论是从移动端开始再到桌面端,还是从桌面端开始再到移动端,都是两种有效的方法。
然而,每个人都会同意这一点:“移动设备优先”绝对意味着你从一开始就为移动设备做规划,而不是等到项目中期或末期才考虑。他们还同意,无论是你自己编写的还是像Bootstrap这样的CSS框架,都应该已经是移动设备优先的。这意味着它不应该是一个事后才考虑的东西,在你设置好桌面版本后,才不得不编写或包含一些其他CSS来试图神奇地将整个网站也变成移动网站。
有趣的是,Bootstrap并非一直是一个移动设备优先的框架。我忘了是哪个版本(也许是版本2左右),那时Bootstrap有一个单独的CSS文件和一套单独的类,如果你想让你的网站具有响应式能力,或者基本上也能在移动设备上查看的话。
如今,Bootstrap是一个移动设备优先的框架。
使用Bootstrap的优缺点 ⚖️
与例如像上一讲中我们自己编写框架相比,使用Bootstrap有什么缺点吗?
首要的抱怨,也就是Bootstrap的缺点,是它太大、太臃肿。这意味着里面有很多你可能永远不会用到的功能。
缓解因素是,你可以在Bootstrap网站上使用选择性下载,只下载你认为实际会用到的功能。但事实是,尽管它确实有点臃肿,但情况并没有那么糟糕。
还有一种说法是,你可以编写一个更有针对性、因此更小的自己的框架。这可能是真的,但编写它需要你花费更长的时间,而且肯定需要你花费更长的时间才能写得像Twitter的工程师们那样好,他们对每一个小细节都投入了大量的思考和艰苦的决策,你可能根本没有时间和精力去处理。
下一部分预览
在本讲座的第二部分,我们将跳转到代码编辑器,看看一个基本的、骨架式的Bootstrap HTML文档是什么样子,以及如何正确地设置它。
总结


本节课中我们一起学习了Bootstrap框架的基础知识。我们了解到Bootstrap是一个流行的、移动设备优先的CSS和JavaScript框架,它能帮助开发者快速构建响应式网站。我们讨论了它的核心概念,包括预定义的CSS类、对特定HTML结构的要求、以及其“移动设备优先”的设计哲学。同时,我们也客观地分析了使用Bootstrap可能带来的优点(如快速开发、社区支持)和缺点(如文件体积较大)。下一节,我们将进入实践环节,开始搭建第一个Bootstrap页面。
039:22_第25讲 第2部分 Twitter Bootstrap入门

概述
在本节课中,我们将学习如何下载、安装并设置一个基础的Twitter Bootstrap项目。我们将了解Bootstrap项目的基本结构,以及如何创建一个包含必要依赖的HTML页面。
下载与项目结构
上一节我们介绍了Bootstrap的基本概念,本节中我们来看看如何获取Bootstrap文件并搭建基础项目。
首先,访问Bootstrap官方网站 getbootstrap.com。在“Getting Started”页面,可以找到下载选项。最简单的选择是下载已编译好的Bootstrap版本。下载并解压ZIP文件后,你会得到三个核心文件夹:css、fonts 和 js。
以下是这些文件夹的内容说明:
css文件夹:包含Bootstrap的核心样式文件。其中bootstrap.css是完整版,bootstrap.min.css是压缩版(移除了空格和换行以减小文件体积)。fonts文件夹:包含Bootstrap框架所需的图标字体文件,对基本功能是必需的。js文件夹:包含Bootstrap的JavaScript组件。同样提供完整版 (bootstrap.js) 和压缩版 (bootstrap.min.js)。
此外,Bootstrap的JavaScript组件依赖于jQuery库。你需要从 jquery.com 下载jQuery。通常建议下载1.x版本以确保对旧版浏览器的兼容性,但随着IE旧版本用户减少,未来可考虑使用2.x版本。下载后,将jQuery文件(例如 jquery-1.11.3.min.js)也放入项目的 js 文件夹中。
基础HTML页面解析
了解了项目文件结构后,接下来我们分析一个基础的Bootstrap HTML页面模板。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<title>Bootstrap 101 Template</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Hello, Coursera!</h1>
<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>

以下是页面关键部分的详细说明:
<meta http-equiv="X-UA-Compatible" content="IE=edge">:此标签建议IE浏览器使用其最新版本的渲染引擎。<meta name="viewport" ...>:视口标签,确保页面在移动设备上正确缩放,与响应式设计相关。- 条件注释与脚本:
<!--[if lt IE 9]> ... <![endif]-->这段代码是为IE8及以下版本引入HTML5支持 (html5shiv) 和媒体查询支持 (respond.js) 的垫片脚本。随着IE8逐渐被淘汰,这部分未来可能会移除。 - 样式表顺序:首先引入
bootstrap.min.css,然后引入自定义的styles.css。这样做的目的是让我们的自定义样式能够覆盖Bootstrap的默认样式。 - 脚本顺序:在
</body>标签结束前引入JavaScript文件。顺序很重要:必须先引入jquery...js,然后才是bootstrap.min.js,因为Bootstrap的JS依赖于jQuery。最后引入我们自己的script.js文件。
测试与验证
现在,让我们在页面中添加一些内容来测试Bootstrap是否正常工作。在 <body> 标签内添加一个标题:
<h1>Hello Coursera</h1>

保存文件并在浏览器中打开。你会看到“Hello Coursera”的样式已经发生了变化(字体和颜色与浏览器默认不同),这表明Bootstrap的样式已成功应用。
为了进一步验证,可以在页面上右键点击并选择“检查元素”。在开发者工具中,可以看到该 <h1> 元素确实应用了来自 bootstrap.css 的样式规则。
同时,检查浏览器的控制台(Console)非常重要。如果文件路径错误或脚本加载失败,控制台会显示错误信息。没有错误信息则表明所有依赖都已正确加载,项目基础搭建成功。
这个页面就是你可以用来开始构建基于Bootstrap的网站的基础模板。
总结
本节课中我们一起学习了如何开始使用Twitter Bootstrap。我们认识了GitHub上最受欢迎的项目之一——Bootstrap,并理解了“移动优先”的设计理念。我们详细讲解了如何下载和安装Bootstrap的核心文件及其依赖项jQuery,并逐步构建和测试了一个基础的Bootstrap HTML页面,确保其没有错误且能正常工作。


接下来,我们将讨论Bootstrap CSS框架的核心主题之一:Bootstrap网格系统。
040:23_第26讲 第1部分 Bootstrap网格系统

在本节课中,我们将要学习Bootstrap框架中一个最核心的组件——网格系统。它允许开发者轻松创建响应式布局。我们将详细拆解其组成部分和工作原理,并通过示意图帮助理解其设计思想。
Bootstrap网格系统是创建响应式布局的核心。它通过一套预定义的CSS类,将页面水平划分为最多12列,并允许内容根据屏幕尺寸自动调整排列方式。任何Bootstrap网格布局都包含几个基本的结构组件。
容器 (Container)
首先,Bootstrap网格必须包裹在一个容器元素内。容器有两种主要类型:
.container-fluid:此类使布局拉伸至浏览器的全部宽度,并在网格和其他内容周围提供一致的填充(padding)。.container:此类使用预定的固定宽度,该宽度会根据浏览器宽度(即不同的断点)进行响应式调整。
容器内不仅可以放置网格,也可以放置其他内容。但网格要求必须位于某个容器包装器内。
行 (Row)
上一节我们介绍了容器,本节中我们来看看行。.row类用于创建列的水平分组。
这意味着同一行内的列会作为一个整体进行折叠和交互,而独立于其他行中的列。.row类还会创建一个负外边距(negative margin),用以抵消容器类所设置的填充(padding)。
为什么要这样做呢?因为每一列本身已有内边距(padding)以实现视觉分隔。如果不对行应用负外边距,容器的填充就会与边缘列的填充叠加,导致列的内容无法与网格外、但仍在容器内的其他内容完美对齐。
Bootstrap文档对此有解释,但为了更直观地理解,让我们通过一个示意图来展示。
以下是.row类通过负外边距实现对齐的示意图:
- 未使用负外边距时:容器有填充,列也有填充。列内容的起始线与容器内其他常规内容的起始线不对齐。
- 使用
.row类后:行元素通过负外边距抵消了容器的填充,从而将列内容向外推,使其与容器内其他常规内容的边缘完美对齐。
这种对齐不同内容块边缘的概念是网页设计的基本原则。任何优秀的网站都遵循这一点。观察Facebook或Coursera等页面,你会发现图片、文本和按钮等元素都严格对齐,这使得页面结构清晰、视觉舒适且易于阅读。
列 (Columns)
理解了行如何组织列并实现对齐后,本节我们重点看列本身。你可以在之前的响应式设计讲座中见过类似的列类定义。Bootstrap的列类遵循一个特定的模板。
每个Bootstrap列类都使用以下模板定义:col-{size}-{span}。
让我们分解这个模板的两个部分:
-
尺寸 (Size):这是一个屏幕宽度范围的标识符,例如
md代表中等(medium),lg代表大(large)等。具体数值可在Bootstrap文档中查询。例如,lg定义为1200像素及以上。- 这意味着,在指定宽度(如≥1200px)以上,列将按照你设定的
col-{size}-{span}规则排列。 - 如果屏幕宽度低于该阈值,此列类规则将不再适用,该行内的列将相互“折叠”,即垂直堆叠在一起。此时,除非有其他适用的规则(例如为更小屏幕
md定义的列规则),否则所有内容将呈现为单列。
- 这意味着,在指定宽度(如≥1200px)以上,列将按照你设定的
-
跨度 (Span):这定义了元素应横跨的列数,取值范围为1到12。
- Bootstrap采用12列网格布局。同一行内所有列的
span值之和应等于12。例如,指定三个col-md-4,总和就是12。 - 如果你指定的列跨度总和超过12,超出的列将自动换行到下一行(这并非Bootstrap中一个新的
.row,而是自动换行)。这个过程每满12列就会重复一次。
- Bootstrap采用12列网格布局。同一行内所有列的
最后,在网格中使用<div>元素并非强制。你可以根据设计需要自由使用任何语义化的HTML元素。例如,完全可以使用<header>和<nav>元素来定义容器和行组件。


本节课中我们一起学习了Bootstrap网格系统的三个核心组成部分:容器、行和列。我们了解了容器为布局提供基础宽度和填充,行通过负外边距实现内容的精确对齐以优化视觉体验,而列则通过col-{size}-{span}的类命名规则,让我们能够灵活地创建响应式布局。在下一部分,我们将进入编辑器,动手实践这些概念。
041:Bootstrap网格系统(第二部分)

在本节课中,我们将深入学习Bootstrap的网格系统,了解其核心结构、响应式断点以及如何通过不同的类来控制布局。我们将通过一个实际的例子,演示如何创建响应式网格,并解释不同尺寸标识符(如md、sm、xs)的行为差异。
准备工作 🛠️
首先,我们在Sublime Text中打开位于Exs lecture 26文件夹下的gridd before that HTML文件。这个文件是一个标准的Bootstrap页面起始模板。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap Grid Demo</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="path/to/bootstrap.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div>Column 1</div>
<div>Column 2</div>
<div>Column 3</div>
<!-- jQuery and Bootstrap JS -->
<script src="path/to/jquery.js"></script>
<script src="path/to/bootstrap.js"></script>
</body>
</html>
我们移除了仅用于IE8支持的HTML5 shim代码。当前,页面包含三个简单的<div>元素,它们会像普通块级元素一样垂直堆叠。
构建网格结构 🏗️
上一节我们查看了起始文件,本节中我们来看看如何将其转换为Bootstrap网格。
根据Bootstrap规则,网格系统必须位于一个具有container或container-fluid类的元素内。这里我们使用container-fluid来获得全宽容器和两侧的内边距。
<body>
<div class="container-fluid">
<div>Column 1</div>
<div>Column 2</div>
<div>Column 3</div>
</div>
...
</body>
保存并刷新浏览器后,可以看到容器两侧出现了15像素的内边距。


接下来,我们需要将列放入一个具有row类的元素中。row类用于抵消container的内边距,并为列提供正确的排列环境。
<div class="container-fluid">
<div class="row">
<div>Column 1</div>
<div>Column 2</div>
<div>Column 3</div>
</div>
</div>
现在,我们可以为每个列指定Bootstrap的网格类。我们使用col-md-4,这表示在中等(md)及以上尺寸的屏幕上,每个列占据4个网格单位,总共12个单位,从而形成三列布局。
<div class="container-fluid">
<div class="row">
<div class="col-md-4">Column 1</div>
<div class="col-md-4">Column 2</div>
<div class="col-md-4">Column 3</div>
</div>
</div>
保存并刷新页面,现在三个<div>并排显示为三列。页面中的灰色背景和红色边框来自我们预定义的CSS样式,仅为视觉区分。
理解响应式断点 📱
在上一部分我们提到了md是一个尺寸标识符,代表一个特定的断点。让我们查阅Bootstrap官方文档来理解其含义。
根据文档,md适用于屏幕宽度大于等于992像素的情况。当屏幕宽度小于992像素时,应用了col-md-*的列将垂直堆叠。
我们可以在浏览器中打开开发者工具,调整窗口宽度进行测试。当宽度大于等于992像素时,三列并排。当宽度减小到991像素或以下时,三列立即开始垂直堆叠。
这演示了md类的作用:它定义了布局开始“折叠”或堆叠的临界点。
组合不同断点类 🔀
然而,我们不仅可以定义一个断点的行为。我们可以为更小的屏幕指定不同的布局。例如,我们可以添加col-sm-6类。
<div class="col-md-4 col-sm-6">Column 1</div>
<div class="col-md-4 col-sm-6">Column 2</div>
<div class="col-md-4 col-sm-6">Column 3</div>
这表示:
- 在
md及以上(≥992px):每列宽4个单位,三列并排。 - 在
sm范围(≥768px 且 <992px):每列宽6个单位(即半屏)。第一列和第二列并排,第三列因为一行只有12个单位而换行到下一行,并同样占据半屏。 - 在
xs范围(<768px):所有列垂直堆叠。
通过调整浏览器宽度,可以观察到布局在992px和768px这两个断点发生变化。
使用XS类实现固定布局 📐
Bootstrap的col-xs-*类与其他类有一个关键区别。查看文档表格,xs(超小屏幕)的网格行为是“始终水平排列”,而sm、md、lg是“在断点以下开始堆叠,在断点以上水平排列”。
这意味着,如果你使用col-xs-*,指定的布局将在所有屏幕尺寸下保持不变,永远不会堆叠。
让我们测试一下。我们创建新的一行,并只使用col-xs-6。
<div class="row">
<div class="col-xs-6">Column 1 (XS)</div>
<div class="col-xs-6">Column 2 (XS)</div>
<div class="col-xs-6">Column 3 (XS)</div>
</div>
保存并刷新。无论如何调整浏览器窗口大小,这三列始终保持“两个半宽列在一行,第三个半宽列换行”的布局,永远不会全部垂直堆叠。这证明了col-xs-*创建的是不受断点影响的固定比例布局。
总结 📝
本节课中我们一起学习了Bootstrap网格系统的核心概念和实操。
我们首先了解了构建网格的必要结构:网格必须位于container或container-fluid内,所有列必须放在row类元素中。
我们深入探讨了Bootstrap的网格列类(如col-md-4),重点在于其尺寸标识符(xs, sm, md, lg)定义了布局开始堆叠的响应式断点。通过组合这些类,我们可以为不同设备创建复杂的响应式布局。
最后,我们通过演示特别指出,使用col-xs-*类可以创建一种在所有屏幕尺寸下都保持固定水平比例的布局,而不会发生堆叠。


接下来,我们将转换话题,进入更多与业务/需求相关的分析模式,概述在接触真实客户时期望了解的一些基本要点。
042:总结与展望 🎉

在本节课中,我们将对已完成的模块二学习内容进行总结,并展望下一个模块的学习方向。
概述
你已经完成了大约一半的课程。如果你之前不了解或不太熟悉HTML和CSS,现在请花点时间回顾一下。与那些仅凭感觉摆弄HTML和CSS、却不了解基础概念的人相比,你现在已经拥有了更扎实的技能。花点时间反思一下,这种感觉很有力量,不是吗?
下一阶段学习
在下一个模块中,我们将开始为一个真实的客户编写一个真实的网站。请继续学习模块三,你一定不想错过这个部分。
总结

本节课中,我们一起回顾了模块二的学习成果,并明确了模块三将进入实战项目阶段,通过为真实客户开发网站来应用所学知识。
043:与客户会面(第一部分)

在本节课中,我们将学习如何为一个真实客户创建网站。我们将以一家名为“David Chohu's China Bistro”的本地餐厅为例,探讨在正式与客户会面前需要了解的基本规则和概念。本节内容旨在帮助你理解如何与客户有效沟通,明确项目需求,并为后续的网站开发工作奠定基础。
理解客户需求
上一节我们介绍了课程背景,本节中我们来看看与客户沟通的核心原则。首先需要记住一个关键概念:大多数客户并不清楚他们具体想要什么。这并非因为他们不了解自己的业务,事实上,他们通常非常精通自己的领域。问题在于,他们往往不清楚网站应该如何运作或呈现哪些内容。
因此,你的职责是提出正确的问题,引导客户理清他们的需求,并将其清晰地传达给你。
以下是引导客户的一个有效方法:
- 准备一些同行业已存在的网站示例。
- 向客户展示这些示例,并收集他们的反馈。
- 询问他们喜欢或不喜欢示例中的哪些部分。
- 通过足够的示例,你将逐渐了解客户的偏好和他们对网站的期望。
管理信息呈现
在引导客户明确需求后,我们常常会遇到下一个挑战:信息过载。通常,客户希望将大量关于其业务的信息都放在网站上。他们认为所有信息都很重要,并希望突出显示每一点。
这里存在一个根本问题:当所有内容都被强调时,实际上没有任何内容真正突出。
因此,你需要鼓励客户遵循一个简单的设计原则:少即是多。放置在网站上的信息越精炼,每条信息所能产生的影响力就越大。
你的目标是帮助客户识别出最关键、最具冲击力的核心信息,而不是试图塞进所有可能关于其业务的信息。
确保客户投入
另一个重要技巧是,你应该设法让客户对项目进行投入。这一点在你为了积累作品集而免费提供服务时尤其关键。客户需要感觉到他们也“下了赌注”。
如果客户没有投入,项目很可能变成他们不太关注的次要事务。你将投入大量时间,却可能得不到他们充分的配合。
即使免费提供服务,也应让客户承诺承担某些成本,例如:
- 支付产品摄影的费用。
- 提供用于演示的必要产品。
- 至少提供实物产品,以便你或合作的摄影师进行拍摄。
只要客户实际付出了某些东西(即使是小额资金),他们就会更认真地对待整个项目。
明确决策流程与修订限制
为了避免决策混乱,确保客户指定一人作为项目决策负责人。否则,不同的人可能会给出相互矛盾的意见。你可以告诉其他提建议的人,你会将建议转达给指定的决策者,并由其最终定夺。
这引出了下一点:预先限制修订次数。务必在项目开始前与客户沟通清楚,最好能签署合同,明确约定你愿意进行的免费修订次数。客户可能会无休止地要求修改,而你的时间是有限的。
具体操作可以是:
- 对于付费项目,不要直接拒绝修订,但可以限制免费修订的次数(例如3次)。
- 明确约定,在合理范围内的修订,免费进行3次;此后的修订将按小时收费。
- 具体的收费结构可以根据你的情况来制定。
准备会面与借助外力
在与客户会面前,务必先了解客户的现状。查看他们现有的网站(如果有的话),了解展示了哪些信息,从而对业务有个初步认识。以我们的示例餐厅为例,其现有网站只是一个展示了菜单图片的简单页面,用户体验很差,无法充分展示业务。
此外,如果能力所限,应尝试借助他人的力量。例如:
- 如果你不擅长平面设计,可以联系当地大学,寻找愿意提供免费设计服务以丰富简历的平面设计专业学生。
- 如果你需要产品摄影,可以寻找愿意为积累作品集而免费合作的年轻摄影师。
通过这种方式,你可以整合专业资源,不必独自承担所有工作,也能产出看起来相当专业的成果。在本案例中,讲师很幸运:他的妻子是平面设计师,儿子是初露头角的摄影师,他们分别负责设计和食品摄影。
即便如此,讲师依然遵循了“让客户投入”的原则:餐厅老板同意提供菜单上的每一道菜品,供团队拍摄。这需要餐厅员工加班烹饪,是一项不小的投入,但这表明了客户的参与度和承诺。
核心要点总结
本节课中我们一起学习了与客户初次会面前的关键准备步骤。让我们总结一下核心要点:
- 使用示例引导:带上其他网站示例,帮助不清楚自身需求的客户通过讨论明确方向。
- 倡导精简信息:鼓励客户避免信息堆砌,聚焦于更少但更有效的内容。
- 确保客户投入:想办法让客户在项目中有实际投入(资金、产品或服务),以提高其重视程度,尤其是在免费项目中。
- 指定唯一决策者:请客户指定一人作为最终决策者,以避免混乱和低效沟通。
- 限制修订次数:预先明确约定免费修订的次数,以保护你的时间。
- 善用外部资源:在需要时,联合设计师、摄影师等共同协作,以提升最终成果的专业度。


下一节,我们将前往实际的餐厅,会见店主,具体了解他们对网站的期望。这意味着,实地考察开始了!
044:第27讲 第2部分 与客户的实地考察

在本节课中,我们将跟随课程团队进行一次实地考察,拜访客户并学习如何有效地与客户沟通,以明确网站项目的需求和期望。

概述
实地考察是网站开发项目中的关键环节。本节内容展示了开发团队如何与餐厅老板艾米会面,了解她对网站的需求,并尝试从顾客那里获取对餐厅的独特视角。核心目标是明确双方的期望,确保项目顺利进行。
与客户会面:明确期望
上一节我们介绍了项目背景,本节中我们来看看如何与客户进行初次沟通。这次会面的核心是设定期望。无论是客户对我们的期望,还是我们对客户将提供资源的期望,都需要清晰沟通。如果能做到这一点,我们就完成了初步任务。

收集客户反馈
在明确了沟通目标后,我们开始收集具体信息。以下是会面中采取的几个关键步骤:
1. 了解顾客观点
团队尝试采访了一些餐厅顾客,以获取外部视角。一位常客表示,他喜欢这里的氛围、服务、符合犹太洁食标准,并且食物品质始终如一。另一位顾客称赞了鸡肉的做法。
2. 引导客户表达需求
我们直接询问餐厅老板艾米,她希望改进的地方。她明确回答:“需要一个更好的网站”。这是一个非常清晰的项目起点。
3. 使用视觉参考进行沟通
为了更具体地了解客户的喜好,我们准备了一些其他中餐厅的网站作为参考。我们请艾米浏览这些网站,并指出她喜欢或不喜欢的地方。
- 她喜欢的元素:颜色清晰、看起来干净整洁、非常实用、用户友好。
- 决策流程:我们确认艾米将是该项目的唯一决策者,并承诺在需要决策时能及时响应。
4. 明确项目流程与资源
我们向艾米解释了网站开发的基本流程,并确认了所需资源。
- 流程:我们将首先创建网站的线框图(即布局草图),而非直接编写代码。客户将有三次修订机会来调整设计,之后将定稿。
- 资源:我们确认艾米可以提供完整的菜单,并允许摄影师为每一道菜拍照。同时,我们了解到这是一家犹太洁食餐厅,这意味着在食材和处理方式上有特定要求。
5. 把握品牌调性
我们讨论了餐厅给人的感觉。艾米确认,餐厅虽小但给人一种舒适、放松的感觉,这也是她希望在网站上传达的氛围。
总结与过渡
本节课中,我们一起学习了如何通过实地考察与客户进行有效沟通。我们明确了客户的期望,收集了关于网站风格和功能的初步想法,并确认了项目流程与所需资源。
在明确了所有需求之后,我们就可以离开餐厅,开始设计和编写网站代码了。但在开始之前,有一条最重要的原则:永远不要在饿着肚子的时候写代码。


045:第28讲 设计概述

在本节课中,我们将学习网站开发流程中至关重要的设计规划阶段。我们将了解如何从零开始为一个餐厅网站创建设计稿,包括布局、响应式设计以及从设计到代码的过渡。
从构思到设计稿
上一节我们介绍了项目背景,本节中我们来看看如何将餐厅的初步想法转化为具体的设计方案。直接开始编码是网站设计中最糟糕的做法。正确的方法是先创建一些布局原型图,向客户展示并获得批准,明确开发目标后,再开始编码。
我们将使用一个名为Balsamiq Mockups的工具来快速创建网站原型。这个工具的优势在于它拥有针对不同平台的组件库,并且集成了Bootstrap的组件,这对我们后续的开发非常方便。此外,所有原型都基于一个类似HTML的文本文件,这意味着它可以轻松地纳入代码版本管理系统(如Git)中进行差异对比和团队协作,这在专业环境中是一个巨大的优势。
网站布局设计
以下是我们的餐厅网站主页在不同设备上的布局设计:
- 桌面版布局:页面顶部包含Logo、下方的文字菜单以及一个“我们提供外卖”的电话号码。主体部分是一个展示餐厅内部环境的大型图片区域(在Bootstrap中称为“Jumbotron”)。其下方是三个醒目的大按钮,分别对应“菜单”、“特价菜”和“地图”。页面底部计划展示一些顾客评价和餐厅的实际地址。
- 平板电脑布局:当浏览器变窄时,布局变化不大,主要是各个组件被适度压缩,但整体结构保持不变,依然包含Jumbotron和功能按钮。
- 手机布局:布局发生显著变化。顶部菜单将变为一个可点击下拉的汉堡菜单。电话号码被移到页面更显眼的位置,并设计为可点击直接拨打电话。Jumbotron区域会缩小以避免加载过慢。原来的三个大按钮将重新排列,“地图”会移动到“菜单”和“特价菜”的下方。底部依然保留评价和地址信息。

除了主页,我们还需要设计其他页面。
- 菜单分类页面:这个页面将展示所有的菜单分类(例如开胃菜、主菜、甜点),初步设计为每行展示六个分类,并向下滚动直至展示完全部分类。此页面不再使用Jumbotron。
- 具体菜单列表页面:当用户点击某个分类后,将进入此页面。它会列出该分类下的所有菜品,每项包含名称、描述以及一个唯一的ID。这个ID需要与餐厅实际菜单上的菜品编号保持一致,以确保线上线下数据对应。
视觉设计与实现考量
在布局确定后,设计师会提供更具体的视觉设计稿。这份稿子会定义网站的色彩方案、使用的图片以及字体。
设计师通常会提供网站各部分的十六进制颜色值(例如主色可能是 #FF5733)和指定的字体名称。如果预算有限或希望使用免费资源,建议要求设计师使用Google Fonts上的字体,这将使后续的网页实现更加容易。
在实际操作中,设计稿和最终素材可能出现偏差。例如,我们可能在设计完成后才进行食物摄影,然后发现照片的色调与设计稿中的主色不匹配。这时有两种选择:使用图像处理软件调整所有照片的色调以匹配设计,或者微调网站的主色以更好地配合实际照片和餐厅环境(例如,调整为与餐厅餐巾颜色相近的色调)。
关于设计工具,专业设计师通常使用Adobe Photoshop,但你也可以使用更简单的工具,如PowerPoint或任何绘图软件。此阶段的目标并非完成一个100%完美的最终设计,而是提供足够的细节,让开发者在编码时清楚地知道要达成什么样的视觉效果。

本节课中我们一起学习了网站开发前期的设计规划流程。我们了解了使用Balsamiq创建原型图的重要性,设计了主页及其响应式布局,规划了菜单分类和列表页面,并讨论了从视觉设计稿到代码实现过程中需要考虑的实际问题,如颜色调整和字体选择。现在,我们拥有了清晰的设计蓝图,可以开始动手编写网站代码了。
046:基本规则与开发环境配置概述 🎬

在本节课中,我们将学习课程的基本规则,并快速配置我们的开发环境。我们将使用一个真实的商业网站作为示例,从零开始逐步构建。
基本规则 📋
在开始之前,了解一些基本规则很重要。
首先,请记住这是一个视频课程。你可以根据自己的节奏来学习。如果你觉得我讲得太快,可以暂停或回放。如果觉得太慢,可以加快播放速度。
其次,所有源代码都是可用的。如果你错过了某些内容,或者想稍后查阅,可以随时查看源代码。即使我们构建的是一个真实的商业网站,源代码也100%会提供给你。
我们将逐步构建网站。每个讲座的文件夹都会包含“之前”和“之后”的内容。这样,你可以随时跳入任何一讲,查看我们的起点和终点。
为了节省时间,有些操作会在屏幕外完成。有时我也需要暂停思考下一步。我不想让你坐在那里等我思考,所以有些内容会暂停录制,在屏幕外完成后再放回屏幕。一旦内容出现在屏幕上,我会进行解释。
为了避免让你观看枯燥的打字过程,我会尽量在屏幕外完成一些操作,然后进行解释。当然,一些屏幕上的打字操作可能无法避免。
我通常使用双显示器进行开发,这样在一个屏幕上写代码,另一个屏幕上显示浏览器,使用像Browser Sync这样的工具会非常方便。但遗憾的是,由于这是单屏幕视频,并且为了让你们能看清代码,浏览器和代码编辑器不能同时显示。因此,我们不得不在代码编辑器和浏览器屏幕之间频繁切换。我为此表示歉意,但为了确保你们能看清内容,这是必要的。
开发环境配置 ⚙️
现在,让我们进入代码编辑器,看看我们的起点。
我目前在终端窗口中。如果我执行 pwd 命令,它会显示我当前所在的目录。你会看到我在 examples/lecture29/before 文件夹中。执行 ls 命令,你会看到我有 css、fonts、js 文件夹和一个起始的 index.html 文件。
我们首先要做的是开始使用Browser Sync。如果你向上移动一个文件夹并执行 ls,你会看到 before 和 after 文件夹。我们将主要在 after 文件夹中进行开发。让我们回到 after 文件夹,因为这是我们想要启动Browser Sync的地方。这样,每当我们编辑任何内容,它都会自动在浏览器中更新。
让我回忆一下Browser Sync的命令,并解释一下。希望你在欢迎视频中已经学习了如何安装Browser Sync。如果你还没有安装,请返回那个视频查看安装步骤,例如需要安装Node.js等。
以下是启动Browser Sync的命令:
browser-sync start --server --directory --files "*"
browser-sync start:启动Browser Sync。--server:让Browser Sync作为服务器,从我们当前所在的目录(现在是lecture29/after目录)提供网站服务。--directory:告诉Browser Sync列出目录结构,而不是尝试提供一个特定的文件。它会向我们展示所有文件作为起点。--files "*":指定需要监视哪些文件的修改。"*"表示监视此目录下的任何文件。当任何匹配此条件的文件发生变化时,Browser Sync会自动并立即重新加载页面。
我按下回车键启动浏览器。它直接跳转到了浏览器页面。让我们回来看一下终端输出,它告诉我访问的URL,并直接跳转到了这个页面。但请注意,这里还有另一个页面 http://localhost:3001,这是Browser Sync的用户界面(UI),我之前没有展示过。
让我们进入那个UI,你会看到一个完整的Browser Sync配置界面。我鼓励你探索一下。同时,这是我们的目录列表。如果我们点击 index.html,目前什么也看不到,因为我们的代码中还没有任何内容。

初始代码结构 🏗️
现在让我们看看我们的代码。我们这里有一个标准的Bootstrap起始页面。请注意,我们放弃了对Internet Explorer 9的支持,所以你看不到相关的垫片(shims),我们也没有使用jQuery 1.x,而是使用了jQuery 2.1.4。我们希望不再支持IE9,因为现在使用它的人已经非常少了。
我们这里只有标准的meta标签设置。我们把标题设为“David Chu‘s China Bistro”,这是餐厅的名字。
我们首先要做的是设置样式。在 css 文件夹里有很多文件,但我们真正需要的只是压缩版的 bootstrap.min.css,事实上我们已经引入了它。为了以后参考,我把所有文件都留在这里了。重要的是,你会看到 styles.css 文件就在这个文件夹里。
让我们打开 styles.css 文件。我们要做的第一件事是设置一些全局样式,这些样式将应用于整个页面乃至所有页面。我们将使用 body 选择器来设置。
首先,设置字体大小。我们将使用一个标准字体大小,比如16像素。
font-size: 16px;
接下来,设置颜色。我们将颜色设为白色(#FFF)。这意味着我们所有的文本都将是白色的。
color: #FFF;



然后,设置背景颜色。我们需要参考我们的设计稿。设计稿中的背景颜色是一种特定的红色。但我们将使用 #611122,这个颜色与餐厅里餐巾和其他物品的风格相匹配。

background-color: #611122;
现在,如果我们查看浏览器(它应该自动刷新了),我们可以看到背景变成了这种栗色。

最后,我们来看看通用字体。设计稿上显示字体是“Oxygen”。让我们设置通用字体。
font-family: oxygen;
但这可能不会生效。让我们在HTML中添加一个简单的段落 <p>Hello World</p> 来测试一下。它可能不会显示为Oxygen字体,因为我的机器上可能没有安装这个字体。实际上,它看起来很像Oxygen,所以可能我的机器上已经导入了。但在普通机器上,你可能没有这个字体。
那么,我们如何获取这个字体呢?我们可以访问 fonts.google.com 搜索“oxygen”。你会看到第一个结果是“Oxygen”,第二个是“Oxygen Mono”(那是不同的字体)。要获取这个字体,点击“Select this font”按钮,然后选择我们想要的字重(比如常规和加粗)。你可以看到Google会告诉你这会给页面加载带来多少负担。有时字体有很多选项,加载过多会使页面变慢。我们不想这样,但好在这个字体选项在绿色区域,表示负担较小。
向下滚动一点,你会看到一段代码,需要添加到你的网站。这段代码很简单,是一个链接。我们复制它,回到我们的HTML页面,将这个链接粘贴到 <head> 部分。
现在我们的页面就有了这个字体。回到Google Fonts页面,它还会告诉你应该如何指定这个字体。我们复制那行CSS规则,回到代码编辑器,用他们告诉我们的方式替换我们之前写的 font-family 规则,即 font-family: 'Oxygen', sans-serif;。这样,如果Oxygen字体不可用(现在应该可用了,因为我们从Google导入了),就会使用无衬线字体作为后备。
现在我们可以回到网页,刷新一下看看。字体基本上应该生效了。让我们回到代码编辑器,删除测试用的 <p>Hello World</p> 段落。现在我们准备好了背景颜色、默认字体大小,并且从Google Fonts导入了字体。我们可以继续下一步,即构建网站的页头部分。

总结 📝





本节课中,我们一起学习了课程的基本规则,并配置了开发环境。我们了解了如何利用视频的灵活性,知道了源代码的可用性,以及项目逐步构建的方式。我们使用Browser Sync搭建了本地开发服务器,实现了代码修改后浏览器自动刷新。我们还为项目设置了全局CSS样式,包括背景色、字体大小,并通过Google Fonts引入了“Oxygen”字体。现在,我们的基础开发环境已经就绪,可以开始构建网站的具体部分了。
047:第1部分 导航栏头部编码基础 🏗️

在本节课中,我们将开始构建餐厅网站的头部导航栏。我们将使用HTML5语义标签和Bootstrap框架来创建结构,并通过自定义CSS进行样式调整。
概述
我们将从网站的头部元素开始构建。头部包含导航栏、Logo和品牌名称。我们将使用Bootstrap的navbar组件作为基础,并应用自定义样式以达到设计稿中的效果。
开始编码

首先,我们需要确保在正确的项目目录中,并启动Browser Sync以便实时查看代码更改的效果。
启动开发环境
在终端中,我们使用以下命令启动Browser Sync,并让其监视项目中的所有文件夹和文件:
browser-sync start --server --directory --files "**/*"
这个命令会启动一个本地服务器,并自动刷新浏览器以显示最新的更改。
构建导航栏结构
现在,让我们回到代码编辑器,开始编写HTML结构。我们将使用<header>和<nav>等语义化标签。

<header>
<nav id="header-nav" class="navbar navbar-default">
<div class="container">
<!-- 导航栏内容将放在这里 -->
</div>
</nav>
</header>
在上面的代码中:
<nav>元素被赋予了IDheader-nav和Bootstrap类navbar与navbar-default。- 内部使用了一个
container类来约束内容的宽度,使其不会拉伸到浏览器边缘。
应用自定义样式
接下来,我们需要为导航栏应用设计稿中的黄色背景,并移除默认的圆角边框。这通过自定义CSS来实现。

我们打开styles.css文件,为#header-nav添加样式:
#header-nav {
background-color: #f8b400; /* 黄色背景 */
border-radius: 0; /* 移除圆角 */
border: 0; /* 移除边框 */
}
保存后,Browser Sync会自动更新页面,导航栏现在应该显示为纯黄色背景。

添加Logo

Logo将作为可点击的链接放置在导航栏的左上角。我们使用一个<a>标签包裹一个<div>,并将Logo设置为这个<div>的背景图片。
以下是HTML代码:
<div class="navbar-header">
<a href="index.html">
<div id="logo-img" alt="Logo image"></div>
</a>
</div>

然后,在CSS中定义#logo-img的样式,指定背景图片和尺寸:
#logo-img {
background: url('../images/restaurant-logo_large.png') no-repeat;
width: 150px;
height: 150px;
margin: 10px 15px 10px 0; /* 添加一些外边距 */
}
保存后,Logo图片就会显示在导航栏中。
总结
在本节中,我们一起学习了如何构建网站导航栏的基础框架。我们完成了以下步骤:
- 设置了开发环境,使用Browser Sync实现实时预览。
- 使用Bootstrap的
navbar和container类创建了导航栏的基本HTML结构。 - 通过自定义CSS覆盖了Bootstrap的默认样式,设置了正确的背景色和边框。
- 在导航栏内添加了Logo图片。



在下一部分,我们将继续完善导航栏,添加餐厅名称并对其进行排版和样式设计。
048:导航栏头部编码基础

在本节课中,我们将学习如何使用HTML、CSS和Bootstrap框架来构建一个响应式导航栏的头部区域。我们将重点处理餐厅名称和认证标志的布局与样式。
首先,我们回到代码编辑器,粘贴预先准备好的餐厅名称和犹太洁食认证标志的代码。

如果直接放置代码,其效果并不理想。让我们先解释一下这段代码的结构。
第一项是 Navbar-brand。根据Bootstrap文档,任何品牌信息(如公司名称)都应放在具有 navbar-brand 类的元素内。我们将餐厅名称“David Chu's China Bistro”放在一个 <a> 链接内,使其成为可点击区域,并包裹在 <h1> 标签中,因为这是页面上最重要的信息。
接下来是犹太洁食认证标志,它是一个小尺寸的图片,我们暂时不为其考虑响应式设计。图片内有一个 <span>,文字内容为“KOSHER CERTIFIED”。
保存代码并在浏览器中查看,效果并不理想。名称和标志垂直堆叠,看起来不美观。我们需要将它们并排放在头部区域。
问题在于这两个元素都是 <div>,默认会垂直堆叠。由于 navbar-brand 类已经设置了左浮动,我们只需让标志图片也左浮动,就能使餐厅名称出现在标志右侧。
我们使用Bootstrap的方式来实现,为标志所在的 <a> 元素添加 pull-left 类,其效果等同于 float: left。这比直接使用 style 属性添加内联样式更整洁。
<a href="#" class="pull-left">
<img src="img/logo.png" alt="Kosher certification">
</a>
返回浏览器查看,现在所有内容都排列在标志右侧了。

上一节我们完成了基本布局,本节中我们来看看如何优化样式。


我们首先需要移除链接默认的下划线,并根据设计稿调整字体。设计稿中,商业名称使用的字体是“Lora”。
我们需要返回代码编辑器,通过Google Fonts导入Lora字体,方法与之前导入Oxygen字体相同。
<link href='https://fonts.googleapis.com/css?family=Lora' rel='stylesheet' type='text/css'>
接着,在CSS样式文件中,为导航栏品牌区域添加预定义的样式。
首先,为 .navbar-brand 添加顶部内边距,将其向下推一点。
.navbar-brand {
padding-top: 25px;
}
然后,为餐厅名称(即 .navbar-brand h1)设置样式:应用Lora字体家族、改为绿色、增大字体尺寸、转换为大写、加粗、添加文字阴影、调整外边距和行高,并移除链接装饰。
.navbar-brand h1 {
font-family: 'Lora', serif;
color: #557c3e;
font-size: 1.5em;
text-transform: uppercase;
font-weight: bold;
text-shadow: 1px 1px 1px #222;
margin-top: 0;
margin-bottom: 0;
line-height: .75;
}
.navbar-brand a:hover, .navbar-brand a:focus {
text-decoration: none;
}
接下来,为认证标志区域(.kosher)设置样式:调整颜色、转换为大写、减小字体尺寸,并添加顶部外边距以与品牌名称分开。
.kosher {
color: #000;
text-transform: uppercase;
font-size: .7em;
margin-top: 15px;
}
最后,为了让“KOSHER CERTIFIED”文字在星形图标中垂直居中,我们为对应的 <span> 设置垂直对齐属性。
.kosher span {
vertical-align: middle;
}
在浏览器中查看效果,样式已基本符合预期,但布局可能仍有问题。检查CSS代码,修正可能存在的拼写错误(如残留的注释结束符)。修正后,头部区域应能正确显示。
最后,我们希望这个认证标志只在中等(md)和大(lg)屏幕尺寸下显示,在小屏幕上则隐藏,以节省空间。
Bootstrap提供了响应式工具类来实现这一点。我们可以为标志元素添加 visible-md 和 visible-lg 类。
<a href="#" class="pull-left visible-md visible-lg">
<img src="img/logo.png" alt="Kosher certification">
</a>
现在,当浏览器窗口宽度处于中等或大尺寸时,标志会显示;当窗口缩小到小尺寸时,标志会自动隐藏。你可以通过拖动浏览器窗口来观察这一变化。




本节课中我们一起学习了导航栏头部的编码基础。我们使用Bootstrap的类进行布局,通过自定义CSS设置字体、颜色和间距等样式,并利用Bootstrap的响应式工具类控制元素在不同屏幕尺寸下的可见性。这为网站头部区域打下了一个良好的基础。
049:6_第31讲 可折叠菜单按钮编码 🧩

概述
在本节课中,我们将继续构建网站,重点是为移动端视图添加一个可折叠的导航菜单按钮。我们将学习如何使用Bootstrap的组件来实现这一功能,并理解其背后的代码结构。



回顾与目标
上一节我们处理了网站的Logo、餐厅名称和认证标志,并实现了Logo在浏览器窗口缩小时(小于中等尺寸)的隐藏效果。
本节中,我们来看看如何为移动端视图添加一个可折叠的菜单按钮。根据设计文档,在移动设备上,导航菜单项(如“菜单”、“关于”、“奖项”等)将被隐藏,取而代之的是一个按钮。点击此按钮可以展开导航菜单。我们选择现在处理这个按钮,是因为它属于导航栏头部的一部分,而我们已经开始构建这部分内容。

查看Bootstrap文档
在开始编码之前,我们先参考Bootstrap官方文档。文档中有一个导航栏组件的示例,当浏览器窗口缩小时,菜单项会折叠起来,点击一个按钮后才会显示。我们将借鉴这个示例的代码来实现我们的功能。
添加可折叠按钮代码
我们已经启动了浏览器同步工具。现在,需要将准备好的代码粘贴到导航栏头部(navbar-header)内部。这段代码与Bootstrap文档中展示的代码相同。
以下是粘贴的代码:
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#collapsible-nav" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
代码解析
让我们逐一分析这段代码中各个部分的作用:
class="navbar-toggle":这个类控制整个按钮的定位和基本样式。在Bootstrap的CSS中,它定义了按钮的位置(如相对定位、右浮动),并且会在特定的媒体查询宽度下(例如小于中等尺寸时)显示,在其他情况下隐藏。class="collapsed":这个类本身在Bootstrap的CSS中没有定义。它的作用是向Bootstrap的一个插件(Collapse插件)表明按钮当前处于“折叠”状态(即未被点击)。当用户点击按钮后,这个类会被插件自动移除或添加,以切换状态。data-toggle="collapse":这是一个HTML5数据属性(data-*),它告诉Bootstrap的JavaScript插件,这个按钮用于触发“折叠”行为。data-target="#collapsible-nav":这是另一个关键的数据属性。它的值#collapsible-nav是一个我自定义的名称,对应着页面中另一个元素的ID。那个元素(我们稍后会创建)将包含所有需要折叠/展开的导航菜单项。这个属性建立了按钮和目标内容之间的连接。aria-expanded="false":这个属性主要用于辅助技术(如屏幕阅读器),表示关联的内容区域当前是折叠的。虽然本课程不深入讲解可访问性属性,但保留它是一个好习惯。<span class="sr-only">Toggle navigation</span>:这个元素内的文字“Toggle navigation”仅对屏幕阅读器可见,不会显示在浏览器页面上,用于向使用辅助技术的用户说明按钮功能。- 三个
<span class="icon-bar">:这三个元素负责在按钮内渲染出三条水平横线,即常见的“汉堡包”菜单图标。
查看效果与后续步骤
保存代码后,回到浏览器。目前你可能看不到任何变化,因为页面窗口还不够小。当我们缩小浏览器窗口到大约768像素以下时,按钮就会出现。
现在点击按钮,不会有任何反应。这是因为我们还没有在HTML中启用折叠行为,也没有将JavaScript功能连接起来。关键的一步是我们尚未创建ID为 collapsible-nav 的元素来容纳导航菜单。
此外,你可能注意到这个按钮的样式与网站整体风格不太协调,它目前使用的是Bootstrap的默认样式。我们不会保持原样,将在后续的视频中对其进行样式调整。


总结
本节课中我们一起学习了如何为响应式网站添加Bootstrap可折叠导航菜单按钮。我们理解了按钮代码的结构,包括控制样式的类(navbar-toggle)、与JavaScript插件交互的数据属性(data-toggle, data-target),以及用于可访问性和图标显示的元素。目前按钮已经能在移动端视图中显示,但功能尚未激活,样式也未定制,这将是后续课程的任务。
050:第32讲 第1部分 导航菜单按钮编码

概述
在本节课中,我们将继续构建网站,重点是为导航栏添加可交互的菜单按钮。我们将使用HTML来构建菜单结构,并用CSS进行样式设计,最终实现一个在移动设备上可折叠的响应式导航菜单。
构建导航菜单的HTML结构
上一节我们完成了导航栏标题部分。本节中,我们来看看如何为导航栏添加菜单按钮。
首先,我们需要在导航栏标题的下方添加菜单容器。这个容器将在页面变窄、主菜单折叠时,通过一个切换按钮来显示。
以下是构建菜单所需的HTML代码:
<div id="collapsable-nav" class="collapse navbar-collapse">
<ul id="nav-list" class="nav navbar-nav navbar-right">
<li>
<a href="#">
<span class="glyphicon glyphicon-cutlery"></span>
<br class="hidden-xs">
菜单
</a>
</li>
<li>
<a href="#">
<span class="glyphicon glyphicon-info-sign"></span>
<br class="hidden-xs">
关于
</a>
</li>
<li>
<a href="#">
<span class="glyphicon glyphicon-certificate"></span>
<br class="hidden-xs">
奖项
</a>
</li>
<li id="phone" class="hidden-xs">
<a href="tel:123-456-7890">
<span>123-456-7890</span>
</a>
<div>我们提供外送服务</div>
</li>
</ul>
</div>
现在,我们来详细解析这段代码的各个部分。
代码解析
1. 可折叠菜单容器
<div id="collapsable-nav" class="collapse navbar-collapse"> 是整个菜单的容器。
id="collapsable-nav":为这个<div>设置了一个ID。Bootstrap的折叠插件会通过这个ID(例如#collapsable-nav)来定位和控制这个区域的展开与收起行为。class="collapse navbar-collapse":这两个类是Bootstrap框架要求的,它们共同作用,使这个容器具备响应式折叠的功能。
2. 菜单列表
<ul id="nav-list" class="nav navbar-nav navbar-right"> 是一个无序列表,它将包含所有菜单项。
id="nav-list":我们为这个列表设置ID,以便后续通过CSS进行样式定制。class="nav navbar-nav":这两个类告诉Bootstrap,这个列表是导航栏的一个组件,并将其样式化为导航菜单。class="navbar-right":这个类使整个菜单列表在导航栏内向右浮动对齐。在我们的设计中,菜单按钮是位于导航栏右侧的。
3. 菜单项与图标字体
每个菜单项(<li>)内部都包含一个链接(<a>)和一个图标。
- 链接:
<a href="#">定义了按钮的点击目标。#是占位符,在实际项目中应替换为具体的页面链接。 - 图标字体:
<span class="glyphicon glyphicon-cutlery"></span>用于显示图标。glyphicon是基础类。glyphicon-cutlery是具体图标类,代表刀叉图标。- 图标字体是一种矢量图形,可以像文字一样通过CSS调整大小和颜色,并且放大后依然清晰。
- 换行控制:
<br class="hidden-xs">在图标和文字之间插入了一个换行,但在超小屏幕(xs)上,这个换行会被隐藏(hidden-xs)。这样,在移动端展开的菜单中,图标和文字会水平排列,而不是上下堆叠,使按钮更紧凑。
4. 电话号码项
最后一个菜单项比较特殊,它包含了电话号码和外送信息。
id="phone" class="hidden-xs":我们给这个<li>设置了hidden-xs类,这意味着在超小屏幕(如手机)上,整个电话号码区块会被隐藏。这是因为我们计划在移动端以另一种更醒目的方式展示电话号码。- 电话链接:
<a href="tel:123-456-7890">使用tel:协议。当用户点击这个链接时,在支持通话的设备(如手机)上会直接触发拨号功能。 - 外送信息:
<div>我们提供外送服务</div>是一个简单的文本提示。
为导航菜单添加CSS样式

现在HTML结构已经就位,但看起来还很简陋。接下来,我们通过CSS来美化它。

我们将对之前定义的 #nav-list 及其子元素进行样式设计。
以下是需要添加的CSS代码:
#nav-list {
margin-top: 10px;
}
#nav-list a {
color: #951C49;
text-align: center;
}
#nav-list a:hover {
background-color: #f0f0f0;
}
#nav-list a span {
font-size: 1.8em;
}
#phone {
margin-top: 5px;
}
#phone a {
text-align: right;
padding-bottom: 0;
}
#phone div {
color: #557c3e;
text-align: right;
padding-right: 15px;
}
样式解析
以下是每个样式规则的作用:

#nav-list { margin-top: 10px; }:为整个菜单列表添加上边距,使其在导航栏中位置下移一些。#nav-list a { color: #951C49; text-align: center; }:设置所有菜单链接的文字颜色为网站主题的酒红色,并将文字居中对齐。#nav-list a:hover { background-color: #f0f0f0; }:当鼠标悬停在菜单项上时,添加一个浅灰色的背景色,提供视觉反馈,提示用户这是可点击的。#nav-list a span { font-size: 1.8em; }:将图标字体的大小增加到原来的1.8倍,使其更醒目。#phone { margin-top: 5px; }:为电话号码项添加上边距,使其与上面的菜单项有一些间隔。#phone a { text-align: right; padding-bottom: 0; }:将电话号码的文字右对齐,并移除底部内边距,使其紧贴底部。#phone div { color: #557c3e; text-align: right; padding-right: 15px; }:将“我们提供外送服务”的文字设置为绿色,右对齐,并添加右内边距,使其与电话号码的右边缘完美对齐。
应用这些样式后,我们的导航菜单在桌面端看起来就美观多了,并且拥有了悬停效果。更重要的是,由于我们正确使用了Bootstrap的类(如 collapse, navbar-collapse),当浏览器窗口缩小到移动端尺寸时,菜单会自动折叠起来。点击右上角的切换按钮,菜单可以平滑地展开和收起,这一切功能都是由Bootstrap框架自动提供的。


总结
本节课中我们一起学习了如何构建一个响应式导航菜单。我们首先使用HTML创建了菜单的结构,利用Bootstrap的类来实现折叠功能,并添加了图标字体来美化按钮。然后,我们通过CSS对菜单进行了详细的样式设计,包括颜色、对齐、间距和悬停效果。现在,我们的导航栏在桌面端显示完整菜单,在移动端则能优雅地折叠,为后续的移动端优化打下了基础。在下一部分,我们将重点设计那个控制菜单折叠的切换按钮。
051:8_第32讲 第2部分 导航菜单按钮编码 🧭

在本节课中,我们将继续为移动端导航菜单编写样式,重点是设计并美化那个用于展开和收起折叠菜单的按钮。我们将调整按钮的颜色、解决布局问题,并确保它在不同屏幕尺寸下都能正常工作。
在上一节中,我们为主屏幕和折叠状态的菜单项编写了代码并应用了样式。本节中,我们来看看如何设计那个控制移动端菜单展开与收起的按钮。
让我们回到代码编辑器,为这个按钮添加更多样式,使其与网站的整体主题相匹配。
首先,我们要改变按钮边框的颜色。以下是实现此目标的CSS代码:
.navbar-header button.navbar-toggle,
.navbar-header .icon-bar {
border: 1px solid #61122f !important;
}
这段代码的目标是:在 .navbar-header 元素内,找到类名为 navbar-toggle 的按钮以及类名为 icon-bar 的元素(即按钮中的三条横线),并将它们的边框颜色设置为更接近栗色的 #61122f,这与网站其他部分的背景色一致。
现在,如果我们查看网站,可以看到按钮边框的颜色已经变成了我们想要的栗色(或餐巾纸色)。按钮背景目前显示为偏白的颜色,是因为它当前处于被选中状态。我们尚未编写代码使其在点击后自动取消选中状态。如果你点击页面其他区域,按钮会恢复为黄色。这个问题我们稍后会修复。

目前,按钮紧挨着餐厅名称,并且黄色的标题区域(.navbar-header)溢出了。这是因为,正如之前关于浮动的课程所讲,当一个元素浮动后,其父容器的高度可能会塌陷。
我们可以通过简单地应用 clear 属性来解决这个问题。让我们现在就在CSS文件中添加以下代码:
.navbar-header {
clear: both;
}
保存文件并刷新页面后,可以看到父容器不再塌陷,现在它包含了内部所有的浮动元素。
然而,这里仍然存在一个问题。即使在小型移动浏览器上,我们可能也不希望餐厅名称保持如此巨大的字体。这并不实用。我们需要将这两个元素(餐厅名称和按钮)更紧密地排列在一起,使用更小的字体,并缩小整个黄色区域的高度。
我们可以通过为按钮应用一个负的 margin-top 值来实现。让我们尝试一下:
.navbar-header button.navbar-toggle {
margin-top: -30px;
}
回到网站查看效果,可以看到黄色区域的高度确实变小了。但不幸的是,这在一定程度上抵消了我们之前通过 clear: both 解决的浮动塌陷问题。
真正彻底的解决方案是缩小这些元素(餐厅名称和按钮)本身的尺寸,让它们适应这个区域,而不是去改变区域的大小。我们将在后续的课程中处理这个问题。
目前,我们的切换按钮已经可以正常工作。我们可以缩小浏览器窗口来查看折叠的菜单,点击按钮后菜单也能很好地展开。

按钮样式调整后

移动端菜单展开效果


本节课中,我们一起学习了如何为移动端导航菜单的切换按钮添加样式。我们改变了按钮边框的颜色以匹配主题,通过 clear: both 解决了浮动导致的布局塌陷问题,并尝试使用负边距来调整按钮位置。虽然布局的最终优化留待后续课程,但我们已经实现了一个功能完整、样式协调的菜单切换按钮。
052:9_第33讲 第1部分 修正导航栏布局文本与下拉菜单

概述
在本节课中,我们将学习如何修复导航栏在不同屏幕尺寸下的布局问题。我们将通过编写媒体查询来调整Logo大小、优化文本样式,并确保导航栏在各种设备上都能良好显示。
在上一讲中,我们发现在屏幕从宽屏切换到中等尺寸时,布局出现了一点问题。
让我展示一下具体的情况。当我将屏幕宽度调整到1200像素的断点以下时,这个元素会突然跳到这里,这看起来不太美观。我们更希望它能保持在稍高一点的位置。
这个问题的原因是,这里的这个div和另一个div都设置了浮动。由于这一行的空间不足,就像其他浮动元素一样,它会被推到下一行,并且仍然保持右浮动。
这就是为什么它仍然在这里,而没有移动到那边。让我们验证一下。
让我们选中这里的元素,这是我们的header-navbar-header。如果我们减小它的宽度,比如设为400像素,你会看到它跳到了这里。因为我们把它变小了,所以它跳过来了。让我们刷新一下,我们实际上并不想减小整个div的宽度。
我们真正想做的是让这个Logo变小一点。在屏幕变小的情况下,Logo本身也是需要缩小的候选对象,这样既能适应小屏幕,也能节省一些带宽。
让我们回到代码编辑器。我位于第33讲的after文件夹中,我将打开styles.css文件。显然,我们现在需要处理断点,这意味着我们需要编写自己的媒体查询。我已经在这里写好了我们将需要的一些媒体查询。
我们将为大型设备(最小宽度1200像素)、中型设备(宽度在992到1199像素之间)、小型设备(宽度在768到991像素之间)以及超小型设备(最大宽度767像素)分别设置媒体查询。这些数值并非我随意编造,而是直接取自Bootstrap框架定义其断点的方式。
目前,我们针对的是中型设备。我已经准备好了一些代码,可以直接复制粘贴到这里,让我们的Logo变小。代码如下:
@media (min-width: 992px) and (max-width: 1199px) {
.navbar-header .navbar-brand .logo {
background: url(../images/restaurant-logo_medium.png) no-repeat;
padding: 0;
width: 100px; /* 示例值,需调整 */
height: 100px; /* 示例值,需调整 */
}
}
我们针对.logo图像,将其背景图片URL更换为一个不同的文件:restaurant-logo_medium.png。这个文件尺寸更小。之前的尺寸可能是150x150像素,现在会小一些,内边距也可能相应调整。这需要你自己进行微调,直到看起来合适为止。
让我们保存代码,然后查看浏览器。现在情况好多了。我们可以看到Logo缩小了,从而为按钮腾出了足够的空间。
现在,如果我们将屏幕挤压到小型设备的断点,Logo会完全消失。此时,导航内容将无法完全容纳在这一行,它会滑到餐厅品牌名称的下方。实际上,这看起来相当不错,很紧凑,所以我暂时不打算修复它。
在大型和中型断点下,我们的布局现在都正常了。你可以看到图片会根据断点切换大小。
看起来,我们唯一需要开始修复的是当屏幕变得非常小(超小型设备)时的情况。
餐厅名称“David Chu‘s China Bistro”看起来还不错,但可能太大了,而且文字溢出的情况肯定也不好。
让我们回到代码编辑器,尝试修复这个问题。我们将滚动到针对超小型设备的媒体查询部分(max-width: 767px)。
让我粘贴一些预先准备好的代码:
@media (max-width: 767px) {
.navbar-brand {
padding: 10px 15px;
height: 80px;
}
.navbar-brand .restaurant-name {
padding-top: 10px;
font-size: 5vw;
}
.navbar-brand .kosher-cert {
font-size: 0.8em;
margin-top: 5px;
}
.navbar-brand .star-image {
height: 20px; /* 示例值,用于垂直对齐 */
}
}
现在让我解释一下这段代码。
首先,对于.navbar-brand,我们想做两件事:一是将内边距减小一点(之前可能是15像素,现在设为10像素);二是为其设置一个具体的高度(80像素),这样我们就不会依赖周围元素的自动收缩来定义它的高度。
其次,餐厅名称的内边距也会减小一些(padding-top设为10像素)。字体大小单位我们使用了5vw。vw代表视口宽度。1vw等于视口宽度的1%。我们使用这个单位是因为它是响应式的,也就是说,随着屏幕被拉伸或挤压,字体大小会相对于屏幕宽度而变化,这正是我们想要的。5vw这个值看起来比较合适。这是一种几年前引入的响应式字体单位,并且得到了几乎所有浏览器的广泛支持。
接下来,我们将减小“Kosher Certified”(犹太洁食认证)文字的字体大小,并保持其大写。我们可能之前已经设置过大写,可以快速检查一下。如果.restaurant-name p已经设置了text-transform: uppercase,那么这里就是重复的,可以删除。我们还会给它一个较小的上边距。
最后,为了约束那个星形图标,我们将给它设置一个具体的高度,这样“Kosher Certified”文字就能和星形图标很好地垂直居中对齐。
让我们保存代码,然后回到浏览器。现在可以看到,餐厅名称比之前更大了,因为它填充了更多空间(Logo已隐藏)。同时,“Kosher Certified”也很好地放置在黄色区域里。
如果我们把屏幕拉宽,餐厅名称应该会切换回之前的大小,并且在头部显示更多内容。当屏幕宽度超过1200像素时,餐厅的Logo会再次显示出来,并且变得更大。
回到小尺寸屏幕,我们可以看到餐厅名称会根据屏幕的宽度进行拉伸。
总结
本节课的第一部分到此结束。我们一起学习了如何通过媒体查询响应式地调整导航栏的布局。我们修复了Logo在中型屏幕下的显示问题,并优化了超小型屏幕下的品牌文本样式,使用了vw单位来实现字体大小的自适应。

在第二部分,我们将把注意力转向如何为下拉菜单添加样式。
053:10_第33讲 第2部分 修正导航栏布局文本与下拉菜单

📖 概述
在本节课中,我们将学习如何为移动设备上的下拉菜单进行样式调整。我们将重点解决菜单图标与文本大小不一致的问题,并优化它们之间的间距,使移动端导航栏的视觉效果更加协调。
🖥️ 问题分析
上一节我们介绍了在桌面全屏模式下菜单的样式设计。本节中,我们来看看当浏览器切换到移动端下拉菜单模式时,样式上出现的问题。
观察我们这里的折叠菜单,可以发现存在一个问题:Glyph图标看起来太大了,尤其是与旁边的“菜单”文本相比。我们希望图标和文本的字体大小能够保持一致。
首先,我们来检查一下为什么这两者的字体大小确实不同。
- 右键点击“菜单”文本,选择“检查”。
- 点击这个
a元素(链接元素)。 - 尝试滚动查找
font-size属性的首次定义。它应该来自我们的styles.css文件,值为16px。
接下来,我们检查Glyph图标。Glyph图标实际上是一个 span 元素,位于 a 元素内部。点击图标进行检查,会发现它的字体大小由选择器 # 后跟 a 和 span 定义,同样来自 styles.css。
我们希望覆盖这个样式,让菜单文本稍微变大一点,因为它目前看起来有点小。
✏️ 调整菜单文本大小
以下是调整菜单文本大小的步骤:
- 打开代码编辑器,找到可以应用的样式位置。
- 添加以下CSS规则,将菜单文本的字体大小增加约20%。
/* 增加菜单链接的字体大小 */
#navbarSupportedContent ul li a {
font-size: 1.2em;
}
保存更改后,返回浏览器查看效果。现在“菜单”文本变大了,但Glyph图标也随之变大了。这是因为图标位于 a 元素内部,继承了其新的字体大小。
🔄 统一图标与文本大小
我们希望Glyph图标的大小与菜单文本完全相同。为此,我们需要让 span 元素继承其父元素(即 a 元素)的字体大小。
由于CSS选择器存在特异性,我们可以通过一个更具体的选择器来覆盖原有的图标样式。
以下是具体的实现方法:
- 复制原有图标选择器,使其更具体。
- 将字体大小设置为
1em,这意味着它将100%继承父元素的字体大小。
/* 让图标继承父级a元素的字体大小 */
#navbarSupportedContent ul li a span {
font-size: 1em;
}
保存并刷新页面。现在,图标和文本的字体大小应该看起来一致了。父元素 a 的字体大小现在是 16px * 1.2 ≈ 19.2px,图标将继承这个值。
📐 优化间距
最后一步是优化图标和文本之间的间距。目前它们看起来太近了。
我们可以为图标添加一个右边距来增加间距。以下是具体操作:
- 在刚才的图标样式规则中,添加
margin-right属性。 - 设置一个合适的值,例如
5px。
/* 为图标添加右边距,优化与文本的间距 */
#navbarSupportedContent ul li a span {
font-size: 1em;
margin-right: 5px;
}
保存更改并查看浏览器。现在,下拉菜单的视觉效果得到了显著改善,图标和文本大小一致,间距也恰到好处。

✅ 总结
本节课中,我们一起学习了如何优化移动端下拉菜单的样式。我们首先分析了图标与文本大小不一致的原因,然后通过调整 font-size 属性统一了它们的大小,最后使用 margin-right 属性优化了它们之间的间距。至此,我们完成了当前网站开发阶段中导航菜单的样式调整工作。
054:第34讲 巨幕区域编码 🖼️

在本节课中,我们将学习如何为网页的主要内容区域进行编码,特别是创建一个响应式的巨幕(Jumbotron)区域。我们将使用Bootstrap框架,并处理不同屏幕尺寸下的图片加载与样式调整。


上一节我们完成了页面的头部导航栏。本节中,我们来看看如何构建页面的主要内容区域,首先从展示餐厅内部环境的大型图片区域开始。
在Bootstrap中,这种大型展示区域被称为“Jumbotron”。但在开始编码之前,我们需要定义一个容器元素来包裹网站的所有主要内容。这是因为我们之前将页头放在了.container容器中,为了使所有内容都能与页头边缘对齐,我们需要在主内容区域的最外层也应用.container类。
让我们回到代码编辑器,打开位于示例文件夹lecture 34 after中的index.html文件。向下滚动到关闭的</button>标签下方,创建一个新的<div>元素。
<div id="main" class="container">
我们为这个<div>添加了id="main",以便后续更容易地定位它。现在,我们准备开始编码巨幕区域。
我们的images文件夹中有多张不同尺寸的巨幕图片(例如768px、992px、1200px宽)。目标是根据屏幕尺寸加载不同分辨率的图片,以节省移动设备的带宽。
我们将创建一个<div>来容纳巨幕。其背景图片将通过CSS属性响应式加载,即根据媒体查询加载不同尺寸的图片。但背景图片有一个限制:它无法很好地拉伸和收缩。因此,对于超小屏幕,我们将使用一个<img>标签,并为其应用Bootstrap的.img-responsive类,这个类能让图片随浏览器宽度按比例缩放。
以下是用于超小屏幕的<img>标签代码:
<img src="images/jumbotron_768.jpg" alt="Restaurant Interior" class="img-responsive visible-xs">
class="visible-xs"确保此图片仅在超小屏幕尺寸下显示。保存后,在浏览器中挤压窗口到超小尺寸,可以看到图片出现。
现在,我们需要为更大的屏幕尺寸设置背景图片。我们打开styles.css文件,在对应的媒体查询中为.container .jumbotron类添加样式。
首先是为大屏幕设备(min-width: 1200px)添加样式:

@media (min-width: 1200px) {
.container .jumbotron {
background: url('../images/jumbotron_1200.jpg') no-repeat;
height: 675px;
box-shadow: 0 2px 4px rgba(0,0,0,.5);
border: 2px solid #ccc;
}
}
这段代码设置了背景图片、固定高度、阴影和边框。保存后查看浏览器,巨幕区域已显示,并带有边框和阴影效果。
接下来,我们需要为中等屏幕和小屏幕设备重复此过程,分别指定对应的图片和调整高度。
以下是为中等屏幕设备(min-width: 992px)添加的样式:
@media (min-width: 992px) {
.container .jumbotron {
background: url('../images/jumbotron_992.jpg') no-repeat;
height: 558px;
box-shadow: 0 2px 4px rgba(0,0,0,.5);
border: 2px solid #ccc;
}
}
以下是为小屏幕设备(min-width: 768px)添加的样式:
@media (min-width: 768px) {
.container .jumbotron {
background: url('../images/jumbotron_768.jpg') no-repeat;
height: 432px;
box-shadow: 0 2px 4px rgba(0,0,0,.5);
border: 2px solid #ccc;
}
}
保存后,在浏览器中调整窗口大小,可以看到不同尺寸的图片会根据屏幕宽度切换。
最后,我们需要为超小屏幕设备(max-width: 767px)完善巨幕的样式。这里我们不仅使用之前添加的<img>标签,还需要为.container .jumbotron类本身添加一些样式,如边距和内边距。

@media (max-width: 767px) {
.container .jumbotron {
margin-top: 30px;
padding: 0;
box-shadow: 0 2px 4px rgba(0,0,0,.5);
border: 2px solid #ccc;
}
}
保存后查看,可能会发现巨幕两侧出现了不需要的白边。通过浏览器开发者工具检查元素,发现是CSS选择器特异性(Specificity)问题。Bootstrap原有的.container .jumbotron规则比我们定义的更具体。
为了解决这个问题,我们需要确保我们的选择器具有相同的或更高的特异性。我们将代码中所有的.jumbotron选择器都改为.container .jumbotron。
修改后,白边问题得以解决。
此外,我们注意到box-shadow和border属性在每个媒体查询中都被重复定义。为了遵循DRY(Don‘t Repeat Yourself)原则,我们可以将这些公共样式提取到媒体查询之外。
/* 公共样式,适用于所有屏幕尺寸 */
.container .jumbotron {
box-shadow: 0 2px 4px rgba(0,0,0,.5);
border: 2px solid #ccc;
}
然后从各个媒体查询块中删除这两行属性。保存后,所有功能依然正常,代码也更加简洁。
本节课中我们一起学习了如何构建一个响应式的巨幕区域。关键步骤包括:
- 使用容器包裹主要内容。
- 结合使用
<img>标签(用于超小屏幕)和CSS背景图片(用于更大屏幕)来实现响应式图片加载。 - 利用媒体查询为不同屏幕尺寸指定不同的图片和样式。
- 处理CSS选择器特异性冲突。
- 提取重复的CSS属性以优化代码。

现在,我们的巨幕区域已经能够在各种设备上良好显示,为后续添加更多页面内容打下了基础。
055:12_第35讲 第1部分 导航模块编码 🧱

在本节课中,我们将学习如何使用Bootstrap网格系统来创建响应式的导航模块(或称为“磁贴”)。我们将构建三个磁贴,分别代表“菜单”、“特价菜”和“地图”,并让它们在不同屏幕尺寸下(桌面、平板、手机)以不同的布局方式呈现。
上一节我们完成了Jumbotron(巨幕)部分的编码。本节中,我们来看看如何构建其下方的三个导航磁贴。
分析设计需求 📱
首先,有必要了解这些磁贴在各种设备上的预期行为。
- 在桌面视图下,三个磁贴并排显示,各占屏幕宽度的三分之一。
- 在平板视图下,布局基本与桌面视图相同。
- 在移动设备视图下,我们希望“菜单”和“特价菜”磁贴并排显示在上方,而“地图”磁贴则滑到下方,并占据一整行的宽度。
- 在超小屏幕(如非常小的手机)上,我们希望所有磁贴垂直堆叠。
编写HTML结构 💻
现在,让我们转到代码编辑器,看看如何实现它。我们将在 index.html 文件中,Jumbotron div 的下方,主内容容器内添加代码。
由于主内容已经是Bootstrap的容器(.container),我们无需再添加一层容器。但我们需要定义一个行(.row)来放置我们的列。
以下是核心的HTML结构代码:
<div class="row" id="home-tiles">
<div class="col-md-4 col-sm-6 col-xs-12">
<a href="menu-categories.html">
<div id="menu-tile">
<span>菜单</span>
</div>
</a>
</div>
<div class="col-md-4 col-sm-6 col-xs-12">
<a href="#">
<div id="specials-tile">
<span>特价菜</span>
</div>
</a>
</div>
<div class="col-md-4 col-sm-12 col-xs-12">
<a href="#" target="_blank">
<div id="map-tile">
<span>地图</span>
</div>
</a>
</div>
</div>
网格类解析
以下是每个 div 上Bootstrap网格类的含义:
col-md-4: 在中等(medium)及以上屏幕(如桌面),每个磁贴占据4列。由于Bootstrap总共有12列,4+4+4=12,因此三个磁贴将并排显示。col-sm-6: 在小(small)屏幕(如平板),前两个磁贴各占6列(即半屏宽),第三个磁贴占12列(即全屏宽)。这会导致第三个磁贴(地图)滑到新的一行。col-xs-12: 在超小(extra small)屏幕(如手机),所有磁贴都占据12列,即垂直堆叠。
链接与内部结构解析
每个磁贴都包含一个链接(<a> 标签),其内部是一个带有特定ID的 div 和一个 span 标签。
- 链接 (
<a>):- 前两个磁贴的链接指向网站内的页面(如
menu-categories.html)。 - 地图磁贴的链接设置了
target="_blank",这会在用户点击时在新标签页中打开链接,确保用户不会离开我们的餐厅网站。
- 前两个磁贴的链接指向网站内的页面(如
- 内部
div: 这个div(例如#menu-tile)将被我们用来设置磁贴的尺寸、边框和作为内部元素定位的参考点(通过position: relative)。 - 内部
span: 这个span(例如包含文字“菜单”)将使用绝对定位(position: absolute)被放置在磁贴内部的特定位置(如右下角)。
编写基础CSS样式 🎨
现在HTML结构已经就位,但还没有样式。让我们在 styles.css 文件中为这些磁贴添加基础样式。
我们将首先为所有磁贴共享的样式编写规则。

#menu-tile,
#specials-tile,
#map-tile {
height: 250px;
width: 100%;
margin-bottom: 15px;
position: relative;
border: 2px solid #3F0C1F;
overflow: hidden;
}
以下是每个CSS属性的作用:
height: 250px;: 设置磁贴的固定高度。width: 100%;: 设置磁贴宽度为其父容器(即Bootstrap网格列)的100%。这意味着它会自动适应网格系统分配的宽度。margin-bottom: 15px;: 为磁贴底部添加外边距。这在磁贴堆叠时能提供间距,并使其与后续的页脚内容分开。position: relative;: 这是关键的一步。它将磁贴设置为相对定位,这本身不会移动元素,但会为其内部使用绝对定位(position: absolute)的子元素(即我们的span标签)建立一个定位的参考坐标系。border: 2px solid #3F0C1F;: 为磁贴添加一个2像素宽、实线、酒红色的边框。overflow: hidden;: 确保任何可能超出磁贴边界的内容(例如之后嵌入的地图iframe)会被裁剪隐藏。
接下来,让我们添加一个鼠标悬停效果,以提升用户体验。
#menu-tile:hover,
#specials-tile:hover,
#map-tile:hover {
box-shadow: 0 1px 5px 1px #cccccc;
}
:hover: 这是一个伪类选择器,用于定义当用户鼠标悬停在元素上时的样式。box-shadow: 0 1px 5px 1px #cccccc;: 为磁贴添加一个灰色的阴影效果,向用户直观地反馈该元素是可交互的。
当前效果与下节预告 👀
保存并刷新浏览器后,我们现在可以看到三个带有边框的矩形框。当鼠标悬停时,它们会出现阴影效果。基本的响应式布局也已经生效:调整浏览器窗口大小,可以观察到磁贴的排列方式会根据我们定义的网格类自动变化。
不过,磁贴内部目前还是空的。我们还没有添加背景图片和定位文字标签。
本节课中我们一起学习了如何使用Bootstrap网格系统创建响应式布局结构,并为其编写了基础的CSS样式,包括尺寸、边框和悬停效果。

在下一部分,我们将为这些磁贴填充内容:添加背景图片,并使用绝对定位将文字标签精确地放置在磁贴的右下角,最终完成导航模块的视觉呈现。
056:第2部分 导航模块编码

概述
在本节课中,我们将继续完善导航模块的内容填充与样式调整。我们将为菜单和特价商品区域添加实际图片,嵌入地图,并编写必要的响应式样式,以确保这些模块在不同屏幕尺寸下都能良好显示。

填充菜单与特价商品图片
上一节我们完成了菜单、特价商品和地图模块的布局与尺寸设置。本节中,我们首先来填充菜单和特价商品模块的图片内容。
我们将使用来自真实餐厅的图片,而非占位符。这些图片已存放在 images 文件夹中。
以下是用于设置背景图片的CSS代码:
.menu-tile {
background: url('../images/menu-tile.jpg') no-repeat center center;
background-size: cover;
height: 250px;
}
.specials-tile {
background: url('../images/specials-tile.jpg') no-repeat center center;
background-size: cover;
height: 250px;
}
代码解释:
background: url(...):将图片设置为元素的背景。../images/:URL路径中的../表示从styles文件夹向上退一级目录,再进入images文件夹。no-repeat center center:确保图片不重复,并水平和垂直居中。background-size: cover:使背景图片覆盖整个元素区域。height: 250px:设置模块的固定高度。
将图片居中放置的好处是,当容器元素因响应式布局而缩小时,图片的核心内容(如菜肴)仍能保持在视野中心,无需为不同屏幕尺寸准备多套图片。
保存代码后,页面中的菜单和特价商品模块已显示对应的背景图片。
定位模块标签
接下来,我们需要将每个模块底部的文字标签(如“菜单”、“特价”)定位到正确位置。
以下是定位标签的CSS代码:
.menu-tile span,
.specials-tile span,
.map-tile span {
position: absolute;
bottom: 0;
right: 0;
width: 100%;
text-align: center;
font-size: 1.6em;
text-transform: uppercase;
background-color: #000;
color: #fff;
opacity: 0.8;
}
代码解释:
position: absolute:使标签相对于其父容器(.menu-tile等)进行绝对定位。bottom: 0和right: 0:将标签定位到容器的右下角。结合width: 100%,使其拉伸至容器底部全宽。text-align: center:文字居中对齐。font-size: 1.6em:将字体大小设置为继承值的1.6倍。text-transform: uppercase:将文本转换为大写。background-color和color:设置背景为黑色,文字为白色。opacity: 0.8:将不透明度设置为80%,产生半透明效果,使背景图片能隐约透出。
应用此样式后,标签已整齐地显示在每个模块底部,半透明的黑色背景确保了文字清晰可读。
嵌入地图
现在,我们来处理地图模块,将Google地图嵌入其中。
操作步骤如下:
- 访问 Google 地图。
- 搜索需要显示的地点。
- 点击“分享”按钮,选择“嵌入地图”选项。
- 复制提供的
<iframe>代码。
将复制的 <iframe> 代码粘贴到 index.html 文件中对应的 map-tile 的 <div> 内。然后,需要调整iframe的尺寸以匹配我们的设计:
<iframe src="https://www.google.com/maps/embed?pb=..." width="100%" height="250" frameborder="0" style="border:0;" allowfullscreen></iframe>
调整说明:
height="250":将高度设置为250像素,与其他模块保持一致。width="100%":将宽度设置为100%,这样iframe会填满Bootstrap网格分配给它的单元格宽度。
完成嵌入后,地图模块已能正常显示。
实现响应式调整
我们已经完成了基本内容的填充。接下来,需要确保这些模块在小屏幕设备上也能完美显示。让我们测试一下响应式效果。
当浏览器窗口缩小到移动设备尺寸时,菜单和特价商品模块的边框会随着容器一起收缩,导致视觉效果不佳。我们希望图片和边框能作为一个整体保持固定宽度,并在屏幕中居中显示。
我们可以在浏览器开发者工具中模拟并调试这个问题。通过检查元素,我们发现为 .menu-tile 和 .specials-tile 设置一个固定宽度(例如360像素),并添加 margin: 0 auto 15px; 的样式,可以使它们在容器中水平居中,且上下保留15像素的边距。
由于这个问题主要出现在最大宽度为767像素的断点(Bootstrap的 sm 尺寸),我们需要将修复代码写入对应的媒体查询中。
回到代码编辑器,在 styles.css 文件中找到 @media (max-width: 767px) 媒体查询,并在其中添加以下样式:
@media (max-width: 767px) {
/* ... 其他现有样式 ... */
.menu-tile,
.specials-tile {
width: 360px;
margin: 0 auto 15px;
}
}
保存后刷新页面,在767像素及以下的宽度中,菜单和特价模块现在能保持合适宽度并居中显示。
针对超小设备的优化
然而,在屏幕更小的设备(如iPhone 4)上测试时,360像素的宽度加上内边距会导致布局溢出。此外,顶部的餐厅名称字体也显得过小。
为了解决这个问题,我们需要创建一个针对超小屏幕的额外媒体查询。

我们在 styles.css 中 @media (max-width: 767px) 的闭合花括号后,添加一个新的媒体查询:
/* 针对超小屏幕设备,如iPhone 4 */
@media (max-width: 479px) {
.menu-tile,
.specials-tile {
width: 300px; /* 减小宽度以适应更小屏幕 */
margin: 0 auto 10px;
}
.navbar-brand h1 {
padding-top: 5px;
font-size: 1.8em; /* 增大餐厅名称字体 */
}
}
代码解释:
@media (max-width: 479px):这个断点专门针对屏幕宽度小于480像素的超小设备。- 我们进一步减小了
.menu-tile和.specials-tile的宽度,并调整了边距。 - 通过选择器
.navbar-brand h1,我们增大了导航栏中餐厅名称的字体大小,使其在小屏幕上更醒目。
经过这番调整,页面在iPhone 4等超小屏幕设备上也能完美适配,所有元素都清晰可辨且布局合理。

总结
本节课中,我们一起完成了导航模块核心内容的填充与精细化样式调整。我们学习了如何为元素设置居中背景图片、如何绝对定位底部标签、如何嵌入并调整第三方地图,以及如何通过编写多个媒体查询来解决不同断点下的响应式布局问题。最终,我们实现了一个在从桌面到手机的各种屏幕尺寸上都能优雅显示的导航模块。
057:第36讲 第1部分 页脚编码 🧱

在本节课中,我们将学习如何为网站主页编码和样式化页脚部分。我们将使用语义化HTML标签和Bootstrap的网格系统来创建一个响应式布局,使其在大屏幕上显示为三列,在小屏幕上则垂直堆叠。

上一节我们完成了主页的标题、巨幕和主要内容区域的编码。本节中,我们来看看如何构建页脚部分。
首先,我们使用 <footer> 语义化标签来定义页脚区域,并为其添加 panel-footer 类,这个类会提供一些默认的背景和内边距样式。
<footer class="panel-footer">
在页脚内部,我们使用一个 container 类的 <div> 来包裹内容,这能确保页脚内容与页面其他部分的容器对齐。接着,我们添加一个 row 类的 <div> 来创建行。
<div class="container">
<div class="row">
我们的目标是创建三个等宽的列,分别用于显示营业时间、地址信息和客户评价。在Bootstrap网格系统中,我们使用 col-sm-4 类来实现这一点。这意味着在屏幕宽度大于等于小屏幕尺寸(约768像素)时,这三个部分会并排显示;当屏幕小于此尺寸时,它们会垂直堆叠。
以下是每一列的基本结构:
<section class="col-sm-4">
<span>营业时间</span><br>
周一至周四:11:15 - 23:00<br>
周五:11:15 - 02:00<br>
周六:不营业
<hr class="visible-xs">
</section>
请注意,我们在每个部分后面添加了一个 <hr> 水平线,并为其设置了 visible-xs 类。这意味着这条水平线只在超小屏幕尺寸下才会显示,用于在堆叠时提供视觉上的分隔。
最后,在页脚底部,我们添加了版权信息。
<div class="text-center">
© 大卫周中餐馆 2016
</div>
现在,让我们为页脚添加一些自定义样式,使其与网站的整体设计更加协调。
首先,我们调整页脚的整体外观,包括边距、内边距和背景颜色。
.panel-footer {
margin-top: 30px;
padding-top: 35px;
padding-bottom: 30px;
background-color: #222;
border-top: 0;
}
接下来,我们调整内容行与版权信息之间的间距。
.panel-footer div.row {
margin-bottom: 35px;
}
为了使营业时间和地址的标题更突出,我们增大其字体大小。
#hours span,
#address span {
font-size: 1.3em;
}
对于地址部分中的附加说明(如配送范围),我们将其设置为绿色并减小字体大小。
#address p {
color: #557c3e;
font-size: .8em;
line-height: 1.8;
}
最后,我们样式化客户评价部分,使其文字变为斜体,并增加第二条评价的上边距以增强可读性。
#testimonials {
font-style: italic;
}
#testimonials p:nth-child(2) {
margin-top: 25px;
}


本节课中我们一起学习了如何构建一个响应式页脚。我们使用了Bootstrap的网格系统来创建列布局,并添加了自定义CSS来改善其视觉外观。通过使用 visible-xs 类,我们确保了在不同屏幕尺寸下都有良好的视觉分隔。下一部分,我们将继续完善页脚在小屏幕上的布局和样式。
058:第36讲 第2部分 页脚编码

在本节课中,我们将学习如何为网页的页脚部分编写响应式样式,确保它在桌面、平板和手机等不同屏幕尺寸下都能良好显示。
在上一节中,我们完成了页脚在桌面浏览器尺寸下的样式布局。本节中,我们将专注于调整页脚样式,使其在平板和手机等较小屏幕尺寸下也能正常显示。
当浏览器处于完整的桌面模式时,页脚布局一切正常。
但是,当我们稍微挤压浏览器窗口时会发生什么呢?让我们来看看效果。
首先,可以看到页脚整体发生了跳动并自行挤压,这是一个好现象。如果我们继续缩小窗口,它仍然能很好地适应。当我们缩到最小尺寸时,整体效果不错,水平分隔线也显示出来了。
唯一的问题是,内容看起来有点过于紧凑,并且如果这些内容能居中显示,而不是靠左对齐,效果会更好。接下来,我们尝试解决这个问题。
让我们回到代码编辑器。这里已经有一些预设的样式,我们的目标显然是针对超小屏幕的媒体查询。
向下滚动到超小屏幕的媒体查询部分,它就在这里。我们找到位于页眉样式之后的最后一个媒体查询,并将我们的样式代码放在这里。
以下是我们要添加的样式代码,让我们快速浏览一下:
首先,我们将底部边距增加到30像素,因为之前的边距可能有点小。其次,我们设置面板页脚内的每个区块文本居中对齐,并增加其底部边距。
然而,对于最后一个区块,之前的通用桌面样式已经为其应用了边距。为了避免重复添加边距,我们特别指定第三个区块(即“客户评价”区块)的底部边距为零,因为整个行已经为其提供了边距。
此外,为了让水平分隔线看起来更美观,我们将其宽度设置为屏幕宽度的50%,这样它就能在各区块之间形成一条整洁的分隔线。
保存代码后,回到浏览器查看效果。现在所有内容都已居中,区块之间有了一条美观的分隔线,并且没有出现双倍边距的问题,整体看起来很不错。
让我们在不同尺寸下测试一下效果。在小屏幕下,布局看起来很好。当我们拉伸窗口时,页脚应该能平滑过渡到三列布局。在拉伸过程中,一切显示正常,底部内容分隔清晰、对齐整齐。
非常好,这就是我们的页脚部分。至此,主页的主要部分就全部完成了。接下来,我们将开始研究子页面,这些页面可以通过点击菜单磁贴或特色磁贴进入。显然,当我们进入菜单分类后,还需要进一步深入查看每个具体的菜单项。

本节课中,我们一起学习了如何为页脚编写响应式CSS代码。我们通过媒体查询针对小屏幕调整了边距、对齐方式和分隔线样式,确保了页脚在不同设备上都能提供良好的用户体验。下一节,我们将开始构建网站的子页面。
059:16_第37讲 第1部分 菜单分类编码

概述
在本节课中,我们将开始为网站的菜单分类页面编写代码。我们将创建一个网格布局来展示不同的菜单分类,并应用样式使其美观且响应式。

我们已经完成了网站首页的编码。现在,是时候回顾一下网站的导航结构了。
导航结构实际上相当简单。当用户首次访问首页时,他们可以选择点击“菜单”按钮,或者点击“菜单”图块。这两种操作都会将用户带到同一个地方:菜单分类页面。
这个页面会列出菜单中的所有分类,每个分类都配有一张图片和名称。其中一个分类叫做“特价菜”,这应该会让你想起首页上那个名为“特价菜”的图块。实际上,首页的“特价菜”图块只是一个指向名为“特价菜”的特定菜单分类的直接链接。
重点是,当用户点击“菜单”时,他们将进入这个视图,该视图会列出所有带有图片和名称的菜单分类。

如果他们点击页面右上角的菜单按钮,也会到达这个菜单分类页面。
现在,让我们回到代码编辑器,开始编写这个页面的代码。我目前在 Sublime Text 编辑器中,位于 examples/lecture37/after 文件夹。
尽管最终页面的结构在我们引入 JavaScript 并开始编写动态 HTML 后会有所不同,但在当前设计阶段,最简单的方法是复制 index.html 文件,并将其重命名为类似 menu-categories.html 的文件。我们这样做是因为 index.html 中的许多内容(如页眉、页脚和菜单项)都将完全相同,只有主要内容区域会发生变化。
我们复制并创建了 menu-categories.html 文件。现在,我们需要删除与这个新页面无关的所有内容。具体来说,我们需要清空 main-content 容器内的所有内容,以便为我们的菜单分类内容腾出空间。
现在,我们准备开始编写 menu-categories.html 的内容。如果我们现在在浏览器中查看这个页面(目前还是 index.html),点击“菜单”图块,实际上会跳转到 menu-categories.html。如你所见,目前只有页眉和页脚,页面内容区域是空的。
接下来,我们开始填充内容。
首先,我们需要让用户知道他们正在浏览哪个页面。我们将在主要内容区域添加一个 H2 标题(因为 H1 是网站主标题“David Chu‘s China Bistro”)。这个二级标题将是“Menu Categories”,并赋予它一个ID menu-categories-title 和一个用于居中的Bootstrap类 text-center。
保存后,在浏览器中刷新,我们应该能看到“Menu Categories”标题出现在页面中央。
接下来,我们需要添加一段来自原始菜单的提示文字,提醒顾客可以用糙米替换白米等。这属于餐厅特定的业务信息。我们同样将其放在一个 div 中,并使用 text-center 类使其居中。我们稍后可能会对它进行一些样式调整。
页面的标题部分基本完成。现在,我们需要开始编写分类图块的代码。这些图块当然需要放在一个网格布局中。
我们已经在 container 类内部,所以不需要再添加一个容器。但我们确实需要一个网格行。因此,我们将创建一个 section 元素,并赋予其 row 类。
观察我们想要的布局效果:在完整的浏览器宽度下,我们希望一行显示六个图块。显然,随着浏览器窗口缩小,我们希望它们能自适应排列,尽可能多地显示在同一行,同时保持良好的视觉效果,当然某些情况下它们需要堆叠显示。
我知道每个网格单元格都将是一个 div。我们将用一个 div 包裹它。我为每个网格单元格内部准备了一些代码,现在将其粘贴进来。
让我们快速浏览一下这段代码。我们还没有为这个网格分配任何列宽,但这将是每个网格单元格的基本内容。
- 首先,我们有一个指向
single-category.html的锚点(<a>)标签。single-category.html是一个我们尚未编写的通用页面,它将指向由这个div和图片标识的特定分类。目前它只是一个占位页面,未来它将是一个展示特定分类的指定页面(或通过JavaScript动态加载内容)。 - 锚点内部是一个
div,其类为category-tile(这是我们创建的自定义类)。 - 在这个
div内部,有一张尺寸为 200x200 的图片。图片源路径指向images/menu/B/B.jpg。我们暂时将这个分类命名为“Lunch”。

到目前为止一切顺利。让我们在浏览器中预览一下。我们可以看到图片,并且“Lunch”字样出现在图片右侧,但样式显然还没有完全调整好。
接下来,我们复制这个代表网格单元格的整个 div,并粘贴多次,比如粘贴8到9次,以模拟多个分类。

现在,我们实际上有了一个网格。它们目前看起来都一样,唯一不同的将是锚点标签的 href 链接地址和图片的 src 路径。
现在,让我们开始为网格设置样式。我们原本希望一行显示六个图块,这意味着每个单元格需要占据两列的宽度。我们可能希望在中型屏幕上也保持六列的布局,所以我们将使用类 col-md-2。
我们将这个类添加到每个网格单元格的 div 中。
保存并查看浏览器效果,我们立刻发现这行不通。因为图片相互重叠了,即使在完整的大屏幕尺寸下也是如此。显然,随着窗口缩小,情况会更糟。
这意味着六图块的布局确实不可行,我们需要重新考虑。
如果六图块不行,那试试一行四个图块怎么样?我们可以很容易地通过将 col-md-2 改为 col-md-3 来实现(因为12/4=3)。
我们将所有网格单元格的类都改为 col-md-3。
现在回到浏览器查看。虽然目前看起来有些混乱,但就图片在屏幕上的适应程度而言,看起来还可以。我们稍后会处理这些混乱的问题。

这看起来差不多了。现在,让我们开始使用 styles.css 文件来为实际单元格内部的内容设置样式。
我们滚动到CSS文件中首页样式之后的位置,大约在页脚样式附近。我将粘贴一些在本讲之前准备好的代码,并现在进行讲解。
首先,针对 .category-tile 类。在我们的HTML中,.category-tile 是锚点标签内部的 div。我们将采用与首页菜单图块类似的结构:我们有一些内容(这里实际上只是一张图片),并且我们有一个 span 元素。我们将定位这个 span,使其相对于 div 元素本身绝对定位。div 将作为其内部 span 的相对定位锚点,并且我们将把 span 放在底部。
让我们看看具体怎么做:
.category-tile本身设置为position: relative,作为定位锚点。- 给它一个漂亮的边框。
overflow: hidden,这样如果图片或其他内容超出边界,它将被裁剪而不是凸出来。- 因为我们知道图片尺寸是200x200,所以我们将
width和height也设置为200像素。 - 设置一些边距:
margin-top: 0,margin-right: auto,margin-bottom: 15px,margin-left: auto。这是我们之前讨论过的技巧:只要给元素一个宽度,并将左右边距设为auto,它就会自动水平居中。
接下来,针对 span 元素,也就是分类的标题:
- 设置为
position: absolute。 bottom: 0和right: 0,使其定位在右下角。width: 100%,使其横跨整个图片宽度。- 文本居中,字体大小为
1em,并转换为大写。 - 背景色为黑色,前景色(文字颜色)为白色。
- 稍微降低不透明度,以产生半透明的视觉效果。
然后,我们添加悬停效果。我们将添加一个与首页图块悬停时类似的盒阴影效果。请记住,这些分类图片中的每一个都是可点击的项目,因此我们需要某种视觉指示来告知用户。
最后,我们将通过选择器 #menu-categories-title + div 来定位 menu-categories-title 元素的下一个兄弟 div 元素。在我们的HTML中,#menu-categories-title 是顶部的“Menu Categories”标题,它的下一个兄弟元素就是包含“substituting white rice...”提示文字的 div。我们给这个 div 设置 margin-top: 50px,目的是在页面标题、给顾客的提示文字和下方的实际菜单分类之间创造一个良好的视觉分隔。
保存CSS文件,然后回到浏览器。我们可以看到至少第一行看起来很不错,但第二行似乎有些问题。
让我们回到代码,检查一下。此时它们应该都是 col-md-3。检查一下我们是否在哪里搞错了。果然,我们发现有些地方可能遗漏或写错了类名。确保所有网格单元格的 div 都正确应用了 col-md-3 类。
修正后,再次回到浏览器。现在它们应该都整齐地对齐了。如果我们挤压浏览器窗口,它们会靠得更近。在某个点(因为我们没有为小于中等屏幕的尺寸指定列宽),它们会开始堆叠显示。但显然,我们不希望它们在中型屏幕尺寸下就堆叠,我们还需要为小屏幕和超小屏幕指定样式,而这正是我们将在本讲座第二部分要处理的内容。
总结
本节课中,我们一起学习了如何创建菜单分类页面。我们复制了首页模板作为基础,清除了主要内容区域,并添加了页面标题和提示信息。接着,我们构建了一个网格系统来展示菜单分类图块,最初尝试了一行六列的布局,但发现不可行后调整为一行四列(使用 col-md-3)。然后,我们通过CSS为分类图块添加了样式,包括尺寸、边框、居中、内部标题的绝对定位以及悬停效果,并在页面标题和内容之间添加了间距。目前页面在中型及以上屏幕显示良好,响应式适配更小屏幕的工作将在下一部分完成。
060:17_第37讲 第2部分 菜单分类编码

概述
在本节课中,我们将继续完成菜单分类页面的响应式布局。上一部分我们处理了中等及更大屏幕尺寸的布局,本节我们将专注于为小屏幕和超小屏幕尺寸调整布局,并学习如何创建自定义的Bootstrap断点来解决特定问题。
调整小屏幕布局
上一节我们介绍了中等屏幕的布局,本节中我们来看看如何为小屏幕尺寸调整列数。
在中等屏幕尺寸下,我们使用了col-md-3类,这意味着每行显示4个分类项。对于小屏幕尺寸,我们希望调整为每行显示3个项,因此需要将列类改为col-sm-4。
以下是操作步骤:
- 使用文本编辑器的多区域编辑功能,选中所有分类项的列类部分。
- 将
col-md-3修改为col-sm-4。 - 保存更改并在浏览器中测试。当屏幕尺寸缩小到小屏幕范围时,布局应变为每行3个分类项,多余的项会自动换行到下一行。
处理超小屏幕布局
现在,我们来看看当屏幕尺寸进一步缩小到超小屏幕时会发生什么。
在超小屏幕尺寸下,我们希望每行显示2个分类项。为了实现这一点,我们需要为这些元素添加col-xs-6类。
以下是操作步骤:
- 再次使用多区域编辑功能,选中所有分类项。
- 为它们添加
col-xs-6类。 - 保存并测试。此时,布局应能在小屏幕下显示3列,在超小屏幕下显示2列。
创建自定义断点
当我们把屏幕尺寸缩放到比超小屏幕更小时,会出现一个问题:图片无法很好地适应布局。Bootstrap没有提供比xs更小的内置断点,因此我们需要创建一个自定义断点来解决这个问题。
我们需要创建一个新的CSS类,当屏幕尺寸极其小时,强制让每个分类项占据整行宽度。我们将这个类命名为col-xxs-12。
以下是定义这个自定义类的步骤:
- 打开项目的CSS文件(例如
styles.css)。 - 在文件底部,找到或创建一个针对极小屏幕的媒体查询。
- 在该媒体查询内,定义
col-xxs-12类。为了与Bootstrap的行为保持一致,我们可以参考Bootstrap中col-xs-12类的定义。 - 关键样式属性包括将元素设置为相对定位、向左浮动,并且宽度设置为100%。
代码示例:
@media (max-width: 480px) { /* 自定义的极小屏幕断点 */
.col-xxs-12 {
position: relative;
min-height: 1px;
float: left;
width: 100%;
}
}
应用自定义类

定义好自定义类之后,我们需要将其应用到HTML元素上。
以下是应用步骤:
- 回到
menu-categories.html文件。 - 为每一个菜单分类项的元素添加
col-xxs-12类。 - 保存文件并在浏览器中进行测试。现在,当屏幕尺寸缩小到我们定义的极小断点时,分类项应该会堆叠成单列显示。
总结

本节课中我们一起学习了如何完善响应式布局。我们首先调整了小屏幕和超小屏幕的列布局,然后通过创建一个自定义的Bootstrap断点类col-xxs-12,解决了在极小屏幕下布局错乱的问题。现在,菜单分类页面已经能够在所有屏幕尺寸下正确显示,从大屏幕的四列到极小屏幕的单列,实现了流畅的响应式效果。后续我们将通过JavaScript动态生成这些内容,但目前的静态布局已经为开发打下了良好的基础。
061:单个菜单分类页面编码(第一部分)

在本节课中,我们将学习如何为单个菜单分类页面编写HTML结构。我们将基于一个设计稿,创建一个展示特定分类下所有菜单项的页面,并重点讲解如何使用Bootstrap网格系统来布局这些项目。
概述与页面分析
上一节我们完成了菜单分类页面的编码。本节中,我们来看看当用户点击某个分类后,如何构建对应的单个分类页面。

根据设计稿,单个分类页面包含分类名称、一段描述信息,以及该分类下的所有菜单项。菜单项的布局特点是:每行显示两个项目,每个项目内部又分为图片/价格区和文字描述区。这意味着我们需要使用嵌套的Bootstrap网格系统。
设置页面基础与导航状态
首先,我们需要创建一个新的HTML文件作为单个分类页面。由于所有页面的头部、导航和页脚都相同,我们可以从菜单分类页面复制一份作为起点。
在开始编码主要内容之前,我们先优化导航菜单的视觉反馈,让用户明确知道自己位于“菜单”页面。
以下是具体步骤:
- 在导航菜单中找到“Menu”链接。
- 为其添加
class="active"。这是一个Bootstrap类,我们之前已自定义其样式以匹配悬停效果。

这样,当用户位于菜单相关页面时,“Menu”导航项会高亮显示。
构建页面主要内容结构
现在,我们开始编写页面的核心内容。首先清除从模板中复制的旧内容,得到一个干净的开始区域。
页面的主要内容结构如下:
- 一个居中的页面标题(
<h2>)。 - 一段居中的分类描述文本。
- 一个用于容纳所有菜单项列表的网格区域。
以下是初始代码结构:
<h2 id="menu-categories-title" class="text-center">午餐菜单</h2>
<div class="text-center">午餐供应时间从下午1点开始。</div>
<section class="row">
<!-- 菜单项将在这里插入 -->
</section>
标题和描述文本未来将通过JavaScript动态填充。
创建单个菜单项的网格布局
接下来,我们编码单个菜单项的结构。这是本节课的核心。每个菜单项将占据外部网格的一列(col-md-6),从而实现在中等及以上屏幕尺寸上每行显示两项。
在每个菜单项内部,我们还需要一个嵌套的网格来排列图片和文字。这个内部网格使用 row 类,并包含两个列:
- 图片/价格区:使用
col-sm-5。 - 文字描述区:使用
col-sm-7。
选择 col-sm-* 意味着在小型(sm)及以上屏幕,这两个区域并排显示;在超小(xs)屏幕,它们将上下堆叠。
以下是单个菜单项的完整HTML结构代码:
<div class="menu-item-tile col-md-6">
<div class="row">
<!-- 图片与价格区域 -->
<div class="col-sm-5">
<div class="menu-item-photo">
<div>D01</div>
<img class="img-responsive" width="250" height="150" src="images/menu/xxx.jpg" alt="菜品">
</div>
<div class="menu-item-price">$10.95<span> (品脱)</span><br>$14.95 <span>(夸脱)</span></div>
</div>
<!-- 文字描述区域 -->
<div class="col-sm-7">
<h3 class="menu-item-title">菜品名称</h3>
<p class="menu-item-details">这里是关于这道菜的详细描述文本,例如主要食材和口味特点。</p>
</div>
</div>
<hr class="visible-xs">
</div>
代码要点解析:
menu-item-photo:包裹图片的容器,我们将使用CSS将其设置为相对定位,以便绝对定位其中的编号(D01)。menu-item-price:价格信息。我们将<span>标签包裹货币单位,以便后续单独设置更小的字体。visible-xs:这是一个Bootstrap响应式工具类。我们只在超小屏幕(xs)下显示<hr>水平线,作为菜单项堆叠时的视觉分隔符。
总结与下节预告
本节课中我们一起学习了单个菜单分类页面的HTML结构搭建。我们创建了页面的基础框架,并重点构建了使用嵌套Bootstrap网格的菜单项组件,实现了响应式布局。
现在,基本的HTML结构已经编写完成。在下一部分,我们将为这些元素添加CSS样式,让页面真正变得美观起来。
062:19_第38讲 第2部分 单个菜单分类页面编码

概述
在本节课中,我们将继续为单个菜单项网格编写样式。我们将从基本的布局和间距开始,逐步调整图片、价格和描述的样式,并最终解决在移动设备上的响应式布局问题。
样式编写:基础布局与间距
在上一部分,我们完成了单个菜单项网格的HTML结构。本节中,我们开始为其添加CSS样式,使其在视觉上更符合设计。
首先,我们为第二个单元格添加一个不同的类名d2,以便与第一个单元格d1区分。接着,我们在styles.css文件中,在已有的菜单分类样式下方,添加预设的样式代码。为了便于逐步讲解,我们将这些样式先注释掉。
以下是针对菜单项网格容器的初始样式:
.menu-item-tile {
margin-bottom: 25px;
}
这段代码为每个菜单项网格添加了25像素的下边距,目的是在视觉上分隔每一行项目,即使它们并非严格意义上的Bootstrap网格行。
接下来,我们处理水平分隔线<hr>的显示逻辑。我们希望它仅在浏览器窗口缩小到超小(extra small)屏幕尺寸时才显示,以视觉上分隔项目。
.menu-item-tile hr {
display: none;
}
/* 在超小屏幕的媒体查询中,我们会将其显示出来 */
保存并查看浏览器,当窗口缩小时,可以看到水平线出现,起到了分隔作用。
样式编写:价格与图片
上一节我们设置了基础布局,本节中我们来看看如何设置价格和图片的样式。
首先,我们设置价格的样式。价格元素位于类名为menu-item-price的<div>中。
.menu-item-tile .menu-item-price {
font-size: 1.2em;
text-align: right;
margin-top: -15px;
margin-right: -15px;
}
这段代码增大了价格字体,并将其向右对齐,以匹配图片右侧的布局。负的margin值用于微调位置,使其与图片顶部对齐。
同时,我们希望价格单位(如“品脱”、“夸脱”)的字体小一些。这通过选择menu-item-price内的<span>元素来实现。
.menu-item-tile .menu-item-price span {
font-size: 0.8em;
}
现在,我们来设置图片容器的样式。图片容器(.menu-item-photo)需要相对定位,以便在其内部绝对定位菜单编号。
.menu-item-photo {
position: relative;
border: 2px solid #800000; /* 栗色边框 */
overflow: hidden; /* 溢出部分裁剪 */
padding: 0;
margin-right: 15px;
margin-left: auto;
margin-bottom: 20px;
max-width: 250px;
}
我们设置了边框、内外边距,并限制了最大宽度为250像素,以防止图片拉伸导致像素化或布局混乱。
图片容器内部的菜单编号<div>需要绝对定位在右下角。
.menu-item-photo div {
position: absolute;
bottom: 0;
right: 0;
width: 80px;
background-color: #557c3e; /* 绿色背景 */
text-align: center;
}
保存后,可以看到菜单编号已正确显示在图片右下角。
此时,由于图片的定位和边距,价格的位置可能有些偏移。这正是之前我们为价格设置负margin的原因,现在取消注释后,价格与图片的对齐看起来更协调了。
样式编写:标题与描述
上一节我们美化了价格和图片区域,本节我们来调整标题和描述的样式。
首先设置描述的样式。我们希望描述内容与右侧的网格单元格(或浏览器边缘)保持一定距离。
.menu-item-description {
padding-right: 30px;
}
接着,设置标题(<h3>)的样式。我们清除其可能继承的默认边距,并只设置底部边距。
.menu-item-tile .menu-item-title {
margin: 0 0 10px 0;
}
最后,调整描述详情文本的样式,使其稍小并呈斜体,看起来更美观。
.menu-item-details {
font-size: 0.9em;
font-style: italic;
}
保存后,在浏览器中查看,标题和描述现在已整齐地对齐。
响应式调整:超小屏幕布局
到目前为止,在大中型屏幕上布局看起来不错。但当我们缩小浏览器窗口到超小(extra small)屏幕尺寸时,布局出现了问题:图片和描述没有居中,而是分别偏左和偏右。
我们需要在针对超小屏幕的媒体查询中添加样式来修复这个问题。
首先,调整图片容器(.menu-item-photo)的右边距。在大屏幕上它是15px,但在小屏幕上我们需要将其设置为auto以实现水平居中。
@media (max-width: 767px) {
.menu-item-photo {
margin-right: auto;
}
}
其次,让价格在超小屏幕上也居中对齐。
@media (max-width: 767px) {
.menu-item-tile .menu-item-price {
text-align: center;
}
}

最后,让描述文本也居中对齐。
@media (max-width: 767px) {
.menu-item-description {
text-align: center;
}
}
保存所有更改后,再次在超小屏幕尺寸下查看浏览器,现在图片、价格和描述都已完美居中显示。

总结
本节课中,我们一起学习了如何为一个菜单项网格编写完整的CSS样式。我们从基础间距和边框开始,逐步设置了价格、图片、标题和描述的样式。最后,我们通过媒体查询解决了在移动设备上的响应式布局问题,确保了在所有屏幕尺寸下都有良好的视觉效果。在下一部分,我们将探讨如何处理来自服务器的、长度不固定的描述文本所带来的布局挑战。
063:单个菜单分类页面编码(第三部分)

概述
在本节课中,我们将继续完善单个菜单分类页面的编码工作。上一节我们完成了页面的HTML结构和CSS样式布局。本节中,我们将重点解决页面内容数据变化时可能出现的布局问题,并学习如何使用Bootstrap的网格系统工具来确保布局的稳定性。

在本次讲座的第一和第二部分,我们已经创建了HTML和CSS样式,来布局我们的单个菜单分类页面。在本讲中,我们将通过使用之前部分创建的网格,来处理我们所显示数据的可变性。
在宣告本页面完成之前,让我们先在页面中添加大量菜单项。
以下是添加多个菜单项的示例代码。我们复制一个现有的菜单项区块,并创建多个副本。
<!-- 菜单项 D01 -->
<div class="col-md-6">
<div class="row">
<div class="col-sm-5">
<div class="menu-item-photo">
<div>D01</div>
<img class="img-responsive" width="250" height="150" src="images/menu/B/B-1.jpg" alt="Item">
</div>
<div class="menu-item-price">$10.95<span> (pint)</span> $14.95 <span>(quart)</span></div>
</div>
<div class="menu-item-description col-sm-7">
<h3 class="menu-item-title">Veal with Mixed Vegetables</h3>
<p class="menu-item-details">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque inventore esse minima incidunt impedit. Asperiores, voluptatem. Sint aspernatur provident, rem odio dolorem eaque voluptatibus modi reprehenderit minima, itaque cupiditate totam.</p>
</div>
</div>
<hr class="visible-xs">
</div>
<!-- 菜单项 D02 -->
<div class="col-md-6">
<!-- ... 内容与D01类似,但ID和标题不同 ... -->
</div>
<!-- 菜单项 D03 -->
<div class="col-md-6">
<!-- ... 内容 ... -->
</div>
<!-- 菜单项 D04 -->
<div class="col-md-6">
<!-- ... 内容 ... -->
</div>
<!-- 菜单项 D05 -->
<div class="col-md-6">
<!-- ... 内容 ... -->
</div>
<!-- 菜单项 D06 -->
<div class="col-md-6">
<!-- ... 内容 ... -->
</div>
保存后,回到浏览器,可以看到它们排列得非常整齐。菜单项都很好地排列在此。如果挤压浏览器窗口,可以看到它们都能很好地对齐,并且在最小屏幕尺寸下也能很好地折叠。我们甚至可以看到之前设计的页脚。
接下来,我们需要测试一个在内容不受控制时非常重要的情况。换句话说,我们无法控制描述文本的长度。描述可能很短,也可能很长。我们不确定当描述文本的高度超过当前行或这个想象中的行时会发生什么。
让我们来看一下。例如,我们将第一个菜单项的描述文本变得长很多。
<p class="menu-item-details">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque inventore esse minima incidunt impedit. Asperiores, voluptatem. Sint aspernatur provident, rem odio dolorem eaque voluptatibus modi reprehenderit minima, itaque cupiditate totam.
<!-- 复制并粘贴上述段落多次以加长描述 -->
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque inventore esse minima incidunt impedit. Asperiores, voluptatem. Sint aspernatur provident, rem odio dolorem eaque voluptatibus modi reprehenderit minima, itaque cupiditate totam.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque inventore esse minima incidunt impedit. Asperiores, voluptatem. Sint aspernatur provident, rem odio dolorem eaque voluptatibus modi reprehenderit minima, itaque cupiditate totam.
</p>
保存后,回到浏览器查看。可以看到第三个项目被推到了这边。它本应坐在这里。第四个项目本应仍在第二行。现在布局被打乱了。
那么,我们如何解决这个问题呢?有几种方法。
第一种方法是限制文本字段的长度。也就是说,可以规定只允许这么多字符,超出的部分将被截断。我们将根据我们的规格裁剪任何不适合这里的内容。这是一种相当严格的方法。并且很难向餐厅老板提出要求,说描述不能超过这个长度。尽管让描述简洁明了确实是个好主意,没人想读很长的描述。但同时,如果我们的布局现在反过来规定某个菜单项的描述能有多长,这听起来有点奇怪。
因此,我们可以做的是修复整个布局问题。
那么,为什么一开始会出现这个布局问题呢?请记住,Bootstrap中的网格布局(至少在当前版本中)是基于浮动元素的。所有这些元素都向左浮动。
当一个元素向左浮动,而它前面的某个元素变得太长时,会发生什么?它开始换行。现在它无法适应这一行,因为这个东西在这里凸出来了。所以它能适应的下一个位置就在这里。然后这个第四号元素试图向左浮动,但它不能,因为没有空间了。所以它去了下一行。
如果你回顾关于浮动的讲座,这是我们处理过的问题,还记得 clear 属性。这基本上就是我们需要在这里做的,只不过Bootstrap在其网格系统中预先内置了一个特殊的清除浮动工具来解决这些问题。
如果我们访问Bootstrap官网,现在我们在网格系统部分,可以看到这里提到了“响应式列重置”。这指的就是这个特定问题:当一列变得更大时,其余列会变得混乱。它告诉你,为了修复这个问题,需要在你的网格中添加另一个 div。这个 div 基本上有一个类 clearfix,然后有一个限定条件,说明这个 div 应该在何时显示。例如,他们希望只在屏幕为超小尺寸时显示为 visible-xs-block。
在我们的案例中,让我们看看。这个问题确实发生在大屏幕上,也发生在较小的屏幕上。在中型屏幕上似乎也发生了,但当我们到达这个部分时,由于我们已经堆叠了,这没有区别。所以在小屏幕上这不是问题,但在中型屏幕上肯定是问题,在大屏幕上绝对是问题。
我们可以通过在网格中放置这种类型的 div 来修复它。让我们回到代码编辑器。基本上,这是第一个,但我们需要在第二个之后。第二个是我们需要清除的。
所以,在第二个网格单元格之后,我们将放置一个清除浮动。但在我们的案例中,我们希望它在中型屏幕生效,并且也需要在大型屏幕生效时显示相同的东西。
以下是修复布局的代码示例。我们在每两个菜单项之后插入一个清除浮动的 div。
<!-- 菜单项 D01 -->
<div class="col-md-6">...</div>
<!-- 菜单项 D02 -->
<div class="col-md-6">...</div>
<!-- 在第二个单元格后插入清除浮动,针对中型和大型屏幕 -->
<div class="clearfix visible-md-block visible-lg-block"></div>
<!-- 菜单项 D03 -->
<div class="col-md-6">...</div>
<!-- 菜单项 D04 -->
<div class="col-md-6">...</div>
<!-- 在第四个单元格后再次插入清除浮动 -->
<div class="clearfix visible-md-block visible-lg-block"></div>
<!-- 菜单项 D05 -->
<div class="col-md-6">...</div>
<!-- 菜单项 D06 -->
<div class="col-md-6">...</div>
<!-- 在第六个单元格后插入清除浮动 -->
<div class="clearfix visible-md-block visible-lg-block"></div>
保存后,回到浏览器查看我们的网站。可以看到这里清除得非常漂亮。当然,我们需要重复这个操作,在每个第二个单元格后插入这个 div。我们在这个之后插入了一个,现在需要在第四号之后和第六号之后插入。
让我们回到代码编辑器,实际执行这个操作。复制这个 div,在第三号之后(实际上我们需要在第四号之后插入)。这里是第四号,我们回到这里并在那里插入。现在,如果第三号和第四号的任何描述变得有点失控,我们的布局仍然会保持良好。
我们也需要在第六号之后放置这个。这里是第六号,向下滚动一点。我们要确保不要在这里犯错,在第六号之后,我们将那个 div 插入在那里。
回到浏览器,我们可以看到,布局仍然非常整齐。如果我们挤压窗口使其变为中等尺寸,它们仍然排列整齐。整个第二行被推下来,但仍然对齐得很好。可以看到三在四之前,没有以错误的方式浮动,其他一切看起来都很好。
总结
本节课中,我们一起学习了如何通过添加多个菜单项来测试页面布局的健壮性,并发现了当描述文本长度不一致时可能引发的浮动布局问题。我们深入探讨了Bootstrap网格系统基于浮动的原理,并学习了如何使用 clearfix 类和响应式可见性工具(visible-md-block、visible-lg-block)来强制清除浮动,从而确保每行两个菜单项的布局在任何内容长度下都能保持稳定。这完成了我们的单个分类页面,也基本上完成了整个网站的布局。剩下的工作就是动态地将内容插入到“菜单分类”下的每一个位置。目前我们只有“午餐”,并且只是重复插入一些随机图片。显然,我们希望所有这一切都能更动态地工作,但就布局而言,我们已经基本完成了。
064:在真机上测试移动端版本

在本节课中,我们将学习如何在实际的移动设备上测试我们开发的网站,并发现和修复一些在移动端浏览时才会显现的布局与交互问题。
上一节我们完成了网站的主要布局和响应式设计。本节中我们来看看如何在实际的移动设备上进行测试,以确保网站在手机上的体验同样出色。

在我们宣布布局完全成功之前,让我们在实际的手机上测试这个网站。
我手边有一部iPhone 6。我将在iPhone 6上加载我们的网站,然后点击浏览,看看是否能发现任何需要修复的问题。
现在,我正使用我的iPhone 6浏览我们的网站,这是网站的主页。
让我们滚动页面看看效果,看起来一切正常。接下来,点击“菜单”板块,它应该会带我们进入菜单分类页面。这个功能运行良好。
我们再点击其中一个菜单分类,页面会跳转到显示具体菜单项的单个分类页面。这个功能也运行得很好。
现在,让我们点击电话号码按钮,看看它是否会尝试拨打电话。确实如此,它正在尝试拨打电话。我们取消这个操作。

接着,看看右上角的菜单按钮。如果我点击这里,菜单会显示出来。如果我点击菜单中的“菜单”选项,它会带我进入菜单分类页面。

让我们返回到单个分类页面。现在,打开那个下拉菜单。
有一个问题让我感到困扰:菜单里没有“主页”导航选项。目前只有“菜单”、“关于”和“奖项”。
在桌面版本中,我们或许可以接受没有“主页”按钮,因为不想让界面显得拥挤。但在移动端,除了点击餐厅名称“David Chu‘s China Bistro”返回主页外,我们确实需要其他选项。
因此,我真的很希望在这里能有一个“主页”选项。
还有另一个稍显奇怪的地方。例如,如果我点击“午餐菜单”并尝试选择它,我自然会认为下拉菜单应该自动消失。但它并没有,只有当我再次点击菜单按钮时它才会关闭。
这个问题我们目前无法解决,因为它不能仅靠HTML和CSS来处理,需要使用JavaScript。我们将在后续课程中学习JavaScript,所以这个问题暂时搁置。
但是,在下拉菜单中添加“主页”按钮,是我们现在就可以做的事情。
所以,让我们回到代码编辑器并添加这个功能。


现在,我回到了Sublime Text编辑器,位于examples/lecture39/after文件夹中,正在查看index.html文件。
我们需要做的只是添加一个新的菜单项,这其实相当简单。我们只需要找到菜单列表。
以下是我们的菜单列表代码片段,每个菜单项都是一个<li>元素:
<ul class="nav navbar-nav">
<li>
<a href="index.html"><span class="glyphicon glyphicon-home"></span> Home</a>
</li>
<!-- 其他菜单项 -->
</ul>
我们只需要在前面腾出一些空间。我将粘贴一段预先写好的代码。
我们希望这个菜单项只在屏幕尺寸为超小(xs)时可见。并且,由于我们当前在index.html主页上,我们希望将其标记为“active”(激活状态),这样在移动端视图中,它会有一个灰色的背景高亮显示。
它只是一个指向index.html的链接,同时我们使用了Glyphicon的“home”图标,使其显示为一个小房子按钮,并加上文字“Home”。
让我们保存修改,然后回到浏览器查看效果。
点击菜单按钮,现在“主页”选项出现了。如果我们进入“菜单”页面,再次点击菜单按钮,现在显示的是“菜单”页面。但我们还没有在菜单分类页面上添加这个按钮。
所以,让我们来修复这个问题。复制刚才的代码。
打开我们的菜单分类页面(例如menu-categories.html),同样向下滚动到<li>列表所在位置。
我们将代码粘贴在这里,但这次需要移除active类,因为在这个页面上“主页”不是激活状态。
在单个分类页面(例如single-category.html)上,我们也需要进行同样的操作。向下滚动找到<li>列表,粘贴代码,并同样移除active类。
让我们回到浏览器测试一下。点击菜单按钮,现在我们进入了“菜单”页。如果我们点击“主页”,就会回到主页,并且“主页”选项会高亮显示。
如果我们进入“菜单”,再进入单个分类页面,菜单中仍然显示“菜单”为激活状态,这很好。点击“主页”,我们就能回到主屏幕。

看起来我们已经修复了这个次要问题,除了那些需要JavaScript才能处理的部分。现在,我们已经准备好继续前进了。

本节课中我们一起学习了在实际移动设备上测试网站的重要性。通过测试,我们发现了移动端导航中缺少“主页”入口的问题,并学会了如何通过修改HTML代码,在响应式导航栏中添加一个仅在移动端显示的“主页”链接。我们还了解到,一些更复杂的交互行为(如下拉菜单的自动关闭)需要未来学习的JavaScript知识来实现。
065:模块3总结

概述
在本节课中,我们将对模块3的学习内容进行总结,并了解如何为课程提供反馈,同时预告下一个模块的开始。
课程内容总结
模块3的学习已经结束。如果你到目前为止很享受这门课程,我们非常感谢你能花几分钟时间为这门课程打一个五星好评。这对我们意义重大。
后续安排
下一个模块是模块4。我们将在下一个模块中再见。

总结
本节课中我们一起回顾了模块3的结束,了解了课程反馈的重要性,并得知了模块4即将开始。
066:配置JavaScript开发环境 🛠️

在本节课中,我们将学习如何为JavaScript开发配置一个高效的工作环境。我们将重点介绍如何优化代码编辑器,以及如何设置浏览器工具来辅助我们的开发工作。
优化Sublime Text工作区

在深入学习JavaScript之前,我们先调整一下工作区,以便在Sublime Text中获得更好的体验。这样做是因为,当我们开始编辑JavaScript或CSS文件时,Sublime Text会尝试通过代码补全来帮助我们。然而,如果它认为我们整个课程的所有40个讲座文件都属于当前项目,它就会将所有文件(包括内含的CSS和JavaScript代码)加载到内存中,这可能导致编辑器运行缓慢。
因此,我们需要重新配置工作区,只包含我们当前正在学习的特定讲座内容。
以下是具体步骤:
- 首先,打开命令行工具。
- 使用
cd命令导航到第40讲的目录。cd lecture40 - Sublime Text提供了一个跨平台(Windows、Linux、Mac)的命令,可以在特定目录中启动编辑器。我将这个命令映射为关键字
sublime。 - 在当前目录下执行
sublime .命令。这个点号代表当前目录。sublime . - 执行后,Sublime Text会启动,并且只将当前文件夹作为工作区打开,不会包含其他无关文件。
关于如何在你的操作系统上设置 sublime 命令,课程提供了详细的教程链接。对于Mac和Linux用户,设置方法非常相似;Windows用户也有对应的指导教程。你可以按照这些步骤进行配置,以便在任何目录下都能方便地使用Sublime Text。
启动实时预览
现在,我们已经打开了只包含第40讲文件的工作区。接下来,我们需要启动浏览器同步工具,这样我们编写的代码就能实时在浏览器中更新,无需手动刷新。
- 确保命令行当前位于
lecture40目录中。 - 输入以下命令启动Browser Sync服务器:
browser-sync start --server --directory --files "**/*" - 命令中的
**/*表示监视当前目录及其所有子目录中的文件变化。这是因为我们的项目结构里包含一个js目录,里面存放着JavaScript脚本文件(例如script.js)。我们需要确保对这个目录下文件的修改也能被同步到浏览器。 - 启动成功后,在浏览器中打开提供的本地地址。虽然页面可能不会自动加载
index.html,但我们可以手动点击它。 - 现在,我们看到了第40讲的页面,标题为“Lecture 40”。


使用Chrome开发者工具
从现在开始,我们关注的重点将不仅仅是页面本身,更重要的是运行在页面上的JavaScript代码。Chrome开发者工具是Chrome浏览器内置的强大调试工具,我们将频繁使用它。
- 在打开的页面中,按下
Cmd + Option + I(Mac)或Ctrl + Shift + I(Windows/Linux)打开开发者工具。 - 在前面的课程中,我们主要使用 Elements(元素) 标签页。而从现在起,我们将主要在 Console(控制台) 标签页中工作。
- 点击 Console 标签页。这里将显示JavaScript代码的输出、日志信息和错误消息。
- 为了获得更大的代码编写和查看空间,我们可以将开发者工具面板从浏览器底部分离出来。点击面板右上角的三个点菜单,选择“Undock into separate window”(分离到独立窗口)。
- 现在,我们可以调整这个独立窗口的大小和位置,让它与代码编辑器并排显示,从而获得一个完整的JavaScript控制台视图,方便我们边写代码边调试。
整理编辑器布局
为了进一步优化开发环境,我们可以整理一下Sublime Text的布局。
- 在Sublime Text中,我们同时打开了
index.html和js/script.js文件。 - 我们可以使用
Cmd + K, B(Mac)或Ctrl + K, B(Windows/Linux)来切换侧边栏的显示,或者直接关闭不需要的文件标签页,让界面更简洁。 - 由于我们知道当前的工作目录,可以暂时关闭文件浏览器侧边栏,为代码编辑区留出更多空间。
总结


本节课中,我们一起学习了如何配置一个高效的JavaScript开发环境。我们通过优化Sublime Text工作区来提升编辑器性能,使用Browser Sync实现了代码的实时浏览器预览,并设置了Chrome开发者工具的控制台作为我们调试JavaScript的主要阵地。现在,我们的开发环境已经准备就绪。在下一部分,我们将探讨JavaScript代码应该放置在HTML文档中的什么位置。
067:JavaScript代码放置位置

在本节课中,我们将学习在HTML文档中引入和执行JavaScript代码的不同位置。理解代码的放置位置对于控制代码的执行顺序和确保网页功能正常至关重要。
开发环境设置完成
上一节我们介绍了如何设置开发环境。本节中,我们来看看编写JavaScript代码的几个主要位置。
代码放置位置
以下是几种常见的JavaScript代码引入方式:
1. 在 <head> 标签内的 <script> 标签中
您可以在HTML文档的 <head> 部分直接编写JavaScript代码。代码写在 <script> 开始标签和结束标签之间。
<head>
<script type="text/javascript">
// 您的JavaScript代码写在这里
</script>
</head>
其中 type="text/javascript" 属性在现代HTML中不是必需的,可以简写为 <script>。
2. 通过外部文件引入
您可以将JavaScript代码保存在一个独立的 .js 文件中,然后通过 <script> 标签的 src 属性引入。
<script src="script.js"></script>
请注意,即使是通过 src 属性引入外部文件,<script> 标签仍然需要闭合标签。
代码执行顺序
JavaScript代码的执行是顺序且单线程的。这意味着:
- 顺序执行:代码按照在HTML中出现的顺序,一行一行地执行。
- 单线程:在执行一段代码时,不会被其他地方的JavaScript代码中断。
让我们通过一个例子来理解执行顺序。假设我们有一个外部文件 script.js:
// script.js 文件内容
var x = "hello world";
然后在HTML的 <head> 中引入它,并在后面添加一个内联脚本:
<head>
<script src="script.js"></script>
<script>
console.log(x); // 输出: hello world
</script>
</head>
浏览器会先加载并执行 script.js 文件中的代码(定义变量 x),然后执行紧随其后的内联脚本(打印 x 的值)。因此,控制台会成功输出 “hello world”。
在 <body> 中放置代码
JavaScript代码不仅可以放在 <head> 中,也可以放在 <body> 的任何位置。常见的做法是将 <script> 标签放在 <body> 结束标签之前。
<body>
<!-- 页面内容 -->
<script>
console.log("在body结束前执行");
</script>
</body>
这样放置可以确保在脚本执行时,HTML文档的DOM元素已经加载完成,这对于需要操作页面元素的脚本非常有用。
同样,您也可以在 <body> 中引入外部JavaScript文件:
<body>
<!-- 页面内容 -->
<script src="script.js"></script>
</body>
引入外部文件的效果等同于将该文件中的所有代码“剪切并粘贴”到 <script> 标签所在的位置。
总结
本节课中我们一起学习了JavaScript代码在HTML中的几种放置方式:
- 在
<head>部分的<script>标签内编写。 - 通过
<script src="...">引入外部.js文件。 - 在
<body>部分的任何位置(包括末尾)使用以上两种方式。

关键是要记住代码的顺序执行特性,后引入的代码可以访问先引入代码中定义的变量和函数。根据脚本的功能和依赖关系,合理选择放置位置,是构建稳定Web应用的基础。
068:变量定义、函数与作用域

在本节课中,我们将要学习JavaScript中的三个核心概念:如何定义变量、如何定义函数,以及这些元素在何处“存活”,也就是作用域的概念。
变量定义
上一节我们介绍了课程概述,本节中我们来看看如何定义变量。
在JavaScript中定义变量非常直接,应始终以关键字 var 开始。JavaScript被称为动态类型语言,这意味着JavaScript引擎在运行时确定变量的类型,并且同一个变量在程序执行期间可以持有不同类型的值。例如,一个变量可以初始化为字符串,然后更改为数字,再更改为对象,这些都是完全合法的。
以下是变量定义的示例:
var myVariable;
函数定义
了解了变量定义后,接下来我们看看如何定义函数。
在JavaScript中,定义函数有两种主要方式。第一种是使用 function 关键字,后跟函数名和括号,然后是花括号。第二种方式是创建一个变量,并将其值设为一个函数。
以下是两种定义方式的示例:
// 方式一:函数声明
function functionName() {
// 函数体
}
// 方式二:函数表达式
var functionName = function() {
// 函数体
};
请注意,当使用第二种方式时,function 关键字后没有函数名。但无论哪种语法,你都可以通过名称 functionName 来引用这个函数。这里,函数本身被赋值给了变量,而不是函数执行后的返回结果。
定义函数后,你可以通过在其名称后加括号来调用(或执行)它:
functionName();
函数参数与返回值
现在我们已经知道如何定义和调用函数,本节中我们来了解如何向函数传递参数以及如何返回值。
函数参数在定义时直接写在括号内,无需使用 var 关键字。如果使用 var,会导致语法错误。若希望函数返回一个值,需使用 return 关键字,后跟要返回的值。如果 return 后没有值,则向引擎发出信号,表示应终止函数执行且不返回任何内容。
以下是函数参数与返回值的示例:
function compare(x, y) {
return x > y;
}
var result = compare(4, 5); // 调用函数并存储结果
compare(4, 5); // 合法调用,但不存储结果
compare(4, “5”); // 语法合法(动态类型)
compare(); // 语法合法,所有参数都是可选的
JavaScript函数的参数都是可选的,你可以调用函数时不传递任何参数,这在语法上是合法的。
作用域
理解了函数的基本结构后,我们需要知道变量和函数在何处有效,这就是作用域。
在JavaScript中,主要有两种作用域:全局作用域和函数作用域(也称为词法作用域)。词法作用域意味着作用域取决于代码在物理上的定义位置。例如,在函数内部定义的变量,其作用域就在该函数内部。JavaScript中没有块级作用域(由花括号 {} 创建的新作用域),只有函数会创建新的作用域。
以下是关于作用域的要点:
- 全局作用域中定义的变量和函数在任何地方都可用。
- 函数作用域中定义的变量和函数仅在该函数内部可用。
- 你可以在一个函数内部定义另一个函数。
作用域链
为了理解变量如何被查找,我们必须引入作用域链的概念。
JavaScript引擎在执行代码时,会为每一段代码创建一个“执行上下文”。函数调用会创建一个新的执行上下文。每个执行上下文都有自己的变量环境(用于存储新定义的变量和函数),一个特殊的 this 对象,以及对其外部环境的引用(即定义该函数的那个环境)。全局作用域没有外部环境,因为它是最外层的。
作用域链的工作原理如下:
- 当引用一个变量时,引擎首先在当前作用域(变量环境)中查找。
- 如果未找到,则通过外部环境引用去其外部作用域中查找。
- 此过程会沿着作用域链一直向上,直到全局作用域。
- 如果在全局作用域中也未找到,则该变量为
undefined。
让我们通过一个可视化例子来理解。假设在全局作用域中执行以下代码:
var x = 2; // 全局变量 x
function A() {
var x = 5; // 函数A内的变量 x
B(); // 在函数A内部调用函数B
}
function B() {
console.log(x); // 函数B尝试打印变量 x
}
A(); // 执行函数A
当 B() 执行并尝试打印 x 时,它会:
- 首先在自身作用域(函数B)内查找
x,未找到。 - 然后通过其外部环境引用(即定义它的环境,这里是全局作用域)查找
x。 - 在全局作用域中找到
x,其值为2。
因此,控制台将输出 2。决定变量查找位置的关键,不是函数在哪里被调用,而是函数在物理上被定义在哪里。函数B定义在全局作用域,所以它的外部环境引用指向全局作用域。


本节课中我们一起学习了JavaScript的基础构建块:使用 var 定义变量、使用两种语法定义函数、如何传递参数与返回值,以及最重要的作用域和作用域链概念。理解变量和函数在何处定义及如何被查找,是掌握JavaScript编程的关键一步。
069:变量定义、函数与作用域 🔍

在本节课中,我们将学习JavaScript中变量定义、函数以及作用域的核心概念。我们将通过具体的代码示例,理解变量如何存储、函数如何创建自己的作用域,以及JavaScript引擎如何通过作用域链查找变量。
概述
我们将从在全局作用域中定义一个变量开始,然后探讨在函数内部定义变量会发生什么。接着,我们会观察函数如何创建自己的执行上下文,以及变量如何通过作用域链从外部环境被访问。
全局作用域中的变量定义
首先,我们在全局作用域中定义一个变量 message,并将其值设置为字符串 "in global"。然后,我们使用 console.log 将其输出到控制台。
var message = "in global";
console.log("message = " + message);
保存代码后,浏览器控制台会显示 message = in global,这与我们的定义一致。
这个 message 变量存储在全局执行上下文中。在浏览器环境中,全局执行上下文与 window 对象相关联。因此,message 变量实际上是附加在 window 对象上的一个属性。
console.log(this); // 指向 window 对象
console.log(window.message); // 同样可以访问到 "in global"
函数作用域与变量
上一节我们介绍了全局变量,本节中我们来看看在函数内部定义变量会发生什么。
我们定义一个函数 a,并在其内部声明一个同名的 message 变量。
function a() {
var message = "inside a";
console.log("a: message = " + message);
}
a(); // 调用函数 a
当我们执行函数 a 时,它会创建自己的执行上下文和作用域。函数内部的 message 变量独立于全局的 message 变量。因此,控制台会先输出全局的 in global,然后输出函数 a 内部的 inside a。
嵌套函数与作用域链
现在,我们引入另一个函数 b,并观察其变量查找行为。
最初,我们将函数 b 定义在全局作用域中。它内部没有定义自己的 message 变量,因此会通过作用域链向外查找。
function b() {
console.log("b: message = " + message); // 引用外部的 message
}
a(); // 先调用 a
b(); // 再调用 b
由于 b 的外层引用是全局作用域,所以它找到的 message 值是 "in global"。
接下来,我们将函数 b 的定义移到函数 a 的内部。
function a() {
var message = "inside a";
console.log("a: message = " + message);
function b() {
console.log("b: message = " + message); // 现在引用 a 作用域中的 message
}
b(); // 在 a 内部调用 b
}
a();
此时,函数 b 的外层引用变成了函数 a 的作用域。因此,当 b 尝试访问 message 变量时,它找到的是 a 作用域中的 "inside a",而不是全局的 "in global"。这演示了词法作用域(或函数作用域)的工作方式:函数在定义时(而非调用时)就确定了其可访问的外部变量环境。
核心概念总结

本节课中我们一起学习了以下核心概念:

-
变量定义:使用
var关键字定义变量。JavaScript 是动态类型语言,变量类型在运行时确定。var x = 10; // 数字var name = "Alice"; // 字符串
-
执行上下文:所有JavaScript代码都在某个执行上下文中运行。每个执行上下文都有自己的变量环境。
-
函数与作用域:当函数执行时,它会创建自己的执行上下文和作用域。在函数内部定义的变量,其作用域仅限于该函数(函数作用域)。
-
作用域链:当在当前执行上下文中找不到某个变量时,JavaScript引擎会沿着作用域链向外层查找,直到全局作用域。这个链由函数定义时的位置决定。
// 作用域链示例:b -> a -> global var globalVar = "I'm global"; function a() { var outerVar = "I'm in a"; function b() { console.log(outerVar); // 在 a 的作用域中找到 console.log(globalVar); // 在全局作用域中找到 } b(); } a();
总结


在本节课中,我们通过实践探索了JavaScript的变量定义、函数以及作用域机制。我们了解到,函数会创建独立的作用域,变量访问遵循词法作用域规则,并通过作用域链进行解析。理解这些概念是掌握JavaScript编程的基础。
070:JavaScript 内置类型

在本节课中,我们将要学习 JavaScript 的内置类型系统。理解类型是理解任何编程语言的基础,JavaScript 也不例外。我们将探讨什么是类型,JavaScript 提供了哪些内置类型,以及它们各自的特点。
什么是类型?
类型是一种特定的数据结构。每种编程语言都定义了一些内置类型,JavaScript 也是如此。这些内置类型可以用来构建更复杂的数据结构,以满足特定的业务逻辑需求。
JavaScript 的内置类型
JavaScript 有七种内置类型:六种原始类型和一种对象类型。
对象类型
在 JavaScript 中,对象本质上是一个名值对的集合。它可以包含零个或多个名值对。让我们通过一个简单的例子来理解。
let person = {
firstName: "Yaov",
lastName: "hikeken",
social: {
twitter: "@yaov",
facebook: "yaov.hikeken"
}
};
在上面的例子中,person 是一个对象。firstName 是属性名,"Yaov" 是其对应的值。值也可以是另一个对象,例如 social 属性的值就是一个嵌套的对象。
原始类型
原始类型代表一个单一的、不可变的值。这意味着它不是一个对象(对象是名值对的集合),并且一旦其值被设定,就无法被改变。如果你基于一个原始值创建了新值,JavaScript 引擎会分配新的内存空间,而不是修改原有的值。
以下是 JavaScript 定义的六种原始类型:
1. 布尔值
布尔类型只有两个可能的值:true 或 false。这两个都是 JavaScript 的保留关键字。
2. 未定义
undefined 类型表示一个变量已被声明但从未被赋值。这是 JavaScript 引擎为变量分配内存空间后的默认值。虽然你可以手动将一个变量设置为 undefined,但强烈不建议这样做,因为这与其“未定义”的核心含义相悖,会造成代码逻辑混乱。
3. 空值
null 类型表示“没有值”。它与 undefined 不同,undefined 表示“未定义”。null 也是一个保留关键字,在代码中显式地将变量设置为 null 是完全正常且合理的。
4. 数字
number 是 JavaScript 中唯一的数值类型。它在底层始终以双精度 64 位浮点数的形式表示。这意味着 JavaScript 没有独立的整数类型,整数只是浮点数的一个子集。
5. 字符串
string 类型用于表示文本,它是一个字符序列。在 JavaScript 中,你可以使用单引号或双引号来定义字符串,两者都是合法的。
let greeting1 = "Hello";
let greeting2 = 'World';
6. 符号
symbol 是 ES6(ECMAScript 2015)中引入的新原始类型。由于它相对较新,尚未被广泛支持和使用,因此在本课程中我们将不深入讨论它。
总结


本节课我们一起学习了 JavaScript 的内置类型系统。我们了解了类型是数据结构的基础,并详细探讨了 JavaScript 的七种内置类型:六种原始类型(布尔值、未定义、空值、数字、字符串、符号)和一种对象类型。理解这些类型的特性和区别,对于编写正确、高效的 JavaScript 代码至关重要。在接下来的课程中,我们将看到这些类型在实际代码中的应用。
071:理解 Undefined 与变量声明
在本节课中,我们将学习 JavaScript 类型系统中的一个核心概念:undefined。我们将通过代码示例,理解变量声明、内存分配以及 undefined 与“未定义”错误的区别。
概述
JavaScript 定义了七种内置类型:一种对象类型和六种原始类型。对象类型是名值对的集合。原始类型则只能包含单一的、不可变的值。undefined 是原始类型中的一种特殊状态,表示变量已被声明但尚未被赋值。理解这一点对于避免常见的引用错误至关重要。
变量声明与 Undefined
上一节我们概述了 JavaScript 的类型。本节中,我们来看看 undefined 的具体表现。
首先,我们在代码编辑器中打开 script.js 文件。我们定义一个变量 x 但不为其赋值,然后将其打印到控制台。

var x;
console.log(x);
保存文件后,控制台输出的 x 的值是 undefined。这是因为变量 x 已经被声明(内存空间已分配),但从未被设置为任何值。
测试 Undefined 状态
我们可以直接使用 undefined 这个保留关键字来测试变量是否处于未赋值状态。
以下是测试 undefined 的代码示例:
var x;
if (x === undefined) {
console.log('x is undefined');
} else {
console.log('x has been defined');
}
保存并运行代码,控制台会输出 “x is undefined”。这说明 x 确实处于 undefined 状态。
赋值后的变化
现在,我们为变量 x 赋值,然后再次进行测试。
var x = 5;
if (x === undefined) {
console.log('x is undefined');
} else {
console.log('x has been defined');
}
保存后,控制台会输出 “x has been defined”。因为 x 已经被显式地赋值为 5,不再等于 undefined。
Undefined 与 “未定义” 错误的区别
这是一个关键概念。undefined 和 “变量未定义” 是两回事。
如果我们注释掉变量的声明语句,然后尝试打印它:
// var x;
console.log(x);
保存后,浏览器控制台会抛出一个错误:Uncaught ReferenceError: x is not defined。
这个错误信息容易让人困惑。它不是说 x 是 undefined,而是说 x 根本没有被声明。在 JavaScript 中:
undefined:变量已被声明,内存已分配,但未赋值。not defined(引用错误):变量从未被声明,尝试使用一个不存在的标识符。
核心概念总结
为了清晰理解,以下是本节课的核心要点:
- 原始类型的不可变性:原始类型的值一旦创建就不可改变。你可以基于它创建新值,但不能改变原始值本身。
undefined的含义:它是一个原始类型值,特指“变量已声明但未赋值”的状态。可以用===进行测试。- 引用错误:当尝试使用一个从未声明过的变量时,JavaScript 引擎会抛出
ReferenceError,阻止代码继续执行。
总结


本节课中我们一起学习了 JavaScript 类型系统中的 undefined。我们明确了变量声明与赋值的区别,理解了 undefined 是一种特殊的、已声明但未赋值的状态,并且将其与导致程序错误的“变量未定义”情况区分开来。掌握这些概念是编写健壮 JavaScript 代码的基础。
072:常见语言结构

在本节课中,我们将学习JavaScript中的一些基础但核心的语言结构。这些结构是几乎所有编程语言共有的,我们将重点了解它们在JavaScript中的具体实现方式。我们将涵盖字符串拼接、数学运算符、变量相等性比较等主题。
字符串拼接
就像许多其他语言一样,JavaScript允许你动态地拼接字符串,其操作方式类似于算术运算。


以下是字符串拼接的示例:
var string = "Hello";
string += " World";
console.log(string + "!");
在这段代码中,我们首先声明了一个值为“Hello”的字符串变量。下一行使用 += 运算符将字符串与另一个包含空格和“World”的字符串拼接起来。+= 运算符等同于 string = string + " World"。最后,我们使用 console.log 输出拼接后的字符串并加上感叹号。
数学运算符
JavaScript中的数学运算符非常直观,如果你在其他语言中进行过数学运算,这里几乎没有区别。
以下是数学运算符的示例:
var result = (5 + 4) / 3;
console.log(result); // 输出 3
这里使用了加号 +、除号 / 和括号。括号的优先级规则在此同样适用,(5 + 4) 会先于除以 3 执行,因此结果是 3。
一个需要注意的情况是,如果尝试用 undefined 进行数学运算:
var a;
console.log(a / 5); // 输出 NaN
NaN 是一个特殊值,表示“不是一个数字”。在调试代码时,如果函数内部出现 NaN,通常意味着某个应该传入的参数未被传递,或者在传递过程中出现了问题,导致函数内部变量为 undefined。
变量与相等性
上一节我们介绍了基本的数学运算,本节中我们来看看变量的声明和比较。
在JavaScript中,可以使用 var 关键字声明变量,并且可以在一条语句中声明多个变量。
以下是变量声明和相等性比较的示例:
var x = 4, y = 4;
if (x == y) {
console.log("x is equal to y");
}
这里,我们使用 var 同时声明了 x 和 y 两个变量。在 if 语句中,我们使用双等号 == 来比较 x 和 y 的值。需要注意的是,一个常见的错误是误用单等号 =,它表示赋值操作,而非比较。
类型转换与严格相等
当我们比较不同类型的值时,JavaScript会进行“类型强制转换”。
考虑以下情况:
var x = "4", y = 4;
if (x == y) {
console.log("x equals to 4 as a string is equal to y equals to 4");
}
尽管 x 是字符串 "4",y 是数字 4,但使用 == 比较时,JavaScript会自动将其中一个类型转换为另一个类型,然后比较值,因此结果为真。这种行为称为“类型强制转换”。
如果你不希望JavaScript自动进行类型转换,可以使用“严格相等”运算符 ===。
以下是严格相等的示例:
var x = "4", y = 4;
if (x === y) {
console.log("This will not print.");
} else {
console.log("Strict: x as a string 4 is not equal to y equals 4");
}
三重等号 === 在进行比较时,会首先检查两个值的类型是否相同。如果类型不同,它会直接返回 false,而不会尝试转换类型。因此,字符串 "4" 和数字 4 严格不相等,代码会执行 else 分支。


本节课中我们一起学习了JavaScript的几个基础语言结构:字符串的拼接、数学运算符的使用、变量的声明与相等性比较,以及类型强制转换与严格相等的区别。理解这些概念是编写正确JavaScript代码的基石。在下一部分,我们将继续探讨JavaScript中真值与假值的概念。
073:布尔值与真值/假值

在本节课中,我们将学习 JavaScript 中布尔运算的核心概念,特别是哪些值会被强制转换为 true(真值),哪些会被强制转换为 false(假值)。理解这一点对于编写条件判断和控制程序流程至关重要。
在上一节中,我们讨论了严格相等与普通相等的区别。本节中,我们来看看 JavaScript 如何评估布尔表达式中的真与假。
与所有编程语言一样,JavaScript 在评估布尔表达式时,有一组特定的值会被视为 false,其余则被视为 true。让我们先注释掉之前的代码,进入讨论 if 语句的部分。实际上,我们将列出所有会被 if 语句视为 false 的值,然后再列出被视为 true 的值。
以下是所有会被 if 语句视为 false 的值:
false:布尔值false关键字本身。null:null值在 JavaScript 中会被强制转换为false。undefined:undefined关键字同样会被强制转换为false。- 空字符串:即中间没有任何内容的双引号
""。 - 数字
0:数字零会被强制转换为false。 NaN:表示“非数字”的值。
以上所有值都被视为 false。这意味着,在一个条件判断中,如果遇到这些值,条件将不会通过。
现在,让我们看一个实用技巧。如果你不确定某个值会被强制转换为什么,JavaScript 为原始类型提供了包装对象。对于布尔类型,有一个包装对象叫 Boolean(首字母大写)。你可以将任何值传入 Boolean() 来查看其强制转换结果。
例如:
console.log(Boolean(null)); // 输出:false
console.log(Boolean("")); // 输出:false
console.log(Boolean("Hello world")); // 输出:true
正如最后一行所示,任何不在上述假值列表中的值,通常都会被评估为 true。这自然引出了下一个话题:哪些值在 JavaScript 中被视为 true。
以下是会被视为 true 的值:
true:布尔值true关键字本身。- 非空字符串:例如
"Hello"。 - 任何非零数字:包括正数、负数,例如
1、-1、3.14等。 - 注意:即使字符串的内容是
"false",因为它是一个非空字符串,所以它仍然会被评估为true。
理解真值与假值列表,是掌握 JavaScript 条件逻辑的基础。
本节课中,我们一起学习了 JavaScript 中的真值与假值。我们明确了会被强制转换为 false 的六种情况,并了解到其他大多数值(包括非空字符串、非零数字等)都会被评估为 true。掌握这些概念,能帮助你更准确地编写条件判断语句。


在下一部分课程中,我们将探讨在 JavaScript 中,大括号 {} 是放在同一行还是下一行,这仅仅是一个代码风格问题,还是一个公认的最佳实践。
074:第43讲 第3部分 常见语言结构 🧱
在本节课中,我们将学习 JavaScript 中几个核心的语言结构,包括大括号的放置规则、分号的使用以及 for 循环的语法。理解这些概念对于编写正确且高效的 JavaScript 代码至关重要。
大括号放置的最佳实践 🔧

上一节我们介绍了表达式,本节中我们来看看代码格式中的一个重要细节:大括号的放置位置。关于大括号是应该放在同一行还是下一行,开发者们常有不同意见。但在 JavaScript 中,这不仅仅是一个风格问题,更关系到语法和引擎的解析方式。
让我们通过一个例子来演示。我们有两个函数,functionA 和 functionB,它们都返回一个对象字面量。区别在于大括号的放置位置。
function functionA() {
return
{
name: "Yaov"
};
}
function functionB() {
return {
name: "Yaov"
};
}
console.log(functionA()); // 输出:undefined
console.log(functionB()); // 输出:{ name: 'Yaov' }
你预料到这个结果了吗?functionA 返回了 undefined。这是因为 JavaScript 引擎在解析代码时,会在 return 语句后自动插入一个分号。因此,functionA 实际上等价于:
function functionA() {
return; // 引擎在此处插入了分号
{
name: "Yaov"
};
}
这个例子清晰地表明,在 JavaScript 中,将左大括号 { 放在与声明语句(如 function、return)同一行是一种最佳实践,这能避免因自动分号插入(ASI)机制导致的意外错误。
分号的使用 💬
虽然 JavaScript 允许在语句末尾省略分号,但普遍接受的最佳实践是始终加上分号。这可以确保代码行为的一致性,并避免潜在的解析错误。尽管存在一些争议,但遵循大多数人的做法是明智的选择。
For 循环 🔁
任何编程语言都离不开循环结构。接下来,我们来看看 JavaScript 中基础的 for 循环语法。
以下是一个简单的 for 循环,用于计算 0 到 9 的和:
let sum = 0;
for (let i = 0; i < 10; i++) {
console.log(`当前 i 的值是: ${i}`);
sum = sum + i;
}
console.log(`0 到 9 的和是: ${sum}`); // 输出:45
一个 for 循环包含三个部分:
- 初始化:在循环开始前执行一次,通常用于初始化计数器变量(如
let i = 0)。 - 条件判断:在每次循环迭代前检查。如果条件为
true,则执行循环体;如果为false,则退出循环(如i < 10)。 - 最终表达式:在每次循环体执行完毕后执行,通常用于更新计数器变量(如
i++,这是i = i + 1的简写形式)。
循环的执行顺序是:初始化 -> 检查条件 -> 执行循环体 -> 执行最终表达式 -> 再次检查条件,如此往复。
课程总结 📝
本节课中我们一起学习了 JavaScript 的几个重要概念:
- 大括号放置:左大括号
{应放在与函数声明、return语句等同一行,这是避免自动分号插入导致错误的最佳实践。 - 分号使用:尽管可选,但在每个语句末尾添加分号是推荐的做法,以确保代码清晰和稳定。
- For 循环:掌握了
for循环的基本语法结构,包括初始化、条件判断和计数器更新三个部分。


理解并应用这些基础语言结构,是编写可靠 JavaScript 代码的第一步。
075:默认值处理 🍗

在本节课中,我们将学习一个在编码时非常常见的场景:如何处理函数参数的默认值。当调用函数时未提供某个参数,该参数的值会变成 undefined,这可能导致不理想的输出。我们将探讨如何为参数设置默认值,以避免这种情况。
问题引入
假设我们有一个函数 orderChickenWith,它接收一个参数 sideDish(配菜),并在控制台打印出“Chicken with [配菜]”。
function orderChickenWith(sideDish) {
console.log("Chicken with " + sideDish);
}
当我们调用 orderChickenWith("noodles") 时,控制台会正确输出“Chicken with noodles”。
然而,如果我们调用函数时不传递任何参数,例如 orderChickenWith(),那么 sideDish 的值将是 undefined。JavaScript 会将 undefined 强制转换为字符串,导致输出“Chicken with undefined”。这显然不是我们想要展示给用户的结果。
解决方案一:使用 if 语句
为了解决这个问题,我们可以使用 if 语句来检查参数是否为 undefined,如果是,则为其赋予一个默认值。
以下是具体步骤:
- 在函数内部,检查
sideDish是否严格等于undefined。 - 如果条件为真,则将
sideDish的值设置为一个默认字符串(例如“whatever”)。
function orderChickenWith(sideDish) {
if (sideDish === undefined) {
sideDish = "whatever";
}
console.log("Chicken with " + sideDish);
}
现在,再次调用 orderChickenWith(),输出将变为“Chicken with whatever”。这种方法虽然有效,但代码显得有些冗长。
解决方案二:使用逻辑或 (||) 运算符
JavaScript 提供了一种更简洁的方式来设置默认值,即使用逻辑或 (||) 运算符。
其工作原理如下:
- 逻辑或运算符会从左到右依次评估其两边的操作数。
- 它会返回第一个能被强制转换为
true(真值)的操作数的原始值。 - 如果所有操作数都被评估为
false(假值),则返回最后一个操作数的值。
在 JavaScript 中,undefined 是一个假值。因此,当 sideDish 为 undefined 时,表达式 sideDish || “whatever” 会跳过 sideDish(因为它是假值),并返回字符串 “whatever”。
我们可以这样重写函数:
function orderChickenWith(sideDish) {
sideDish = sideDish || "whatever";
console.log("Chicken with " + sideDish);
}
这段代码与之前的 if 语句功能完全相同,但更加简洁。这是 JavaScript 中一种非常常见且高效的模式,你会在许多专业的代码库(如 jQuery)中看到它。
逻辑或运算符的评估机制
为了更好地理解 || 运算符,让我们看几个简单的例子:
true || false返回true。“hello” || “”返回“hello”,因为非空字符串“hello”是真值。“” || “world”返回“world”,因为空字符串“”是假值,所以运算符继续评估并返回下一个真值“world”。
关键在于,|| 运算符一旦找到第一个真值,就会立即返回该值,并停止评估后续的操作数。这种“短路求值”特性使其非常适合用于设置默认值。
总结


本节课中,我们一起学习了如何为函数参数设置默认值。我们首先遇到了当参数为 undefined 时可能产生的问题,然后通过 if 语句解决了它。接着,我们介绍了一种更简洁、更专业的模式——使用逻辑或 (||) 运算符。这种模式利用了 JavaScript 的类型强制转换和短路求值特性,是代码中处理默认值的常用技巧。理解这个模式将帮助你更流畅地阅读和编写 JavaScript 代码。
076:10_第45讲 第1部分 使用new语法创建对象 🏗️

概述
在本节课中,我们将要学习如何在JavaScript中创建对象。我们将重点介绍使用 new Object() 语法创建对象,并探讨如何为对象添加和访问属性。理解对象是掌握JavaScript的关键一步。
对象类型简介
在之前的课程中,我们提到JavaScript有一种非常特殊的类型,那就是对象类型。一个对象是名值对的集合。在本节中,我们将回顾以不同方式创建对象的方法,以及如何分配属性,也就是如何创建那些名值对。
使用 new Object() 创建对象
现在,我们来看一个非常简单的对象创建示例。我们创建一个名为 company 的变量,并将其设置为 new Object()。
var company = new Object();
一旦我们创建了一个对象的引用,就可以开始为该对象创建属性了。
为对象添加属性
在JavaScript中,当一个属性尚不存在时,你只需在对象中提到该属性,JavaScript引擎就会为你创建它。
例如,我们写下 company.name。此时 name 这个属性还不存在。
company.name;
但是,一旦我为其赋值或仅仅是在对象上提及它,它就被创建了。
company.name = "Facebook";
现在它确实存在了。如果我执行 console.log(company),就会看到 company 对象现在有了一个值为 "Facebook" 的 name 属性。
创建嵌套对象属性
我可以继续添加属性。例如,我想添加一个 CEO 属性,并且希望 CEO 本身也是一个包含多个属性的对象。
首先,我尝试这样做:
company.CEO.firstName = "Mark";
然后执行 console.log(company) 来查看结果。我们会得到一个错误:Cannot set property 'firstName' of undefined。
为什么这行不通?原因是 company.CEO 这个属性还没有被定义。这就像我声明了 var a,然后尝试执行 a.hello = "world" 一样,a 是 undefined,无法设置其属性。
要解决这个问题,我必须先创建 company.CEO 属性本身。
company.CEO = new Object();
company.CEO.firstName = "Mark";
现在,我可以成功设置 firstName 属性了。让我们再为CEO添加一个属性:
company.CEO.favoriteColor = "blue";
现在,CEO 对象就拥有了 firstName 和 favoriteColor 两个属性。
访问对象属性:点表示法
就像我使用点表示法设置属性一样,我当然也可以用点表示法来获取属性。
例如:
console.log("Company CEO name is " + company.CEO.firstName);
JavaScript引擎会遍历 company 对象,找到 CEO 属性(它本身也是一个对象),然后从中检索出 firstName 属性的值。
访问对象属性:括号表示法
点表示法并不是访问属性的唯一方式。我还可以使用括号表示法。
console.log(company["name"]);
这里,属性名 "name" 需要放在引号中,因为它是一个字符串。这样也能成功输出 "Facebook"。
那么,为什么有两种表示法呢?我不能总是使用点表示法吗?原因是点表示法只适用于有效的JavaScript标识符(即有效的变量名或函数名)。
例如,我想添加一个属性叫 "stock of company",中间包含空格。
company["stock of company"] = 110;
我无法使用 company.stock of company 这样的点表示法,因为它包含空格,不是有效的标识符。但是,使用括号表示法就可以成功设置。
现在,我可以通过括号表示法来获取这个属性的值:
console.log("Stock price is " + company["stock of company"]);
注意,括号内的内容不一定非得是字符串字面量,它也可以是一个变量。这让我可以在代码中动态计算属性名。
var stockPropName = "stock of company";
console.log("Stock price is " + company[stockPropName]);


本节小结
本节课中,我们一起学习了使用 new Object() 语法创建JavaScript对象的基础知识。我们掌握了如何通过点表示法和括号表示法为对象添加及访问属性,并理解了括号表示法在处理动态或包含特殊字符的属性名时的必要性。这是一种创建对象的方式,但代码中遍布 new Object() 可能会显得冗长。在下一部分课程中,我们将探讨一种更简洁、更易读的语法来实现同样的功能。
077:第2部分 使用对象字面量语法创建对象 🏗️

在本节课中,我们将学习如何使用对象字面量语法来创建JavaScript对象。这是一种比使用 new Object() 更简洁、更易读的方法。
概述
上一节我们介绍了如何使用 new Object() 语法创建对象并设置其属性。本节中,我们来看看如何使用对象字面量语法来简化这一过程。
对象字面量语法
对象字面量语法允许我们直接使用花括号 {} 来定义对象及其属性,无需调用 new Object()。
以下是创建对象的基本结构:
var objectName = {
property1: value1,
property2: value2
};
创建对象示例
让我们创建一个名为 Facebook 的对象,其中包含公司名称、CEO信息和股价。
var Facebook = {
name: "Facebook",
CEO: {
firstName: "Mark",
favoriteColor: "blue"
},
stock: 110
};
在这个例子中:
name是一个属性,其值为字符串 "Facebook"。CEO是一个属性,其值本身是另一个对象(嵌套对象)。stock是一个属性,其值为数字 110。
访问对象属性
创建对象后,我们可以像之前一样访问其属性:
console.log(Facebook.CEO.firstName); // 输出: Mark
使用字符串作为属性名
对象字面量的一个优势是,可以使用包含空格的字符串作为属性名,这在点表示法中是无法直接设置的。
var Facebook = {
name: "Facebook",
CEO: {
firstName: "Mark",
favoriteColor: "blue"
},
"stock of company": 110 // 使用字符串作为属性名
};
要访问此类属性,需要使用方括号表示法:
console.log(Facebook["stock of company"]); // 输出: 110
编码技巧
在编写对象字面量时,一个实用的技巧是先写好结束符号(如右花括号 } 和分号 ;),然后再填充内容。这有助于避免语法错误,并使代码结构更清晰。
例如,可以这样开始:
var company = {
// 光标在这里开始添加属性
};
然后逐步添加属性:
var company = {
CEO: {
// 子对象属性
}, // 注意这里的逗号
"stock of company": 110
};
总结
本节课中我们一起学习了:
- 对象字面量语法:使用
{}直接创建对象,这是一种更简洁、更常用的方法。 - 定义属性:在花括号内使用
属性名: 值的格式定义属性,不同属性间用逗号分隔。 - 嵌套对象:属性值可以是另一个对象,从而创建复杂的数据结构。
- 特殊属性名:可以使用引号包裹的字符串作为属性名,以包含特殊字符或空格。
- 访问属性:创建的对象与之前用
new Object()创建的对象在属性访问方式上完全一致。


对象字面量是JavaScript中最常见和推荐的对象创建方式,因为它书写快捷,结构一目了然。
078:函数详解

概述
在本节课中,我们将要学习 JavaScript 中函数的本质。我们将了解到,函数在 JavaScript 中是一种“一等公民”数据类型,这意味着它们可以像变量和对象一样被传递、赋值和操作。通过理解函数的对象特性,我们可以实现更高级的编程模式,例如函数工厂和将函数作为参数传递。
函数是对象
上一节我们介绍了对象的基本概念,本节中我们来看看 JavaScript 中函数的真实身份。在 JavaScript 中,函数实际上是一种特殊的对象。
function multiply(x, y) {
return x * y;
}
这意味着,你可以对函数做任何可以对对象做的事情,例如为函数设置属性。
multiply.version = "v1.0.0";
console.log(multiply.version); // 输出:v1.0.0
每个对象都有一个内置的 toString 方法,函数也不例外。当我们直接输出函数时,实际上调用的是它的 toString 方法。
console.log(multiply); // 输出函数本身的代码
函数工厂
既然函数是对象,我们可以创建返回函数的函数,这被称为“函数工厂”。
以下是创建一个函数工厂的步骤:
- 定义一个名为
makeMultiplier的函数,它接收一个参数multiplier。 - 在
makeMultiplier函数内部,定义并返回一个新的函数。 - 这个新函数接收一个参数
x,并返回multiplier * x的结果。
function makeMultiplier(multiplier) {
var myFunc = function (x) {
return multiplier * x;
};
return myFunc;
}
现在,我们可以使用这个工厂来创建特定的乘法函数。
var multiplyBy3 = makeMultiplier(3);
console.log(multiplyBy3(10)); // 输出:30
var doubleAll = makeMultiplier(2);
console.log(doubleAll(100)); // 输出:200
将函数作为参数传递
函数作为“一等公民”,可以被传递给其他函数作为参数。这极大地增加了代码的灵活性。
让我们创建一个名为 doOperationOn 的函数,它接收一个数值 x 和一个函数 operation 作为参数。
function doOperationOn(x, operation) {
return operation(x);
}
现在,我们可以将之前创建的 multiplyBy3 和 doubleAll 函数传递给 doOperationOn。
var result = doOperationOn(5, multiplyBy3);
console.log(result); // 输出:15
result = doOperationOn(100, doubleAll);
console.log(result); // 输出:200
请注意,我们传递的是函数引用(multiplyBy3),而不是函数调用的结果(multiplyBy3())。这样,doOperationOn 函数内部才能够在合适的时机执行传入的操作。

总结
本节课中我们一起学习了 JavaScript 函数的核心特性。我们了解到函数是“一等公民”数据类型,本质上是特殊的对象。因此,我们可以为函数设置属性,可以将函数作为值从另一个函数中返回(创建函数工厂),也可以将函数作为参数传递给其他函数。正是这些特性,使得 JavaScript 如此强大和灵活。掌握这些概念是理解高阶函数、回调函数和函数式编程的基础。
079:传值 vs 传址 📚

在本节课中,我们将要学习JavaScript中一个至关重要的概念:传值与传址。理解这两者的区别对于掌握变量赋值、函数参数传递以及对象操作至关重要。
概述
首先,我们来明确一下这两个术语的含义。当我们谈论“传值”或“传址”时,通常指的是在赋值或传递变量时,数据是如何被处理的。
- 传值:将变量
a的内容复制给变量b(即b = a)。这意味着,之后改变b的值不会影响a的值,反之亦然。它们是两个独立的副本。 - 传址:同样是将变量
a的内容复制给变量b。但此时,改变b的值会影响a的值,反之亦然。因为它们实际上指向内存中的同一个位置。
在JavaScript中,规则可以概括为:基本类型通过传值操作,而对象通过传址操作。更准确地说,基本类型是按值复制,对象是按引用复制。其根本原因在于它们是如何在内存中存储的。
基本类型:传值操作 🔢
让我们通过一个具体的例子来理解基本类型的传值过程。
假设我们有以下代码:
let a = 7;
let b = a; // 将 a 的值复制给 b
b = 5; // 修改 b 的值
console.log(a); // 输出:7
console.log(b); // 输出:5
在这个例子中,a的值是7,b的值也是7。当我们把b改为5后,a的值仍然是7。这说明a和b是相互独立的。
为了更直观地理解,我们可以看看内存中发生了什么:
- 声明
let a = 7;:内存中分配一个位置(例如地址001),并存入值7。 - 执行
let b = a;:内存中为b分配一个新位置(例如地址002),并将a的值7复制到这个新位置。 - 执行
b = 5;:将地址002中的值从7替换为5。
此时,地址001(a)的值依然是7,地址002(b)的值是5。a和b互不影响。这种机制就是传值。
对象:传址操作 🏗️
上一节我们介绍了基本类型的传值机制,本节中我们来看看对象是如何通过传址进行操作的。
考虑以下代码:
let a = { x: 7 };
let b = a; // 将 a 的“引用”复制给 b
b.x = 5; // 通过 b 修改对象属性
console.log(a.x); // 输出:5
console.log(b.x); // 输出:5
在这个例子中,a是一个对象。当我们把a赋值给b后,通过b修改了属性x,结果a.x的值也变成了5。这说明a和b操作的是同一个对象。
让我们深入内存层面看看这个过程:
- 声明
let a = { x: 7 };:- 为变量
a分配一个内存地址(例如001)。 - 对象
{ x: 7 }本身被存储在内存的另一个位置(例如003)。 - 地址
001中存储的值,实际上是对象所在地址003的引用(可以理解为一个指向003的指针)。
- 为变量
- 执行
let b = a;:- 为变量
b分配一个新地址(例如002)。 - 将
a的值(即地址003的引用)复制到地址002中。 - 现在,
a(地址001)和b(地址002)存储着相同的值——都是指向地址003的引用。
- 为变量
- 执行
b.x = 5;:- 通过
b找到它指向的地址003。 - 修改地址
003中对象属性的值(将x从7改为5)。
- 通过
由于a和b都指向同一个内存地址(003),所以通过任何一个变量去修改这个地址里的内容,另一个变量“看到”的内容也会随之改变。这种机制就是传址。
核心区别总结
以下是传值与传址的核心区别对比:
- 传值:复制的是数据的实际值。创建的是原始数据的一个完全独立的副本。修改副本不影响原始数据。
- 传址:复制的是数据的内存地址引用。创建的是指向同一块内存空间的另一个“别名”。通过任何一个“别名”修改数据,都会影响所有指向该数据的变量。
本节课总结
本节课中我们一起学习了JavaScript中传值与传址的核心概念。我们了解到:
- 基本类型(如数字、字符串、布尔值等)是通过传值进行复制和传递的。操作的是数据的独立副本。
- 对象(包括数组、函数等)是通过传址进行复制和传递的。操作的是对同一内存位置的引用。

理解这一区别,能帮助我们避免在编程中产生意想不到的副作用,例如无意中修改了本不想改变的对象。在下一部分,我们将进入代码编辑器,通过实际例子来观察这些概念是如何运作的。
080:传值 vs 传址

在本节课中,我们将通过具体的代码示例,深入理解JavaScript中“传值”与“传址”这两个核心概念。我们将看到原始类型和对象类型在复制和传递时的不同行为。
概述
理解“传值”与“传址”是掌握JavaScript编程的基础。简单来说,原始类型(如数字、字符串)通过“传值”方式操作,而对象类型(如对象、数组)则通过“传址”方式操作。本节将通过代码演示这两种方式的区别。
传值示例
首先,我们来看一个原始类型“传值”的例子。当我们复制一个原始类型的变量时,新变量会获得原始值的一个独立副本。
let a = 7;
let b = a; // 此时 b 是 a 值的一个独立副本
console.log(a); // 输出:7
console.log(b); // 输出:7
b = 5; // 修改 b 的值
console.log(a); // 输出:7 (a 的值未受影响)
console.log(b); // 输出:5
在上面的例子中,b 最初获得了 a 的值(7)。当我们将 b 修改为 5 时,a 的值保持不变,因为 a 和 b 指向内存中两个独立的存储位置。
传址示例
接下来,我们看看对象类型“传址”的例子。当我们复制一个对象变量时,新变量实际上获得的是对同一内存地址的引用,而不是一个独立的对象副本。
let a = { x: 7 };
let b = a; // b 现在指向 a 所指向的同一个对象
console.log(a); // 输出:{ x: 7 }
console.log(b); // 输出:{ x: 7 }
b.x = 5; // 通过 b 修改对象的属性
console.log(a); // 输出:{ x: 5 } (a 指向的对象也被修改了)
console.log(b); // 输出:{ x: 5 }
在这个例子中,a 和 b 都指向内存中的同一个对象。因此,通过 b 修改对象的 x 属性后,通过 a 访问该属性也会看到变化。
函数参数传递
“传值”和“传址”的概念在函数参数传递时同样适用。将变量作为参数传递给函数,本质上也是一种复制操作。
向函数传递原始类型(传值)
当向函数传递一个原始类型的值时,函数内部得到的是该值的一个独立副本。
function changePrimitive(primValue) {
console.log('函数内部,修改前:', primValue);
primValue = 5; // 修改函数内的局部变量
console.log('函数内部,修改后:', primValue);
}
let value = 7;
changePrimitive(value);
console.log('函数外部,原始值:', value); // 输出:7 (原始值未变)
函数 changePrimitive 接收了 value 的一个副本。在函数内部修改 primValue 不会影响外部的 value 变量。
向函数传递对象类型(传址)
当向函数传递一个对象时,函数内部得到的是对该对象的引用。因此,在函数内部修改对象会影响原始对象。
function changeObject(objValue) {
console.log('函数内部,修改前:', objValue);
objValue.x = 5; // 修改传入对象的属性
console.log('函数内部,修改后:', objValue);
}
let value = { x: 7 };
changeObject(value);
console.log('函数外部,原始对象:', value); // 输出:{ x: 5 } (原始对象被修改)
函数 changeObject 接收了 value 对象的引用。在函数内部修改 objValue.x 实际上修改了 value 所指向的同一个对象。
核心规则总结

以下是需要牢记的核心规则:
- 原始类型(如
Number,String,Boolean,null,undefined,Symbol)在赋值或作为参数传递时,采用的是 传值 方式。 - 对象类型(如
Object,Array,Function)在赋值或作为参数传递时,采用的是 传址 方式。
本节课总结


本节课我们一起学习了JavaScript中“传值”与“传址”的关键区别。我们通过代码示例明确了:原始类型的操作互不影响,因为它们拥有独立的数据副本;而对象类型的变量可能指向同一个内存地址,修改其中一个会影响所有指向该地址的变量。深刻理解这一概念对于避免程序中的意外错误至关重要。
081:函数构造器、原型与this关键字 🏗️

在本节课中,我们将学习在 JavaScript 中创建对象的另一种非常常见的方法。这种方法特别适用于需要将功能与对象封装在一起的情况,类似于 Java 中的类。我们将重点探讨函数构造器、prototype 属性以及 this 关键字的工作原理。
函数执行与 this 关键字
上一节我们提到了对象创建的不同方式。在深入新的方法之前,我们先来探讨一个特定的主题:函数执行时 this 关键字的行为。
我们创建一个简单的函数:
function test() {
console.log("Hello Coursera");
}
test();
当我们调用一个函数时,JavaScript 引擎会创建一个新的执行上下文。这个执行上下文包含一个特殊的变量 this。让我们看看在普通函数调用中,this 指向什么:
function test() {
console.log(this);
}
test();
执行上述代码,你会发现 this 指向全局的 window 对象。这意味着,在函数内部,我们可以通过 this 为全局对象添加属性:
function test() {
this.myName = "Yaakov";
console.log(window.myName); // 输出 "Yaakov"
}
test();
然而,如果我们希望将数据封装在特定对象内部,并让 this 指向那个新对象,就需要使用函数构造器。
使用函数构造器创建对象
函数构造器提供了一种创建具有特定结构和方法的对象的方式。以下是创建函数构造器的步骤:
- 定义构造器函数:使用大写字母开头命名函数(这是一种约定,表示它是一个构造器)。
- 使用
new关键字调用:这会改变this的指向。 - 在函数内部使用
this:为正在创建的新对象设置属性。
让我们创建一个 Circle 构造器:
function Circle(radius) {
console.log(this);
this.radius = radius;
}
var myCircle = new Circle(10);
console.log(myCircle);
当你使用 new 关键字调用 Circle 函数时:
- JavaScript 引擎会创建一个新的空对象。
- 这个新对象的
this被设置为指向这个新创建的对象。 - 执行构造器函数体内的代码(这里将
radius属性赋给this)。 - 构造器函数不能有返回值(除非返回另一个对象)。
new操作符会自动返回这个新创建的对象。
因此,myCircle 现在是一个拥有 radius 属性的对象。
为对象添加方法
一个只有属性的对象是不够的,我们还需要方法。方法就是附加在对象属性上的函数。
以下是直接为每个实例添加方法的做法(不推荐):
function Circle(radius) {
this.radius = radius;
this.getArea = function() {
return Math.PI * Math.pow(this.radius, 2);
};
}
var myCircle = new Circle(10);
console.log(myCircle.getArea()); // 输出面积
这种方法的问题在于,每次使用 new Circle() 创建一个新圆时,都会在内存中创建一个全新的 getArea 函数。如果创建成千上万个圆,就会浪费大量内存,因为每个圆的 getArea 函数逻辑是完全相同的。
使用 prototype 共享方法
为了解决上述问题,JavaScript 提供了 prototype 属性。每个函数都有一个 prototype 对象。通过构造器创建的所有对象实例,都可以访问其构造器的 prototype 对象上的属性和方法。这样,方法只需定义一次,所有实例即可共享。
以下是使用 prototype 的正确方式:
// 1. 定义构造器,只初始化属性
function Circle(radius) {
this.radius = radius;
}
// 2. 将方法添加到构造器的 prototype 上
Circle.prototype.getArea = function() {
return Math.PI * Math.pow(this.radius, 2);
};
// 3. 创建实例
var myCircle = new Circle(10);
var myOtherCircle = new Circle(20);
console.log(myCircle.getArea()); // 输出第一个圆的面积
console.log(myOtherCircle.getArea()); // 输出第二个圆的面积
console.log(myCircle.getArea === myOtherCircle.getArea); // true,证明是同一个函数
现在,getArea 方法只存在于 Circle.prototype 上。当你调用 myCircle.getArea() 时,JavaScript 引擎会先在 myCircle 对象本身查找 getArea 方法。如果没找到,它会沿着原型链(通过 __proto__ 属性)向上查找,最终在 Circle.prototype 上找到并执行它。
查看对象,你会发现 getArea 不在对象自身,而在其 __proto__ 中。
重要注意事项
在使用函数构造器和原型时,有两点需要特别注意:
-
prototype方法应定义在构造器函数外部
不要将Circle.prototype.getArea = ...这行代码放在Circle函数内部。因为构造器函数每次被new调用时都会执行,如果放在内部,会导致每次创建实例时都重新定义(覆盖)prototype上的方法,造成不必要的性能开销。 -
调用构造器时不要忘记
new关键字
如果忘记使用new,Circle就只是一个普通函数。此时函数内的this会指向全局对象(如window),并且函数默认返回undefined,导致变量无法获得预期的对象。var myCircle = Circle(10); // 错误!没有使用 new console.log(myCircle); // undefined console.log(window.radius); // 10,属性被泄露到全局使用大写字母开头的函数名(如
Circle)有助于提醒你这是构造器,必须与new一起使用。
总结
本节课我们一起学习了 JavaScript 中创建和封装对象的强大工具:
- 函数构造器:通过
function Circle(radius) { ... }定义,并使用new关键字调用,用于初始化对象属性。 this关键字:在构造器函数内部,this指向正在创建的新实例对象。prototype属性:用于存放应由所有实例共享的方法(如getArea)。这避免了为每个实例重复创建相同函数,优化了内存使用。- 原型链:当实例自身没有某个属性或方法时,JavaScript 会通过
__proto__查找其构造器的prototype对象。


通过结合使用函数构造器和原型,我们可以创建出结构清晰、高效且易于维护的对象,这是理解 JavaScript 面向对象编程的基础。
082:对象字面量与 this 关键字

在本节课中,我们将要学习对象字面量的创建方式,并深入探讨 this 关键字在对象字面量内部的行为。我们将通过实例来理解 this 的指向规则,以及如何处理一个常见的 JavaScript 行为。
概述
上一节我们介绍了在函数构造器中 this 关键字的行为,即当函数与 new 关键字一起调用时,this 指向函数自身或新创建的对象。本节中,我们来看看在对象字面量内部,this 关键字是如何表现的。
创建对象字面量
首先,我们创建一个简单的对象字面量。对象字面量由一对花括号 {} 定义,并以分号结尾。
以下是创建一个名为 literalCircle 的对象字面量的步骤:
var literalCircle = {
radius: 10
};
我们为这个对象添加了一个 radius 属性,其值为 10。
为对象添加方法
接下来,我们为这个对象添加一个方法。方法是对象的一个属性,其值是一个函数。
以下是添加 getArea 方法的代码:
var literalCircle = {
radius: 10,
getArea: function () {
console.log(this);
}
};
在这个方法内部,我们使用 console.log(this) 来查看 this 关键字当前指向什么。
调用方法并观察 this
现在,我们调用 getArea 方法,观察控制台的输出。
literalCircle.getArea();
执行后,你会发现 this 不再指向全局的 window 对象,而是指向我们创建的 literalCircle 对象本身。这是因为在对象字面量中,this 自动绑定到该对象。
在方法中使用 this
既然 this 指向对象本身,我们就可以在方法中安全地使用它来访问对象的其他属性。
以下是修改后的 getArea 方法,它使用 this.radius 来计算圆的面积:
var literalCircle = {
radius: 10,
getArea: function () {
return Math.PI * Math.pow(this.radius, 2);
}
};
console.log(literalCircle.getArea());
执行这段代码,控制台会输出正确的圆面积(约 314.16)。这证明了 this 在对象方法中按预期工作。
理解 this 的绑定原理
你可能会问,为什么这里的 this 会指向对象而不是全局对象?原因在于,对象字面量的创建在底层等价于使用 new Object()。new 关键字的存在使得函数内部的 this 被绑定到新创建的对象上,而不是外部环境。
嵌套函数中的 this 问题
然而,JavaScript 中存在一个常见的行为(有些人认为这是一个设计上的缺陷)。当在一个对象方法内部定义另一个函数(嵌套函数)时,内部函数中的 this 会重新指向全局对象(如 window),而不是外层对象。
以下是一个演示此问题的例子:
var literalCircle = {
radius: 10,
getArea: function () {
console.log(this); // 指向 literalCircle
var increaseRadius = function () {
this.radius = 20; // 这里的 this 指向 window!
};
increaseRadius();
console.log(this.radius); // 输出仍然是 10
console.log(window.radius); // 输出是 20
return Math.PI * Math.pow(this.radius, 2);
}
};
console.log(literalCircle.getArea());
运行这段代码,你会发现 this.radius 的值没有改变,而 window.radius 被设置成了 20。这是因为 increaseRadius 函数内部的 this 指向了全局对象。
解决方案:使用 self 变量
为了解决这个问题,一个常见的模式是在外层方法开始时,将 this 保存到一个变量(通常命名为 self 或 that)中。然后在内部函数中使用这个变量来引用外层对象。
以下是使用 self 变量修复问题的代码:
var literalCircle = {
radius: 10,
getArea: function () {
var self = this; // 将 this 保存到 self
var increaseRadius = function () {
self.radius = 20; // 使用 self 而不是 this
};
increaseRadius();
console.log(this.radius); // 现在输出是 20
return Math.PI * Math.pow(this.radius, 2);
}
};
console.log(literalCircle.getArea());
现在,increaseRadius 函数正确地修改了 literalCircle 对象的 radius 属性,面积计算也基于新的半径值。
关于此行为的说明
关于这个行为是否是 JavaScript 的缺陷存在一些争论。无论如何,对于开发者来说,它可能带来困惑。值得注意的是,在更新版本的 JavaScript(如 ECMAScript 6)中,通过箭头函数等特性,这个问题已经得到了改善。
总结

本节课中我们一起学习了对象字面量的基本结构,并深入探讨了 this 关键字在其中的行为。我们了解到,在对象方法中,this 通常指向对象本身。然而,在嵌套函数内部,this 会指向全局对象,这是一个需要注意的常见陷阱。我们通过引入 self 变量的模式,有效地解决了这个问题,确保了代码的正确性。理解 this 的绑定规则对于编写可靠的 JavaScript 代码至关重要。
083:数组

在本节课中,我们将要学习 JavaScript 中的数组。数组是用于存储数据集合的核心数据结构。由于 JavaScript 的动态类型特性,数组展现出一些非常有趣和灵活的特性。
创建数组
上一节我们介绍了数组的基本概念,本节中我们来看看如何创建数组。在 JavaScript 中,主要有两种创建数组的方式。
第一种是使用 new Array() 构造函数:
var array = new Array();
创建数组后,我们可以使用方括号 [] 和索引(从0开始)向其中添加元素。JavaScript 数组可以存储不同类型的数据。
array[0] = “Yaakov”; // 字符串
array[1] = 2; // 数字
array[2] = function(name) { // 函数
console.log(“Hello ” + name);
};
array[3] = { // 对象字面量
course: “HTML, CSS & JS”
};
第二种,也是更常用的方式,是使用数组字面量表示法,它更简洁:
var names = [“Yaakov”, “John”, “Joe”];
访问数组元素
创建数组后,我们可以使用相同的方括号语法来访问其中的元素。
以下是访问数组元素的几种方式:
console.log(array[1]);// 输出数字 2array[2](“Yaakov”);// 调用存储在索引2处的函数console.log(array[3].course);// 访问对象属性,输出 “HTML, CSS & JS”
遍历数组
为了处理数组中的所有元素,我们通常使用循环。for 循环是最常见的选择,它利用数组的 .length 属性来确定循环次数。
for (var i = 0; i < names.length; i++) {
console.log(“Hello ” + names[i]);
}
稀疏数组
JavaScript 数组可以是“稀疏的”,这意味着数组元素之间的索引可以不连续。你可以直接为某个较高的索引位置赋值。
names[100] = “Jim”;

此时,数组 names 在索引 0, 1, 2 和 100 处有值,而索引 3 到 99 是“空”的。如果遍历这个数组,这些空位置会输出 undefined。

本节课中我们一起学习了 JavaScript 数组的创建、元素访问、遍历以及稀疏数组的特性。数组是组织和管理数据的强大工具。在下一部分,我们将探讨数组的另一个重要特性。
084:18_第50讲 第2部分 数组

在本节课中,我们将要学习在使用特定类型的for循环遍历数组时需要注意的一个重要问题。我们将通过对比数组和对象的遍历方式,理解JavaScript中数组的本质,并掌握如何正确选择循环方法。
上一节我们介绍了数组的基本遍历方法,本节中我们来看看一个在使用for...in循环遍历数组时可能遇到的陷阱。
数组与对象的相似性
在JavaScript中,数组本质上是一种特殊类型的对象。数组的索引(如0, 1, 2)实际上是对象的属性名。这意味着某些用于遍历对象属性的循环方法也可以用于数组,但这并不总是理想的做法。
让我们通过一个例子来理解这一点。首先,我们创建一个数组和一个对象:
// 创建一个数组
let names2 = ["Yaov", "John", "Joe"];
// 创建一个对象
let myObj = {
name: "Yakov",
course: "HTML, CSS, JavaScript",
platform: "Coursera"
};
使用for...in循环遍历对象
对于对象,我们可以使用for...in循环来遍历其所有属性。这种循环非常方便,因为对象没有像数组那样的数字索引,而是具有属性名。
以下是使用for...in循环遍历对象的示例:
for (let prop in myObj) {
console.log(prop + ": " + myObj[prop]);
}
这段代码会输出对象的所有属性名和对应的值。prop变量在每次循环中代表对象的一个属性名,我们可以使用点表示法或方括号表示法来访问属性值。
尝试用for...in循环遍历数组
既然数组也是对象,理论上我们也可以使用for...in循环来遍历数组。让我们尝试一下:
for (let name in names2) {
console.log("Hello " + names2[name]);
}
这段代码确实会输出"Hello Yaov"、"Hello John"和"Hello Joe",看起来工作正常。然而,这里存在一个潜在问题。
问题的根源
数组作为对象,可以拥有除数字索引之外的其他属性。让我们给数组添加一个自定义属性:
names2.greeting = "Hi";
现在,如果我们再次使用相同的for...in循环:
for (let name in names2) {
console.log("Hello " + names2[name]);
}
输出结果将包括"Hello Hi",这是因为greeting属性也成为了数组对象的一部分,而for...in循环会遍历对象的所有可枚举属性,包括我们添加的自定义属性。
推荐的做法
对于数组遍历,更安全、更明确的方法是使用标准的for循环或forEach方法:
// 使用标准for循环
for (let i = 0; i < names2.length; i++) {
console.log("Hello " + names2[i]);
}
// 使用forEach方法
names2.forEach(function(name) {
console.log("Hello " + name);
});
这些方法只会遍历数组的实际元素,而不会包括可能添加的其他属性。
关键要点总结
以下是本节课的核心要点:
- 数组是对象:在JavaScript中,数组是特殊类型的对象,索引是属性名
- for...in循环的局限性:
for...in循环会遍历对象的所有可枚举属性 - 自定义属性的影响:添加到数组的自定义属性会被
for...in循环包含 - 推荐的数组遍历方法:使用标准
for循环或forEach方法更安全可靠


本节课中我们一起学习了数组与对象遍历的重要区别。我们了解到,虽然数组在JavaScript中是对象,但使用for...in循环遍历数组可能存在风险,因为它会包含所有可枚举属性,而不仅仅是数组元素。对于数组遍历,建议使用专门设计的方法,如标准for循环或forEach方法,以确保只处理预期的数据。
085:理解闭包

在本节课中,我们将要学习 JavaScript 中的一个核心概念——闭包。闭包对于理解 JavaScript 的高级功能至关重要,例如实现 Ajax 等功能。实际上,你已经在不知不觉中接触过闭包了。
概述
闭包允许一个函数访问并记住其外部作用域中的变量,即使该外部函数已经执行完毕。这为创建具有私有状态或记忆功能的函数提供了可能。
一个闭包的例子
让我们通过一个具体的例子来理解闭包。我们将创建一个名为 makeMultiplier 的函数。
function makeMultiplier(multiplier) {
return function (x) {
return multiplier * x;
};
}
这个函数接收一个参数 multiplier,并返回一个新的函数。这个返回的函数接收一个参数 x,并返回 multiplier 乘以 x 的结果。
现在,我们来使用这个函数。
var doubleAll = makeMultiplier(2);
console.log(doubleAll(10)); // 输出:20
当我们调用 makeMultiplier(2) 时,它返回了一个新的函数,并将其赋值给变量 doubleAll。随后,当我们调用 doubleAll(10) 时,它正确地计算出了 2 * 10 = 20。
闭包是如何工作的
上一节我们介绍了闭包的基本用法,本节中我们来看看其背后的工作原理。关键在于理解 JavaScript 的执行上下文和词法作用域。
当一个函数(如 makeMultiplier)被执行时,它会创建一个执行上下文。这个上下文包含了函数的参数(如 multiplier)和局部变量。当函数返回另一个函数时,返回的函数会“记住”它被创建时所处的词法环境。
即使 makeMultiplier 函数已经执行完毕并从执行栈中弹出,其内部变量 multiplier 所占用的内存空间也不会被立即回收。这是因为返回的函数(doubleAll)仍然保留着对其外部词法环境(即 makeMultiplier 的作用域)的引用。这种机制就是闭包。
以下是其工作流程的简化描述:
- 调用
makeMultiplier(2),创建执行上下文,其中multiplier = 2。 makeMultiplier返回一个新的函数,该函数内部引用了变量multiplier。makeMultiplier执行完毕,但其作用域(包含multiplier)被返回的函数“关闭”在内,得以保留。- 调用
doubleAll(10)时,新函数创建自己的执行上下文。 - 新函数在其自身作用域中找不到
multiplier,于是沿着作用域链向上查找,在闭包保留的makeMultiplier作用域中找到了multiplier = 2,并完成计算。
闭包的关键特性
以下是闭包的一些核心特性:
- 函数嵌套:闭包通常涉及一个函数内部定义另一个函数。
- 内部函数引用外部变量:内部函数引用了其外部函数作用域中的变量。
- 外部函数已执行完毕:即使外部函数已经返回,内部函数仍能访问那些外部变量。
总结


本节课中我们一起学习了 JavaScript 的闭包。我们了解到,闭包是一个函数与其周围状态(词法环境)的引用捆绑在一起的现象。这使得内部函数可以访问外部函数的作用域,即使外部函数已经执行结束。通过 makeMultiplier 的例子,我们看到了闭包如何创建具有记忆功能的函数。理解闭包是掌握 JavaScript 异步编程、模块模式等高级主题的基础。
086:伪命名空间(第一部分)

在本节课中,我们将学习一个在JavaScript开发中非常常见的场景:全局变量冲突。我们将探讨这个问题的成因,并学习如何使用“伪命名空间”模式来解决它,从而让不同的脚本模块能够和谐共存。
概述:全局变量冲突问题
当我们加载多个JavaScript文件时,如果这些文件都在全局作用域中声明了相同名称的变量或函数,就会发生冲突。后加载的脚本会覆盖先加载的脚本中的同名变量,导致程序行为与预期不符。
上一节我们介绍了课程背景,本节中我们来看看一个具体的冲突案例。
案例分析:变量覆盖
我们有一个HTML文件,它按顺序加载了三个脚本文件。
<script src="script1.js"></script>
<script src="script2.js"></script>
<script src="app.js"></script>
以下是各个脚本文件的内容:
-
script1.js:声明了一个全局变量name和一个函数sayHello。var name = ‘Yaakov’; function sayHello() { console.log(‘Hello ‘ + name); } -
script2.js:也声明了一个同名的全局变量name和一个不同的函数sayHi。var name = ‘John’; function sayHi() { console.log(‘Hi ‘ + name); } -
app.js:尝试调用前两个脚本中定义的函数。sayHello(); // 预期输出:Hello Yaakov sayHi(); // 预期输出:Hi John
然而,实际运行后,控制台的输出是:
Hello John
Hi John
问题根源:script2.js 中的 var name = ‘John’; 覆盖了 script1.js 中定义的 var name = ‘Yaakov’;。因此,当 app.js 调用 sayHello() 时,函数内部引用的 name 已经是 ‘John’,而不是预期的 ‘Yaakov’。
解决方案:使用伪命名空间
JavaScript本身没有像其他语言那样的内置命名空间。但是,我们可以利用对象来模拟命名空间,为每个脚本模块创建一个专属的容器。
以下是创建和使用伪命名空间的步骤:
- 创建命名空间对象:为每个脚本模块定义一个唯一的全局对象。
- 将变量和函数挂载到对象上:将原本暴露在全局的变量和函数,改为这个对象的属性。
- 通过对象访问功能:在其他脚本中,通过这个命名空间对象来访问其内部的变量和函数。
让我们应用这个方案来修改之前的脚本。
修改 script1.js
我们将创建一个名为 yaakovGreeter 的命名空间对象,并将 name 和 sayHello 作为其属性。
// 创建命名空间对象
var yaakovGreeter = {};
// 将变量和函数定义为对象的属性
yaakovGreeter.name = ‘Yaakov’;
yaakovGreeter.sayHello = function () {
console.log(‘Hello ‘ + yaakovGreeter.name);
}
修改 script2.js
同样,我们为第二个脚本创建 johnGreeter 命名空间。
// 创建命名空间对象
var johnGreeter = {};
// 将变量和函数定义为对象的属性
johnGreeter.name = ‘John’;
johnGreeter.sayHi = function () {
console.log(‘Hi ‘ + johnGreeter.name);
}
修改 app.js
现在,我们需要通过命名空间对象来调用函数。
yaakovGreeter.sayHello(); // 输出:Hello Yaakov
johnGreeter.sayHi(); // 输出:Hi John
经过这样的修改,两个脚本中的 name 变量被分别隔离在 yaakovGreeter 和 johnGreeter 对象内部,互不干扰。程序输出了正确的结果。
本节总结
本节课中我们一起学习了如何解决JavaScript中的全局变量冲突问题。核心方法是使用伪命名空间模式,即通过创建一个唯一的全局对象,将每个模块的变量和函数封装在该对象内部。这有效地隔离了不同模块的代码,避免了命名冲突。


然而,目前的命名空间对象及其所有属性仍然是公开的、可被直接访问和修改的。在下一部分,我们将探讨如何进一步保护命名空间内的“私有”变量,实现更精细的封装和控制。
087:立即调用函数表达式(IIFE) 🚀

在本节课中,我们将学习如何通过立即调用函数表达式(IIFE)来进一步管理代码,避免全局作用域中的变量和函数冲突。这是一种创建私有作用域并选择性暴露功能的强大技术。
概述
上一节我们介绍了如何使用对象来模拟命名空间,以隔离全局作用域中的代码。本节中,我们来看看一种更彻底的方法:立即调用函数表达式(IIFE)。这种方法允许代码在加载时立即执行,并创建一个独立的作用域,从而避免污染全局命名空间。
从问题出发
假设我们有两个脚本文件,它们都定义了一个名为 greeting 的变量。当这两个脚本在同一个页面中加载时,后加载的脚本会覆盖先加载的脚本中的 greeting 变量,导致意外的行为。
例如,script1.js 可能包含:
var greeting = "Hello";
function sayHello(name) {
console.log(greeting + " " + name);
}
而 script2.js 可能包含:
var greeting = "Hi";
// ... 其他使用 greeting 的代码
当两个脚本都加载后,greeting 的最终值将是 "Hi",这可能会破坏 script1.js 中 sayHello 函数的预期功能。
引入立即调用函数表达式(IIFE)
为了解决这个问题,我们可以将代码包裹在一个函数中,并立即调用它。这就是立即调用函数表达式。
以下是一个IIFE的基本结构:
(function() {
// 你的代码在这里
})();
这段代码定义了一个匿名函数,并立即通过末尾的 () 执行它。函数内部的所有变量都存在于这个函数自己的执行上下文中,与全局作用域隔离。
如何创建和使用IIFE
让我们通过一个例子来分解IIFE的创建过程。
首先,我们从一个常规函数开始:
function greet() {
console.log("Hello Coursera");
}
greet(); // 调用函数
我们可以将这个函数转换为函数表达式,并立即调用它:
(function greet() {
console.log("Hello Coursera");
})();
甚至可以使用匿名函数:
(function() {
console.log("Hello Coursera");
})();
当这段代码被浏览器加载时,函数会立即执行,输出“Hello Coursera”,并且函数内部声明的任何变量都不会泄露到全局作用域。
向IIFE传递参数
IIFE也可以接收参数。这对于将全局对象(如 window)传递到函数作用域内非常有用,以便有选择地暴露某些功能。
例如,我们可以创建一个模块,只将需要公开的部分附加到 window 对象上:
(function(global) {
var privateGreeting = "Hello"; // 私有变量
var myGreeter = {
sayHello: function(name) {
console.log(privateGreeting + " " + name);
}
};
// 将有选择地暴露 myGreeter 到全局作用域
global.myGreeter = myGreeter;
})(window); // 将 window 对象作为参数传入
现在,在全局作用域中,我们可以访问 window.myGreeter.sayHello("Yaakov"),但无法直接访问 privateGreeting 变量。这有效地创建了公共和私有成员的分离。
使用IIFE解决冲突
回到最初的两个脚本冲突问题,我们可以用IIFE将它们分别包裹起来:
script1.js 可以改写为:
(function() {
var greeting = "Hello";
function sayHello(name) {
console.log(greeting + " " + name);
}
// 根据需要暴露 sayHello 到全局
window.sayHello1 = sayHello;
})();
script2.js 可以改写为:
(function() {
var greeting = "Hi";
// ... 其他使用 greeting 的代码
// 这个 greeting 与 script1 中的完全隔离
})();
这样,每个 greeting 变量都存在于各自独立的函数作用域中,互不干扰。
IIFE的优势总结
以下是使用立即调用函数表达式的主要好处:
- 避免全局污染:减少全局作用域中的变量和函数数量,降低命名冲突的风险。
- 创建私有作用域:函数内的变量对外部是不可见的,实现了数据的封装和私有化。
- 模块化代码:有助于组织代码,形成独立的模块,每个模块拥有自己的作用域。
- 立即执行:代码在定义时即刻运行,适用于初始化任务。
总结


本节课中我们一起学习了立即调用函数表达式(IIFE)。我们了解了它如何通过创建一个独立且立即执行的函数作用域,来有效地隔离变量、避免全局命名冲突,并实现代码的模块化封装。通过将需要公开的部分有选择地附加到全局对象(如 window),我们可以在保持代码私密性的同时,提供必要的接口。IIFE是构建更清晰、更健壮JavaScript应用程序的基础工具之一。
088:模块四总结

概述
在本节课中,我们将对模块四的学习内容进行回顾与总结。模块四主要深入探讨了JavaScript的核心概念,为构建动态网页应用奠定了坚实基础。
课程内容回顾
上一节课程中,我们完成了模块四的学习。虽然这门课程时间有限,无法涵盖JavaScript的所有知识,但我们已经进行了相当深入的探讨。我们所学习的内容为你打下了非常坚实的基础。
实际上,我敢打赌,即使是一些经验丰富的开发者也不一定完全掌握本模块中的所有主题。
后续学习展望
在下一个模块中,我们将开始应用JavaScript来创建网页应用程序。请继续坚持学习,你只剩下一个模块,就能完成整个课程了。

总结
本节课中,我们一起回顾了模块四的核心内容,并展望了后续的学习方向。你已经掌握了JavaScript的关键知识,为进入实际的Web应用开发做好了准备。
089:使用JavaScript构建Web应用 🚀
在本模块中,我们将进入课程的最后一个阶段。我们已经掌握了JavaScript的语言基础,现在将深入探索如何使用JavaScript来操作网页中的元素,以及如何利用AJAX技术从服务器获取实时数据,而无需用户重新加载整个页面。最后,我们将运用新掌握的JavaScript和AJAX技能,完成餐厅网站项目,使其能够从服务器动态获取内容和菜单数据,并生成相应的HTML视图。
模块概述 📋

上一节我们完成了JavaScript基础的学习。本节中,我们将正式开启模块五的学习旅程。本模块的核心目标是:将静态网页转变为动态的、数据驱动的Web应用。
为了实现这个目标,我们将学习两个关键技术:
- DOM操作:使用JavaScript与网页内容进行交互和修改。
- AJAX:实现网页与服务器之间的异步数据交换。
以下是本模块将涵盖的主要内容列表:
- DOM操作基础:学习如何选取、修改、创建和删除HTML元素。
- 事件处理:让网页能够响应用户的点击、输入等交互行为。
- AJAX原理与实践:理解如何在不刷新页面的情况下向服务器发送请求并处理返回的数据。
- 项目实战:动态餐厅网站:综合运用所学知识,构建一个从服务器动态加载数据的完整网站。
核心技术点解析 🔧
1. DOM操作
文档对象模型(DOM)是HTML文档的编程接口。JavaScript可以通过DOM来访问和操作网页的结构、样式和内容。
一个基本的DOM操作示例是改变元素的内容:
// 选取id为“title”的元素,并将其文本内容改为“新标题”
document.getElementById('title').textContent = '新标题';
2. AJAX
AJAX(Asynchronous JavaScript and XML)允许网页与服务器进行异步通信。这意味着可以在不干扰用户当前操作的情况下,在后台获取新数据并更新部分网页内容。
现代JavaScript通常使用 fetch API 来进行AJAX调用:
// 向指定URL发送GET请求,并处理返回的JSON数据
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log('获取到的数据:', data);
// 在这里使用数据更新DOM
})
.catch(error => {
console.error('请求出错:', error);
});
从静态到动态的转变 🔄
在之前的模块中,我们构建的网站内容是直接写在HTML文件里的,是静态的。在本模块结束时,我们的网站将焕然一新:页面框架(HTML)依然存在,但具体的内容(如菜品详情、餐厅介绍)将通过JavaScript从服务器请求获得,然后动态地插入到页面中。这种方式使网站更灵活,内容更容易管理和更新。

总结 🎯
本节课中我们一起学习了模块五的总体目标和学习路径。我们了解到,本模块将聚焦于使用JavaScript赋予网页“生命”,即通过DOM操作实现交互性,并通过AJAX技术实现数据的动态加载。接下来,我们将从DOM操作开始,一步步学习如何让网页动起来。
090:24_第53讲 第1部分 DOM操作

概述
在本节课中,我们将要学习如何在浏览器中使用Javascript来操作网页。这被称为DOM(文档对象模型)操作。我们将从了解如何将Javascript脚本引入HTML页面开始,并认识一个核心对象——document。
认识 document 对象
如果无法操作Javascript所依附的页面,那么浏览器中的Javascript将失去意义。因此,本节我们将讨论DOM操作。
开始讨论DOM操作时,第一个问题是如何将实际的脚本(script.js)或Javascript库包含到HTML页面中。在深入讨论脚本位置之前,我们先来了解一个特殊的对象:document。
document 对象代表了整个HTML文档,即文档对象模型。我们知道有一个名为 window(也称为 this)的对象,它包含许多属性,其中之一就是 document 属性。如果我们执行 window.document,你将看到一个 document 对象。展开它,你会发现这个对象包含了我们整个HTML页面的结构。
这个 document 对象是我们获取网页中各个元素的基石。有多种方法可以实现这一点。
分析示例页面
让我们先查看 index.html 文件,熟悉一下页面的结构。
我们有一个非常简单的页面:
- 它有一个H1标题,内容是“lecture 53”,我们给它分配了ID
title。 - 接着是一个段落,内容是“say hello2”。
- 然后是一个输入字段,同样有ID
name,类型是文本,所以它会显示一个文本框。 - 我们还包含了一个按钮,上面写着“say it”。
- 最后,这里有一个空的
div,其ID为content。我们稍后会明白为什么需要它。
通过ID获取元素
document 对象的核心方法之一是能够通过ID获取元素。
让我们在控制台中尝试获取这个H1元素。我们可以使用一个特殊的方法:document.getElementById()。在括号内,我们用引号指定实际的ID(注意,这里不需要 # 符号,直接写ID名即可,本例中是 title)。
执行该语句,你会看到返回的是具有 title ID 的H1元素。
当然,我们也可以在 script.js 中使用 console.log 来实现完全相同的效果。让我们先尝试一下。
首先,我们在文档的 head 部分引入位于 js 文件夹中的 script.js 文件。我们使用 <script src="js/script.js"></script>。这里可以省略 type 属性,因为Javascript是此处唯一可能的类型。
保存后,我们转到 script.js 并执行相同的操作。我们输入 document.getElementById('title'),并用 console.log 打印它。
我们之所以能在这里使用 document,是因为我们处于全局作用域中。全局作用域是 window,而 window 有一个名为 document 的属性。因此,当我们说 document 时,它显然会在 window 对象上查找 document 属性,而事实上它确实存在。
保存后,我们得到了 null。这有点奇怪。为什么是 null?为什么我们没有得到“lecture 53”这个标题?
我们没有得到它的原因在于这行代码执行的时间点。让我们再次查看 index.html。这行代码的执行位置与任何其他Javascript代码一样,就在它被提及的地方。这就好比我们把这一行代码复制并粘贴到了 <script> 标签的中间。
问题在于,当你在HTML页面渲染的这个时间点,调用并请求文档检索具有 title ID 的元素时,那个具有 title ID 的H1元素还不存在。因此,你得到的是 null。
解决脚本执行时机问题
我们如何解决这个问题呢?有几种方法。
一种方法是使用一个特殊的方法来监听页面加载生命周期中的事件,并声明“当页面加载完成后,再执行这行代码”。然而,有一个更简单的解决方法,特别是考虑到我们试图做的是向页面注入一些行为(而不是页面如何显示)。像这样的Javascript代码,最好永远不要放在 <head> 中,而是应该放在文档的最末尾。
我们将在这里插入它。这样,这段Javascript代码将在所有这些元素都已被浏览器处理、加载到内存中,并且文档对象模型已经准备就绪之后才执行。
现在保存,你会看到此时我们成功获取到了ID为 title 的H1元素。这段代码只有在元素已经加载和渲染完成后才能正常执行。
document 对象的本质
我们之所以能够使用 getElementById 这个函数,是因为 document 是一个特殊的对象。document 的对象名是 HTMLDocument。它是一个特殊的对象,拥有大量你可以调用的方法。
事实上,我们可以用 console.log(document) 输出它,并使用我们之前未见过的 instanceof 操作符来测试某个特定实例是否属于某个特定类。在本例中,它是 HTMLDocument。如果我们将其打印到控制台并保存,你会看到结果为 true。所以,document 实际上是 HTMLDocument 的一个实例。如果你在网上搜索 HTMLDocument,你将能看到 HTMLDocument 定义的所有用于帮助你操作网页的方法。


总结
本节课中,我们一起学习了DOM操作的基础。我们认识了核心的 document 对象(即 HTMLDocument 的实例),了解了如何通过 document.getElementById() 方法根据ID获取页面元素,并解决了脚本执行时机的问题,明白了将行为脚本放在页面底部的重要性。在下一部分,我们将开始使用这个特殊对象来实际操纵网页。
091:DOM操作实战

在本节课中,我们将学习如何使用JavaScript的document对象来操作网页内容。我们将通过一个简单的“打招呼”示例,学习如何获取用户输入、更新页面元素以及动态修改HTML结构。
上一节我们介绍了document对象的基本概念,本节中我们来看看如何实际运用它来与网页进行交互。
获取用户输入
首先,我们需要为页面上的按钮添加一个点击行为。在HTML中,我们可以使用onclick属性来调用一个JavaScript函数。
以下是HTML代码片段:
<p>Say hello to: <input type="text" id="name"> <button onclick="sayHello()">Say It!</button></p>
<div id="content"></div>
接下来,我们定义sayHello函数。这个函数的第一步是获取用户在文本框中输入的值。
以下是获取输入值的代码:
function sayHello() {
var name = document.getElementById('name').value;
console.log(name);
}
在这段代码中,document.getElementById('name')用于获取ID为name的输入框元素,.value属性则用于读取用户在其中输入的内容。
更新页面内容
获取到用户名后,我们的目标是生成一条问候语,并将其显示在页面上预留的div区域中。
以下是生成并显示问候语的步骤:
- 组合问候消息。
- 找到目标容器元素。
- 将消息插入到该容器中。
以下是实现代码:
function sayHello() {
var name = document.getElementById('name').value;
var message = "Hello " + name + "!";
document.getElementById('content').textContent = message;
}
这里,我们使用textContent属性将纯文本消息message设置到ID为content的div元素内部。
插入HTML元素
如果我们希望问候语以<h2>标题的形式显示,而不仅仅是纯文本,就需要使用不同的属性。
以下是使用innerHTML插入HTML代码的方法:
function sayHello() {
var name = document.getElementById('name').value;
var message = "<h2>Hello " + name + "!</h2>";
document.getElementById('content').innerHTML = message;
}
innerHTML属性会将字符串解析为HTML代码并渲染,因此<h2>标签会被正确识别为标题元素。
实现条件逻辑与高级选择器
现在,我们为程序添加一点“智能”:当用户输入“student”时,我们不仅显示问候语,还要修改页面的主标题。
以下是实现条件逻辑的步骤:
- 判断输入的用户名。
- 如果用户名是“student”,则修改页面标题。
- 使用更通用的
querySelector方法来选择元素。
以下是完整的条件逻辑代码:
function sayHello() {
var name = document.getElementById('name').value;
var message = "<h2>Hello " + name + "!</h2>";
document.getElementById('content').innerHTML = message;
if (name === "student") {
var title = document.querySelector('h1').textContent;
title += " and loving it";
document.querySelector('h1').textContent = title;
}
}
在这段代码中,document.querySelector('h1')使用CSS选择器语法选中页面上的第一个<h1>元素。我们获取其文本内容,追加字符串,然后再重新设置回去,从而更新了页面标题。

本节课中我们一起学习了DOM操作的核心技能:通过getElementById和querySelector获取页面元素,使用.value读取输入内容,以及通过textContent和innerHTML属性来修改元素内的文本或HTML结构。利用这些基础方法,你已经可以开始让网页动态地响应用户的操作了。
092:26_第54讲 事件处理

概述
在本节课中,我们将学习如何为网页元素绑定事件处理器。我们将探讨几种不同的绑定方法,理解它们的区别,并学习如何利用事件对象来编写更灵活、更易于维护的代码。
我们已经看到了一个如何将事件处理器绑定到特定元素的简单示例。
我现在位于第54讲的文件夹中的 index.html 文件里。
让我关闭文件浏览器以便看得更清楚。
你记得这个HTML文档,我们在中间有一个按钮,上面写着 onclick="sayHello()"。你可以回想一下,当我们在输入框中输入名字并点击“Say It”按钮时,它会使用 sayHello 函数显示“Hello Yaakov”。
现在我想探讨几种可以为元素分配事件处理器的不同方法。
首先,什么是事件处理器?事件处理器本质上是函数,你可以使用特定的方法将它们绑定到浏览器中发生的特定事件上。这些事件可能由页面的生命周期触发,例如页面加载完成,也可能由用户交互触发,例如用户输入字符或点击了某个元素。
为特定元素分配事件处理器最简单的方法之一,就是直接在该元素上使用 on 开头的属性。在这里,我们可以看到我们使用了 onclick,并让它执行JavaScript函数 sayHello。但事实上,如果你查看一下,会发现有很多这类属性,它们主要与键盘、鼠标事件以及焦点事件相关。例如,onblur 意味着当这个特定元素失去焦点时触发。我实际上可以在这里的输入字段上添加 onblur="sayHello()"。
那么,当我在这里输入内容时,同样的函数也会被执行。让我们刷新浏览器。假设我输入“Yaakov”,一旦我让这个输入框失去焦点,就像这样,sayHello 函数就会被执行,并显示“Hello Yaakov”。让我们先清除这个,以免界面显得拥挤。
直接在HTML上使用 onclick、onblur 或类似的 on 事件属性是一种选择。然而,这种方法会带来一些副作用。一个副作用是你必须在HTML中混杂这些 onclick、onblur、onkeydown 等事件属性。通常,正如我们之前讨论过的,HTML应该专注于内容,而不是行为,这是其一。
第二个区别是,如果我们查看 sayHello 函数,也就是当有人点击“Say It”按钮时执行的函数,我们在其中添加 console.log(this)。这个 this 指向什么?让我们保存并点击“Say It”按钮,即使不输入任何内容。显然,它指向的是 window 对象。为什么会这样?因为 sayHello 函数是在全局上下文中定义的,所以当我们说 this 时,它再次指向全局上下文,也就是 window 对象。在这里,我们通过 onclick 属性分配函数时,并没有做任何事情来改变 sayHello 函数的上下文,我们只是在这里执行它,所以上下文保持不变。
然而,有另一种方法可以实现同样的事情,即将这个函数绑定到点击事件上。让我们来看看另一种方法,这种方法被称为“非侵入式事件绑定”。之所以这样称呼,是因为HTML不需要知道任何关于你的JavaScript的信息。
让我们回到HTML,删除这里的 onclick 属性。现在HTML对点击处理器一无所知。保存后,转到脚本部分。我们在这里做的是:document.querySelector('button'),我们选中了那个元素,然后调用一个名为 addEventListener 的函数。我们告诉它监听 click 事件,当事件发生时,使用这个函数表达式或函数值,也就是指向我们 sayHello 函数的 sayHello。
现在当我们这样做时,让我们看看 this 现在指向什么。保存脚本,然后点击“Say It”。显然我们没有提供任何输入,但你可以看到,现在 this 指向的是实际的按钮元素。这完全合理,因为我们把这个函数的值传递给了这个特定的元素。这个按钮元素调用了 addEventListener,在这个调用中,我们以某种方式将 sayHello 分配给了它的事件。因此,this 变量现在指向包含它的对象,也就是按钮元素。
还有一种类似的方法来分配点击处理器,那就是使用 onclick 属性。让我们注释掉 addEventListener 这行,取消注释下面这行。这里有个拼写错误,应该是 onclick。关键是,元素本身可用的任何 onclick、onblur 等属性,你都可以将其作为你选中的对象的一个属性来使用。在这个例子中,我们再次选中元素对象,并将其 onclick 属性设置为函数 sayHello 的值。再次强调,这只是一个值,我们在函数后面没有加括号,我们不是在执行它,只是将它的值赋给属性。
那么,你认为当我们执行 console.log(this) 时,sayHello 函数会输出什么?由于这是在按钮对象内部执行的,当我们点击“Say It”时,它再次指向按钮对象。
虽然这种方式可能看起来比直接在HTML上写 onclick 稍微复杂一点,但它确实为我们提供了更多的灵活性。让我展示一下我的意思。让我注释掉 onclick 属性这行,只保留 addEventListener 的方式来绑定点击事件。
现在我想做的是:当我输入“Yaakov”并点击“Say It”时,我希望按钮的标签改变。如果事件处理器内部的 this 变量已经直接指向你正在操作的目标元素,那么这很容易做到。让我们在这里添加代码:this.textContent = 'Set It'。我们将把按钮的文本内容改为“Set It”。保存后,当我输入“Yaakov”并点击“Say It”时,按钮的文本会变成“Set It”。
这是一种更方便的做法,因为你不需要再次使用查询API来选中这个按钮并更改它的标签。这是因为在这个JavaScript代码中,我不需要知道是什么触发了点击事件。如果我必须用另一种方式做,我就必须知道是按钮触发了点击事件。而在这里,我不需要知道这一点,我只需要知道某个具有 textContent 属性的元素触发了那个特定事件。
让我们再讨论一个事件,它是页面的一个生命周期事件,它允许我们在页面元素加载完成之后、但在任何其他资源(如图像、CSS等)加载之前,为这些元素分配事件。由于我们将监听这个事件,我们不再需要将脚本放在页面底部。我们可以将脚本从这里剪切出来,轻松地放在 <head> 部分。保存后,我们现在需要做的是监听浏览器生命周期中的一个特定事件。这个事件叫做 DOMContentLoaded。
我们需要做的是:document.addEventListener('DOMContentLoaded', function(event) { ... })。在这个函数内部,我们现在可以开始分配不同的事件。所以,我们现在可以做的是,创建一个函数 hello,把之前的所有代码剪切出来,放在这里。现在,整个函数都位于 addEventListener 内部,它将在 DOMContentLoaded 事件触发时执行。这个事件会在任何图像、CSS或其他脚本加载之前发生。
如果我们保存它,输入“Yaakov”,点击“Say It”,你可以看到,我们的功能仍然像以前一样正常工作。但这次,我们不必等到所有元素都加载完毕才将代码放在页面底部。相反,我们监听所有元素加载完成的事件,在这种情况下,我们能够执行这些函数,使用 getElementById 等,并确保这些元素已经存在于文档对象模型中。


总结
本节课中,我们一起学习了事件处理的基础知识。我们比较了在HTML中直接使用 on 属性绑定事件与在JavaScript中使用 addEventListener 或 onclick 属性进行绑定的区别。我们了解到,非侵入式绑定(使用 addEventListener)不仅使HTML更简洁,专注于内容,还提供了更灵活的上下文(this 指向触发事件的元素),并且允许我们在页面内容加载完成后、其他资源加载前安全地执行脚本。这些知识是构建交互式网页应用的重要基石。
093:27_第55讲 事件参数 🎯

在本节课中,我们将要学习JavaScript事件处理中的一个核心概念:事件参数。这个参数由JavaScript引擎自动传递给每一个事件处理函数,它包含了与事件相关的丰富信息,例如鼠标点击的位置、按下的按键等。理解并利用这个参数,可以让我们编写出更强大、更交互的网页功能。
上一节我们介绍了如何为元素添加事件监听器,本节中我们来看看事件处理函数接收到的这个特殊参数。
事件参数对象
JavaScript引擎会向每一个事件处理函数传递一个参数,通常命名为 event 或 e。这个参数是一个对象,包含了触发事件的详细信息。
button.addEventListener('click', function(event) {
console.log(event);
});
当我们运行上述代码并点击按钮时,控制台会打印出一个 MouseEvent 对象。展开这个对象,可以看到它拥有许多属性,例如:
clientX和clientY:记录了事件发生时,鼠标指针在当前视口内的X和Y坐标。screenX和screenY:记录了事件发生时,鼠标指针在整个屏幕内的X和Y坐标。shiftKey:一个布尔值,表示事件发生时Shift键是否被按下。
即使我们之前没有显式地使用这个 event 对象,它始终会被传入事件处理函数。
不同类型的事件对象
event 参数的具体类型取决于触发的事件种类。不同的事件类型对应着不同的事件对象,这些对象包含与该事件类型相关的特定属性。
例如:
- 键盘事件(如
keydown,keyup)会传递KeyboardEvent对象,其中包含key属性来告知是哪个键被按下。 - 鼠标事件(如
click,mousemove)会传递MouseEvent对象,其中包含像clientX,button等与鼠标相关的属性。
我们可以查阅MDN(Mozilla Developer Network)的文档来了解每种事件对象的所有属性和方法。

实践:使用事件参数
让我们通过一个例子来实践。我们将实现一个功能:仅在按住Shift键时,在控制台打印出鼠标在页面body区域内移动时的坐标。
以下是实现此功能的步骤:
首先,我们需要为整个文档的 body 元素添加一个 mousemove 事件监听器。
document.querySelector('body').addEventListener('mousemove', function(event) {
// 事件处理逻辑将写在这里
});
接着,在事件处理函数内部,我们需要判断Shift键是否被按下。这可以通过检查 event.shiftKey 属性来实现。
document.querySelector('body').addEventListener('mousemove', function(event) {
if (event.shiftKey === true) {
// 只有当Shift键被按下时,才执行以下代码
console.log(`X: ${event.clientX}, Y: ${event.clientY}`);
}
});
现在,保存代码并刷新页面。当你普通移动鼠标时,控制台不会有输出。但当你按住Shift键并移动鼠标时,控制台就会持续输出鼠标指针当前的X和Y坐标。松开Shift键,输出便会停止。
这是因为 if 语句检查 event.shiftKey 的值。当它为 false(即Shift键未按下)时,内部的 console.log 语句会被跳过,事件处理函数实际上没有执行任何操作。


本节课中我们一起学习了JavaScript的事件参数(Event Parameter)。我们了解到这个参数是一个包含事件详细信息的对象,其具体类型(如 MouseEvent)取决于触发的事件。我们通过一个实践例子,学会了如何访问该对象的属性(如 clientX, shiftKey)来增强网页的交互逻辑。掌握事件参数是处理复杂用户交互的关键一步。
094:HTTP基础 🌐

在本节课中,我们将学习HTTP协议的基础知识。HTTP是浏览器(客户端)与服务器之间进行通信所使用的语言或协议。理解HTTP是掌握后续Ajax等客户端-服务器通信技术的关键。
在深入讨论Ajax(一种客户端-服务器通信技术)之前,我们需要先了解浏览器(客户端)与服务器之间通信所使用的语言。这种语言,或者说协议,就是HTTP。因此,让我们先探讨一些HTTP的基础知识。
什么是HTTP? 🤔
HTTP代表超文本传输协议。它是一种基于请求-响应的无状态协议。这里的“无状态”意味着,当服务器响应浏览器的请求时,该响应不依赖于浏览器之前发出的任何请求。实际上,就客户端(浏览器)与服务器通信的实际语言而言,服务器并不知道客户端或浏览器之前是否向它请求过任何内容。
当然,在现实世界中,我们需要能够识别用户身份(例如,确保两分钟前点击购买书籍的用户与之后去购物车结账的是同一个人)。这涉及到会话管理等技术,超出了本课程的范围。但HTTP协议本身是无状态的,其本身并不包含任何能记住你是几分钟前在浏览器和服务器之间发出请求的同一人的机制。
客户端-服务器通信流程 🔄
客户端-服务器通信的工作方式如下:
- 客户端(通常是浏览器)打开到服务器的连接。
- 客户端发送一个针对特定资源的HTTP请求。
- 服务器用一个HTTP响应来回应客户端,通常包含客户端请求的资源(但有时资源未找到或其他错误发生)。
- 客户端(即浏览器)通常会关闭与服务器的连接。
在讨论实际的通信语言(HTTP协议)之前,我们先来了解一下如何在网络上标识资源。
资源标识:URN、URI 与 URL 📍
在讨论网络资源时,有三个术语需要理解:
- 统一资源名称:URN是一种唯一标识或命名资源的方式。但URN不告诉我们如何获取该资源。URN通常被希望在其组织内标准化、保持特定资源唯一性的组织和机构使用。
- 示例:
urn:course:html-css-javascript-web-developers-yakov-haykin
- 示例:
- 统一资源标识符:URI唯一标识资源或其位置。尽管它唯一地标识了资源的位置,但仍不一定告诉我们如何获取该资源,因为URI中通常缺少上下文。它类似于URN,但看起来更像目录结构。
- 示例:
/official_website/index.html
- 示例:
- 统一资源定位符:URL是URI的一种形式,它提供了如何获取该资源的信息。当你在网上向别人询问网站地址时,那就是一个URL。
- 示例:
http://www.mysite.com/official_website/index.html
- 示例:
HTTP请求结构 📤
现在,让我们看看HTTP请求的结构,即浏览器与服务器通信的实际语言。以下是一个GET请求的示例:
GET /official_website/index.html?first=yaakov&last=haykin HTTP/1.1
这个请求以单词GET开头,这就像一个命令,表示“我想要获取某个特定资源”。这个命令被称为方法。该命令带有特定的参数:首先是GET,然后传递一个URI字符串,接着是一个查询字符串(非必需,但常见)。查询字符串本质上是由&符号分隔的名-值对。最后一部分是HTTP/1.1,表示HTTP协议的版本。
你可能会注意到这个命令中缺少网站名称。这是因为这个命令是在浏览器已经连接到服务器(例如mysite.com)之后才发出的。一旦连接建立,浏览器就会在特定网站的上下文中使用HTTP协议来请求一个URI。
常见的HTTP方法 🛠️
HTTP有多种方法,以下是两个对我们非常重要的方法:
- GET方法:用于从特定位置检索资源。使用此方法传递数据时,数据是作为URI的一部分(即查询字符串)发送给服务器的。这通常用于告诉服务器识别或过滤特定资源,而不是为了在服务器上存储数据。
- 数据传递方式:作为URI(查询字符串)的一部分。
- POST方法:向服务器发送数据以进行处理。发送给服务器的数据位于消息体中。服务器通常会响应,可能是一个HTML页面或一些数据,以确认数据已被处理(例如存储或产生了其他副作用)。
- 数据传递方式:作为消息体的一部分。
以下是POST请求的典型结构示例:
POST /process_form.php HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
first=yaakov&last=haykin
你可以看到,请求行是POST /process_form.php HTTP/1.1,没有查询字符串。数据(first=yaakov&last=haykin)作为消息体的一部分传递。请求头(如Content-Type)告诉服务器如何解析消息体。
HTTP响应结构 📥
HTTP响应的结构如下:它以一个响应状态行开始,该行包含三个部分,各部分之间用一个空格分隔:
- HTTP版本
- 响应状态码
- 描述该状态码的英文短语
一个典型的HTTP响应可能如下所示:
HTTP/1.1 200 OK
Date: Mon, 23 May 2022 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 138
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
响应消息体不一定是HTML,也可以是纯文本、XML或JSON等格式。
常见的响应状态码 📊
以下是一些常见的HTTP响应状态码:
- 200 OK:服务器成功处理了请求,并返回了请求的内容。
- 404 Not Found:服务器找不到请求的资源。
- 403 Forbidden:服务器理解请求,但拒绝授权访问(通常是因为缺乏认证,如用户名、密码或证书)。
- 500 Internal Server Error:服务器内部发生错误,通常是未处理的异常。
总结 📝
本节课我们一起学习了HTTP协议的基础知识。要理解客户端-服务器通信,至少需要掌握HTTP的基础。请记住:
- HTTP是一种基于请求-响应的无状态协议。
- GET方法将数据作为URI(查询字符串)的一部分发送给服务器。
- POST方法将数据作为消息体的一部分发送给服务器。
- 常见的响应状态码包括:200(成功)、404(未找到)、403(禁止访问)和500(服务器内部错误)。


掌握这些概念将为学习更高级的Web开发技术(如Ajax)打下坚实的基础。
095:第57讲 第1部分 AJAX基础

在本节课中,我们将要学习AJAX的基础知识。AJAX是一种允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。理解AJAX的工作原理对于构建现代、响应迅速的Web应用至关重要。
什么是AJAX?
AJAX这个词最初代表“异步JavaScript和XML”。虽然它始于XML作为浏览器与服务器之间传输的数据格式,但如今很少有应用真正使用XML。相反,现在大多数时候使用纯文本、HTML,或者我们稍后会讨论的JSON来替代XML。
传统Web应用与AJAX应用流程对比
要理解AJAX存在的意义,我们可以先看看传统Web应用的工作流程。
在传统Web应用流程中,用户从一个网页开始,通常会点击一个按钮。这个操作会生成一个HTTP请求发送到服务器。服务器则响应一个全新的页面。这个新页面可能与原始页面99%相同,可能只有一个字符串发生了变化。然而,在传统流程中,即使只是为了显示一个微小的更新,也必须返回包含所有相同元素的整个页面。
在AJAX Web应用流程中,同样从一个网页开始,它也会发出一个HTTP请求,但这个请求通常由JavaScript发起。服务器回复时,其响应是一小段数据,这通常会被插入到当前页面中。发生变化的只是一小部分数据。
显然,AJAX方式更快。响应速度更快,使用的带宽更少,因为只返回一小段数据,并且用户体验也更好。
同步与异步执行
在深入AJAX工作原理之前,我们先回顾一下同步与异步执行的区别,因为AJAX的第一个词就是“异步”。
同步执行是指一次执行一条编程指令。这意味着在第一条指令完成其执行之前,不能开始执行另一条指令。
另一方面,异步执行是指同时执行多个编程结构。异步指令会立即返回。它发出指令后,立即准备执行下一条指令。该异步指令的实际执行是在一个单独的线程或进程中完成的。
此时你可能会想,如果JavaScript是一种同步语言,其中的一切都是同步执行的,那么AJAX怎么可能实现异步呢?让我们通过一个图表来理解。
浏览器中的JavaScript引擎与AJAX
虽然JavaScript引擎运行在浏览器中,但它并不是浏览器中唯一的组件。浏览器中还有其他软件组件。例如,有一个事件队列负责处理和排队事件,如鼠标点击或浏览器加载。有一个HTML渲染引擎负责获取HTML并知道如何在浏览器网页中显示它。还有WebGL,它允许JavaScript进行一些高级图形工作。此外,还有一个我们可以称之为“HTTP请求器”的组件,它负责发出HTTP请求。
虽然JavaScript引擎本身是同步的,但HTTP请求器组件肯定不是。HTTP请求是异步的。基于这个认识,让我们来看看AJAX的流程。
AJAX基本流程
我们有JavaScript引擎和HTTP请求器组件。JavaScript引擎将执行一些JavaScript代码行,然后最终遇到执行AJAX请求的JavaScript代码行。
此时会发生什么?AJAX请求使用一个特殊的JavaScript对象来访问HTTP请求器组件。请注意,幻灯片上指示的是,下一行JavaScript代码会立即执行,我们不会等待HTTP请求器的响应。相反,当我们发出实际的AJAX请求并访问这个HTTP请求器组件时,除了请求本身,我们还会传入一个JavaScript函数的地址。这个函数将作为响应处理器,用于处理当来自服务器或HTTP请求器的响应返回时的情况。
与此同时,我们继续执行后续代码,JavaScript引擎继续一次一行地执行代码。在某个时刻,它执行了所有能执行的代码,其执行栈为空。如果此时HTTP请求器已经完成并从我们的服务器收到了响应,它就可以使用我们之前提供的那个JavaScript函数的地址,回访JavaScript引擎,开始执行响应的处理。这就是回调函数,也就是那个将处理服务器响应的函数。
以上就是AJAX的基本流程。理论部分就介绍到这里。在本讲座的第2部分,我们将跳转到代码编辑器,看看如何实际操作。
总结


本节课中我们一起学习了AJAX的基础知识。我们了解了AJAX的含义,对比了传统Web应用与AJAX应用在数据交换和页面更新流程上的根本区别,这解释了AJAX在速度和用户体验上的优势。我们明确了同步与异步执行的核心概念,并揭示了AJAX异步特性的实现原理:它依赖于浏览器中异步的HTTP请求器组件,通过回调函数机制,使得JavaScript能在不阻塞主线程的情况下与服务器通信。下一节我们将进入实践环节,在代码编辑器中具体实现AJAX功能。
096:AJAX基础实践

概述
在本节课中,我们将学习AJAX的实际应用,并查看实现AJAX请求的代码。我们将分析一个封装了AJAX功能的JavaScript工具库,理解其工作原理,并了解如何避免异步编程中的常见陷阱。
代码结构与准备
在上一节中,我们介绍了AJAX的理论基础。本节中,我们来看看具体的代码实现。
我们位于第57讲的文件夹中。这里有几个新文件需要注意。第一个是 ajax-utils.js。这个文件包含了用于执行AJAX请求的JavaScript代码。我们将AJAX功能分离到这个独立文件中,以便清晰地查看整个AJAX流程,然后在主脚本中调用它。
在HTML文件 index.html 中,我将 ajax-utils.js 的引用放在了 script.js 之上。这是因为 script.js 会使用 ajax-utils.js 中的功能,所以必须确保浏览器先加载它。
与之前点击按钮后向文本框中的名字问好的例子不同,这次我们希望在点击后,从服务器获取要问候的人的名字。为此,我在服务器的 data 文件夹中准备了一个简单的数据文件 name.txt,其内容仅为“Yaakov”。这是一个纯文本文件。
为了验证在浏览器同步运行时能否访问这个文件,我们可以在地址栏输入URL:data/name.txt。按下回车后,可以看到返回的正是这个简单的字符串。这证明我们可以通过浏览器获取该文件的数据。
分析 AJAX 工具库
现在,让我们关闭文件浏览器,深入查看 ajax-utils.js 文件。
首先,我使用了一个IIFE(立即调用函数表达式)来包裹所有代码。我将全局对象 window 作为参数传入(在函数内部称为 global)。这样做的目的是创建一个命名空间来管理我们的工具函数。
以下是创建命名空间的代码:
var ajaxUtils = {};
我将所有想要对外暴露的功能都附加到这个 ajaxUtils 对象上。第一个函数 getRequestObject 并没有附加到 ajaxUtils 上,这意味着它不会直接暴露给这个IIFE外部的使用者。
创建请求对象
getRequestObject 函数的作用是返回一个HTTP请求对象。它检查当前浏览器支持哪种类型的AJAX对象。
以下是其核心逻辑:
function getRequestObject() {
if (window.XMLHttpRequest) {
return (new XMLHttpRequest());
}
else if (window.ActiveXObject) {
// 为旧版IE浏览器
return (new ActiveXObject("Microsoft.XMLHTTP"));
}
else {
window.alert("Ajax is not supported!");
return(null);
}
}
- 第一个
if语句检查现代浏览器普遍支持的XMLHttpRequest对象,这是最重要的部分。 - 第二个
else if是为非常旧版本的Internet Explorer浏览器准备的,它们使用ActiveXObject。 - 最后的
else块是用于处理完全不支持AJAX的浏览器(这种情况现在极为罕见),它会弹出警告并返回null。
我们将这个创建对象的逻辑单独封装成一个函数,是为了避免在主函数中混杂大量的条件判断语句,使代码更清晰。
发送GET请求的主函数
接下来,我们来看真正核心的函数,它被附加到了 ajaxUtils 对象上。
以下是该函数的定义:
ajaxUtils.sendGetRequest = function(requestUrl, responseHandler) {
var request = getRequestObject();
request.onreadystatechange = function() {
handleResponse(request, responseHandler);
};
request.open("GET", requestUrl, true);
request.send(null);
};
这个 sendGetRequest 函数期望接收两个参数:
requestUrl:要发送GET请求的服务器URL。responseHandler:一个回调函数,用于处理服务器返回的响应结果。
函数内部执行以下步骤:
- 获取请求对象:调用
getRequestObject()来获得一个新的XMLHttpRequest对象。 - 设置状态变更监听器:将请求对象的
onreadystatechange属性设置为一个函数。这个函数会在浏览器与服务器通信状态发生改变时被调用,包括最终完成通信的时刻。在这个监听函数内部,我们调用了handleResponse函数,并传入了当前的request对象和用户提供的responseHandler回调函数。 - 配置并打开请求:使用
request.open方法配置请求。参数"GET"指定请求方法,requestUrl是目标地址,true表示这是一个异步请求。如果设置为false,请求会变成同步的,浏览器将冻结并等待响应,这不是我们想要的。 - 发送请求:最后,
request.send(null)执行并发送请求。前面的步骤都是在设置请求的参数,这一行才是真正触发网络通信。对于GET请求,参数通常通过URL传递,所以send方法的参数为null。如果是POST请求,则需要将请求参数作为字符串放在这里发送。
处理服务器响应
现在,让我们看看 handleResponse 函数是如何工作的。
以下是该函数的代码:
function handleResponse(request, responseHandler) {
if ((request.readyState == 4) && (request.status == 200)) {
responseHandler(request);
}
}
这个函数接收两个参数:request 对象和用户提供的 responseHandler 回调函数。它的职责是进行“脏活累活”的检查,确保在正确的时机调用用户的处理函数。
它检查两个关键条件:
request.readyState == 4:这表示请求已经完成,所有数据都已就绪。readyState有多个状态,但状态4是我们最关心的“完成”状态。request.status == 200:这表示HTTP状态码为200,即“成功”。服务器处理请求一切正常。
只有当这两个条件同时为真时,我们才会调用用户传入的 responseHandler 回调函数,并将 request 对象传递给它。用户可以从这个 request 对象中提取服务器返回的响应数据(例如通过 request.responseText)。
暴露工具库到全局
最后,我们需要将这个工具库暴露给全局作用域,以便在页面其他脚本中使用。
以下是暴露代码:
global.$ajaxUtils = ajaxUtils;
我将本地变量 ajaxUtils 赋值给了全局对象的一个属性 $ajaxUtils。使用美元符号 $ 是为了模仿像jQuery这样的流行库的命名风格,使其看起来更“酷”。
理解异步与闭包的重要性
你可能会好奇,为什么 onreadystatechange 的设置如此复杂?我们为什么不直接写成 request.onreadystatechange = handleResponse; 呢?
原因在于参数传递和异步执行环境。
如果我们直接赋值,将无法把 request 对象和 responseHandler 函数作为参数传递给 handleResponse。因为赋值时我们传递的是函数引用,而不是执行函数,所以不能带括号和参数。
一种常见的错误解决方案是将 request 变量声明在更外层(成为全局或函数作用域变量),这样 handleResponse 似乎就能访问到它了。但这样做会引发一个严重问题:竞态条件。
由于AJAX是异步的,从发送请求到收到响应可能需要几秒钟。在这段时间内,用户或其他代码可能再次调用 sendGetRequest 函数。如果 request 变量是共享的,那么第二个请求就会覆盖第一个请求的 request 对象。当服务器的响应陆续返回时,它们可能会被错误的回调函数处理,或者数据被相互覆盖,导致程序逻辑混乱。
我们当前的实现——通过闭包(即在 onreadystatechange 的回调函数内部引用外层函数的 request 和 responseHandler 变量)——为每一次AJAX调用都创建了一个独立的上下文环境。这样,每个请求及其对应的处理函数都被完美地封装在一起,互不干扰,从而避免了竞态条件。
总结
本节课中,我们一起学习了AJAX的实际编码实现。我们分析了一个结构良好的AJAX工具库,它包含了创建请求对象、发送GET请求和处理响应的完整流程。我们重点理解了:
- 如何使用
XMLHttpRequest对象。 - 如何设置异步请求和状态监听。
- 如何正确地检查请求状态和HTTP状态码以确保成功。
- 最重要的是,我们了解了为什么需要通过闭包来管理异步请求中的变量,以避免危险的竞态条件,确保每个请求和响应的独立性。


在下一部分,我们将看到如何在主脚本 script.js 中实际使用这个 $ajaxUtils 工具库来从服务器获取数据并更新页面。
097:31_第57讲 第3部分 AJAX基础

概述
在本节课中,我们将学习AJAX的基础知识,特别是如何处理异步请求。我们将通过一个具体的例子,演示如何向服务器发送请求,并在收到响应后更新网页内容。我们将重点关注异步调用的特性,以及为什么必须在回调函数中处理服务器返回的数据。
回顾AjaxUtils库
上一节我们介绍了AjaxUtils.js,这是一个我们编写的小型库,用于向服务器发送AJAX请求并处理响应。这个库的核心是sendRequest方法,它允许库的使用者(目前就是我们自己)传入一个函数,这个函数将在服务器返回响应时被调用。
让我们快速回顾一下AjaxUtils.js中的关键代码。我们定义了一个AjaxUtils对象,它有一个sendGetRequest方法。这个方法期望接收两个参数:一个请求URL和一个响应处理函数。
// AjaxUtils.js 中的核心结构
var AjaxUtils = {
sendGetRequest: function(requestUrl, responseHandler) {
// ... 创建XMLHttpRequest对象并发送请求的代码 ...
// 当请求完成时,调用传入的 responseHandler 函数
}
};
分析script.js中的初始代码
现在,让我们看看script.js文件中的代码。这个文件位于lecture57文件夹中。
首先,我们为DOMContentLoaded事件添加了一个监听器。这意味着下面的代码只会在页面加载了所有HTML内容(但可能尚未加载CSS或图片)后执行。
document.addEventListener('DOMContentLoaded', function(event) {
// 代码在这里执行
});
接下来,我们查看HTML文件,发现其中只有一个按钮。我们通过JavaScript“非侵入式”地为此按钮绑定了一个点击事件监听器。
document.querySelector('button').addEventListener('click', function() {
// 点击按钮时执行的函数
});
在点击事件的回调函数中,我们做了以下几件事:
- 将
this的引用保存在变量self中。 - 声明一个变量
name并初始化为空字符串。 - 使用
AjaxUtils.sendGetRequest向服务器发送请求,获取名字数据。 - 在请求的回调函数中,将服务器返回的响应文本赋值给
self.name。 - 在AJAX调用之后,我们尝试用
self.name更新页面内容。
以下是初始的问题代码逻辑:
var self = this;
var name = '';
AjaxUtils.sendGetRequest('/data/name.txt', function(request) {
self.name = request.responseText; // 从服务器获取名字
});
// 注意:这行代码在服务器响应返回前就执行了!
document.querySelector('#content').innerHTML = '<h2>Hello ' + self.name + '!</h2>';
遇到的问题与调试
当我们保存代码并在浏览器中点击按钮时,页面显示的是“Hello !”,名字部分为空,这并不是我们期望的结果。
为了调试,我们首先验证服务器通信是否成功。我们在回调函数中添加了console.log(self.name)来打印获取到的名字。结果显示,self.name确实成功地从服务器获取到了“Yaakov Chaikin”这个值。
那么问题出在哪里?为什么名字没有显示在页面上?
理解异步调用的本质
问题的根源在于我们忘记了AJAX调用是异步的。当我们执行AjaxUtils.sendGetRequest时,JavaScript会立即发送请求,但不会等待服务器响应。它会立刻继续执行下一行代码,也就是尝试用self.name更新HTML的那一行。
在发送请求时,self.name仍然是空字符串(或未定义),因为服务器的响应还没有返回。因此,页面被更新为“Hello !”。稍后,当服务器响应返回时,回调函数被执行,self.name被正确赋值,但此时更新页面的代码早已执行完毕,所以页面上看不到名字。
正确的解决方案
要解决这个问题,我们必须将所有依赖于服务器响应的操作,都放到响应处理函数(即回调函数)内部执行。
以下是修改后的正确代码:
document.querySelector('button').addEventListener('click', function() {
// 发起异步请求
AjaxUtils.sendGetRequest('/data/name.txt', function(request) {
// 所有处理响应的逻辑都在这里
var name = request.responseText; // 从服务器获取名字
// 在这里更新页面内容
document.querySelector('#content').innerHTML = '<h2>Hello ' + name + '!</h2>';
});
// 在这里不能执行依赖响应的操作
});
我们移除了外部的self和name变量,因为不再需要它们。我们将更新#content元素内容的代码直接移到了回调函数内部。这样,只有当服务器响应返回、name变量被赋值后,页面才会被更新。
保存修改后,再次点击按钮,页面成功显示“Hello Yaakov Chaikin!”。这个名字字符串正是从服务器动态获取的。
总结
本节课我们一起学习了AJAX异步调用的核心概念。我们通过一个实例演示了:
- 如何使用工具库发送GET请求。
- 异步代码的执行顺序:发送请求后,后续代码会立即执行,而不会等待响应。
- 关键点:所有依赖于服务器响应数据的逻辑,都必须放在传递给AJAX方法的回调函数内部处理。
- 我们修复了因错误放置响应处理逻辑而导致的页面更新问题,确保了数据在获取后才被使用。


通过这个完整的流程,我们实现了从设置AJAX工具、发起调用,到正确处理异步响应并更新页面的闭环。理解并正确处理异步性是现代Web开发的基础。
098:第58讲 JSON数据处理 📚

在本节课中,我们将要学习JSON数据处理。JSON是一种广泛使用的数据格式,尤其在服务器与客户端之间传递数据时非常常见。
什么是JSON? 🤔
JSON代表JavaScript对象表示法。它是一种轻量级的数据交换格式。JSON是数据的简单文本表示,其优点在于易于人类读写,同时也易于机器解析和生成。在许多方面,JSON与XML类似,但JSON的语法更简单、更简短。最重要的是,与XML一样,JSON完全独立于任何编程语言。
JSON语法规则 📝
JSON的语法规则非常简单。它是JavaScript对象字面量语法的子集,但有两点例外:
- 属性名称必须包含在双引号中。
- 字符串值也必须使用双引号。
除此之外,其他语法与JavaScript对象字面量完全相同。
让我们来看一个例子:
{
"firstName": "Yaakov",
"lastName": "Chaikin",
"likesChineseFood": false,
"numberOfDisplays": 2
}
这是一个典型的JSON字符串,可以从服务器发送到客户端。即使你在浏览器的JavaScript程序中,也可以将这个字符串存储在一个变量中,只需在其外部加上单引号即可。这是因为字符串内部已经使用了双引号。

这里需要澄清一个常见的误解:JSON不是JavaScript对象字面量,它只是一个字符串。JSON的语法基于对象字面量,但两者并不等同。
JSON与JavaScript对象的转换 🔄
如果你想在JavaScript中获得一个对象,需要将JSON字符串转换为对象。反之,如果你想获得JSON字符串,则需要将对象转换回JSON字符串。JavaScript提供了两个实用工具函数来实现这种转换:
- 将JSON字符串转换为对象:
JSON.parse() - 将对象转换为JSON字符串:
JSON.stringify()
实践:在代码中处理JSON 💻
上一节我们介绍了JSON的基本概念和转换方法,本节中我们来看看如何在代码中实际应用。
我们将重构之前的示例,使其从服务器获取JSON数据,而不是普通字符串。
首先,我们有一个名为 namethat.json 的JSON文件,其内容与上面例子中的对象类似,但它是一个存储在服务器上的字符串。
接下来,我们需要更新我们的 ajaxutils.js 工具函数,使其能够预处理JSON响应。我们为 sendGetRequest 函数添加一个名为 isJsonResponse 的可选参数。这样,调用者可以指定是否期望JSON格式的响应。
在 handleResponse 函数中,我们进行如下处理:
- 如果没有提供
isJsonResponse参数,则默认其为true。 - 如果
isJsonResponse为true,则使用JSON.parse()将响应的文本解析为JavaScript对象,然后将这个对象传递给回调函数。 - 如果
isJsonResponse为false,则将原始的响应文本传递给回调函数。
现在,让我们看看 script.js 中的变化。调用 sendGetRequest 的方式与之前相同,但这次在响应处理函数中,我们收到的 response 参数已经是一个由JSON字符串转换而来的JavaScript对象。
因此,我们可以像使用普通JavaScript对象一样使用它:
var message = response.firstName + " " + response.lastName;
if (response.likesChineseFood) {
message += " likes Chinese food.";
} else {
message += " does not like Chinese food.";
}
message += " and uses " + response.numberOfDisplays + " displays for coding.";
我们可以直接访问 response 对象的属性,如 firstName、likesChineseFood 等。likesChineseFood 属性在JSON中是一个布尔值 false,因此可以在 if 语句中直接使用。numberOfDisplays 是一个数字,我们也可以对其进行运算。
最后,我们将构建好的 message 显示在网页上。
通过修改服务器上的JSON文件(例如将 likesChineseFood 改为 true,或将 numberOfDisplays 加1),然后重新点击按钮,可以看到页面上的信息会相应更新,这证明了我们成功地解析并使用了JSON数据。

总结 ✨


本节课中我们一起学习了JSON数据处理。总结如下:
- JSON是一种轻量级的数据表示格式,非常适合在服务器和客户端之间传递数据。
- JSON的语法基于JavaScript对象字面量,但请记住,JSON本身只是一个字符串。
- 使用
JSON.parse()可以将JSON字符串转换为JavaScript对象。 - 使用
JSON.stringify()可以将JavaScript对象转换为JSON字符串。 - 在实际开发中,我们经常需要从服务器获取JSON数据,解析后在前端使用,这大大增强了Web应用处理结构化数据的能力。
099:修复移动端导航菜单自动折叠

在本节课中,我们将学习如何使用JavaScript修复一个移动端导航菜单的交互问题。具体来说,我们将为David Chohu's China Bistro网站的导航菜单添加一个功能:当菜单展开后,如果用户点击页面其他区域,菜单会自动折叠收起。
概述
上一节我们介绍了JavaScript的基础知识,本节中我们来看看如何应用这些知识来解决一个实际的网页交互问题。我们将修复移动端视口下,导航菜单按钮在失去焦点后不会自动折叠的行为。
问题分析
目前,当浏览器窗口宽度小于特定断点时,导航菜单会变成一个可折叠的按钮。点击此按钮可以展开菜单,但点击页面其他区域时,菜单不会自动收起。这影响了移动端用户的体验。
我们需要为这个按钮添加一个事件处理器,监听其“失去焦点”的事件。在JavaScript中,这被称为 blur 事件。
实现步骤
以下是实现自动折叠功能的具体步骤。
1. 确定执行时机
我们显然不希望在大屏幕状态下为所有按钮添加此行为,而只希望在出现可折叠按钮的断点下生效。
通过浏览器开发者工具可以观察到,这个按钮大约在宽度为768像素的断点处出现。这与我们之前设定的CSS媒体查询断点一致。
2. 编写JavaScript代码
我们需要在 script.js 文件中添加代码。由于Bootstrap的折叠插件依赖于jQuery库,因此我们在此处也必须使用jQuery。
首先,我们需要确保代码在DOM加载完成后执行。在jQuery中,$(function() { ... }) 等同于原生JavaScript的 DOMContentLoaded 事件监听器。
$(function() {
// 代码将在此处执行
});
接下来,我们需要为导航切换按钮绑定 blur 事件。
$(function() {
$('#navbarToggle').blur(function(event) {
// 事件处理逻辑
});
});
上面的代码使用jQuery选择器 $('#navbarToggle') 选中ID为 navbarToggle 的按钮元素,这等同于原生代码 document.querySelector('#navbarToggle')。.blur() 方法用于绑定失去焦点事件。
3. 添加条件判断与折叠逻辑
在事件处理函数中,我们需要先判断当前屏幕宽度是否小于768像素,即是否处于移动端断点。如果是,则找到可折叠的导航菜单元素并将其隐藏。
$(function() {
$('#navbarToggle').blur(function(event) {
var screenWidth = window.innerWidth;
if (screenWidth < 768) {
$('#collapsable-nav').collapse('hide');
}
});
});
window.innerWidth用于获取浏览器窗口的内部宽度。if (screenWidth < 768)判断是否处于移动端视图。$('#collapsable-nav')选中ID为collapsable-nav的导航菜单容器。.collapse('hide')是Bootstrap折叠插件提供的方法,用于隐藏元素。
4. 代码整合与解释
将以上步骤整合,完整的代码如下所示:
// 等待DOM加载完成
$(function() {
// 为导航切换按钮绑定失去焦点事件
$('#navbarToggle').blur(function(event) {
// 获取当前窗口宽度
var screenWidth = window.innerWidth;
// 仅在移动端断点下执行折叠操作
if (screenWidth < 768) {
// 使用Bootstrap的collapse方法隐藏导航菜单
$('#collapsable-nav').collapse('hide');
}
});
});
这段代码的含义是:当页面加载完成后,为导航按钮设置一个监听器。一旦该按钮失去焦点(即用户点击了其他地方),就检查当前窗口宽度。如果宽度小于768像素,则调用Bootstrap插件的方法将导航菜单折叠起来。
效果验证
保存代码并刷新浏览器页面。将浏览器窗口宽度调整至小于768像素,点击导航按钮展开菜单,然后点击页面其他任意区域。可以观察到,菜单会如预期般自动折叠收起。
总结


本节课中我们一起学习了如何利用JavaScript和jQuery来增强网页的交互性。我们通过监听 blur 事件,并结合屏幕宽度判断,成功修复了移动端导航菜单的自动折叠功能。这个实例展示了如何将JavaScript事件处理与现有的CSS框架(如Bootstrap)结合,以解决具体的用户体验问题。
100:34_第60讲 动态加载首页内容

在本节课中,我们将学习如何动态加载单页应用(SPA)的首页内容。我们将通过Ajax技术,在不刷新整个页面的情况下,仅更新页面的核心区域,从而提升用户体验和页面性能。
上一节我们介绍了单页应用的基本概念。本节中,我们来看看如何具体实现首页内容的动态加载。
首先,我们需要理解为何要采用动态加载。我们正致力于构建一个单页应用(SPA)。这种现代Web开发技术依赖于Ajax,它允许我们只动态插入页面主体内容,而保持页面的其他部分(如导航栏、页脚)不变。这样做的好处是页面加载更快,并且避免了整个页面的刷新,从而提供更流畅的用户体验。
现在,让我们进入代码编辑器。我们位于 lecture 60 的 after 文件夹中,查看 index.html 文件。为了实现整个主体内容的动态化,我们实际上需要清空原有的静态内容。这正是我们已经完成的工作。
在 index.html 中,主体内容区域已不再包含具体的HTML代码,取而代之的是一个占位符:
<div id="main-content"></div>
那么,原有的内容去了哪里呢?我们新增了一个名为 snippets 的文件夹。打开它,里面有一个 home-snippet.html 文件。这个文件包含了我们首页的全部内容,包括巨幕横幅(jumbotron)、首页磁贴(tiles)等。我们预先修改了其中的一些链接,例如菜单项现在不再是一个普通的超链接,而是一个带有 onclick 事件处理器的链接,为后续步骤做准备。
本节课的目标是:将 home-snippet.html 中的内容动态插入到 index.html 的 #main-content 占位符中。

让我们转到 scripts 文件夹,查看预先编写好的代码。首先,顶部的代码是上一讲编写的,用于在菜单失去焦点时折叠响应式按钮。
接下来,我们设置了一个立即调用的函数表达式(IIFE),它将 window 对象作为参数传入。我们在内部创建了一个命名空间 $dc(代表 David Cho),并将它暴露给全局的 window 对象,以便在其他脚本中使用。
以下是代码的核心部分:
首先,我们定义了首页HTML片段的URL路径:
var homeHtmlUrl = "snippets/home-snippet.html";
接着,我们编写了一个便捷函数 insertHtml。它的作用是:给定一个CSS选择器和一段HTML字符串,它将找到对应的目标元素,并将其 innerHTML 设置为提供的HTML字符串。
function insertHtml(selector, html) {
var targetElem = document.querySelector(selector);
targetElem.innerHTML = html;
}
然后,我们需要一个函数来显示加载图标 showLoading。在Ajax异步请求数据时,我们希望向用户显示一个动画图标,而不是空白区域。这个函数会构建一个包含加载GIF图片的 div 元素,并利用上面定义的 insertHtml 函数将其插入到指定的选择器元素中。
function showLoading(selector) {
var html = "<div class='text-center'>";
html += "<img src='images/ajax-loader.gif'></div>";
insertHtml(selector, html);
}
这里使用的 ajax-loader.gif 图标可以从 ajaxload.info 等网站免费生成和下载。

现在,我们设置当页面DOM内容加载完毕后的执行逻辑。我们使用 document.addEventListener(‘DOMContentLoaded‘, ...) 来确保DOM就绪后再执行脚本。
在事件监听器内部,我们首先调用 showLoading(‘#main-content‘),在内容区域显示加载动画。紧接着,我们发起Ajax请求(使用 $ajaxUtils.sendGetRequest 函数)来获取 home-snippet.html 文件的内容。我们将最后一个参数设为 false,表示返回的不是JSON,而是纯文本(HTML代码)。
当请求成功返回后,在回调函数中,我们再次使用 insertHtml 函数,将获取到的HTML响应文本插入到 #main-content 元素中,替换掉加载图标。
document.addEventListener("DOMContentLoaded", function (event) {
showLoading("#main-content");
$ajaxUtils.sendGetRequest(
homeHtmlUrl,
function (responseText) {
insertHtml("#main-content", responseText);
},
false
);
});
保存代码并刷新浏览器,现在首页内容应该通过Ajax动态加载并显示出来。



为了验证,我们可以打开浏览器的开发者工具,切换到“Network”(网络)面板,并过滤“XHR”请求。刷新页面后,你会看到一个对 home-snippet.html 的Ajax请求,这证实了我们的动态加载机制正在工作。
目前,点击页面上的“Menu”按钮会报错,提示 $dc.loadMenuCategories 不是一个函数。这是因为我们还没有定义这个函数,它的功能是加载并显示菜单分类页面。这将是下一讲的内容。


本节课中我们一起学习了单页应用(SPA)中动态加载内容的基本原理。我们通过Ajax技术,将首页的HTML片段从独立文件异步加载并插入到主页面中,实现了无刷新更新页面内容。我们还创建了显示加载状态的函数,以改善用户体验。在下一讲,我们将继续完善SPA,实现页面间的动态导航。
101:动态加载菜单分类视图(第一部分)

在本节课中,我们将学习如何动态地从远程服务器获取菜单分类数据,并将其插入到我们的网页中。我们将了解跨域资源共享(CORS)的概念,并查看我们将要使用的数据格式。
概述
上一节我们动态地插入了主页的内容,包括菜单特色和地图板块。本节中,我们将在用户点击菜单板块时,动态地插入菜单分类。我们已经实现了这个功能,让我们先看看效果:点击按钮后,菜单分类会显示出来,并带有所有图片,这些图片也是可点击的。这与之前类似,但现在我们拥有的是对应正确分类的真实图片,而非占位符。这些内容显然是动态填充的。
此时,我们需要数据来填充这些菜单分类,并指向每个分类特有的图片。
获取数据
那么,我们如何做到这一点呢?首先,如何获取数据?
我们有一个部署在Heroku上的应用,名为davidsrestaurant。这个应用本质上是一个REST API,为我们的应用提供所需的JSON数据。尽管实际的网站将托管在GitHub Pages上,但数据实际上将来自Heroku上的这个应用。
重要更新(2022年11月28日):Heroku将取消免费套餐,这意味着我们将无法再在Heroku上免费部署应用。因此,我们将改用Google Firebase来部署我们的数据作为REST API。
以下是两个关键更新:
- 我们将使用
firebase.io作为获取分类和后续课程中菜单项的URL,而不是屏幕上显示的Heroku URL。正确的URL已更新在GitHub仓库的FAQ中,所有示例代码也已相应更新。 - 本课程中引用的Ruby on Rails网络开发课程系列已不再在Coursera上提供,但这不影响本课程及其后续AngularJS课程的学习。
这个应用实际上是一个Ruby on Rails应用。如果你想了解更多关于如何自己创建这类应用,建议你查找相关的Ruby on Rails学习资源。
跨域资源共享(CORS)
数据托管在Heroku(或Firebase),而我们的应用目前为开发目的托管在本地(通过BrowserSync),最终将托管在GitHub。它们位于不同的域名下,这带来了一个问题。
每个浏览器都有一个称为“同源策略”的安全机制。这意味着,如果一个HTML页面中的JavaScript试图通过Ajax访问与该页面来源不同的域名,浏览器会阻止此行为。这是为了防止恶意脚本利用用户计算机在防火墙后的权限,访问不应访问的内部网络资源。
那么,我们如何才能获取数据呢?浏览器会阻止我们。
这时,一项名为“跨域资源共享”的新技术出现了。CORS通过一些HTTP标头来工作。服务器(如我们的David‘s Restaurant应用)会向浏览器发送这些标头,告知浏览器允许来自我们网页的JavaScript访问其数据是安全的。这就是我们能够从本地开发服务器或GitHub Pages访问Heroku/Firebase上数据的方式。
查看数据格式
了解数据来源和获取方式后,我们来看看具体的数据格式。直接阅读原始JSON可能比较困难。
这里有一个小技巧:你可以打开Chrome开发者工具,进入“Network”标签页,刷新页面。你会看到一个名为categories.json的请求,这就是我们感兴趣的URL。点击它,然后进入“Preview”标签页,Chrome会以格式良好的方式显示JSON预览。
你可以看到,categories.json返回的JSON实际上是一个对象数组。每个对象包含以下属性:
idnameshort_namespecial_instructions
下节预告
现在我们已经知道从哪里获取数据以及如何(在CORS的帮助下)获取数据。在下一部分,我们将继续执行代码,使用Ajax拉取这些数据,正确解析数据,并生成动态HTML以将其插入为页面的主要内容。

总结

本节课我们一起学习了动态加载菜单分类视图的背景知识。我们了解了数据来自一个远程REST API,并遇到了因不同域名部署而产生的同源策略问题,以及CORS技术如何帮助我们解决这个问题。我们还查看了我们将要使用的JSON数据的具体结构。下一节,我们将动手编写代码来实现数据的获取与展示。
102:动态加载菜单分类视图

在本节课中,我们将学习如何通过Ajax请求从服务器获取JSON数据,解析数据,并使用这些数据动态生成和插入HTML内容,以构建一个完整的菜单分类页面。
在上一部分,我们讨论了跨源资源共享以及数据来源。我们有一个部署在Heroku上的应用,它将提供本课所需的JSON数据。本节中,我们将详细讲解如何编写代码,向该应用发起Ajax请求,获取JSON数据,解析数据,并填充一个HTML片段,最终将其插入到我们的主页中。这个HTML片段包含了显示菜单分类页面所需的所有完整标签。
首先,我们将查看几个代码片段。我们移除了原有的菜单分类HTML,并用几个片段替代。我将菜单分类页面分成了两个片段:一个是categories-title-snippet.html,它只包含该页面的标题;另一个是category-snippet.html,它只包含单个分类的HTML结构。
我将标题分离出来的原因是,category-snippet.html只包含一个分类的HTML。当我们获取到一个分类数组时,我们将遍历这个数组。每次获取到一个分类信息,我们就用这个片段作为模板,填充数据,复制它,然后插入到页面中,从而逐步构建我们的HTML。
如果你仔细观察category-snippet.html,会发现我使用了双花括号{{}}(Mustache语法)来标记需要插入数据的属性位置。虽然有很多库可以很好地处理这种模板,但为了不引入更多库,我们将采用一种简单的方法:将这个HTML当作字符串,然后查找并替换其中的属性占位符。
在循环中,我们将获取实际数据值,并用它们替换HTML字符串中对应的属性名占位符。
在这个片段中,我为每个菜单分类创建了一个onclick事件,它将调用一个函数(例如loadMenuItems)。我们需要传入分类的short_name属性。查看我们的JSON数据,每个分类都有一个short_name,这非常重要,因为我们将用它从应用中获取其他相关数据。
接下来,我们转到script.js文件,看看如何实现这一切。
首先,我们在文件顶部声明了一些URL:
allCategoriesUrl:指向我们服务器端应用的URL,用于获取包含所有菜单分类的JSON字符串。categoriesTitleHtml和categoryHtml:指向我们两个HTML片段的路径。
我们之前见过insertHtml和showLoading函数。这里有一个新函数insertProperty。
insertProperty函数的作用是:接收一个字符串、一个属性名和一个属性值,然后将字符串中所有匹配该属性名(被双花括号包围)的地方替换为实际的属性值。它使用字符串的replace方法和一个简单的正则表达式(带有g标志),以确保替换所有出现的地方,而不仅仅是第一个。
这就是我们用来将数据填充到HTML片段模板中的方法。
向下滚动,我们看到用于主内容的ajaxGetRequest函数,以及我们的loadMenuCategories函数。当需要将分类加载到页面时,会触发这个函数。
其流程如下:
- 首先显示加载图标。
- 发起Ajax请求,使用
allCategoriesUrl从Heroku应用获取数据。 - 请求成功后,调用
buildAndShowCategoriesHTML函数,并将获取到的JSON对象(已转换为JavaScript对象)作为参数传入。
但在完成Ajax请求之前,我们还需要获取标题HTML片段。因此,在第一个请求的回调函数中,我们发起了第二个Ajax请求来获取categoriesTitleHtml片段。接着,在第二个请求的回调函数中,我们又发起了第三个Ajax请求来获取categoryHtml片段。
这种嵌套是必要的,因为只有在第一个请求完成后,我们才能在正确的上下文中处理其结果并发起后续请求。
当这三个请求都完成后,我们就拥有了所需的所有数据:分类对象、标题片段字符串和单个分类的HTML片段字符串。
此时,我们调用buildCategoriesViewHtml函数,传入分类对象、标题片段和分类片段。这个函数将同步地构建出完整的视图HTML字符串。
构建完成后,我们使用insertHtml函数将这个完整的HTML字符串插入到ID为main-content的元素中。
请注意,这两个获取HTML片段的Ajax调用都传递了false参数,这是为了告诉Ajax工具不要尝试将这些HTML片段当作JSON来处理。
现在,让我们看看buildCategoriesViewHtml函数。这是一个相当简单的函数:
- 它首先创建一个
section元素,并为其添加row类。 - 然后,它遍历传入的分类对象数组。
- 对于数组中的每个分类,它复制一份分类HTML片段字符串。
- 接着,它使用
insertProperty函数,将片段中所有{{name}}占位符替换为实际的分类名称,将所有{{short_name}}占位符替换为实际的短名称。 - 每次循环后,将处理好的单个分类HTML追加到最终的HTML字符串中。
- 遍历结束后,关闭
section标签,并返回构建好的完整HTML字符串。
这里的关键点是,每次循环都创建了分类片段的一个新副本(通过值复制),因此每个分类的数据都是独立插入的,互不干扰。
最终返回的HTML被传递回调用处,并插入到页面中。这就是当我们访问首页并点击“菜单”链接时,页面能够动态加载并显示所有菜单分类的过程。
本节课中,我们一起学习了如何通过多个Ajax请求获取数据和模板,如何使用简单的字符串替换来填充模板,以及如何将动态生成的HTML内容插入到页面中。现在,菜单分类页面的动态加载功能已经完成。


接下来,我们将进入单个分类页面的开发。当用户点击某个分类(如“汤”或“开胃菜”)时,页面应该能显示该分类下的所有菜单项。这将是我们下一步要编写代码实现的功能。
103:动态加载单个分类视图 🍽️

在本节课中,我们将学习如何动态加载并展示餐厅菜单中单个分类下的所有菜品。我们将通过Ajax请求从服务器获取特定分类的数据,并使用JavaScript模板动态构建和渲染页面。
在上一讲中,我们实现了从服务器动态获取数据来填充菜单分类页面。该服务器是一个基于Ruby on Rails实现的Heroku应用。本节中,我们将进行非常类似的操作,但这次我们将获取特定分类以及该分类下的所有菜单项。
现在,我们来看一下 category-snippet.html 文件,它位于 lecture62/after 文件夹中。回顾这个我们在上一讲中实现的代码片段,是为了仔细查看我们设置的“钩子”。这个钩子使我们能够点击单个分类,进而获取该分类的菜单项。
这个钩子就在这里:onclick="$dc.loadMenuItems('{{short_name}}')"。loadMenuItems 函数我们稍后会详细查看。但 short_name 属性至关重要,因为它似乎是我们获取特定分类数据的关键。
让我们回到浏览器,打开我们的“David's Restaurant”应用。可以看到另一个REST API端点(endpoint):menu_items.json。这正是我们需要的。我们向它传递一个名为 category 的请求参数,根据这个参数值(字母或字母组合),我们会得到不同的结果。
例如,如果我们传入 VG(代表蔬菜),并查看返回的JSON字符串,它会转换为一个包含两个属性的对象:category 和 menu_items。category 本身又是一个对象,而 menu_items 是一个包含多个菜单项对象的数组。如果我们传入 L(代表午餐),返回的数据就会变成午餐分类及其所有菜品。
关键在于,short_name 对我们非常重要,因为我们将把它附加到基础URL上。基础URL是 menu_items.json?category=,我们将把用户点击的分类的短名称(如 VG 或 L)附加在后面,从而获取特定分类的数据。
让我们回到代码编辑器,看看我们将用于本节的几个代码片段。第一个是 menu-items-title.html。为了理解最终效果,我们可以先看看实际页面:在首页点击“菜单”,进入分类页面,再点击“午餐”,就会进入“午餐菜单”的特定分类页面。可以看到,这里有许多带图片的菜品。
这个页面由两个主要组件构成:
- 标题组件,显示“午餐菜单”以及该分类的特殊说明。
- 列表组件,逐一列出该分类下的每个菜品。
以下是 menu-items-title.html 的内容概要。我们可以看到,菜单名称(如“午餐菜单”)和特殊说明都将通过双花括号 {{ ... }} 绑定到对应的属性值上。从上一讲我们知道,这能让我们轻松地将属性值插入到这些占位符中。
接下来,我们查看 script.js 文件,了解如何实现这一功能。

首先,我们定义了几个新的URL:
menuItemsUrl:基础URL,即menu_items.json?category=,我们将在运行时把分类短名称附加在后面。menuItemsTitleHtmlUrl和menuItemHtmlUrl:分别对应标题和单个菜品项的HTML模板片段。
向下滚动,我们会看到 loadMenuItems 函数。它看起来与上一讲的 loadMenuCategories 函数非常相似。主要区别在于,当我们发起Ajax请求时,我们组合了 menuItemsUrl 和传入的 shortName 参数(这个参数来自HTML片段中的 onclick 事件)。这样,我们就构建出了完整的请求URL。
请求成功后,会调用 buildAndShowMenuItemsHTML 函数来处理返回的数据。这个函数位于文件底部。
buildAndShowMenuItemsHTML 函数的工作流程与之前的类似,但这次我们需要处理两个Ajax请求:
- 获取
menu-items-title.html模板片段。 menu-item.html模板片段。
menu-item.html 片段内容较多,它实际上是从我们之前编写的静态单个分类页面中提取出来的。它的作用是将菜品对象的各个属性(如名称、描述、价格、图片路径)放置到HTML模板中正确的位置。
例如,它需要构造图片的URL(图片位于 images/menu/ 目录下,按分类短名称和菜品编号组织),处理价格(有时只有一种价格,没有大/小份之分),以及插入菜品名称和描述。
在 script.js 中,我们构建最终的 menuItemsViewHtml 时,传入了三个东西:从服务器返回的 categoryMenuItems 数据对象、标题HTML片段和单个菜品HTML片段。
然后,我们将构建好的完整HTML插入到ID为 main-content 的元素中,页面就渲染完成了。
现在,让我们仔细看看 buildMenuItemsViewHtml 函数。与之前类似的函数不同,我们这次也需要在标题片段中插入值(即分类名称和特殊说明)。这些值来自 categoryMenuItems.category 对象。
因此,我们首先处理标题片段,将分类名称和特殊说明插入进去,得到一个已经包含正确数据的标题HTML字符串。我们将以此作为最终HTML的开头。
接着,我们开始遍历 categoryMenuItems.menu_items 数组中的每一个菜品项。对于每一项,我们使用 menuItemHtml 片段,并插入对应的属性值,如菜品名称、描述、价格等。
这里有两个特殊的辅助函数:insertItemPrice 和 insertItemPortionName。之所以需要它们,是因为并非每个菜品都有“大份价格”和“小份价格”。我们需要检查某个价格属性是否存在。如果不存在,我们就用空字符串替换掉模板中的对应占位符,而不是向用户显示 {{price_large}} 这样的文本。如果存在,我们还会使用 .toFixed(2) 方法将价格格式化为标准的美元金额(如 $10.00)。
最后,还有一个重要的布局问题需要处理。我们无法控制每个菜品描述框的高度,有些可能很长,有些可能很短。为了确保我们的网格布局不被破坏,我们需要在每两个菜品项之后添加一个清除浮动的HTML元素(<div class="clearfix"></div>)。
我们在遍历菜品数组时实现这一点。利用JavaScript的取模运算符 %,我们检查当前项的索引 i。如果 i % 2 != 0(即索引为奇数,对应第2、4、6...项,因为数组索引从0开始),就在该项的HTML后面追加清除浮动的代码。这样就能确保每行只显示两个菜品,布局保持整齐。
最终,所有拼接好的HTML被保存到 menuItemsViewHtml 变量中,并插入到页面的 main-content 区域。这就是当我们点击一个分类时,能够动态加载并显示该分类下所有菜品页面的全过程。

本节课总结
在本节课中,我们一起学习了如何动态加载单个菜单分类的视图。我们回顾了如何通过 short_name 参数构造特定的API请求URL,以获取单个分类及其所有菜品的数据。我们分析了用于构建页面的两个HTML模板片段(标题和单个菜品),并详细讲解了 script.js 中的核心函数 loadMenuItems 和 buildMenuItemsViewHtml 是如何工作的。关键点包括:处理可能缺失的价格数据、格式化金额,以及通过计算索引来插入清除浮动元素以维护网格布局的完整性。通过这一系列步骤,我们实现了从用户点击到完整页面渲染的动态数据加载流程。
104:通过JavaScript切换激活按钮样式 🎯

在本节课中,我们将学习如何使用JavaScript动态切换导航按钮的激活状态,以提升单页面应用(SPA)的用户体验。我们将修复一个常见的界面问题:当用户浏览不同页面内容时,导航栏的“高亮”指示未能正确更新。
概述
在上一节中,我们几乎完成了David Cho China Bistro网站的开发,但还存在一个小问题。具体表现为:当用户点击进入“菜单分类”页面时,导航栏中的“菜单”按钮并未像预期那样被高亮显示。这是因为我们的网站是单页面应用,页面切换时不会重新加载整个HTML,导致原本通过HTML class 属性设置的激活状态(active)无法自动更新。
本节我们将编写一个JavaScript函数 switchMenuToActive,其核心逻辑是:
- 移除当前可能处于激活状态的按钮(如“首页”按钮)的
active类。 - 添加
active类到目标按钮(“菜单”按钮)上。
我们将通过操作DOM元素的 className 属性来实现这一功能。
问题复现与思路分析
首先,让我们在浏览器中查看问题。当网站加载后,默认“首页”按钮处于激活状态。点击“菜单”按钮进入菜单分类页面后,内容区域发生了变化,但导航栏的“菜单”按钮并未高亮,而“首页”按钮的高亮状态依然保留,这会给用户造成困惑。

问题的根源在于,active 类是通过CSS定义按钮高亮样式的。在传统的多页面网站中,每个页面会单独加载,其对应的导航按钮在HTML中直接写有 active 类。但在我们的单页面应用中,页面内容通过JavaScript动态切换,我们需要手动更新导航按钮的激活状态。
因此,我们的解决方案是:在加载菜单分类页面内容时,调用一个JavaScript函数来更新导航栏按钮的CSS类。
代码实现详解
现在,让我们回到代码编辑器,看看如何具体实现 switchMenuToActive 函数。
第一步:移除首页按钮的激活状态
首先,我们需要找到ID为 nav-home-button 的首页按钮元素,并移除其 class 属性中的 active 类。
以下是实现此步骤的代码:
// 获取首页按钮当前的class属性字符串
let classes = document.querySelector('#nav-home-button').className;
// 使用正则表达式全局替换掉‘active’类名
classes = classes.replace(/active/g, '');
// 将处理后的class字符串重新赋值给元素
document.querySelector('#nav-home-button').className = classes;
代码解释:
document.querySelector(‘#nav-home-button’)通过ID选择器精确获取首页按钮的DOM元素。.className属性用于获取或设置元素的class属性值,它是一个由空格分隔的字符串。.replace(/active/g, ‘’)使用正则表达式/active/g匹配字符串中所有出现的“active”字样(g表示全局匹配),并将其替换为空字符串,从而移除它。
第二步:为菜单按钮添加激活状态
接下来,我们需要为ID为 nav-menu-button 的菜单按钮添加 active 类。在添加之前,最好先检查它是否已经拥有这个类,避免重复添加。
以下是实现此步骤的代码:
// 获取菜单按钮当前的class属性字符串
let classes = document.querySelector('#nav-menu-button').className;
// 检查‘active’类是否不存在
if (classes.indexOf('active') === -1) {
// 如果不存在,则在原class字符串后追加一个空格和‘active’
classes += ' active';
// 将新的class字符串重新赋值给元素
document.querySelector('#nav-menu-button').className = classes;
}
代码解释:
indexOf(‘active’)方法用于在字符串中查找子串。如果找不到,则返回-1。我们用它来判断active类是否已存在。+= ‘ active’是classes = classes + ‘ active’的简写,它在原有类名字符串末尾追加一个空格和“active”。添加空格是为了正确分隔多个类名。
功能测试与验证
函数编写完成后,我们需要在适当的时机调用它,例如在加载菜单分类页面内容的Ajax请求成功之后。
让我们启动开发服务器并测试效果。刷新页面后,点击“菜单”按钮:

现在可以看到,“菜单”按钮成功被高亮显示。即使我们进一步点击某个具体的菜单分类,查看该分类下的所有项目,“菜单”按钮的高亮状态依然保持。

反之,当我们点击“首页”按钮返回主页时,switchMenuToActive 函数(或类似的针对首页的函数)会被调用,将高亮状态切换回“首页”按钮。
至此,导航栏的状态指示功能已完全实现。

总结
在本节课中,我们一起学习了如何通过JavaScript动态管理DOM元素的CSS类,以实现交互式界面状态反馈。我们重点掌握了:
- 问题识别:在单页面应用中,导航状态需要手动同步。
- 核心操作:使用
element.className属性获取和设置类名,结合String.replace()和String.indexOf()方法进行类名的移除与添加。 - 实现步骤:
- 移除旧激活按钮的
active类。 - 为新激活按钮添加
active类(添加前先做检查)。
- 移除旧激活按钮的
- 最终成果:网站导航栏现在能正确反映用户的当前位置,提供了清晰的视觉指引。
这个功能的完成标志着我们网站的核心交互已经全部实现。接下来,只需补充“关于我们”和“获奖信息”等页面的静态内容,整个网站就可以准备部署上线了。



105:39_课程总结

概述
在本节课中,我们将对《Web开发者的HTML, CSS, and Javascript》课程进行总结,回顾学习成果,并为你的后续学习路径提供建议。
你已经完成了这门课程。恭喜你,也感谢你在这段学习旅程中一直坚持。我希望你喜欢这门课程并学到了很多知识。
那么,接下来你该做什么呢?如果我可以建议,本专业系列课程的下一门是《使用Angular JS开发单页Web应用》。
由Google支持的Angular是当今最流行的前端框架之一。它之所以流行是有原因的:你可以用很少的代码实现非常丰富的功能。
因此,请查看相关课程并报名学习。整个约翰霍普金斯大学的团队都非常努力地制作了这门课程,并以最佳的方式呈现给你。
如果你喜欢这门课程,我想请你花几分钟时间,给这门课程一个五星评价。这对我们来说意义重大。
最后,请保持联系。你可以在Twitter上关注我,在LinkedIn上与我建立联系,并加入我们专门为本专业前端课程创建的Facebook页面。所有这些链接都在本视频下方。

感谢你的观看,并祝你一切顺利。

浙公网安备 33010602011771号