密西根大学每个人的-Web-开发笔记-全-

密西根大学每个人的 Web 开发笔记(全)

001:为何选择PHP 🎯

在本节课中,我们将探讨为何选择PHP作为学习Web应用程序开发的首选语言。我们将分析其持久性、普适性以及作为Web开发基础的核心价值。

大家好,我是查尔斯·塞弗伦,欢迎来到《面向所有人的Web应用程序》课程。

我经常被问到的一个问题是:为什么选择教授PHP?原因在于,我经历过很多技术变迁。未来最前沿、最新潮的技术,往往并非能长久留存的技术。这一点可以从我的蒸汽朋克装扮中理解:蒸汽朋克描绘了一个另类未来,在那里,我们想象中所有很酷的发明都没有被创造出来。

问题在于,那些最新潮的框架,例如Angular 1、Angular 2、Angular 4,甚至Angular 75。它们很有趣,你可能会在咖啡馆里听到人们谈论它们。但我了解到,我至今仍在运行着10年前编写的代码。那段代码并不“性感”,我也没在咖啡馆里听人谈论过它。事实上,我10到15年前写的代码,那时我听到有人在咖啡馆谈论一些很酷的东西,那些代码现在已经是垃圾,无法工作,难以使用,完全过时了。反而是那些“无聊”的、不“性感”的东西,我至今仍在运行。

所以对我来说,未来并不总是看起来那么美好。有些东西虽然有趣,但如果你要用它们来创业,也可以,只是你可能在几年后就得完全抛弃它们。另一个重点是,本课程的核心并非仅仅是编码。

本课程是关于请求-响应周期的:浏览器如何与Web服务器协作,Web服务器又如何与数据库交互。我们将学习什么是SQL、HTML、Ajax以及Web应用程序的整体架构。PHP恰好是其中的一部分。我真心希望你们能学习不止一种语言。所以,如果我现在教你们PHP,之后你们去参加一个Ruby on Rails的训练营,你们将在那个训练营中表现出色,然后找到一份Ruby on Rails的工作。我并非只推崇一种语言。

那么,我为什么选择一种最容易安装、托管成本最低、使用最广泛的语言作为教学起点呢?实际上,在Web应用开发领域,PHP的工作机会可能比任何其他语言都多。如果我教你们像Angular 7这样的技术(虽然Angular 7甚至还不存在),可能有一些很酷的工作机会,但一周之后,这些“酷”工作可能就消失了。这并非真正对你们有利。

我希望你们能学到一些能在整个职业生涯中持续发挥作用的东西。另一个我认为重要的点是,我们试图面向全球的受众教学,而不仅仅是旧金山或西雅图那些需要Ruby on Rails或Django等最新技术的人群。我希望教给你们一些在伊朗、印度、巴基斯坦和美国都能同样轻松使用的技术。

因此,PHP就是这种适用于Web的通用语言。有些人会问,为什么不用Python?我也教Python课程,我很喜欢Python,它是一门很棒的语言。但Python并非为Web而设计。Python在数据挖掘和数据分析方面非常出色,是一门值得学习的好语言。但仅仅因为它是一门你知道的语言,并不意味着它就是Web开发的正确选择。

而PHP,从PHP1到2、3、4、5、7(他们跳过了PHP6),PHP社区的每一次创新都是为了让它变得更好,更适合构建Web应用程序。可扩展的Web应用程序,如Facebook等众多公司都在使用它,并对其进行优化。所有这些特性都旨在实现高性能、易用性和卓越体验。

PHP可能不是最适合你的技术,但我认为毫无疑问,它应该是你首次学习Web开发时应该掌握的正确技术。因此,我鼓励你们享受学习PHP的过程,并理解它可能不是你唯一要做的事情,也可能不是你将来赖以谋生的技术。但我们将共同打下坚实的基础,让每个人都能学会如何编程Web应用程序。

😊,我们课堂上见。


本节课总结:我们一起探讨了选择PHP作为Web开发入门语言的核心理由。我们了解到PHP因其持久性低门槛(易于安装和托管)、广泛的应用市场以及作为理解Web请求-响应周期应用程序架构的绝佳工具而成为理想起点。记住,本课程的目标是建立扎实的Web开发基础,而PHP是实现这一目标的可靠桥梁。

002:欢迎学习专项课程第一门课 🎉

你好,欢迎来到本课程。我是查尔斯·塞弗伦斯,你的讲师。

这是本专项课程的第一门课。关于课程内容的安排,我曾有过一些考量。最终这门课的体量稍长,我们原本可能希望是五周课程,但我找不到合适的地方将其拆分为两门课。因此,这是一门内容充实的课程。如果你完成了这门课,你将掌握我们所需的核心基础知识。

在本课程中,我们将涵盖超文本标记语言(HTML)、层叠样式表(CSS)、超文本传输协议(HTTP)和PHP。我希望你已经对CSS有初步了解。本课程的目标并非教你如何成为一名网页设计师,去展示所有这些技术的外观效果;我们更希望你成为一名“网络技师”,因为我们将在后端投入更多精力,学习如何创建应用程序,而非如何构建一个外观精美的应用程序。

如果你想学习如何构建美观、响应式且易于访问的应用程序,那是网页设计的范畴,由我的同事科琳·范·伦特教授的《面向所有人的网页设计》课程会涉及。从技术上讲,你可以按任意顺序学习这两个专项课程。你可以先学习本课程的技术原理,再学习如何让应用变得美观;或者先学习设计,再学习技术原理。我个人认为,最佳路径是先学习网页设计,再学习本应用开发课程,这也是我和科琳规划这两个专项课程的初衷。但你可以自由选择学习顺序。

如果你先学习了本课程,请记住,我们不会教你如何让事物变得美观,那属于另一个完整的专项课程。如果在学习过程中遇到问题,可以在论坛中向我们提问,教学团队会提供帮助。教学团队可以查看你的提交内容并给予指导。如果需要联系我,最佳方式是通过Twitter,只需提及 @DrChuck,通常我在地球上的某个地方,几分钟内就能看到消息。例如,如果有人说“作业4出问题了”,我会很快得知。

再次欢迎你加入本课程,祝你好运,期待在课程结束时看到你圆满完成所有学习任务。

003:我们的技术栈 🛠️

在本节课中,我们将一起了解构建现代Web应用程序所需的核心技术栈。我们将探讨Apache、PHP、SQL、JavaScript、jQuery和JSON这些关键技术,并了解它们背后的创造者与设计理念。理解这些技术的历史和设计哲学,有助于我们更好地使用它们。

概述

我们将从Web服务器开始,逐步深入到服务器端编程、数据库交互、客户端脚本以及数据交换格式。每一部分都由人创造,充满了特定的设计选择和权衡。

Apache Web服务器 🌐

上一节我们介绍了课程的整体目标,本节中我们来看看整个技术栈的基础——Web服务器。

Apache Web服务器是我们所有工作的基石。它是一个开源项目,起源于1992-1994年间美国国家超级计算应用中心(NCSA)的Mosaic项目。Mosaic不仅是一个浏览器(客户端),其HTTPD(超文本传输协议守护进程)组件后来演变成了Apache Web服务器。当NCSA的Mosaic项目停滞时,其代码被开源,并由包括Brian Behlendorf在内的一群开发者接手维护。“Apache”这个名字的一种解释是“一个非常补丁的服务器”,因为它被不断地修补和改进。这最终催生了Apache开源基金会,而Apache服务器是其第一个产品。

Apache使用C语言编写。C语言是一种底层且功能强大的编程语言,诞生于1972年,它支撑着我们使用的许多技术。

以下是关于C语言及其影响的一些背景:

  • C语言被用于编写Unix、Linux,甚至Python解释器本身。
  • 在C语言出现之前(20世纪50-70年代),计算机主要被视为快速计算器,编程语言如Fortran也侧重于此。
  • 随着计算机被用于电子邮件、社交网络等文本处理任务,出现了一批专注于文本处理的语言。
  • C语言是第一个在文本处理、数值计算、运行速度以及编写系统程序方面都表现出色的语言。
  • 它的语法简洁,使用花括号{}定义代码块,for循环的语法结构为 for (初始化; 条件; 递增) { ... }
  • 许多后来的系统级编程语言都深受C语言影响,包括Java、C++、Objective-C、C#等。JavaScript的语法也借鉴了C。

PHP:服务器端脚本语言 🐘

在了解了作为基础的Web服务器和C语言后,我们来看看将在服务器端使用的PHP语言。

PHP是一种与HTML页面混合的模板化编程语言。它的模式是HTML、代码、HTML、代码交替出现。这使得在网页中动态地添加小型计算或代码片段变得非常容易。网页的一部分来自包含print语句的运行代码,另一部分则直接来自静态HTML。

PHP由Rasmus Lerdorf创建,如今由成千上万的开发者作为一个大型开源项目维护。Rasmus并非科班出身的计算机科学家,他更关注实用性和易用性,而非构建世界上最完美的编程语言。PHP诞生于上世纪90年代中期,至今已有约20年历史。随着时间的推移,PHP(特别是PHP5和PHP7)已经变得更加优雅。其核心设计理念是让Web应用开发者能够尽快地投入生产。Rasmus在90年代中期因为用C语言编写所有网页而感到非常困难,从而催生了PHP。

PHP本身用C语言编写,其设计灵感大量来源于C,同时也从Perl(另一种早期脚本语言)汲取了养分,可能也受到Python的一些影响。

SQL:数据库查询语言 🗃️

现在,我们有了处理逻辑的服务器(Apache)和编写逻辑的语言(PHP)。接下来,我们需要一种与数据库对话的方式来存储和检索数据,这就是SQL。

SQL是一门优美而强大的语言。它将一个极其复杂的问题——如何优化地在计算机内存和磁盘上存储数据,并使可能同时被访问的数据彼此靠近——进行了完美的抽象。数据库本身(处理连接、插入、选择、更新、删除)非常复杂,但其复杂性通过SQL被优雅地封装起来。我们只需声明“我想做这个”,数据库就会神奇地完成。

SQL的诞生故事与其他语言不同。在20世纪60年代,IBM、Oracle、Sybase等数据库厂商各自为政,拥有不同的数据库实现和查询语言。后来,美国国家标准与技术研究院(NIST)介入,要求行业共同制定一个标准的数据库查询语言,于是SQL应运而生。Elizabeth Fong在NIST参与了这一过程。SQL作为一个抽象层,并非直接实现任何厂商的数据库,而是提供了一种与任何数据库对话的优美方式。

JavaScript:客户端脚本语言 ⚡

处理完服务器端的事务后,我们将进入浏览器(客户端)的世界,这就需要JavaScript。

JavaScript是一种类C的编程语言(使用花括号等语法),但其早期设计目标是在网页浏览器内部运行。因此,它预定义了如documentwindow这样的对象,使其能够操作浏览器页面。你可以想象成JavaScript在页面背后“悄悄”修改内容,这些变化通过文档对象模型(DOM)或window对象呈现出来。例如,页面上弹出新窗口或出现小红点,通常都是JavaScript的功劳。

JavaScript由Brendan Eich在1995年发明,当时他在Mozilla基金会,参与Netscape项目。Brendan是一位物理学家,他视JavaScript为在紧迫时间内,秘密构建他心目中人类有史以来最优雅编程语言的机会。他借鉴了Java的类C语法,并融入了许多他欣赏的其他语言的优点,创造出了JavaScript。正因为其优雅和强大,JavaScript日益流行,甚至通过Node.js等框架被用于服务器端开发。

jQuery:DOM操作库 🎯

我们已经有了强大的JavaScript,但它在操作浏览器时曾面临一个挑战:浏览器兼容性。

JavaScript语言本身很快被标准化,但文档对象模型(DOM)——即JavaScript用来改变浏览器内容的部分——却未统一。Internet Explorer、Chrome、Firefox等浏览器对DOM的实现各不相同。jQuery就是由John Resig构建的一个可移植性层,它提供了一种优雅且跨浏览器的方式来操作DOM。如今,许多人甚至认为jQuery就是JavaScript,因为它提供了更简洁、更好的方式来处理浏览器交互,能显著缩短客户端代码。

JSON:数据交换格式 📦

最后,当我们的浏览器端代码和服务器端代码都变得足够智能时,它们需要在用户无感知的情况下进行通信。这就需要一种数据交换格式。

JSON(JavaScript对象表示法)就是这种格式。例如,页面背后的代码可以自动向服务器询问是否有新消息,而服务器返回的新消息就采用JSON格式。Douglas Crockford并不自称JSON的发明者,而是“发现者”,因为JSON本质上源于JavaScript语言的一部分。他提取了JavaScript中的对象表示法,将其确立为一种协议,现在已被广泛用于各种数据交换场景。

总结

本节课中,我们一起学习了构建Web应用的全栈技术:从基础的Apache Web服务器,到服务器端的PHP编程语言,再到与数据库交互的SQL语言。接着,我们探索了浏览器端的JavaScript及其辅助库jQuery,最后了解了用于前后端数据通信的JSON格式。

更重要的是,我们了解到所有这些技术都是由人创造的,是许多人协作、权衡和选择的产物。它们并非完美无缺的魔法,而是体现了创造者的理念和当时的实际需求。理解这一点,能帮助我们在未来无论是使用Node.js、Ruby on Rails还是其他任何技术时,都能保持清晰的视角:技术是工具,由人创造,也由人使用和改进。

004:超文本传输协议(HTTP) 🧩

在本节课中,我们将学习Web应用程序的核心通信机制——超文本传输协议(HTTP)。我们将了解浏览器与服务器之间如何通过请求和响应来交换信息,并理解URL、HTTP请求格式等基本概念。这些知识是理解后续所有Web技术的基础。

浏览器与服务器:多层架构

现在我们来讨论网页是如何构建的。你可能会问,为什么要了解这些细节?

部分原因是我希望你能真正理解Web应用程序的工作原理。因为Web应用程序,以及许多移动应用程序,都是由多个层次的组件协同工作的。在浏览器端,你有浏览器本身;在服务器端,你有服务器和数据库服务器。我希望你能理解,当你编写代码时,你正在与这个多层系统中的哪个部分打交道。

浏览器是运行在你的硬盘(或手机等设备)上的软件。我们将要学习的浏览器端技术包括HTML和CSS。稍后我们还会学习文档对象模型(DOM),它是JavaScript操作HTML元素、改变文字内容的方式。JQuery则让这些操作变得更加简单。

因此,我们将在浏览器端学习许多技术。同样,在服务器端我们也将学习许多技术:我们将学习PHP,以便在服务器端编写代码;我们将学习SQL,以便在数据库中查找数据并将其取出并发送回浏览器。

我们还将学习请求-响应周期。这是指浏览器与服务器进行通信,我们在服务器端编写代码来响应请求,以及数据来回传输的过程。这个过程的核心就是HTTP(超文本传输协议)。此外,JSON也是我们来回传输数据时将要使用的一部分。

在本课程中,我必须从一个起点开始。这个起点就是让你理解浏览器端和服务器端之间发生了什么,因为这是两者之间的基本分界点。我们将简要讨论超文本传输协议,但更重要的是,我希望你认识到,在这些应用程序中,不同的代码片段和技术正在被使用。

HTTP协议:简单而优雅的基石 🌐

HTTP是占主导地位的应用层协议。它由蒂姆·伯纳斯-李和罗伯特·卡里奥于1989-1990年发明万维网时创造。当然,在HTTP之前还有其他协议,如用于文件传输的FTP、Telnet、用于简单邮件传输的SMTP。这些协议都已发展成熟。

但蒂姆·伯纳斯-李和罗伯特·卡里奥想要一个简单的协议,主要是因为他们并非协议开发者,他们只是想构建一个能显示页面并允许人们编辑这些页面的应用程序。因此,他们构建了尽可能简单的协议。这些协议来回通信。在HTTP中,它建立一个连接,请求一个文档,然后收到一个文档作为回应,这确实非常简单。我不确定它是否被设计成简单的,但结果是,我们能够在这个简单协议之上构建像Web服务这样的东西,以及其他只是在其基础上略有变化的协议。

因此,我们使用的大多数新协议,基本上都是在HTTP之上采用不同约定的协议。它的优雅之处在于其简洁性,也是我们能够“破解”它的唯一原因,因为它足够简单。过去我也能“破解”邮件协议,但后来安全措施使其变得难以使用。

HTTP的另一个核心贡献,同样来自蒂姆和罗伯特在20世纪90年代初期的构想,是统一资源定位符的概念。现在你会在各种地方看到它,比如宣传册上写着“http://bla.bla.bla”,你一看就知道那是什么。你把它输入浏览器,就能获取一个文档或一组文档。

但实际上,这些统一资源定位符背后是有科学依据的。在万维网出现之前,你必须知道要连接到哪个主机、使用什么协议、向该主机发送什么命令集,以及你可能想从该主机检索什么内容。URL只是提出了一种约定,然后将这些东西连接在一起。

所以,http:// 是协议。协议不止这一种。server.com 是你与之通信的服务器。在云端,有许许多多的服务器。哪个服务器拥有这个文档?我们用什么协议与之通信?在服务器内部,还有文件。因此,服务器内的这个文档路径告诉我们将要查看什么。

当然,实际情况比这更复杂。你可以在URL末尾添加参数,也可以添加所谓的锚点。所以有很多东西,比如 ?x=2。但URL的基本思想就是:如何获取、去哪里获取、获取什么,所有这些都连接成一个长字符串。

其理念是,只要我们有一个这样的URL,并在浏览器中输入它,我们就能获取到它。另一种方式不是直接在浏览器中输入URL,而是在HTML中嵌入所谓的锚标签,即可点击的链接。这些链接内部有一个 href(超文本引用)属性。我们将在下一节HTTP课程中讨论这个。href 属性表示:当有人点击此链接时,丢弃当前页面并跳转到另一个页面。这就是它的超文本特性:你点击一个链接,然后跳转到下一个链接。因此,链接会变成一个GET请求,该请求检索一个页面,返回新的HTML,然后显示在我们的浏览器中。

实践:超文本导航 🔗

我们要做的第一件事是输入一个URL。如果你输入 data.pr4e.org/page1.htm 然后按回车,你就发出了一个GET请求。如果我在这里查看源代码(你可能需要开启开发者模式或类似功能才能看到“查看页面源代码”),我们可以看到页面源代码。你会看到这种标记语言,这就是HTML。我们稍后会详细讨论它。这里有一个锚标签,表示当点击“第二页”时,去获取这个URL。所以我们当前在 page1.htm,而链接指向 page2.htm

如果我点击它,几乎是瞬间就跳转了。但我们实际上已经跳转到了另一个页面,并且那个页面上有一个链接可以返回第一页,如此反复。这就是基本的超文本导航。我们的浏览器正在执行操作:看到这些点击,然后执行某些操作,获取不同的页面。

当然,实际情况远比这复杂,我们稍后会看到。但我想非常仔细地看一下这个简单超文本导航过程中发生的具体步骤。

深入请求-响应周期 🔄

我们在浏览器中输入了URL,按下了回车键,这触发了一个GET请求。现在我们看到了这个页面。页面中有一个锚标签,它是一个超文本引用。在早期,所有链接都是蓝色并带有下划线,因为人们需要提示才能知道哪里可以点击。我们现在已经习惯了浏览网页,以至于觉得超文本是理所当然的。你会点击所有看起来可以点击的东西。在过去,我们用蓝色文字和下划线来表示“点击这里”。

当你点击这个链接时,你的浏览器(一个运行在你电脑上的软件)会执行操作。这个白色的大框代表你的电脑,无论是手机还是其他设备。浏览器是一个软件应用程序,如Chrome、Firefox、Internet Explorer(现在叫Edge)、Safari、Opera等。有很多浏览器。这些是你的客户端,用于浏览网页。浏览器是一个应用程序,是向你展示这个页面的东西。

当你点击链接时,你电脑上的浏览器会说:“哦,有人点击了一个链接。”然后它会去查找你想要哪个链接。当它知道你请求的是哪个链接后,它会通过解析该链接来建立连接。它通过一个叫做端口80的端口(Web服务器通常监听的端口)连接到正确的Web服务器。然后它发送一个请求,发送一小行文本,看起来就像这样:GET /page2.htm HTTP/1.0。这整行文字表示:“这就是我想要的文档。”

然后,在这个Web服务器上(通过互联网),服务器要么生成响应,要么从磁盘读取响应,然后返回响应。响应本身是HTML格式,就像我在“查看源代码”中展示给你的那样,这些是标签:<html> 标签、<head> 标签、<body> 标签。然后这里有一个超文本引用,它将“第二页”变成了一个链接(在截图中显示为紫色,因为我之前点击过它)。

当这个HTML返回时,你的浏览器会解析它,然后渲染它。中间有一个文档对象模型,浏览器解析HTML后构建DOM,然后呈现为页面。但基本思想就是:点击 -> 请求 -> 页面被获取 -> 响应 -> 解析 -> 显示 -> 你得到新页面。然后又是:点击 -> 请求 -> 响应。到我们学完时,情况会复杂得多,可能会有许多请求-响应周期,但这就是基本的请求-响应周期。

互联网标准:开放的基石 📜

这一切都由互联网标准管理,这些标准源于非常开放和开源的文化。它们由一个名为互联网工程任务组的团体制定,该团体早于我们所说的现代互联网(80年代中期),IETF甚至可以追溯到70年代一个更早的网络ARPANET。

他们提出了IETF和这些标准的概念。这些标准是开放的、免费的、无阻碍的,意味着任何人都可以阅读它们,并实现符合这些标准的东西,从而构建自己的Web浏览器或Web服务器。当然,现在这些东西都已经存在,我们直接下载使用即可。但管理我们所有代码互操作性的协议,正是这些IETF标准。

还有其他标准来源,比如万维网联盟负责HTML、CSS等标准,但协议类的东西主要通过IETF。

有趣的是,这些标准文件被称为RFC,代表“请求评论”。这是一种书呆子式的工程学承认:没有标准是完美的。即使它们已经完成并且有10年、15年甚至更久的历史(比如从1981年到2017年,现在可能30多年了),它们仍然可能需要改进。所以,即使它已经30多岁了,它仍然在“等待评论”。如果你有评论,如果你发现了问题,或者有更好的构建方法,工程师们希望听到你的意见。所以,RFC是这些互联网标准一个有趣且具有讽刺意味的命名惯例。

如果你去查阅,会发现有许多标准管理着HTTP。你可以去阅读它们,翻页、翻页、再翻页,然后很快你就会意识到你并不想自己写一个浏览器,你更愿意使用一个现成的浏览器。但最终,你会翻到很后面的一页,上面基本上说明了如何发出一个请求。

HTTP请求格式 📝

如果你继续看下去,它会告诉你,你应该把方法放在前面,后面跟着URI,然后是协议版本,最后以回车换行符结束。这里就是这么说的:方法标记(在我们的例子中是GET),后面跟着URL,以回车换行符结束,等等。

所以,如果你读得足够久,最终就能看到我们应该如何做。基于这个规范,接下来我将向你展示如何手动“破解”一个HTTP请求。

总结

本节课中,我们一起学习了Web通信的基础——HTTP协议。我们了解了浏览器与服务器之间的请求-响应周期,认识了URL的结构和作用,并知道了互联网标准(如RFC)如何规范这些交互。理解这些基本概念,是后续学习HTML、CSS、JavaScript、PHP和SQL等具体技术,并构建完整Web应用程序的关键第一步。

005:请求响应周期 🔄

在本节课中,我们将深入探讨HTTP请求如何工作。我们将学习如何手动模拟一个HTTP请求,并了解浏览器开发者工具如何帮助我们查看和分析请求与响应的细节。

手动模拟HTTP请求

上一节我们介绍了HTTP的基本概念,本节中我们来看看如何通过命令行工具手动发送一个HTTP请求。

我们将使用一个名为Telnet的命令行工具。它允许我们连接到服务器的特定端口(如Web服务器常用的80端口)并直接发送HTTP命令。

以下是使用Telnet发送HTTP 1.0 GET请求的基本步骤:

  1. 打开终端或命令行工具。
  2. 输入命令连接到目标服务器的80端口。
  3. 手动输入HTTP请求行和头部,然后发送。

具体命令格式如下:

telnet data.pr4e.org 80
GET /page1.htm HTTP/1.0

输入完请求后,需要按两次回车键(一次结束请求行,一次发送空行表示请求头结束)。服务器随后会返回响应。

理解HTTP响应

服务器返回的响应可以分为两部分:响应头响应体。它们由一个空行分隔。

响应头包含元数据,例如:

  • Content-Type:告诉浏览器返回内容的类型(如text/htmlimage/jpeg)。
  • Content-Length:内容的大小。
  • Last-Modified:文件最后修改日期。
  • 状态码:如200 OK表示成功,404 Not Found表示未找到资源。

响应体则是请求的实际内容,例如HTML代码或图片数据。

使用浏览器开发者工具

虽然手动发送请求有助于理解原理,但在实际开发中,我们使用浏览器的开发者工具来调试和分析网络请求。

以下是查看网络请求的步骤:

  1. 在浏览器中打开任意网页(例如 www.drchuck.com)。
  2. 打开开发者工具(通常通过右键点击页面并选择“检查”,或按F12键)。
  3. 切换到 “网络” 标签页。
  4. 刷新页面,工具将记录页面加载过程中发生的所有网络请求。

通过开发者工具,你可以看到:

  • 每个请求的详细信息(方法、URL、状态码)。
  • 请求头和响应头的内容。
  • 请求的响应时间线。
  • 对于复杂页面,浏览器会发起多个请求来获取HTML、CSS、JavaScript和图片等资源。

Web应用架构回顾

理解请求响应周期是理解整个Web应用架构的关键。一个典型的Web应用包含三层:

  1. 浏览器:负责发送HTTP请求,接收响应,并渲染内容(HTML, CSS, JavaScript)。
  2. Web服务器:接收HTTP请求,处理业务逻辑(可能使用PHP等语言),并生成响应。
  3. 数据库服务器:存储应用数据,Web服务器通过SQL查询与之交互。

HTTP协议是连接浏览器和Web服务器的桥梁。开发者工具主要帮助我们观察和分析浏览器与Web服务器之间的通信。

总结

本节课中我们一起学习了HTTP请求响应周期的核心机制。我们通过手动使用Telnet工具发送请求,直观地看到了请求和响应的原始格式。接着,我们介绍了如何使用浏览器内置的开发者工具来更方便地监控和调试网络活动,这是Web开发中诊断问题的必备技能。最后,我们回顾了浏览器、Web服务器和数据库服务器协同工作的三层架构,明确了HTTP在其中扮演的角色。掌握这些基础知识,将为后续学习更复杂的Web技术打下坚实的基础。

006:旧金山 🏙️

在本节课中,我们将回顾一次成功的“面向所有人的Web应用程序”课程附加办公时间。我们将看到来自世界各地的学生如何介绍自己,并分享他们学习这门课程的体验与收获。


大家好,我们又一次成功举办了“面向所有人的Web应用程序”课程的办公时间。

这门课程与“面向所有人的Python”、“面向所有人的网页设计”以及“面向所有人的互联网历史”等课程一样,都属于大规模开放在线课程(MOOC)。

和往常一样,我希望大家认识一下你的同学们。我们将轮流进行自我介绍,你可以说出你的名字,并按照你喜欢的方式向同学们问好。

以下是同学们的自我介绍:

  • 嗨,我叫Valerie,很高兴认识大家。
  • 嗨,我叫Emma。这是最好的课程。我同意。
  • 我听说过你,我叫Shu Lee,很高兴认识你。
  • 我也很高兴认识你。嗨,我叫Anthony。Python是最棒的,这门课程也是最好的。谢谢。
  • 嗨,我叫Robert。Chuck博士太棒了,他拯救了我的职业生涯。我也拯救了我自己的职业生涯。
  • 我叫Rosalie,欢迎来到这门课程,希望你们喜欢。谢谢你们的到来。
  • 嗨,我叫Matt,这门课程帮助我重新开始编程,并从密尔沃基搬到了加利福尼亚。太酷了。你拿到贴纸了吗?我还没有。好的,这是你的贴纸。
  • 嗨,我叫Lana,希望你们像我一样享受这门课程。谢谢。
  • 嗨,我叫Dick,希望你们和我一样喜欢这门课程。
  • 嗨,我叫Ben,我只想说谢谢Chuck博士。我想你还没拿到贴纸。我还没有。给你。
  • 嗨,我叫Boick,这门课程让我进入了计算机科学领域。非常感谢计算机科学。是的,这太棒了,真的非常棒。我想你也没拿到贴纸吧?谢谢,所以你拿到贴纸了。
  • 嗨,我叫Bobby,这门课程以及“无望”课程将帮助我实现职业转型。是的,我经常听到这样的故事,我为你感到骄傲,这真的很酷,谢谢你。
  • 嗨,我叫Alex,能来到这里我非常兴奋。后面好像有些随机的、匿名的人,哦,算了。
  • 嗨,我叫Ryan,我真的很喜欢这门课。我是Ryan,我想你已经拿到贴纸了。我拿到了贴纸,好的。
  • 大家好,我叫Dany,我非常喜欢Chuck博士的课程,能见到Chuck博士真是太棒了。很好,我也很高兴Andy来上课。

上一节我们听了同学们的自我介绍,本节中我们来看看办公时间的其他传统环节。

我们还有一个传统,就是为大家鼓掌。

哦,我忘了。哎呀,我们又来了一位新同学。好的,进来吧,你刚好赶上。你叫什么名字?Wendyinki。向班里的同学们问好吧。大家好。

  • 嗨,我叫Tataya,我真心向所有人推荐这门课程。谢谢。

谢谢大家提醒我搞砸了。我是说,我经常搞砸,我总是搞砸。

所以,我们在这些活动中的另一个传统是,为所有提供帮助的教学助理们热烈鼓掌。

哦,我没事,好的,让我们为她热烈鼓掌。


本节课中我们一起学习了“面向所有人的Web应用程序”课程一次办公时间的记录。我们看到学生们热情地分享学习体验,课程帮助他们开启了编程之旅、实现了职业转型或重拾了编程兴趣。这再次证明了社区学习和支持的重要性。在“面向所有人的Web应用程序”课程启动之际,这又是一次很棒的办公时间,我们下周在北卡罗来纳州的罗利再见。干杯。

007:Chuck博士在日内瓦唱蓝调 🎸

在本节课中,我们将回顾并整理课程中一个轻松有趣的环节。这个环节展示了课程社区互动的一面,并包含了一些技术演示的片段。

概述

本节内容并非严格的技术教程,而是记录了课程进行中的一段插曲。Chuck博士在瑞士日内瓦通过网络与课堂互动,并进行了即兴的音乐表演。这段内容体现了在线课程的灵活性与趣味性。

环节记录

上一节我们介绍了课程的核心技术内容,本节中我们来看看这个轻松的互动时刻。

课程进行中,Chuck博士通过网络接入课堂。他邀请大家协助让另一位参与者加入连线。

以下是连线过程中的一些对话与互动要点:

  • 所有人都会出现在YouTube Zoom会议中。
  • 博士认为那位参与者才华横溢。
  • 博士感谢大家的帮助。

随后,环节进入了即兴音乐表演部分。Chuck博士使用了他的设备进行演奏。

以下是表演中的一些歌词和互动片段:

  • 演唱了关于“密歇根货运司机”的内容。
  • 歌词中混合了多种语言的感叹词。
  • 表演中提到了“远程控制”设备。
  • 歌词表达了情感与故事。

表演过程中,共享了多张现场或相关的图片,增强了互动氛围。

总结

本节课中我们一起回顾了课程中的一个趣味互动环节。这个环节通过音乐和实时连线,展示了在线学习社区生动、人性化的一面,为严谨的技术学习增添了轻松的色彩。

008:超文本标记语言(HTML) 🏗️

在本节课中,我们将要学习超文本标记语言(HTML)的基础知识。我们的目标不是让你成为世界顶尖的网页设计师或前端专家,而是为你提供足够的HTML知识,以便你能顺利完成本课程后续的学习。本课程的核心主题是:给你一些HTML,然后我们将探索如何用更酷的方法生成更多的HTML。

概述:Web应用架构与HTML的角色

在深入HTML细节之前,我们首先需要将所学内容置于正确的上下文中。一个典型的Web应用涉及三个主要软件组件和两个网络连接。

  • 浏览器:运行在你的电脑上,例如Chrome。
  • Web服务器:运行在网络另一端,例如Apache服务器,我们将在此运行PHP等程序。
  • 数据库:通常是第三台硬件设备,例如MySQL。

当你向服务器发起请求时,服务器可能会处理一些逻辑(如运行PHP),并从数据库读取数据,最终将结果返回给你的浏览器。这个返回结果的格式就是HTML。浏览器会解析HTML,识别其中的 <> 等标签,最终在屏幕上渲染出你看到的图像和布局。因此,HTTP是用于获取文档的协议,而HTML则是这些文档的格式。HTML是浏览器层面的技术,它决定了我们如何在浏览器中创建和呈现网页的外观与感觉。

HTML的本质:一种可查看的标记语言 📝

HTML是一种标记文本的方式。它的核心思想类似于文字处理软件中的“显示代码”功能,用于标注哪些文本是粗体,哪些是斜体,哪些是普通文本。HTML的巧妙之处在于,它采用了一种你可以直接查看的格式。与某些二进制格式(如旧的.doc文件)不同,HTML的内部表示形式就是纯文本,但其中包含了赋予文本含义的“标签”。

标签使用尖括号 <> 来定义。例如:

  • <p> 表示一个段落的开始,</p> 表示段落的结束。
  • <strong> 表示开始加粗文本,</strong> 表示结束加粗。

这些标签为文本赋予了结构和样式上的含义。HTML是一种用户可查看的标记格式,类似的格式还有XML(例如现代的.docx文件就基于XML)。

HTML的起源与演变 🕰️

理解HTML为何是现在这个样子,有助于我们更好地掌握它。万维网始于20世纪90年代初,并在1994年开始流行。它的发明者蒂姆·伯纳斯-李和罗伯特·卡里奥最初只是一个小团队,他们需要构建简单、实用的工具。我们得益于他们工程师式的简洁设计,才有了今天丰富多彩的网络世界。在核心层面,它依然是简单而优美的。

值得注意的是,1990年发明的Web与今天的Web大不相同。最初的Web主要用于严肃的文档和学术交流,并非为了娱乐、观看视频或消费信息。HTML最初只是一个“实干家”,用于展示设计文档和图片。点击一个链接就能跳转到另一个页面,这在当时就足以让人们感到兴奋。那些带下划线的蓝色链接仿佛在说“点我,点我”,这在1996年看来就是未来。

然而,随着更多非技术用户使用网络,网页需要变得更美观。于是,Web技术不断演进。如今,网页已成为强大的商业引擎。像雅虎这样的公司甚至会精确计算页面中白色边框的像素数,通过细微调整来测试用户是否更喜欢某个版本,从而优化出令人愉悦的用户界面。这与早期“链接能用就很棒”的理念形成了鲜明对比。层叠样式表(CSS)在实现这种美观性方面扮演了重要角色,我们将在后续课程中讨论。

从宽容到规范:HTML标准的建立 📜

在“美好的旧时光”里,HTML非常宽容。标签可以不闭合,可以使用大写字母,属性可以不加双引号,列表项末尾可以不加 </li>。浏览器为了友好显示,会尽力修复这些错误,而不是直接报错。

但随着Web在1994-1996年间日益成为商业和生产力的引擎,建立标准变得至关重要。蒂姆·伯纳斯-李从欧洲核子研究中心(CERN)转到了麻省理工学院(MIT),并成立了万维网联盟(W3C)。HTML从一个由工程师为解决特定问题而创建的工具,演变为支撑未来产业的基础技术。从此,HTML开始被系统性地修订,变得更加专业和清晰。

以下是HTML的主要版本演进:

  • HTML 1.0
  • HTML 2.0
  • HTML 3.0(存在时间较短)
  • HTML 4.0(存在了很长时间)
  • HTML5(我们当前使用的版本)

W3C制定了HTML应遵循的规则,例如:

  • 标签必须使用小写字母。
  • 所有标签必须有开始和结束(对于非空元素)。
  • 属性值必须用双引号括起来。

尽管现代浏览器仍然对许多错误保持一定的容忍度,但遵循这些标准能让代码更清晰、更易于维护。正是W3C的工作奠定了我们今天在Web上所做一切的基础,使得网络既强大又美观,同时核心部分依然足够简单易懂。

总结

本节课我们一起学习了HTML的基础知识。我们了解了HTML在Web应用架构中的角色,它是一种用于标记文本结构样式的可查看格式。我们回顾了HTML从早期简单、宽容的规范,演变为如今由W3C制定的严谨标准(HTML5)的历程。掌握这些基础知识,将帮助我们更好地理解后续课程中如何动态生成和处理HTML。

接下来,我们将更详细地探讨HTML文档是如何组织起来的。

009:HTML标签详解 🏷️

在本节课中,我们将深入学习HTML(超文本标记语言)的基本结构和核心标签。HTML是构建网页的基石,它使用一系列标签来定义网页的内容和结构。我们将从文档的基本构成开始,逐步介绍常用标签、特殊字符、链接、图像、列表和表格等元素。

HTML文档的基本结构 📄

上一节我们介绍了Web的基本概念,本节中我们来看看一个标准HTML文档的组成部分。任何HTML文档都包含两个基本部分:<html>标签和文档内容。

一个完整的HTML文档结构如下:

<!DOCTYPE html>
<html>
<head>
    <!-- 元数据区域,如标题、CSS链接等 -->
    <title>页面标题</title>
</head>
<body>
    <!-- 页面可见内容区域 -->
</body>
</html>
  • <html>标签包裹整个文档。
  • <head>部分包含元数据,如页面标题、链接的样式表(CSS)等,这些内容不会直接显示在浏览器中。
  • <body>部分包含所有在浏览器中可见的实际内容。

特殊文件与默认页面 🗂️

当你开始创建HTML文件时,会发现一些文件具有特殊作用。

以下是关于Web服务器默认页面的说明:

  • 当你访问一个指向目录(文件夹)的URL时,例如 http://example.com/folder/,Web服务器(如Apache)会查找该目录下的特定默认文件。
  • 最常见的默认文件名是 index.htmlindex.htm
  • 服务器配置决定了当用户导航到一个文件夹时,应自动加载哪个文件作为该文件夹的默认视图。
  • 因此,你可以将 index.html 视为一个文件夹的“主页”,而其他HTML文件、图像文件等可以放在同一文件夹中并通过链接相互访问。

HTML标签与文本流 📝

HTML通过一系列标签来标记内容。标签通常成对出现,有开始标签和结束标签,样式应用于标签之间的文本。

关于HTML中的文本显示,有一个重要特性:

  • HTML源代码中的换行、空行或行尾通常不会直接影响浏览器中的显示效果,除非你使用特定标签(如<br>)或CSS进行控制。
  • 浏览器会根据窗口或屏幕的宽度动态地重新排列(换行)文本内容,以实现自适应布局。
  • 这种动态换行特性使得网页能够适应不同尺寸的设备屏幕。
  • 使用CSS可以更精确地控制布局,例如设置固定边距或创建不换行的设计。

标签、属性与自闭合标签 🔧

标签是HTML的核心。它们有开始和结束。

以下是关于标签及其属性的要点:

  • 标签由尖括号 < > 包围。开始标签如 <b>,结束标签则在标签名前加斜杠,如 </b>。样式应用于这对标签之间的文本。
  • 标签可以拥有属性。属性是键值对,为标签提供额外信息。
  • 例如,图像标签 <img> 使用 src 属性来指定图片来源:<img src="picture.jpg">
  • <img> 是一个自闭合标签,它不需要单独的结束标签,因为它本身代表一个要插入的元素。
  • 一个标签可以拥有多个属性。

HTML实体与特殊字符 ⚙️

在HTML中,像小于号<和大于号>这样的字符具有特殊含义(用于定义标签)。为了在页面上显示这些字符本身,我们需要使用HTML实体。

以下是关于HTML实体的说明:

  • HTML实体是一种用于表示保留字符或特殊字符的代码。
  • 它以和号&开头,以分号;结尾
  • 常用实体包括:
    • &lt; 表示小于号 <
    • &gt; 表示大于号 >
    • &amp; 表示和号 & 本身
  • 实体还可以用来表示各种符号,如箭头(&larr;, &uarr;, &rarr;, &darr;)甚至表情符号(Emoji)。这为网页内容增添了丰富的表现力。

HTML注释 💬

任何编程语言都需要注释机制,HTML也不例外。注释用于在源代码中添加说明性文字,这些文字不会被浏览器渲染。

HTML注释的写法如下:

<!-- 这是一个注释,不会在网页上显示 -->
  • 注释以 <!-- 开始,以 --> 结束。
  • 注释可以跨越多行。
  • 注释的用途包括:
    • 为代码添加说明,方便自己或其他开发者理解。
    • 临时“注释掉”一段代码,使其在测试期间不生效,而无需删除它。
    • 有时,开发者会在注释中隐藏一些“彩蛋”信息。

超链接:HTML的核心 🔗

HTML中的“H”代表“超文本”(Hypertext),其核心就是链接。链接不仅用于导航,也是搜索引擎发现和评估网页内容的重要依据。

创建链接使用锚点标签 <a>

<a href="https://www.example.com">点击这里访问示例网站</a>
  • <a> 是开始标签,</a> 是结束标签。
  • href属性,其值(放在双引号内)指定了链接的目标URL。
  • 开始标签和结束标签之间的文本(如“点击这里访问示例网站”)将成为页面上可点击的链接。
  • 默认情况下,未访问的链接显示为蓝色带下划线,访问后变为紫色。
  • 点击链接会触发“请求-响应”周期,浏览器将导航到 href 指定的地址。

链接可以是相对路径绝对路径

  • 相对路径:指向当前网站目录下的文件,例如 href="page2.html"
  • 绝对路径:包含完整协议和域名的URL,例如 href="https://www.example.com/page2.html"

图像与列表 🖼️

图像是网页的重要组成部分。使用 <img> 标签可以将图像嵌入到文本流中,就像处理一个特殊字符一样。

列表用于呈现一系列项目。HTML提供了有序列表和无序列表。

以下是创建列表的方法:

  • 无序列表(项目符号列表) 使用 <ul> 标签,每个列表项使用 <li> 标签。
    <ul>
      <li>项目一</li>
      <li>项目二</li>
    </ul>
    
  • 有序列表(编号列表) 使用 <ol> 标签,列表项同样使用 <li> 标签。
  • 默认情况下,列表项之间没有大的间距。如果需要间距,可以在列表项内容外包裹 <p>(段落)标签,但这并非标准做法,更好的方式是通过CSS来控制样式。

你还可以组合标签,例如将图像放在链接内,使图片可以点击。

表格 📊

表格用于展示行列数据。在过去,表格曾被滥用于页面布局,但现在我们使用CSS进行布局。表格应仅用于呈现真正的表格式数据。

以下是创建表格的基本结构:

<table>
  <tr>
    <th>标题1</th>
    <th>标题2</th>
  </tr>
  <tr>
    <td>数据A</td>
    <td>数据B</td>
  </tr>
</table>
  • <table> 标签定义整个表格。
  • <tr> 标签定义表格中的一行。
  • <th> 标签定义表头单元格,通常加粗居中。
  • <td> 标签定义标准数据单元格。
  • 使用CSS可以精细控制表格的边框、宽度、对齐方式等样式。

查看与探索源代码 🔍

HTML的一个优点是开放性。你可以轻松查看任何网页的源代码来学习其实现方式。

以下是两种查看网页结构的方法:

  • 查看源代码:在浏览器页面右键点击,选择“查看页面源代码”。这会显示服务器最初发送的原始HTML文档。
  • 检查元素(开发者工具):在浏览器页面右键点击,选择“检查”。这会打开开发者工具,显示当前的文档对象模型(DOM)。DOM是浏览器内存中页面的实时表示,当通过JavaScript修改页面时,DOM会改变,但“查看源代码”看到的内容不变。

你可以访问本课程示例代码网站(如 webapplicationsforeverybody.com/code/html)来查看和练习这些HTML示例。

总结 📚

本节课中我们一起学习了HTML的核心知识。我们从HTML文档的基本结构开始,了解了<head><body>的分工。接着,我们深入探讨了各种标签:用于定义结构的段落和标题标签,用于强调的粗体和斜体标签,以及网页的核心——超链接标签<a>

我们还学习了如何用<img>标签插入图像,用<ul><ol><li>创建列表,以及用<table><tr><td><th>构建表格来呈现数据。同时,我们掌握了如何使用HTML实体(如&lt;)来显示特殊字符,以及如何使用<!-- -->来添加注释。

HTML始于一种优雅简洁的标记语言,经过多年发展,结合CSS和JavaScript,已成为构建现代强大、灵活网页应用的基础。下一节,我们将学习CSS(层叠样式表),它专门用于控制网页的视觉表现和布局。

010:HTML代码详解 🧑‍💻

在本节课中,我们将通过分析示例代码来深入了解HTML。我们将探讨HTML源代码与浏览器解析后生成的文档对象模型(DOM)之间的区别,并学习如何查看和调试它们。你可以下载代码在本地操作,也可以直接在浏览器中查看。


源代码与DOM的区别

上一节我们介绍了课程目标,本节中我们来看看HTML源代码和DOM的核心区别。

源代码是服务器发送给浏览器的原始文本文件。你可以通过浏览器的“查看页面源代码”功能看到它。它完全反映了文件在服务器上的内容。

文档对象模型(DOM) 是浏览器解析HTML源代码后,在内存中创建的一个结构化、可编程的对象表示。它反映了页面的当前状态,可以通过开发者工具(如“检查元素”)查看。

一个关键点是:DOM可以被JavaScript动态修改,但源代码永远不会改变。源代码是静态的,而DOM是动态的。


基础HTML结构分析

现在,让我们分析一个基础的HTML文档结构。

我们有一个基本的文档,包含 htmlheadbody 标签。在 body 中,有标题(header)、段落(p)等元素。

  • HTML中的空白字符(如空格、换行)通常不影响最终渲染效果,浏览器会忽略大部分多余的空白。
  • 一个 strong 标签用于加粗文本。
  • 一个 em 标签用于强调文本。
  • 一个 a 锚点标签用于创建链接。链接的文本位于开始标签 <a> 和结束标签 </a> 之间。

链接可以是相对的或绝对的。相对链接基于当前页面的位置,浏览器会自动将其转换为完整的绝对链接。

<!-- 这是一个相对链接的例子 -->
<a href="list.html">转到列表页</a>

列表与特殊字符

以下是关于列表和如何在HTML中显示特殊字符的要点。

无序列表<ul> 标签定义,列表中的每一项由 <li> 标签定义。为了在列表项之间创建间距,我们可以在 <li> 内部使用 <p> 段落标签。

<ul>
  <li><p>第一项</p></li>
  <li><p>第二项</p></li>
</ul>

在HTML中,有些字符是保留的,必须使用字符实体来表示。

  • < 必须写成 &lt;
  • > 必须写成 &gt;
  • & 必须写成 &amp;

浏览器内置了对这些字符实体的支持,可以显示各种符号,如 &clubs;(♣)、&hearts;(♥)等。


链接与图像

接下来,我们看看如何创建链接和插入图像。

链接href 属性指定目标地址。target="_blank" 属性可以让链接在新标签页中打开。

<a href="https://www.example.com" target="_blank">在新标签页中打开示例网站</a>

图像使用 <img> 标签插入,其 src 属性指定图片文件的路径。图像本身不产生布局空间,它像一个巨大的字符嵌入在文本流中。周围的 <p> 标签会为其添加上下边距。

<p><img src="image.png" alt="描述文字"></p>

图像也可以作为链接的点击目标,只需将 <img> 标签放在 <a> 标签内部即可。


表格与常见错误

表格曾经被用于页面布局,但现在应仅用于展示表格化数据。以下是创建表格的基本结构。

一个标准的表格应包含 <table><thead><tbody><tr><td> 等标签。即使你在源代码中遗漏了 <tbody>,浏览器在构建DOM时也会自动补全,以使结构完整。

<table>
  <thead>
    <tr><th>姓名</th><th>年龄</th></tr>
  </thead>
  <tbody>
    <tr><td>张三</td><td>25</td></tr>
  </tbody>
</table>

HTML非常宽容,浏览器会尽力修复一些常见的编码错误,例如:

  • 将标签名自动转换为小写。
  • 尝试补全未闭合的标签。
  • 修正属性值缺少的引号。

这些修复都体现在DOM中,而不是源代码里。这也是为什么有时调试需要同时查看源代码和DOM。


动态修改DOM

最后,我们强调DOM的动态特性。这是前端开发的核心概念之一。

使用浏览器的开发者工具,你可以直接编辑DOM中的文本内容,页面会立即更新以反映更改。但这只改变了内存中的DOM,并未改变服务器上的原始HTML源代码

在后续课程中,我们将学习使用JavaScript编写代码来动态地、程序化地修改DOM,从而实现丰富的交互效果。这是现代Web应用的基础。


本节课中我们一起学习了HTML源代码与文档对象模型(DOM)的区别,分析了基础HTML元素如列表、链接、图像和表格的用法,了解了浏览器如何容错地解析HTML,并初步认识了DOM的动态可修改特性。理解这些概念是进行Web应用程序开发的重要第一步。

011:北卡罗来纳州罗利

概述

在本节附加办公时间内容中,我们将跟随课程来到北卡罗来纳州罗利市,了解一次线下交流活动的概况,并认识几位参与课程学习的同学。


大家好,我们现在位于北卡罗来纳州罗利市。

我们在一个名为“Level up”的地方进行了一次很棒的办公时间活动。

我会给大家看一些楼上的视频,那里有食物、饮料、电子游戏和一些老式街机游戏。

和往常一样,我想向大家介绍几位来到现场的朋友。他们可以告诉大家自己的名字,如果愿意,也可以对课程说些什么。那么,我们开始吧。从你开始。

大家好,我是Ellen,这是我的伴侣。

你好,我是Neil。在接受了如此棒的教育之后,现在查克博士实际上是在说我,对吧?是的。Neil为AT&T工作,那些学过互联网历史、技术与安全课程的人完全明白这意味着什么。整个晚上的谈话都非常有趣。

确实如此。

大家好,我是John,我学习了Python系列课程。

非常好,谢谢。

我是Scott,目前正在学习Python系列课程的中间部分。Scott,你是做什么工作的?我已经退休了。你之前在英特尔工作吗?你制造微处理器吗?好的。我以为每个在英特尔工作的人都制造微处理器。

大家好,我叫Gabe,我正在学习“使用Python访问网络数据”这门课。我不理解的是,为什么Java程序员必须学Python。

因为Python就是这么棒。好的,好的。

我叫Ross,非常感谢你,查克博士。你让学习变得如此有趣。

谢谢Ross,很高兴你能来。谢谢你。

大家好,我叫Al。我是一名数据分析师,数据工程师,现在我在我的很多流程中使用Python。很酷。

大家好,我叫Jonathan。我一直在学习这个专项课程,非常享受,就是不断地钻研下去,这就是我想说的。

好的。

我是Henry,我为北卡罗来纳州政府工作。加油卡罗来纳。你见过州政府,对吧?总之,说真的,如果你在这个城市谈论一支赢不了的体育队伍,这里没有飓风队。不,没有飓风队。

我几年前开始学习Python,我学习了你的大部分课程,我非常喜欢。多年来它对我来说有点神秘,然后查克就像揭开了面纱,这真的很酷。很荣幸,太棒了,非常感谢。

大家好,我叫Mike。我完成了专项课程大约一半的内容。我从人文学科转到了编程领域,这是一个非常好的方式。人文学科的哪部分?古典学,古代语言?好的。我经常用这个作为大学里不该主修什么的例子。

好的,我们又完成了一次成功的办公时间活动。

我不完全知道下一次会在哪里举行。

可能会是几周或一个月左右之后。再次感谢,网上见。干杯。


总结

本节课中,我们一起了解了在北卡罗来纳州罗利市举行的一次课程线下办公时间活动。我们看到了学习社区的氛围,并认识了多位来自不同背景、正在学习Python及相关编程课程的同学。这展示了在线课程如何与线下交流相结合,构建学习共同体。

012:层叠样式表(CSS) 🎨

在本节课中,我们将要学习层叠样式表(CSS)的基础知识。CSS是用于控制网页外观和布局的语言。我们的目标不是让你成为顶尖的平面设计师,而是让你掌握足够的CSS知识,能够进行基本的样式调整,使后端开发出的网页看起来不至于太糟糕。这对于实现“关注点分离”的理念至关重要,它允许后端开发者和前端设计师各司其职。

网页如何工作 🌐

上一节我们介绍了HTML,本节中我们来看看CSS如何与HTML协同工作。

当你在浏览器中点击一个链接时,浏览器会向服务器请求一个HTML文件。服务器返回HTML文件后,浏览器开始解析它并构建文档对象模型(DOM)。在解析过程中,如果HTML文件里引用了CSS文件,浏览器会暂停解析,去获取这个CSS文件。CSS文件加载后,其中的规则会应用到DOM元素上,最终,HTML和CSS共同决定了你在屏幕上看到的每一个像素的精确布局。

目前,我们主要关注浏览器如何渲染内容。很快,我们将学习如何编写代码并与数据库交互。但现阶段,我们讨论的是浏览器端,因此很多示例代码甚至不需要运行在Web服务器上,因为我们还没有涉及动态内容。

开发者工具 🔧

如果你使用Firefox浏览器,有一款插件可能对你非常有用。当然,你可能也用Chrome。有些开发者同时使用Firefox和Chrome,因为Firefox的Web开发者工具(注意,这不同于开发者控制台)提供了一些非常酷的功能,能让你很好地调试CSS。你可以去Firefox安装这个工具来使用它。它和普通的Web开发者控制台不同,更侧重于CSS和其他设计方面的调试。

从朴素到精美:CSS的作用 ✨

我之前说过,HTML非常古老,已经有超过20年的历史。它最初以灰色背景、蓝色带下划线的链接为特征,我们当时能有一个列表就很开心了。在上一讲的HTML中,我们介绍了<ul>列表标签、<a>锚点标签、<img>图片标签等。

而现在,我们拥有了精美、商业化、以用户为导向、能创造收入的用户界面外观和体验。CSS正是实现这种精美外观的技术。正如我所说,我不会教你如何精通CSS,但我会告诉你它的重要性。

如果你访问我的网站“web applications for everybody .com”,并使用Web开发者插件关闭CSS,你会看到导航栏脱离了样式之后的样子。关闭CSS是我最喜欢做的事情之一。这样做时,你应该看到的是语义化标记——即没有CSS时,你的标记结构依然清晰、有意义。

以下是关闭CSS后你可能会看到的内容:

  • 你会看到链接,以及一个链接列表。
  • 这个链接列表实际上就是网页上那个漂亮的导航栏。
  • 旁边可能还有另一个列表。

对于视觉无障碍人士来说,这就是他们“看到”你网站的方式。他们看不到那些漂亮的阴影和渐变效果。因此,将视觉元素与语义元素分离非常重要。这不仅是为了方便视觉障碍人士,也为我们开发者自己着想。我们希望保持HTML结构尽可能简洁优美,然后让优秀的平面设计师来创造美丽的外观。

作为后端程序员,我们必须确保网页看起来不至于太差。看看我所有的网站,你就能明显看出我更偏向于后端开发。我使用Bootstrap和一些简单的CSS,让网站不至于难看,但也谈不上精美。我的目标是构建真正优美的标记结构,然后配上足够的CSS让它不至于丢人,最后再由比我更有才华的人把它变得真正漂亮。

CSS与HTML的协作 🤝

最终,你拥有HTML文件,它会加载CSS等资源。在CSS文件中,包含了大量高度详细的标记性指令,比如某个灰色条的宽度、某个部分使用的字体、各元素之间的间距等等。

你可以这样理解:浏览器加载HTML,加载CSS,然后将两者结合起来,逐像素地生成视觉上精美的外观。但保持你的HTML在技术或语义上的优美同样重要。

后端与前端的桥梁 🌉

我们再次回到后端和略微涉及前端的开发者角色,这也是本课程的目标。我们将生成一些HTML,并尽可能使其结构简单。我们会引入一些CSS,由它来告诉浏览器如何渲染这些元素。

在CSS中,规则是这样工作的:例如,你看到body标签,我就定义body标签应该发生什么;你看到p段落标签,我就定义所有p标签应该发生什么;你看到h1标题标签,我就定义它应该是蓝色的。这是两个独立的文件。

我编写代码,构建一个勉强能用的CSS来应付日常开发,然后可以把这部分工作交给别人。他们可以只编辑CSS文件,不断刷新页面,让网页变得越来越漂亮。这允许开发者与设计师分工协作。当然,有时你既是开发者也是设计师。但它确实允许有才华的开发者专注于构建逻辑,而有才华的设计师专注于让开发者的作品看起来更出色。

CSS语法基础 📝

CSS的语法与HTML、PHP和JavaScript都不同。HTML和CSS确实是编程语言,但它们不是过程式编程语言。HTML是一种声明式语言,你只需声明“这是一个段落”,浏览器会自己解决如何显示它。CSS也是如此,它是一种声明式语言。

这意味着你不写循环,没有变量,不控制逻辑步骤。你只是声明所有你希望发生的事情,它们基本上会同时生效。这就是CSS编程语言的语法,尽管它和任何过程式编程语言都大不相同。顺便说一句,很多人喜欢这种非过程式编程。

CSS的语法相当简单优雅,很多人甚至不用上课就能学会。其基本结构如下:

selector {
    property: value;
    another-property: another-value;
}
  • 选择器:最简单的选择器就是标签名,比如body
  • 声明块:由一对花括号 {} 包裹。
  • 属性与值:在花括号内,是一系列属性: 值;的声明。例如,font-family是一个属性,我们将其值设置为Arial。如果Arial字体不可用,则使用通用的sans-serif字体。font-family属性允许你按优先级降序列出一系列字体选项,用逗号分隔。每个声明以分号结束。

在花括号内可以有多个这样的声明。这就是基本的CSS语法。在之前的幻灯片中,你可能看到所有代码挤在一起,实际上CSS中的空白字符(空格、换行)不影响解析,但为了人类阅读方便,我们通常会进行缩进,这在认知上非常重要。

所以,规则分为两部分:规则应用的对象(选择器),以及一系列键值对(属性:值)。我们需要查阅资料来学习每个属性的作用和用法。网络是学习CSS的绝佳资源。例如,你可以搜索“CSS透明度属性是什么?”,然后会找到一个小示例,你可以复制粘贴。我们通过复制粘贴和Stack Overflow这样的网站来学习。Stack Overflow上有成千上万个关于“如何给按钮添加2像素边框”的答案。

学习资源与总结 📚

你可能需要为自己准备一份速查表。我并不擅长记忆CSS属性——也许因为我是后端程序员,而非前端。我知道一些东西,比如margin(外边距)、padding(内边距)、strongboldem(强调)和a(锚点)标签等,但我经常需要查阅资料。随着使用CSS越来越多,我有所进步,但像paddingmargin如何工作这类细节,你可能在一段时间内都需要速查表的帮助。

接下来,我们将讨论一些具体的CSS属性,但请理解,这绝不是CSS的全部。在接下来的课程中,我们会涵盖其中一部分。

本节课中我们一起学习了CSS的基本概念、它在网页渲染中的作用、与HTML的协作方式、基础的语法结构,以及作为后端开发者掌握基础CSS技能的重要性。记住,我们的目标是能够创建结构良好(语义化)的HTML,并应用足够的CSS使其外观得体,为后续可能的设计美化工作打下坚实基础。

013:CSS基础详解 🎨

在本节课中,我们将学习层叠样式表(CSS)的基础知识。CSS用于控制网页的样式和布局,我们将通过实例代码来理解其核心概念和工作原理。

概述

CSS允许我们为HTML元素定义样式,例如颜色、字体和间距。其核心思想是“层叠”,即多个样式规则可以应用于同一个元素,最终的样式由优先级和继承规则决定。

代码结构与查看方式

你可以下载源代码并在计算机上解压,或者直接在浏览器中浏览和修改代码,因为所有内容都是静态的。使用浏览器的开发者控制台来查看和调试CSS会非常有帮助。

理解CSS层叠

CSS样式可以通过多种方式应用到元素上。例如,我们可以使用内联样式属性,如 style="color: blue;"。其中 color 是属性,blue 是值。

在开发者工具中,当我们浏览文档对象模型(DOM)的不同元素时,可以看到每个元素应用的CSS值。例如,body 元素的字体被设置为 Arialsans-serifArial 是首选字体,sans-serif 是备用字体。

浏览器默认样式与层叠

每个HTML元素都有浏览器提供的默认样式。例如,<h1> 标签默认具有 display: blockfont-size: 2em 等属性。我们通过CSS样式表可以覆盖这些默认样式。

层叠规则决定了最终应用的样式。通常,距离元素最近、最具体的样式规则优先级最高。例如,一个内联样式会覆盖外部样式表中定义的样式。

常用CSS属性示例

以下是一些常用的CSS属性及其效果:

  • 边框border 属性常用于调试,可以直观地看到元素的边界。例如:border: 5px solid red; 会创建一个5像素宽的红色实线边框。
  • 边距margin 属性控制元素外部的空间。例如:margin-top: 1em; 会在元素上方增加1个“em”单位的空间。“em”是一个相对单位,等于当前字体中字符“M”的高度。
  • 字体font-family 属性定义字体。例如:font-family: Arial, sans-serif; 表示优先使用Arial字体,如果不可用则使用任何无衬线字体。

从内联样式到样式表

虽然可以直接在每个标签上使用 style 属性,但这会使代码难以维护。更常见的做法是使用样式规则。

我们可以为特定标签类型定义通用样式。例如,在 <style> 标签或外部CSS文件中编写:

body {
    font-family: Arial, sans-serif;
}
h1 {
    color: blue;
}
p {
    border-style: solid;
    border-color: red;
    border-width: 5px;
}
a {
    color: green;
    text-decoration: none;
    background-color: lightgray;
}

这样,样式规则会应用到整个文档中所有对应的标签上。

使用外部样式表

当有多个页面时,将CSS代码复制到每个页面会很繁琐。更好的方法是使用外部样式表。

在HTML文件的 <head> 部分,通过 <link> 标签引入外部CSS文件:

<link rel="stylesheet" href="style.css">

外部CSS文件(如 style.css)包含了所有样式规则,可以被多个HTML文件共享,便于统一管理和维护。

使用选择器定位元素

上一节我们介绍了为整个标签类型设置样式,但有时我们需要更精确地控制。本节中我们来看看如何使用ID和类选择器。

为了更灵活地控制样式,我们需要为HTML元素添加“句柄”。<div><span> 标签本身没有默认样式,常用于包裹内容以方便应用样式。

  • <div> 是一个块级元素。
  • <span> 是一个行内元素。

我们通过ID和Class属性来标记这些元素,并使用CSS选择器来定位它们。

以下是主要的选择器类型:

  • 标签选择器body { ... } 选择所有 <body> 标签。
  • ID选择器:使用 # 符号。ID在文档中应是唯一的。例如 #first { font-family: monospace; } 会应用到 id="first" 的元素上。
  • 类选择器:使用 . 符号。同一个类可以应用到多个元素上。例如 .more-space { margin-left: 2em; margin-right: 2em; } 会给所有具有 class="more-space" 的元素增加左右边距。
  • 后代选择器:用于选择特定元素内的元素。例如 #third p { background-color: yellow; } 只会将黄色背景应用到ID为 third 的元素内部的所有 <p> 段落上,而不会影响其他段落。

一个元素可以拥有多个类,例如 class="shout more-space",这样可以组合多种样式效果。

实战:构建一个导航栏

让我们来看一个简单的导航栏例子。良好的HTML结构应该清晰、语义化,然后再用CSS使其变得美观。

首先,我们使用HTML5的 <nav> 标签来定义导航区域,这对屏幕阅读器等辅助工具很有帮助。导航内容通常是一个链接列表。

<nav>
    <ul>
        <li><a class="back" href="#back">Back</a></li>
        <li><a class="forward" href="#forward">Forward</a></li>
    </ul>
</nav>

在没有CSS的情况下,这只是一个简单的列表。然后,我们可以通过一个单独的CSS文件(如 navbar.css)来为它添加样式,将其变成常见的横向导航栏模样。我们将在后续课程中详细讲解如何实现这些样式。

总结

本节课中我们一起学习了CSS的基础知识。我们了解了CSS层叠的概念,学习了如何通过内联样式、内部样式表和外部样式表来应用样式。我们掌握了使用标签选择器、ID选择器和类选择器来精确控制页面元素的外观,并通过 <div><span> 标签来组织内容。最后,我们看到了如何从语义化的HTML结构开始,再通过CSS来构建一个导航栏组件。理解这些基础是掌握网页样式设计的关键。

014:CSS样式设计 🎨

在本节课中,我们将要学习如何在HTML中应用CSS来改变网页的样式。我们将介绍三种主要的CSS引入方式,理解“层叠”的含义,并学习如何使用<div><span>标签以及类和ID选择器来精确控制样式。

概述

CSS(层叠样式表)用于定义HTML元素的视觉呈现。要使用CSS,首先需要将其与HTML文档关联起来。有三种主要方式可以实现这一点:内联样式、文档头部样式和外部样式表。理解这些方法以及CSS的“层叠”规则是有效设计网页的基础。

CSS的三种引入方式

有三种主要方法可以将CSS规则应用到HTML文档中。

1. 内联样式

内联样式通过HTML标签的style属性直接应用。这种方式定义的样式只对该特定标签有效。

例如,我们可以为一个段落标签添加红色实线边框:

<p style="border-style: solid; border-color: red; border-width: 5px;">这是一个有边框的段落。</p>

style属性内的规则仅作用于这个<p>标签,不会影响文档中的其他段落。

2. 文档头部样式

我们可以将CSS规则放在HTML文档的<head>部分的<style>标签内。这样,样式可以应用于整个文档中的特定元素类型。

例如,在<head>中定义规则,使所有<h1>标题变为蓝色:

<head>
    <style>
        h1 {
            color: blue;
        }
    </style>
</head>
<body>
    <h1>这个标题是蓝色的</h1>
</body>

这种方法比内联样式更高效,因为你无需在每个<h1>标签上重复编写style="color: blue;"。这遵循了“不要重复自己”的原则。

3. 外部样式表

最常用且推荐的方式是将CSS规则保存在一个独立的.css文件中,然后在HTML中通过<link>标签引入。

例如,创建一个名为style.css的文件:

/* style.css */
h1 {
    color: blue;
}

在HTML文件中引入它:

<head>
    <link rel="stylesheet" href="style.css">
</head>

这种方式使HTML文档更简洁,并且同一个样式表可以被多个页面共享,有利于维护和浏览器缓存。

理解“层叠”

“层叠”是CSS的核心概念。它意味着样式可以继承自父元素,并且更具体的规则会覆盖更一般的规则。

例如,如果在<body>标签上设置了字体:

<body style="font-family: Arial, sans-serif;">
    <p>这个段落继承了Arial字体。</p>
    <p style="font-family: monospace;">这个段落覆盖了继承的字体,使用等宽字体。</p>
</body>

第一个段落继承了<body>的Arial字体。第二个段落通过自己的style属性覆盖了继承的规则,使用了monospace字体。离元素“最近”的样式规则具有最高的优先级。

使用 <div><span> 标签

<div><span>是HTML中两个没有默认样式的通用容器标签,专门用于配合CSS进行布局和样式化。

<span> 标签

<span>是一个行内元素。它不会打断内容的正常流,就像<b><i>标签一样。它本身没有任何视觉效果,仅用于标记文本的一部分以便应用样式。

<p>这是一段<span style="color: red;">红色</span>的文字。</p>

<div> 标签

<div>是一个块级元素。它会独占一行,打断内容的水平布局。与<p>(段落)标签不同,<div>没有任何默认的边距或内边距。

<div style="border: 1px solid black;">第一个div块</div>
<div style="border: 1px solid black;">第二个div块紧挨着第一个</div>

两个<div>会上下排列,并且如果没有设置marginpadding,它们之间将没有空隙。而<p>标签则自带上下边距。<div>标签可以嵌套使用,常用于构建页面布局结构。

使用类和ID选择器

为了更灵活、更高效地应用样式,避免重复代码,CSS提供了类和ID选择器。

类选择器

类选择器以点号.开头。同一个类可以被多个HTML元素使用,一个元素也可以拥有多个类(用空格分隔)。

/* CSS规则 */
.highlight {
    background-color: yellow;
}
.large-text {
    font-size: 20px;
}
<!-- HTML应用 -->
<p class="highlight">这个段落有黄色背景。</p>
<p class="highlight large-text">这个段落既有黄色背景,又是大字体。</p>

ID选择器

ID选择器以井号#开头。在一个HTML文档中,每个ID应该是唯一的,只能用于一个元素。

/* CSS规则 */
#main-header {
    color: blue;
    text-align: center;
}
<!-- HTML应用 -->
<h1 id="main-header">网站主标题</h1>

选择器组合与层叠示例

选择器可以组合使用,并且层叠规则在此同样适用。更具体的选择器规则会覆盖更一般的规则。

/* 规则1:所有在id为“container”的元素内的段落 */
#container p {
    background-color: lightgray;
}
/* 规则2:拥有“special”类的段落(更具体) */
p.special {
    background-color: orange;
}
<div id="container">
    <p>这个段落背景是浅灰色。</p>
    <p class="special">这个段落虽然也在container里,但因为special类更具体,背景是橙色。</p>
</div>

总结

本节课我们一起学习了CSS样式设计的基础知识。我们掌握了三种引入CSS的方式:内联样式、文档头部样式和外部样式表,其中外部样式表是最佳实践。我们理解了“层叠”的含义,即样式继承和优先级规则。我们还学习了如何使用没有默认样式的<div>(块级)和<span>(行内)标签作为样式“钩子”。最后,我们探讨了使用类(.)和ID(#)选择器来创建可重用、模块化的样式规则,从而构建更清晰、更易维护的网页。

015:CSS中的图像、颜色与链接 🎨

在本节课中,我们将学习如何使用CSS来美化网页中的图像、设置颜色与字体,并掌握链接的样式控制。这些是构建视觉吸引力强、用户体验良好的网页的基础技能。

图像浮动与环绕

上一节我们介绍了CSS的基本布局概念,本节中我们来看看如何让图像与文本和谐共存。网页早期最吸引人的特性之一,就是能够实现文本环绕图像的效果。这通过CSS的 float 属性实现。

以下是如何让一张图片浮动到右侧,并使文本环绕其周围的代码示例:

img {
    float: right;
    margin: 1em;
}
  • float: right;:这条规则将图像从正常的文档流中“提升”出来,并将其浮动到其容器的右侧边缘。后续的文本内容会将其作为对齐边界,从而实现环绕效果。
  • margin: 1em;:这条规则为图像四周添加了 1em 的外边距,以防止文本紧贴图像,创造舒适的视觉间距。

这里引入了一个重要的CSS单位:em1em 等于当前字体中字母“M”的宽度。它是一个相对单位,会随着页面缩放或字体大小改变而相应变化,这使得布局更具响应性和适应性。

有时,你需要强制后续内容在浮动元素下方开始新行,这时可以使用 clear 属性。

<br clear="all">

clear="all" 属性(或CSS中的 clear: both;)会清除所有浮动,强制其后的元素(如下一段落)从左侧边界开始,即使这会在上方留下一些空白区域。

颜色的设置

颜色是网页设计中的核心元素。CSS提供了多种方式来指定颜色。

首先,有一些内置的颜色名称,如 aquablackbluered 等。虽然这些颜色可能不够精美,但对于开发者调试样式(例如临时设置 background-color: red; 来高亮某个元素)非常有用。

更常用和强大的是使用十六进制代码来定义颜色。

color: #ff0000; /* 纯红色 */
background-color: #00ff00; /* 纯绿色 */

十六进制颜色代码以 # 开头,格式为 #RRGGBB

  • RRGGBB 分别代表红、绿、蓝三个颜色通道。
  • 每个通道的值范围是 00FF(十六进制),对应十进制的 0 到 255。
  • 通过混合不同强度的红、绿、蓝光,可以创造出数百万种颜色。

例如,#ff0000 表示红色通道最大(ff),绿色和蓝色通道为0,因此是纯红色。这类似于图形软件中的RGB滑块。

还有一种简写形式是三位十六进制代码 #RGB,它相当于将每位重复一次,即 #RGB 等于 #RRGGBB。例如,#f00 等同于 #ff0000

字体与文本样式

默认的网页字体(如Times New Roman)可能显得过于传统。CSS的 font-family 属性允许我们指定更符合现代审美的字体。

body {
    font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
}

font-family 的值是一个字体栈,即按优先级排列的字体名称列表。浏览器会从左到右尝试使用列表中的字体,如果用户系统上没有第一种字体,则尝试下一种。通常,最后会指定一个通用字体族(如 sans-serifserifmonospace)作为兜底方案,确保至少有一种可用字体。

以下是其他常用的文本样式属性:

p {
    font-size: 16px; /* 字体大小,也可用em、rem等单位 */
    font-weight: bold; /* 字体粗细:normal 或 bold */
    font-style: italic; /* 字体样式:normal 或 italic */
    text-decoration: underline; /* 文本装饰:none, underline, overline, line-through */
}
  • font-weightfont-style 是独立的属性,可以组合使用,例如同时设置 bolditalic
  • 关于 font-size:使用像素(px)是直接的方式,但现代浏览器在用户缩放页面时通常会智能地缩放以px定义的字体大小,以提升可访问性。相对单位如 emrem 是更灵活的选择。

链接的样式控制

超链接是网页的基石。CSS允许我们根据链接的不同状态来设置样式,从而提升用户的交互体验。

链接有以下几种主要状态,可以使用伪类选择器来分别定义样式:

/* 默认/未访问的链接 */
a:link {
    color: blue;
    text-decoration: underline;
}

/* 已访问的链接 */
a:visited {
    color: purple;
}

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/5a0b9cd2614fbc79e29a6e109b1c96b5_24.png)

/* 鼠标悬停在链接上时 */
a:hover {
    color: red;
    background-color: yellow;
    text-decoration: none;
}

/* 链接被点击的瞬间(到新页面加载前) */
a:active {
    color: green;
}

以下是各状态的作用:

  • :link:定义尚未被用户访问过的链接的样式。
  • :visited:定义已被访问过的链接的样式。
  • :hover:当鼠标指针悬停在链接上时生效。常用来提供视觉反馈,例如改变颜色或背景。
  • :active:在链接被点击(鼠标按下但未释放)的瞬间生效。由于这个过程通常非常短暂,此状态使用较少。

这种基于状态的样式设计,能够有效引导用户注意力,并让导航操作更加直观和吸引人,这在像谷歌搜索结果页这样的链接列表中效果显著。

总结

本节课中我们一起学习了CSS中几个美化网页内容的关键技术。我们掌握了如何使用 float 属性实现图像的文字环绕效果,并了解了 em 这个相对单位。我们学习了通过颜色名称和十六进制代码来设置颜色。我们还探讨了如何通过 font-family 字体栈来指定字体,并控制字体大小、粗细等样式。最后,我们深入了解了如何使用伪类选择器(:link:visited:hover:active)为超链接的不同交互状态设置样式,从而大大增强用户的浏览体验。结合这些技能,你已经可以为网页添加丰富的视觉层次和交互反馈了。

016:CSS样式设计详解 🎨

在本节课中,我们将深入学习CSS的核心概念,包括字体、颜色、链接样式、盒模型、定位以及如何构建一个导航栏。我们将通过具体的代码示例来解释每个概念,确保初学者能够理解并应用。


字体设计

上一节我们介绍了CSS的基础,本节中我们来看看如何为网页设置字体。

字体可以通过几种方式引入到网页中。

以下是几种主要的字体来源:

  • 默认字体:每个浏览器都必须支持一些默认字体,如 SerifSans-serifmonospacecursivefantasy。这些字体通常用作备用字体。
  • 系统字体:可以使用用户系统上已安装的字体,如 ArialHelvetica。但并非所有系统都安装了这些字体,因此通常需要指定多个备用字体。
  • 网络字体:可以从网络引入自定义字体。例如,通过 <link> 标签引入 Lato 字体。浏览器会下载并缓存这些字体。在代码中,可以这样设置:
    font-family: 'Lato', sans-serif;
    
    这样,如果无法加载 Lato 字体,浏览器会回退到 sans-serif 字体。

颜色应用

了解了字体之后,我们来看看如何为网页元素添加颜色。

颜色可以通过多种方式定义。

以下是定义颜色的几种方法:

  • 颜色名称:最早的浏览器定义了一些默认颜色名称,如 redblue 等。现代浏览器支持更丰富的颜色名称,但一些经典名称在所有浏览器中都通用。
  • 十六进制值:使用 # 开头的十六进制数表示颜色。格式为 #RRGGBB,其中 RR 代表红色,GG 代表绿色,BB 代表蓝色。例如,#8b4513 是一种棕色。
  • Web安全色:为了在不同设备上显示一致,有时会使用特定的十六进制组合,如 #884411,以减少颜色差异。
  • HTML5颜色选择器:在表单中,可以使用 <input type="color"> 创建一个颜色选择器。用户选择的颜色值(如 #00FF00 代表绿色)可以被JavaScript或服务器端代码读取。

链接样式

现在,我们来看看如何为网页中的链接(<a> 标签)设置样式。

在早期,未访问的链接是蓝色的,访问过的链接是紫色的。现在,我们可以通过CSS完全控制链接的外观。

以下是一个为特定区域内的链接设置样式的例子:

#cool a {
    font-weight: bold;
}

这段代码表示,只对 idcool 的元素内部的链接应用加粗样式。这样可以保留页面其他部分的默认链接样式。

链接有几种不同的状态,可以分别设置样式:

  • a:link:设置未访问链接的样式,例如 color: red;
  • a:visited:设置已访问链接的样式,例如 color: orange;
  • a:hover:设置鼠标悬停在链接上时的样式。例如,可以移除下划线并改变颜色:
    a:hover {
        text-decoration: none;
        color: white;
        background-color: navy;
    }
    
  • a:active:设置链接被点击但页面尚未跳转瞬间的样式。这个状态通常很难捕捉,但可以用于提供即时反馈。

图像与浮动

接下来,我们学习如何处理图像,特别是如何让文字环绕图片。

使用 float 属性可以让图像浮动到文本的左侧或右侧,从而实现文字环绕效果。

以下是关于图像浮动的重要知识点:

  • 基本浮动:将 float: left;float: right; 应用于 <img> 标签,图像就会脱离正常的文档流,后续的文本会环绕它。
  • 清除浮动:有时我们不希望某个元素受到前面浮动元素的影响。这时可以使用 <br clear="all"> 或CSS的 clear: both; 属性来清除浮动,使该元素从新的一行开始。
  • 内联图像:图像可以像字符一样嵌入在文本行中。
  • 图像链接:可以将 <img> 标签放在 <a> 标签内,使图像成为一个可点击的链接。

CSS层叠与优先级

CSS的全称是“层叠样式表”(Cascading Style Sheets)。理解样式的层叠和优先级至关重要。

样式的应用遵循“就近原则”和“重要性”规则。

  • 就近原则:离HTML元素更近的样式规则优先级更高。例如,内联样式(写在标签的 style 属性里)优先级高于 <style> 标签中的样式,而 <style> 标签中的样式又高于外部样式表。
  • !important 规则:在样式声明后添加 !important 可以大幅提高其优先级,使其覆盖其他冲突的样式,即使那些样式“更近”。
  • 谨慎使用 !important:过度使用 !important 会导致样式难以维护和管理,通常意味着样式结构存在问题,例如在与某个框架的样式冲突时采取的临时解决方案。

盒模型

每个HTML元素(如 <div><p><h1>)都可以被看作一个矩形的盒子。理解盒模型是进行页面布局的基础。

盒模型从内到外由四部分组成:

  1. 内容(Content):元素的实际内容,如文本、图片。
  2. 内边距(Padding):内容与边框之间的透明区域。
  3. 边框(Border):围绕内容和内边距的线。
  4. 外边距(Margin):盒子与其他盒子之间的透明区域,用于控制元素间的距离。

重要特性

  • 元素的 background-color 会填充到内容和内边距区域。
  • 边框有自己的颜色和样式。
  • 外边距区域是透明的,会显示父元素的背景。

可以通过 widthheightpaddingbordermargin 等属性来控制盒子的大小和间距。


控制溢出与尺寸

当我们为盒子设置了固定尺寸,而内容超出时,就需要处理溢出问题。

CSS提供了 overflow 属性来控制超出盒子范围的内容。

以下是 overflow 属性的几种常用值:

  • overflow: visible;(默认):内容会溢出并显示在盒子外面。
  • overflow: scroll;:无论内容是否溢出,都会在盒子内显示滚动条。
  • overflow: auto;:只有内容溢出时才会显示滚动条。
  • overflow: hidden;:溢出的内容会被直接裁剪掉,不可见。

在设置尺寸时,建议使用相对单位(如 em),而不是绝对单位(如 px),这样在用户缩放页面时,布局能更好地适应。


元素定位

默认情况下,元素按照它们在HTML中出现的顺序进行排列(即“文档流”)。CSS定位允许我们打破这种流,将元素精确地放置在页面的特定位置。

CSS的 position 属性有几个关键值:

  • static(默认):元素处于正常的文档流中。
  • relative:元素先按正常流布局,然后相对于其原始位置进行偏移。使用 toprightbottomleft 属性来移动它。重要:它原来在文档流中占用的空间会被保留。
  • fixed:元素相对于浏览器窗口进行定位,即使页面滚动,它也会固定在窗口的某个位置(如常见的“回到顶部”按钮)。
  • absolute:元素相对于其最近的非 static 定位的祖先元素进行定位。如果没有这样的祖先,则相对于 <body> 标签。重要:它会脱离正常的文档流,不再占用空间。

使用 relativeabsolutefixed 定位时,可能会造成元素重叠。


Z轴索引(Z-index)

当元素发生重叠时,z-index 属性决定了哪个元素显示在上层。

z-index 的值是一个整数。数值越大,元素在堆叠顺序中就越靠前(离用户越近)。

注意事项

  • 默认情况下,元素的 z-indexauto,可视为 0。
  • 只有定位元素(position 值为 relativeabsolutefixed)的 z-index 才会生效。
  • 管理复杂的 z-index 可能很棘手,特别是当使用多个第三方库或框架时,它们可能设置了很高的 z-index 值。

构建导航栏实例

最后,我们运用所学知识,来看一个简单导航栏的实现片段。

导航栏通常使用 <nav> 标签包裹一个无序列表 <ul> 来构建。

以下是实现导航栏的一些关键CSS技巧:

  • 整体样式:为 <nav> 设置背景色、高度等。
  • 列表样式:移除 <ul> 默认的项目符号 (list-style-type: none;) 和内边距 (padding: 0;)。
  • 列表项布局:将 <li>display 属性设置为 inline-block 或使用 float,使其水平排列。
  • 链接样式:为 <a> 标签设置颜色、内边距,并移除下划线 (text-decoration: none;)。
  • 高级定位:可以使用 position: absolute; 将“上一页”、“下一页”这样的按钮精确放置在导航栏的左右两端。

CSS提供了强大的控制能力,但也需要大量的练习才能熟练掌握。本节只是一个入门,帮助你理解CSS的基本机制。


本节课总结
我们一起学习了CSS的核心概念,包括字体和颜色的设置、链接状态的样式控制、利用浮动实现图文混排、理解CSS层叠优先级规则、掌握盒模型的组成、处理内容溢出、使用不同方式进行元素定位、通过 z-index 控制层叠顺序,最后还分析了一个导航栏的构建实例。掌握这些基础知识是进行精美网页布局的第一步。

017:宾夕法尼亚州费城

在本节课中,我们将回顾一次在费城举行的课程附加办公时间活动。通过几位参与学生的分享,你可以了解这门课程对他们的实际帮助以及学习体验。


活动概述

大家好,我们在费城。又一次成功的办公时间活动刚刚结束,我们有机会见到一些你的同学。他们愿意向大家打招呼,介绍自己的名字,并分享一条信息。

以下是他们的分享。


学生分享

以下是在场几位学生的自我介绍与学习感言。

  • Pre: 你好,我叫Pre。我最近在上查克博士的课,我很兴奋能从中学习更多关于PHP的知识。谢谢你。
  • Ludne: 我叫Ludne。我实际上在密歇根上过学,但不是密歇根大学,是凯特林大学。查克博士的课程帮助我进入了一个商业信息与分析的研究生项目,我对此感觉非常好。
  • Ellen Thompson: 大家好,我是Ellen Thompson。我证明了“老狗也能学会新把戏”。我49岁了,过去总是雇佣程序员,也许现在我自己终于能知道他们在做什么了。谢谢查克博士。(查克博士补充:49岁一点也不老!)
  • 来自厄瓜多尔的学生: 你好,我来自厄瓜多尔。我上查克博士的课……那是很久以前的事了。我学到了很多,你们也应该来上这门课。(提问:你特地从尼加拉瓜赶来参加办公时间?)厄瓜多尔,是厄瓜多尔。我从厄瓜多尔一路赶来参加办公时间。(开玩笑)不完全是,我是来参加一个会议的。我们都在参加一个开源会议。
  • Will: 我是Will。我上过互联网历史课程和Python课程。查克博士太棒了。只要他在你的城市,一定要去找他喝杯啤酒。哦,对了,每次有人说“向Will开火”时他都会跑开。(注:此为英文名“Will”与短语“at will”的双关玩笑)
  • Tom Brown: 我是Tom Brown。我一直在上Python编程课,这门课太棒了,对我的工作帮助很大。你的教学风格非常出色,我真的很享受学习过程,这是一段很棒的时光。(关于工作背景的讨论略)
  • Tom Brown的儿子: 你好,Tom Brown,我是他儿子,T2。我一直在学一些Java,我想根据我所听到的,我也应该上一门(查克博士的)课。(查克博士建议:是的,你应该先学Python,然后再学Java。这很有帮助。任何能让学习变简单的东西都好。)实际上,我想他的名字是TB2,不是T2。

活动尾声与预告

好了,各位,以上就是我们的分享。你在这里,你想入镜吗?你上我的课吗?哦,是的。我以为你是……哦,你在打车,好吧,就这样吧。

街上的人,一个没上我课的人,在整个费城……我的意思是,这概率有多大?

所以,下周我们将会在……我想是爱荷华州。但我可能不会举行办公时间,因为那是个非常小的镇子。也许我应该试试,也许我会在爱荷华州的一个小镇上找到一个正在上我课的人。那个镇子叫……不是得梅因,那是个大城市。好吧,下次见。


总结

本节课中,我们一起回顾了费城办公时间活动中学生们的真实反馈。从转行学习到助力升学,再到提升职业技能,这些分享展示了本课程对不同背景学习者的广泛价值。同时,课程主讲人查克博士与学生的轻松互动,也体现了开放、互助的学习社区氛围。

018:在Macintosh上安装MAMP 🍎

在本节课中,我们将学习如何在Macintosh电脑上安装和配置MAMP软件。MAMP是一个集成了Apache服务器、MySQL数据库和PHP的本地开发环境,是进行Web应用程序开发的重要工具。我们将从下载开始,完成安装,并配置PHP以显示错误信息,最后创建一个简单的PHP页面来验证安装是否成功。

下载MAMP

首先,我们需要从官方网站下载MAMP软件。

以下是下载步骤:

  1. 访问 mamp.info 网站。
  2. 找到适用于Mac的MAMP版本并点击下载。

下载完成后,文件通常会保存在“下载”文件夹中。

安装MAMP

下载好安装文件后,我们就可以开始安装了。

以下是安装过程:

  1. 打开“下载”文件夹,找到MAMP安装文件(例如 MAMP_PRO_4.0.1.pkg)。
  2. 双击该文件以启动安装程序。
  3. 在安装过程中,接受所有默认设置并继续安装。

安装完成后,MAMP应用程序会被放置在“应用程序”文件夹中。

启动与配置MAMP

上一节我们完成了MAMP的安装,本节中我们来看看如何启动它并进行必要的配置。

启动MAMP后,控制面板会显示服务器状态和系统配置信息,例如PHP版本和配置文件的位置。对于开发工作,我们需要确保PHP能显示错误信息,这有助于调试代码。

默认情况下,PHP的 display_errors 设置是关闭的。我们需要编辑PHP的配置文件来开启它。

以下是修改PHP配置的步骤:

  1. 在MAMP控制面板中,查看PHP配置文件的路径(例如 /Applications/MAMP/bin/php/php7.1.0/conf/php.ini)。
  2. 使用文本编辑器(如TextEdit)打开这个 php.ini 文件。
  3. 在文件中搜索 display_errors 这一行。
  4. display_errors = Off 修改为 display_errors = On
  5. 同时,可以搜索 display_startup_errors 并将其设置为 On
  6. 保存文件。

修改配置文件后,必须重启MAMP服务器才能使更改生效。

创建第一个PHP页面

配置好开发环境后,现在我们来创建一个简单的PHP页面,以测试环境是否工作正常。

Web服务器的根目录位于 /Applications/MAMP/htdocs/。我们在这个目录下创建的文件可以通过浏览器访问。

以下是创建测试页面的步骤:

  1. /Applications/MAMP/htdocs/ 目录下,创建一个新文件夹,例如命名为 first
  2. 在该文件夹内,创建一个名为 index.php 的文件。
  3. index.php 文件中,输入以下简单的PHP代码:
    <?php
    echo "Hello from my first web page.";
    ?>
    
  4. 保存文件。

现在,打开浏览器,访问地址 http://localhost:8888/first/。如果配置正确,浏览器将显示“Hello from my first web page.”。服务器会自动寻找并执行 index.php 这个默认文件。

总结

本节课中我们一起学习了在Macintosh上搭建PHP开发环境的完整流程。我们从下载MAMP开始,完成了软件的安装。接着,我们启动了MAMP,并修改了PHP配置文件,开启了错误显示功能,这对于开发调试至关重要。最后,我们在服务器的根目录下创建了一个简单的PHP页面,并通过浏览器成功访问,验证了整个开发环境已配置成功。现在,你已经拥有了一个本地的Web服务器环境,可以开始进行PHP和Web应用程序的开发了。

019:在Windows 10上安装MAMP与编写第一个PHP程序 🛠️

在本节课中,我们将学习如何在Windows 10系统上安装MAMP集成开发环境,并使用Atom文本编辑器编写并运行第一个PHP程序。课程将涵盖从软件下载、安装配置到编写、调试代码的完整流程。

安装MAMP

上一节我们介绍了课程目标,本节中我们来看看如何下载并安装MAMP。

首先,访问MAMP官方网站并下载适用于Windows的安装程序。下载完成后,运行安装程序。

安装程序启动后,选择安装语言为“English”,然后点击“Next”。

在安装选项中,取消勾选“安装MAMP Pro”的复选框,仅安装免费的MAMP版本。接着,接受许可协议。

选择MAMP的安装目录,建议使用默认路径。然后继续点击“Next”完成后续安装步骤。

安装完成后,运行MAMP应用程序。

配置MAMP服务器

上一节我们完成了MAMP的安装,本节中我们来看看如何启动和配置服务器。

启动MAMP后,桌面上会出现其快捷方式。首次运行时,系统可能会弹出防火墙警告。

以下是需要允许的访问权限:

  • 允许Apache HTTP服务器通过防火墙。
  • 允许MySQL数据库服务器通过防火墙。

这两个权限非常重要,以确保Web服务器和数据库能正常通信。配置完成后,服务器应成功启动。

此时,可以打开MAMP的“Start Page”起始页。在起始页中,可以查看PHP信息或打开phpMyAdmin。

如果phpMyAdmin能够正常加载并显示类似界面,则表明MAMP已成功安装并运行。

安装Atom文本编辑器

上一节我们配置好了服务器环境,本节中我们来看看如何安装代码编辑器。

欢迎回到课程。现在我们将安装Atom文本编辑器。你可以选择任何喜欢的文本编辑器,推荐Atom是因为它跨平台且功能强大。请勿使用记事本或Word,因为它们会破坏代码文件结构。我们需要一个具备语法高亮等功能的专业文本编辑器。

下载Atom安装程序并运行。

按照安装向导的提示完成Atom的安装。

编写第一个PHP程序

上一节我们准备好了所有工具,本节中我们来看看如何创建并运行第一个PHP脚本。

现在我们将编写第一个PHP应用程序。请同时启动MAMP和Atom。

在MAMP中,需要启动Apache服务器和MySQL数据库服务器。

然后,打开MAMP的起始页,这里包含有用的信息,例如PHP配置详情。运行phpMyAdmin以确认数据库服务正常。

如果phpMyAdmin能正常显示,说明所有服务运行良好。

接下来开始编写代码。在Atom中,创建一个新文件。

输入以下HTML代码:

<h1>Hello from a web page</h1>

现在保存这个文件。需要将其保存到MAMP的网站根目录下。

该目录路径通常为:C:\MAMP\htdocs\。你可以在此目录下创建文件夹来组织项目。

创建一个名为first的新文件夹。

将文件以index.php为名保存到first文件夹中。index.php是一个特殊文件名,当浏览器访问目录时,默认会打开此文件。

保存后,Atom会为代码提供语法高亮。

现在,打开浏览器,访问以下地址来运行这个文件:

http://localhost/first/index.php

你会看到网页显示了“Hello from a web page”。目前这只是一个纯HTML页面。

在PHP中嵌入代码

上一节我们创建了一个纯HTML文件,本节中我们来看看如何在其中执行PHP代码。

到目前为止,我们还没有运行任何PHP代码。现在让我们添加一些PHP代码。

PHP代码需要包裹在特定的标签内。在index.php文件中,添加以下代码:

<?php
echo "Hi there.\n";
?>

保存文件,然后在浏览器中刷新页面。

页面上会显示“Hi there”。这段文本是由PHP的echo语句输出的。

我们可以在PHP代码中混合HTML。例如:

<p>
<?php
echo "Hi there";
?>
</p>

保存并刷新后,输出结果会嵌入到段落标签中。

在PHP代码块中,服务器会执行其中的逻辑,并将结果输出到网页上。例如,我们可以进行计算:

<?php
$x = 6 * 7;
echo "The answer is " . $x;
?>

保存并刷新页面,浏览器将显示计算结果。

所有在PHP代码块中echo或打印的内容,都会成为最终生成的网页的一部分。你可以将文件和文件夹放在htdocs目录下,然后通过浏览器访问来执行它们。

启用PHP错误显示

上一节我们编写了能正常工作的代码,本节中我们来看看如何处理代码错误,这是一个非常重要的开发配置。

我们刚刚完成了第一个Web应用程序。现在,让我们故意在代码中制造一个语法错误,例如删除一行代码末尾的分号。

保存文件并在浏览器中刷新。

此时,页面可能只显示一个不明确的错误(如HTTP 500错误),而没有具体的错误信息。这对于调试代码非常不利。

默认情况下,MAMP安装后关闭了在网页上显示错误的功能。这对生产环境是安全的,但对开发者不友好。我们需要启用它。

打开MAMP起始页,点击“PHPInfo”链接。

在PHPInfo页面中,找到“Loaded Configuration File”这一行。它显示了当前加载的PHP配置文件路径,例如:C:\MAMP\conf\php7.1.5\php.ini。请记下你的PHP版本号。

用Atom或其他文本编辑器打开这个php.ini配置文件。

在文件中搜索display_errors设置项。

找到以下两行:

display_errors = Off
display_startup_errors = Off

将它们修改为:

display_errors = On
display_startup_errors = On

修改后保存文件。

由于修改了服务器核心配置,需要重启MAMP服务才能使更改生效。在MAMP界面中停止Apache和MySQL服务器,然后再次启动它们。

服务器启动后,再次在浏览器中刷新那个包含错误的页面。

现在,页面上会显示详细的错误信息,例如:“Parse error: syntax error, unexpected ‘echo’ (T_ECHO) in C:\MAMP\htdocs\first\index.php on line 6”。这明确指出了错误位置和类型。

根据错误提示,回到代码中,在第6行补上缺失的分号。

保存文件并再次刷新浏览器,页面应该能正常显示了。

请务必在开发早期完成此项设置。如果关闭错误显示,你在调试代码时将浪费大量时间。开启错误显示能让你在犯错时立刻获得反馈,是保持开发效率的关键。


本节课中我们一起学习了在Windows 10上安装和配置MAMP开发环境,安装了Atom编辑器,创建并运行了第一个PHP程序,并学会了如何启用PHP错误显示功能以方便调试。这些是开始PHP Web开发的基础步骤。

020:在Windows 10上安装XAMPP 🖥️

在本节课中,我们将学习如何在Windows 10操作系统上安装XAMPP。XAMPP是一个集成了Apache、MySQL、PHP和Perl的免费开源软件包,它是搭建本地Web开发环境的理想工具。我们将从下载开始,逐步完成安装、配置,并运行一个简单的PHP程序来验证安装是否成功。

下载XAMPP

首先,我们需要从Apache Friends官方网站下载XAMPP的Windows版本。

以下是下载步骤:

  1. 访问Apache Friends网站。
  2. 找到适用于Windows的XAMPP版本。
  3. 点击下载链接,开始下载安装程序。

安装XAMPP

下载完成后,我们就可以运行安装程序了。安装程序通常位于系统的“下载”文件夹中。

以下是安装过程中的关键步骤:

  1. 运行下载好的安装程序。
  2. 在安装向导中,建议使用默认的安装路径(通常是 C:\xampp)。这可以避免一些潜在的权限问题。
  3. 在组件选择界面,对于本课程,我们主要需要Apache(Web服务器)和MySQL(数据库)。可以取消勾选不需要的组件,如Tomcat、Perl和Fake Sendmail。
  4. 完成上述设置后,继续安装过程。安装可能需要一些时间。

启动XAMPP控制面板

安装完成后,我们不会立即启动控制面板。我们先找到它的位置,以便日后可以方便地启动它。

以下是启动步骤:

  1. 打开文件资源管理器,进入XAMPP的安装目录(例如 C:\xampp)。
  2. 在该目录下,找到名为 xampp-control.exe 的文件,这就是控制面板程序。
  3. 运行该程序。首次启动时,可能会提示选择语言,并可能出现系统安全对话框,请选择“允许”或“是”。
  4. 为了方便,建议右键点击任务栏上的控制面板图标,选择“固定到任务栏”。

启动Apache和MySQL服务

在XAMPP控制面板中,我们需要启动两个核心服务。

以下是启动服务的步骤:

  1. 在控制面板中,找到Apache模块,点击其右侧的“Start”按钮。启动成功后,其名称旁会显示绿色的“Running”标识,背景变为绿色。
  2. 同样地,找到MySQL模块,点击“Start”按钮启动它。
  3. 确保两个服务都成功运行,没有出现红色的错误提示。

验证安装与配置PHP

服务启动后,我们可以通过浏览器访问本地服务器来验证安装。

以下是验证步骤:

  1. 打开浏览器,在地址栏输入 http://localhosthttp://localhost/dashboard/。如果看到XAMPP的欢迎面板,说明Apache服务器运行正常。
  2. 在欢迎面板上,可以点击“PHPInfo”链接查看详细的PHP配置信息。
  3. 在PHP配置中,有一个对开发非常重要的设置叫 display_errors开发时,此选项应设置为 On,以便在页面上显示错误信息,方便调试;上线(生产环境)时,则应设置为 Off,以隐藏敏感信息。
  4. 在XAMPP中,默认通常已设置为 On。如需修改,可以点击控制面板中Apache模块所在行的“Config”按钮,选择“PHP (php.ini)”。在打开的配置文件中,使用 Ctrl+F 搜索 display_errors,将其值修改为 On,保存文件后,必须重启Apache服务(先Stop,再Start)才能使更改生效。
  5. 返回浏览器,刷新PHPInfo页面,再次搜索 display_errors,确认其状态已更新。

创建并运行第一个PHP程序

现在,我们来创建一个简单的PHP文件,测试整个开发环境是否工作正常。

以下是创建和测试步骤:

  1. 打开你喜欢的文本编辑器(如VS Code、Sublime Text或Notepad++)。课程演示中使用了Atom。
  2. 新建一个文件,输入以下混合了HTML和PHP的代码:
    <!DOCTYPE html>
    <html>
    <head>
        <title>My First PHP</title>
    </head>
    <body>
        <h1>Hello World from HTML</h1>
        <?php
            echo "<p>Hello World from PHP</p>";
            $x = 5;
            $y = 6;
            $z = $x + $y;
            echo "<p>The sum of $x and $y is: $z</p>";
        ?>
    </body>
    </html>
    
  3. 保存文件。关键的一步是:必须将文件保存在XAMPP的 htdocs 目录下,该目录是Apache服务器的默认根目录。路径通常是 C:\xampp\htdocs\
  4. 为了管理不同的项目,建议在 htdocs 下为每个项目创建一个单独的文件夹。例如,创建一个名为 first 的文件夹。
  5. 将刚才创建的文件以 index.php 为名,保存到 C:\xampp\htdocs\first\ 目录下。
  6. 打开浏览器,访问 http://localhost/first/index.php。如果页面正确显示“Hello World from HTML”和PHP计算出的结果“The sum of 5 and 6 is: 11”,则说明你的本地PHP开发环境已完全配置成功。


本节课中我们一起学习了在Windows 10上搭建PHP本地开发环境的完整流程。我们从下载并安装XAMPP开始,学习了如何启动Apache和MySQL服务,验证了安装并检查了关键的PHP配置(display_errors)。最后,我们通过创建并运行一个包含HTML和PHP代码的文件,成功测试了整个环境。现在,你已经拥有了一个功能完备的本地服务器,可以开始进行数据库构建和PHP代码编写等Web开发工作了。

021:Brian Behlendorf谈Apache基金会

概述

在本节特别内容中,我们将跟随Apache软件基金会联合创始人Brian Behlendorf的讲述,了解Apache HTTP服务器项目的起源故事、开源协作模式及其核心原则。这对于理解开源文化和现代Web技术的基础至关重要。

Apache的起源:一群Web管理员的协作

Apache项目始于Web的早期阶段。当时,一群不满现状的Web管理员正在使用一款免费提供的Web服务器软件,但他们在使用中遇到了困难。

于是,这群人开始修复软件中的错误。他们彼此分享这些修复补丁,就像交换棒球卡一样。这些补丁在当时被称为“patches”。

有一天,他们发现最初发布这款Web服务器的团队已经解散了,因为其所有开发人员都离开并加入了一家名为Netscape的新公司。

从用户到维护者的转变

因此,我们中的一群人决定,既然我们依赖这款软件,我们不想成为全职的Web服务器开发者,但我们希望能够继续免费使用它,并能够改进它。

我们查看了代码的许可证。许可证的内容大致是:软件在此,你可以用它做任何事,但如果它出问题了别怪我们。我们认为,这是一笔相当不错的交易。

我们决定将同样的交易传递给下一批人。于是,我们组建了一个邮件列表。这个群体主要由Web管理员和在早期互联网服务提供商、网站设计公司或像亚马逊、互联网电影数据库这类地方工作的人员组成。

我们将各自的补丁合并在一起,并因此决定将其命名为Apache服务器。项目就这样向前推进了。

开源协作的核心模式

我们工作模式的核心是基于我们作为一个同行群体,共同提出想法、审查彼此的想法和补丁,并以团队形式修复错误。

我们中大多数人从未见过面。当然,有些人见过,但作为一个整体,我们直到1998年才首次线下见面。那时距离我们项目启动已经过去了三年,而且我们早已成为全球最主要的Web服务器产品。

尽管如此,当时仍然没有从这款开源软件中获得任何直接收入。但我们中的许多人通过在这款软件之上构建产品来谋生。

开源项目的成功逻辑

我认为,这实际上概括了大多数成功开源项目的故事:人们为了共同的技术和共同的问题而协作,这样他们就可以在其他领域赚钱,或者可以享受乐趣、尝试新想法、进行实验。

Apache、Linux以及所有其他开源项目的故事都是如此。事实证明,当人们拥有共同的目标时,协作并不困难。这个目标就是:让我们构建一个能实现所有这些伟大功能的产品。

促进决策的关键设计:模块化API

我们做的一件事让某些决策变得容易,那就是设计了一个非常模块化的API

这使得我们可以轻松地说:如果你想要那个特别酷的功能,就把它作为一个独立的模块来实现,并让它获得成功。一旦它成功了,我们再决定是否将其纳入核心产品。

开源项目的基石:分叉权

另一个适用于所有开源项目的关键点是,像我们的项目以及Linux等所使用的开源许可证,都附带一项称为 “分叉权” 的权利。

这意味着,如果我变得独断专行,开始强行推动项目朝某个方向发展,而其他人都不愿意跟随,那么所有其他人可以决定拿走代码,去其他地方启动一个不同的项目。

如果他们无法将我踢出项目(这通常是他们会先尝试的做法),这项分叉权意味着你无需容忍独裁者,也无需应对做出糟糕技术决策的人。你可以将未来掌握在自己手中。如果你找到一群认同你想法的人,你们可以围绕它创建一个新项目。

分叉权对治理的影响

我认为,这项规则,即分叉权,限制了我们每当讨论群体如何决策以及冲突出现时可能看到的极端行为。它意味着领导风格不应该是控制和预先策划行动,而应该是能够争取人们的支持,说服他们你将重视他们的努力、重视他们所做的贡献。

总结

本节课中,我们一起学习了Apache HTTP服务器项目的诞生背景。它源于用户对共同依赖工具的需求,并通过邮件列表和补丁共享形成了早期的开源协作。其成功依赖于模块化设计和开源许可证赋予的 “分叉权” ,这共同塑造了一种基于共识、尊重贡献而非强制控制的去中心化治理模式。这不仅是Apache的故事,也是许多成功开源项目的缩影。

022:不列颠哥伦比亚省温哥华 🇨🇦

在本节课中,我们将回顾一次在加拿大温哥华举行的课程附加办公时间,了解其背景并与部分参与的学生见面。

大家好,我们现在位于加拿大不列颠哥伦比亚省的温哥华。本次办公时间的节奏堪称世界纪录——在三周内,跨越两个大洲、三个国家,举行了四次办公时间。这确实是一个异常忙碌的办公时间月。

和以往一样,在温哥华这里,我们希望与一些同学见面并打招呼。以下是几位愿意向课堂问好的同学。

以下是参与本次办公时间的学生自我介绍:

  • JD:大家好。
  • Friends:大家好,我是Friends。
  • Anna Lora:大家好,我是Anna Lora。
  • Catherine:大家好,我是Catherine。
  • Ausa:我是Ausa,我们正和Chauck博士在一起。我们非常高兴他能在温哥华举行这次会议。

以上就是我们本次的会面。可能到场人数没有预期得多,因为目前斯坦利杯半决赛正在进行第七场比赛,很可能大多数人都在观看冰球比赛。

下一场办公时间预计将在费城举行。我们届时再见。

023:PHP入门 🚀

在本节课中,我们将要学习PHP编程语言的历史背景和起源,了解它如何从早期的编程语言演变而来,并理解它在现代Web开发中的定位。

历史背景

上一节我们介绍了课程的整体目标,本节中我们来看看PHP语言诞生的历史背景。PHP是一门灵感来源于多种不同编程语言的语言。

编程语言的演变

为了理解PHP,我们需要回顾编程语言的发展历程。

以下是编程语言发展的几个关键阶段:

  • 机器码:在计算初期,我们直接为硬件编程,内存有限,编程必须非常谨慎。早期计算主要用于科学计算,如天气和弹道计算。
  • 高级语言的出现:像1955年的Fortran这样的语言出现,它们略高于汇编语言,提高了我们在处理数学问题时的生产力。
  • 字符串处理的需求:随着我们从50年代进入70年代,计算机的用途不再局限于数值计算。处理像“hello”这样的字母和单词(即字符串)变得重要。在Fortran中编写文本编辑器或类似Twitter的程序效果不佳。
  • 面向字符串的语言:70年代早期,随着人机交互成为计算机的重要用途,面向字符串的语言开始发展。当时有成千上万种不同的语言,如Pascal和SNOBOL。Fortran 77也加入了字符串功能。

C语言的革命

接下来,C语言出现了。C是一门令人惊叹的语言,它虽然原始但功能强大。C语言在提供汇编语言速度的同时,实现了不同计算机系统间的可移植性,并且能较好地处理字符串。

C语言的出现标志着我们从Fortran过渡到C,成为计算中最重要的语言,这正是人类开始通过计算机发送信息和交流的时代。

C语言衍生出了许多分支。任何使用花括号{}的语言,其灵感都部分来源于C。

以下是受C影响的主要语言:

  • C++:C的面向对象版本。
  • Objective-C:另一个C的面向对象版本。
  • Java:一种面向Web的类C语言。
  • C#:微软的语言,灵感来源于C++和JavaScript的结合。
  • JavaScript:1995年出现,从C和其他当时的一些语言中汲取了大量灵感,但其面向对象的模式与上述语言不同。

脚本语言的兴起

在构建和创新这些“硬核”系统语言的同时,我们也为系统管理员等非专业程序员用户构建语言。这些语言深受C语言启发,并且本身是用C语言编写的。

以下是两个重要的例子:

  • Perl:1987年开始,报表生成器。
  • Python:1991年开始,如今已变得非常流行。

虽然Python和Perl没有使用花括号,但它们拥有类似str(字符串)处理函数等功能,其灵感来自C。由于它们用C编写,因此在底层函数实现上继承了许多C的特性。

PHP的定位

现在,我们来看看今天讨论的主角——PHP。PHP也是C语言的衍生物。它有很多灵感来源于C,例如使用花括号{},并拥有一些类似的字符串处理函数(如str_...)。这些函数从C借鉴而来,并经过调整以在PHP中工作。

在这些“硬核”语言之下,PHP等语言的一个侧重点是简单性和易用性。它们服务于专业计算机科学家之外的人群,用于构建网站、处理非计算机科学核心的数据分析等任务。

因此,如果你查看计算机科学课程,它们大多专注于上层的“硬核”语言。但理解整个语言发展史,即使你不是计算机科学家,也很有价值。

总结

本节课中我们一起学习了PHP语言的历史脉络。我们看到PHP植根于C语言的传统,并与其他如Perl、Python等脚本语言一样,致力于在强大功能与开发效率之间取得平衡,从而成为Web开发中广泛应用的工具。接下来,我们将开始深入探讨PHP语言本身。

024:PHP基础 🐘

在本节课中,我们将要学习PHP语言的基础知识。PHP是一种广泛用于Web开发的服务器端脚本语言,它被设计为HTML的扩展,主要用于构建动态网页和Web应用程序。我们将了解PHP的起源、基本语法结构以及它如何与HTML协同工作。


PHP的起源与设计哲学

上一节我们介绍了课程背景,本节中我们来看看PHP语言的起源和其核心设计思想。

PHP的语法源自C语言,因此你会看到它使用花括号 {} 来定义代码块。我们之后要学习的JavaScript也使用花括号,并且同样不依赖缩进(即“无意义的空白字符”)来定义结构,这些特性都继承自C语言。

PHP也从Perl语言中汲取了灵感,主要体现在两个特性上:

  • 使用美元符号 $ 作为变量前缀,这一点有时会让初学者感到困扰。
  • 引入了关联数组,这是一个非常受开发者喜爱的功能。

PHP本质上是一种语言,虽然理论上可以用于HTML之外的其他用途,但它主要被设计为一种HTML模板语言。它的核心使命是服务于Web应用程序,这也是它备受青睐的原因之一。

PHP是一个生产力工具,而非教学工具。与Python这类在出错时会明确“报错”的语言不同,PHP会尽可能地解释并执行你的代码,假设你的输入都是有意的。这意味着有时错误会被“静默”处理。因此,使用PHP需要开发者承担更多责任,主动避免和检查错误,而不是依赖语言本身来阻止你犯错。


PHP与HTML的协作方式

上一节我们了解了PHP的设计理念,本节中我们来看看PHP如何与HTML文件结合工作。

正如之前所说,PHP就像是HTML的扩展。我们创建以 .php 为后缀的文件。在这些文件中,第一行通常就是标准的HTML代码。

在需要的时候,我们可以通过一种特殊的标签切换到PHP模式:<?php。这个标签最初是内置于HTML中,用于切换到服务器端编程语言的“转义”符号。当浏览器请求页面时,服务器会执行这些PHP代码,并将结果嵌入到最终的HTML响应中。

切换到 <?php 之后,我们就进入了PHP语言环境。这里的代码会生成输出。例如,echo 语句(相当于打印语句)的输出会成为最终网页的一部分。代码执行完毕后,我们使用 ?> 标签切换回HTML模式。

因此,浏览器接收到的并不是原始的PHP源代码,而是这些代码执行后所产生的HTML输出。

以下是一个简单的示例,展示了PHP代码如何嵌入在HTML中:

<p>这是静态HTML部分。</p>
<?php
    echo "<p>这是由PHP的echo语句动态生成的段落。</p>";
    $answer = 6 * 7;
    echo "<p>答案是:" . $answer . "</p>";
?>
<p>我们又回到了静态HTML部分。</p>

运行上述代码,浏览器将看到类似这样的结果:

  • “这是静态HTML部分。” 来自静态HTML。
  • “这是由PHP的echo语句动态生成的段落。” 和 “答案是:42” 来自PHP代码的执行输出。
  • “我们又回到了静态HTML部分。” 再次来自静态HTML。

你可以在一个文件中多次在HTML和PHP模式之间切换。有时,这种切换甚至发生在一行代码之内。例如,我们可以在输出字符串的中间插入PHP代码来动态生成内容。

<p>你好,<?php echo "Dr. Chuck"; ?>,欢迎来到课程!</p>

这行代码的输出会是:“你好,Dr. Chuck,欢迎来到课程!”。PHP代码 echo "Dr. Chuck"; 的输出被直接嵌入到了HTML文本的对应位置。


PHP的应用范围

上一节我们看到了PHP在Web中的典型用法,本节中我们来简要探讨一下PHP的其他应用场景。

需要明确的是,PHP是专为Web而设计的语言,这也是它的主要优势所在。不过,理论上PHP也可以用于命令行脚本,例如读取文件、解析字符串等任务。

然而,如果你主要目标是编写命令行应用程序,Python通常是更合适的选择。PHP在命令行环境下的应用并不常见,也不作为推荐的最佳实践。


总结

本节课中我们一起学习了PHP的基础知识。我们了解到PHP是一种源自C和Perl的服务器端脚本语言,其核心设计是作为HTML的模板语言来构建动态Web应用。我们掌握了PHP文件的基本结构,即通过 <?php ... ?> 标签在HTML中嵌入可执行的PHP代码,并由服务器执行后输出结果到浏览器。最后,我们明确了PHP主要服务于Web开发,虽然技术上可用于命令行,但并非其设计初衷。在接下来的课程中,我们将深入探讨PHP的基本语法。

025:PHP关键字与基础语法 🗝️

在本节课中,我们将要学习PHP语言中的一些基础但至关重要的概念,包括关键字、变量命名规则、字符串的用法以及注释和输出语句。理解这些内容是编写正确PHP代码的第一步。

关键字与保留字

任何编程语言的一个重要组成部分都是关键字和保留字。这些是您不能用于其他用途的词语。这意味着如果您使用诸如 classclonedoelse 这样的词,PHP会将其理解为具有特定含义的指令,而不会将其视为变量名等。您必须避免使用这些保留字。有时,错误地使用它们可能会导致语法错误。

以下是PHP中的部分关键字示例:

  • class
  • clone
  • do
  • else
  • case
  • as

变量命名规则

上一节我们介绍了关键字,本节中我们来看看PHP中变量的独特之处。PHP最奇怪的特点之一是所有变量都以美元符号 $ 开头。这可以追溯到Perl语言。美元符号后必须紧跟一个字母或下划线,之后则可以包含字母、数字和下划线。

变量命名的基本规则可以用以下公式描述:
$[a-zA-Z_][a-zA-Z0-9_]*

我们通常避免使用下划线,除非是内部使用的变量。例如,在构建库时,如果有一个不希望外部直接使用的变量,可能会在其前面加上下划线,以减少命名冲突的可能性。

关于变量,一个需要特别注意的问题是:如果遗漏了美元符号,它可能不会立即导致语法错误,但会引发难以调试的问题。例如,x(没有$)可能被PHP解释为一个预定义的常量,其值可能被视为0,并产生非致命错误。而在赋值语句的左侧遗漏$则会导致解析错误。因此,务必养成正确书写变量名的习惯。

字符串处理

字符串在PHP中的处理方式与其他一些编程语言有所不同。PHP的字符串功能非常强大。

您可以使用单引号或双引号来定义字符串,它们的功能略有不同。反斜杠 \ 被用作转义字符。

以下是字符串的主要特性:

  • 双引号字符串:被认为是“智能字符串”。变量在其中会被展开(替换为变量的值),并且转义序列(如 \n 表示换行)会生效。字符串可以跨越多行,这非常方便。

    $expand = 12;
    echo "Value is $expand"; // 输出:Value is 12
    echo "Line 1\nLine 2"; // 输出两行
    
  • 单引号字符串:被认为是“非智能字符串”。变量不会被展开,大多数转义序列(除了 \\\')也不会被解释。\n 会直接作为两个字符输出。但单引号字符串中可以直接包含双引号。

    echo 'Value is $expand'; // 输出:Value is $expand
    echo 'Line 1\nLine 2'; // 输出:Line 1\nLine 2
    echo 'She said "Hello"'; // 输出:She said "Hello"
    
  • 字符串连接:PHP使用点号 . 作为字符串连接运算符,这与许多使用加号 + 进行字符串连接的语言不同。

    $str1 = "Hello";
    $str2 = "World";
    echo $str1 . " " . $str2; // 输出:Hello World
    

注释与输出

注释在PHP中非常灵活,它支持多种风格的注释,这方便了来自不同编程背景的开发者。

以下是PHP支持的注释方式:

  • C++风格// 这是一行注释
  • Shell/Perl风格# 这是一行注释
  • C风格多行注释/* 这是多行注释 */

对于输出,PHP提供了多种方式,体现了其“有多种方法可以完成同一件事”的哲学。

主要的输出语句是 echoprint

  • echo:是一个语言结构,可以接受多个参数,参数之间用逗号分隔,输出时直接连接而不添加空格。
    $x = "Hello";
    echo $x, " World"; // 输出:HelloWorld
    
  • print:本质上是一个函数,但括号可以省略。它只能接受一个参数。echoprint 的功能几乎相同,主要区别在于 echo 可以输出多个值。

总结

本节课中我们一起学习了PHP的基础语法要素。我们了解了必须避开的关键字,掌握了以美元符号 $ 开头的变量命名规则。我们深入探讨了单引号双引号字符串在变量展开和转义字符处理上的重要区别,以及使用点号 . 进行字符串连接。最后,我们认识了PHP灵活的注释风格和用于输出内容的 echoprint 语句。理解这些基础概念是后续学习PHP运算符、表达式和更复杂功能的关键。

026:PHP表达式 🧮

在本节课中,我们将要学习PHP编程语言中一个核心部分:表达式。我们将了解PHP如何处理不同类型的表达式,包括算术运算、字符串连接以及PHP特有的类型转换行为。通过学习这些概念,你将能够理解PHP代码中各种运算背后的逻辑。


表达式基础

任何编程语言的一个重要部分都是它如何处理表达式。表达式通常出现在赋值语句的右侧,或者代码的各个地方。

如果你使用加号、减号等运算符,其工作方式与其他语言非常相似,使用 +-/*。实际上,这一整套约定来自Fortran语言。这非常古老,可以追溯到1955年。当时他们决定用星号表示乘法,可能比那更早。我们至今仍在使用它。所以,每次你想做乘法时输入星号,请记住这来自1955年。

最简单的表达式通常出现在赋值语句的右侧。PHP的一个有趣之处在于它有一个相当罕见的特点,尽管JavaScript是另一种具有非常激进类型转换的语言。PHP不希望代码出错,它希望进行转换。

例如,当它看到字符串 "15" + "27" 时,可能会发生两种情况。它可以将其视为字符串,最终得到 "1527",将两者连接在一起。但PHP实际上不是这样做的。它会将这个字符串转换为整数,然后将它们相加,得到 42。因此,PHP中存在很多隐式类型转换。这里的 + 是数值运算符,所以它会尝试将其操作数转换为数字,这就是我们得到 42 的原因。

值可以是字符串和数字,我们可以使用函数调用,并且存在一个所有编程语言都相同的求值顺序。表达式也可以产生操作。


运算符概览

以下是一些值得注意的运算符。如果你来自Python,其中一些有Python等效项,一些则没有。我们将讨论其中一部分。

  • 递增/递减运算符++--,用于给变量加一或减一。
  • 连接运算符.,用于连接字符串,这很特殊。
  • 相等与不等运算符==!=,这在所有类C语言中都很常见,也来自C语言。
  • 恒等运算符===!==== 是带类型转换的值相等,而 === 是不带类型转换的值和类型都相等。
  • 三元运算符? :,我们稍后会讨论。这也是1972年C语言的一个经典思想。
  • 副作用运算符:如 +=
  • 位运算符:这些也来自C语言,是面向位的运算符,处理0和1,进行与、或、移位等操作。除非你在做压缩或加密之类的事情,否则通常没有用处。所以你不会经常使用这些运算符。

递增与递减运算符

现在,让我们具体看看递增和递减运算符。++ 会产生副作用。

假设我们有 $x = 12。在表达式 $y = 15 + $x++ 中,像任何右侧表达式一样,它需要先解析这个右侧。所以它必须首先获取 $x 的值,将 12 取出作为表达式的一部分。但是,由于 ++$x 之后,作为一个副作用,它将 $x 的值变为 13。此时,12 仍然存在于 15 + 12 中,所以我们最终得到 $y = 27。但在下一个语句中,$x 已经变成了 13,因为读取并加一产生了这个副作用。

你也可以写成 ++$x。这唯一的区别是,它在将值复制到表达式之前就加一。在这种情况下,$y = 15 + ++$x 的结果会是 28,而 $x 仍然是 13

大多数文明人倾向于不使用这种写法,除非在某些特定场景。通常我根本不用它。我们倾向于显式地加一,比如 $x = $x + 1。这样我们在下一个语句中单独执行加法,使代码意图更清晰。有些人喜欢炫耀,写尽可能紧凑的代码。我倾向于避免那样做。


字符串连接运算符

. 字符是一个字符串运算符。它不仅仅是连接字符串,还会将其操作数转换为字符串。它不会自动添加空格,是纯粹的连接。

如果我想得到 "hello world",我必须在 "hello" 后面加上空格。例如,我可以 echo "hello world" 不加换行,然后连接一个换行符变量,这样我就能得到带换行的 "hello world"

PHP运算符的一个特点是它们有“态度”,它们会根据类型进行期望和转换。. 是字符串运算符,+ 是数学运算符。这在某些方面比一些语言(如JavaScript)更具可预测性,因为JavaScript的运算符更面向对象,你并不总能预测它在做什么。


三元运算符

三元运算符来自C编程语言,源于我们希望代码非常简洁的时代。三元运算符基本上是将 if-then-else 放在一行中。

我们这里有一个赋值语句,它包含三个部分。之所以叫三元,是因为有三个部分。

  1. 第一部分是一个问题,其求值结果为真或假。
  2. 第二部分是表达式的结果(如果问题为真)。
  3. 第三部分是表达式的结果(如果问题为假)。

你可以把它想象成有两个值“悬在空中”,准备进入变量 $message,然后我们根据条件选择其中一个。

例如,$message = ($ww > 100) ? "large" : "small"。如果 $ww 大于100,条件为真,我们选择 "large",它被放入 $message

我们还可以做这样的事情:取一个数除以2的余数,判断它是否为零,这意味着它是偶数或奇数。例如,$message = ($ww % 2) ? "odd" : "even"。如果 $ww 是123,余数为1(非零,在布尔上下文中为 true),那么 "odd" 被放入 $message。我调换了 "even""odd" 的顺序,因为如果结果是1(true),就是奇数;如果结果是0(false),就是偶数。

我希望我不必教你这些,但我们确实在某些情况下大量使用它,比如检查一个键是否在数组中。有一些语法我们反复使用,所以我必须教你。我宁愿不教,但如果不这样做,在某些非常特定的情况下我们会有太多的 if-else 语句。这些是惯用的情况,你看一眼就知道他在做什么,你理解那是什么,这真的是一种习惯用法,他用了三元运算符,所以你会原谅他。但你不应该过度使用任何这些花哨的副作用运算符。


赋值运算符与字符串构建

你可以使用这些运算符。例如,$count += 1 等同于 $count = $count + 1,这只是该表达式的缩写。

我永远不会使用 $count = $count + 1 这种写法。我也不会使用 $count += 1。但在构建字符串时,我确实会使用 .=,因为在某些代码位置,你开始一个字符串,然后不断向其中添加内容。

一种方法是:你开始一个字符串,然后想在其末尾添加一个空格。你可以写 $out = $out . " ",这是在末尾添加一个空格。如果你要反复这样做,我们可以将其缩写为 $out .= "world",这会将 "world" 连接到其末尾,然后再连接一个换行符。这样就能打印出包括换行符的内容。

这通常是一个简单的例子。但我们使用这种方法来增长字符串:开始一个字符串,添加一点,再添加一点,再添加一点。我们使用 .= 来向字符串末尾添加内容。这个我倾向于使用,我喜欢它。


类型转换与强制转换

正如我提到的,PHP中会发生很多激进的类型转换。你也可以在需要时显式地强制转换。实际上,在PHP中,显式强制转换比其他编程语言中使用得更少。

让我们看看这里会发生的一些“疯狂”的事情。

  • 除法:即使两个操作数都是整数,除法也会产生一个浮点数,这实际上是一个合理的想法。像Python 2那样,整数相除产生非浮点数的做法反而不那么符合逻辑。
  • 混合运算36.25 + true + "100"+ 是数值运算符。36.25 是浮点数。true 被转换为 1 以成为数字。字符串 "100" 变成数字 100。所以这将是 137.25,确实如此。这不是语法错误。你看着PHP可能会觉得这很糟糕。但你会习惯它,并说:“嗯,我想只要明智地使用,我不介意这是一种强大的能力。”当然,这行代码本身并不明智,我只是向你展示什么是可能的。这是一行糟糕的代码,但确实如此。
  • 字符串连接. 是字符串操作。所以 "Sam is " . 42 中,42 是数字,但 . 会自动强制将其转换为字符串。你可以写成 (string) 42,这是一种类型声明,表示将那个非字符串变量转换为字符串。但这并不太有用,因为 . 无论如何都会将其转换为字符串。
  • 整数转换:你可以将像 9.9 这样的值转换为整数 (int) 9.9,这会将其截断为 9,然后 $y = (int) 9.9 - 1 得到 8。这实际上是一种向整数的截断转换。
  • 字符串与数字:再次强调,"Sam" + 25 中,+ 是数值运算符,这意味着它会强制其操作数变为数字。所以它把 "Sam" 变成了 0,因此 "Sam" + 25 是数字 25。而 "Sam" . 25 是字符串运算符,所以它会将 25 转换为字符串,你最终得到 "Sam25"

+ 用于数字,. 用于字符串。这部分是相当一致的。如果有 +,我们将以某种方式强制将它们转换为数字,包括像字符串 "Sam" 变成 0 这样的疯狂行为。但 . 是用于字符串的。我实际上希望更多的编程语言这样思考。

像Python这样的编程语言倾向于“报错”。在PHP中,我们可以将字符串 "100" + 25 作为数值相加,得到 125。我们可以连接 "100" . 25,得到 "10025"。我们可以将 "Sam" + 25,得到 25,因为 "Sam" 变成了 0

在Python中,我们必须显式地处理。我们必须说 int("100"),然后转换并相加,得到 125。或者如果我们想连接,我们使用相同的 + 运算符进行连接,但我们必须将 25 转换为字符串 str(25) 才能连接。如果我们愚蠢地尝试将 "Sam" 转换为整数 int("Sam"),我们会得到一个回溯错误。

这某种程度上告诉了你PHP的哲学:它允许你做一些在Python中会“爆炸”的事情。在某种程度上,这也是我喜欢Python作为入门语言的原因之一,因为它让你处于一个非常严格的约束中。你必须非常精确地表达你想做什么。如果你尝试做一些愚蠢的事情,它会阻止你,这样你就可以说:“等等,我做了什么?为什么我那样做?哦,也许这很愚蠢。但如果那是一个变量呢?哦,那是一个字符串,我忘了。我忘了他们有时会把‘sal’放进去。”所以这很有帮助,因为虽然得到一个回溯错误可能令人沮丧,但它也可以告诉你你做错了什么。而PHP,再次强调,它很“负责任”。它会做你说的事情,即使你说的事情不是最合乎逻辑的,它也会做点什么。


类型转换的细节与陷阱

强制转换方面,true 变成 1,我提到过。作为一个程序员,最令人沮丧的事情之一是 false 变成空值。

如果我使用 . 作为字符串操作,它会强制将 false 转换为字符串。当它强制将 true 转换为字符串时,true 变成 "1"。所以 $x . "1" . $y,如果 $xtrue$yfalse,结果会是 "1",因为 false 是空字符串。如果你尝试用 echo 语句打印 false,它不会打印任何东西。这让我不止一次抓狂,因为我心想:“echo 这个变量 $x,怎么什么都没有?是不是没执行到那一行?哦,等等,20分钟后才意识到,那是个 false。这就是为什么我什么都没看到。”所以,false 打印不出来。

正如我之前提到的,相等运算符 == 是一个激进的类型转换运算符,它尝试匹配类型转换。所以这里有一些在许多语言中会“爆炸”但在PHP中完全没问题的事情。

  • 123 == "123":因为它尝试转换它们,发现它们相等。
  • " 123" == 123:字符串 " 123" 会转换成 123,所以成立。
  • false == 0false 可以变成整数 00 也是整数 0,所以成立。
  • (5 < 6) == ("2" - "1")5 < 6true,变成 1"2" - "1" 中,- 是算术运算符,所以变成整数 2 - 1,结果是 1。看,11 相等。这简直是你能写出的最疯狂的、还不会产生语法错误的代码行之一。我不是说你该这么做,我只是说这就是PHP中所有运算符的“侵略性”。

有时,我们想阻止这种 == 的侵略性。所以我们使用 ===。这基本上是在没有转换的情况下比较是否相同。如果值相同且类型相同,则为 true。如果像 1true 比较,就会是 false,因为 === 抑制了类型转换。与Python相比,=== 类似于 is!== 类似于 is not=== 的概念几乎完全相同。


一个常见的陷阱:strpos 函数

一个会让你陷入麻烦的事情是任何数值上下文中的 false 都会变成 0null 在数值计算中也会自动变成 0

特别是 strpos 函数。strpos 的工作方式是寻找一个字符串(“干草堆”)中的子串(“针”)。例如,如果我在 "ABC" 中寻找 "A",它返回位置 0。如果寻找 "B",返回 1。问题是,如果它返回 0,意味着它在开头找到了。而如果没找到,它返回 false。所以你必须小心区分是在开头找到了还是根本没找到。

因此,你需要阅读文档。即使在文档中,它也警告你使用 === 运算符。这就是 === 运算符在PHP中有多重要。


strpos 函数示例

这里有一些 strpos 函数的简单例子。

我们有 "hello world"。我们寻找 "wo" 的位置,它是 0,1,2,3,4,5,6,所以会给我们 6。我们寻找字符串 "he",它会在位置 0。然后我们问 "zz" 的位置,这将返回 false。我们连接它,你会注意到又没有打印出任何东西。这就是当我尝试打印 false 而它们不显示时让我抓狂的地方。

这是一个你可能犯的错误。你可以写 if (strpos($s, "he") == false)。这将会找到 "he",你以为你在问是否找到了它,但由于 == 的激进转换,0 会被转换为 false,所以这个条件会错误地成立。如果我们寻找 "zz",这个会正常工作,因为返回 falsefalse == false 在类型转换后仍然匹配,这符合你的预期。但这就是你容易出错的地方。

你必须使用 ===,因为如果它返回 0,它并不恒等于 false0 不等于 false,因为 === 抑制了类型转换。这里也一样。

所有这些都说明:阅读文档。print 不显示 falseprint_r 允许你打印更多细节,但它们也根本不显示。有很多方法尝试打印 false,但只有一种方法可以真正显示它们,那就是 var_dump


总结与预告

本节课中,我们一起学习了PHP表达式的核心概念。我们了解了PHP中算术和字符串运算符的使用,重点探讨了PHP独特的隐式类型转换机制,以及 ===== 运算符的关键区别。我们还学习了三元运算符、递增/递减运算符以及字符串连接运算符 ..= 的用法。最后,我们通过 strpos 函数的例子,理解了在特定场景下使用 === 避免错误的重要性。

接下来,我们将讨论控制结构,例如 if-then-else 等。

027:PHP控制结构 🧠

在本节课中,我们将要学习PHP中的控制结构。控制结构是编程的基石,它允许程序根据条件做出决策,并重复执行某些任务。我们将从基础的if语句开始,逐步介绍循环、逻辑运算符以及breakcontinue等关键概念。

逻辑运算符与if语句

上一节我们介绍了控制结构的重要性,本节中我们来看看最基础的条件判断结构——if语句。if语句允许程序根据条件的真假执行不同的代码块。

PHP使用一系列逻辑运算符来构建条件。其中一些运算符与其他编程语言类似,例如:

  • == 等于
  • != 不等于
  • < 小于
  • > 大于
  • <= 小于或等于
  • >= 大于或等于

对于有C语言背景的开发者,以下运算符也很熟悉:

  • && 逻辑与
  • || 逻辑或
  • ! 逻辑非

if语句的基本结构如下。它需要一个用圆括号包裹的条件表达式,该表达式最终会求值为truefalse。值得注意的是,在PHP中,非零值会被视为true,零值被视为false

if ($ants == 42) {
    echo "Hello World";
} else {
    echo "Goodbye World";
}

在上面的例子中,如果变量$ants等于42,条件为真,程序将执行第一个代码块,输出“Hello World”。如果条件为假,程序将跳过第一个代码块,执行else后的代码块,输出“Goodbye World”。

代码风格与多分支if

理解了基础if语句后,我们需要注意PHP的代码风格。PHP是一种基于C的语言,它对空格和缩进没有强制要求。这意味着你可以将所有代码写在一行,但这会严重影响可读性。

以下是两种常见的花括号风格。第一种是“K&R风格”,花括号与控制语句在同一行。第二种是“Allman风格”,花括号独占一行。选择哪种风格是个人或团队的偏好,关键在于在同一个项目中保持风格一致。

// K&R 风格
if ($x) {
    // ...
} else {
    // ...
}

// Allman 风格
if ($x)
{
    // ...
}
else
{
    // ...
}

接下来,我们看看多分支条件判断,即if-elseif-else结构。程序会按顺序检查每个条件,一旦找到第一个为真的条件,就会执行对应的代码块,然后跳过其余所有分支。

if ($fuel > 10) {
    echo "Fuel is high";
} elseif ($fuel > 5) {
    echo "Fuel is medium";
} elseif ($fuel > 0) {
    echo "Fuel is low";
} else {
    echo "Out of fuel";
}

如果$fuel的值为8,程序会检查第一个条件($fuel > 10)为假,然后检查第二个条件($fuel > 5)为真,于是输出“Fuel is medium”,并忽略后面的elseifelse

循环结构:whiledo-while

掌握了条件判断,现在让我们进入循环的世界。循环允许我们重复执行一段代码。首先介绍的是while循环,它是一种“先测试”循环,意味着在执行循环体之前会先检查条件。

while循环在循环开始前检查条件。如果初始条件为假,循环体一次也不会执行。在循环内部,程序员必须负责更新迭代变量,否则可能导致无限循环。

$fuel = 10;
while ($fuel > 1) {
    echo "Vroom vroom\n";
    $fuel = $fuel - 1; // 更新迭代变量,避免无限循环
}

while循环相对的是do-while循环,它是一种“后测试”循环,意味着循环体至少会执行一次,然后再检查条件。

do-while循环保证循环体内的代码至少执行一次,执行后再判断条件是否满足以决定是否继续循环。

$count = 1;
do {
    echo "Count is: $count\n";
    $count++;
} while ($count <= 5);

在上面的例子中,即使初始时$count可能不满足<=5的条件,echo语句也会先执行一次。

for循环与循环控制

对于已知循环次数的场景,for循环是更简洁的选择。它是一种功能强大的计数循环,将初始化、条件判断和迭代更新集中在一行。

for循环的语法包含三个用分号分隔的部分:初始化表达式、循环条件、以及每次循环结束后执行的表达式。它同样是“先测试”循环。

for ($count = 1; $count <= 6; $count++) {
    echo "Count: $count\n";
}
// 输出:Count: 1 ... Count: 6

有时我们需要更精细地控制循环流程,这时就会用到breakcontinue语句。

break语句用于立即终止整个循环,跳出循环体继续执行后面的代码。continue语句则用于跳过当前循环迭代中剩余的代码,直接进入下一次循环的迭代条件判断(在for循环中,会先执行第三个表达式)。

// break 示例
for ($i = 0; $i < 10; $i++) {
    if ($i == 5) {
        break; // 当 $i 等于5时,终止循环
    }
    echo $i;
}
// 输出:01234

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/cf6099e91be86794ef655c798c3e51ae_9.png)

// continue 示例
for ($i = 0; $i < 5; $i++) {
    if ($i == 2) {
        continue; // 当 $i 等于2时,跳过本次循环的剩余部分
    }
    echo $i;
}
// 输出:0134

总结

本节课中我们一起学习了PHP的核心控制结构。我们从基础的if-else条件判断开始,了解了逻辑运算符的使用。接着,我们探讨了代码风格的重要性以及多分支if-elseif结构。然后,我们深入学习了三种循环:至少执行零次的while循环、至少执行一次的do-while循环,以及用于明确计数的for循环。最后,我们介绍了用于精细控制循环流程的break(终止循环)和continue(跳过本次迭代)语句。掌握这些控制结构是进行有效PHP编程的关键。下一节,我们将探讨PHP中的数组。

028:Rasmus Lerdorf发明PHP

概述

在本节特别内容中,我们将跟随PHP语言的创始人Rasmus Lerdorf的讲述,了解PHP在1990年代诞生和发展的故事。我们将看到PHP如何从一个个人效率工具演变为一个全球性的开源项目,并理解其“解决问题优先”的设计哲学。

PHP的诞生:一个个人效率工具

故事始于1993年,当时我看到了Mosaic网页浏览器。那是我真正对万维网产生兴趣的时刻。在此之前,我一直在使用Gopher、IRC和其他与互联网相关的东西。但有了Mosaic,我突然可以向我的母亲解释我在做什么了。所以我知道这个东西将会非常、非常有趣。

当时我住在加利福尼亚州的山景城,但我搬回了多伦多做咨询工作。我为许多公司提供咨询服务,并且一遍又一遍地编写相同的代码,基本上都是用C语言编写的CGI脚本。我编写了类似的代码来处理表单和POST数据、过滤以及所有那些你在编写原始C语言CGI程序时最终必须编写和查看的常见Web任务。

这变得很繁琐。我对编程本身并不是很感兴趣,它有点乏味和无聊。所以,如果我能减少花在编程上的时间,最大化产出,并更快地找到解决方案,那就是我创建PHP的目标。我将所有常用的东西整合到一个C语言库中,将其打包到NCSA网络服务器里,然后在上面添加了一个小的模板系统,让我可以轻松地调用它。这就是PHP的第一个版本。它是我个人的一个生产力工具,这样我就可以非常快速地为一个新客户构建一个新的Web应用程序,并将其连接到他们的数据库或他们需要的任何东西。每个客户的要求都略有不同,所以我不断地扩展我的工具。

走向开源:从“我的工具”到“我们的项目”

然后,其他人开始向我询问这些东西。他们问我如何构建这些应用。我告诉他们我使用了一个自己构建的小工具,他们问是否可以拥有它。我说当然可以。我的意思是,我出售的不是工具本身。我出售的是我解决问题的服务。工具本身真的无关紧要。它只是我的锤子。任何人都可以使用我的锤子。

然后他们开始给我发送补丁,我认为这非常酷。他们在我的代码中发现了连我自己都还没发现的错误。这意味着我也可以去客户那里说:“嘿,这是新版本,它修复了这个、这个和这个问题。”他们会认为我的效率极高,能如此快速地编写所有这些代码并修复所有这些问题。

那是在1994、95年,那时“开源”这个词还不存在。但我真正体会到了开源的力量。我与一群同行聚在一起,他们都是来自世界各地对解决Web问题感兴趣的人。我们都面临类似的问题和挑战,通过协作,我们可以构建一个解决这个问题的工具。这就是PHP真正起步的方式。

项目治理:放弃控制与建立社区

我在此过程中学到了一点:为了让这个项目成长,我必须放弃对PHP的控制。我必须让其他人也拥有一些控制权。我不能重写每个人的补丁,一方面是因为我相当懒,工作量很大;但另一方面,也是为了给人们在项目中一些所有权。一旦他们拥有了一些所有权,一旦他们完全控制了属于他们的那部分,他们就会更加投入,变得充满热情,这项目就变成了他们的,而不仅仅是他们为我的项目做贡献。它变成了我们的项目。这真正改变了PHP的本质。这大约发生在1997年左右,那时我真正地将权力下放,并给予人们访问我用来开发这些东西的CVS代码仓库的完全权限。

在那之后,PHP的发展速度惊人。一方面是因为万维网在增长,PHP出现在了正确的时间和正确的地点。但另一方面,也因为进入和开始使用PHP非常、非常容易。显然,使用PHP的门槛非常低。所以,无论是在使用PHP方面,还是在为PHP做贡献方面,我都试图将准入门槛保持得非常低。即使在今天,要在PHP项目上获得一个CVS账户也不需要太多条件。

源代码版本控制是一件美妙的事情。来自某个贡献者的初始补丁有多糟糕并不重要,我们总是可以修复它。我们更愿意欢迎新的贡献者,引导他们,让他们变得高效和有用,而不是因为他们提交了一个糟糕的初始补丁而斥责他们。从外部看,你看起来像一台运转良好的机器,你知道它是一台运转良好的机器,能比商业组织更有效地完成事情。但同时,在某种程度上,它也是一个松散的、兄弟般的团体。

我们有接近1400人拥有CVS账户,这意味着这些人都有权限向代码仓库的某个部分提交代码,无论是PHP核心、PECL扩展,还是文档树。当然,他们并非都活跃。在过去一年半里,可能只有略超过一半的人提交过东西。

组织模式:自我组织与精英管理

实际上并没有人对这些人进行管理。他们基本上是自我组织成更小的小组。显然,管理1400人是不可能的,尤其是当他们是志愿者,并不实际来办公室开会或必须阅读邮件时。他们会围绕自己感兴趣的东西自我组织。例如,我们有针对各种语言的文档团队,有一个庞大的法语文档团队,只负责文档的法语翻译;还有一个日语翻译团队;一大群核心文档编写者。这些团队自我组织,没有一个大总管来指挥这一切。我不会告诉人们该做什么,我也不能,他们不会听我的。他们为什么要听我的,对吧?他们做自己感兴趣的事,这是志愿者工作的唯一方式。

然后是PECL扩展的开发者们,他们构建PECL扩展。这里通常是我们测试新扩展并引入某些扩展的地方。比如在PHP 5.1中,我们引入了JSON扩展,它最初就是在PECL中诞生的。接下来,可能会是OAuth或其他一些目前很流行的扩展。所以,新的特性和新功能最终就是这样慢慢进入核心的。它们生活在核心代码树之外,当它们有足够的普及度,有足够多的人安装它们,我们看到Linux发行版将它们纳入其核心PHP版本中,我们就会观察外面发生了什么,然后将其纳入。但这个过程也没有真正的管理。

这是一个精英管理制度。我的意思是,代码会说话。如果你写了一个补丁或一段代码来实现一个功能,这本身就说明了很多。如果有人想反对那种做法,他们能提供一个替代的实现方案,那是一个非常好的论据。如果他们所做的只是抱怨,那是一个非常糟糕的论据。很可能,那个有代码的实现会胜出,即使它可能不是最好的实现方式。有代码,它某种程度上能工作,这就是我们采用的标准。这并不总是带来一致性,但它确实带来了功能,并且让你能够实际地做一些事情,能够连接到这种类型的数据库,即使这可能不是最好的方式,但至少它能让你达到目的。这一直是PHP所关注的:解决问题。

我们宁愿有一个丑陋的功能,也不愿根本没有这个功能。

总结

本节课中,我们一起学习了PHP语言的起源故事。我们看到,PHP最初是Rasmus Lerdorf为了提升个人工作效率而创建的工具,后来通过开放源代码和放弃个人控制权,发展成为一个由全球开发者共同维护的庞大开源项目。其成功的关键在于极低的参与门槛、基于兴趣的自我组织、精英管理的决策机制,以及始终秉持的“解决问题优先”的务实哲学。这不仅是PHP的历史,也是开源协作模式的一个生动范例。

029:华盛顿州西雅图 🏙️

在本节课中,我们将回顾一次在华盛顿州西雅图举行的课程附加办公时间。本次活动中,许多学生分享了他们的学习经历和心得。

学生自我介绍与分享

以下是参与本次办公时间的学生们进行的自我介绍和简短分享。

  • Raul: 大家好,我是Raul。这门课程到目前为止非常棒。
  • Josh: 嗨,我是Josh。我们下次湾区见。
  • Muhammad: 我是Muhammad。我已经完成了Python系列的三门课程,非常喜欢您的教学风格,并期待完成数据库和毕业项目。
  • Brian: 我是Brian。Chuck太棒了,祝大家学习愉快。
  • Eileen: 嗨,我是Eileen。干得好,这可能是你们的第一门编程语言,请继续保持。
  • D: 嗨,我是D。Python是目前我最喜欢的编程语言,我也喜欢Chuck的幽默和他提到的哈利波特梗。
  • Gerardo Garcia: 你好,我是Gerardo Garcia,正在学习Python。
  • Amanda: 嗨,我是Amanda。这真是一门好课,谢谢。
  • Heidi: 嗨,我是Heidi。我才上到第二门课,很兴奋能学到更多。关于我用Python做什么?我在西雅图莎士比亚剧院工作,他们让我处理一些数据和管理工作,我很期待最终能将Python应用到莎士比亚相关的工作中。
  • Steph: 嗨,我是Steph。我上了Chuck的课,现在在全球健康领域找到了一份做数据分析和编程的工作,非常有趣。时间线是这样的:大约一年半前的秋天我开始上课,花了一段时间学习。到第二年六月,我在一次线下见面会上见到了Chuck,那时我已经完成了第一门课。到了九月,我就找到了一份使用Python、R等语言编程的工作。我没有参加编程训练营,只是上了这门课。
  • John: 嗨,我叫John。给大家两条建议:第一,Chuck说要做他的课程作业,他完全正确。第二,如果有机会参加他的线下见面会,一定要去,因为他会回答问题,这会让你受益匪浅。谢谢Chuck。
  • Osvaldo: 大家好,我是Osvaldo。我非常喜欢这次见面会,也很喜欢“Python for Everybody”这门课程。欢迎大家来讨论这门课。

活动总结与预告

上一节我们聆听了学生们的精彩分享。本节中,我们来了解一下本次活动的结尾和后续安排。

活动组织者表示,下一次线下见面会预计将在40小时后于温哥华举行,届时需要找一个合适的场所,例如酒吧,以便与大家见面。

最后,我们线上再见。


本节课中我们一起回顾了西雅图办公时间的学生分享,听到了不同背景的学习者如何应用课程知识,并获得了实用的学习建议。

030:PHP数组 🧮

在本节课中,我们将要学习PHP中一个非常强大且受欢迎的特性:数组。我们将了解什么是数组,如何创建和使用它们,以及如何利用它们来构建和管理数据。


什么是PHP数组?🤔

上一节我们介绍了课程背景,本节中我们来看看PHP数组的核心概念。

PHP数组非常出色。它们的设计灵感来源于Perl语言,被称为“关联数组”。Perl语言就有关联数组。

程序员需要编写算法,即一系列代码和步骤,以及处理数据。我们通过逻辑和创建的数据来解决问题。数据结构决定了数据的组织形式。在C语言中,有struct(结构体)来组织数据;在C++和Java中,有对象(Objects)。这些都是通过复杂语法来定义数据形状的方式,例如定义一个包含姓名和电话号码的人。

而PHP的关联数组采用键值对的形式。你可以直接说:这个人有名字、姓氏和电话号码,然后就完成了。这使得程序员无需学习大量额外语法,就能轻松创建数据结构,甚至可能意识不到自己在使用数据结构。Python中的字典(Dictionaries)非常受欢迎,是Python最受欢迎的特性之一。Java有哈希映射(Hash Maps),Windows有属性包(Property Bags)。它们都是关联数组或键值对的一种形式。

当我从PHP转向Python时,我最怀念的就是PHP数组,因为我希望数据能保持顺序。PHP数组是我最喜欢的关联数组实现。

在PHP中,数组可以是类似列表的形式,即通过数字索引的线性列表;也可以是键值对的形式,例如 first_name => Chucklast_name => Severancephone_number => 555-1234。PHP还有二维数组,但实际上它们是“数组的数组”。我们稍后会简单讨论,但不会深入。


创建和使用数组 🛠️

上一节我们了解了数组的概念,本节中我们来看看如何具体创建和使用数组。

创建线性列表数组

你可以创建一个线性列表式的数组。以下代码演示了如何创建:

$stuff = array("Hello", "world");
echo $stuff[1]; // 输出 "world"

这段代码使用array()构造函数创建了一个包含两个元素的数组。与大多数编程语言一样,第一个元素的索引是0。我们使用下标(索引)运算符[]来访问数组中的元素。$stuff[1]访问的是数组中的第二个元素,因此会输出“world”。除非特别指定,PHP会默认将这些元素放在索引01的位置。

创建键值对数组

然而,人们真正喜爱的是键值对数组。其语法如下:

$stuff = array("name" => "Chuck", "course" => "SI664");
echo $stuff["course"]; // 输出 "SI664"

我这样理解:键name映射到值Chuck。这里"name"是键,"Chuck"是值。同样,键"course"映射到值"SI664"。然后,你可以再次使用索引运算符[],通过键"course"来查找对应的值,结果会输出“Web Applications for Everybody”。

我非常喜欢这种形式。它允许我们在没有过多思考的情况下构建数据结构。当然,后期当你使用对象或其他更复杂的结构时,可能不希望数据结构过于依赖数组,但这在初期是完全可以的。


打印和查看数组内容 📄

当我们开始构建数据形状(即数据结构)时,需要几种方式来打印它们的内容。作为程序员,你决定使用namecourse这样的键名。久而久之,你就形成了自己的数据形状。这是一个非常简单的形状,因此你需要一种方法来打印它。

以下是打印数组的几种方法:

使用 print_r() 函数

print_r()函数可以遍历数组并打印出键和值。它会按照键值对的格式输出。

echo "<pre>\n";
print_r($stuff);
echo "\n</pre>\n";

我在这里添加了HTML的<pre>标签,是为了防止换行符被打乱,从而更清晰地看到打印的实际格式。print_r()会遍历数组,打印出键和值。你会注意到,输出的顺序与我放入数组时的顺序是一致的,这是我喜欢它的一个原因。print_r中的“R”我认为代表“递归”(Recursive),这意味着如果数组内嵌套了数组,它会一层层深入打印出来。虽然这个例子很简单,但你可以打印出复杂得多的结构,有时你甚至会得到非常复杂的嵌套结构。

使用 var_dump() 函数

另一个更详细、更明确地显示数据类型的函数是var_dump()。我认为它更底层,输出不那么美观,但更明确。

var_dump($stuff);

这段代码会输出类似这样的内容:$stuff是一个包含2个元素的数组。第一个元素:键name映射到一个5个字符的字符串值Chuck;第二个元素:键course映射到一个字符串值SI664。它只是更详细一些。当我需要深入调试时,我倾向于使用var_dump(),因为print_r()虽然让输出更美观,但有时会丢失一些细节。

var_dump()的一个优点是它可以打印出false(布尔假值)。例如:

$thing = false;
print_r($thing); // 输出为空,什么也不显示
var_dump($thing); // 输出 `bool(false)`

使用print_r()打印false时,屏幕上什么都没有,这可能会让人误以为代码没有执行。但使用var_dump(),它会明确显示这是一个布尔类型的false值。这样设计肯定有原因,但对于调试打印来说,print_r()的方式不太方便。


数组的构造与循环 🔄

上一节我们学习了如何查看数组,本节中我们来看看如何动态构建数组以及如何遍历它们。

动态构建数组

你可以用几种不同的方式构造数组。可以从一个空数组开始,然后向末尾添加元素。

$stuff = array();
$stuff[] = "Hello";
$stuff[] = "World";

这段代码先创建了一个空数组$stuff,然后使用$stuff[]语法向数组末尾添加了两个元素。PHP会自动为它们分配整数索引位置,即01,从而形成一个线性列表。

对于键值对数组,你也可以这样做:

$stuff = array();
$stuff["name"] = "Chuck";
$stuff["course"] = "WA4E";

这段代码创建了一个空数组,然后分别为键"name""course"赋值。顺序?是的,顺序会保持不变。

遍历键值对数组

以下是遍历键值对数组的方法:

foreach($stuff as $k => $v) {
    echo "Key=", $k, " Val=", $v, "\n";
}

这是一个foreach循环结构,语法上有些不同。第一个参数是数组$stuff,然后是关键字as,接着可以有两个迭代变量:一个用于键($k),一个用于值($v)。我这样理解:对于$stuff中的每一对,将键映射到值。=>符号就像一个箭头,我认为这正是语言设计者的意图。这个循环运行时,$k会遍历所有键,$v会遍历所有值。每次循环迭代,它们都会同时前进。我认为这是一个非常优雅的语法。

遍历线性数组

如果你有一个线性数组,也可以获取键和值。在这种情况下,键就是01

foreach($stuff as $k => $v) {
    echo "Key=", $k, " Val=", $v, "\n";
}

对于线性数组,你还可以使用计数循环(for循环),但需要确保它是一个格式良好的线性数组。

for($i = 0; $i < count($stuff); $i++) {
    echo "I=", $i, " Val=", $stuff[$i], "\n";
}

count()函数返回数组中元素的数量。在这个例子中,结果是2。循环条件$i < count($stuff)意味着$i将是01$stuff[2]是无效的,因为数组只有索引01。然后$i++使索引递增。这是一个会运行两次的计数循环。就语法而言,对于这种遍历,我更想使用foreach循环。但有时你可能确实需要计数循环,因为你需要索引变量$i来做一些其他操作。在循环内部,通过$stuff[$i]来引用数组元素。


多维数组简介 📊

最后,我们简要讨论一下二维数组。它们并非真正的“二维数组”,而是嵌套数组,或者说递归嵌套数组。

你可以在一个数组内再放入一个数组。

$products = array(
    'paper' => array(
        'copier' => "Copier & Multipurpose",
        'inkjet' => "Inkjet Printer",
        'laser' => "Laser Printer",
        'photo' => "Photographic Paper"),
    'pens' => array(...),
    'misc' => array(...)
);

echo $products['paper']['copier']; // 输出 "Copier & Multipurpose"

$products是外层数组。这个外层数组包含三个东西:‘paper’‘pens’‘misc’。注意,逗号是外层数组的一部分,所以外层数组有三个元素。

你可以这样理解:$products[‘paper’]会进入外层数组,获取‘paper’对应的值(即一个内层数组)。然后,[‘copier’]在这个内层数组中查找键‘copier’对应的值,最终取出“Copier & Multipurpose”

所以,这并不是真正的二维数组,而是“数组中的数组”。通常,你不会经常手动构建这种结构,但当你从数据库获取数据、从网络读取JSON并解析时,可能会得到这种嵌套结构。然后你需要弄清楚如何深入挖掘和访问其中的数据。


总结 📝

本节课中我们一起学习了PHP数组。我们了解到PHP数组可以是数字索引的线性列表,也可以是强大的键值对(关联数组)。我们学习了如何使用array()构造函数或[]语法来创建数组,如何使用print_r()var_dump()来调试和查看数组内容。我们还掌握了使用foreach循环遍历数组(包括键值对),并简要了解了嵌套数组(多维数组)的概念。数组是PHP中组织和管理数据的基石,掌握它们对Web开发至关重要。

接下来,我们将讨论一系列帮助我們构建、搜索和处理数组的函数。

031:PHP数组函数 📚

在本节课中,我们将学习PHP中一系列强大的数组函数。这些函数是PHP早期非面向对象编程风格的产物,它们以array_为前缀,用于对数组进行各种操作,例如检查键是否存在、排序、计数和拆分字符串等。掌握这些函数对于高效处理数据至关重要。

数组函数概述

上一节我们介绍了PHP数组的基本概念。本节中,我们来看看PHP提供的一系列内置数组函数。由于PHP最初并非面向对象语言,它通过全局命名空间中的函数来组织功能。数组函数均以array_开头,字符串函数则以str_开头。我们通过将数组作为参数传递给这些函数来进行操作。

检查数组键是否存在

在处理数组时,经常需要检查某个键是否存在。直接访问不存在的键会导致非致命错误,因此我们需要安全的方法进行检查。

以下是两种检查键是否存在的常用方法:

  • array_key_exists()函数:这是一种更清晰的方法。其语法为 array_key_exists($key, $array),如果键存在则返回true,否则返回false
  • isset()函数:这是更简洁、更常用的方法。其语法为 isset($array[$key]),同样返回布尔值。

两种方法都不会引发错误。而直接使用 $array['nonexistent_key'] 则会触发非致命错误。

使用三元运算符进行条件赋值

在实际编码中,我们经常需要根据键是否存在来赋值。在PHP 5及更早版本中,通常使用三元运算符来实现。

// 语法:条件 ? 值1 : 值2
// 如果条件为真,返回‘值1’;否则返回‘值2’
echo isset($za['name']) ? 'name is set' : 'name not set';

这段代码检查$za数组中是否存在'name'键。如果存在,则输出'name is set';否则输出'name not set'。这是PHP 5时代兼容性代码中非常常见的模式。

PHP 7的空合并运算符

PHP 7引入了一个更优雅的运算符来处理这类情况,即空合并运算符??

// 语法:$value = $array['key'] ?? ‘默认值’;
// 如果‘key’存在且不为null,则取其值;否则使用‘默认值’
$username = $_GET['user'] ?? ‘nobody’;

这个运算符同样会抑制访问不存在的键时产生的非致命错误。它更简洁,是PHP 7及更高版本的推荐做法。但为了保持与旧版本PHP的兼容性,了解三元运算符的写法仍然很重要。

数组信息函数

除了检查键,我们还需要获取数组的基本信息。

  • count()函数:用于计算数组中元素的数量。公式为 $num_elements = count($array);
  • is_array()函数:用于判断一个变量是否为数组。这在编写接收“混合类型”参数(可能是字符串或字符串数组)的函数时非常有用。

数组排序函数

PHP数组会保持元素的插入顺序。PHP提供了多种排序函数来改变这个顺序。

以下是几个核心的排序函数:

  • sort()函数:对数组按值进行排序,但会重新分配数字索引(丢弃原有关联键)。适用于索引数组。
  • asort()函数(“awesome sort”):对数组按值进行排序,并保持键与值的关联。适用于关联数组。
  • ksort()函数:对数组按键名进行排序。

选择哪种排序方式取决于你的需求:asort()常用于按值排序且需保留键的情况;ksort()则在你需要按键名浏览或查找时很有用。

字符串拆分为数组

另一个常见的编程任务是将字符串拆分为数组。PHP使用explode()函数实现此功能。

// 语法:$array = explode(分隔符, 字符串);
$temp = explode(' ', 'This is a sentence with seven words');
// $temp 现在是一个数组:['This', 'is', 'a', 'sentence', 'with', 'seven', 'words']
echo $temp[6]; // 输出:words

explode()函数的第一个参数是分隔符(如空格、逗号、冒号等),第二个参数是要拆分的字符串。它返回一个由分割后的子串组成的数组,是解析数据的强大工具。

总结与展望

本节课中我们一起学习了PHP的核心数组函数。我们了解了如何安全地检查数组键是否存在(使用isset()array_key_exists()),以及如何使用三元运算符和PHP 7的空合并运算符进行条件赋值。我们还学习了获取数组信息的count()is_array()函数,对数组进行排序的sort()asort()ksort()函数,以及将字符串拆分为数组的explode()函数。

这些函数是构建PHP程序的基础,能够帮助你高效地处理和操作数据。下一节,我们将探讨数组如何与Web开发中的请求-响应周期协同工作。

032:HTTP与PHP数组 🧩

在本节课中,我们将学习如何将HTTP请求响应周期与PHP数组结合起来。具体来说,我们将了解浏览器如何通过URL传递数据,以及PHP如何自动将这些数据解析并存储到一个名为 $_GET 的特殊全局数组中。掌握这个机制是构建交互式Web应用的基础。


上一节我们介绍了PHP数组的基本概念,本节中我们来看看HTTP请求如何与PHP数组产生关联。

HTTP请求响应周期与GET参数

我们回顾一下这个令人愉快的请求响应周期。当你在浏览器中点击一个链接时,浏览器会发送一个HTTP请求。我们的Web服务器(如Apache)接收到这个请求。如果服务器发现请求的是PHP代码文件,它就会启动PHP解释器来执行代码。

关键在于URL的末尾部分。如果URL中包含像 ?x=2&y=1 这样的参数,这些键值对会被自动解析,并放入一个名为 $_GET 的全局数组中。这个变量在你的代码第一行执行之前就已经被定义好了。

因此,如果你想获取参数 x 的值,你可以直接使用 $_GET[‘x’]。你无需自己编写任何解析代码。PHP理解带有GET参数的HTTP请求响应周期,它会自动完成解析工作,然后将数据存入 $_GET 数组。之后,你只需编写业务逻辑代码,生成HTML并发送回浏览器,浏览器解析后你就能看到新的页面。

我们通常将从浏览器传来的这类数据称为“超全局数组”。

实践:查看 $_GET 数组

以下是我们可以用PHP数组做的一个简单演示。这是一个PHP文件,你可以直接在URL后面加上 XY 参数来运行它。

记住,PHP文件默认输出HTML,直到我们告诉它做别的事情。这里我们使用 <pre> 标签,是为了让输出格式更清晰,避免所有内容挤在一行。虽然有时不加 <pre> 标签我也能阅读,但加上它会更容易阅读。

我们将打印这个 $_GET 数组。第一行代码中的 $_GET 数据就来源于URL中的这两个参数。这两个参数被解析后,分别存放在 $_GET 数组的 ‘x’ 和 ‘y’ 键下。

这为你节省了大量工作,因为解析过程并不像看起来那么简单。PHP需要处理分割、字符转换等问题,因为URL中可能包含各种特殊字符。但这不是你需要操心的问题,这是PHP的任务。它会解析所有内容并将其存入数组。

我们将使用 print_r 打印它,然后再用 var_dump 打印一次,因为 var_dump 的输出信息更详细。但本质上,这些参数都进入了这个数组,你只需访问这个数组即可。

这些就是GET参数,因为我按回车发送的是一个HTTP GET请求。所以,? 问号后面的就是参数。格式是:问号后跟第一个键值对,然后用 & 连接下一个键值对,依此类推。你可以将这些参数添加到URL末尾,它们会被直接传入 $_GET 数组,PHP会自动处理。它被称为“超全局”变量,因为它存在于主代码以及PHP所有函数内部。


本节课中我们一起学习了HTTP GET请求如何与PHP的 $_GET 超全局数组协同工作。我们看到了浏览器如何通过URL传递数据,以及PHP如何自动、高效地为我们解析这些数据,使我们能够轻松地在代码中访问它们。这为后续处理用户输入和构建动态网页打下了坚实基础。

在下一讲中,我们将进一步探索PHP数组的更多有趣功能。

033:在Windows系统中使用Ngrok连接自动评分器 🖥️

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地计算机上的Web应用程序临时暴露到公网,以便完成需要与远程自动评分器交互的作业。

概述

当你完成一个Web应用程序作业后,代码通常运行在你的本地计算机上,地址类似于 localhost。然而,自动评分器位于互联网上,无法直接访问你本地的 localhost 地址。为了解决这个问题,我们需要使用Ngrok。Ngrok能为你本地运行的服务器创建一个临时的、可公开访问的网址,让自动评分器能够与你的应用程序进行通信。

下载与安装Ngrok

首先,你需要下载Ngrok软件。我们将以Windows系统为例进行演示。

  1. 访问Ngrok官方网站(ngrok.com)。
  2. 下载适用于Windows系统的版本。
  3. 下载完成后,打开压缩包,将 ngrok.exe 文件解压到一个方便的位置,例如桌面。这样便于在命令行中调用。

运行本地服务器与Ngrok

上一节我们准备好了Ngrok工具,本节中我们来看看如何启动它并与你的本地服务器配合工作。

假设你的Web应用程序(例如一个PHP项目)已经在本地运行,默认端口是80。你需要在命令行中导航到存放 ngrok.exe 的目录,然后执行启动命令。

以下是启动Ngrok并映射到本地80端口的命令:

ngrok http 80

执行该命令后,Ngrok会启动并在命令行窗口中显示信息。其中最重要的信息是它为你分配的临时公共网址,格式类似于 https://xxxx-xx-xx-xx-xx.ngrok.io

这个网址现在指向了你本地 localhost:80 上运行的服务。

提交作业到自动评分器

现在,你获得了一个可以公开访问的临时地址。接下来,你需要将这个地址提交给自动评分器。

  1. 复制Ngrok提供的临时公共网址(例如 https://xxxx-xx-xx-xx-xx.ngrok.io)。
  2. 打开你的应用程序的特定页面,例如 guessinggame.php?guess=12
  3. 将页面的完整路径拼接到Ngrok网址后面,形成完整的可访问URL,例如:
    https://xxxx-xx-xx-xx-xx.ngrok.io/guessinggame.php?guess=12
    
  4. 将这个完整的URL复制下来。
  5. 进入课程作业的自动评分器页面,将复制的URL粘贴到指定的提交框中并运行评分。

当自动评分器工作时,你可以在运行 ngrok 的命令行窗口中看到请求和响应的日志记录,这表明自动评分器正在通过Ngrok创建的安全隧道与你的本地应用程序成功通信。

完成后的操作

作业提交并评分完成后,你应该断开Ngrok连接以关闭对本地服务的公开访问。

在运行 ngrok 的命令行窗口中,按下 Ctrl + C 组合键即可停止Ngrok服务。服务停止后,之前分配的临时网址将立即失效,外部(包括自动评分器)无法再访问你的本地应用。

需要注意的是,每次重新启动Ngrok,它都会生成一个全新的临时网址。因此,每次提交作业前,如果重启了Ngrok,都需要使用最新的网址来构建提交给评分器的URL。

总结

本节课中我们一起学习了如何使用Ngrok工具解决本地开发环境与远程自动评分器之间的连接问题。核心步骤包括:下载Ngrok、在命令行中启动它并映射到本地服务器端口、使用生成的临时公共网址构建完整的应用程序访问链接,最后将该链接提交给自动评分器。完成评分后,记得停止Ngrok服务以保障本地环境的安全。掌握这个流程,你就能顺利提交那些需要与在线自动评分器交互的Web应用作业了。

034:在Macintosh系统上使用Ngrok连接自动评分器 🚀

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地电脑(如Macintosh)上的Web应用程序暴露到公网,以便课程中的自动评分器能够访问并评估你的作业。这对于完成“猜数字游戏”等需要在线评分的作业至关重要。

概述与问题背景

上一节我们介绍了如何在本地的MAMP环境中运行PHP应用程序。本节中我们来看看如何让互联网上的自动评分器访问到你本地运行的代码。

核心问题在于,自动评分器运行在真实的互联网上,而你的应用程序运行在本地服务器(如localhost:8888)。互联网上的服务器无法直接访问你电脑上的localhost。因此,直接提交类似http://localhost:8888/assignments/guess/guess.php的链接给评分器会导致连接失败。

解决方案:引入Ngrok

为了解决上述连接问题,我们需要使用一个名为Ngrok的工具。Ngrok能创建一个安全的隧道,将你本地服务器上的一个端口(例如8888)映射到一个临时的、公开的互联网地址(如https://xxxxxx.ngrok.io)。这样,自动评分器就可以通过这个公开地址访问到你本地的应用程序。

其工作原理可以用一个简单的模型表示:

互联网上的自动评分器 <---> [你的公开Ngrok地址] <---> [Ngrok隧道] <---> [你的本地服务器 localhost:8888]

在Macintosh上配置Ngrok的步骤

以下是配置Ngrok的具体操作流程。

第一步:下载Ngrok软件

首先,你需要从Ngrok官网下载适用于Mac系统的软件。下载完成后,文件通常位于你的“下载”文件夹中,是一个ZIP压缩包。

第二步:在终端中启动Ngrok

  1. 打开Mac的“终端”应用程序。
  2. 使用cd命令切换到存放Ngrok文件的目录。例如:
    cd ~/Downloads
    
  3. 解压并运行Ngrok。你需要指定协议和本地端口。对于运行在localhost:8888的MAMP服务器,命令如下:
    ./ngrok http 8888
    
    运行此命令后,Ngrok会启动并在终端中显示一个公开的URL(例如 https://a1b2c3d4.ngrok.io)。这个URL只有在Ngrok程序运行时才有效。

第三步:在自动评分器中使用Ngrok地址

现在,你可以用这个生成的Ngrok公开地址(例如 https://a1b2c3d4.ngrok.io/assignments/guess/guess.php)替换原来的localhost地址,并将其提交到课程的自动评分器中。评分器现在可以通过互联网访问到你本地的应用程序了。

第四步:调试与修改代码

提交后,自动评分器会访问你的应用程序并进行测试。你可以在运行Ngrok的终端窗口或Ngrok提供的Web监控界面(通常访问 http://127.0.0.1:4040)查看请求和响应的流量。

如果测试失败,评分器会给出错误提示。例如,它可能提示“在标题标签中未找到‘Chuck Severance’”或“你的猜测值太高”。这时,你需要根据提示修改本地的PHP代码(例如,将正确的答案从42改为37,或在HTML标题中加入要求的名字),保存文件,然后重新在评分器中运行测试。

第五步:完成与关闭

当作业全部通过评分后,你可以在运行Ngrok的终端窗口中按 Control + C 来停止Ngrok服务。此时,对应的公开URL将立即失效。下次启动时,Ngrok会生成一个全新的随机地址。

总结

本节课中我们一起学习了如何使用Ngrok工具搭建桥梁,将本地开发的Web应用程序临时暴露到公网。关键步骤包括:下载Ngrok、在终端中启动隧道指向本地服务器端口、将生成的公开URL提交给自动评分器,并根据反馈调试代码。记住,Ngrok地址是临时的,每次启动都会变化,且必须在Ngrok运行期间评分器才能正常工作。掌握这个方法,你就能顺利提交并完成需要在线自动评分的编程作业了。

035:南非开普敦

在本节课中,我们将回顾一次在南非开普敦举行的课程附加办公时间。本节内容将展示师生交流的场景,并介绍几位参与课程的学生。

概述

本次办公地点位于南非开普敦,紧邻开普敦大学的山脚下。与往常一样,课程讲师查克博士首先介绍了参与本次办公时间的几位学生。

学生介绍

以下是几位参与本次办公时间的学生自我介绍。

  • 克里斯蒂:我正在攻读男性健康领域的博士学位,这门课程对我的学习也很有帮助。
  • 卡邦戈:我去年完成了这门课程,现在正在学习机器学习。
  • 瑞安:我去年学习了这门课程,这是我第一次接触编程和相关技术。
  • 杰夫:我去年学习了大部分Python课程,非常享受所有的在线学习体验,我会坚持编程和实践。
  • 斯蒂芬:我来自开普敦大学,我的电子邮箱地址曾出现在Python课程的一个解析示例中。
  • 约瑟夫:我学习了互联网历史课程,觉得非常棒。同时我也在学习“Python for Everyone”课程。
  • 加里:我从互联网历史课程开始学习,虽然最初对计算机编程感到害怕,但现在我已成为一名Web开发人员。
  • 乔伊:我终于见到了查克博士,感谢您的课程。我专程来到南非与乔伊见面。
  • 约翰:我是一名工程师,每天在工作中都会用到从这些课程中学到的技能。
  • :我也是一名工程师,非常喜欢查克博士的所有课程,并期待未来能有更多新课程。

总结

本节课中,我们一起回顾了在南非开普敦举行的一次课程办公时间。通过学生们的自我介绍,我们可以看到这门课程吸引了来自不同背景和专业领域的学习者,并且对他们的学术或职业发展产生了积极的影响。本次交流在轻松的氛围中结束,并预告下一次办公时间可能在丹佛举行。

036:PHP函数 🧩

在本节课中,我们将学习如何编写和使用PHP函数,以及如何将功能拆分到多个文件中。函数是代码复用的核心,能帮助我们避免重复劳动,使代码更整洁、更易于维护。

概述

PHP是一门大量使用函数的语言。学习函数不仅是为了编写它们,更是为了有效地使用它们。我们将探讨何时应该创建函数,以及PHP内置函数和自定义函数的使用方法。

函数的基本概念

上一节我们概述了函数的重要性,本节中我们来看看函数的具体定义和调用。

函数是一段被命名的、可重复执行的代码块。你可以将数据(参数)传递给它,它也可以返回一个结果。定义函数使用 function 关键字。

代码示例:定义一个简单的函数

function greet() {
    echo "Hello, World!";
}
// 调用函数
greet(); // 输出:Hello, World!

内置字符串函数示例

PHP提供了丰富的内置函数来处理各种任务,尤其是字符串操作。以下是几个常用的字符串函数示例:

  • strrev($string):反转字符串。
  • str_repeat($string, $multiplier):重复字符串指定次数。
  • strtoupper($string):将字符串转换为大写。
  • strlen($string):获取字符串的长度。

代码示例:使用内置字符串函数

echo strrev("Hello"); // 输出:olleH
echo str_repeat("Hip ", 2); // 输出:Hip Hip
echo strtoupper("hooray"); // 输出:HOORAY
echo strlen("Hello"); // 输出:5

定义与调用自定义函数

了解了内置函数后,现在我们来学习如何创建自己的函数。定义函数时,需要指定函数名、可选的参数列表以及用花括号 {} 包裹的函数体。

代码示例:定义和调用带参数的函数

function greetPerson($name) {
    echo "Hello, " . $name . "!";
}
// 调用函数两次
greetPerson("Glen"); // 输出:Hello, Glen!
greetPerson("Sally"); // 输出:Hello, Sally!

函数的返回值

函数不仅可以直接输出内容,更常见的做法是通过 return 语句返回一个值,供调用者使用。

代码示例:使用return返回值

function getGreeting() {
    return "Hello";
}
$message = getGreeting() . " Glen!";
echo $message; // 输出:Hello Glen!

函数参数与默认值

我们可以向函数传递参数来改变其行为。PHP的一个优雅特性是支持为参数设置默认值。

代码示例:带默认参数的函数

function sayHello($name, $language = 'en') {
    if ($language == 'es') {
        return "Hola, " . $name;
    } elseif ($language == 'fr') {
        return "Bonjour, " . $name;
    } else {
        return "Hello, " . $name;
    }
}
echo sayHello("Glen"); // 输出:Hello, Glen
echo sayHello("Sally", "fr"); // 输出:Bonjour, Sally

变量的作用域与传值方式

在函数内部使用的变量通常与外部隔离。这引出了两个重要概念:按值传递按引用传递

按值传递

默认情况下,PHP函数参数是“按值传递”的。这意味着函数内部获得的是参数值的一个副本,修改这个副本不会影响外部的原始变量。

代码示例:按值传递

function doubleValue($num) {
    $num = $num * 2;
    return $num;
}
$value = 10;
$result = doubleValue($value); // $result 是 20
echo $value; // 输出:10 (原始值未改变)

按引用传递

有时我们需要函数直接修改外部的变量。这时可以在参数前加上 & 符号,表示“按引用传递”。

代码示例:按引用传递

function tripleValue(&$num) {
    $num = $num * 3;
}
$value = 10;
tripleValue($value); // 直接修改了 $value
echo $value; // 输出:30 (原始值已被改变)

理解这一点对于阅读PHP文档非常重要。如果你在文档中看到函数参数前有 & 符号,就意味着这个函数可能会修改你传入的变量。

总结

本节课中我们一起学习了PHP函数的核心知识。我们了解了如何定义和调用函数,如何使用参数和返回值,并探讨了按值传递与按引用传递的区别。函数是构建模块化、可复用代码的基石,掌握它们对任何PHP开发者都至关重要。下一节,我们将深入探讨变量的作用域。

037:PHP变量作用域 🧱

在本节课中,我们将要学习PHP中一个非常重要的概念:变量作用域。我们将探讨变量在函数内外如何被访问和修改,理解如何控制变量的“可见性”,并学习一些相关的实用技巧。

概述

变量作用域定义了变量在代码中的“可见”或“可访问”范围。理解作用域是编写结构清晰、避免意外错误的代码的关键。本节我们将从基本概念讲起,逐步深入到高级用法和最佳实践。

变量作用域的基本概念

上一节我们介绍了函数和参数传递。本节中我们来看看变量在函数内部和外部是如何被隔离的。

变量作用域的核心思想是:函数内部的代码通常不会影响到外部世界。我们之前讨论过按值调用,即传递一个副本给函数。还有按引用调用,它像是一个小门,允许你通过它修改外部的某个变量。

作用域意味着我们要么隐藏一些东西,要么允许某些东西“泄漏”出去。但总的来说,函数内部的东西不应该泄漏到外部。它们之间没有混合。你可以选择相同的变量名,但它们不会互相干扰。

局部变量与全局变量

以下是理解作用域的一个典型例子。

$vow = 10; // 全局变量

function trizaap() {
    $vow = 100; // 局部变量
    echo $vow; // 输出 100
}

trizaap();
echo $vow; // 输出 10

在这个例子中,函数 trizaap 内部的 $vow 和外部的 $vow 不是同一个东西。它们互不影响。函数内部的变量有自己的命名空间,可以看作是 trizaap 函数内的 $vow。而外部的变量存在于全局上下文中。我们可以想象有一堵墙将它们隔开,正常情况下,墙是密不透风的。

当然,像 $_GET$_POST$_SESSION(我们稍后会学到)这样的超全局变量可以穿透这堵墙。但为了理解基础,我们先忽略它们。

如何使用 global 关键字

既然我们画了一堵无法穿透的墙,现在就来告诉你如何“逃出”这堵墙。逃出墙的方法是使用 global 关键字。

$vow = 10; // 全局变量

function trizaap() {
    global $vow; // 声明使用全局变量 $vow
    $vow = 100; // 修改全局变量
}

trizaap();
echo $vow; // 输出 100

global 关键字的作用是:打开一扇门,去函数外部寻找名为 $vow 的变量(如果不存在则创建它)。现在,函数内的 $vow 就成为了外部那个 $vow 的别名。变量名必须相同,因为它是根据名称来匹配的。这样,我们就打破了那堵墙,建立了一个可以进出的通道。

使用全局变量的注意事项

需要非常小心。你通常应该尽量避免使用全局变量,尤其是使用一些奇怪的变量名时。例如,一个名为 $i 的变量,如果外部代码正在循环中使用 $i,而你在函数内使用了 global $i 并修改了它,就可能会破坏外部的循环逻辑。

你本应作为一个独立服务的函数,却因为干扰了外部代码使用的变量而把事情搞砸。因此,应该非常谨慎地使用全局变量

如果让我给全局变量打分,我会不惜一切代价避免使用它。如果可能,尽量通过参数传递值进来,再通过返回值传递出去。这两种方式能让所有程序员都感到愉快。一个接收按值传递的参数并返回结果的函数,就像一个没有副作用的纯函数,这是最理想的情况。

所以,按值传递参数和返回结果是最理想的。按引用传递参数也可以接受,但在PHP中比在其他语言中更常见一些。而全局变量应该是你最后的手段

全局变量的命名规范

如果你必须使用全局变量,我建议并遵循大多数人的做法:使用非常长且独特的名字。这是为了避免无意中与代码中其他地方恰好使用的变量名发生冲突。

例如,你可能通过全局变量来通信:“嘿,当我的函数全部执行完后,我会设置这个全局变量,然后你可以来获取它。”

以下是我在实际代码(比如自动评分器)中创建的一些示例变量:

$last_owath_body_bass_string = “...”;
$reallyLongCamelCaseVariable = “...”;
$REALLY_LONG_UPPERCASE_SNAKE_CASE = “...”;
  • 驼峰命名法:第一个单词首字母小写,后续单词首字母大写。
  • 蛇形命名法:所有字母小写,单词间用下划线连接。
  • 大写蛇形命名法:所有字母大写,单词间用下划线连接。

我使用这些带有良好、独特前缀的长名称,是希望它也能提醒我:我不应该轻易使用它们,同时也能降低命名冲突的风险,因为全局变量只有一个命名空间。

检查函数是否存在:function_exists

PHP另一个有趣的地方是它随着时间不断演进。我提到过PHP 1, 2, 3, 4, 5, 7。记住,PHP 6这个版本不存在。多年来发生了很多变化,比如面向对象特性在PHP 5引入,空合并运算符在PHP 7引入。

因此,有些功能只存在于较新版本的PHP中。如果你想编写能在早期版本上运行的代码,就必须处理这个问题。function_exists 函数可以返回真或假。你传入一个函数名的字符串,就可以询问该函数是否存在。这允许你的代码适应不同的环境。

有时,你的PHP安装可能不包含所有功能。有些是核心功能,有些是可选的。使用像XAMPP或MAMP这样的集成环境,你会得到很多可选功能,所以不太需要担心这个。但当你将代码部署到生产环境时,可能会发现:“咦,这些东西怎么都没了?” 这时你就需要找到解决方法。

function_exists 是一种让你编写更智能代码的方式。有时你可以这样做:如果 array_combine 函数在你的PHP版本中不存在,你可以去Stack Overflow上找一个纯PHP实现的 array_combine 函数。

以下是具体做法:

if (!function_exists(‘array_combine’)) {
    // 如果函数不存在,则定义它
    function array_combine($keys, $values) {
        // 这里是纯PHP实现的 array_combine 功能
        $result = array();
        foreach ($keys as $i => $key) {
            if (isset($values[$i])) {
                $result[$key] = $values[$i];
            }
        }
        return $result;
    }
}

如果函数已经存在,你就不能再次定义它,否则会导致错误。所以你可以说:“如果这个函数还不存在,那我就添加这个函数。” 这是一种实现向后兼容(有时甚至是向前兼容)的方法。有时新版本会移除某些功能,你可以检查它是否存在,如果不存在,就自己写一个,或者设置一个变量来采取不同的处理方式。

查看PHP配置信息:phpinfo()

我之前提到,PHP超级可配置,有很多可以选装的功能。有时你需要弄清楚你的PHP安装里到底有什么。你可以打电话给系统管理员问:“嘿,这是怎么回事?” 但事实证明,PHP有一个非常酷的内置方法来为你转储这些信息。

你只需要写一个文件,我通常叫它 info.php,然后在里面放入完全相同的三行代码:

<?php
phpinfo();
?>

phpinfo() 是一个PHP内置函数,它能转储特定PHP环境的全部配置信息。你可以在你的笔记本电脑、服务器或其他任何地方运行这个文件,从而查明PHP的版本、配置方式、日志文件存储位置等等。

如果你使用MAMP或XAMPP,通常会有一个叫“PHP Info”的按钮,直接点击就能显示这些信息,这非常方便。了解里面有什么很重要,因为有很多东西是作为模块加载的。这也是我推荐XAMPP和MAMP的原因,因为它们预装了大量的组件,你不太会遇到“我缺了某个东西”的问题。

总结

本节课中我们一起学习了PHP的变量作用域。我们了解到函数内部的变量默认是局部的,与外部隔离。使用 global 关键字可以访问和修改全局变量,但这应作为最后的手段。我们探讨了纯函数(无副作用)的优点,以及通过参数和返回值进行数据交换是最佳实践。此外,我们还学习了如何使用 function_exists 来编写兼容不同PHP版本的代码,以及如何使用 phpinfo() 来查看详细的PHP配置信息。理解这些概念对于构建健壮、可维护的Web应用程序至关重要。

接下来,我们将讨论如何将一个应用程序的网页,拆分成多个文件,以处理那些在许多页面中出现的公共元素。

038:PHP模块化 📦

在本节课中,我们将学习PHP中的模块化概念,特别是如何通过包含和重用多个文件来组织代码,避免重复,并构建结构更清晰的Web应用程序。


概述

之前我们讨论了函数,这是一种重用代码的方式。现在,我们将从文件的角度来探讨模块化。到目前为止,我们主要处理单个PHP文件。但在实际项目中,我们常常希望将功能分散到多个文件中,并在需要时重复引入它们。PHP为此提供了 includerequire 等指令。


包含文件:includerequire

PHP提供了两种主要方式来引入外部文件:includerequire。它们的核心区别在于处理错误的方式。

  • include:查找并引入指定文件。如果文件不存在,会产生一个警告(非致命错误),但脚本会继续执行。
  • require:查找并引入指定文件。如果文件不存在,会产生一个致命错误,脚本会停止执行。

为了确保关键文件(如数据库配置或核心函数库)的缺失不会导致后续代码在错误状态下运行,通常建议使用 require


避免重复引入:require_once

在复杂的项目中,多个文件可能都需要引入同一个公共文件(例如一个工具函数库)。如果使用普通的 require,可能会导致同一个文件被多次引入,可能引发函数重复定义等错误。

为了解决这个问题,PHP提供了 require_once 指令。它的作用是:无论代码中请求引入该文件多少次,只要它已经被成功引入过一次,就不会再次引入。这极大地简化了代码管理,无需手动编写条件判断来检查文件是否已包含。

核心指令对比:

include 'file.php';       // 引入文件,失败则警告
require 'file.php';       // 引入文件,失败则致命错误
require_once 'file.php';  // 仅引入一次文件,失败则致命错误


实践应用:构建可重用的页面部件

requirerequire_once 最常见的用途是创建可在多个页面中重复使用的页面部件,例如页头、导航栏和页脚。

假设我们有一个网站,其主导航菜单需要在首页 (index.php) 和另一个页面 (install.php) 上显示。我们不希望在每个页面的HTML中重复编写相同的导航菜单代码。

解决方案如下:

  1. 将导航菜单的HTML代码单独保存到一个文件,例如 nav.php
  2. 同样,将页面的头部信息(如 <head> 标签内容)保存到 top.php,将页脚内容保存到 foot.php
  3. 在每个页面文件中,使用 require 引入这些公共部件。

以下是 index.php 文件结构的示例:

<?php
    require(‘top.php’);    // 引入页面头部
    require(‘nav.php’);    // 引入导航栏
?>
<!-- 页面独有的主体内容放在这里 -->
<div>
    <p>这是首页的特定内容。</p>
</div>
<?php
    require(‘foot.php’);   // 引入页脚
?>

install.php 文件的结构将非常相似:

<?php
    require(‘top.php’);    // 引入相同的页面头部
    require(‘nav.php’);    // 引入相同的导航栏
?>
<!-- 另一个页面的独有内容 -->
<div>
    <iframe src="some_content.html"></iframe>
</div>
<?php
    require(‘foot.php’);   // 引入相同的页脚
?>

通过这种方式,我们实现了:

  • 一致性:所有页面共享相同的页头、导航和页脚,确保网站风格统一。
  • 可维护性:如需修改导航菜单,只需编辑 nav.php 一个文件,所有引用它的页面都会自动更新。
  • 不重复自己(DRY原则):代码被封装一次,然后多处复用。

模块化的本质就是捕获一次逻辑或表现层代码,然后反复使用它


总结

在本节课中,我们一起学习了PHP模块化的关键知识。我们首先回顾了通过内置函数和自定义函数实现代码复用的通用模块化思想。接着,我们重点探讨了基于文件的模块化,详细比较了 includerequirerequire_once 指令的区别与适用场景。最后,我们通过一个构建可重用页面部件(页头、导航、页脚)的实践例子,展示了如何将页面分解为多个文件,从而遵循DRY原则,创建出更易于维护的Web应用程序结构。掌握这些技巧,将帮助你编写出结构更清晰、更专业的PHP代码。

039:英国伦敦肖尔迪奇

在本节课中,我们将回顾一次在伦敦肖尔迪奇举行的附加办公时间活动。这次活动是临时组织的,几位课程参与者到场并与大家分享了他们的学习体验。

活动介绍

我们在伦敦肖尔迪奇的Blues Kitchen临时组织了一次办公时间活动。有几位参与者到场。我想介绍一下他们,让他们向大家问好,并分享他们想对课程其他同学说的话。

以下是到场参与者的自我介绍。

参与者问候

  • 约翰:我叫约翰,在附近不远的地方工作。很高兴能见到查克博士。我在课程中学得非常愉快,非常感谢。
  • 法拉:大家好,我叫法拉,住在伦敦东部。我在2015年就学习了这门课程,并且很快会开始学习新的Python免费课程,对吗?
  • 弗格斯:嗨,我是弗格斯。我在去年十一月学习了查克博士的几门课程,感觉非常棒,非常感谢。我想下次见面应该是在几周后的南非了,干杯。

本节课中,我们一起了解了在伦敦肖尔迪奇举行的附加办公时间活动,并聆听了三位课程参与者——约翰、法拉和弗格斯的亲切问候与学习分享。

040:HTML表单 📝

在本节课中,我们将要学习HTML表单。表单是Web应用程序中用户输入数据的主要方式,它允许用户通过填写文本框、选择选项等方式与服务器进行交互。我们将探讨表单的基本结构、如何将数据发送到服务器,以及PHP如何处理这些数据。


上一节我们介绍了PHP中的函数和超全局变量,本节中我们来看看如何利用HTML表单来收集用户输入,并通过HTTP协议将这些数据传递给PHP程序进行处理。

表单提供了一种比直接在URL后附加参数更便捷的数据输入方式。其基本思想是创建一个包含输入字段的页面,用户填写后提交,数据会被发送到服务器。

表单的基本结构

一个简单的HTML表单包含以下核心元素:一个<form>标签用于包裹所有输入元素,若干个<input>标签用于定义不同类型的输入字段,以及一个提交按钮。

以下是创建一个简单猜数字表单的HTML代码示例:

<p>Please enter your guess:</p>
<form>
    <input type="text" name="guess" size="5" />
    <input type="submit" />
</form>
  • <form> 标签定义了一个表单区域,其中的所有输入字段将被作为一个整体提交。
  • <input type="text"> 创建了一个文本输入框。name="guess"属性至关重要,它定义了提交到服务器时该数据的键名。
  • <input type="submit"> 创建了一个提交按钮。点击它,浏览器会收集表单内所有数据并发送到服务器。

当用户输入“12”并点击提交后,浏览器会构造一个类似?guess=12的请求发送给服务器。

PHP如何接收表单数据

PHP通过超全局变量(如$_GET$_POST)来接收表单提交的数据。当表单使用GET方法(默认方法)提交时,数据会附加在URL之后,PHP会自动将其解析并存入$_GET数组中。

以下代码演示了如何接收并显示guess参数:

<p>Your guess was:</p>
<pre>
<?php
    print_r($_GET);
?>
</pre>
  • 当页面首次加载时,$_GET数组是空的。
  • 当用户提交表单后,页面重新加载,此时$_GET[‘guess’]将包含用户输入的值(例如“12”)。
  • 开发者可以通过检查$_GET$_POST数组中是否存在某个键,来判断用户是否提交了数据。

GET与POST方法

表单数据可以通过GETPOST方法提交,这两种方法在<form>标签的method属性中指定。

  • GET方法:数据以查询字符串的形式附加在URL之后(例如 ?guess=12)。它适用于获取数据、搜索等非敏感操作,因为数据在地址栏可见。
  • POST方法:数据包含在HTTP请求的正文中发送,不会显示在URL里。它更适合提交敏感信息(如密码)或大量数据。

在PHP中,使用POST方法提交的数据通过$_POST超全局数组来访问。


本节课中我们一起学习了HTML表单的基础知识。我们了解了表单如何作为用户与服务器交互的桥梁,其基本结构包括<form><input>和提交按钮。我们还回顾了PHP如何通过$_GET$_POST超全局变量来接收和处理表单提交的数据。理解表单是构建交互式Web应用的关键一步。下一节,我们将深入探讨表单的其他输入类型和更复杂的数据提交方式。

041:表单GET与POST方法 📝

在本节课中,我们将要学习HTML表单中两种提交数据的方法:GET和POST。我们将了解它们的工作原理、区别以及在实际开发中如何选择合适的方法。

上一节我们介绍了GET请求如何通过URL发送数据。本节中我们来看看POST请求,这是一种不同的HTTP请求方式。

概述:GET与POST的基本区别

GET请求将数据附加在URL中发送,而POST请求则通过HTTP请求体发送数据,不会在URL中显示。

配置表单使用POST方法

要使用POST方法,需要在HTML的<form>标签中指定method属性。

<form method="post" action="form1.php">

当用户点击提交按钮时,表单数据将通过POST方法发送。数据不会出现在URL中,而是被放置在PHP的$_POST超全局数组中。此时,$_GET数组为空。POST方法的优点之一是URL看起来更简洁。

HTTP协议层面的工作原理

为了理解GET和POST的区别,我们需要从HTTP协议的角度来看。

以下是GET请求的简化表示:

GET /form1.php?guest=42 HTTP/1.1
[其他头部信息]
[空行]

以下是POST请求的简化表示:

POST /form1.php HTTP/1.1
[其他头部信息]
Content-Type: application/x-www-form-urlencoded
[空行]
guest=42

POST是一种不同的HTTP动词(或称方法)。常见的HTTP动词包括GET、POST、PUT、DELETE、HEAD等。在POST请求中,参数位于请求体(Body)部分,在空行之后发送。这就是为什么在浏览器地址栏中看不到POST数据的原因。Web服务器(如Apache)会解析这些数据,并将其填充到$_POST数组中,供我们的PHP代码使用。

如何选择GET或POST

作为开发者,需要根据场景选择正确的方法。

以下是选择GET或POST的指导原则:

  • 使用POST:当操作会创建或修改服务器上的数据时。例如,从银行账户取款、发表博客评论、更新用户资料等。这可以防止用户意外重复执行操作(例如,通过书签重复取款)。此外,网络爬虫(如搜索引擎蜘蛛)通常不会自动提交POST请求,这有助于防止意外操作。
  • 使用GET:当操作主要用于查询或搜索数据,且不会产生副作用时。例如,在商品目录中搜索零件编号、使用筛选器查看产品等。这样用户可以将包含搜索条件的URL加入书签或分享给他人。

从技术上讲,GET请求应该是幂等的。这意味着多次发送相同的GET请求,应该返回相同的结果(内容可能因数据状态变化而更新,但请求的语义不变)。例如,搜索“零件编号=123”应该总是返回关于该零件的信息。

另一个考虑因素是数据长度。GET请求的参数有长度限制,这个限制因浏览器和服务器而异,并不明确。因此,如果要发送大量数据(如一段长文本),应使用POST方法。对于少量参数,GET通常没有问题。

总结

本节课中我们一起学习了表单的GET和POST方法。我们了解到GET通过URL传递数据,适合幂等的查询操作;而POST通过请求体传递数据,适合会修改数据的操作,且对数据长度没有严格限制。理解两者的区别对于构建安全、符合预期的Web应用程序至关重要。

接下来,我们将探讨如何在表单中创建不同类型的输入控件。

042:HTML输入类型 📝

在本节课中,我们将学习HTML表单中各种不同的输入类型。理解这些输入类型是构建交互式网页的基础,它们决定了用户如何向服务器提交数据。

上一节我们介绍了表单的基本结构,本节中我们来看看具体的输入控件。

文本与密码输入

最基本的输入类型是文本字段。它创建一个方框,用户可以在其中输入内容。密码字段在功能上与文本字段完全相同,数据在浏览器和服务器之间以纯文本形式发送。它的唯一区别是,在屏幕上显示时,密码字符会被隐藏(通常显示为星号),以防止旁人窥视。但这只是视觉上的隐藏,数据本身并未加密。

以下是文本和密码输入框的代码示例:

<input type="text" name="account">
<input type="password" name="pw">

每个表单字段都有一个唯一的name属性。当数据被打包发送到服务器时,这个name将成为$_GET$_POST数组中的键。例如,上面的代码会生成类似account=用户输入&pw=密码的数据。

你还会看到id属性。id是一个HTML/CSS概念,主要用于连接<label>标签以提升可访问性,或者用于CSS样式选择。name属性影响浏览器到服务器的数据传递,而idsize等属性则纯粹是浏览器端的HTML/CSS功能。虽然有人会让idname保持一致,但它们在功能上是完全独立的。

单选按钮与复选框

接下来,我们看看用于选择的输入类型:单选按钮和复选框。

单选按钮是一组按钮,当你选中其中一个时,同组其他按钮会自动取消选中。你可以创建任意数量的单选按钮。实现的关键在于为它们设置相同的name属性和不同的value属性。

以下是单选按钮的示例:

<input type="radio" name="when" value="AM"> AM
<input type="radio" name="when" value="PM"> PM

当用户提交表单时,服务器会收到类似when=PM的键值对。单选按钮通过相同的name属性关联,而不是它们在页面上的物理位置。

复选框用于允许用户同时选择多个选项的情况。与单选按钮不同,每个复选框通常拥有不同的name

以下是复选框的示例:

<input type="checkbox" name="class1" value="SI502">
<input type="checkbox" name="class2" value="SI539">

如果用户选中了某个复选框并提交,服务器会收到对应的键值对,例如class1=SI502。如果没有设置value属性,默认值将是"on"

下拉选择框

下拉选择框(<select>)是我们经常使用的控件。它特别有用,因为我们可以从数据库(如汽车品牌列表)中读取数据并动态填充选项,这节省了大量手动编码的工作。目前我们保持简单,后续课程会学习如何预填充数据。

下拉选择框是一个单一的字段,包含多个<option><select>标签的name属性是提交时的键,而每个<option>value属性是对应的值。

以下是下拉选择框的示例:

<select name="soda">
    <option value="0">Please Select</option>
    <option value="1">Coke</option>
    <option value="2">Pepsi</option>
    <option value="3">Mountain Dew</option>
</select>

如果用户没有选择,直接提交,服务器会收到soda=0。你可以在代码中检查这个值,如果为0,则提示用户必须做出选择。可以使用selected属性来设置默认选中的项。value可以是任何字符串,不一定是数字。

文本区域

文本区域(<textarea>)用于输入大段文本,如博客评论或文章。它与之前的输入控件模式不同。之前的控件通过value属性决定发送到服务器的数据,而文本区域的内容是写在开始和结束标签之间的。

以下是文本区域的示例:

<textarea name="about" rows="4" cols="50">I love building websites.</textarea>

用户在此输入的任何内容,在提交时都会以about为键发送到服务器。rowscols属性控制其显示尺寸。

多选列表与提交按钮

以下是两种需要特别注意的控件。

首先是一种多选列表,它允许用户通过按住Ctrl(或Cmd)键选择多个选项。在服务器端,它会以数组的形式提交数据。但大多数用户体验专家建议不要使用这种控件,而是构建其他更友好的界面。

其次是提交按钮。提交按钮也可以有namevalue属性。这里比较特殊的是,value属性的值既会作为发送到服务器的数据,也会显示为按钮上的文本。在编写代码时,我们通常使用isset($_POST[‘button_name’])来判断哪个按钮被点击了。

你还会看到一种使用按钮触发JavaScript跳转的写法:

<input type="button" value="Escape" onclick="location.href=‘escape.php‘; return false;">

onclick属性内的JavaScript代码会在按钮点击时执行。location.href会让浏览器跳转到指定页面(发起一个GET请求)。return false;是为了阻止按钮默认的表单提交行为。这本质上是一个看起来像按钮的链接。更现代的做法是使用CSS来样式化<a>标签。

总结

本节课中我们一起学习了多种HTML输入类型:文本、密码、单选按钮、复选框、下拉选择、文本区域以及按钮。我们了解了namevalue属性在构成键值对以向服务器提交数据时的核心作用,也区分了影响数据提交的name属性和主要用于样式与交互的id等属性。掌握这些基础控件是创建功能丰富、用户友好的Web表单的第一步。

043:HTML输入类型详解 🧩

在本节课中,我们将详细讲解HTML表单中各种输入类型的工作原理和代码实现。我们将逐一分析文本、密码、单选按钮、复选框、下拉菜单、文本域、多选列表以及提交按钮等元素,并了解它们如何与服务器进行数据交互。


文本与密码输入

上一节我们介绍了表单的基本结构,本节中我们来看看最基础的文本输入类型。

文本输入 (<input type="text">) 是最常见的表单字段,用于接收用户输入的单行文本。为了保持代码的语义化,我们通常使用 <label> 标签与之关联。

密码输入 (<input type="password">) 在视觉上与文本输入类似,但用户输入的内容会被隐藏(通常显示为圆点或星号)。需要注意的是,密码字段仅在输入时提供视觉保护。当表单通过HTTP POST方法提交时,密码会以明文形式在网络中传输。因此,它主要用于防止他人从旁窥视(“肩窥”),而非提供真正的加密安全。

以下是这两种输入类型的代码示例:

<label for="nickname">Nickname:</label>
<input type="text" id="nickname" name="nickname">

<label for="secret">Password:</label>
<input type="password" id="secret" name="secret">

单选按钮与复选框

接下来,我们探讨允许用户进行选择的输入类型:单选按钮和复选框。

单选按钮 (<input type="radio">) 允许用户从一组选项中选择唯一的答案。它的工作原理类似于汽车收音机的选台器。通过为同一组的所有单选按钮设置相同的 name 属性,浏览器会确保它们中只有一个能被选中。value 属性定义了选中该项后要发送到服务器的值。checked 属性可以设置默认选中的项。

复选框 (<input type="checkbox">) 则允许用户进行多项独立选择。每个复选框通常拥有独立的 namevalue。如果未指定 value,则默认提交字符串 "on"。在服务器端,我们通常检查该 name 是否存在于 $_POST 数组中,来判断复选框是否被选中,而不是直接依赖其 value 值。

以下是这两种输入类型的代码示例:

<!-- 单选按钮 -->
<input type="radio" id="am" name="when" value="AM">
<label for="am">AM</label>
<input type="radio" id="pm" name="when" value="PM" checked>
<label for="pm">PM</label>

<!-- 复选框 -->
<input type="checkbox" id="class1" name="class1" checked>
<label for="class1">Class I</label>
<input type="checkbox" id="class2" name="class2" checked>
<label for="class2">Class II</label>


下拉选择菜单

现在,我们来看两种允许用户从列表中选择的表单控件:标准下拉菜单和带默认值的下拉菜单。

下拉菜单 (<select>) 通过 <option> 子元素提供一系列选项。当用户提交表单时,所选 <option>value 属性值会作为该字段的数据发送到服务器。

以下是下拉菜单的代码示例:

<label for="soda">Soda:</label>
<select id="soda" name="soda">
    <option value="1">Coke</option>
    <option value="2">Pepsi</option>
    <option value="3">Mountain Dew</option>
</select>

<label for="snack">Snack:</label>
<select id="snack" name="snack">
    <option value="nuts">Peanuts</option>
    <option value="chips" selected>Chips</option>
    <option value="cookie">Cookies</option>
</select>

在第一个例子中,用户选择“Mountain Dew”会提交 soda=3。在第二个例子中,我们使用 selected 属性预先选中了“Chips”,因此默认会提交 snack=chips


文本域与多选列表

表单中还有两种用于处理多行文本和多项复杂选择的控件。

文本域 (<textarea>) 用于接收用户输入的多行文本。与 <input> 不同,它的默认值直接写在开始和结束标签之间,可以包含空格和换行符。提交后,所有内容会作为一个完整的字符串发送到服务器。重要提示:由于用户可以在此输入任何内容(包括HTML和JavaScript代码),如果不对其进行过滤就直接输出到网页,可能导致跨站脚本(XSS)攻击,因此必须谨慎处理。

多选列表 (<select multiple>) 是下拉菜单的变体,允许用户通过按住 Ctrl(Windows/Linux)或 Command(Mac)键选择多个选项。提交时,它会将一个数组发送到服务器。然而,这种交互方式对用户不够友好,在实际应用中应谨慎使用。

以下是这两种控件的代码示例:

<!-- 文本域 -->
<label for="about">About:</label>
<textarea id="about" name="about">Blah blah blank...</textarea>

<!-- 多选列表 -->
<label for="topping">Topping:</label>
<select id="topping" name="topping[]" multiple>
    <option value="cheese">Cheese</option>
    <option value="ham">Ham</option>
</select>

提交按钮与小技巧

最后,我们来了解表单的提交机制以及一个实用的小技巧。

提交按钮 (<input type="submit">) 用于发送表单数据。它的 value 属性有两个作用:一是作为按钮上显示的文本,二是作为提交时发送的数据。在有多语言需求的应用程序中,按钮文本可能会被翻译,因此更常见的做法是为不同的按钮设置不同的 name,然后在服务器端检查哪个 name 存在于 $_POST 数据中,以此判断哪个按钮被点击。

此外,我们还可以创建一个不提交表单的按钮。通过将 type 设置为 button 并添加 onclick 的JavaScript事件,可以让按钮执行跳转链接等操作,而不触发表单提交。这在实现“取消”或“返回”功能时非常有用。

以下是按钮的代码示例:

<!-- 提交按钮 -->
<input type="submit" name="do_post" value="Submit">

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/39fed7f85ac1accb5bef0955b96fa136_7.png)

<!-- 作为链接的按钮(不提交表单) -->
<input type="button" value="Cancel" onclick="location.href='https://example.com'; return false;">

本节课中我们一起学习了HTML表单中多种输入类型的详细用法,包括文本、密码、单选、复选、下拉选择、文本域和多选列表。我们还探讨了提交按钮的工作方式以及如何创建一个不提交表单的导航按钮。理解这些基础元素的特性和数据提交机制,是构建交互式Web应用程序的重要第一步。

044:HTML5输入类型 📝

在本节课中,我们将学习HTML5引入的一些新输入类型。这些类型为表单提供了更好的用户体验和内置验证功能,同时能优雅地降级,在不支持的浏览器中表现为普通文本输入框。


HTML历经多个版本发展,最初的版本是1.0,而HTML5是目前最新的、且已非常成熟的版本。因此,我们倾向于使用HTML5。HTML5引入了一些新的输入类型。如果你使用非常古老的浏览器,它们将不支持这些新类型,这些输入框会全部回退为 type="text" 的普通文本框。有很多优秀的例子,这里将为你介绍一些基础知识,帮助你入门。

以下是HTML5中一些新的输入类型示例:

  • 颜色选择器 (type="color")
    如果浏览器不支持此类型,它将显示为一个文本框,你可以手动输入十六进制颜色值。但支持时,浏览器会提供一个原生的颜色选择器UI。这不是你编写的代码,也不是CSS或JavaScript实现的。浏览器会根据用户的操作系统(如Mac、Windows或Linux)提供相应的原生颜色选择器界面。你点击选择颜色后,它会返回一个十六进制颜色值字符串,并在表单提交时发送该值。

  • 日期选择器 (type="date")
    同样,它也会回退为文本输入。type="date" 会指示浏览器创建一个符合当前操作系统习惯的日期选择器。它返回一个基于所选日期的字符串,格式类似于“年-月-日”。你无需编写相关代码,且其体验与操作系统本身的日期选择器保持一致,这非常方便。

  • 邮箱输入 (type="email")、数字输入 (type="number)、URL输入 (type="url")
    type="email" 本质上是一个带有验证功能的文本字段。type="number" 也是一个带有验证的文本字段,并提供了上下调整的小箭头,你还可以为其指定范围。type="url" 则表示期望输入一个URL。这三个类型都会在浏览器端进行验证。其工作原理是:当你按下提交按钮时,在表单数据被发送出去之前,浏览器会检查这三个字段的格式是否正确。如果格式错误,浏览器会显示一个不受你控制的提示信息,并阻止表单提交。因此,在请求-响应周期中,数据在发送前就被拦截,你必须修正格式错误后才能成功提交数据。

但请注意:在服务器端,用户仍有办法绕过这些客户端验证。因此你必须谨慎,不能仅仅因为设置了 type="number" 就在服务器端假定收到的数据一定是数字。服务器端必须采取额外措施来防护不良数据,我们稍后会讨论这一点。


上一节我们介绍了具有验证功能的输入类型,本节中我们来看看如何处理浏览器不支持的输入类型。

最后要讨论的一点是,如果浏览器遇到无法识别的输入类型(例如 type="flyingsaucer",“飞碟”并不是HTML5中定义的类型,也许未来HTML6会有,但现在没有),浏览器会将其视为未知类型。它的反应会是:“好吧,我不知道这是什么”,然后提供一个基本的文本输入框。因此,提交的数据会包含一个名为“saucer”的字段。这就是HTML5新输入类型的安全网机制:在不支持HTML5的旧浏览器中,它们都能回退为文本输入。虽然你可以通过JavaScript代码(即“垫片”)来模拟这些新功能,但本质上,这些新的输入类型只是你用来影响用户体验的一种可选方式。


本节课中,我们一起学习了HTML5引入的多种新输入类型,包括颜色选择器、日期选择器以及带有内置验证的邮箱、数字和URL输入框。我们了解了它们如何提升用户体验,以及在不支持的浏览器中如何优雅地回退为普通文本输入。同时,我们强调了服务器端验证的不可或缺性,因为客户端验证可以被绕过。

接下来,我们将讨论数据如何进入服务器,以及如何在服务器端验证和处理这些数据。

045:表单数据处理与HTML注入 🛡️

在本节课中,我们将学习如何处理从服务器接收到的表单数据。我们将重点关注如何在不同页面间持久化表单数据,以及如何防范一种常见的安全漏洞——HTML注入攻击。


数据持久化:在请求间保留表单值

上一节我们介绍了如何创建HTML表单以及如何通过$_POST接收数据。本节中,我们来看看如何处理$_POST数据。首先要讨论的是如何将表单数据从一个页面持久化到另一个页面。

用户提交表单后,如果出现错误,他们通常希望看到之前输入的值,以便进行修改。例如,用户输入了“20”并提交,如果数据没有保留,表单再次显示时输入框将是空的。作为开发者,我们需要主动将旧值从一个请求复制到下一个请求。

这是一个非常普遍的需求,但实现它需要我们编写特定的代码。同时,这也成为了最常见的安全漏洞来源之一。

我们的脚本会在两种情况下运行:

  1. 首次通过GET请求访问URL时,显示空表单。
  2. 用户填写表单并提交后,脚本通过POST请求再次运行,此时需要显示带有上次输入值的表单。

我们需要使用isset()来判断当前是POST请求(包含guess值)还是GET请求(仅显示表单)。首次访问显示空表单,第二次则显示包含上次猜测值的表单。

以下是实现此逻辑的代码示例,我们使用三元运算符来简化判断:

$old_guess = isset($_POST['guess']) ? $_POST['guess'] : '';

变量$old_guess将传递到模板代码中。表单的其他部分与之前类似,关键区别在于输入框的value属性:

<input type="text" name="guess" value="<?= $old_guess ?>">

value属性允许我们在生成HTML时预填充表单字段。这样,用户上次提交的“42”就会显示在输入框中。


PHP短标签:简化输出

通常,在HTML中嵌入PHP变量需要这样写:

value="<?php echo $old_guess; ?>"

这表示切换到PHP模式,执行echo命令,然后切换回HTML。由于这种操作非常频繁,PHP提供了一个简写语法:

value="<?= $old_guess ?>"

<?=<?php echo ... ; ?> 的快捷方式。我们在混合HTML和PHP代码时经常使用这种简写,特别是当只需要输出一个变量值时。


安全漏洞:HTML注入攻击 ⚠️

然而,上面展示的代码 value="<?= $old_guess ?>" 是你能写出的最糟糕的应用程序代码之一。原因在于“HTML实体”和“HTML注入”问题。

如果用户足够聪明,他们可能会在表单字段中输入一些能构成有效HTML的代码。例如,他们知道你的代码会生成 value="用户输入"。但如果用户输入的内容本身包含引号呢?

假设用户输入了:"><b>die die die</b>

提交后,页面上显示的旧猜测值不再是文本,而是加粗的“die die die”。这是因为用户输入的内容“破坏”了原有的HTML结构,并插入了他们自己的代码。

这被称为HTML注入。用户通过精心构造的输入,在一定程度上“接管”了页面。通常,攻击者做的远比显示“die die die”更邪恶,他们可能窃取信用卡号,或者诱使教师登录系统后篡改成绩——本质上,是让浏览器执行攻击者希望的代码。

问题的根源在于,浏览器解析HTML时,无法区分哪些是开发者编写的原始代码,哪些是来自用户输入的数据。当用户输入包含特殊字符(如引号、尖括号)时,这些字符会改变页面的HTML结构。

对于开发者来说,每当使用来自用户的输入数据生成HTML输出时,都必须高度警惕。$old_guess 不是一个普通的变量,它是一个来自用户输入的变量。打印用户输入时,必须确保以安全的方式进行。


解决方案:使用 htmlentities() 函数进行转义

幸运的是,修复这个问题非常简单。PHP提供了一个名为 htmlentities() 的函数。

回顾HTML基础,我们知道某些字符需要用实体(Entity)来表示:

  • 小于号 < 表示为 &lt;
  • 与号 & 表示为 &amp;
  • 双引号 " 表示为 &quot;

这些替代表示法就是HTML实体。htmlentities() 函数的作用是,将数据中所有可以表示为HTML实体的字符,都转换为其对应的实体形式。

因此,安全的写法应该是:

value="<?= htmlentities($old_guess) ?>"

让我们看看经过 htmlentities() 处理后的输出是什么:

value="&quot;&gt;&lt;b&gt;die die die&lt;/b&gt;"

现在,用户输入的双引号 " 被转换成了 &quot;,尖括号 < > 被转换成了 &lt;&gt;。当浏览器解析时,它会正确地将 &quot; 识别为值内部的一个普通字符,而不是属性值的结束引号。因此,页面上会安全地显示出用户输入的原始文本:"><b>die die die</b>

无论用户提交多么“疯狂”的内容,都会被安全地转义显示。在本课程的自动评分系统中,我会反复检查这一点,因为很多人在构建Web应用时都会在此犯错,从而暴露于HTML注入攻击之下。

请务必牢记(在接下来的课程中我还会提醒无数次):来自用户的数据是危险的,我们必须对它们进行转义(Escape)


总结

本节课中我们一起学习了:

  1. 数据持久化:如何使用 $old_guess = isset($_POST['guess']) ? $_POST['guess'] : '';value="<?= $old_guess ?>" 在请求间保留表单值。
  2. PHP短标签<?=<?php echo ... ; ?> 的快捷输出语法。
  3. HTML注入风险:直接将用户输入(如 $old_guess)输出到HTML中会导致安全漏洞,攻击者可以注入恶意代码。
  4. 安全转义:使用 htmlentities() 函数对输出到HTML中的用户数据进行转义,例如 value="<?= htmlentities($old_guess) ?>",这是防止HTML注入攻击的关键措施。

记住,处理用户输入时,永远要保持警惕并正确转义。

046:表单与HTML注入详解 🛡️

在本节课中,我们将学习Web表单的基础知识,包括如何使用GET和POST方法提交数据,以及如何安全地处理用户输入以防止HTML注入攻击。我们将通过分析一系列PHP代码示例来理解这些概念。

表单基础与GET方法

上一节我们介绍了课程概述,本节中我们来看看HTML表单的基本工作原理。HTML中的 <form> 标签用于创建用户输入界面,允许用户输入数据并提交回服务器。

以下是一个简单的表单示例,它使用GET方法提交数据:

<form action="form1.php" method="get">
  <label for="guess">输入猜测:</label>
  <input type="text" name="guess" id="guess">
  <input type="submit" value="提交">
</form>

  • <form> 标签:定义了表单的开始和结束。
  • action 属性:指定表单数据提交到的服务器端脚本(例如 form1.php)。
  • method 属性:定义数据发送的HTTP方法。get 方法会将数据附加在URL之后。
  • <input> 标签:创建输入字段。type="text" 定义文本输入框,name="guess" 是发送到服务器的参数名,id="guess" 用于关联 <label> 标签,提升可访问性。
  • <input type="submit">:创建一个提交按钮。

当用户点击提交按钮时,浏览器会发起一个新的请求-响应周期。数据会以查询字符串的形式附加在URL后面,例如:form1.php?guess=12

在PHP中,我们可以通过超全局变量 $_GET 来获取这些数据。$_GET 是一个关联数组,包含了URL查询字符串中的所有键值对。

使用POST方法提交数据

GET方法虽然简单,但会将数据暴露在URL中,不适合提交敏感信息(如密码)或大量数据。POST方法则通过HTTP请求体发送数据,不会在URL中显示。

要将表单改为使用POST方法,只需将 method 属性改为 post

<form action="form3.php" method="post">
  <label for="guess">输入猜测:</label>
  <input type="text" name="guess" id="guess">
  <input type="submit" value="提交">
</form>

在PHP中,使用 $_POST 超全局变量来获取通过POST方法提交的数据。与 $_GET 类似,$_POST 也是一个包含所有提交数据的关联数组。

保持表单数据的持久性

使用POST方法后,一个常见的问题是:页面刷新或提交后,之前输入的数据不会自动保留在表单字段中。这给用户修改数据带来了不便。

为了解决这个问题,我们需要在生成表单的HTML时,将之前提交的值回填到输入框中。以下是实现方法:

<?php
$oldguess = isset($_POST['guess']) ? $_POST['guess'] : '';
?>
<form method="post">
  <label for="guess">输入猜测:</label>
  <input type="text" name="guess" id="guess" value="<?= $oldguess ?>">
  <input type="submit" value="提交">
</form>

这段代码首先检查 $_POST[‘guess’] 是否存在。如果存在(即用户已提交表单),则将其值赋给变量 $oldguess;否则 $oldguess 为空字符串。然后,在 <input> 标签的 value 属性中输出 $oldguess,从而实现数据持久化。

HTML注入攻击与防御

然而,直接将用户提供的数据输出到HTML中会引入严重的安全风险,即 HTML注入跨站脚本攻击

考虑以下恶意输入:用户在一个猜测框中输入 “/><input type=“submit” value=“怪物按钮”><hr>。当这段数据被未经处理地回填到 value 属性时,它会“逃逸”出原有的属性,在页面上创建一个额外的、可能执行恶意操作的“怪物按钮”。

攻击者可以利用此漏洞窃取其他用户的Cookie或会话信息,危害极大。

防御这种攻击的核心方法是:对所有最终来源于用户的输出数据进行转义。在PHP中,使用 htmlspecialchars() 函数可以安全地将特殊字符转换为HTML实体。

以下是安全的代码写法:

<?php
$oldguess = isset($_POST['guess']) ? $_POST['guess'] : '';
$safe_oldguess = htmlspecialchars($oldguess);
?>
<form method="post">
  <label for="guess">输入猜测:</label>
  <input type="text" name="guess" id="guess" value="<?= $safe_oldguess ?>">
  <input type="submit" value="提交">
</form>

htmlspecialchars() 函数会将字符如 <>& 转换为 &lt;&gt;&quot;&#039;&amp;。这样,浏览器会将这些实体显示为普通字符,而不会将其解释为HTML代码的一部分,从而有效阻止了注入。

关键安全准则:对于任何可能包含用户提供数据的变量,在将其输出到HTML页面时,务必使用 htmlspecialchars() 进行转义。即使对非用户数据使用此函数也无害,但养成这个习惯能帮助你清晰地识别数据来源并确保安全。

总结

本节课中我们一起学习了Web表单的核心机制。我们了解了GET和POST两种HTTP方法在数据提交上的区别,并学会了如何在提交后保持表单数据的持久性。最重要的是,我们认识到了直接将用户输入输出到HTML中所带来的HTML注入安全风险,并掌握了使用 htmlspecialchars() 函数进行有效防御的方法。牢记:永远不要信任用户输入,输出前务必转义

047:服务器端数据验证

在本节课中,我们将要学习服务器端数据验证。这是指在服务器上接收用户提交的数据时,为确保数据安全、正确和符合预期而进行的检查。我们将探讨其重要性、常用方法,并通过一个猜数游戏的例子来演示如何实现。

上一节我们介绍了HTML5客户端验证,本节中我们来看看服务器端验证。

服务器端验证的必要性

HTML5验证发生在用户与浏览器交互时。当数据最终提交到服务器后,我们必须进行自我保护。POST数据到达服务器后,我们需要判断数据是否良好、正确或存在危险。

我们之前讨论过HTML实体转义,那是在将数据回显输出时进行的防护。现在,我们讨论的是在服务器内部、脚本逻辑的早期,在将数据存入数据库或进行其他操作之前,判断数据是否合格。如果不合格,我们可能需要返回一个错误信息给用户,要求其重新提交。

验证方法与函数

验证代码通常写在程序的开头。以下是几种常用的验证函数:

  • empty(): 用于检查一个变量是否为空(没有值或长度为0)。
  • is_numeric(): 检查变量是否为数字或数字字符串。
  • strpos(): 可以用来进行简单的格式检查,例如检查电子邮件地址中是否包含“@”符号。

此外,PHP内置了一个更强大的函数 filter_var(),它提供了一系列过滤器,可以更精确地验证电子邮件、URL等数据的格式。

实践案例:猜数游戏验证

让我们通过一个猜数游戏的例子,看看如何防御错误或恶意的输入。游戏规则是用户通过URL参数提交猜测的数字。

以下是验证用户猜测数据的代码示例:

// 检查是否提供了‘guess’参数
if (!isset($_GET[‘guess’])) {
    echo “Your guess is too short”;
} else if (strlen($_GET[‘guess’]) < 1) {
    // 检查参数值是否为空字符串
    echo “Your guess is too short”;
} else if (!is_numeric($_GET[‘guess’])) {
    // 检查参数值是否为数字
    echo “Your guess is not a number”;
} else if ($_GET[‘guess’] < 42) {
    // 执行游戏逻辑:检查是否小于目标数42
    echo “Your guess is too low”;
} else if ($_GET[‘guess’] > 42) {
    // 检查是否大于目标数42
    echo “Your guess is too high”;
} else {
    // 猜对了
    echo “Congratulations - You are right”;
}

这段代码的结构体现了验证的层次性:

  1. 首先检查参数是否存在。
  2. 然后检查参数值是否非空。
  3. 接着检查参数值是否为数字。
  4. 在确认是有效数字后,才执行具体的游戏逻辑比较。

这种由宽到严、逐层过滤的方式是编写健壮验证逻辑的常见模式。

应用架构概念:模型-视图-控制器

在讨论了如何处理输入数据后,接下来我想谈谈应用程序的基本组织结构。这涉及到处理输入、产生输出、更新数据库等操作,一个核心的概念叫做模型-视图-控制器

MVC是一种将应用程序逻辑分为三个相互关联部分的设计模式:

  • 模型:负责管理应用程序的数据和业务逻辑。
  • 视图:负责呈现数据,即用户界面。
  • 控制器:接收用户输入,协调模型和视图。

理解MVC有助于我们以更清晰、更可维护的方式组织代码,将数据验证、业务处理和界面展示分离开来。

本节课中我们一起学习了服务器端数据验证的重要性。我们了解了在数据进入服务器后,必须对其进行一系列检查以确保安全性和正确性。我们介绍了几种关键的PHP验证函数,并通过一个猜数游戏的实例演示了如何结构化地实施验证。最后,我们简要提及了模型-视图-控制器这一组织代码的架构模式,为构建更复杂的Web应用打下基础。

048:猜数游戏 🎮

在本节课中,我们将通过一个简单的“猜数字”游戏代码示例,深入探讨Web应用程序开发中的一个核心架构模式:模型-视图-控制器(MVC)。我们将分析一段初始代码,并学习如何将其重构为更清晰、更易于维护的MVC结构。

概述

我们将从一段功能完整但结构较为混乱的PHP脚本开始。这段脚本混合了HTML和PHP逻辑,虽然能正常工作,但在代码组织上存在不足。本节的目标是理解MVC模式的基本思想,并学习如何将数据处理(模型)、用户界面(视图)和控制逻辑(控制器)清晰地分离开来。

初始代码分析

首先,我们来看一段典型的、结构较为基础的PHP脚本。它的主要功能是让用户猜测一个预设的数字(例如42)。

<?php
// ... 一些初始的PHP逻辑和HTML混合代码
$guess = $_POST['guess'] ?? false;
$number = 42;

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/77eb364cf79ccf0bfe9380087c3af294_3.png)

if ($guess === false) {
    echo "请提交一个猜测值。";
} elseif (!is_numeric($guess)) {
    echo "您的猜测必须是一个数字。";
} else {
    $guess = (int)$guess;
    if ($guess < $number) {
        echo "猜得太低了!";
    } elseif ($guess > $number) {
        echo "猜得太高了!";
    } else {
        echo "恭喜你,猜对了!";
    }
}
// ... 后续的HTML输出
?>

这段代码的优点在于它进行了良好的输入验证,检查了参数是否存在、是否为数字。然而,其缺点是将PHP业务逻辑与HTML展示代码紧密耦合在一起,随着功能增加,代码会变得难以阅读和维护。

引入模型-视图-控制器(MVC)模式

上一节我们分析了混合编码的弊端,本节中我们来看看如何用MVC模式来改善它。

MVC是一种将应用程序分为三个核心部件的设计模式:

  • 模型(Model):负责处理数据和业务逻辑(例如,检查猜测值是否正确)。
  • 视图(View):负责渲染用户界面(例如,生成HTML页面)。
  • 控制器(Controller):负责接收用户输入,协调模型和视图(例如,决定调用哪个模型函数,选择哪个视图进行渲染)。

这种分离使得代码更模块化,更易于测试和维护。即使在简单的应用中,遵循这种分离原则也是有益的。

重构为MVC结构

以下是如何将之前的猜数游戏重构为遵循MVC原则的代码。关键是在脚本中建立一条清晰的“分界线”。

<?php
// ========== 模型/控制器部分 (数据处理与逻辑) ==========
// 初始化“上下文”变量,用于向视图传递数据
$oldguess = $_POST['guess'] ?? '';
$message = false;
$number = 42; // 模型数据

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/77eb364cf79ccf0bfe9380087c3af294_9.png)

// 控制器逻辑:检查是否有POST请求并处理
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    // 模型逻辑:验证和处理数据
    if (strlen($oldguess) < 1) {
        $message = "您的猜测不能为空。";
    } elseif (!is_numeric($oldguess)) {
        $message = "您的猜测必须是一个数字。";
    } else {
        $guess = (int)$oldguess; // 快速转换为整数
        // 核心游戏逻辑
        if ($guess < $number) {
            $message = "猜得太低了!";
        } elseif ($guess > $number) {
            $message = "猜得太高了!";
        } else {
            $message = "恭喜你,猜对了!";
        }
    }
}
// ========== 分界线:以上无输出,以下无核心数据操作 ==========
?>
<!— ========== 视图部分 (用户界面) ========== —>
<!DOCTYPE html>
<html>
<head><title>猜数游戏 (MVC)</title></head>
<body>
    <h1>猜猜我的数字(1-100之间)</h1>
    <?php if ($message !== false): ?>
        <p><?= htmlentities($message) ?></p>
    <?php endif; ?>
    <form method="post">
        <p><label for="guess">输入猜测:</label>
        <input type="text" id="guess" name="guess"
               value="<?= htmlentities($oldguess) ?>" /></p>
        <input type="submit" value="提交猜测"/>
    </form>
</body>
</html>

代码结构解析

以下是重构后代码各部分的详细说明:

  1. 模型与控制器(分界线上方)

    • 这部分代码专注于数据处理。它检查请求类型(GET或POST),验证用户输入,并执行猜数字的核心逻辑。
    • 它不直接产生任何输出(如echo),而是将结果存储在变量(如$message$oldguess)中。这些变量构成了传递给视图的“上下文(Context)”。
  2. 视图(分界线下方)

    • 这部分几乎全是HTML,夹杂少量用于展示的PHP代码(如<?= ?>短标签)。
    • 它从“上下文”中读取数据($message, $oldguess)并将其渲染到页面上。
    • 在此部分,我们避免进行数据库查询或复杂的业务逻辑计算。

  1. 控制器的作用
    • 在整个脚本中,控制器是隐式存在的。它由顶部的条件逻辑(if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’))扮演,负责根据用户请求决定执行哪些模型逻辑。
    • 在更复杂的应用中,控制器可能会决定重定向到不同的页面(路由)。

工作流程示例

  • 首次加载(GET请求):脚本执行模型部分,由于不是POST请求,$messagefalse$oldguess为空。然后跳至视图部分,显示一个空的表单。
  • 提交猜测(POST请求):脚本执行模型部分的所有逻辑,计算出相应的提示信息并存入$message,同时将用户输入存入$oldguess。然后进入视图部分,将信息和旧值填充到HTML表单中并显示给用户。

总结

本节课中我们一起学习了MVC(模型-视图-控制器)设计模式在一个简单PHP猜数游戏中的应用。我们首先分析了一段混合编码的脚本,然后通过引入一条清晰的逻辑“分界线”,将其重构为结构更清晰的版本:将数据处理逻辑置于上方(模型/控制器),将展示逻辑置于下方(视图)。这种分离使得代码更易于理解、调试和扩展。记住,上下文(Context) 是连接数据处理和界面展示的桥梁。掌握这一基本模式,将为未来学习更复杂的Web框架打下坚实的基础。

049:模型-视图-控制器(MVC)模式 🧩

在本节课中,我们将学习一个重要的编程模式——模型-视图-控制器(MVC)。这个模式是Web应用开发中的核心概念,能帮助我们更好地组织代码结构,分离数据处理、业务逻辑和用户界面。

概述

模型-视图-控制器(MVC)是一种将应用程序分解为三个核心组件的设计模式。它有助于清晰地分离数据管理、用户界面和业务逻辑,使代码更易于维护和理解。我们将探讨MVC的基本概念,并通过一个简单的PHP示例来展示如何应用这一模式。

什么是MVC?

MVC代表模型(Model)、视图(View)和控制器(Controller)。这个模式的核心思想是将请求-响应周期分解为三个基本操作。

  • 模型(Model):负责管理应用程序的数据和业务逻辑。它直接与数据库交互,处理数据的创建、读取、更新和删除(CRUD)操作。
  • 视图(View):负责呈现数据给用户。它生成用户看到的HTML界面,并根据从控制器接收的数据动态更新内容。
  • 控制器(Controller):作为模型和视图之间的协调者。它接收用户输入(如表单提交),决定调用哪些模型方法处理数据,并选择相应的视图来展示结果。

一个典型的流程是:用户请求进入控制器,控制器调用模型处理数据,模型更新后,控制器将处理结果(称为上下文)传递给视图,视图最终生成HTML返回给用户。

如何在代码中实现MVC?

MVC有多种实现方式,不同的框架(如CakePHP、Angular、React、Symfony)对其解释和应用各不相同。在本课程中,我们将学习一种简单直观的实现方式,它虽然将所有代码放在一个文件中,但严格遵循MVC的职责分离原则。

以下是实现的基本结构:

<?php
// ---------- 模型(Model)部分 ----------
// 此部分处理所有数据逻辑,无任何HTML输出
$oldguess = '';
$message = false;

if (isset($_POST['guess'])) {
    // 处理用户提交的猜测数据
    $oldguess = $_POST['guess'] + 0;
    if ($oldguess == 42) {
        $message = "恭喜你,猜对了!";
    } elseif ($oldguess < 42) {
        $message = "猜的数字太小了。";
    } else {
        $message = "猜的数字太大了。";
    }
}
// ---------- 模型部分结束 ----------
?>
<!DOCTYPE html>
<!-- ---------- 视图(View)部分 ---------- -->
<html>
<head><title>猜数字游戏</title></head>
<body>
<?php
// 视图根据上下文($message, $oldguess)动态生成内容
if ($message !== false) {
    echo "<p>$message</p>\n";
}
?>
<p>请猜一个数字:</p>
<form method="post">
    <p><input type="text" name="guess" value="<?= htmlentities($oldguess) ?>"/></p>
    <p><input type="submit"/></p>
</form>
</body>
</html>
<!-- ---------- 视图部分结束 ---------- -->

代码结构解析

  1. 模型部分(顶部):负责所有数据处理。它检查是否有来自表单的POST数据($_POST),进行验证和逻辑判断(如比较数字),并设置将传递给视图的变量($message, $oldguess)。关键原则是:此部分不生成任何HTML输出。

  1. 视图部分(底部):负责所有HTML输出。它接收从模型部分传递过来的变量(即上下文),并使用它们来动态构建页面。例如,它根据$message的值决定是否显示提示信息,并使用htmlentities()函数安全地回显用户上次的猜测值($oldguess),以防止HTML注入攻击。关键原则是:此部分不直接与数据库交互。

  2. 控制器逻辑:在上面的简单示例中,控制器逻辑隐含在模型部分的if (isset($_POST[‘guess’]))判断中。它决定了何时处理输入数据(POST请求时),以及处理完成后,流程自然“落入”视图部分进行展示。在更复杂的应用中,控制器可能会决定加载不同的模型或视图文件。

请求流程详解

  • 首次加载(GET请求):用户首次访问页面,没有POST数据。模型部分的isset($_POST[‘guess’])false,因此跳过数据处理逻辑。变量$message保持为false$oldguess为空字符串。程序直接进入视图部分,显示一个空的表单。
  • 提交表单(POST请求):用户输入数字并提交表单。浏览器发送POST请求,数据包含在$_POST[‘guess’]中。此时模型部分的isset($_POST[‘guess’])true,执行数据处理逻辑,根据猜测值设置$message$oldguess。然后程序进入视图部分,视图使用新的上下文变量来显示提示信息和回填用户上次的输入。

遵循MVC的规则

为了保持代码的清晰,建议遵循以下简单规则:

  • 模型部分(文件顶部),只进行数据处理和业务逻辑,不要生成任何HTML
  • 视图部分(文件底部),只进行数据展示,不要执行数据库查询或复杂的业务逻辑。所有需要展示的数据都应作为变量(上下文)从模型部分传递过来。

这种分离是一种编程纪律。它使得代码更易于阅读、调试和测试,尤其是在项目变得复杂时。

总结

本节课我们一起学习了模型-视图-控制器(MVC)模式。我们了解了MVC的三个核心组件:模型管理数据,视图负责显示,控制器协调两者。我们通过一个猜数字游戏的PHP示例,演示了如何在一个文件中应用MVC原则来组织代码,将数据处理(模型)与HTML输出(视图)清晰分离,并由隐含的控制器逻辑驱动流程。

我们还回顾了之前课程的知识,包括使用$_GET$_POST超全局变量处理表单数据,进行输入验证,以及使用htmlentities()函数对输出进行转义以防止安全漏洞。MVC是一种强大的组织模式,虽然存在多种实现方式,但掌握其核心思想将为学习更复杂的Web开发框架打下坚实的基础。

050:布莱切利园

在本节课中,我们将回顾一次特别的线下聚会,来自世界各地的课程学员在布莱切利园分享了他们的学习经历与收获。


大家好,这是我们第二次几乎算得上年度的课程同学会,地点在布莱切利园。我们有一群很棒的伙伴。我们将按惯例进行,让每个人打个招呼,说说自己的名字或任何想说的话。那么,我们开始吧。

以下是各位学员的自我介绍:

  • hi, 我是来自伦敦的 Erzger, 我修完了 Dr. Chuck 的所有 Python 课程,一直到顶点课程。
  • hi, 我叫 Lota, 来自瑞典,我一直在学习 Python for everybody。你今天是从瑞典来的吗?不,我今天从伦敦来,我住在伦敦。
  • hi, 我是 Steve。我多次学习 Python 课程,我是你的导师之一。所以我们给 Stephen 寄了一件很酷的T恤。恭喜你,你是一名志愿者,为我工作了大概两年了,你还得到了一件T恤。当然,还有这次不错的出行。让我们给 Stephen 一轮掌声。Stephen 很有名。Stephen 以每天都在论坛里告诉大家不要贴代码而闻名,他每天大概要说20次“不要在论坛贴代码”。
  • 继续。hello, 你叫什么名字?hi, 我是 Caroline, 来自伦敦。我真的很喜欢这门课程,还没到顶点课程。我希望有时间学习历史课程。能来布莱切利园真的很棒。
  • hi, 我是 Le, 来自乌克兰。这是我的第一次。我想感谢你的讲座,我真的很荣幸。荣幸的是我,我叫 Chuck, 欢迎你 Le。
  • hi, 我是 Brian, 来自弗吉尼亚州里士满。我一直在学习你的课程,我想来看看这次聚会。你基本上是来度假的吗?当我去拉斯维加斯时,我和四个人见了面,他们没有一个来自拉斯维加斯。
  • hi, 我是 Jason, 来自牛津郡的惠灵顿。我学完了互联网历史课程,很棒。我现在正在学习一门化学课程,我意识到你的课程要好得多。哦,我可能得把那段剪掉。不,我不会剪掉。真的很有趣。能教你们所有人真的是一种荣誉。你知道,能进入你们的家,出现在你们的手机上等等,这本身就是一种荣誉。
  • hi, 我叫 Yuki, 我是日本人,一直住在伦敦。我现在正在学习 Python 专项课程。总是很有趣。
  • hi, 我是 Irena, 来自波兰,但住在伦敦。我基本上是语言学毕业的,但我真的爱上了 Python, 谢谢你。你发现布莱切利园这里的波兰联系了吗?是的,我实际上看了去年的视频。你看了关于波兰人的视频?是的。这里有人是第二次来吗?比如一年半前?哦,所以你一年半前来过?不,我指的是像在办公时间那样的聚会。
  • hi, 我是 Jeff, 我教计算机科学,但我不是学这个的。所以我必须学习你的课程。你三年前教我的关于互联网历史的知识,至今仍然构成我教学的基础。我仍然会偷用你的一些幻灯片,我经常让我的学生报名你的课程,他们通常会照做。你教哪个年级?我教中学,我们这里叫 GCSE 和 A level。我们得加强这方面,得让他们正确理解计算机的工作原理。
  • Mike, 你想说几句吗?Mike。我学习了最初版本的 Python 课程,第一个,那个十周的长期课程。很好,欢迎。
  • hi, 我是 Sarah, 我从牛津来。我很久以前学习了最初的 Python for everyone 课程,编程终于开窍了。所以现在我教孩子们在 workshops 等地方编程。我不知道你是否注意到了,我在很多示例中都使用了 Sarah 这个名字。所以现在我有一个真实的 Sarah 了,有 Sally 和 Glenn 等等这些人,但不知为何总有一个 Sarah, 但你显然就是那个 Sarah。欢迎。
  • hi, 我是 Robin, 来自赫默尔亨普斯特德。我真的很喜欢你的课程,我喜欢 Python。
  • hi, 我是 Caroline, 原籍波兰,但住在伦敦。所以我在这里已经找到一个波兰朋友了,真好。我一直在学习 Python 课程,目前到第三级,希望能完成顶点课程。越来越难了,但很令人兴奋。
  • hi, 我是 Richard, 正在学习 Python for everyone。学习 Python 很有趣,不仅仅是学“hello world”这种语言。只是为了兴趣。
  • hi, 我是 Claire, 来自德文郡。我只学了第一门 Python 课程,但真的很有趣,我认为你是一位非常好的讲师。谢谢,谢谢。整个想法的一部分就是让你入门,激发你的好奇心,这样你的好奇心会驱动你走向你想去的地方,而不是吓跑你。剩下的就交给我吧。
  • hi, 我是 B, 来自埃塞克斯郡的索森德。我学习了互联网历史课程。
  • 你想说几句吗?那是我儿子,今天只是陪我来。好的,没关系,我们很好。
  • hi, 我刚刚完成了你的 python for everybody 专项课程,太棒了。你说顶点课程有点太简单了?是的,但它背后的概念很好,能看到这些很好。如果整个课程让最后一部分显得简单,那我就认为我成功了。哈哈,我赢了,太棒了。
  • hello, Peter。我正在学习 Python 课程的第二门,学做 Pythonista, 非常好,我很享受。第三门会难一点。
  • hi, 我是一名档案管理员,在伦敦的一家博物馆工作。我已经完成了 Python for everyone 的顶点课程。这是我这几年做过的最有用的事情。我处理很多博物馆藏品数据和档案藏品数据,在两个月前对编码一无所知之后,我现在可以编写程序来拆解我们博物馆的整个目录数据库,做一些我们长期以来一直想做的事情,并且几秒钟内就能完成。期待在我所学的基础上继续发展,非常感谢。你知道我工作的地方有一个档案学硕士学位,涉及技术吗?我不知道。不,我在信息学院工作。图书馆学、档案学,书呆子气的东西。但我得给自己弄个美国签证。是的,最好快点。
  • hi, 我的名字是 Pe, 我在当地政府工作,周末我帮助她编写一个叫 Doger 的代码,但我不懂 Python, 所以我需要学习这门课程。
  • hi, 我叫 Luke。我正在学习第三门 Python 课程,我非常享受。
  • hi, 我的名字是 Rogerja。我正在学习互联网历史课程。

好了,这就是我们的办公时间。我们将给所有人一个大合影。让我们再次为 Stephen 和他有多棒鼓掌。


本节课中,我们一起回顾了在布莱切利园举行的学员聚会。来自不同背景和国家的学员们分享了他们学习 Python 和互联网历史等课程的体验、收获以及如何将所学应用于工作与生活。这次聚会体现了在线学习社区的活力与连接。

051:欢迎学习SQL与数据库课程 👋

在本节课中,我们将要学习一门全新的计算机语言——SQL(结构化查询语言),它是我们与数据库服务器进行对话的方式。本节是课程的欢迎与介绍部分,将为你勾勒出整个课程的学习路径和重要性。

课程定位与学习路径 🔄

欢迎来到SQL与数据库课程。这门课程与之前的课程非常不同。

我们不会编写任何HTML代码。我们也不会编写任何PHP代码。我们不会编写任何CSS,也不会讨论请求-响应周期。我们要做的是学习一门全新的计算机语言——SQL(结构化查询语言),这是我们与数据库服务器对话的方式。

有趣的是,第一门课程是关于PHP、HTML等内容的,而这一门是SQL课程。实际上,你可以以任意顺序学习这两门课程。因此,如果你偶然进入这门课程,但对HTML、CSS或PHP一无所知,这完全没有问题。这两门课程可以按任何顺序学习。

但是,你必须完成第一门和第二门课程,才能为第三门课程做好准备。在第三门课程中,我们将把PHP和SQL结合起来。如果你不了解这两个部分,那将会显得非常困难。

SQL语言的魅力 ✨

如果你从未接触过SQL,但可能了解一些编程语言,比如Python,或者了解PHP、HTML和CSS,我认为你会收获一份惊喜。

如果你看过我的其他课程,我有时会表示歉意,我可能会说:“是的,我对某些工作原理感到有些抱歉,但没关系,只管加上那些括号,因为这是你必须做的。”但我从不为SQL道歉。对我来说,SQL是我使用过的最优美的编程语言。它非常强大,表达力超强,并且合乎逻辑。

我经常告诉校园里的学生,我先教你们Python,再教你们SQL。因为如果我先教你们SQL,你们可能会拒绝学习Python。这是因为人类的思维方式是“我想要这个”,而我们在SQL中所做的正是说“我想要这个”。因此,它感觉非常符合人类的直觉。

SQL的定位与学习意义 🎯

如果你像我一样爱上了SQL,你可能会想:“那么我以后就用SQL来编写所有程序了。”但事实证明,SQL更像是一种高级语言,这正是它如此优美的原因,但你确实无法用SQL编写所有程序。所以你仍然需要学习JavaScript,仍然需要学习PHP,仍然需要学习Python。

正如我所说,这门课程对于为你准备下一门课程——数据库与PHP课程——至关重要。第三门课程是我们将所有内容融合在一起的地方。我希望能直接从第三门课程开始,因为在课程结束时,你将构建出一个真正可运行的数据库应用程序,一个具备CRUD(创建、读取、更新、删除)功能的应用程序。但现在,你需要学习SQL,并且纯粹地学习SQL本身,享受它。然后,我们将在稍后真正应用它来构建应用程序。

沟通与支持渠道 📢

感谢你对本课程的兴趣。如果遇到任何问题或发现有错误,请通过表单告知我们。我很乐意修复问题。如果你需要紧急联系我,只需在推特上@DrChuck,我通常会在几分钟内看到。

再次感谢你来上课,我们课程结束时再见。


本节课中我们一起学习了SQL课程的定位、SQL语言的独特魅力及其在Web开发技术栈中的位置。我们明确了先独立学习SQL,再将其与PHP结合的学习路径,为后续构建完整的数据库应用程序打下坚实基础。

052:在Macintosh上安装MAMP 🍎

在本节课中,我们将学习如何在Macintosh电脑上安装MAMP软件。MAMP是一个集成了Apache服务器、MySQL数据库和PHP的本地开发环境,是进行Web应用程序开发的重要工具。我们将从下载安装开始,到配置PHP设置,最后创建一个简单的PHP页面来验证安装是否成功。

下载与安装MAMP

首先,我们需要从MAMP的官方网站下载安装程序。访问 mamp.info 即可找到下载链接。

下载完成后,文件通常会保存在“下载”文件夹中。找到名为 MAMP_Pro_4.0.1.pkg(版本号可能不同)的安装包,双击开始安装。

在安装过程中,接受所有默认设置即可。安装程序会自动完成所有必要的步骤。

启动与验证MAMP

安装完成后,打开“访达”,进入“应用程序”文件夹,可以找到名为 MAMP 的应用程序。

双击启动MAMP。MAMP控制面板会随之打开,面板上会显示服务器的状态信息,例如PHP版本和配置文件位置。一个绿色的圆点表示服务器正在运行。

此时,在浏览器中访问 http://localhost:8888,可以打开MAMP的欢迎页面。点击页面上的“PHP信息”链接,可以查看当前PHP的详细配置。

配置PHP开发设置

默认情况下,MAMP的PHP配置可能不会在页面上显示错误信息,这对于开发调试很不方便。我们需要修改PHP配置文件来开启错误显示。

以下是需要修改的配置项:

  • display_errors: 设置为 On,以在页面上显示运行时错误。
  • display_startup_errors: 设置为 On,以显示PHP启动时的错误。

注意:这些设置仅适用于本地开发环境。在生产环境中,必须将其关闭,以避免泄露敏感信息。

具体操作步骤如下:

  1. 在MAMP控制面板中,点击“停止服务器”。
  2. 使用文本编辑器(如TextEdit)打开PHP配置文件。文件路径通常为:/Applications/MAMP/bin/php/php[版本号]/conf/php.ini
  3. 在文件中搜索 display_errorsdisplay_startup_errors,将其值从 Off 改为 On
  4. 保存文件。
  5. 返回MAMP控制面板,点击“启动服务器”。

再次访问“PHP信息”页面,搜索“display_errors”,确认其值已变为 On,即表示配置成功。

创建第一个PHP页面

现在,我们来创建一个简单的PHP页面,以测试开发环境是否工作正常。

首先,找到MAMP的网站根目录。默认路径是:/Applications/MAMP/htdocs/。所有需要通过本地服务器访问的网页文件都应放在此目录或其子目录下。

接下来,我们创建一个测试文件:

  1. htdocs 目录下,新建一个名为 first 的文件夹。
  2. 在该文件夹内,创建一个名为 index.php 的文件。
  3. 用文本编辑器打开 index.php,输入以下代码:

<?php
echo "Hello from my first web page.";
?>

保存文件后,在浏览器中访问 http://localhost:8888/first/。服务器会自动寻找并执行 index.php 文件,你将在页面上看到输出的文字:“Hello from my first web page.”

总结

本节课中,我们一起学习了在Macintosh上搭建PHP本地开发环境的完整流程。我们完成了MAMP软件的下载与安装,学会了如何启动服务器并修改关键的PHP配置(开启错误显示),最后通过创建一个简单的 index.php 文件验证了环境配置成功。现在,你已经拥有了一个功能完备的本地Web开发环境,可以开始进行PHP编程了。

053:在Windows 10上安装MAMP与编写第一个PHP程序 🚀

在本节课中,我们将学习如何在Windows 10系统上安装MAMP集成开发环境,并编写和运行你的第一个PHP程序。整个过程包括下载、安装、配置MAMP,以及使用文本编辑器Atom创建并调试一个简单的PHP网页。

下载与安装MAMP

首先,我们需要下载MAMP的Windows版本。访问官方网站,选择Windows版本进行下载。

下载完成后,运行安装程序。在安装过程中,请按照以下步骤操作:

  1. 选择安装语言为“English”。
  2. 在安装选项界面,取消勾选“MAMP PRO”的付费版本,仅安装基础版本。
  3. 阅读并接受软件许可协议。
  4. 使用默认的安装路径(例如 C:\MAMP)。
  5. 完成安装向导,并启动MAMP应用程序。

安装过程中,系统可能会弹出Windows防火墙警告。这是正常现象,因为MAMP需要与网络通信。

以下是关键步骤的图示:


启动与验证MAMP服务

成功安装后,桌面上会出现MAMP的快捷方式。启动MAMP,其主控制面板将显示Apache和MySQL服务器的状态。

为了确保所有功能正常工作,我们需要启动这两项服务,并允许它们通过防火墙。

  1. 在MAMP控制面板中,点击“Start Servers”按钮,启动Apache和MySQL服务器。
  2. 当Windows防火墙提示时,务必为Apache和MySQL都选择“允许访问”,这是服务器能正常通信的关键。

服务启动后,点击“Open Start Page”按钮,将在浏览器中打开MAMP的本地欢迎页面。这个页面包含了许多有用信息。

为了验证PHP和MySQL已正确安装并运行,请执行以下操作:

  1. 在MAMP欢迎页面上,点击“phpMyAdmin”链接。
  2. 如果phpMyAdmin管理界面能够正常加载并显示数据库信息,如下图所示,则说明你的MAMP环境已成功安装并配置完毕。


安装文本编辑器Atom

上一节我们成功搭建了PHP运行环境,本节中我们来看看如何准备代码编写工具。虽然你可以使用任何文本编辑器,但我们推荐使用Atom,因为它跨平台(Windows、Mac、Linux)且功能强大,支持语法高亮等对编程至关重要的特性。请避免使用记事本(Notepad)或Word,它们可能会破坏代码文件的格式。

从Atom官网下载Windows安装程序并运行。按照安装向导的提示完成安装即可。

以下是安装过程的图示:

编写第一个PHP程序

现在,我们将结合MAMP和Atom,创建第一个PHP应用程序。

首先,确保MAMP中的Apache和MySQL服务器已经启动。然后,在Atom中新建一个文件。

我们将编写一个简单的PHP页面。在Atom的新文件中,输入以下代码:

<h1>Hello from a web page</h1>
<?php
echo "Hi there\n";
?>
<p>Some HTML</p>

接下来,我们需要将这个文件保存到MAMP的网站根目录,这样Apache服务器才能找到并执行它。

  1. 在Atom中,点击“File” -> “Save”。
  2. 在保存对话框中,导航到MAMP的 htdocs 目录。通常路径是 C:\MAMP\htdocs
  3. htdocs 目录下,创建一个新文件夹,命名为 first
  4. 将文件保存到这个 first 文件夹中,并命名为 index.phpindex.php 是一个特殊名称,当浏览器访问一个目录时,服务器默认会寻找并打开这个文件。

保存后,Atom会自动为PHP代码进行语法高亮。

现在,打开你的网页浏览器,在地址栏输入以下URL来访问我们刚创建的程序:
http://localhost/first/index.php

如果一切正常,你将看到一个网页,显示“Hello from a web page”的标题,以及由PHP代码输出的“Hi there”文本。

以下是代码和浏览器结果的图示:


理解PHP代码执行

让我们深入理解一下刚才的程序是如何工作的。在 index.php 文件中,我们混合了HTML和PHP代码。

  • 普通的HTML标签(如 <h1><p>)会被服务器直接发送到浏览器渲染。
  • 当服务器遇到 <?php?> 标签时,它会切换到PHP解释器模式,执行其中的代码。
  • 在PHP代码块中,echo 语句用于向网页输出内容。例如,echo "Hi there\n"; 会将字符串“Hi there”插入到网页中该代码所在的位置。

你可以把PHP代码块想象成一个“内容生成器”。服务器执行代码,将所有 echo 输出的结果组合起来,替换掉原来的 <?php ... ?> 块,最终生成一个完整的HTML页面发送给浏览器。

我们可以添加更多逻辑。修改你的 index.php 文件,加入变量和计算:

<h1>Hello from a web page</h1>
<?php
echo "Hi there\n";
$x = 6 * 7;
echo "The answer is " . $x;
?>
<p>Some HTML</p>

保存文件并刷新浏览器,你将看到输出结果中包含了计算出的答案“The answer is 42”。

配置PHP错误显示

在开发过程中,犯错是不可避免的。一个友好的开发环境应该能清晰地告诉我们错误在哪里。然而,出于安全考虑,MAMP默认关闭了在网页上直接显示PHP错误的功能。这会导致我们在代码出错时,只看到一个笼统的“500服务器错误”,而不知道具体原因。

为了获得详细的错误信息,我们需要修改PHP的配置文件。

以下是配置步骤:

  1. 在MAMP欢迎页面,点击“PHPInfo”链接。
  2. 在打开的PHP信息页面中,找到“Loaded Configuration File”这一行。它显示了当前使用的PHP配置文件的路径,例如 C:\MAMP\bin\php\php7.1.5\php.ini。请记下你的PHP版本号(如7.1.5)。
  3. 使用Atom或任何文本编辑器打开这个 php.ini 文件。
  4. 在文件中搜索 display_errors 设置。
  5. 找到 display_errors = Off 这一行,将其改为 display_errors = On
  6. 同时,确保 display_startup_errors 也设置为 On
  7. 保存 php.ini 文件。

重要提示:修改PHP配置文件后,必须重启Apache服务器才能使更改生效。在MAMP控制面板中,先点击“Stop Servers”,然后再点击“Start Servers”。

重启完成后,刷新PHPInfo页面,确认 display_errorsdisplay_startup_errors 的状态已变为“On”。

现在,让我们故意在代码中制造一个语法错误(例如删除一行代码末尾的分号),然后刷新浏览器页面。此时,你将看到一个详细的错误信息,明确指出错误发生在哪个文件的哪一行,以及错误类型是什么(例如“Parse error: syntax error...”)。

根据这个明确的提示,你可以快速定位并修复代码中的错误。修复后(重新加上分号),刷新页面,程序就会恢复正常运行。

养成在开发初期就开启错误显示的习惯,可以为你节省大量调试时间。

以下是查找配置文件和开启错误显示的图示:



课程总结

本节课中我们一起学习了Web应用开发环境的搭建与初体验。我们完成了在Windows 10上安装MAMP集成环境,验证了Apache、PHP和MySQL服务的正常运行。随后,我们安装了Atom代码编辑器,并在MAMP的 htdocs 目录下创建了第一个PHP程序,理解了HTML与PHP代码混合执行的基本原理。最后,我们完成了开发中至关重要的一步:配置PHP以在浏览器中显示详细错误信息,这为后续的高效调试打下了坚实基础。现在,你的本地开发环境已经准备就绪,可以开始探索更复杂的Web应用程序开发了。

054:在Windows 10上安装XAMPP 🖥️

在本节课中,我们将学习如何在Windows 10操作系统上安装XAMPP。XAMPP是一个集成了Apache、MySQL、PHP和Perl的免费开源软件包,它是搭建本地Web开发环境的理想工具。我们将一步步完成下载、安装、配置以及运行第一个PHP程序的全过程。

下载XAMPP安装程序

首先,我们需要从Apache Friends官方网站获取XAMPP的Windows版本安装程序。

以下是下载步骤:

  1. 访问Apache Friends网站。
  2. 找到适用于Windows的XAMPP版本。
  3. 点击下载链接,开始下载安装程序。

运行安装程序

下载完成后,我们将在默认的下载文件夹中找到安装文件,并启动安装过程。

以下是安装过程中的关键步骤:

  1. 运行下载好的安装程序。
  2. 在安装向导中,选择默认的安装路径 C:\xampp。虽然这个路径可能稍显不便,但它能简化后续操作。
  3. 在组件选择界面,取消勾选不需要的组件,例如Tomcat、Perl和Fake Sendmail。对于基础的Web开发,我们主要需要Apache和MySQL。
  4. 等待安装程序完成所有文件的复制和配置。

启动XAMPP控制面板

安装完成后,我们不会立即启动控制面板。我们将手动找到其位置并启动它,以便你了解如何独立启动。

以下是启动步骤:

  1. 打开文件资源管理器,进入 C:\xampp 目录,这是XAMPP的默认安装位置。
  2. 在该目录中找到 xampp-control.exe 文件并运行它。
  3. 首次启动时,可能会弹出语言选择和安全警告对话框,请选择“是”或允许操作。
  4. 为了方便后续使用,建议右键点击任务栏上的控制面板图标,选择“固定到任务栏”。

启动Apache与MySQL服务

现在,我们通过控制面板来启动Web开发所需的核心服务:Apache(Web服务器)和MySQL(数据库服务器)。

以下是启动服务的步骤:

  1. 在XAMPP控制面板中,找到Apache模块,点击其右侧的“Start”按钮。
  2. 观察控制台输出,确保没有出现红色错误提示。
  3. 以同样的方式,启动MySQL服务。
  4. 当Apache和MySQL旁边的状态指示灯都变为绿色时,表示服务已成功运行。

验证安装与配置PHP

服务启动后,我们可以通过访问本地仪表盘来验证安装是否成功,并检查一项重要的PHP开发配置。

以下是验证和检查步骤:

  1. 打开浏览器,访问 http://localhost。如果看到XAMPP欢迎页面(仪表盘),则说明Apache服务器运行正常。
  2. 在仪表盘页面,点击“PHPInfo”链接,查看详细的PHP配置信息。
  3. 在PHPInfo页面中,搜索 display_errors 配置项。对于开发环境,此选项应设置为 On,以便在页面上显示错误信息,方便调试。
  4. 如果发现 display_errors 为 Off,则需要修改PHP配置文件。你可以通过XAMPP控制面板的Apache模块“Config”按钮,选择“PHP (php.ini)”来编辑配置文件。找到 display_errorsdisplay_startup_errors,将其值改为 On,保存文件后,必须重启Apache服务才能使更改生效。

创建并运行第一个PHP程序

最后,我们将创建一个简单的PHP文件,并将其放置在Apache服务器的文档根目录下,通过浏览器访问来测试整个环境。

以下是创建和测试步骤:

  1. 打开你喜欢的文本编辑器(例如VS Code、Sublime Text或Notepad++)。
  2. 创建一个新文件,输入以下混合了HTML和PHP的代码:
    <!DOCTYPE html>
    <html>
    <head>
        <title>My First PHP</title>
    </head>
    <body>
        <h1>Hello World from HTML</h1>
        <?php
            echo “<p>This is coming from PHP.</p>”;
            $sum = 6 + 4;
            echo “<p>The sum of 6 and 4 is: “ . $sum . “</p>”;
        ?>
    </body>
    </html>
    
  3. 将文件保存到XAMPP的文档根目录。具体路径为:C:\xampp\htdocs\。建议在该目录下为你的项目创建一个新文件夹,例如 first,然后将文件以 index.php 为名保存到 C:\xampp\htdocs\first\ 目录下。
  4. 打开浏览器,访问 http://localhost/first/index.php。如果页面成功显示“Hello World from HTML”以及PHP计算出的结果“The sum of 6 and 4 is: 10”,则表明你的本地PHP开发环境已经完全配置成功。


本节课中我们一起学习了在Windows 10上搭建本地Web开发环境的完整流程。我们从下载XAMPP安装程序开始,逐步完成了安装、启动核心服务(Apache和MySQL)、验证安装并配置关键的PHP开发选项,最后通过创建并运行一个简单的PHP程序,确认了整个环境工作正常。现在,你已经拥有了一个功能完备的本地服务器,可以开始进行PHP编程、数据库操作等Web应用开发学习了。

055:海牙 🇳🇱

在本节课中,我们将回顾一次在荷兰海牙举行的课程附加办公时间活动。活动中,来自世界各地的学生分享了他们学习Python等编程语言的经历与感受。

活动概述

大家好,欢迎来到我们的互联网历史、技术安全与“面向所有人的Python”系列课程的又一次办公时间。我们现在位于荷兰海牙。我想让大家认识一下你们的一些同学。

以下是参与活动的同学们依次进行的自我介绍:

  • S:我是S,我对Python很感兴趣,也是Charles博士的忠实粉丝。
  • We:大家好,我叫We,我刚开始学习Python,对于能学习更多Python知识感到非常兴奋。
  • Bine:嗨,我是Bine,我也对Python感到兴奋。谢谢,Severs先生。
  • Root:我叫Root,Charles博士是最棒的,现在你也是最棒的。
  • Eva:你好,我是Eva,很高兴见到Charles博士。
  • Hera:嗨,我是Hera,超级开心能和Charles博士在这里。
  • Jane:嗨,我是Jane,Charles博士帮助我轻松掌握了Python,这太棒了。
  • Martin:嗨,我是Martin,我正在跟Charles博士和Ilavitu学习Python。
  • Victor:你好,我叫Victor,能和Chuck教授在这里我非常激动。我真心想感谢他和Coursera,因为他们,我现在开始编程了,并且我非常喜欢编程。
  • Catalina:我是Catalina,我真的、真的、真的很喜欢这门课。

活动花絮与感想

上一节我们听到了同学们的热情介绍,本节中我们来看看活动中的一些特别时刻和更多同学的分享。

让我们为Catalina快速鼓掌,感谢她组织了这次活动。谢谢。不,谢谢你。这是第一次有人在Twitter上问我是否会来,然后说“我来安排”,所以……你好。

以下是更多同学的分享:

  • Georgia:我是Georgia,我学习了Charles博士的一些课程,你们也应该这样做。
  • Tim:我和Tim一起学习了Charles博士的一些课程,终于有机会见到他了。
  • Stefan:嗨,我是Stefan,我正在学习Charles博士的Python课程,我非常喜欢。这非常棒,谢谢。
  • Rob:嗨,我叫Rob,我正在学习相关课程,再次见到Charles博士真好。你有一个关于Coursera如何影响你就业的有趣故事。
    • Rob的补充:是的,我在Coursera和edX上学习了很多课程,现在……我终于找到了工作。
    • 回应:恭喜,他们会很高兴听到这个消息。
  • Arriif:你好,我叫Arriif。感谢你促成这次活动,让我看到其他有相同兴趣的人。非常感谢,不客气。
  • Marina:嗨,我是Marina,这门课给了我很多自信,所以谢谢你。
    • 回应:非常欢迎。

活动场景与未来展望

前面我们了解了同学们的参与和收获,最后来看看这次活动的现场情况和未来的计划。

这真是一个非常庞大的团队。这里的天气有点冷,今天是荷兰的春天第一天,我甚至看到一些郁金香开始冒头。但我们都戴着手套,因为我们人太多,室内挤不下。然后我们在这里坐了一个小时才意识到,这里有暖气,但我们没开。所以我们正在研究如何打开暖气。

我想我下次可能见到你们的地方是爱沙尼亚,因为下一次Coursera办公时间将在爱沙尼亚举行。那么,我们到时候见!加油!


本节课中我们一起学习了在海牙办公时间活动中,来自全球的学习者如何分享他们的编程学习旅程、获得的帮助以及建立的自信。活动不仅促进了师生交流,也展现了在线学习社区的活力。我们期待在未来的活动中再次相聚。

056:数据库概述

在本节课中,我们将要学习关系型数据库的基本概念、发展历史以及SQL语言的核心思想。我们将从数据存储的早期挑战开始,逐步了解现代数据库技术的诞生背景和设计哲学。

从顺序存储到随机访问

上一节我们介绍了课程主题,本节中我们来看看数据存储技术是如何演进的。

最初的计算机主要用于计算,例如弹道轨迹和天气预测。这些计算数据都存储在计算机内部。然而,当开始处理像银行业务这样的应用时,数据量变得非常庞大,无法全部存放在计算机内存中。因此,需要一种方法来存储比计算机自身容量更大的数据。

在磁盘驱动器出现之前,我们使用磁带驱动器。磁带是顺序存储介质,这意味着你必须按顺序读取数据,无法直接跳转到特定位置。想象一下,如果你是一家银行,所有账户及其昨日余额都存储在一盘磁带上。今天,人们进行了存取款交易。为了计算今日结束后的新余额,你需要处理所有交易。在20世纪60年代和70年代,银行余额可能每天只更新一次,因为处理所有数据需要很长时间。

以下是当时使用的一种称为“顺序主文件更新”的算法步骤:

  1. 所有账户记录按账号顺序排序存储在磁带上。
  2. 所有交易记录也按账号排序。
  3. 编写一个程序,同时读取旧主文件磁带和交易磁带。
  4. 程序比较当前读取的账号。如果交易账号大于当前主文件账号,则直接将旧主文件记录复制到新磁带。
  5. 当交易账号与主文件账号匹配时,程序应用交易(如加减金额),计算出新余额,并将更新后的记录写入新磁带。
  6. 处理完所有记录后,就得到了包含所有新余额的新磁带。这盘新磁带将在下一天成为旧的“主文件”磁带。

这个过程可能需要数小时,因为它依赖于物理移动磁带。这个算法解决的核心问题是:我们需要比计算机内存更大、更持久的存储。

磁盘驱动器与索引的诞生

上一节我们了解了顺序处理的挑战,本节中我们来看看随机访问存储技术如何改变了游戏规则。

最终,我们迎来了磁盘驱动器。磁盘驱动器的关键特性是它一直在旋转,并且有一个磁头可以移动到不同位置读取数据。这意味着你不再需要顺序读取所有数据。如果你需要读取磁盘上某个特定位置的数据,只需将磁头移动到正确的位置,然后等待数据旋转到磁头下方即可读取。

磁盘的转速通常为每分钟7200转,平均访问时间在毫秒级别。这使得数据库的概念得以诞生:现在我们不必等到晚上10点才更新数据;我们可以将账户记录存储在磁盘的不同位置,并且每秒可以读写成千上万次。

但问题依然存在:如何快速找到特定的记录?如果仍然需要读取所有数据,可能仍需一小时。解决方案是索引

索引就像一个目录或面包屑路径。你可以创建一个小的“目录”,记录每条数据在磁盘上的位置。例如:

  • 记录1 位于 位置A
  • 记录2 位于 位置B
  • 记录3 位于 位置C

当需要读取记录5时,你无需扫描所有数据。只需查询索引,找到记录5的位置,然后直接跳转到那里读取。索引本身通常比被索引的数据小得多。这一切都是为了最大限度地利用这项新技术,使得查询和更新银行余额能在秒级而非天级完成。

关系型数据库:理论与标准的形成

上一节我们看到了索引如何提升效率,本节中我们来看看关系型数据库这一标准是如何在竞争中诞生的。

关系型数据库是一种技术。当我们思考如何最佳地利用随机访问存储时,计算机科学家们提出了各种想法,如图表模型、网状数据库等。在20世纪60年代和70年代,关于最佳方式的争论催生了一个标准。

有趣的是,关系型数据库最初是一个非常理论化、看似不实用的概念。它建立在强大的数学理论之上。早期的关系型数据库性能不佳,但随着它们不断改进,其魔力逐渐显现。最终,我们得到了一种强大而优雅的数据管理方式。

SQL:与数据库对话的语言

上一节我们介绍了关系型数据库的理论背景,本节中我们来看看我们如何与它进行交互。

我们通过一种语言来操作关系型数据库,而不是直接处理硬盘上的数据。这种语言面向的是一个非常复杂且强大的软件——数据库服务器(如MySQL)。服务器知道所有数据的存储位置和索引。我们最终得到了一种极其简单、优美、优雅的方式来表达:“这是我想要的数据。你是个天才,去想办法以最快的速度获取并返回给我。” 这就是 SQL

SQL 是结构化查询语言。它的诞生有一段有趣的历史。美国国家标准与技术研究院的Elizabeth Fong在视频访谈中解释道,当时各家数据库供应商争论不休,都希望政府采用自己的技术标准。美国政府作为大量技术的采购方,并没有偏袒任何一方,而是要求所有供应商必须达成一个统一的通信协议标准,否则将不采购任何一家的产品。正是在这样的背景下,SQL 作为行业共识诞生了。它抽象了底层复杂的软件和存储,提供了一个统一的接口。

数据库的核心操作:CRUD

上一节我们了解了SQL的由来,本节中我们来看看用SQL能完成哪些基本操作。

数据库的基本操作可以概括为四项:创建、读取、更新、删除。在SQL中,它们有对应的命令:

  • Create -> INSERT (插入)
  • Read -> SELECT (选择)
  • Update -> UPDATE (更新)
  • Delete -> DELETE (删除)

SQL 的设计理念是让这些最常用、最基础的操作变得超级简单。你只需要告诉数据库“做什么”(例如,删除那一行),而不需要关心“如何做”或数据存储在哪里。

行、列、表与关系、元组、属性

上一节我们介绍了CRUD操作,本节中我们来看看描述数据库结构的两种视角。

由于SQL有深厚的数学理论背景,你会看到两种描述数据库的方式:

  1. 程序员/通俗视角:我们谈论。这就像电子表格:每个像一个工作表标签,里面有(记录)和(字段)。
  2. 数学家/理论视角:他们谈论关系元组属性。本质上,关系对应元组对应属性对应

虽然我在调侃数学家,但正是他们的理论让数据库运行得如此高效。所以,如果你在文档中看到“关系”、“元组”这些词,不要觉得困惑,它们只是“表”、“行”、“列”更学术的说法。

模式:数据的蓝图

上一节我们对比了两种术语体系,本节中我们来看看如何精确地定义数据的结构。

在电子表格中,我们通常用第一行来定义每一列的含义(例如,“标题”、“评分”),这行本身不是真实的数据,而是关于数据的元数据

在数据库中,我们对此更加严格和精确,称之为模式。我们会用非常复杂的语句来定义每一列:它是什么类型的数据(整数、文本、日期等)、最大长度是多少、允许什么字符集等等。例如,我们可以定义“评分”列是一个有符号整数,且值不超过20亿。虽然细节更多,但其核心思想与电子表格的第一行作为元数据是相似的。

常见的数据库系统

上一节我们了解了模式的概念,本节中我们来看看实践中常用的几种数据库系统。

本课程将使用 MySQL 数据库服务器。它是一款非常流行的开源数据库,广泛应用于Web开发。当然,它并非唯一的选择:

  • Oracle:大型商业企业级数据库。
  • SQL Server:微软的数据库产品。
  • PostgreSQL:另一款非常流行的开源SQL数据库。
  • SQLite:轻量级数据库,在之前的Python课程中可能接触过。

对于构建Web应用,我们选择切换到MySQL。

总结与展望

本节课中我们一起学习了数据库的概述。我们从数据存储的历史挑战讲起,看到了从顺序磁带存储到随机访问磁盘驱动的飞跃,以及索引技术的引入如何极大提升了数据检索效率。我们探讨了关系型数据库作为一项标准技术的形成过程,并认识了SQL这一优雅的数据库操作语言,了解了其CRUD核心操作。最后,我们区分了描述数据库结构的两种术语体系,引入了“模式”的概念,并列举了常见的数据库系统。

这为我们提供了SQL的历史和背景。接下来,我们将开始实际输入一些SQL语句,进行真正的操作。

057:基本SQL操作 🗄️

在本节课中,我们将学习SQL(结构化查询语言)的基础知识。SQL是用于与数据库服务器通信的语言,它允许我们创建、读取、更新和删除数据。我们将从理解请求-响应周期中SQL的角色开始,然后学习如何通过命令行和图形化工具(如phpMyAdmin)与数据库交互,并最终掌握执行基本CRUD操作的核心SQL命令。

请求-响应周期中的SQL

上一节我们介绍了Web应用的基本架构,本节中我们来看看SQL在其中扮演的具体角色。

在典型的Web请求-响应周期中,浏览器向Web服务器发送HTTP请求。服务器可能运行PHP代码,而PHP代码需要与数据库服务器通信。这时,SQL就作为通信语言被发送到数据库服务器。数据库服务器处理SQL指令,找到所需数据,并将其返回给PHP。PHP处理这些数据并生成HTML,最终作为响应发送回浏览器,由浏览器解析并呈现给用户。

对于本讲座,我们的重点是学习在网络中来回流动的SQL语言。通常,浏览器、Web服务器和数据库服务器可能运行在三台独立的机器上,但在开发环境中,它们可以共存于一台机器。

数据库服务器与客户端

我们即将使用的数据库服务器软件(如MySQL)功能强大且高度优化。我们从不直接操作其底层数据文件,而是通过发送SQL命令来指挥它工作。这个服务器可以同时处理来自多个客户端的请求。

在开发或生产环境中,开发者通常不直接操作数据库。但为了学习,我们将扮演拥有全部权限的数据库管理员角色。

设置开发环境

在开始之前,你需要安装一个集成了Web服务器、PHP和MySQL的开发环境,例如XAMPP(Windows)、MAMP(Mac)或LAMP(Linux)。这能为你节省大量配置时间。

与数据库交互的两种方式

以下是两种与MySQL数据库服务器交互的主要方式。

命令行界面

命令行是与服务器(尤其是Linux服务器)交互的重要技能。虽然本课程不会深入讲解,但了解其基本用法很有帮助。你可以通过终端登录到MySQL服务器并执行命令。

phpMyAdmin图形界面

对于软件开发,我们更常使用phpMyAdmin这类图形化管理工具。它是一个被广泛用于生产环境数据库管理的Web应用程序,功能强大且实用。我们将主要使用它来学习SQL。

重要提示:无论是命令行还是phpMyAdmin,它们都只是客户端。它们连接到同一个数据库服务器并发送SQL命令。在一个客户端所做的更改(如删除数据库)会立即反映在另一个客户端上,这演示了多个客户端可以同时与一个数据库服务器交互。

创建数据库和表

现在,我们开始使用SQL进行实际操作。首先需要创建一个数据库,然后在其中创建数据表。

创建数据库

我们使用 CREATE DATABASE 语句来创建一个新数据库。指定字符集(如 utf8)是一个好习惯,它能确保数据库支持非拉丁字符(如中文、希腊文)。

CREATE DATABASE people DEFAULT CHARACTER SET utf8;

创建表

数据库创建后,我们使用 CREATE TABLE 语句在其中创建表。这相当于定义电子表格的列(字段)。我们需要为每个字段指定精确的数据类型和约束。

例如,创建一个 users 表,包含 nameemail 两个字段,它们都是最大长度为128字符的变长字符串(VARCHAR)。

CREATE TABLE users (
    name VARCHAR(128),
    email VARCHAR(128)
);

定义字段长度(如128)是与数据库服务器建立的一种“契约”,它有助于优化存储效率。服务器知道无需为超过此长度的数据预留空间。

查看表结构

创建表后,可以使用 DESCRIBE 命令查看其结构。

DESCRIBE users;

此命令会列出表中的所有字段及其数据类型、是否允许为空等属性。

基本CRUD操作

CRUD代表创建(Create)、读取(Read)、更新(Update)和删除(Delete),这是操作数据的四种基本方式。我们已经完成了“创建”(建表),接下来学习其余三种。

插入数据(Create)

使用 INSERT INTO 语句向表中添加新行。

INSERT INTO users (name, email) VALUES ('Chuck', 'csev@umich.edu');

可以一次性执行多条插入语句,只需用分号分隔。

删除数据(Delete)

使用 DELETE FROM 语句从表中删除行。通常需要结合 WHERE 子句来指定删除哪些行,否则将删除表中的所有数据。

DELETE FROM users WHERE email = 'ted@umich.edu';

WHERE 子句就像一个循环中的 if 条件,它让操作只应用于满足条件的记录。

更新数据(Update)

使用 UPDATE 语句修改表中现有的数据。同样,需要使用 SET 指定要更改的列和新值,并用 WHERE 子句限定范围。

UPDATE users SET name = 'Charles' WHERE email = 'csev@umich.edu';

查询数据(Read)

使用 SELECT 语句从表中读取数据,这是最常用的操作。

  • 查询所有列和所有行
    SELECT * FROM users;
    
    * 是通配符,代表所有列。

  • 带条件的查询
    SELECT * FROM users WHERE email = 'csev@umich.edu';
    

  • 查询不存在的记录:如果 WHERE 条件没有匹配任何行,查询将返回空结果集,这本身并不是错误。

高级查询功能

SELECT 语句非常灵活,支持许多用于处理和筛选数据的功能。

结果排序

使用 ORDER BY 子句可以按指定字段对结果进行排序。

SELECT * FROM users ORDER BY email;

模糊查询

使用 LIKE 操作符和通配符 % 进行模式匹配。% 代表任意数量的任意字符。

SELECT * FROM users WHERE name LIKE '%e%';

这条语句会查找 name 字段中包含字母 ‘e’ 的所有记录。

计数

使用 COUNT() 函数可以获取匹配的行数,而不返回实际数据。

SELECT COUNT(*) FROM users;

这将返回 users 表中的总记录数。

限制结果数量

使用 LIMIT 子句可以限制返回的行数,常用于分页。

SELECT * FROM users LIMIT 10;

总结

本节课中我们一起学习了SQL的基础知识。我们了解了SQL在Web应用架构中的位置,学会了通过命令行和phpMyAdmin与MySQL数据库服务器交互。我们掌握了执行基本CRUD操作的核心SQL语句:

  • 创建CREATE DATABASE, CREATE TABLE, INSERT INTO
  • 读取SELECT (结合 WHERE, ORDER BY, LIKE, COUNT, LIMIT
  • 更新UPDATE
  • 删除DELETE FROM

这些操作构成了SQL的一半核心内容。它们让你能够在一个数据表内高效地管理数据。在接下来的课程中,我们将探索如何建立多个表之间的关系,并通过连接(JOIN)等操作在这些表之间进行更复杂和强大的数据查询,这才是数据库真正发挥威力的地方。

058:SQL数据类型 📊

在本节课中,我们将要学习SQL中用于定义数据表列的各种数据类型。理解这些类型是设计高效、准确数据库的基础。

概述

我们将探讨文本字段、二进制数据字段、数值字段、自动增量字段以及其他类型的字段。了解每种类型的特点和适用场景,能帮助我们为数据选择最合适的存储方式。


文本字段:CHAR与VARCHAR 📝

上一节我们介绍了数据表的基本结构,本节中我们来看看如何描述构成表的列。首先,最常见的字段类型是字符字段,即CHARVARCHAR

这两种类型都支持字符集,这意味着它们可以存储拉丁字符、亚洲字符、俄语、波斯语等各种文字。VARCHAR是可变长度的字符字段。当你指定VARCHAR(512)时,你告诉数据库:这个字段可能长达512个字符,但也可能只有4个字符。数据库会采用一种高效的方式来存储长度在4到512之间的字符串。

如果你使用CHAR类型,例如CHAR(20),你是在声明这个字段几乎总是20个字符长。对于恰好是20个字符的数据,它的存储效率很高;但如果数据只有5个字符,存储可能就不那么高效了。

以下是选择建议:

  • 如果字段长度变化较大(例如在5到500个字符之间),应使用VARCHAR
  • 如果字段长度基本固定(例如总是20个字符),应使用CHAR

你为这些类型指定的长度是绝对最大值。例如,VARCHAR(128)就是一个约束,数据库会强制要求该列的数据不能超过128个字符。


大文本与二进制字段 🔤

除了CHARVARCHAR,还有一些用于存储大量文本的字段类型,统称为TEXT字段。

这些字段的关键特性是,它们不能像普通字符字段那样被有效地建立索引或排序。如果你需要存储博客文章或Facebook评论这类长文本,就会用到它们。它们都有最大长度限制:

  • TEXT:最多约65,000个字符,适合小型博客文章。
  • MEDIUMTEXT:容量更大。
  • LONGTEXT:最多可存储4GB的字符数据。

由于这些字段也支持字符集,因此65,000个拉丁字符与65,000个亚洲字符所占用的“字符数”是相同的,数据库能够处理所有这些字符。

接下来,我们看看很少使用的字节类型。在拉丁字符集(如ASCII)中,一个字符是8位(1字节)。但在Unicode中,一个字符可能长达32位(4字节)。而一个字节(BYTE)严格等于8位。

如果你存储的是二进制数据,并且确切知道其值范围在0到255之间(即一个字节),你可以使用BINARYVARBINARY类型来定义字段。这种类型不常用,你通常不会为它建立索引或排序,因为它对其内容“一无所知”。但有时,例如从传感器读取的原始0和1数据流,就适合用这种类型存储。

你还可以在数据库中存储图像、PDF或视频等文件。数据库非常擅长处理这类数据,它们被称为BLOB(二进制大对象)。BLOB也有不同的大小类型,如TINYBLOBBLOBMEDIUMBLOBLONGBLOB

数据库存储这类数据效果很好,但问题在于,时间长了会显著拖慢数据库的备份速度。因此,常见的做法是:将中等大小的数据(如个人资料照片)存入数据库,以便与其他数据统一管理;而对于视频或大型文档,则倾向于以普通文件的形式存储在服务器上,然后在数据库中记录其文件路径。除了可能导致备份文件过大之外,在数据库中存储二进制对象本身并无不妥。


数值字段:整数与浮点数 🔢

整数类型有不同的大小。你可能会问为什么需要多种尺寸。答案是:为了效率。如果我们要存储数百万条记录,而某个整数字段的值范围只在1到15之间,我们就不需要为它分配与存储20亿数量级数字相同的空间。

因此,我们有不同大小的整数:

  • TINYINT:非常小的整数。
  • SMALLINT:小型整数。
  • INT:标准整数,范围大约是0到20亿(32位整数)。
  • BIGINT:更大的整数,占用更多空间。

整数类型,特别是INT,优点很多:排序速度快、占用存储空间相对少、易于比较和排序。它们常被用于建立索引,因此在很多场景下,我们倾向于使用整数而非字符串来表示信息。

浮点数在计算机中的工作方式与所有编程语言一致。例如98.63.146.02e23(科学计数法),你一直在使用的就是浮点数。关于浮点数的关键点是:像1.7这样的数字无法被完美精确地表示。浮点数是对实数的近似。

以下是浮点数的类型:

  • FLOAT:较小的浮点数,32位,范围可达10的38次方,但无论数字大小,精度只有约7位有效数字
  • DOUBLE:双精度浮点数,提供更高的精度(约14位有效数字)。

对于温度、速度等测量值,浮点数通常完全够用,因为你能测量的精度很难超过7位有效数字。对于大多数科学计算,FLOATDOUBLE类型都很合适。但是,不要用浮点数来存储货币金额,因为像$10.25这样的金额无法被完美表示,会导致计算误差。实际上,存储金额通常使用按比例缩放的整数(例如,以分为单位存储)。


日期与时间字段 ⏰

SQL提供了多种时间和日期格式。

有一种类型叫TIMESTAMP(时间戳)。它存储的是自1970年1月1日(UTC)以来的秒数,并以32位整数形式保存。因为它本质上是一个整数,所以排序非常高效和快速。

但它有几个问题:

  1. 它只能精确到秒,无法表示毫秒或更小单位。
  2. 它有一个绝对的长度限制。时间戳的概念在1970年代被提出,从1970年1月开始计算秒数。这意味着在2037年,Unix系统和数据库系统将遇到一个类似“千年虫”的问题,因为届时32位整数将溢出(大约在2038年1月19日)。不过在此之前它都工作良好,未来我们可以将其升级为64位整数,那样就能用到太阳毁灭之日了,所以不必过于担心。

因此,对于仅需要秒级精度、记录行创建或更新时间的情况,我们倾向于使用TIMESTAMP

DATETIME类型则更为通用,它占用空间稍大,可以表示任何年份(例如1300年),因为它的存储方式不依赖于1970年这个基准点。它可以表示任何符合 YYYY-MM-DD HH:MM:SS 格式的日期和时间,并且能安全度过2037年。DATE类型则只包含年月日部分,TIME类型只包含时分秒部分。

MySQL有一个内置函数可以获取当前日期时间。你可以在插入数据时这样使用:INSERT ... VALUES (..., NOW(), ...)NOW()函数会返回数据库服务器当前理解的日期时间。


总结

本节课中我们一起学习了SQL的核心数据类型。

  • 我们了解了文本字段(CHARVARCHARTEXT)如何存储字符串及其在字符集和长度上的特点。
  • 探讨了二进制字段(BINARYBLOB)的用途,以及存储大型二进制对象的利弊。
  • 分析了数值字段,包括不同大小的整数(TINYINTINTBIGINT)和近似值的浮点数(FLOATDOUBLE),并特别指出货币存储应避免使用浮点数。
  • 最后,我们比较了日期时间类型,如高效的TIMESTAMP和更通用的DATETIMEDATE

理解这些数据类型是优化数据库存储、确保数据完整性和提升查询性能的关键一步。在下一节中,我们将讨论如何为每一列的数据指定更详细的使用意图和约束。

059:数据库键与索引 🔑

在本节课中,我们将要学习数据库设计中两个至关重要的概念:键(Keys)和索引(Indexes)。我们将了解如何通过定义主键、外键以及创建索引来优化数据的存储和查询效率,为后续学习多表连接打下基础。

定义列与使用意图

上一节我们讨论了如何定义列的数据类型(如整数、字符、图像等)。本节中,我们来看看如何进一步描述这些列的用途,而不仅仅是它们存储什么内容。

创建表和定义列的过程,实际上是在告诉数据库我们打算如何使用这些数据。现在,我们将对此进行更详细的说明。

主键与自动递增

以下是一个创建新用户表的SQL语句示例。我们首先关注 user_id 列。

CREATE TABLE users (
    user_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(128),
    email VARCHAR(128),
    PRIMARY KEY (user_id),
    INDEX (email)
);

在后续课程中,当我们需要连接两个表时,会用到一种机制:用一个表中的某一行指向另一个表中的特定行。例如,表A有第1、2、3、4行,表B中的一行可能需要指向表A的第4行。我们通过在表B中存入数字“4”来实现。这涉及到主键和外键的概念,我们稍后会详细讨论。

这种机制的核心是:我们希望在向表中插入行时,只需使用某个数字作为标识,而不必关心这个数字具体是什么。实际上,我们可以让数据库自动生成这个数字。这正是 AUTO_INCREMENT 的作用。

AUTO_INCREMENT 是一种告诉数据库的方法:我们将创建一个整数列,如果我们不提供值,请自动为我们生成。让我们分析 user_id 列的定义:

  • INTINTEGER 的缩写,表示整数。
  • UNSIGNED 表示只允许正数。
  • NOT NULL 表示该列是必填项。
  • AUTO_INCREMENT 表示如果我们不提供值,请自动生成。其生成方式是从1开始递增(1, 2, 3, 4...)。当我们插入一行时,数据库会处理这一切,它会知道“下一个是5”。这样,我们的程序就不需要自己跟踪这些数字。

nameemail 字段被定义为 VARCHAR(128),这与我们之前定义列的方式相同。

索引:数据的快捷方式

接下来,我们要通过两条语句告诉数据库一些关于数据以及我们将如何使用它们的信息。这涉及到之前提到的“索引”——它们就像是磁盘上不同位置的快捷方式。

  • PRIMARY KEY (user_id):我们声明 user_id 列将作为主键使用。这意味着我们会频繁使用它进行查询,数据库需要能够非常快速地查找它。因此,数据库会在磁盘上额外存储一些“面包屑”(索引结构),以便实现超快速访问。user_id 是一个数字,我们希望为其建立一个非常高效的索引,以实现快速查找。
  • INDEX (email):这个索引声明表示,我们将会大量使用 WHERE 子句通过 email 进行查询(例如 WHERE email = '...'),甚至可能使用 LIKE 子句,或者经常按 email 排序。这个声明并不改变我们存储的数据内容,而是改变了我们对数据的使用预期。

这些声明既是规则(例如,主键必须是唯一的),也是给数据库的提示。例如,INDEX (email) 是在说:“嘿,数据库,虽然我们现在只是存储数据,但以后我会经常通过电子邮件地址来查询这张表。如果你要创建索引,请为 email 列创建一个。我现在只是给你一个提示,我们以后会大量使用带 emailWHERE 子句进行字符串查找,所以请提高效率。” 但你并没有明确告诉数据库具体要做什么,只是给出了使用意图的提示。

自动递增的实际效果

现在让我们执行这个 CREATE TABLE 语句来创建表。创建成功后,其结构与其他表类似,但我们注意到 user_id 旁边有一个小钥匙图标,这表示它是主键,并且数据库知道它是自动递增的。

如果我们现在执行插入操作,会看到自动递增的效果。以下是插入语句:

INSERT INTO users (name, email) VALUES ('Chuck', 'csev@umich.edu');
INSERT INTO users (name, email) VALUES ('Sally', 'sally@umich.edu');
INSERT INTO users (name, email) VALUES ('Somesh', 'somesh@umich.edu');
INSERT INTO users (name, email) VALUES ('Caitlin', 'cait@umich.edu');

请注意,在这些插入语句中,我没有指定 user_id 的值。这就是自动递增的作用:如果我不指定,数据库会自动为我提供。同时,它必须保证唯一性。执行后查看数据,数据库自动为我们分配了 user_id 值(1, 2, 3, 4)。稍后在使用PHP时,我们可以获取这个自动生成的数字。当我们在课程第二部分讨论多表连接时,这个功能将变得至关重要。目前,自动递增只是一种自动设置列值的方式。

数据库还有许多内置函数,例如我们之前提到的 NOW()。你不需要在PHP中知道具体日期,只需在插入时对 DATETIME 类型的字段使用 NOW() 即可。还有很多很酷的字符串函数等。除了 NOW(),我不会教太多这些函数,因为你可以在Stack Overflow等地方轻松找到,例如搜索“MySQL函数获取字符串前三个字符”,然后复制粘贴即可。

深入理解索引

正如我提到的,索引非常重要。如果你没有为某列创建索引,然后基于该列进行查询,数据库可能不得不扫描整个表。如果表中有数十万条记录,这会非常慢。

索引本质上是一个目录或快捷方式。数据存储在磁盘的不同位置,读取时需要等待磁盘转动。而索引是一小块数据,它指向各个记录的位置。读取索引(数据量较小)后,数据库可以直接跳转到目标记录(例如第4条),而不必顺序读取第1、2、3条。这是一种极大的简化,实际上有大量关于如何优化索引的博士论文,涉及单磁盘、多磁盘、SSD等各种场景。数据库工作的美妙之处在于,成千上万的计算机科学博士论文致力于让这些软件变得更快,而你只需要说 SELECT,一切就都工作了。

主要有几种不同类型的索引:哈希(Hash)索引和树(Tree)索引,后者通常指B树(B-Tree)。

  • 哈希索引 通常用于主键的精确匹配查找。
  • B树索引 适用于排序和前缀匹配(例如查找以“CH”开头的名字)。

在我之前展示的例子中,我甚至没有指定索引类型。主键索引(在整数字段上)占用空间小,用于精确匹配且不允许重复,查找速度极快。而为 email 列创建的索引,无论是哈希还是B树,都适用于前缀查找,其中B树最适合前缀查找。很多人建议甚至不要指定使用哈希还是B树,让数据库根据数据自动调整。

B树索引的工作原理

让我们换个方式理解索引。假设数据分散存储在磁盘各处,顺序扫描可能需要很长时间。你可以创建一个索引,它是一小块数据,包含指向实际数据记录的指针。这样你就可以跳过大量数据,直接访问目标。

这是一种简化的B树示意图。数据块基本上是有序的。索引中存储了一些关键值和指向其他数据块的指针。例如,所有小于7的值存储在A块,7到16之间的值在B块,大于16的在C块。如果要查找9,数据库先读取索引,发现9在7到16之间,于是读取B块,然后在B块中找到9。这只需要三次读取,而不是顺序扫描可能需要的很多次。

当你向这样的结构中插入数据时(比如插入4),数据库需要将其放入正确的块(A块)。如果A块满了,它可能会进行“分裂”(Split),将A块分成两部分,并更新索引中的指针。虽然你不需要了解这些具体细节(这是计算机科学家解决的问题),但你需要知道B树适用于需要排序和前缀匹配的数据,尤其是字符串。

哈希索引的工作原理

哈希索引理解起来稍微复杂一些。它使用一种称为哈希函数的巧妙数学计算。Python中字典的快速查找正是基于此。

哈希函数根据查找键(如电子邮件地址或姓名)执行一个简单计算,生成一个数字(例如0到15之间)。根据这个计算结果,数据被存储到对应的“槽”(Slot)中。这个过程不需要访问磁盘,只需计算哈希函数,然后直接定位。哈希的问题在于,不同的键可能计算出相同的哈希值(哈希冲突),这时就需要处理溢出或重新组织。但重组之后,查找会非常快,因为计算哈希后只需很少的磁盘读取。

索引的创建与调整

哈希和B树是两种不同的索引类型。正如我们开始时看到的,我们可以在创建表时指定希望在哪些列上建立什么类型的索引。这只是一个提示,并不改变你使用的SQL语法。

在我们的例子中,我们暗示 user_id 上将建立一个基于整数的、超快的哈希类索引,而 email 上将建立一个基于字符串的、用于排序和前缀查找的B树类索引。

最酷的是,如果你忘记添加这些索引,后来发现应用程序运行缓慢,你随时可以使用SQL命令来后期添加索引。甚至有些工具可以监控你的数据库,并建议“你可能应该在 email 列上加一个B树索引”。正如我提到的,有些同事建议永远不要指定索引类型,让数据库自己决定最佳方案。关于使用B树还是哈希,如果是字符串你可能想要B树,否则可能想要哈希。但你也可以让数据库决定。我们真正要表达的是:“我将会频繁使用 WHERE 子句通过这个 email 列进行查找,所以,数据库先生,请尽可能高效地存储它。”

总结

本节课中我们一起快速浏览了SQL的一个重要部分。我们学习了如何设置数据的结构、定义数据规则,并给出如何使用数据的提示。创建、读取、更新和删除(CRUD)操作并不难。

接下来,我们将讨论如何创建多个表并将它们连接起来。这将是实现真正性能提升的关键。目前,我们找到了存储数据的规范方式,虽然索引很酷,但连接(Joins)才是真正强大的功能,我们将在下一节讨论它。

060:Liz Fong谈SQL标准化 💬

在本节课中,我们将跟随Liz Fong的分享,了解SQL(结构化查询语言)标准化的背景、核心概念及其重要性。标准化确保了不同数据库产品之间的互操作性,是构建可移植应用程序的关键。

标准化的背景与需求

随着数据库技术的发展,市场上出现了多种产品。用户在选择时面临困惑:是购买IBM、Oracle的产品,还是选择更便宜的方案?这种决策需求开始显现。

同时,在数据库管理系统(DBMS)之上可以构建的应用种类繁多。为了确保应用程序能在不同平台上运行,就需要一种标准。

数据库模型的演进

我们最初使用文件系统,文件是层次结构的。后来出现了IBM信息管理系统(IMSS),它是一种树状结构。当时业界争论的焦点是数据应采用树状、网状还是平面文件结构。随后,关于数据是否应包含自描述标签(后来称为元数据,现在常称为模式)的讨论仍在继续。

数据库系统研究组与标准制定

数据库系统研究组提出了一个参考模型或规范,定义了数据库管理系统应具备的最小功能集。为了成为一个合格的DBMS,它必须能够存储数据、检索数据、修改数据、组织数据、删除和操作数据。这成为了一项规范。

在此期间,一个名为X3 H2的NC小组(现称为Insight,隶属于美国国家标准组织)诞生了。Don Deutsch和Ln Gallagher等人都参与其中。该小组被称为数据管理语言组。

标准化的核心:接口与语言

要标准化任何事物,关键在于找到需要统一的接口。例如,灯泡可以有红色、白色等多种类型,但需要标准化的是通信接口,即双方都能理解的共同词汇或领域。

因此,软件系统标准化的核心并非其功能本身,而是其语言。对于数据库而言,这个语言就是查询语言。

关系型数据库与SQL的诞生

当时,IBM的Codd提出了关系型数据库的概念。他谈论规范化,开始使用“平面文件”并称之为“表”,这是一个易于理解的概念。

为了从表中检索数据,你可以使用类似 SELECT column_name FROM table_name 的语句,例如 SELECT name FROM employee。就这样,一种简单的查询语言诞生了,这就是SQL。

符合性测试与市场应用

测试是采用标准时非常重要的一个环节。你需要认证产品符合特定版本的ISO标准(如JTC1)。否则,你的应用程序可能无法正常工作。

假设你构建了一个学生课程记录系统。无论底层是Oracle、Sybase还是Microsoft SQL Server,你都希望应用程序能正常运行。这正是市场,也就是用户所期望的。

在采购时,用户会要求产品“符合SQL标准”。因此,供应商必须提供符合性测试证书。有经过认证的实验室(如Nav Labs)提供已验证的产品列表。采购者可以从该列表中购买已验证符合标准的产品。这是一个严格的要求,因为付钱的是用户。

标准化时机的重要性

时机决定一切。标准化不能太早,否则会扼杀创新,因为人们会说市场已经定型,即使现有标准不够好,他们也不会进入这个领域。标准化也不能太晚,否则会错失良机,市场上会出现太多变体,选择过于繁杂。

当然,SQL是标准化成功案例之一,它推动了整个行业的发展。

总结

本节课我们一起学习了SQL标准化的历程。我们了解到,标准化源于市场对互操作性的需求,其核心是统一查询语言(SQL)而非具体功能实现。标准化过程需要平衡时机,既不能过早扼杀创新,也不能过晚导致市场分裂。SQL的成功标准化,使得应用程序能够跨不同的数据库平台运行,极大地促进了软件生态的发展。

061:佛罗里达州奥兰多

在本节课中,我们将一起回顾一次在佛罗里达州奥兰多举行的面对面办公时间。本次会议中,课程讲师Chuck与部分学生进行了交流,学生们分享了他们的学习背景和参与课程的体验。


大家好,我是Chuck。我们正在佛罗里达州奥兰多进行又一次的面对面办公时间。

我希望大家认识一下班上的一些同学。

请介绍一下你的名字,并可以随意向班级说些简短的话。

大家好,我是Ann。我已经修完了所有“Python for everyone”课程,我非常享受这个过程,也很喜欢Chuck博士的教学风格,所以我学得很开心。谢谢Ann。

我是Pete。我50年前从密歇根大学毕业,Go Blue!我想学习一门面向对象的语言,而不是我之前学过的Fortran和汇编语言。那么是你要求讲面向对象内容的吗?不是你?好吧,有人提了,所以我就讲了。大家好,我是Amy,这是我第四次上Chuck博士的课。

大家好,我是Em。我刚开始上Chuck博士的课程,能亲自见到他真是太棒了,你们也应该试试。大家好,我是Tom,这是我第一门真正坚持学习了超过两天的Coursera课程。

Tom,做得好。大家好,我是Rahul,这是我第三次上Chuck博士的课,今天非常有趣。请继续。

大家好,我是Roy,这是我的第二门Python课程,我非常喜欢。大家好,我是Sean,这是我的第三门Python课程,我真的很享受。

好的,各位,Noah。我真希望你们都能来到奥兰多。祝大家学习愉快。

大家好,我是Tamar。我是Python专项课程的导师之一。

我们所有人都欠你一份巨大的感激之情,因为你是一名志愿者。

你每周花多少时间在这门课程上?可能比你想的要多,大概40到45小时。

是志愿者。让我们大家鼓掌表示感谢。非常感谢你,Tamar。

是的,如果没有导师和志愿者们日复一日地付出,让课程保持活力,这些课程绝对无法顺利进行。因此,我向Tamar和每一位为每门Coursera课程倾注如此多心血的导师表示感谢。

下次见,也许在南非。干杯。


本节课中,我们一起回顾了在奥兰多举行的附加办公时间。我们看到了来自不同背景的学生们分享他们的学习旅程,并特别感谢了志愿者导师们为课程成功所做出的不可或缺的贡献。这种社区和支持是学习体验的重要组成部分。

062:关系型数据库设计 🗄️

在本节课中,我们将要学习关系型数据库设计的核心概念。我们将从理解如何将应用需求转化为高效的数据模型开始,探讨如何识别和分离数据,以及如何建立表与表之间的关系。

概述

上一讲我们学习了如何在单张表中进行操作。本节中,我们来看看如何连接多张表,这正是“关系型”数据库的核心所在。我们将学习复杂的数据模型和关系,以及如何以允许数据库进行高效、高性能数据检索的方式来表示这些数据。这是我们将理论付诸实践,真正学习如何构建数据库的关键环节。

从这里开始,我们将从“如何编写SQL语句”转向“如何设计数据库”。

数据库设计之美

我个人学习数据库的时间较晚,是在20世纪80年代的研究生阶段。那时的关系型数据库并不完善,我一度认为它们很笨拙,宁愿自己写循环来读取数据。直到大约2000年,我首次以专业身份参与一个Web应用项目,需要设计数据库时,我才真正开始学习。我很快掌握了它,并发现这是一件非常美妙的事情。

你可以绘制一张图,画出这些线条,就像为你的应用程序创建一个数据网络。数据库设计的基础很容易理解,我相信在接下来的几节课后,你就能很好地掌握它们。基础部分易于理解,而高级技巧通常是在你遇到实际问题时,通过请教他人或查阅Stack Overflow等平台来学习的。

这是一种优美的艺术形式。掌握基础后,你将能够参与数据库设计,构建中等复杂度的数据库而不犯错误。当然,总会有更多技巧可以学习。

数据模型实例

这是一张我构建的数据模型图,它与我们本节课要讨论的内容类似。这是一个学习管理系统,实际上是我为本课程自动评分工具使用的软件的数据模型。

图中的每个小方框代表一张表,每条线代表表之间的关系。我们稍后会理解这些符号(如“多”、“一”等)的含义。我们正在创建的是一个网络图,而“关系型”的精髓以及使其速度极快的关键,就在于你对这些关系线的思考。它们看似只是技术细节,但这就是关系型数据库的魔力所在。

实际上,几小时后,你将能理解这张图上的几乎所有内容。这意味着当你进入工作岗位,看到墙上挂着类似的复杂图表时,你将能看懂。这是另一个我参与的开源项目“Sakai”(一个开源学习管理系统)的数据模型,而这只是其整个数据模型的约四分之一。

作为一名应用开发者,你必须了解并能够修改数据模型。理解数据模型是编写高性能代码的关键,因为任何人都能写出糟糕的代码,但对于一个成功的Web应用,性能必须相对良好。

从应用界面到数据模型

基本思路是:你不是从数据模型开始构建应用,而是从应用界面推导出数据模型。你观察应用界面,思考“这里有哪些数据块?”,然后决定如何将它们分配到不同的表中,因为把所有数据放在一张表里会很慢。所以,我们需要将数据拆分到几张表中,并决定哪些部分最适合放在哪张表里。

让我们假设我们刚成立了一家公司,我们的创新想法是:未来人们将按单曲购买音乐,而不是按专辑。专辑是一组音乐曲目,通常以约9美元的价格整张出售,而我们可以以1美元的价格出售单曲。这听起来是个好主意,对吧?

我们聘请了一位平面设计师,他给出了这个应用界面的设计稿。作为开发者,我们的首要任务不是去争论这个设计稿,比如“你意识到这个数据模型没有正确规范化吗?所以你必须修改应用界面”。我们不能这样做。我们假设这就是我们想要的样子。

对于一个数据建模人员来说,这个界面可能看起来很“吓人”,因为存在字符串的垂直重复,这不应该发生。我们的工作是构建一个满足用户需求的数据模型,而不是告诉用户,仅仅因为他们在用户界面的某一列中多次输入了“Paranoid”这个词,他们就违反了规范化规则,应该设计一个不同的用户界面。

那么,问题是如何从这个用户界面出发。我们有这些列:曲目、时长、艺术家、专辑、流派、评分、账户。现在,一种方法是创建一张名为“music”的大表,把所有列都放进去。但你会发现,就像你可能在整理自己的音乐电子表格时经历过的那样,你会反复输入相同的信息,然后意识到“这里有问题”。接着,你可能会打错字,然后需要在很多地方修复它。

关系型数据库的理念是:这些数据不应该放在一张大表里。专辑信息需要有自己的表。我们只将“Paranoid”这个词存储一次。然后,我们会在曲目表中放一个标记,比如“专辑编号7”。这样,“Paranoid”对应的所有曲目都会引用这个编号7。稍后这会更清楚,但基本思想就是这样。

我们作为数据建模人员,不会要求设计师改变界面外观,我们会在后端进行补偿。

识别核心对象与分组

我们需要查看所有列,因为我们确实需要表示所有列。我们知道会有多张表,但我们希望以合理的方式对它们进行分组。因此,我们必须弄清楚需要哪些表,这里表示的核心对象是什么。为每一列创建一个对象也不是好主意。我们需要在“属于一起的数据”和“需要分开并通过链接关联的数据”之间找到平衡。一旦找到这种平衡,我们就能获得最高的效率。

你可以暂停一下,去喝杯咖啡,因为我们将要坐下来,在白板前讨论并确定这个应用的数据模型。

以下是我们必须创建的列。我们首先要进行的讨论通常是:“第一张表是什么?”因为我们知道会有多张表。在构建数据模型时,你经常会问:“第一张表是什么?”通常你会思考:“这个应用的核心目的是什么?”

对于Twitter,核心可能是“推文”;对于学习管理系统,核心可能是“课程”;对于电子邮件系统,核心可能是“用户”。现在,我们需要为我们这个小应用进行辩论:核心是什么?用户不是我们的核心,因为我们构建的是一个每个人单独使用的小工具。而且,我们的界面上没有名为“用户”或“电子邮件”的列。

观察这个界面,我认为每一行的基本要素是“曲目”。所以,我认为核心是“曲目”。让我们确定第一张表是 Track(曲目)。

构建数据模型

现在,我们要做的是查看所有这些列,并判断哪些是每个曲目独有的、互不相同的方面,哪些是在多个曲目间相同的。这其实就是关于垂直重复的问题。

垂直重复是线索。如果你看到某个字符串垂直重复,那就是个问题。而数字的垂直重复通常是可以接受的。数字存储起来很廉价、容易。

以下是我们的分析步骤:

  1. 曲目:这是我们的核心表。
  2. 标题:每个曲目的标题都不同,所以它属于 Track 表。
  3. 时长:这是一个数字,即使不同曲目时长相同也没关系。它属于 Track 表。
  4. 评分:这是一个0到5的数字,属于 Track 表。
  5. 播放次数:这是一个计数数字,属于 Track 表。

这样,我们的第一张 Track 表就包含了:title(标题)、length(时长)、rating(评分)、count(播放次数)。我们把这些列勾选掉了。

接下来,我们处理那些存在垂直重复的列。这些列将促使我们创建新表。

  1. 专辑:专辑名(如“Paranoid”)在多个曲目中重复。我们创建一个 Album(专辑)表,包含 title(专辑名)。然后,我们建立关系:每个曲目(Track)属于(belongs to)一个专辑(Album)。
  2. 艺术家:艺术家名(如“AC/DC”)也在多个专辑中重复。我们创建一个 Artist(艺术家)表,包含 name(艺术家名)。然后,我们建立关系:每个专辑(Album)属于(belongs to)一个艺术家(Artist)。同时,曲目通过专辑间接关联到艺术家。
  3. 流派:现在的问题是,流派应该连接到哪里?是连接到艺术家、专辑还是曲目?这是数据建模中需要做出的决策,并且会影响我们的应用。
    • 如果连接到艺术家,意味着AC/DC的所有作品都必须是摇滚乐。这限制性太强。
    • 如果连接到专辑,意味着《Who Made Who》专辑的所有曲目都必须是摇滚乐。这更接近,但如果一张“精选集”专辑包含不同流派的歌曲呢?这也不行。
    • 因此,为了灵活性,我们将流派连接到曲目。我们创建一个 Genre(流派)表,包含 name(流派名)。然后,我们建立关系:每个曲目(Track)属于(belongs to)一个流派(Genre)。

最终的数据模型图

最终,我们得到了一个包含四张表的数据模型:

  • Track (曲目)
  • Album (专辑)
  • Artist (艺术家)
  • Genre (流派)

以及它们之间的关系:

  • 曲目 属于 专辑
  • 专辑 属于 艺术家
  • 曲目 属于 流派

我们并不需要过分担心专业术语,我们只是尝试将列拆分到属于一起的组中,并建立一些有意义的、人类可以理解的关系。接下来,我们将讨论如何将这幅图转化为实际的数据库代码,包括列命名约定和使这一切组合在一起的数据库特性。

总结

本节课中,我们一起学习了关系型数据库设计的基础。我们从应用界面出发,识别出核心数据对象(曲目),并通过分析数据的垂直重复情况,将信息拆分到不同的表(专辑、艺术家、流派)中。我们建立了表与表之间的“属于”关系,从而构建了一个高效、灵活且易于维护的数据模型。这个模型避免了数据冗余,并为未来的功能扩展留下了空间。在下一节中,我们将学习如何将这个设计图转化为具体的SQL表结构和关系键。

063:规范化与外键 🗃️

在本节课中,我们将学习数据库设计中的两个核心概念:规范化外键。我们将了解如何通过消除数据冗余来优化数据库结构,以及如何使用外键在不同表之间建立联系。


从概念图到数据库表

上一节我们通过绘制概念图来规划数据结构。现在,我们需要将这些概念转化为实际的数据库表和列。关键在于,我们不能让数据(如专辑名、艺术家名)在表中垂直重复出现。我们需要一种方法来连接不同的表,例如将曲目表中的行与专辑表中对应的行关联起来。

这个过程的核心就是数据库规范化


什么是数据库规范化?🧮

数据库规范化是我们一直在做的事情。你可以阅读大量关于它的资料,它涉及到关系型数据库之所以强大的底层数学原理(如谓词演算)。虽然我们程序员不必深究这些数学细节,但我们可以享受它带来的好处:数据库运行速度极快

通过规范化,我们构建的数据库结构能够充分利用底层高效的数学算法。反之,如果我们设计了一个糟糕的数据库,这些高效的“魔法”就无法发挥作用。

规范化本身是一门学问,甚至可以用一整个学期来学习其背后的数学理论和各种范式(如第一范式、第二范式等)。如果你对此感兴趣,可以深入研究。但在这里,我会将核心规则浓缩为一页内容。

实际上,我已经告诉过你所有规则了。


规范化的核心规则

规则很简单:不要垂直复制数据,即不要将相同的字符串存入两次。

例如,在一个需要存储用户姓名的系统中,名字“Charles Severance”只应出现在一个地方。然后,我们为这条记录分配一个数字,即整数键。这样,“Charles Severance”就对应数字2。之后,在所有需要引用这个名字的地方,我们都使用数字2来指向用户表中的那条记录。

我们使用这些整数键,为每一行数据创建一个特殊的“句柄”。你已经知道如何创建它了:使用 AUTO_INCREMENT。记住,AUTO_INCREMENT 会创建一个键列,这就是我们为每个表添加主键列的方法。

最终,每个表都会有一个键列


主键与外键 🔑

以下是我们需要理解的核心术语和结构:

  • 主键:标识表中每一行的键。它通常是一个整数,并使用 AUTO_INCREMENT 自动生成。我有一个命名惯例:如果表名是 Artist,那么主键列名就是 artist_id。我倾向于表名首字母大写(驼峰式),而所有列名都使用小写。
    • 代码示例CREATE TABLE Artist (artist_id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(255), PRIMARY KEY(artist_id));
  • 逻辑键:这是人类用来查找数据的键。例如,电子邮件地址、用户名或专辑标题。如果用户界面中有一个搜索框,你输入并点击搜索的内容,就是逻辑键。应用程序外部的人类永远不应该知道或看到内部的主键,它只是一个用于抓取特定行的序列号(1, 2, 3...)。
  • 外键:当一个表中的列指向另一个表(例如 Artist 表)中的某一行时,这个列就称为外键。它不是当前表的键,而是另一个表的键
    • 示例:在 Album 表中,有一个 artist_id 列,其值为2。这表示该专辑对应于 Artist 表中 artist_id 为2的那一行艺术家。

一个至关重要的警告永远不要使用逻辑键(字符串)作为主键。这是人们常犯的最大错误之一。

原因如下:

  1. 逻辑键会改变:人们可能会改名、更换电子邮件地址等。
  2. 性能差异:整数(通常4字节)比较和排序的速度远快于长度不定的字符串(可能100-200字节)。计算机处理数字比处理各种字符集(如亚洲字符、俄文字符)要高效得多。
  3. 维护困难:想象一下,在一个拥有数百个应用、每个应用有20-100张表的大型机构(如大学)中,如果电子邮件地址作为主键遍布所有表,那么当一个人更改邮箱时,更新将是一场灾难。而如果使用整数ID,每个应用中只需在一处(用户表)更新逻辑键,所有关联通过不变的整数ID保持,这就简单得多。

所以,请记住:主键是整数,外键也是整数。它们快速、占用空间小、易于比较,好处多多。


总结

本节课中,我们一起学习了数据库设计的两个基石。

  • 我们了解了数据库规范化的目标:通过消除数据冗余来构建高效、一致的数据库结构。
  • 我们掌握了三个关键概念:
    • 主键:每个表唯一的整数标识符(通常 AUTO_INCREMENT)。
    • 逻辑键:供人类使用和查找的字符串标识符(如邮箱、名称)。
    • 外键:一个表中指向另一个表主键的整数列,用于建立表间关联。

核心要点是:始终使用独立的整数作为主键,并通过整数外键来关联数据,切勿使用可能变化的逻辑键作为主键。遵循这些原则,你将能设计出健壮且高性能的数据库。

064:构建物理数据模型 🗺️

在本节课中,我们将学习如何将之前绘制的逻辑数据模型图,转化为实际的、可以在数据库中创建和填充的物理数据模型。我们将通过一个音乐数据库的示例,一步步地创建表、定义主键和外键,并插入数据。


从逻辑模型到物理模型

上一节我们介绍了逻辑数据模型的概念,并用图表描绘了数据之间的关系。本节中,我们来看看如何将这些图表转化为具体的SQL表结构。

我们从一个在白板上绘制的逻辑模型图开始。这个图展示了艺术家、专辑、曲目和流派之间的关系。在绘制逻辑模型时,我们关注的是数据的逻辑结构,而非具体的列名或技术细节。

现在,我们的任务是将这个逻辑结构映射到一个更具体的物理结构。这意味着我们需要决定表名、列名以及如何精确地实现图表中的关系。

构建表结构

以下是构建物理数据模型的核心步骤。我们将从最外层的表开始创建,逐步向内,因为一个表必须在其所引用的表创建之后才能创建。

1. 创建艺术家表

首先,我们创建 artist 表。几乎每个表的第一步都是定义一个自增的主键,这为我们后续引用每一行数据提供了一个唯一的“句柄”。

CREATE TABLE artist (
    artist_id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(128),
    PRIMARY KEY (artist_id)
) ENGINE=InnoDB;
  • artist_id: 这是表的主键,一个自增的整数。
  • name: 这是逻辑键,我们可能会通过艺术家名称来查找记录。

2. 创建专辑表

接下来创建 album 表。专辑属于艺术家,因此我们需要在 album 表中建立一个指向 artist 表的外键。

CREATE TABLE album (
    album_id INTEGER NOT NULL AUTO_INCREMENT,
    title VARCHAR(128),
    artist_id INTEGER,
    PRIMARY KEY (album_id),
    INDEX (title),
    CONSTRAINT FOREIGN KEY (artist_id) REFERENCES artist (artist_id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
  • CONSTRAINT FOREIGN KEY: 这是关键部分。它明确告诉数据库,album 表中的 artist_id 列引用了 artist 表中的 artist_id 列。这就在两者之间建立了“多对一”的关系(一个艺术家可以有多张专辑)。
  • ON DELETE CASCADE: 这是一个可选的约束,表示如果引用的艺术家记录被删除,那么属于该艺术家的所有专辑记录也会被自动删除。

3. 创建流派表

genre 表是关系树中的一个“叶子”节点,它不指向其他表,只被其他表引用。

CREATE TABLE genre (
    genre_id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(128),
    PRIMARY KEY (genre_id),
    INDEX (name)
) ENGINE=InnoDB;

4. 创建曲目表

最后创建 track 表。曲目同时指向专辑和流派,因此它包含两个外键。

CREATE TABLE track (
    track_id INTEGER NOT NULL AUTO_INCREMENT,
    title VARCHAR(128),
    len INTEGER,
    rating INTEGER,
    count INTEGER,
    album_id INTEGER,
    genre_id INTEGER,
    PRIMARY KEY (track_id),
    INDEX (title),
    CONSTRAINT FOREIGN KEY (album_id) REFERENCES album (album_id) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT FOREIGN KEY (genre_id) REFERENCES genre (genre_id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
  • 多个外键: 一个表拥有多个外键是完全正常的,这准确地反映了数据间的复杂关系。
  • 命名约定: 遵循一致的命名约定(如 artist_id, album_id)至关重要。它能让代码更易读、易维护,也便于在需要时获得帮助。

执行SQL与插入数据

创建表时,必须按照从“叶子”到“根”的顺序,即先创建没有外键依赖的表。如果尝试先创建 track 表,数据库会因为找不到它引用的 albumgenre 表而报错。

表创建完成后,我们开始插入数据。插入数据也需要遵循类似的顺序:必须先插入被引用的数据(如艺术家、流派),获取它们的主键ID,然后才能插入引用这些ID的数据(如专辑、曲目)。

以下是插入数据的示例:

  1. 插入艺术家: 插入“Led Zeppelin”和“AC/DC”。数据库会为它们分配 artist_id (例如 1 和 2)。
  2. 插入流派: 插入“Rock”和“Metal”,获取 genre_id (例如 1 和 2)。
  3. 插入专辑: 插入专辑“Who Made Who”,并指定其 artist_id 为 2 (AC/DC)。插入专辑“IV”,指定 artist_id 为 1 (Led Zeppelin)。数据库会为专辑分配 album_id
  4. 插入曲目: 插入曲目“Black Dog”,并指定它所属的 album_idgenre_id

当使用PHP等编程语言操作数据库时,这个过程会更简单,因为可以在插入一条记录后立即从数据库获取其自动生成的主键ID,然后用于后续的插入操作。

成果与总结

完成所有操作后,我们就成功地用数据库表重建了最初的逻辑模型图。通过外键约束,数据库管理工具(如phpMyAdmin)能够识别这些关系,并允许我们通过点击链接在不同表的数据间导航。

本节课中我们一起学习了如何将逻辑数据模型转化为物理数据模型。我们掌握了创建表、定义主键和外键约束的SQL语法,并理解了按正确顺序创建表和插入数据的重要性。最终,我们构建了一个没有字符串数据垂直冗余(如专辑名、流派名只存储一次)的高效数据库结构,而整数外键的重复则是允许且高效的。这个过程的核心在于用清晰的文本(SQL)来精确描述和实现我们心中的数据关系图。

065:关系型数据库设计4

概述

在本节课中,我们将学习如何将分散在多个表中的数据重新组合起来,以呈现给用户。我们将重点介绍SQL中的JOIN操作,它允许我们从多个表中同时提取数据,并根据外键关系将它们连接起来。我们还将探讨ON DELETE CASCADE等约束的作用,并简要介绍多对多关系。

我们已经成功地将所有数据拆分到不同的表中,并使用主键和外键在数据库层面建立了连接。然而,我们不能直接让用户界面设计师去查看这些包含外键ID的新表结构。用户需要看到的是直观、有意义的数据,而不是内部ID。因此,我们的任务是将这些分散的数据重新组合起来。

使用JOIN操作重建数据

上一节我们介绍了如何通过规范化将数据拆分到多个表中。本节中,我们来看看如何通过SQL的JOIN操作,将这些数据高效地重新组合,以满足用户界面的需求。

JOIN操作允许我们从多个表中同时提取数据,并将它们组合在一起。ON子句则指定了连接这些表的规则,即哪些行应该被关联起来。这非常简单:JOIN指定了涉及的表,而ON子句指定了连接的规则。

以下是一个基础的JOIN示例。假设我们有一个album表和一个artist表,我们想显示专辑标题和对应的艺术家姓名,而不是内部的ID数字。我们需要做的就是“跟随”表之间的链接,这正是JOIN操作能为我们完成的。

以下是实现此功能的SQL语句:

SELECT album.title, artist.name
FROM album
JOIN artist
ON album.artist_id = artist.artist_id;

我们来分解这个语句:

  • SELECT album.title, artist.name:这是我们想要显示的列。由于涉及多个表,我们使用表名.列名的格式来明确指定。
  • FROM album:指定查询的起始表。
  • JOIN artist:表示我们要将album表与artist表连接起来,从两个表中获取数据的超集。
  • ON album.artist_id = artist.artist_id:这是连接规则。它指定只有当album表中的artist_id列(外键)等于artist表中的artist_id列(主键)时,才将两表的行连接起来。ON子句的作用就是过滤掉所有不匹配的行组合。

通过这个查询,我们就从一个内部高效、可扩展的数据模型,转换成了用户期望看到的界面:显示了专辑名和对应的艺术家名。我们的任务就是重建所有这些数据关系。

深入理解JOIN与ON子句

为了更好地理解JOIN和ON子句的工作原理,我们可以将其想象为两个步骤。

首先,JOIN操作会创建一个临时的“超长”元数据行,它包含两个表中所有行的所有可能组合。例如,如果track表有4行,genre表有2行,在没有ON子句的情况下,JOIN会产生 4 * 2 = 8 行结果。

其次,ON子句的作用就是从这个所有组合的集合中,过滤掉那些不满足连接条件的行。它只保留那些外键与主键匹配的行。在上面的例子中,ON子句会将8行结果过滤到只剩下实际匹配的2行。

因此,ON子句至关重要,它确保我们只得到有意义的、相关联的数据,而不是所有可能的随机组合。虽然数据库知道我们定义了外键关系,但在SQL中,我们仍然需要显式地使用ON子句来告诉它如何进行连接。

当我们添加了正确的ON子句后,查询结果就只显示匹配的行,去除了中间不相关的组合。这时,我们可能会再次看到字符串数据的“垂直重复”,例如同一个艺术家的名字出现在多张专辑旁边。关键点在于:这种重复只出现在查询输出的临时结果集中,而不是存储在数据库里的。数据库在需要时动态构建这个结果,它不浪费存储空间,却能以用户期望的格式快速呈现数据。

连接多个表

JOIN操作可以扩展到连接多个表。只要命名规范清晰,这个过程并不复杂。

为了重建我们最初想要的完整视图(例如显示音轨标题、艺术家名、专辑名和流派名),我们需要跨越四个表进行连接。以下是相应的SQL语句:

SELECT track.title, artist.name, album.title, genre.name
FROM track
JOIN genre ON track.genre_id = genre.genre_id
JOIN album ON track.album_id = album.album_id
JOIN artist ON album.artist_id = artist.artist_id;

这个查询的逻辑如下:

  • 我们有四个表(track, genre, album, artist),它们之间存在三个关系(箭头)。
  • 因此,我们需要三个JOIN(连接四个表)和三个ON子句(对应三个关系)。
  • 每个ON子句都简单地描述了表之间的关系:track.genre_id = genre.genre_id(外键等于主键),以此类推。

每个条件都只是我们捕获表间关系的一种方式,当我们需要匹配的数据时,就沿着这些关系“走”到其他表。编写这样的查询并不困难。我们通常在phpMyAdmin等工具中键入它,可能会犯语法错误,但最终会得到正确的结果。同样,输出中的“垂直重复”是临时的,不占用数据库的存储空间,但却为用户提供了他们想要的信息。

回顾与性能考量

让我们回顾一下整个历程。一周前,我们从用户界面中发现了数据重复的问题。于是,我们创建了四个相互关联的表,设计了包含约束的CREATE语句,并开始插入数据,用数字ID来维护关系。然后,我们学习了使用JOIN操作将所有数据重新组合起来。我们起点是分散的数据,终点是整合的视图。

你可能会问,既然最终呈现给用户的数据看起来和最初差不多,为什么不直接用一个Google文档或简单的表来存储?答案是:速度规模

当前示例使用极少量的数据,所以一切看起来都很简单。但在真实的Web应用程序中,性能至关重要。如果你的应用成功了,用户量会增长。一个设计糟糕、存在大量数据冗余的数据库,在数据量变大时会变得极其缓慢。因此,我们进行所有这些规范化、拆分和连接操作,核心原因就是为了保证应用的速度和性能,使其能够处理大规模数据。

补充知识:ON DELETE CASCADE

现在,让我们补充一个之前未详细讨论的话题:ON DELETE CASCADE(级联删除)。

还记得在创建外键约束时,我们有时会添加ON DELETE CASCADEON UPDATE CASCADE吗?这就像是在告诉数据库:在这个表中,我们有一行指向另一个表中的某一行。问题是,如果被指向的那一行(父行)发生了变化或被删除了,该怎么办?

具体来说,假设我们有一个“父表”(如artist)和一个“子表”(如album),子表通过外键引用父表。这种关系是“多对一”的(多个专辑属于一个艺术家)。如果我们删除了父表中的某个艺术家行,那么引用它的所有子行(专辑)应该如何处理?

ON DELETE CASCADE的作用是:如果父行被删除,数据库会自动且立即地删除所有引用它的子行。这样做是为了维护数据库的参照完整性。这很方便,因为你只需删除父项,所有依赖它的子项就会自动消失。

当然,你也可以选择其他行为:

  • RESTRICT(或NO ACTION):默认行为。如果存在子行引用,则阻止删除父行。
  • SET NULL:删除父行后,将所有引用它的子行的外键字段设置为NULL(空值)。

作为程序员,你可以根据业务逻辑来选择。通过在CREATE TABLE语句中定义这些约束,你是在告知数据库引擎你的意图:“我希望你自动为我执行这个操作”。数据库会处理复杂的实现细节,你只需声明你想要什么。

总结

本节课中,我们一起学习了关系型数据库设计的核心环节——数据的重组与呈现。

  1. 我们掌握了SQL JOIN操作,它能够根据外键关系,高效地从多个关联表中提取和组合数据。
  2. 我们理解了ON子句的关键作用,它通过指定连接条件(通常是外键等于主键),过滤出有意义的数据行。
  3. 我们看到了JOIN产生的数据重复是临时的、不存储的,从而在保证数据库高效规范化的同时,满足了用户界面的需求。
  4. 我们探讨了ON DELETE CASCADE等外键约束,它们能自动维护数据的一致性,简化了我们的删除逻辑。
  5. 我们认识到,进行所有这些复杂的表拆分和连接操作,最终目的是为了支撑Web应用在大规模数据下的速度和性能

到目前为止,我们处理的主要是“一对多”关系。在接下来的课程中,我们将探讨另一种重要的表连接方式:多对多关系

066:德克萨斯州休斯顿

在本节课中,我们将跟随课程团队,了解一次在德克萨斯州休斯顿举行的线下办公时间活动。我们将看到参与的学生们以及他们学习编程的经历。

大家好,欢迎来到德克萨斯州休斯顿。这是Coursera面对面办公时间的又一次活动。

现场有很棒的参与者,天气也非常宜人。我们恰好错过了一周前的大洪水,所以大家能看到这里没有被淹。我们正在一家名为Bohemos的酒吧露台上愉快地交流。接下来,让大家认识一下在场的同学们。请向你的同学们打个招呼,说点你想说的。

大家好,我是Snehalanki,我是一名化学工程师。我是Python课程的新手,很高兴能来到这里听Dr. Chuck的课程。谢谢。

我是Randy,我年纪不小了,但我热爱Python。这是你参加的第二次办公时间了。这是我的第二次办公时间。第一次是在他的公路旅行中,他当时去了很多爵士乐场所。没错,是和我儿子一起去的爵士乐场所。是的,我儿子当时也在。

大家好,我是Brad。好的,请把相机对准我。

大家好,我是Chuck。我们现在在伊利诺伊州芝加哥市帕尔默之家希尔顿酒店的地下室,这是“互联网历史、技术与安全”课程的又一次办公时间。让我为大家介绍几位朋友。请打个招呼吧。

大家好,同学们好,我是Allison。

同学你好,我是来自芝加哥的James。

你好,我是Randy。

这次我和我儿子一起来到了轮椅橄榄球的现场,所以这又是和我儿子一起的旅行之一。

我叫Roy Hanson,我已经退休了。我来这里是想看看发生了什么,因为我想学点新东西。很酷。

我也叫Dr. Chuck,但我是一名退休的牙医。不,那不是我,是别人。我确实是Chuck医生,但拼写不同。好的。

你好,Many。

还有Andy,你好像是某种计算机专家之类的?上完Dr. Chuck的所有课程,你会爱上它们的。好的,你能学到很多。玩得开心,谢谢。

我是Ragu,我是一名管理员,正在努力学习Python。好的,谢谢。

大家好,我叫Lue,我开始学习Python了。我很喜欢这门课,所以我们会坚持下去。

大家好,我是Cheryl,Randy是我的丈夫。我来这里是为了学习除了拆电脑之外的东西。

大家好,我是一名电气工程师。我已经上过Python和互联网存储课程,两门课都非常好。好的。

大家好,我是Amy。我已经上过互联网历史和Python课程,现在受到鼓舞要去完成Python的毕业项目。我想我能做到。你当然能做到,我相信你。谢谢。

我是Lee,我来这里是为了欢迎Dr. Chuck来到德克萨斯。我非常喜欢他的课程,强烈推荐。他一直在请大家喝啤酒。

大家好,我叫Rahiemm。我正在学习Python,以便理解我工作中的程序员同事。很酷。

我叫Carrie Lee,我和我儿子Dalton一起来的。我上过Python课,还和我的两个孩子一起上过互联网历史课。Dalton,你想打个招呼吗?酷,我想。哈。

大家好,我叫Barak。我正在学习“Python for Everybody”专项课程,以及Dr. Andrew的机器学习课程。感谢Dr. Chuck和Coursera团队让远程教育变得如此便捷。

好的,我叫Leon El Porera,我来自安哥拉。我邀请你们来和我们一起学习,真的很有趣。我很乐意去,如果我有合适的理由的话。

好的,以上就是全部内容了。我想下一次办公时间将在几周后的纽约市,在Per会议期间举行。但在我们离开、在我结束之前,我想特别感谢一下课程的导师们。这些人非常专注,付出了巨大的努力。哇,我差点被一只鸟撞到。

当鸽子哭泣时,对吧?Prince(歌手)就在昨天或两天前去世了。但我想为我们课程中出色的导师们鼓掌。好的,非常感谢每一位导师,不仅是我课程中的,而是所有在线课程的导师。你们做了很多工作,让这一切对每个人都意义非凡。干杯。

本节课中,我们一起了解了在休斯顿举行的一次课程线下聚会。我们看到了来自不同背景的学习者分享他们学习编程的经历和热情,也感受到了学习社区的支持与活力。课程导师们的辛勤付出是这种在线学习体验不可或缺的一部分。

067:Chuck博士的教育科技纹身

在本节课中,我们将跟随Chuck博士,通过他独特的纹身故事,了解教育科技领域的一些趣闻和思考。


概述

本节内容并非传统的编程教程,而是课程中的一个趣味环节。Chuck博士分享了他身上一系列与科技公司相关的纹身,并讲述了这些纹身背后的故事,以此引出关于教育、技术和个人表达之间关系的轻松讨论。


上一节我们介绍了课程的背景,本节中我们来看看Chuck博士身上的特殊“装饰”。

Chuck博士展示了他身上纹有的多个公司标志。他向这些公司展示时,对方的反应常常是:“这看起来一点也不像我们的Logo。” 对此,Chuck博士的回应是:“伙计,这是皮肤。皮肤能做什么呢?” 这句话幽默地指出了纹身艺术与精确复制之间的差异。

以下是Chuck博士提到的几个纹身及其故事:

  • 教育软件纹身:他有一个纹身代表其为大学等机构开发的学习软件。
  • “天使之翼”的构想:他曾想在一个纹身上添加天使翅膀。
  • 纹身师的反对:当他将这个想法告诉纹身师时,纹身师非常生气。纹身师认为,如果客户真的想纹天使,应该去找更擅长此题材的艺术家,并坚决反对他添加天使翅膀。
  • 最终结果:因此,那个纹身最终没有加上天使翅膀。

总结

本节课中我们一起聆听了Chuck博士分享的纹身趣事。这些故事虽然与技术代码无关,但以一种轻松的方式展现了创作者(无论是程序员还是纹身师)对其作品的想法和坚持。它提醒我们,在严谨的技术世界之外,个人的表达与艺术的视角也同样重要。

068:多对多关系 📚

在本节课中,我们将学习数据库关系模型中的一个核心概念:多对多关系。我们将探讨为什么需要这种关系,如何通过“连接表”来实现它,以及如何在SQL中创建和查询这种关系。


概述

到目前为止,我们讨论的都是“一对多”关系。但在数据库设计中,表之间还存在其他类型的关系,其中最重要且尚未讨论的就是“多对多”关系。本节将详细介绍多对多关系的概念、应用场景以及实现方法。


从一对多到多对多 🔄

上一节我们介绍了一对多关系,例如一张专辑属于一位艺术家。但在现实世界中,许多关系更为复杂。例如,一位艺术家可以创作多张专辑,而一张专辑也可能由多位艺术家合作完成。这种关系就是典型的多对多关系。

多对多关系意味着一个实体(如表A中的记录)可以与多个其他实体(如表B中的记录)相关联,反之亦然。例如:

  • 一个用户可以注册多门课程。
  • 一门课程可以包含多名用户。

为何需要连接表? 🧩

在数据库中,我们无法像处理一对多关系那样,通过在一个表中添加指向另一个表的外键列来直接建立多对多关系。因为这样会导致数据冗余和结构问题。

以下是解决多对多关系的核心方法:

我们必须在两个表之间创建一个新的中间表,通常称为连接表联结表关系表。这个表的作用是将一个多对多关系拆分为两个一对多关系。

逻辑模型
用户 <--多对多--> 课程

物理实现(通过连接表)
用户 <--一对多--> 成员表 <--一对多--> 课程

连接表包含两个外键列,分别指向两个主表的主键。这两个外键的组合,共同构成了连接表的“复合主键”。


连接表示例与数据建模 📊

连接表不仅可以存储关系,有时还可以存储与这种关系本身相关的数据。

以用户和课程为例,连接表(可以命名为 member)可能包含以下列:

  • account_id:外键,指向用户表 (account)。
  • course_id:外键,指向课程表 (course)。
  • role:表示用户在该课程中的角色(例如:“教师”或“学生”)。

代码示例:创建连接表

CREATE TABLE member (
    account_id INTEGER,
    course_id INTEGER,
    role VARCHAR(16),
    PRIMARY KEY (account_id, course_id), -- 复合主键
    FOREIGN KEY (account_id) REFERENCES account (account_id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES course (course_id) ON DELETE CASCADE
);

关键点解析

  • PRIMARY KEY (account_id, course_id):这定义了一个复合主键。它确保了(account_id, course_id)这个组合在整个表中是唯一的,防止同一用户在同一门课程中出现多次。同时,它也会为这个组合建立索引,提高查询速度。
  • 连接表通常没有独立的、自增的单一主键(如id)。
  • 外键约束确保了数据的引用完整性。

插入与连接数据 ➕

在插入数据时,我们需要先向两个主表(accountcourse)插入记录,获取它们自动生成的主键ID。然后,在连接表(member)中,使用这些ID来建立关联。

代码示例:插入关系数据

-- 假设已知ID:Jane (1), Ed (2), Sue (3); 课程 PHP (1), Python (2), Perl (3)
INSERT INTO member (account_id, course_id, role) VALUES
(1, 1, '教师'), -- Jane 在 PHP 课程中是教师
(2, 1, '学生'), -- Ed 在 PHP 课程中是学生
(3, 1, '学生'), -- Sue 在 PHP 课程中是学生
(1, 2, '学生'), -- Jane 在 Python 课程中是学生
(2, 2, '教师'), -- Ed 在 Python 课程中是教师
(2, 3, '教师'), -- Ed 在 Perl 课程中是教师
(3, 3, '学生'); -- Sue 在 Perl 课程中是学生

查询多对多关系 🔍

要获取完整的信息(例如,生成一份包含用户姓名、角色和课程名称的课程花名册),我们需要使用 JOIN 语句将三个表连接起来。

代码示例:连接三个表进行查询

SELECT account.name, member.role, course.title
FROM account
JOIN member ON member.account_id = account.account_id
JOIN course ON member.course_id = course.course_id
ORDER BY course.title ASC, member.role DESC, account.name ASC;

查询逻辑

  1. FROM account:从用户表开始。
  2. JOIN member ON ...:通过account_id连接member表,获取用户参与课程的角色信息。
  3. JOIN course ON ...:再通过course_id连接course表,获取课程名称。
  4. ORDER BY:对结果进行排序,例如先按课程名升序,再按角色降序(使“教师”排在前面),最后按姓名升序排列。


总结与重要性 🚀

本节课中,我们一起学习了数据库中的多对多关系。

核心要点总结

  1. 概念:多对多关系描述了实体间复杂的双向关联,无法用单一外键直接实现。
  2. 解决方案:通过创建连接表,将多对多关系分解为两个一对多关系。连接表的核心是两个外键及其可能构成的复合主键
  3. 数据建模:连接表本身也可以存储与关系相关的属性(如role)。
  4. 查询:使用多表JOIN(特别是通过连接表进行“穿针引线”)来获取完整的关联信息。
  5. 设计原则:使用数字ID作为键,避免使用字符串。正确的数据模型(规范化)是应用能够高效处理大规模数据、并保持良好性能的基础。早期重视数据模型设计,可以避免应用在增长时面临重构甚至崩溃的风险。

掌握多对多关系是构建复杂、可扩展Web应用程序的关键一步。虽然数据库设计是一门深奥的艺术,但理解这些基础概念已足以让你设计出专业级别的数据模型。

069:俄勒冈州波特兰

在本节课中,我们将回顾密歇根大学《面向所有人的Web应用程序》课程在俄勒冈州波特兰市举行的一次附加办公时间。本次办公时间汇集了来自不同背景的学习者,他们分享了各自的学习经历和收获。


大家好,我们来到了俄勒冈州的波特兰市,进行又一次的办公时间活动。

我们将有两位……哦,等等,我刚才说了什么?哦,我只是感觉……至少我是在正确的州。俄勒冈州,波特兰市,俄勒冈州。好的,波特兰市,俄勒冈州。在一条非常繁忙的街道上,我们在这里又一次成功地举办了办公时间。和往常一样,我想让班上的其他同学认识一下大家。那么,开始吧,请打个招呼,说说你的名字以及任何你想对班上其他同学说的话。

以下是参与者的自我介绍:

  • 嗨,我叫阿尔文。我报名参加了多门在线课程,这门Python课程是我第一个完成了全部四个部分课程的。我也是,这要感谢出色的讲师。
  • 你好,我叫斯科特。我正在学习Python用于信息处理。
  • 嗨,我叫阿里·礼萨。在过去的几个月里,我见到查克博士的次数比见到我孩子的次数还多。听起来你也是。我刚刚完成了Python课程,它结构清晰,做得非常好。谢谢。
  • 嗨,我叫保罗。我有一个17岁的儿子,他也叫保罗。我们正在一起使用Coursera学习,作为一个父子项目。他是一名高中生,学得很好。对我来说这是爱好,对他来说是为了获得一些未来能用得上的技能。我的梦想是最终让这门课程进入每一所高中。我的意思是,这就是我想要的,但这是一件非常困难的事情。不过,它已经进入了我们的家庭。
  • 嘿,我是麦迪,我是匹泽学院有机生物学专业的学生,但我对学习Python感到兴奋。
  • 谢谢。嗨,我是玛格丽特,实际上我是从田纳西州来这里度假的。Python课程是我一直想学的编程入门课。
  • 很棒。嗨,我是黛安。我正在学习这个系列课程的第四门,今年夏天我将完成毕业设计。这很有趣,很有趣,很有趣。
  • 是的,毕业设计是所有课程中最简单的。嗨,我是亚历杭德拉。三年前,当我住在古巴哈瓦那工作时,我参加了精彩的互联网历史课程。所以,感谢查克博士的精彩课程。
  • 嗨,我叫安德鲁。我和我的同事内森一起加入了Coursera,他今天因为工作不能来。不过,他有点像是在虚拟地抢镜。你可以放一张内森的照片吗?不,我没有。那会很酷。你可以把他放在我心脏旁边。但我已经离开了那份工作。感谢查克博士所做的所有出色工作。
  • 酷。嗨,我是赫布。很久以前,我学习了互联网历史、技术和安全课程。然后我成为了社区助教,现在我是导师。我已经参与这门课程很长时间了,非常享受帮助学生的过程。
  • 赫布是不到十位导师中的一位。赫布是不到十位导师中的一位,他不知疲倦地帮助所有学生,并且完全没有报酬。所以我喜欢做的一件事就是去导师所在的城市,请他们吃顿饭。对于三年辛勤工作来说,一顿免费的饭只是很小的补偿。但如果没有像赫布这样的导师,这些课程真的无法活跃起来。即使有最好的讲座视频、最好的作业和最好的测验,如果没有人的参与,也毫无意义,因为学习是人的事情。这不是信息,是人的事情。像赫布这样的人让这些事情保持活力,即使在我离开很久之后,在所有讲座都结束之后,等等,等等。所以,让我们为出色的赫布鼓掌。
  • 好的,我们还有随机的……是的,各位,我们还有像赫布这样的随机参与者。哦,当然不是化学家。我是莫琳,我是一名化学家。基本上,我学习这门课程是为了能够以自动化的方式提取数据并每周创建图表,而不是在Excel中手动操作。
  • 酷。

好的,我们就在这里,就这样了。下一次办公时间将在不到24小时后,在西雅图举行。在那之后,你们都被邀请参加再下一次在英格兰布莱切利公园举行的办公时间。所以,如果你要去英格兰度假,可以来参加办公时间。那么,再见。


本节课中,我们一起回顾了在波特兰举行的办公时间活动,聆听了多位学习者分享他们学习Python和Web开发课程的经历、动机与收获。这些分享体现了在线学习的社区氛围和互助精神,也展示了编程技能在不同职业和兴趣领域的广泛应用。

070:欢迎学习本课程 🎉

在本节课中,我们将概述《构建数据库应用程序与PHP》这门课程。我们将回顾前序课程的基础,并介绍本课程的核心目标:将之前所学的零散知识整合起来,构建一个功能完整的Web应用程序。

欢迎来到《构建数据库应用程序与PHP》课程。如果你已经完成了本系列的前两门课程,那么恭喜你并欢迎你。这些知识现在至关重要。

在你学习前两门课程的过程中,你可能产生过疑问:这一切有意义吗?我们为什么要做这些?这门课程将回报你的耐心。我们将把所有学过的零散知识,像乐高积木一样组合起来。我们将建立这个基础,并在其上继续构建。现在,就是我们将所有这些概念整合在一起的时刻。

本课程会快速进入主题,因为我们假定你已经掌握了前两门课程的所有知识:HTML、CSS、SQL以及PHP的基础。你已经搭建好了开发环境,并且知道如何编写代码。如果你对其中任何一项不熟悉,你真的需要回到之前的课程,因为我们不会放慢速度。我们不是在教PHP基础,而是在学习如何运用PHP对象,进度会很快。

所以,如果你准备好了,这正是你想要的。是时候开始工作,构建真正优秀、真实且完整的应用程序了。如果一开始你觉得跟不上,只需回去学习其他课程。我们希望你开始本课程前已具备所需知识。

一旦我们开始,我们将快速推进,学习如何开发数据库应用程序和复杂的Web功能。我们将学习一些至关重要的内容,例如cookiesHTTP headerssessions以及登录和登出的实现。这些都是我们需要真正掌握的知识。

这门课程包含大量内容,即使你最终不使用PHP,无论是使用Ruby on Rails、Java、Node.js还是其他技术,你仍然需要了解会话、HTTP头、请求-响应周期等概念。因此,这是一个转折点,我们不再仅仅是学习编程语言,而是真正开始学习Web应用程序开发。

在本课程结束时,我们将完成一个CRUD应用程序。我们会循序渐进地构建一个应用程序,更多地了解应用程序是如何组合在一起的,我们将连接PHP和SQL。当我们创建出基本的、必不可少的Web应用程序——即CRUD应用程序时,课程就告一段落了。

CRUD代表创建(Create)、读取(Read)、更新(Update)和删除(Delete)。这是数据库能做的四件事。我们还需要一点用户界面来完成这四项操作。从那时起,你开发的所有Web应用程序都将是这个主题的变体。

因此,在本课程结束时,你将成为一个Web开发者。剩下的只是关于你使用什么语言、什么特性的细节问题。我非常高兴你能走到这一步,但我更期待你完成这门课程。

再次感谢你对本课程的兴趣,我们课堂上见。

071:面向对象概念

在本节课中,我们将学习面向对象编程的基本概念。面向对象编程是一种强大的编程范式,它通过将数据和操作数据的代码“封装”在一起,来帮助我们更好地组织和管理复杂的程序。本节课的重点是理解相关的术语,而不是立即编写复杂的代码。

概述

面向对象编程是PHP等现代编程语言的核心特性之一。它提供了一种将程序分解为独立“对象”的方法,每个对象都包含自己的数据和功能。理解其基本概念是学习高级PHP编程的基础。

PHP与面向对象编程

PHP语言经历了漫长的发展。从PHP 1到PHP 4,其编程风格主要受C和Perl语言影响,大量使用全局函数,并通过为函数添加前缀(如date_)来组织代码库。这是一种非面向对象的、管理复杂性的方式。

从PHP 5开始,面向对象编程被正式引入。PHP 7及更高版本中,面向对象编程变得更加自然和成熟。如今,整个PHP社区都非常依赖面向对象技术。虽然PHP在面向对象方面经历了一个过渡期,但现在它已成为构建现代、可维护应用程序的标准方式。

什么是面向对象编程?

一切皆模式。我们之前讨论过的“模型-视图-控制器”就是一种编程模式。模式帮助我们理解彼此的代码,并高效地共享代码。

任何程序都由两部分组成:代码(如循环和条件语句)和数据(如数组)。传统的编程方式是将所有代码和数据放在一个大的程序中。

面向对象编程则改变了这种思路。它将数据和操作该数据的代码组合成更小的、独立的单元,我们称之为对象。每个对象都像一个独立的“盒子”,里面封装了特定的数据和功能。这种将数据和代码打包在一起的做法,就叫做封装

封装的优点在于,它隐藏了内部的复杂性。当你使用一个别人写好的对象时,你只需要知道它能做什么(它的方法),而不需要关心它是如何实现的。这就像使用一个复杂的电器,你只需要按按钮,而不需要懂里面的电路。

核心术语

以下是面向对象编程中的几个核心术语,我们将逐一解释。

类与对象

  • :类是一个模板蓝图。它定义了如何创建某种类型的对象,但它本身不是一个具体的对象。例如,“狗”是一个类,它定义了狗的一般特征(如会叫、有四条腿)。
  • 对象:对象是类的一个具体实例。它是根据类的模板“制造”出来的一个实实在在的东西。例如,根据“狗”这个类,我们可以创建出名为“斑点”的具体狗对象。

一个生动的比喻是:类是饼干模具,对象是用这个模具压出来的饼干。模具(类)本身不能吃,但用它做出来的每个饼干(对象)都是独立的、可以“使用”的实体。

实例

“实例”这个词通常与“对象”同义。当我们说“创建一个类的实例”时,意思就是“根据这个类创建一个对象”。所以,一个对象就是一个类的实例。

方法与属性

每个对象内部都包含两部分:数据代码

  • 属性:属性是对象内部的数据(或状态)。例如,一个“狗”对象可能有颜色品种年龄等属性。
  • 方法:方法是对象内部的代码(或功能)。它们看起来很像普通的函数,但它们是属于某个对象的。例如,一个“狗”对象可以有叫()跑()吃()等方法。方法是对象能够执行的操作。

新旧代码风格对比

为了更好地理解面向对象带来的变化,我们来看一个处理日期和时间的例子。

旧风格(过程式编程)

在PHP早期(PHP 5之前),我们使用一系列全局函数,并通过添加前缀来组织它们。

// 设置时区
date_default_timezone_set(‘America/New_York‘);
// 获取当前时间戳
$current_time = time();
// 计算一周后的时间戳
$next_week = $current_time + (7 * 24 * 60 * 60);
// 格式化并输出
echo date(‘Y-m-d‘, $current_time) . “\n“;
echo date(‘Y-m-d‘, $next_week) . “\n“;

这种方式有效,但函数名很长(如date_default_timezone_set),并且所有函数都存在于全局命名空间中,容易造成命名冲突。

新风格(面向对象编程)

从PHP 5.2开始,引入了DateTime类,我们可以用更清晰、更模块化的方式处理日期。

// 使用‘new‘关键字创建DateTime类的实例(对象)
$now = new DateTime();
// 创建另一个表示一周后的DateTime对象
$next_week = new DateTime(‘+1 week‘);

// 调用对象的方法来格式化输出
echo $now->format(‘Y-m-d‘) . “\n“;
echo $next_week->format(‘Y-m-d‘) . “\n“;

代码解析:

  1. new DateTime()new是一个关键操作符,它根据DateTime类这个“模板”,创建出一个新的日期时间对象,并赋值给变量$now
  2. $now->format(...)->是对象操作符。它表示“调用$now这个对象内部的format方法”。方法知道它属于哪个对象,因此能自动使用该对象内部的数据(即具体的日期时间)。
  3. 我们创建了两个独立的对象$now$next_week。它们内部的数据不同,但共享相同的format方法。调用$now->format()输出当前日期,调用$next_week->format()输出一周后的日期。

面向对象的方式通过命名空间自然地组织了代码。format方法属于DateTime类,不会与其他同名函数冲突。代码的意图也更加清晰。

总结

本节课我们一起学习了面向对象编程的基础概念。我们了解到:

  • 是创建对象的蓝图或模板。
  • 对象是类的具体实例,它封装了属性(数据)和方法(功能)。
  • 封装是面向对象的核心思想,它将数据和操作数据的代码捆绑在一起,隐藏内部细节,只暴露必要的接口。
  • 通过对比新旧日期处理代码,我们看到了面向对象编程如何使代码更模块化、更易读、更易维护。

记住,目前阶段我们的重点是理解这些术语。在接下来的课程中,我们将动手创建自己的类,并学习如何利用面向对象编程来构建更强大的Web应用程序。

072:在PHP中创建对象

在本节课中,我们将学习PHP中面向对象编程的基础知识,特别是如何创建和使用对象。我们将从理解对象的概念开始,然后通过一个具体的例子来学习如何定义类、创建对象实例以及访问对象内部的属性和方法。

概述

上一节我们介绍了面向对象编程的基本思想。本节中,我们来看看如何在PHP中实际创建和使用对象。我们将通过一个管理人员信息的例子,对比传统的数组数据结构和面向对象的方法,来理解对象的优势。

从数据结构到对象

在深入创建对象之前,让我们先回顾一下PHP4时代处理复杂数据的方式。人们喜欢PHP的一个原因是,使用数组,特别是键值对数组,可以构建出灵活的数据结构。

例如,我们要处理人名信息。有时我们有一个全名,有时我们有名和姓。我们决定将“姓”称为“家族名”,因为“姓”在某些文化中并不排在最后,家族名可能在前,名在后。所以,我们会有一些数据结构。有些包含全名,有些包含家族名和名,并且每个结构都有一个房间号。

这还不是真正的对象,只是使用了对象术语的通用做法。我们创建了数据结构。但问题是,当我们需要打印人名时,我们必须编写一些代码来处理这两种变体。

以下是处理这两种变体的函数代码示例:

function get_person_name($person) {
    if (isset($person[‘full_name’])) {
        return $person[‘full_name’];
    }
    if (isset($person[‘family_name’]) && isset($person[‘given_name’])) {
        return $person[‘given_name’] . ‘ ‘ . $person[‘family_name’];
    }
    // 这里可能根据文化背景有更复杂的拼接逻辑
}

我们不想一遍又一遍地重复编写这段代码。现在我们可以传入Chuck和Colleen的数据,打印出正确的人名。这是一种非面向对象的做法,即使用数组并编写辅助函数来复用代码。

创建对象模板(类)

现在,如果我们用面向对象的模式来重构这个例子,我们会创建一个模板。在PHP中,我们使用 class 关键字来定义这个模板。

class 有点像 function,用于定义一个结构。下面的代码定义了一个模板,它本身不会运行,只是被解析。其效果是添加了一个新的“Person”模板。

class Person {
    public $full_name = false;
    public $given_name = false;
    public $family_name = false;
    public $room = false;

    function get_name() {
        if ($this->full_name != false) {
            return $this->full_name;
        }
        if ($this->family_name != false && $this->given_name != false) {
            return $this->given_name . ‘ ‘ . $this->family_name;
        }
        // 这里可能有更复杂的逻辑
    }
}

在这个类中,我们定义了四个属性(数据)和一个方法(代码)。每个 Person 对象都将拥有这些变量和方法。方法内部的 $this 是一个预定义的常量,只能在类的方法内部使用。$this 总是指向当前正在执行代码的那个对象实例本身。这非常重要,它使得同一个方法可以在成千上万个不同的对象上运行,并访问各自的数据。

实例化对象并使用

定义好类(模板)后,我们就可以创建具体的对象实例了。以下是创建和使用对象的步骤:

  1. 创建对象实例:使用 new 关键字和类名来创建一个新的对象。
  2. 设置对象属性:使用箭头操作符 -> 来访问和设置对象内部的属性。
  3. 调用对象方法:同样使用箭头操作符 -> 来调用对象内部的方法。
// 创建第一个Person对象
$chuck = new Person();
$chuck->full_name = “Chuck Severance”;
$chuck->room = “4, North Quad”;

// 创建第二个Person对象
$colleen = new Person();
$colleen->family_name = “Van Lent”;
$colleen->given_name = “Colleen”;
$colleen->room = “34, North Quad”;

// 调用对象的方法
echo $chuck->get_name(); // 输出:Chuck Severance
echo $colleen->get_name(); // 输出:Colleen Van Lent

当我们调用 $chuck->get_name() 时,方法内部的 $this 指向 $chuck 这个对象,因此它检查的是 $chuck->full_name。对于 $colleen->get_name()$this 则指向 $colleen 对象,检查其 family_namegiven_name

总结

本节课中我们一起学习了PHP中创建对象的基础知识。我们首先回顾了使用数组构建数据结构的方法,然后引入了面向对象的概念。我们学习了如何使用 class 关键字定义包含属性和方法的类模板,如何使用 new 关键字实例化对象,以及如何使用 $this 关键字在方法内部访问当前对象的属性。对象的核心是数据代码的封装,$this 是实现这种封装的关键,它确保了方法能正确访问到所属对象实例的数据。理解这些概念是有效使用PHP内置对象和阅读相关文档的基础。

073:PHP中的面向对象库 📚

在本节课中,我们将学习PHP中面向对象编程的核心概念,特别是如何理解和使用类库。我们将重点介绍访问类与对象内部成员的两种不同方式,并解释静态成员与动态成员的区别。

上一节我们介绍了如何构造一个简单的对象。本节中,我们来看看如何阅读和使用PHP内置的面向对象库文档,并理解其中的关键操作符和概念。

访问对象内部的成员

我们首先回顾一个已经见过的操作符。这个操作符基本上表示“在...之内”的含义。

例如,$z->format() 表示在名为 $z 的对象内部寻找 format 函数。
同理,$colleen->first_name 表示访问 $colleen 对象内部的 first_name 变量。
我将这个操作符理解为“在...之内”。

静态成员与动态成员

类中的某些成员可以被直接访问。尽管类是模板,但有些代码是静态的、不变的,因此它不是动态的。我们可以直接从类模板中取出并运行这段代码。

以下是静态成员与动态成员的关键区别:

  • 静态成员:使用类直接访问,没有 $this 关键字。例如 DateTime::RFC822
  • 动态成员:必须在已实例化的对象上运行。例如 $z->format()

静态成员存在于内部,而动态成员存在于对象(或实例)内部。

阅读面向对象文档

现在,我们来看一些面向对象的文档。

文档中包含了常量。这些是静态定义的常量。例如,DateTime::RFC822 表示获取 DateTime 类内部定义的 RFC822 常量。这个字符串是用于不同日期格式化场景的格式之一。

你需要知道类中存在常量,并了解如何使用它们。

构造方法

类中有一些特殊的方法,我们将特别讨论构造方法。在文档中,你不会看到一个专门展示 new 操作允许做什么的部分。有人可能会认为这里应该叫 new,但你必须将 new__construct 等同起来,因为构造对象是“获取模板并创建实例”的通用概念。new 是触发构造过程的操作符。

阅读文档时,你必须找到 __construct 方法来了解在 new 后面的括号里允许做什么。它本质上是一个函数调用。例如,DateTime 的构造方法有两个可选参数:时间 $datetime 和时区 $timezone。如果不指定,则默认使用当前时间和服务器时区。

通过查找 __construct 方法,你可以理解在调用 new 时允许做什么。

静态方法

有时我们会遇到静态方法。这些是可以直接从类本身访问的方法。

例如,$x = new DateTime() 创建了一个常规对象。但 DateTime::getLastErrors() 是一个静态方法。文档中标注了“static”,这意味着它不依赖于 $this,也意味着你可以直接通过类名调用它。

:: 符号表示“进入类并获取那段代码”。在这个例子中,它是为了获取构造过程中可能发生的错误。因为使用 new 构造时,它要么返回一个有效对象,要么返回空。如果你想查看出了什么错,必须调用类方法并询问:“类啊,上次构造时你遇到了什么错误?”

普通(动态)方法

普通方法没有 static 关键字。我们已经接触过:创建一个新对象,然后访问该对象内部的方法。例如,$z 是一个对象,format 是该对象内部的一个方法。

你会发现,文档中大多数方法都不是静态的。大多数都是动态的,这意味着你只能使用箭头符号 ->(即“在...之内”的表示法)来访问它们。

对象的生命周期与魔术方法

PHP引入面向对象模式较晚,因此能够借鉴其他语言的最佳特性。你会看到一些以下划线 __ 开头的方法,它们与对象的生命周期有关。

例如,__wakeup() 方法。当某个对象从会话中恢复并重新加载到PHP内存时(即使我们还没讲到会话),这个方法会被调用。因此,你可以构建一个对象,要求在其被重新加载和唤醒到内存的时刻被调用和咨询。

接下来,我们将更详细地讨论我刚才提到的内容:对象的生命周期。


本节课中我们一起学习了PHP面向对象库的基本使用。我们区分了通过 -> 访问对象实例成员和通过 :: 访问类静态成员这两种方式,理解了构造方法 __constructnew 操作符的关系,并初步了解了静态方法的作用以及对象生命周期中的魔术方法。这些知识是阅读和使用PHP类库文档的基础。

074:PHP中的对象生命周期 🧬

在本节课中,我们将要学习PHP中对象的生命周期。我们将了解对象从定义模板、创建初始化到最终销毁的完整过程,并重点学习两个特殊的方法:构造函数和析构函数。理解这些概念对于构建结构良好、资源管理得当的Web应用程序至关重要。


上一节我们探讨了PHP对象模型的优势,特别是在处理会话等Web应用常见场景时。本节中,我们来看看对象生命周期中的经典概念:模板定义、对象的创建与初始化,以及对象的销毁。

构造函数与析构函数 🏗️

在PHP中,对象生命周期由几个关键方法控制。作为对象的构建者,你可以定义在对象设置时和销毁时被自动调用的代码。构造函数用于在对象创建时将其数据设置到正确的状态,而析构函数则用于在对象销毁时执行清理工作,尽管后者使用频率较低。

构造函数的主要作用是在对象启动时设置一些主要的实例变量,确保它们拥有正确的初始值。有时,这些变量可能是非公开的,构造函数可以确保它们在对象首次创建时被正确设置。

以下是构造函数和析构函数的一个简单示例:

class PartyAnimal {
    function __construct() {
        echo "正在构造\n";
    }
    function __destruct() {
        echo "正在析构\n";
    }
}

echo "1\n";
$x = new PartyAnimal();
echo "2\n";
$y = new PartyAnimal();
echo "3\n";

运行这段代码时,顺序如下:

  1. 打印 “1”。
  2. 创建 $x 对象,触发其构造函数,打印 “正在构造”。
  3. 打印 “2”。
  4. 创建 $y 对象,触发其构造函数,打印 “正在构造”。
  5. 打印 “3”。
  6. 程序执行完毕。在请求-响应周期结束时,PHP会进行垃圾回收,销毁 $x$y 对象。在销毁每个对象前,会自动调用其析构函数,因此会依次打印 “正在析构” 和 “正在析构”。

在PHP中,析构函数的调用比其他一些语言更可预测,因为PHP明确知道一个HTTP请求何时结束。


实例与构造函数参数 🎯

每个类可以创建多个实例,每个实例都拥有自己独立的变量。构造函数的一个常见用途是通过传入参数来个性化每个实例。

让我们通过一个“问候语翻译器”的例子来看看如何向构造函数传递参数:

class Hello {
    private $lang; // 实例变量

    function __construct($language) {
        $this->lang = $language; // 用传入的参数初始化实例变量
    }

    function greet() {
        if ($this->lang == 'fr') return 'Bonjour';
        if ($this->lang == 'es') return 'Hola';
        return 'Hello';
    }
}

$hi = new Hello('es'); // 创建对象,构造函数将 'es' 存入 $this->lang
echo $hi->greet(); // 输出: Hola

在上述代码中:

  • 创建 $hi 对象时,我们传入了参数 'es'
  • PHP构建对象,然后调用构造函数 __construct('es')
  • 构造函数将传入的 $language 参数值(‘es’)赋值给该实例的私有变量 $this->lang
  • 之后,当我们调用 $hi->greet() 方法时,该方法内部检查 $this->lang 的值,因为它是 ‘es’,所以返回 “Hola”。

通过构造函数传递参数,我们可以定制每个对象的行为,使其在创建时就具备独特的初始状态。


总结 📝

本节课中我们一起学习了PHP对象的生命周期。我们了解到,对象从基于类模板创建开始,会通过 __construct() 方法进行初始化,你可以利用它来设置实例的初始状态。在对象的使命完成、程序运行结束时,PHP会通过 __destruct() 方法通知对象进行清理。我们还实践了如何向构造函数传递参数,从而在创建时为每个对象实例赋予独特的属性。理解创建与销毁的时机,是有效管理对象和资源的关键。

下一节,我们将讨论继承,学习如何让一个新对象获取并扩展另一个对象的能力。

075:PHP中的对象继承

在本节课中,我们将要学习PHP中面向对象编程的一个核心概念:继承。我们将探讨什么是继承、如何通过extends关键字实现它,以及类成员的可见性(publicprotectedprivate)。最后,我们还会了解一种动态创建对象的特殊方式。


🧬 什么是继承?

上一节我们介绍了类和对象的基本概念,本节中我们来看看继承。继承是面向对象编程中的一个基本概念,它允许我们创建一个新类(子类)来继承另一个类(父类)的属性和方法。在PHP中,继承是一种强大的机制,它能帮助我们避免代码重复,并最终形成类的层次结构。

继承也被称为“子类化”。你可以将其理解为父类与子类的关系,或者基类与扩展类的关系。这些概念的本质是相同的:一个类是原始模板,另一个类则是扩展了功能的副本。

在PHP中,我们使用 extends 关键字来实现继承。


🔧 继承的实现

以下是继承的一个简单示例。我们首先有一个之前用过的 Hello 类。

class Hello {
    public $lang;
    function __construct($lang) {
        $this->lang = $lang;
    }
    function greet() {
        if ($this->lang == 'fr') return 'Bonjour';
        if ($this->lang == 'es') return 'Hola';
        return 'Hello';
    }
}

现在,如果我们想创建一个新的 Social 类来扩展 Hello 类的功能,可以这样做:

class Social extends Hello {
    function bye() {
        if ($this->lang == 'fr') return 'Au revoir';
        if ($this->lang == 'es') return 'Adios';
        return 'Goodbye';
    }
}

extends Hello 这行代码意味着:将 Hello 类中的所有内容(属性和方法)都“拉入” Social 类中。因此,Social 类并非从零开始,它已经拥有了 $lang 属性、__construct 构造函数和 greet 方法。然后,我们在其中添加了新的 bye 方法。

现在,我们有了两个模板:Hello 模板和 Social 模板。我们可以这样使用:

$obj = new Social('es');
echo $obj->greet(); // 输出:Hola
echo $obj->bye();   // 输出:Adios

当我们创建 Social 对象并传入 'es' 时,会调用从父类继承来的构造函数。我们可以调用继承来的 greet 方法,也可以调用子类独有的 bye 方法。这是一种复用类功能、避免重复编写代码的有效方式。

父类通常被称为基类,子类则被称为扩展类或派生类。


🛡️ 类成员的可见性

到目前为止,我们一直在类内部和外部自由地访问属性和方法。这是因为我们默认将它们标记为了 public。现在,我们来详细了解一下类成员的可见性。

可见性关键字用于控制类中属性和方法的访问权限,其核心目的是向外部世界隐藏类的内部复杂性。以下是三个关键字:

  • public(公共):可以在类内部、外部以及任何派生类中访问。
  • protected(受保护):可以在类内部及其派生类中访问,但不能从类外部直接访问。
  • private(私有):只能在定义它的类内部访问,不能在派生类或类外部访问。

以下是它们的工作原理总结:

可见性 类内部 派生类内部 类外部
public
protected
private

设置可见性的原因在于,有时你希望某些变量或方法仅供类内部使用,不希望外部代码直接修改,以保持内部状态的一致性。private 就划定了这样一条明确的界限。如果你不介意外部访问,则可以将其设为 public


可见性示例

以下是一个展示可见性如何工作的简单示例:

class MyClass {
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello() {
        echo $this->public;    // 可以
        echo $this->protected; // 可以
        echo $this->private;   // 可以
    }
}

$obj = new MyClass();
echo $obj->public; // 可以
// echo $obj->protected; // 致命错误
// echo $obj->private;   // 致命错误
$obj->printHello(); // 输出 Public, Protected, Private

在类外部,我们只能访问 public 成员。protectedprivate 成员无法从类外部直接访问。

现在,我们创建一个派生类:

class MyClass2 extends MyClass {
    function printFromChild() {
        echo $this->public;    // 可以
        echo $this->protected; // 可以
        // echo $this->private; // 致命错误
    }
}

$obj2 = new MyClass2();
$obj2->printFromChild(); // 可以访问 public 和 protected

在派生类内部,可以访问从父类继承来的 publicprotected 成员,但不能访问 private 成员。


🎭 动态创建对象(stdClass

最后,我想向你展示一种有时会遇到的特殊编码方式。有些程序员并不总是使用 class 关键字预先定义类,而是希望在运行时动态地创建对象并为其添加属性。

PHP 提供了一个内置的通用空类 stdClass。你可以像下面这样使用它:

$player = new stdClass();
$player->name = 'Charles';
$player->score = 100;
$player->score++;
print_r($player);

这段代码创建了一个 stdClass 对象,然后动态地为它添加了 namescore 属性。由于我们是在类外部添加属性,它们本质上就是 public 的。

然而,一种更优雅的方式是预先定义一个类作为模板:

class Player {
    public $name;
    public $score;
}

$player = new Player();
$player->name = 'Charles';
$player->score = 100;
$player->score++;
print_r($player);

这样,Player 类明确地定义了一个玩家应该具有 namescore 属性。当你看到 stdClass 这种用法时,不必感到困惑。这是一种早期常见的、将对象当作一种更“漂亮”的关联数组来使用的模式。


📚 总结

本节课中我们一起学习了PHP中对象继承的核心知识。

我们首先了解了继承的概念及其通过 extends 关键字实现的方式,它帮助我们构建类层次结构并避免代码重复。

接着,我们深入探讨了类成员的三种可见性publicprotectedprivate,理解了它们如何控制对属性和方法的访问权限,从而实现封装。

最后,我们简要介绍了使用 stdClass 动态创建对象的方法,并对比了其与正规定义类模板的区别。

面向对象编程的理解和应用需要循序渐进。希望本节课的内容能为你将来解决更复杂的问题、更深入地使用OOP打下良好的基础。在接下来的课程中,我们将继续探索更多面向对象编程的实用特性。

076:在Macintosh上安装MAMP 🍎

在本节课中,我们将学习如何在Macintosh电脑上安装MAMP软件,并配置PHP开发环境,最后创建一个简单的PHP网页来验证安装是否成功。

概述

MAMP是一个集成了Apache服务器、MySQL数据库和PHP的本地开发环境软件包。通过安装MAMP,我们可以在自己的Mac电脑上搭建一个Web服务器,用于开发和测试PHP应用程序,而无需连接到互联网。本节教程将引导你完成从下载、安装到基本配置的全过程。

下载MAMP

首先,我们需要从MAMP的官方网站下载安装程序。

  1. 打开浏览器,访问 mamp.info
  2. 在网站上找到适用于Mac的MAMP版本并点击下载。

下载完成后,安装文件通常会保存在你的“下载”文件夹中。

安装MAMP

接下来,我们将运行安装程序并完成MAMP的安装。

  1. 打开“访达”,进入“下载”文件夹。
  2. 找到下载的MAMP安装文件(通常是一个 .dmg 文件),双击打开。
  3. 在弹出的安装窗口中,将MAMP图标拖拽到“应用程序”文件夹的图标上,以完成安装。

安装过程会持续几分钟。安装完成后,你可以在“应用程序”文件夹中找到MAMP。

启动与验证MAMP

安装完成后,让我们启动MAMP并查看其基本信息。

  1. 进入“应用程序”文件夹,找到并打开“MAMP”应用程序。
  2. MAMP控制面板将会启动。点击“Start Servers”按钮,启动Apache和MySQL服务。当按钮旁的指示灯变为绿色时,表示服务已成功运行。
  3. 在MAMP控制面板中,点击“Open WebStart page”按钮,这将在浏览器中打开MAMP的欢迎页面。
  4. 在欢迎页面中,点击“PHPInfo”链接。这个页面显示了当前PHP环境的详细配置信息,对我们后续的配置很有帮助。

配置PHP开发环境

默认情况下,MAMP的PHP配置是为生产环境优化的,会隐藏错误信息。但对于开发来说,我们需要看到所有错误以便调试。因此,我们需要修改PHP的配置文件。

上一节我们启动了MAMP并查看了PHP信息,本节中我们来看看如何修改配置以开启错误显示。

  1. 在MAMP的PHPInfo页面中,找到“Loaded Configuration File”这一行。它指明了当前生效的PHP配置文件(php.ini)的路径,例如:/Applications/MAMP/bin/php/php7.1.0/conf/php.ini
  2. 使用文本编辑器(如TextEdit、Sublime Text或VS Code)打开这个 php.ini 文件。
  3. 在文件中搜索 display_errors 这个配置项。你会找到类似下面两行:
    display_errors = Off
    display_startup_errors = Off
    
  4. 将这两行的值从 Off 改为 On
    display_errors = On
    display_startup_errors = On
    

    注意:在生产环境的网站上开启错误显示是不安全的,但我们在本地开发时这样做可以快速定位问题。

  5. 保存并关闭 php.ini 文件。

为了使新的配置生效,我们需要重启MAMP服务器。

  1. 回到MAMP控制面板,点击“Stop Servers”按钮停止服务。
  2. 等待服务完全停止后,再次点击“Start Servers”按钮重新启动。
  3. 刷新浏览器中的PHPInfo页面。
  4. 在页面中搜索 display_errors,确认其状态已变为 On。这表示配置修改成功。

创建第一个PHP网页

现在,我们的开发环境已经配置好了,让我们创建一个简单的PHP文件来测试一切是否正常工作。

上一节我们配置了PHP环境,本节我们将动手创建第一个程序。

首先,我们需要知道网站文件应该放在哪里。MAMP的Web服务器根目录是 htdocs 文件夹。

  1. 打开“访达”,进入路径:Macintosh HD -> 应用程序 -> MAMP -> htdocs。所有需要通过 localhost 访问的网页文件都应放在这个文件夹或其子文件夹中。
  2. 为了保持项目整洁,我们在 htdocs 文件夹内创建一个名为 first 的新文件夹。
  3. 打开文本编辑器,创建一个新文件。
  4. 在文件中输入以下PHP代码:
    <?php
    echo "Hello from my first web page!";
    ?>
    
  5. 将这个文件保存到刚创建的 first 文件夹中,并将文件命名为 index.php

现在,让我们在浏览器中查看这个页面。

  1. 确保MAMP服务器正在运行(控制面板指示灯为绿色)。
  2. 打开浏览器,访问地址:http://localhost:8888/first/
    • localhost 代表你的本地电脑。
    • 8888 是MAMP默认使用的端口号。
    • /first/ 指向我们刚创建的文件夹。
  3. 浏览器会自动寻找并打开 first 文件夹下的 index.php 文件(这是Web服务器的默认文档之一)。你应该能在页面上看到“Hello from my first web page!”这行文字。

总结

本节课中我们一起学习了在Macintosh上搭建PHP本地开发环境的完整流程。我们首先从官网下载了MAMP并完成了安装。接着,我们启动了MAMP服务,并通过修改 php.ini 配置文件开启了PHP错误显示功能,这对于开发调试至关重要。最后,我们在MAMP的 htdocs 目录下创建了项目文件夹和第一个PHP文件 index.php,并通过浏览器成功访问,验证了整个环境的可用性。现在,你已经拥有了一个功能完备的本地PHP开发环境,可以开始构建你的Web应用程序了。

077:在Windows 10上安装MAMP与编写第一个PHP程序 🖥️

在本节课中,我们将学习如何在Windows 10系统上安装MAMP集成开发环境,并利用它编写和运行第一个PHP程序。整个过程包括下载安装、配置服务器、编写代码以及设置错误显示。

安装MAMP

首先,我们需要下载并安装MAMP。访问MAMP官方网站,选择Windows版本进行下载。下载完成后,运行安装程序。

安装程序启动后,选择“是”以继续。在语言选择界面,选择“English”并点击“Next”。

在接下来的界面中,取消勾选安装“MAMP PRO”的选项。这是一个付费版本,本教程使用免费版本即可。然后,阅读并接受许可协议。

选择MAMP的安装路径,建议使用默认路径 C:\MAMP。之后,连续点击“Next”完成安装。安装结束后,运行MAMP。

MAMP启动后,桌面上会出现其快捷方式。启动MAMP控制面板,点击“Start Servers”按钮以启动Apache和MySQL服务器。

在服务器启动过程中,Windows防火墙可能会弹出安全警报。这是非常重要的步骤,必须允许Apache HTTP Server和MySQL的通信通过防火墙。请勾选两个选项并点击“允许访问”。

服务器成功启动后,点击“Open Start Page”按钮。在打开的浏览器页面中,你可以查看PHP信息或打开phpMyAdmin。

如果phpMyAdmin页面能够正常显示,恭喜你,MAMP已成功安装并运行。

安装文本编辑器Atom

上一节我们成功安装了MAMP服务器环境,本节我们将安装一个代码编辑器。虽然你可以使用任何喜欢的文本编辑器,但强烈建议不要使用记事本或Word,因为它们可能破坏代码文件的格式。我们推荐使用Atom,因为它支持语法高亮,并且在Windows、Mac和Linux上表现一致。

从Atom官网下载Windows安装程序并运行。

按照安装向导的提示完成Atom的安装。

编写第一个PHP程序

现在,我们已经准备好了服务器环境和代码编辑器,可以开始编写第一个PHP程序了。

首先,确保MAMP中的Apache和MySQL服务器已经启动。然后,在Atom中创建一个新文件。

以下是创建文件的步骤:

  1. 在Atom中,点击 File -> New File
  2. 在新文件中输入以下HTML代码:
    <h1>Hello from a web page</h1>
    
  3. 点击 File -> Save 保存文件。
  4. 在保存对话框中,导航到MAMP的Web文档根目录:C:\MAMP\htdocs\
  5. 在该目录下创建一个新文件夹,命名为 first
  6. 将文件保存到 first 文件夹中,并命名为 index.phpindex.php 是一个特殊名称,当浏览器访问一个目录时,服务器默认会寻找并执行该文件。

现在,打开浏览器,访问以下地址:http://localhost/first/index.php。你应该能看到显示“Hello from a web page”的页面。

到目前为止,我们只是输出了静态HTML。接下来,让我们在PHP中执行一些代码。

index.php 文件中,添加PHP代码块。PHP代码需要包裹在 <?php?> 标签中。

<?php
    echo "Hi there.\n";
?>

保存文件并刷新浏览器页面,你会看到“Hi there.”被输出。

在PHP中,服务器会执行 <?php ... ?> 标签内的所有代码,并将 echo 语句输出的内容插入到最终的HTML页面中。

我们可以添加更多逻辑,例如使用变量:

<?php
    $x = 6 * 7;
    echo "The answer is " . $x . ".\n";
?>

保存并刷新后,页面将显示“The answer is 42.”。

配置PHP错误显示

在开发过程中,看到详细的错误信息对于调试代码至关重要。然而,出于安全考虑,MAMP默认关闭了在网页上显示错误的功能。我们需要手动开启它。

首先,在MAMP的启动页面,点击“PHPInfo”链接。在打开的页面中,找到“Loaded Configuration File”这一行,记下PHP配置文件的路径,例如 C:\MAMP\conf\php7.1.5\php.ini

然后,用Atom打开这个 php.ini 配置文件。在文件中搜索 display_errors 设置。

找到以下两行:

display_errors = Off
display_startup_errors = Off

将它们修改为:

display_errors = On
display_startup_errors = On

修改后保存文件。

由于修改了服务器配置,需要重启MAMP的Apache和MySQL服务器才能使更改生效。在MAMP控制面板中,先点击“Stop Servers”,等待停止后,再点击“Start Servers”。

服务器重启后,返回你的PHP代码文件,故意制造一个语法错误,例如删除一行代码末尾的分号。

<?php
    echo "Hi there" // 缺少分号
?>

保存并刷新浏览器页面。现在,你应该能看到一个明确的错误信息,例如“Parse error: syntax error, unexpected end of file... on line 3”,这能帮助你快速定位问题。

修复错误(添加上分号),保存文件,再次刷新页面,程序应恢复正常。

在开发初期就开启错误显示功能可以节省大量调试时间,避免因只有模糊的“500错误”而不知所措。

总结

本节课中,我们一起学习了Web应用开发环境的搭建。我们首先在Windows 10上安装并配置了MAMP服务器套件,包括Apache和MySQL。然后,我们安装了Atom代码编辑器用于编写程序。接着,我们在MAMP的 htdocs 目录下创建了第一个PHP文件,并学习了如何混合HTML与PHP代码,以及使用变量和 echo 进行输出。最后,我们通过修改 php.ini 配置文件,开启了PHP的错误显示功能,这对于后续的代码调试和开发至关重要。现在,你已经拥有了一个完整的本地PHP开发环境,可以开始构建更复杂的Web应用程序了。

078:在Windows 10上安装XAMPP 🖥️

在本节课中,我们将学习如何在Windows 10操作系统上安装XAMPP。XAMPP是一个集成了Apache、MySQL、PHP和Perl的免费开源软件包,是搭建本地Web开发环境的理想工具。我们将从下载开始,逐步完成安装、配置,并运行一个简单的PHP程序来验证安装是否成功。

下载XAMPP安装程序

首先,我们需要从Apache Friends官方网站下载XAMPP的Windows版本安装程序。

以下是下载步骤:

  1. 访问Apache Friends网站。
  2. 找到适用于Windows的XAMPP版本。
  3. 点击下载链接,开始下载安装程序。

下载完成后,安装程序通常位于系统的“下载”文件夹中。

运行安装程序并完成安装

上一节我们下载了安装程序,本节中我们来看看如何运行它并进行安装。

以下是安装步骤:

  1. 找到并运行下载好的XAMPP安装程序。
  2. 安装向导启动后,建议使用默认的安装路径,即 C:\xampp。避免安装在“Program Files”目录下,可以简化后续操作。
  3. 在组件选择界面,可以根据需要取消勾选不需要的组件,例如Tomcat、Perl和Fake Sendmail。对于基础的Web开发,通常只需要Apache和MySQL。
  4. 确认选择后,开始安装过程。安装可能需要一些时间。

安装完成后,先不要立即启动XAMPP控制面板。

启动XAMPP控制面板并运行服务

现在安装已经完成,本节中我们来看看如何找到并启动XAMPP控制面板,以运行Web服务器和数据库。

以下是启动步骤:

  1. 打开文件资源管理器,进入XAMPP的安装目录(例如 C:\xampp)。
  2. 在该目录下找到名为 xampp-control.exe 的程序并运行它。
  3. 首次启动时,可能会弹出安全警告或语言选择对话框,请根据提示进行操作。对于安全警告,通常需要点击“允许”或“是”。
  4. 控制面板启动后,为了方便后续使用,可以右键点击任务栏上的控制面板图标,选择“固定到任务栏”。
  5. 在控制面板中,点击Apache和MySQL模块旁边的“Start”按钮,分别启动Web服务器和数据库服务器。启动成功后,模块名称旁会显示绿色的“Running”标识。

验证安装与配置PHP

服务成功启动后,我们可以通过访问本地仪表盘来验证安装。同时,确保PHP的配置适合开发环境。

以下是验证和检查步骤:

  1. 打开浏览器,访问 http://localhost。如果看到XAMPP的欢迎仪表盘页面,说明Apache服务器运行正常。
  2. 在仪表盘页面,可以点击“PHPInfo”链接查看详细的PHP配置信息。
  3. 一个关键的开发配置是 display_errors 设置。在PHPInfo页面中搜索“display_errors”。对于开发环境,此设置应为 On,以便在页面上显示错误信息,方便调试。对于生产环境,则应设为 Off 以隐藏错误信息。
  4. 如果需要修改此配置,可以点击XAMPP控制面板中Apache模块所在行的“Config”按钮,选择“PHP (php.ini)”。
  5. 在打开的配置文件中,使用 Ctrl+F 搜索 display_errors,将其值修改为 On。同时,也可以检查 display_startup_errors 的设置。
  6. 保存配置文件后,必须在XAMPP控制面板中重启Apache服务器(先Stop,再Start),修改才能生效。

创建并运行第一个PHP程序

环境配置妥当后,本节中我们来看看如何编写一个简单的PHP程序,并通过本地服务器运行它。

以下是创建和测试步骤:

  1. 打开你喜欢的文本编辑器(例如VS Code、Sublime Text或Notepad++)。
  2. 创建一个新的文件,输入以下混合了HTML和PHP的代码:
    <!DOCTYPE html>
    <html>
    <head>
        <title>My First PHP</title>
    </head>
    <body>
        <h1>Hello World from HTML</h1>
        <?php
            echo “<p>This is coming from PHP.</p>”;
            $sum = 6 + 4;
            echo “<p>The sum of 6 and 4 is: “ . $sum . “</p>”;
        ?>
    </body>
    </html>
    
  3. 将文件保存到XAMPP的Web根目录下。默认路径是 C:\xampp\htdocs\。建议在该目录下为你的项目创建一个新文件夹,例如 first
  4. 将文件以 index.php 为名保存在 first 文件夹中,完整路径为 C:\xampp\htdocs\first\index.php
  5. 打开浏览器,访问 http://localhost/first/index.php。如果页面正确显示了HTML标题和PHP计算出的结果,说明你的本地开发环境已经成功搭建并可以运行PHP程序了。

总结

本节课中我们一起学习了在Windows 10上搭建PHP本地开发环境的完整流程。我们从下载XAMPP安装程序开始,逐步完成了安装、启动Apache与MySQL服务、验证安装以及配置关键的PHP开发设置。最后,我们通过创建并运行一个包含HTML和PHP代码的简单网页,成功验证了整个环境的工作状态。现在,你已经拥有了一个功能完备的本地服务器,可以开始进行数据库构建和PHP代码编写等Web开发学习了。

079:Windows系统使用Ngrok连接自动评分器 🖥️

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地计算机上的Web应用程序临时暴露到公网,以便完成需要与远程自动评分器交互的作业。

概述

当你完成一个Web应用程序作业后,代码通常运行在你的本地计算机上,地址类似于 localhost。然而,远程的自动评分器无法直接访问你本地的 localhost 地址,因为它被防火墙隔离在互联网之外。为了解决这个问题,我们需要使用Ngrok。Ngrok能创建一个临时的、可公开访问的隧道,将互联网流量转发到你本地运行的服务器上,从而使自动评分器能够访问和测试你的应用程序。

安装与配置Ngrok

上一节我们明确了问题的核心是本地服务器无法被外部访问。本节中,我们来看看如何安装和启动Ngrok来解决这个问题。

以下是下载和放置Ngrok的步骤:

  1. 访问Ngrok官网(ngrok.com)并下载适用于Windows系统的版本。
  2. 下载完成后,打开压缩包。
  3. ngrok.exe 文件解压到方便使用的位置,例如桌面。这样便于在命令行中快速访问。

启动Ngrok并创建隧道

现在我们已经准备好了Ngrok可执行文件,接下来需要启动它,为我们的本地Web服务器创建隧道。

  1. 打开命令提示符(CMD)。
  2. 使用 cd 命令切换到存放 ngrok.exe 的目录。例如,如果放在桌面,可以输入:
    cd %USERPROFILE%\Desktop
    
  3. 启动Ngrok,并指定要转发的本地端口。假设你的Web应用运行在默认的80端口,命令如下:
    ngrok http 80
    
  4. 执行命令后,Ngrok会启动并在命令行中显示一个临时的公共URL(例如 https://xxxxxx.ngrok.io)。这个URL就是你的本地应用在互联网上的临时地址。

提交作业到自动评分器

我们已经获得了可以公开访问的临时地址。本节中,我们来看看如何利用这个地址完成作业提交。

  1. 复制Ngrok提供的临时公共URL(例如 https://xxxxxx.ngrok.io)。
  2. 在你的浏览器中访问这个URL,确认它能正确显示你本地运行的Web应用程序。
  3. 如果应用程序有特定的测试页面(例如 guessinggame.php?guess=12),请将完整的路径追加到Ngrok URL后面,形成完整的可访问地址(例如 https://xxxxxx.ngrok.io/guessinggame.php?guess=12)。
  4. 最后,将这个完整的、可公开访问的URL粘贴到课程自动评分器的提交框中,并运行评分。你可以在Ngrok的命令行窗口中看到自动评分器与你的应用之间发生的所有请求和响应记录。

完成后的操作

作业评分完成后,为了安全起见,你应该关闭Ngrok隧道。

  1. 回到运行Ngrok的命令行窗口。
  2. 按下 Ctrl + C 组合键来终止Ngrok进程。
  3. 隧道关闭后,之前的临时公共URL将立即失效,外部无法再访问你的本地应用。
  4. 请注意,每次重新启动Ngrok,它都会生成一个全新的临时URL。

总结

本节课中我们一起学习了如何使用Ngrok工具。我们了解到,Ngrok通过在公网和你的本地localhost之间建立临时隧道,解决了自动评分器无法访问本地服务器的问题。关键步骤包括:下载Ngrok、在命令行中启动并指定端口、获取临时公共URL,以及最后在提交作业后安全地关闭隧道。掌握这个方法,你就能顺利提交那些需要与远程服务器交互的Web应用作业了。

080:Macintosh系统使用Ngrok连接自动评分器 🖥️➡️🌐

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地电脑(如Macintosh)上的Web应用程序暴露到公共互联网,以便课程中的自动评分器能够访问并评估你的作业。

概述

当你在本地开发环境中(例如使用MAMP)运行PHP Web应用程序时,你的服务器地址通常是localhost127.0.0.1。这意味着只有你本机的浏览器可以访问它。然而,课程中的自动评分器运行在真实的互联网上,它无法直接连接到你的本地localhost地址。为了解决这个问题,我们需要使用一个名为Ngrok的工具,它能在公共互联网和你的本地服务器之间建立一个安全的隧道。

问题:本地服务器与互联网的隔离

假设你正在完成“猜数字游戏”的作业。你的代码运行在本地MAMP服务器上,地址类似于http://localhost:8888/assignments/guess/guess.php

你可以通过浏览器访问这个地址并进行测试。例如,访问http://localhost:8888/assignments/guess/guess.php?guess=42

但是,如果你直接将这个localhost地址提交给自动评分器,评分器会尝试连接它并失败。因为localhost这个地址只在你的电脑内部有意义,在互联网上的评分器无法找到它。你会收到类似“连接被拒绝”的错误。

解决方案:使用Ngrok建立隧道

Ngrok是一个轻量级的工具,它能创建一个从公共互联网到你本地服务器的临时通道。其核心原理可以概括为以下流程:

互联网上的自动评分器 <---> Ngrok提供的公共URL (如 https://abc123.ngrok.io) <---> 你的本地服务器 (localhost:8888)

上一节我们介绍了本地服务器无法被互联网访问的问题,本节中我们来看看如何使用Ngrok来解决它。

步骤一:下载并安装Ngrok

首先,你需要下载Ngrok软件。

  1. 访问Ngrok的官方网站下载页面。
  2. 根据你的操作系统(Mac或Windows)选择对应的版本进行下载。
  3. 下载完成后,文件通常位于你的“下载”文件夹中。对于Mac系统,它是一个可执行的压缩文件。

步骤二:在终端中运行Ngrok

你需要通过终端(Mac/Linux)或命令提示符(Windows)来运行Ngrok。

  1. 打开终端应用程序。
  2. 使用cd命令导航到存放ngrok文件的目录。例如,如果它在下载文件夹:
    cd ~/Downloads
    
  3. 运行以下命令来启动隧道,将本地端口8888暴露到互联网:
    ./ngrok http 8888
    
    这个命令告诉Ngrok:监听本地8888端口,并为其创建一个公共的访问地址。

运行成功后,终端会显示类似以下的信息:

Forwarding    http://abc123.ngrok.io -> http://localhost:8888
Forwarding    https://abc123.ngrok.io -> http://localhost:8888

关键点:屏幕上显示的https://abc123.ngrok.io(你的地址会不同)就是一个真实的、可以在互联网任何地方访问的URL。只要Ngrok程序在运行,这个隧道就有效。

步骤三:使用Ngrok URL进行测试和提交

现在,你可以用这个新的Ngrok URL替换原来的localhost地址。

  1. 在浏览器中访问:https://abc123.ngrok.io/assignments/guess/guess.php。你应该能看到和访问localhost时完全一样的页面。
  2. 将这个完整的Ngrok URL(例如 https://abc123.ngrok.io/assignments/guess/guess.php)复制下来。
  3. 回到课程的自动评分器页面,将复制的URL粘贴到提交框中,然后点击“评估”。

此时,自动评分器会通过互联网访问你的Ngrok URL,Ngrok会将请求转发给你的本地服务器,再将服务器的响应传回给评分器。你可以在运行Ngrok的终端窗口里看到所有的请求和响应日志。

步骤四:调试与完成

在自动评分器运行测试时,请仔细阅读其反馈。

  • 如果测试失败:不要慌张。评分器的错误信息通常会指出问题所在,例如“在标题标签中未找到‘Chuck Severance’”,或者“猜测的数字太高”。你需要根据这些提示,回头修改你本地的PHP代码(例如,将正确的答案从42改为37,或者在HTML标题中添加要求的名字),保存文件,然后重新在评分器中运行测试。
  • 使用Ngrok监控:你还可以在浏览器中访问 http://127.0.0.1:4040(这是Ngrok提供的本地监控界面),来详细查看通过隧道的所有网络请求和响应数据,这有助于深度调试。

当你完成作业并通过所有测试后,可以回到运行Ngrok的终端窗口,按下 Control + C 键来停止Ngrok服务。隧道会立即关闭,之前的Ngrok URL也将失效。

请注意:每次重新启动Ngrok,它都会生成一个全新的随机URL。因此,如果你需要再次提交或测试,必须使用最新的URL。

总结

本节课中我们一起学习了如何利用Ngrok工具桥接本地开发环境与公共互联网。我们了解了将localhost服务暴露出去的必要性,逐步实践了下载、运行Ngrok并创建隧道的流程,最后学会了如何使用生成的公共URL让自动评分器成功访问并评估我们本地的Web应用作业。记住,Ngrok是一个临时解决方案,每次启动都会获得新地址,在开发和测试阶段非常实用。

081:纽约市 🗽

在本节课中,我们将回顾密歇根大学《面向所有人的Web应用程序》课程在纽约市举行的一次大型线下办公时间活动。我们将了解活动的基本情况、参与者的互动以及活动中的一些传统环节。


大家好,我们现在在纽约市,举行一场规模盛大的线下办公时间活动,地点在休斯顿街大厅。这是我的密歇根口音在发挥作用。和往常一样,这次活动的规模可能与布莱切利公园的那次一样大。我们会轮流进行,让大家打个招呼。大家已经参加过足够多次,都清楚这个流程了。

以下是参与者的自我介绍:

  • 大家好,我是Latasha。很高兴终于来到这里见到查克博士。
  • 很高兴见到查克博士,他太棒了,是位很棒的老师。欢迎。
  • 嘿,我是Marceello。我很高兴能在这里见到写书的人,他是最棒的。
  • 大家好,我是Alessandra。查克博士很棒。认识你也很棒。
  • 嗨,我是Emily。这门课程太棒了。非常感谢。
  • 别担心David和T。查克的课程总是最完美的。我真的很荣幸能来到这里见面。我们只是尽力而为,幸运的是它奏效了。
  • 我的名字是… 查克博士是个很棒的人,也是很棒的老师。我们在纽约这里有非常非常多的能量。
  • 我是Dan,我就在楼上工作,所以来这里非常方便。
  • 我爱查克博士。
  • 我是Leah,我唯一完成过的课程就是你的课程。恭喜。我也是,我退出了R课程。如果你正在学习查克博士的任何一门课程,你将会享受到一场知识盛宴。
  • 我是Sean。我上个月完成了这门课程,迫不及待地想继续我的教育。
  • 嗨,我叫Madu。我很高兴查克博士来到纽约市。我一直希望他能来这里,而他来了。我也一直希望能来纽约市。
  • 嗨,我是Pawell,我从波兰来。查克,从波兰来参加办公时间可是很长一段路。查克超级棒。如果你有机会亲自见到他,不要错过。
  • 大家好,我叫Michelle Kobe。感谢你精彩的Python课程。我认为在这个特定的办公时间活动里,除了我之外,你是唯一一个会编程的人。
  • 嗨,我是Joe。欢迎来到纽约市。
  • 嗨,我是Daniel,我也来自… 我来自加纳。加纳?是的,我们会议上有人来自加纳,有一大群人。应该去加纳聊聊。如果你来加纳,请来看看我们。我会在加纳举行办公时间。我知道这不是下一个地点,但我想也许夏末或秋天可以,因为加纳有很多人喜欢编程。我很高兴能让编程变得通俗易懂、易于吸收。

那么,你们知道撒哈拉以南非洲第一个接入互联网的国家是哪个吗?你们会说是埃及吗?不,那不是撒哈拉以南… 这不是… 我在跟你讲非洲地理,那听起来不对。文明,但是… 撒哈拉以南,对吧?如果你上过互联网历史课,你就会知道那是… 你有点太年轻了,甚至不记得那段历史了。从纽约市去… 成为Python,这是可能的,因为… 非常感谢。不过我得告诉你,纽约市在加纳之前就有了互联网。

所以,我们在这里。在这种情况下,我们还有一个传统,就是总是要感谢这些课程中导师们的出色贡献。那么,让我们来一轮掌声。

好了,就这样。实际上我目前没有任何旅行计划,但可能一周后我会安排行程。所以我们会有更多的办公时间活动。再次感谢大家,放轻松。


本节课中,我们一起回顾了在纽约市举行的一次课程线下聚会。我们看到了来自世界各地的学习者与导师查克博士的见面与交流,感受到了学习社群的热情。活动中,参与者分享了学习课程的体验和收获,查克博士也预告了未来的活动安排。这种线下互动是线上课程宝贵的延伸,增强了学习者的归属感和学习动力。

082:PHP数据库连接 🗄️

在本节课中,我们将学习如何将PHP与SQL数据库连接起来。我们将了解PHP如何作为中间层,接收用户请求,与数据库交互,并最终生成HTML响应。核心是使用PHP的PDO(便携式数据对象)库来安全、高效地执行数据库操作。


上一节我们介绍了PHP和SQL的基础知识,本节中我们来看看如何将它们结合起来。

现在我们将所有内容整合起来。我们已经讨论了SQL,也讨论了PHP。接下来我们要将它们连接在一起。这正是我最喜欢的图示开始发挥作用的地方:我们有一个请求-响应循环来回往复。现在我们要做的是,构建一个进入PHP的请求。

PHP将建立数据库连接并发送SQL命令。

SQL在此处来回传递。我们一直在做这件事,通过PHPMyAdmin直接与数据库对话。但现在我们要让PHP创建并发送SQL。

SQL将执行相同的操作:选择、读取、更新、删除等,然后返回一个称为记录集的结果。接着,我们将像读取文件一样遍历这个记录集。它基本上是一系列记录,我们将对其进行处理。

我们将输出一些HTML,然后生成响应。之后,它进入DOM,我们就能看到页面了。这就是我们今天要重点讨论的部分。

我们将使用一个名为PDO的库,即“便携式数据对象”,它是PHP 5的一部分。PHP 5是较旧的版本,而PHP 7是现代版本。


正如我所说,我们一直在这样做。我们拥有这个数据库服务器,并且一直通过使用PHPMyAdmin发送SQL来充当数据库管理员,然后结果直接显示在屏幕上。

现在,我们将使用PDO来完成这项工作,其中用户与我们的PHP代码对话。我们作为应用程序开发者编写一些SQL,发送它,然后结果返回给最终用户。因此,我们的工作是编写一些PHP代码来处理数据库。

我们不让用户直接与数据库对话,因为那样他们可能会看到不该看的东西。我们的职责是,只向他们展示我们应该展示的数据视图。


我提到过,我们目前处于PHP 5环境,并正在向PHP 7过渡。但在PHP 4及更早版本中,有另一种方法来完成这个任务。

PHP备受喜爱的一点是,即使在2、3、4版本中,访问SQL也非常容易。它是内置的。有一些例程,这些是遗留例程,适用于PHP 5之前的版本,例如 mysql_* 系列函数。

从PHP 4过渡到PHP 5时,他们决定进行改进。事实证明,在PHP 4中,针对不同的数据库(如Oracle、SQLite)有不同的函数集。这些函数并非全部内置,但MySQL和SQLite的是内置的。

发生了两件事。他们希望构建一个面向对象的版本。因为每个人都已经使用了这些函数大约15年,并且非常习惯它们。所以随着PHP 5的发布,他们做了两件事。

首先,他们转向了面向对象的方式。但事实证明,你只能建立一个连接。你甚至不能同时连接到两个数据库,除非先连接一个,断开,再连接另一个。除了我们非常熟悉它们之外,这种方式有很多不受欢迎的地方。

因此,他们希望做一个面向对象的版本,因为这样你可以打开一个到数据库A的连接,再打开一个到数据库B的连接,并为每个连接拥有一个对象。当你想与某个数据库对话时,就操作对应的对象,从而实现同时连接。这非常好,对吧?

他们不确定人们是否还想使用旧 mysql_* 函数的模式。因此,他们创建了一个新东西叫 MySQLi。它是一个面向对象的版本,但调用序列与我们钟爱的 mysql_* 例程完全相同。就像我们在面向对象课程中看到的 Date::add 一样,它只是将非面向对象的例程重做为面向对象的例程。他们这样做了。

然后他们又说,让我们从头开始,看看其他语言是如何实现一些非常漂亮的功能的。他们研究了所有不同的MySQL、Oracle等接口,思考哪些模式最优雅。于是他们创建了一个全新的东西,拥有全新的API,并不试图模仿旧有的样子。

当时我们不知道,在转向PHP 5时,我们所有人会转向这两种方式中的哪一种。旧的函数仍然能用,但它们有些过时且不太受欢迎。我当时正在教课,有一段时间我想,好吧,我有所有这些源代码、示例代码,我就用MySQLi吧。但我很快排除了这个想法,因为PDO中有太多让编写SQL变得更容易的绝妙特性。

所以争论持续了一段时间,但我想在这一点上,我们基本都使用PDO了。在这门课中,我只使用PDO。鉴于我刚才所说的一切,你可以争论一会儿,但我认为争论已经结束,PDO赢得了辩论。

我只想给你看一些示例代码,因为你可能会遇到旧代码。

以下是旧式的经典 mysql_* 函数示例。因为它总是以 mysql_ 开头,所以一看就知道这基本上是PHP 5之前的代码。它不是面向对象的,并且存在各种架构上的缺陷。但它之所以被怀念,是因为我们使用了它十多年并且非常擅长它。

// 遗留的 mysql_* 方式 (PHP < 5, 不推荐使用)
$link = mysql_connect('localhost', 'user', 'password');
mysql_select_db('database_name', $link);
$result = mysql_query('SELECT * FROM table', $link);
while ($row = mysql_fetch_array($result)) {
    // 处理每一行数据
}

然后是 MySQLi 方式。你看,它使用了 new 关键字,它是一个对象。但它只支持MySQL。函数 mysql_query 变成了 mysqli->query。这基本上是一对一的翻译,除了它是面向对象的语法。它的优点是,如果你的应用需要连接两个数据库,你可以建立多个数据库连接。

// MySQLi 方式 (面向对象风格)
$mysqli = new mysqli('localhost', 'user', 'password', 'database_name');
$result = $mysqli->query('SELECT * FROM table');
while ($row = $result->fetch_assoc()) {
    // 处理每一行数据
}

但我想很少有人真正使用过那个功能。所以我们最终选择了 PDO

PDO是一种面向对象的模式,它是一个新事物,它完成这些工作,并且会指明要使用哪种数据库等等。然后我们运行查询,这里有一个稍微不同的模式。这里的区别不仅在于PDO是面向对象的(O代表便携式数据对象),它还拥有不同的API模式。

就像我说的,我以为我会喜欢MySQLi,但现在我非常喜欢PDO,我不喜欢旧式的 mysql_*,也不那么喜欢MySQLi。所以我们在这里使用PDO。

你可能会看到那些旧代码,所以要有所准备。无论哪种方式,所有PHP代码的最终目标都是创建这个SQL字符串,将其发送到数据库,取回一些记录,然后遍历这些记录。


接下来,我们将实际讨论如何通过PHP插入数据。


本节课中我们一起学习了PHP与数据库连接的核心概念。我们回顾了从遗留的 mysql_* 函数到面向对象的MySQLi,再到如今广泛采用的PDO的演变过程。PDO以其面向对象的设计、支持多种数据库以及更安全的编程模式而胜出。我们理解了PHP在Web应用中的角色:作为中间层处理用户请求,生成SQL与数据库交互,并将结果转化为HTML响应。下一节我们将深入探讨如何使用PDO执行数据插入操作。

083:在PHP中执行SQL查询

在本节课中,我们将学习如何从PHP代码内部连接到MySQL数据库,并执行SQL查询来读取和插入数据。我们将从建立数据库连接开始,然后学习如何执行SELECTINSERT语句,并将数据库中的数据格式化为HTML输出。

概述:建立数据库与连接

上一节我们介绍了SQL的基础知识。本节中,我们来看看如何从PHP程序内部与数据库进行交互。首先,我们需要创建一个数据库、一个用户账户,并建立PHP到数据库的连接。

创建数据库和用户

在开始编写PHP代码之前,必须在MySQL服务器上设置好数据库。这通常通过命令行或phpMyAdmin等工具完成。

以下是创建数据库和用户账户的SQL命令示例:

CREATE DATABASE misc;
GRANT ALL ON misc.* TO 'fred'@'localhost' IDENTIFIED BY 'zap';
GRANT ALL ON misc.* TO 'fred'@'127.0.0.1' IDENTIFIED BY 'zap';

核心概念解释

  • CREATE DATABASE misc;:创建一个名为misc的新数据库。
  • GRANT ALL ...:授予用户fred从特定主机(localhost127.0.0.1)连接到misc数据库的所有权限,并设置密码为zap
  • 限制连接源(如localhost)是一种安全措施,它像一个防火墙,只允许来自同一台计算机内部的连接访问数据库,阻止外部网络的直接访问。

理解数据库连接

数据库服务器运行在计算机的特定网络端口上。MySQL的默认端口是3306。在PHP中,我们需要提供主机地址、端口号、数据库名、用户名和密码来建立连接。

对于运行MAMP的Mac用户,MySQL端口通常是8889。因此,连接信息会因开发环境而异。

在PHP中建立数据库连接

现在,我们进入PHP部分,学习如何用代码建立到MySQL的连接。

使用PDO进行连接

PHP Data Objects (PDO) 提供了一个统一的接口来连接和操作多种数据库。以下是建立连接的基本代码:

$pdo = new PDO('mysql:host=localhost;port=8889;dbname=misc',
               'fred', 'zap');

代码解析

  • new PDO(...):创建一个新的PDO对象,即数据库连接。
  • mysql:host=localhost;port=8889;dbname=misc:这是数据源名称(DSN),指定了数据库类型(mysql)、主机(localhost)、端口(8889)和数据库名(misc)。
  • 'fred', 'zap':连接所用的用户名和密码。

如果连接信息(如密码、端口)错误,这行代码将产生错误并终止脚本。正确配置连接通常是初学阶段最具挑战性的一步。

设置错误模式

为了在开发过程中更容易地调试错误,建议将PDO的错误模式设置为“异常模式”。这样,当SQL执行出错时,PDO会抛出异常,而不是静默失败。

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

执行SELECT查询并读取数据

成功建立连接后,我们就可以执行SQL查询了。首先从读取数据开始。

发送查询并获取结果

以下代码演示了如何执行一个SELECT查询并遍历所有返回的结果行:

$stmt = $pdo->query("SELECT * FROM users");
while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
    print_r($row);
}

代码解析

  1. $pdo->query("SELECT * FROM users"):通过连接对象$pdoquery方法,向数据库发送一个SQL查询字符串。返回的结果是一个PDOStatement对象,我们将其存储在$stmt变量中。
  2. $stmt->fetch(PDO::FETCH_ASSOC):从结果集中获取下一行数据。PDO::FETCH_ASSOC参数指定以关联数组的形式返回数据,数组的键是数据库的列名(如user_id, name)。
  3. while循环会持续执行,直到fetch方法没有更多数据可返回(返回false)为止。
  4. print_r($row):打印出每一行的数组内容。

将数据格式化为HTML输出

通常,我们不会直接打印原始数据,而是将其格式化为美观的HTML呈现给用户。

以下是如何将查询结果转换为一个HTML表格的示例:

echo "<table border='1'>\n";
$stmt = $pdo->query("SELECT name, email FROM users");
while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
    echo "<tr><td>";
    echo htmlentities($row['name']);
    echo "</td><td>";
    echo htmlentities($row['email']);
    echo "</td></tr>\n";
}
echo "</table>\n";

代码解析

  • 首先输出HTML表格的开始标签。
  • 执行一个更具体的查询,只选择nameemail列。
  • 在循环中,为每一行数据输出HTML表格行(<tr>)和单元格(<td>)。
  • htmlentities()函数用于将数据中的特殊字符转换为HTML实体,这是一种重要的安全措施,可以防止跨站脚本(XSS)攻击。
  • 最后输出表格的结束标签。

这个过程本质上是将数据库中的数据转换并格式化为HTML代码。

代码组织:分离连接配置

在一个拥有多个PHP脚本的网站中,每个脚本都需要数据库连接。为了避免在多个文件中重复编写连接代码,最佳实践是将连接配置放在一个单独的文件中,然后在其他文件中引入它。

创建独立的连接文件

创建一个名为pdo.php的文件,内容如下:

<?php
$pdo = new PDO('mysql:host=localhost;port=8889;dbname=misc',
               'fred', 'zap');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
?>

在其他脚本中引入连接

在需要使用数据库的脚本中,使用require_once语句引入这个连接文件:

<?php
require_once "pdo.php";
// 现在可以直接使用 $pdo 变量来执行查询
$stmt = $pdo->query("SELECT * FROM users");
...
?>

这种方法使代码更易于维护。如果需要修改数据库密码或主机名,只需更新pdo.php一个文件即可。

执行INSERT查询

除了读取数据,我们同样需要通过PHP向数据库插入新数据。

使用PDO执行INSERT

执行INSERTUPDATEDELETE等不返回数据行的操作,通常使用exec方法或预处理语句。一个简单的插入示例如下:

$pdo->exec("INSERT INTO users (name, email, password)
            VALUES ('John', 'john@example.com', 'secret')");

注意:直接将变量值拼接进SQL字符串(如上例)在实践中非常危险,容易导致SQL注入攻击。在生产环境中,必须使用预处理语句(Prepared Statements)来安全地处理用户输入的数据。我们将在后续课程中详细讲解预处理语句。

总结

本节课中我们一起学习了PHP与MySQL数据库交互的核心步骤:

  1. 建立连接:使用PDO对象,并提供正确的主机、端口、数据库名、用户名和密码。
  2. 执行查询:使用query()方法执行SELECT查询,使用exec()方法执行INSERT等操作(注意安全风险)。
  3. 处理结果:使用fetch(PDO::FETCH_ASSOC)在循环中遍历SELECT查询返回的数据行,并以关联数组形式访问每一列。
  4. 格式化输出:将获取到的数据库数据嵌入到HTML代码中,生成用户可见的网页内容。
  5. 组织代码:将数据库连接配置分离到独立文件中,通过require_once引入,提高代码的可维护性。

掌握这些基础是构建动态Web应用的关键。下一节,我们将深入探讨如何使用更安全的预处理语句来执行SQL命令。

084:使用PDO访问MySQL并插入数据 💾

在本节课中,我们将学习如何将网页表单中的数据插入到MySQL数据库中。我们将使用PHP的PDO扩展来安全地执行SQL插入操作,并理解“模型-视图-控制器”(MVC)模式在此过程中的应用。最后,我们还会探讨如何结合查询和删除功能,构建一个简单的用户管理界面。


从表单插入数据到数据库

上一节我们介绍了PDO的基础连接。本节中,我们来看看如何接收用户通过表单提交的数据,并将其安全地存入数据库。

整个过程遵循一个清晰的流程:用户通过GET请求访问页面,看到一个表单;填写表单并提交后,触发POST请求;服务器端的PHP代码处理POST数据,生成SQL语句,并通过PDO将数据插入数据库。

以下是实现此功能的核心代码结构概述。

// 模型(Model)部分:处理数据和业务逻辑
if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {
    // 1. 从POST数组获取表单数据
    $name = $_POST[‘name’];
    $email = $_POST[‘email’];
    $password = $_POST[‘password’];

    // 2. 构造带占位符的SQL语句
    $sql = “INSERT INTO users (name, email, password) VALUES (:name, :email, :password)”;

    // 3. 准备并执行语句
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(‘:name’ => $name, ‘:email’ => $email, ‘:password’ => $password));
}

// 视图(View)部分:显示HTML表单
?>
<form method=“post”>
    Name: <input type=“text” name=“name”><br>
    Email: <input type=“text” name=“email”><br>
    Password: <input type=“password” name=“password”><br>
    <input type=“submit” value=“Add New”>
</form>

理解代码执行流程

现在,让我们深入分析上述代码的每一步是如何工作的。

当用户首次通过GET请求访问页面时,PHP会跳过if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) 内部的代码,直接渲染并输出HTML表单。表单中的 nameemailpassword 字段定义了提交后POST数组中的键名。

用户填写表单并点击“Add New”按钮后,浏览器会发送一个method=“post”的请求。此时,PHP脚本再次运行,并且因为存在POST数据,会进入if语句内部执行。

使用预处理语句与占位符

在模型部分,我们使用了预处理语句来执行SQL插入。这是确保数据库操作安全的关键步骤。

我们构造的SQL语句使用了命名占位符,例如 :name:email:password。占位符的名称可以是任意字符串,但为了清晰,通常与对应的表字段或POST键名保持一致。prepare() 方法会检查SQL语法并准备执行。随后,execute() 方法接收一个关联数组,将数组中的值绑定到对应的占位符上,并最终执行查询。

这种 prepare -> execute 的模式是当SQL语句需要插入外部变量值时的标准做法。它能有效防止SQL注入攻击。

结合查询功能显示结果

为了即时验证插入操作是否成功,我们可以将插入功能和查询功能结合在同一个页面。

在插入数据的代码之后,我们可以添加一个查询所有用户的SQL语句,并将结果以表格形式展示在页面上。这样,用户提交新数据后,无需跳转页面或使用其他管理工具,就能立即在页面底部看到更新后的用户列表。这提供了更流畅的用户体验。

实现删除用户功能

完成了数据的创建(Create)和读取(Read),我们接下来看看如何实现删除(Delete)功能。删除操作需要格外谨慎。

首先,删除操作必须通过POST请求触发,而不应使用GET请求。这是因为根据HTTP规范,GET请求不应用于改变服务器状态(如删除数据)。网络爬虫会跟踪GET链接,可能导致误删。此外,浏览器对重复提交POST请求也有更好的防护机制。

因此,一个常见的用户交互模式是:用户点击一个删除链接(GET请求),跳转到一个确认页面;在确认页面上点击“确认删除”按钮(POST请求),才真正执行删除操作。

以下是删除功能的简化代码逻辑。

// 如果是POST请求,则执行删除
if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’ && isset($_POST[‘delete’])) {
    $user_id = $_POST[‘user_id’];
    $sql = “DELETE FROM users WHERE user_id = :zip”;
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(‘:zip’ => $user_id));
}

构建集成界面:添加与删除

我们可以将添加用户和删除用户的功能集成到同一个页面,形成一个简单的用户管理界面。

关键在于如何在显示用户列表的每一行旁边,动态生成一个独立的删除表单。我们通过在循环输出用户数据的表格中,为每一行创建一个微表单来实现。这个表单包含一个隐藏域(input type=“hidden”),其值设置为该行用户的唯一主键(如user_id),以及一个提交按钮。

当用户点击某个“Delete”按钮时,表单会提交该行对应的用户ID。服务器端通过检查 $_POST[‘delete’]$_POST[‘user_id’] 来判断并执行删除操作。

这样,页面上方是“添加用户”的表单,下方是用户列表,每个用户旁都有一个对应的删除按钮,所有功能一气呵成。

MVC模式回顾

在整个示例中,我们实践了模型-视图-控制器(MVC)模式的简单分离。

  • 模型(Model):位于代码顶部,包含数据库连接(require ‘pdo.php’)、处理添加用户的逻辑、处理删除用户的逻辑。它负责所有与数据相关的操作。
  • 控制器(Controller):逻辑体现在 if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) 等条件判断中,它根据用户的请求(GET或POST,以及具体的POST参数)来决定调用哪个模型函数。
  • 视图(View):位于模型代码之后,负责生成HTML,包括显示表单、循环遍历用户数据并生成表格和删除按钮。

一条清晰的代码注释或空行通常作为模型和视图之间的分界线。


本节课中我们一起学习了如何使用PDO将表单数据安全插入MySQL数据库,掌握了预处理语句和占位符的用法。我们还了解了为何删除操作需通过POST请求进行,并成功构建了一个集用户添加、查询和删除功能于一体的简单Web界面。整个过程体现了MVC设计模式的基本思想,为构建更复杂的Web应用打下了基础。在接下来的课程中,我们将关注这些操作中可能存在的安全漏洞及其修复方法。

085:防范SQL注入 🛡️

在本节课中,我们将要学习一个至关重要的Web安全概念:SQL注入。我们将了解什么是SQL注入,它如何发生,以及如何通过正确的编程实践来防范它。

概述

上一节我们介绍了数据库连接的基本操作。本节中我们来看看一个常见的安全漏洞——SQL注入。这是一种攻击者通过用户输入来操纵数据库查询的技术,可能导致数据泄露、篡改甚至删除。

SQL注入的原理

SQL注入与之前讨论的HTML注入原理相似。它发生在应用程序将用户输入的数据直接拼接到SQL查询语句中时。如果攻击者发现了这一点,他们就可以通过精心构造的输入来改变查询的原始意图。

以下是导致SQL注入的危险代码模式

$email = $_POST['email'];
$password = $_POST['password'];
$sql = "SELECT name FROM users WHERE email='$email' AND password='$password'";
$stmt = $pdo->query($sql);

在这段代码中,用户输入的$email$password被直接放入SQL字符串。双引号内的变量会被PHP直接替换为其值。

一个生动的例子

假设我们有一个登录表单。当用户行为良好时,一切正常。

  • 用户输入:email = csev@umich.edu, password = 12345
  • 生成的SQL:SELECT name FROM users WHERE email='csev@umich.edu' AND password='12345'

然而,恶意用户会尝试不同的输入。

  • 用户输入:email = csev@umich.edu, password = p' OR '1'='1
  • 生成的SQL:SELECT name FROM users WHERE email='csev@umich.edu' AND password='p' OR '1'='1'

此时,WHERE子句的条件变成了“密码等于‘p’ 或者 ‘1’等于‘1’”。由于‘1’=‘1’永远为真,整个条件将永远成立,攻击者就能在没有正确密码的情况下成功登录。这就是SQL注入攻击。

如何防范SQL注入

防范SQL注入的关键是:永远不要将用户输入直接拼接到SQL语句中

幸运的是,我们之前学习的PDO和预处理语句(Prepared Statements)正是为了解决这个问题而设计的。当使用参数化查询时,用户输入的数据会被自动、安全地处理,从而防止注入。

以下是安全的代码模式:

$email = $_POST['email'];
$password = $_POST['password'];
$sql = "SELECT name FROM users WHERE email=:em AND password=:pw";
$stmt = $pdo->prepare($sql);
$stmt->execute(array( ':em' => $email, ':pw' => $password ));

在这段代码中,:em:pw是占位符。execute()方法会确保传入的数据被正确地转义和处理,然后再与查询语句组合。这样,即使用户输入中包含引号或SQL关键字,也会被当作普通数据处理,而不会破坏查询结构。

总结

本节课中我们一起学习了SQL注入的安全威胁及其防范方法。核心要点是:直接拼接用户输入到SQL查询中是极其危险的做法。而使用PDO的预处理语句和参数化查询,可以自动、有效地防止SQL注入攻击,这是编写安全数据库应用程序的基石。

下一节,我们将讨论数据库操作中的异常处理,了解在PHP与数据库交互过程中哪些情况会抛出错误。

086:PDO错误处理 🛡️

在本节课中,我们将学习PHP数据对象(PDO)的错误处理机制。理解并正确配置错误处理方式,对于开发稳定、安全的数据库应用程序至关重要。我们将探讨不同的错误模式,并学习如何在开发和生产环境中妥善处理错误。

概述

PDO的错误处理方式有其独特之处。到目前为止,我们所做的操作在语法上基本是正确的。本节将讨论可能出错的情况以及如何处理这些错误。

PDO的错误模式

PDO提供了几种处理错误的方式。上一节我们介绍了数据库连接,本节中我们来看看如何应对执行SQL时可能发生的错误。

以下是PDO的三种主要错误模式:

  • PDO::ERRMODE_SILENT: 静默模式。这是默认模式,发生错误时不会主动提示,需要手动检查 $stmt->errorCode()不推荐使用
  • PDO::ERRMODE_WARNING: 警告模式。发生错误时会产生一个 E_WARNING 级别的PHP警告,但脚本会继续执行。
  • PDO::ERRMODE_EXCEPTION: 异常模式。发生错误时会抛出一个 PDOException 异常。这是推荐在开发中使用的模式

你可以通过以下代码设置错误模式:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

为什么推荐异常模式?

让我们通过一个例子来理解不同模式的区别。假设我们有一段从GET参数获取用户ID并查询数据库的代码。

// 假设URL是 error.php?user_id=1
$user_id = $_GET['user_id'];
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :xyz');
$stmt->execute(array(':xyz' => $user_id));
$row = $stmt->fetch(PDO::FETCH_ASSOC);

这段代码在语法正确时运行良好。但是,如果我们犯了一个错误,比如在 execute 方法中使用了未定义的参数名:

$stmt->execute(array(':abc' => $user_id)); // 错误!参数名应该是 :xyz

如果设置为 PDO::ERRMODE_WARNING,PHP会产生一个警告,但脚本会继续执行。$stmt->fetch() 会失败,最终可能只是向用户显示“用户未找到”,而开发者却不知道底层SQL已经出错了。这是一种糟糕的策略。

因此,我建议始终使用 PDO::ERRMODE_EXCEPTION。在异常模式下,同样的错误会导致脚本立即终止(除非被捕获),并清晰地指出问题所在,这有助于开发者快速定位和修复语法或逻辑错误。

使用Try-Catch捕获异常

在开发中,让错误直接暴露出来是好事。但在生产环境中,我们可能不希望将详细的错误信息展示给最终用户。这时,可以使用 try-catch 块来优雅地处理异常。

以下是处理异常的模式:

try {
    $user_id = $_GET['user_id'];
    $stmt = $pdo->prepare('SELECT name FROM users WHERE id = :xyz');
    // 故意制造一个错误
    $stmt->execute(array(':abc' => $user_id));
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    // 如果成功,继续处理$row
} catch (PDOException $e) {
    // 错误发生时会跳转到这里
    echo "内部错误,请联系管理员。"; // 给用户看的友好信息
    error_log("数据库查询错误(文件:" . __FILE__ . "):" . $e->getMessage()); // 记录到日志供开发者查看
    exit(); // 终止脚本执行
}

在这个模式中,用户只会看到“内部错误,请联系管理员。”这样友好的信息。而详细的错误信息,包括错误消息和文件位置,则通过 error_log() 函数被记录到了服务器的错误日志中,不会泄露给用户。

查看错误日志

错误日志是开发者调试的重要工具。日志文件的位置因服务器配置而异。

你可以通过创建一个包含 phpinfo(); 函数的PHP文件来查找日志路径,然后在输出页面中搜索 error_log

例如,在MAMP(Mac)环境中,日志路径可能类似于:/Applications/MAMP/logs/php_error.log

在开发时,持续查看日志文件非常有用。在Linux或Mac上,你可以使用 tail -f 命令动态监控日志:

tail -f /path/to/your/php_error.log

在Windows上,你也可以找到类似的工具(如 Tail for Windows)。这样,每当有错误发生时,你都能在终端或命令行窗口中实时看到,而无需反复打开日志文件。

总结

本节课中我们一起学习了PDO错误处理的核心知识。我们首先了解了PDO的三种错误模式,并明确了在开发时应始终使用 PDO::ERRMODE_EXCEPTION 模式。接着,我们探讨了如何利用 try-catch 语句来捕获异常,从而在生产环境中向用户展示友好信息,同时将详细错误 记录到日志 供开发者排查。最后,我们介绍了如何查找和实时监控PHP错误日志文件。正确配置错误处理机制,是构建健壮Web应用程序的关键一步。

087:PHP MySQL与PDO代码详解 🧑‍💻

在本节课中,我们将详细学习如何使用PHP的PDO扩展来连接和操作MySQL数据库。我们将从环境准备开始,逐步讲解建立连接、执行查询、处理结果以及优化代码结构。

概述

本节教程将引导你完成使用PDO进行数据库操作的全过程。我们将首先设置必要的数据库环境,然后通过多个代码示例,演示如何连接数据库、执行SQL查询、处理返回的数据,以及如何组织代码以提高安全性和可维护性。


环境准备与数据库设置

上一节概述了课程目标,本节中我们来看看如何为PDO示例准备运行环境。由于操作需要本地数据库支持,你必须下载并配置相关文件。

首先,你需要下载课程提供的 pdo.zip 文件,并将其解压到本地Web服务器的根目录(例如 htdocs)下,以便通过本地主机(localhost)访问这些文件。

以下是设置数据库的具体步骤:

  1. 创建数据库:使用SQL命令创建一个名为 misc 的数据库。

    CREATE DATABASE misc;
    
  2. 创建用户并授权:创建一个用户 fred,密码为 zap,并授予其访问 misc 数据库的权限。

    CREATE USER 'fred'@'localhost' IDENTIFIED BY 'zap';
    GRANT ALL ON misc.* TO 'fred'@'localhost';
    
  3. 创建数据表:在 misc 数据库中创建 users 表。

    USE misc;
    CREATE TABLE users (
        user_id INT AUTO_INCREMENT,
        name VARCHAR(128),
        email VARCHAR(128),
        password VARCHAR(128),
        PRIMARY KEY(user_id),
        INDEX(email)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  4. 插入测试数据:向表中插入两条示例用户记录。

    INSERT INTO users (name, email, password) VALUES ('Chuck', 'csev@umich.edu', '123');
    INSERT INTO users (name, email, password) VALUES ('Glenn', 'glenn@umich.edu', '456');
    

完成以上步骤后,你的本地数据库中就准备好了可供PDO连接和查询的数据。


建立PDO数据库连接

环境准备好后,我们现在来看看如何使用PDO建立与MySQL数据库的连接。这是所有数据库操作的第一步。

核心操作是实例化一个PDO对象。其构造函数需要三个主要参数:

  • 数据源名称(DSN):一个连接字符串,指定数据库类型、主机地址、端口和数据库名。
  • 用户名:用于连接数据库的账户名。
  • 密码:对应用户的密码。

以下代码展示了如何建立连接:

<?php
$pdo = new PDO('mysql:host=localhost;port=3306;dbname=misc',
               'fred',
               'zap');
?>

代码解释

  • mysql: 指定我们使用MySQL数据库驱动。
  • host=localhost 表示数据库服务器位于本机。
  • port=3306 是MySQL默认端口(根据你的环境调整,如XAMPP可能是3306)。
  • dbname=misc 指定要连接的数据库名称。
  • 'fred''zap' 是之前创建的用户名和密码。

如果连接信息(如密码)错误,实例化PDO对象时会抛出异常,导致脚本终止。因此,确保这些信息正确是调试的第一步。


执行查询与遍历结果

成功连接数据库后,本节我们来看看如何执行SQL查询并处理返回的结果集。

使用连接对象的 query() 方法可以执行SQL语句。查询结果会返回一个 PDOStatement 对象,我们可以通过它来获取数据。

以下是查询并遍历所有用户记录的示例:

<?php
$stmt = $pdo->query("SELECT * FROM users");
while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
    print_r($row);
}
?>

代码解释

  1. $pdo->query("SELECT * FROM users") 执行SQL查询,并将结果集赋值给 $stmt
  2. while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) 是一个循环。
    • $stmt->fetch(PDO::FETCH_ASSOC) 方法每次从结果集中取出一行数据。
    • PDO::FETCH_ASSOC 参数指定将行数据作为关联数组返回,数组的键是数据库表的列名(如 user_id, name)。
    • 当没有更多行可获取时,fetch() 返回 false,循环结束。
  3. print_r($row) 打印出每一行的数据。

运行这段代码,你将看到两个用户的详细信息以数组形式输出。


将数据渲染为HTML表格

直接打印数组对用户不友好。本节我们学习如何将从数据库获取的数据格式化为美观的HTML页面。

我们可以修改循环体内的代码,用echo语句生成HTML标签,从而将数据嵌入到一个表格中。

<?php
echo '<table border="1">'."\n";
$stmt = $pdo->query("SELECT * FROM users");
while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
    echo "<tr><td>";
    echo htmlentities($row['user_id']);
    echo "</td><td>";
    echo htmlentities($row['name']);
    echo "</td><td>";
    echo htmlentities($row['email']);
    echo "</td></tr>\n";
}
echo "</table>\n";
?>

代码解释

  • 在循环中,我们不再使用 print_r,而是通过 echo 拼接出HTML的 <tr>(行)和 <td>(单元格)标签。
  • htmlentities($row['name']) 函数用于将数据中的特殊字符转换为HTML实体,这是一种重要的安全措施,可以防止跨站脚本(XSS)攻击。
  • 最终,这段代码会生成一个包含所有用户数据的HTML表格。


代码重构与安全优化

将数据库连接信息硬编码在每个PHP文件中是一种糟糕的做法。本节我们来看看如何通过代码重构来提高安全性和可维护性。

最佳实践是将数据库连接代码单独放在一个配置文件中,然后在需要的页面中引入(require)它。这样便于统一管理密码,也减少了代码重复。

1. 创建配置文件 (pdo.php):

<?php
$pdo = new PDO('mysql:host=localhost;port=3306;dbname=misc',
               'fred',
               'zap');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
?>

代码解释

  • $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 这行代码设置了PDO的错误处理模式。PDO::ERRMODE_EXCEPTION 表示当发生错误时,PDO会抛出异常。这比默认的静默模式更有利于调试。

2. 在主文件中引入配置并查询:

<?php
require_once "pdo.php"; // 引入数据库连接
$stmt = $pdo->query("SELECT * FROM users");
// ... 后续处理数据的代码
?>

通过这种方式,数据库凭证只存在于 pdo.php 这一个文件中。如果网站有上百个文件需要数据库连接,你只需修改这一个配置文件即可。即使Web目录被部分访问,数据库密码也不容易泄露。


总结

本节课中我们一起学习了PHP PDO操作MySQL数据库的核心流程。

我们首先完成了本地数据库环境的搭建,然后深入讲解了建立PDO连接的关键代码。接着,我们学习了如何使用 query() 方法执行SQL查询,并通过 fetch(PDO::FETCH_ASSOC) 在循环中遍历和访问结果集。之后,我们将数据动态生成HTML表格,使结果更直观。最后,我们探讨了通过代码重构,将连接配置分离到独立文件的最佳实践,这极大地提升了代码的安全性和可维护性。

记住,成功的PDO操作始于正确的连接参数,而清晰的结构化代码是构建稳健Web应用的基础。

088:数据插入与删除详解 🛠️

在本节课中,我们将学习如何使用PHP的PDO(PHP Data Objects)进行数据库的基本操作,包括插入新数据和删除现有数据。我们将通过分析三个逐步演进的示例代码(user1.phpuser2.phpuser3.php)来理解其实现原理。

准备工作与环境设置

在开始编写代码之前,需要确保你的本地开发环境已正确配置。这包括运行一个本地服务器(如XAMPP、MAMP)并设置好数据库连接。我们在之前的视频中已经完成了这些步骤。核心的数据库连接信息通常保存在一个独立的文件中,例如 pdo.php,其中包含数据库的ID和密码。

示例一:基础数据插入 (user1.php)

user1.php 文件展示了最基本的插入操作。它采用了模型-视图-控制器(MVC)模式的思想,将数据处理逻辑放在文件顶部,而将HTML模板放在底部。

代码逻辑解析

文件顶部的“静默处理代码”负责所有后台逻辑。当页面通过GET请求访问时,由于没有POST数据,代码会直接跳过处理部分,渲染底部的表单。这个表单包含三个字段:namepasswordemail

当用户填写表单(例如,姓名:Sally,邮箱:sally@iu.edu,密码:999)并点击提交后,浏览器会发送一个POST请求。

此时,代码再次从顶部开始执行。因为这次是POST请求,所以 $_POST[‘name’]$_POST[‘email’]$_POST[‘password’] 这三个变量将被设置。

执行SQL插入

以下是执行插入操作的核心代码:

$sql = “INSERT INTO users (name, email, password) VALUES (:name, :email, :password)”;
$stmt = $pdo->prepare($sql);
$stmt->execute(array(‘:name’ => $_POST[‘name’],
                     ‘:email’ => $_POST[‘email’],
                     ‘:password’ => $_POST[‘password’]));

这里使用了PDO的预处理语句和命名占位符(如 :name)。这种方法可以防止SQL注入攻击,我们稍后会详细讨论其重要性。代码执行后,新用户“Sally”就被插入到数据库的users表中。

示例二:插入并显示数据 (user2.php)

user2.phpuser1.php 的基础上增加了一个功能:在每次插入操作后,显示数据库中所有用户的列表。

功能增强

除了包含与 user1.php 相同的插入代码外,user2.php 在页面底部添加了一个表格。这个表格通过查询数据库并循环遍历所有用户记录来动态生成。

因此,当访问 user2.php 时,你首先会看到数据库中现有的所有用户列表,然后在列表下方是那个熟悉的输入表单。

当你通过这个表单添加一个新用户(例如,Fred)并提交后,会发生以下事情:

  1. POST请求触发插入逻辑,将Fred添加到数据库。
  2. 代码执行完毕后,继续向下运行。
  3. 执行一个SELECT查询,获取包括新用户Fred在内的所有用户数据。
  4. 用更新后的数据重新生成并显示表格。

这样,页面就实现了“添加即显示”的交互效果,让我们能够即时看到操作结果。

示例三:集成插入与删除 (user3.php)

user3.php 将前两个示例的功能整合在一起,并加入了删除功能,形成了一个简易的用户管理界面。

安全删除操作

在Web开发中,删除操作不应该通过GET请求直接执行,因为这可能导致恶意链接轻易删除数据。安全的做法是使用POST请求。

user3.php 中,我们为每个用户行添加了一个删除按钮。这个按钮实际上是一个微型表单:

<form method=“post”>
    <input type=“hidden” name=“user_id” value=“3”>
    <input type=“submit” name=“delete” value=“Del”>
</form>
  • 它使用 method=“post”
  • 它包含一个隐藏字段 user_id,其值是对应用户的主键ID。
  • 提交按钮的 name 属性被设置为 “delete”

处理多种表单提交

现在,页面上有多个表单:一个用于“添加新用户”,另外每个用户行都有一个“删除”表单。服务器端代码需要区分是哪种操作。

以下是处理逻辑:

  • 判断添加操作:检查 $_POST[‘name’]$_POST[‘email’] 等添加表单的字段是否被设置。如果被设置,则执行插入。
  • 判断删除操作:检查 $_POST[‘delete’] 按钮是否被点击(即该变量是否存在)。如果存在,则读取 $_POST[‘user_id’] 的值并执行删除。

删除的SQL语句如下:

$sql = “DELETE FROM users WHERE user_id = :zip”;
$stmt = $pdo->prepare($sql);
$stmt->execute(array(‘:zip’ => $_POST[‘user_id’]));

重要提示:如果执行的DELETE语句没有匹配到任何行(例如,尝试删除一个不存在的user_id=42),这不是一个SQL语法错误。数据库只是执行了一个没有影响任何行的操作,这是完全合法的。

动态生成删除表单

在循环输出用户表格时,我们为每一行动态生成其对应的删除表单。关键代码如下:

echo ‘<td>’;
echo ‘<form method=“post”><input type=“hidden”‘;
echo ‘ name=“user_id” value=“’ . $row[‘user_id’] . ‘“>’;
echo ‘<input type=“submit” name=“delete” value=“Del”>’;
echo ‘</form>’;
echo ‘</td>’;

这里,我们将数据库中的用户ID($row[‘user_id’])直接拼接到了HTML属性中。因为user_id是我们自己生成的数字,所以这里不需要额外的HTML字符转义。

最终,整个页面实现了无缝的交互:添加用户后,新用户立即出现在列表中;点击某个用户的“Del”按钮,该用户会立刻从列表和数据库中消失,页面随之刷新。

总结

本节课我们一起学习了使用PHP PDO进行数据库操作的核心流程。

  1. 数据插入:我们学会了如何构建INSERT语句,使用命名占位符绑定参数,并通过POST请求安全地添加新数据。
  2. 数据查询与显示:我们掌握了在执行插入操作后,如何查询数据库并将结果以表格形式展示给用户。
  3. 数据删除:我们理解了为何删除操作必须使用POST请求,并学会了如何通过隐藏字段传递主键ID,以及如何安全地执行DELETE语句。
  4. 集成应用:最后,我们将插入、显示和删除功能整合到一个页面中,通过判断不同的POST变量来区分和处理不同的用户操作,构建了一个功能完整的简易用户管理模块。

这些代码示例清晰地展示了Web应用程序中“增删查”基本操作的实现模式,是构建更复杂应用的基础。

089:安全性与SQL注入详解 🔐

在本节课中,我们将详细探讨一个用户登录功能的PHP代码实现,并重点分析其存在的安全漏洞——SQL注入。我们将通过对比两种不同的代码实现方式,来理解为何以及如何使用参数化查询来保护你的Web应用程序免受攻击。

概述

我们将从一个简单的登录页面 login.php 开始。这个页面接收用户输入的邮箱和密码,然后通过拼接SQL字符串的方式查询数据库。虽然这个功能看起来可以正常工作,但它隐藏着一个严重的安全风险。接下来,我们会演示攻击者如何利用这个漏洞,在不掌握正确密码的情况下登录系统。最后,我们将学习如何使用PDO(PHP Data Objects)和参数化查询来修复这个漏洞,从而构建一个安全的登录系统。

数据模型与登录流程

首先,我们来看一下基础的登录逻辑。假设我们有一个 people 数据表,其中存储了用户的邮箱和密码。为了简化示例,我们暂时使用明文密码(在实际应用中,密码必须经过哈希处理)。

当用户在表单中输入邮箱(例如 c7@umich.edu)和密码(例如 123)并提交时,系统会执行以下操作:

  1. $_POST 超全局数组中获取邮箱和密码。
  2. 将这些值直接拼接到一个SQL查询字符串中。
  3. 执行该查询,检查数据库中是否存在匹配的记录。
  4. 根据查询结果返回“登录成功”或“登录失败”的信息。

以下是该流程的初始代码片段:

$email = $_POST['email'];
$password = $_POST['password'];
$sql = "SELECT name FROM users WHERE email='$email' AND password='$password'";
// 执行查询并检查结果...

当输入正确的凭据时,SQL查询会返回一行数据,登录成功。如果凭据错误,则查询返回空结果,登录失败。

初代登录代码的漏洞

上一节我们介绍了基本的登录流程,本节中我们来看看这个实现方式存在的根本问题。问题在于代码使用了字符串拼接来构建SQL语句。

观察以下这行代码:

$sql = "SELECT name FROM users WHERE email='$email' AND password='$password'";

这里,用户输入的 $email$password 被直接嵌入到了SQL命令中。如果用户输入的是普通数据,例如 c7@umich.edu123,那么生成的SQL语句是安全的:

SELECT name FROM users WHERE email='c7@umich.edu' AND password='123'

然而,攻击者可以输入精心构造的数据来改变SQL语句的本意。

什么是SQL注入?

SQL注入是一种攻击技术,攻击者通过在应用程序的输入字段中插入恶意的SQL代码,来操纵后端数据库查询。这可能导致数据泄露、数据篡改,甚至完全控制数据库服务器。

一个经典的例子是“小鲍比表”(Little Bobby Tables)漫画所描述的场景:如果学校系统未对输入进行净化处理,一个名为 Robert’); DROP TABLE students;-- 的学生注册信息可能会执行删除整个学生表的命令。

在我们的登录例子中,攻击者无需知道密码即可登录。

以下是攻击者可能进行的操作:

  1. 在密码字段中,不输入真实密码,而是输入:‘ OR ‘1’=’1
  2. 提交后,PHP代码会将其拼接到SQL语句中。
  3. 最终执行的SQL语句将变为:
    SELECT name FROM users WHERE email='c7@umich.edu' AND password='' OR '1'='1'
    
    由于 ‘1’=’1’ 这个条件永远为真,这个查询将返回用户表中的数据(通常至少是第一行),从而使攻击者绕过密码验证,成功登录。

演示SQL注入攻击

让我们具体演示一下这个攻击过程。假设攻击者在邮箱栏输入 c7@umich.edu,在密码栏输入恶意字符串 ‘ OR ‘1’=’1

当表单提交后,我们的PHP代码会生成并执行以下SQL语句:

SELECT name FROM users WHERE email='c7@umich.edu' AND password='' OR '1'='1'

数据库会这样理解这个查询:“寻找邮箱是 c7@umich.edu 并且密码是空字符串的记录,或者 ‘1’=‘1’ 成立的记录”。因为‘1’=‘1’恒成立,所以整个 WHERE 条件永远为真。这通常会导致查询返回结果集中的第一行用户记录,使得攻击者成功登录。

如何防御SQL注入:使用PDO与参数化查询

我们已经看到了SQL注入的巨大危害,本节将介绍最有效、最根本的防御方法:使用参数化查询(也称为预处理语句)。在PHP中,这通常通过PDO扩展来实现。

参数化查询的核心思想是将SQL代码与数据分离。我们不再拼接字符串,而是先定义一个包含占位符(如 :email)的SQL语句模板。然后,将用户输入的数据“绑定”到这些占位符上。PDO驱动程序会确保数据在插入到SQL命令之前被正确地转义和处理,从而彻底杜绝了数据被解释为代码的可能性。

以下是使用PDO重写后的安全登录代码:

// 1. 准备SQL语句,使用命名占位符
$stmt = $pdo->prepare(‘SELECT name FROM users WHERE email = :em AND password = :pw’);

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/ff91d4be316a8fa95b84c13b4ff866f6_20.png)

// 2. 将用户输入的数据绑定到占位符
$stmt->execute(array( ‘:em’ => $email, ‘:pw’ => $password));

// 3. 获取结果
$row = $stmt->fetch(PDO::FETCH_ASSOC);

在这个版本中,即使用户在密码字段输入了 ‘ OR ‘1’=’1,PDO也会将其视为一个完整的字符串值,而不会破坏SQL语句的结构。最终执行的查询等价于:

SELECT name FROM users WHERE email=‘c7@umich.edu’ AND password=‘\‘ OR \‘1\‘=\‘1’

数据库会去寻找密码字段完全等于这个奇怪字符串的用户,显然找不到,因此登录会失败。攻击手段就此失效。

关键原则:永远不要使用字符串拼接的方式来构建包含用户输入数据的SQL语句。对于任何涉及用户输入的数据库操作,都应使用所在编程语言提供的参数化查询接口。

总结

本节课中我们一起学习了Web应用安全中的一个核心议题——SQL注入。我们从一个存在漏洞的登录代码入手,分析了攻击者如何利用字符串拼接的缺陷来绕过身份验证。随后,我们探讨了解决此问题的黄金标准:使用PDO的参数化查询。通过将查询结构与数据分离,我们可以确保用户输入永远被当作数据处理,从而构建出坚固、安全的数据库交互层。请牢记,使用参数化查询是防止SQL注入的最有效和必要的手段。

090:犹他州奥勒姆 👨‍🏫

在本节课中,我们将回顾一次在犹他州奥勒姆举行的附加办公时间。本次会议主要介绍了参与者的背景和互动,并预告了未来的课程安排。

大家好,我们现在在犹他州的奥勒姆,这是又一次办公时间。我想让大家认识一下到场的各位。我会指向你们,然后你们可以说出自己的名字并向大家问好。

以下是到场学员的自我介绍:

  • 约翰:大家好,我叫约翰。我上过查克博士的很多课程,它们都非常棒。我现在几乎每天都在使用Python。
  • :嘿,我是丹,我来自俄亥俄州,但现在在犹他州。加油,七叶树队!我建造冰城堡,Python可能真的能帮我完成这项工作。冰城堡是大型的旅游景点,完全由冰制成,里面有隧道、洞穴、塔楼、瀑布、滑梯,还有嵌在冰里的灯光,它们会闪烁、变色,并配合音乐,非常美妙。如果你附近有,一定要去看看。
  • 约什:大家好,我是约什。我完成了Python for Everybody课程,觉得它非常棒,查克博士也非常了不起。
  • 基思:我是基思。查克博士,欢迎来到普罗沃或奥勒姆。很高兴在这里见到你,不敢相信你真的来到了我们的小镇。加油,美洲狮队!

我不知道下一次办公时间的确切地点,但我想大约四周后会在韩国首尔举行。那么,首尔见。

本节课中,我们一起回顾了在犹他州奥勒姆举行的附加办公时间,认识了参与课程的几位学员,并了解了他们学习编程的经历与应用。最后,我们得知了下次办公时间将在韩国首尔举行。

091:Cookie技术 🍪

在本节课中,我们将要学习Cookie技术。Cookie是HTTP协议的一部分,用于在浏览器中存储少量数据,以便服务器能够在多次请求之间识别特定的浏览器。这对于实现用户登录、购物车等功能至关重要。

概述

Web服务器通常需要同时与成千上万个不同的浏览器通信。为了区分这些浏览器并记住每个用户的状态(例如登录信息、购物车内容),服务器需要一种机制来“标记”每个浏览器。Cookie技术正是为了解决这个问题而设计的。

Cookie是什么?

Cookie是存储在浏览器中的一小段数据,采用键值对的形式。它由Web服务器创建并发送给浏览器,浏览器会将其保存,并在后续向同一服务器发出的每个请求中自动附带这个Cookie。

GETPOST数据不同,Cookie数据在单个请求结束后不会消失,而是会持续存在于后续的请求中,直到它过期或被覆盖。

核心概念公式

Cookie = 服务器设置的键值对数据,存储在浏览器中,随每个请求发送回服务器。

Cookie的工作原理

上一节我们介绍了Cookie的基本概念,本节中我们来看看它的具体工作流程。

  1. 初始请求:用户首次访问网站时,浏览器向服务器发送请求。此时请求中不包含该网站的Cookie。
  2. 服务器响应并设置Cookie:服务器在处理请求后,会在响应头中添加一个Set-Cookie指令,例如 Set-Cookie: user_id=abc123
  3. 浏览器存储Cookie:浏览器收到响应后,会将这个Cookie(user_id=abc123)保存起来。
  4. 后续请求:此后,每当浏览器向同一服务器发送请求时,都会自动在请求头中附上这个Cookie:Cookie: user_id=abc123
  5. 服务器读取Cookie:服务器从请求头中读取Cookie,从而知道这个请求来自之前标记过的浏览器。

这个过程使得无状态的HTTP协议能够模拟出“有状态”的会话。

Cookie的特性

以下是Cookie的一些重要特性:

  • 域名绑定:Cookie与特定的域名(服务器)绑定。浏览器只会将Cookie发送给创建它的服务器,这保证了不同网站之间的Cookie数据是隔离的。
  • 有效期:Cookie可以设置过期时间。有些是“会话Cookie”,浏览器关闭即失效;有些是“持久Cookie”,可以存在几天甚至几年。
  • 存储限制:每个Cookie有大小限制(通常为4KB),并且每个域名下可存储的Cookie数量也有限制。

在PHP中使用Cookie

PHP为操作Cookie提供了出色的支持,就像处理$_GET$_POST一样简单。

Cookie数据会自动被PHP解析,并存储在一个名为$_COOKIE的超全局数组中。这是一个键值对数组,你可以直接访问其中的值。

核心概念代码

// 读取名为 ‘username‘ 的Cookie值
if (isset($_COOKIE[‘username‘])) {
    $name = $_COOKIE[‘username‘];
    echo “欢迎回来, “ . $name;
}

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/2371360732cd30a82a78008456b5af87_22.png)

// 设置一个Cookie,有效期为1小时
$value = ‘new_user_data‘;
$expire = time() + 3600; // 当前时间戳 + 3600秒
setcookie(‘my_cookie‘, $value, $expire);

让我们通过一个简单的例子来理解这个过程:

  1. 用户第一次访问页面时,$_COOKIE[‘zap‘]不存在。
  2. 服务器代码检测到这一点,于是使用setcookie()函数设置一个名为zap、值为42、有效期1小时的Cookie。
  3. 浏览器收到响应,保存这个Cookie。
  4. 用户刷新页面或进行其他操作,浏览器在请求中自动带上zap=42这个Cookie。
  5. 服务器再次运行时,就能在$_COOKIE[‘zap‘]中读到值42

$_GET$_POST只存在于单次请求不同,$_COOKIE中的数据会随着浏览器的每次请求而持续存在。

总结

本节课中我们一起学习了Cookie技术。我们了解到Cookie是服务器留在用户浏览器中的“标记”,它是一个键值对数据,由服务器设置、浏览器存储并随每次请求发回。这使得服务器能够在无状态的HTTP协议上识别用户身份和维持会话状态。我们学习了Cookie的工作原理、特性,以及如何在PHP中通过$_COOKIE数组读取和通过setcookie()函数设置Cookie。

接下来,我们将探讨Session(会话)技术。Session是建立在Cookie基础上的、在服务器端存储用户数据的更强大机制。

092:会话管理 🍪

在本节课中,我们将要学习PHP中的会话管理。我们将了解会话是什么,它们与Cookie的关系,以及如何在PHP应用程序中有效地使用会话来存储跨多个请求的用户数据。


会话与Cookie的关系

上一节我们介绍了Cookie,本节中我们来看看会话。会话与Cookie相关,但必须区分它们。Cookie是浏览器和HTTP层面的概念。回顾一下,服务器发送一个Cookie,它被存储在浏览器中。

然后,浏览器在后续请求中将其发送回来,例如 x=4。我们见过 Set-Cookie 请求和 Cookie 头。

会话则是我们存储在服务器端的数据。你不会将当前账户余额甚至当前登录用户的信息(至少未加密的)存储在Cookie中,因为Cookie是浏览器的一部分,用户可以修改、删除或丢弃它。

我们可以使用Cookie来有效地“解锁”一个会话。会话是一小段数据,通常存储在Web服务器上(也可以存储在各种外部服务器或数据库中,但为简单起见,我们默认它存储在服务器上的一个小文件中)。

因此,当一个请求到来时,我们有 $_GET$_POST(来自表单和参数)和 $_COOKIE。一旦我们理清所有信息,这里的数据就会是 $_SESSION

会话的酷炫之处在于它是一种双向连接。Cookie也是双向的,我们可以使用 setcookie 来发送它。但会话数据使用起来非常方便,它是另一个超全局变量。

现在,基于我们对Cookie的了解,来探讨如何在PHP中实现和使用会话。


会话的工作原理

当遇到一个新浏览器时,我们检查是否存在一个会话Cookie(它仍然是一个Cookie,但专用于会话)。如果不存在,我们就生成一个大的随机数作为唯一标识符,然后将其作为Cookie发送,并在服务器上创建一个具有相同标识符的会话。

当下一个请求到来时,我们看到这里有一个会话Cookie,然后我们就可以重新连接到那个会话。PHP为我们处理了很多这类会话工作,就像一个Web框架。

会话标识符的安全性基于“通过隐匿实现安全”。我们选取一个大的随机数,将其转换为十六进制字符,放入浏览器Cookie中。

如果有人以某种方式窃取了你的会话标识符,他们就可以通过更改自己浏览器中的Cookie来冒充你的会话。但由于这些标识符是很大的随机数,并且通常只存活很短时间(例如关闭浏览器后会话就失效),我们一般不太担心这个问题。


我们倾向于不在会话中存储太多数据。如果你查看一个典型的PHP应用,首次访问时就会看到一个名为 PHPSESSID 的Cookie(默认名称,可在PHP配置中更改)。这个Cookie在你按下登录按钮之前就存在了。会话和登录是不同的概念,登录我们稍后讨论。会话只是一个存储数据的双向场所。


最终结果是:一个浏览器拥有一个包含大随机数的会话Cookie;另一个浏览器拥有不同的Cookie;更多浏览器,不同用户,不同Cookie。在服务器内部,我们有一个与每个标识符对应的小存储空间。

当请求到来时,我们可以通过Cookie重新关联,在所有会话中查找并选择正确的会话,然后将其作为 $_SESSION 交给PHP。

因此,Cookie可以“解锁”或为我们选择会话。


在PHP中使用会话

PHP内置了对会话的良好支持。我们基本上通过在输出任何内容之前调用 session_start() 来建立或创建新的PHP会话。你会看到很多操作都必须在输出之前完成,这属于“模型”代码,而非“视图”代码。

如果用户启用了Cookie,我们就可以使用 $_SESSION 变量在请求之间存储数据。这是一个双向变量:你可以从中取出数据,也可以存入数据。

现在你有了一个可能希望保留的东西,而POST数据在每次请求-响应周期后都会消失并重新创建,但会话不会。它被重新关联。你拥有Cookie,它重新关联会话,默认情况下我们将这些东西存储在磁盘上。

以下是会话数据随时间变化的过程:

一旦会话建立,Cookie进来,它将磁盘上的会话数据拉入 $_SESSION 变量。如果你更改了其中的数据,那么这些数据会被写回磁盘,然后响应发出。稍后,另一个请求(可能带有GET参数)到来,我们再次拉入会话数据,可以对其进行更改,然后发送响应。时间流逝,一个POST请求到来,带来新的POST数据,我们拉出会话数据,如果做了更改,就存回会话,然后发送请求。如此循环。

这就像跨越一系列请求-响应周期的全局变量。它是一个数组,一个键值对数组,你可以选择键名,做任何操作。但我们不在会话中存放巨型数据,而是像为应用存储一些“面包屑”,比如登录状态。


你可以通过 phpinfo() 找到服务器配置的会话存储位置,甚至可以查看会话文件。你会看到一个小文件,由会话ID作为键名。当请求到来时,它会找到这个小文件。文件内容不一定可读,但有时可以,里面是一系列键值对。这是一种我们不直接查看的魔法格式,但存储在 $_SESSION 变量中的内容就是以此格式存储在磁盘上的。

有些人通过查看这些文件来调试应用。其工作方式是:在你的应用非常早期,在任何输出之前,调用 session_start()session_start() 做两件事之一:如果Cookie不存在,则创建一个空会话并设置Cookie;如果Cookie存在,则重新关联旧的会话。无论哪种情况,完成后就可以使用 $_SESSION 变量了。

在调用 session_start() 之前不能使用 $_SESSION$_GET$_POST$_COOKIE 在第一行代码执行前就已设置,而 $_SESSION 只有在调用 session_start() 后才被设置。

你可以使用 session_destroy() 清除所有键。然后,只需使用赋值语句将数据存入会话。


代码示例与演示

以下是一段可以运行和试验的代码:

<?php
session_start();
?>
<html>
<body>
<?php
if (!isset($_SESSION[‘pizza’])) {
    echo(“Session is empty<br/>\n”);
    $_SESSION[‘pizza’] = 0;
} else if ($_SESSION[‘pizza’] < 3) {
    $_SESSION[‘pizza’] = $_SESSION[‘pizza’] + 1;
    echo(“Added one...\n”);
} else {
    session_destroy();
    session_start();
    echo(“Session Restarted\n”);
}
?>
<p><a href=”sessfun.php”>Click Me!</a></p>
<p>Our Session ID is: <?php echo(session_id()); ?></p>
<pre>
<?php print_r($_SESSION); ?>
</pre>
</body>
</html>

代码流程如下:请求到来,我们调用 session_start()。如果会话中没有 pizza 这个键,我们打印“Session is empty”,然后设置 $_SESSION[‘pizza’] = 0。接着打印出会话内容。

下一次请求到来,session_start() 执行,pizza 现在等于0(从磁盘拉入 $_SESSION)。因为存在 pizza 键,条件判断为假。然后检查 pizza 是否小于3,是则将其旧值0加1,存储为1。然后完成并输出。时间流逝,磁盘上存储着1。请求再次回来,pizza 等于1,它小于3,我们加1变成2,运行,再次回来变成3,依此类推。

关键在于,在 $_SESSION[‘pizza’] = $_SESSION[‘pizza’] + 1; 这个语句中,你正在从会话中读取和写入数据,而会话在服务器端,不在浏览器。Cookie解锁会话,Cookie重新关联会话,但Cookie不是会话本身。会话是存储在服务器上的键值对,其超全局变量名为 $_SESSION。它是在调用 session_start() 后才建立的。如果你在这之前打印 $_SESSION,它不存在。但调用 session_start() 后,它就存在了。


session_start() 从磁盘读取数据。如果你的某些PHP代码完全不使用会话,那么如果你不打算使用 $_SESSION 变量,就不需要调用 session_start()

以下是点击链接时的演示:首次点击创建会话ID并设置为0。

继续点击,值变为1、2、3,然后调用重置,清空它。

对应的代码逻辑是:如果值达到3或更大,就调用 session_destroy(),然后重新启动会话并运行。session_destroy() 不会改变会话ID,但会删除其中的所有键,从而清空会话。



无Cookie的会话

接下来我想讨论会话如何在不使用Cookie的情况下工作,我认为这是PHP一个非常酷的特性。


本节课中我们一起学习了PHP会话管理。我们理解了会话是存储在服务器端、用于跨请求保持数据的机制,而Cookie是客户端用于携带会话标识符以“解锁”对应会话的钥匙。我们学习了如何使用 session_start() 初始化会话,如何使用 $_SESSION 超全局数组存取数据,以及如何使用 session_destroy() 清除会话。我们还通过示例代码看到了会话数据如何在请求间持续存在。最后,我们提到了会话也可以在不依赖Cookie的情况下工作,这为更多场景提供了灵活性。掌握会话是构建需要状态保持的交互式Web应用的基础。

093:无Cookie会话实现 🍪➡️🔗

在本节课中,我们将要学习PHP中一个特殊但强大的功能:无Cookie会话。我们将探讨它的工作原理、适用场景以及如何通过代码实现它。

上一节我们介绍了PHP如何使用Cookie和Session来管理用户状态。本节中我们来看看一种不依赖Cookie的会话管理方式。

概述

大多数(约99%)的PHP应用都使用基于Cookie的会话,这通常工作得很好。然而,在某些特定场景下,例如需要在同一个浏览器的不同标签页中为同一网站维持两个独立的会话(比如一个标签页是教师身份,另一个是学生身份),基于Cookie的会话就无法满足需求,因为Cookie是跨标签页共享的。这时,无Cookie会话就派上了用场。

实现无Cookie会话

以下是实现无Cookie会话的核心代码片段。其基本思路是,将会话标识符(Session ID)通过URL参数(对于GET请求)或表单隐藏字段(对于POST请求)在页面间传递,而不是存储在Cookie中。

// 关键配置:禁用Cookie用于会话,并启用URL重写
ini_set('session.use_cookies', '0');
ini_set('session.use_only_cookies', '0');
ini_set('session.use_trans_sid', '1');

// 像往常一样启动会话
session_start();

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/5a157cb1e9db681f0958a5c7c33331ac_5.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/5a157cb1e9db681f0958a5c7c33331ac_6.png)

// 会话逻辑:一个简单的计数器
if (empty($_SESSION['value'])) {
    $_SESSION['value'] = 0;
} elseif ($_SESSION['value'] < 3) {
    $_SESSION['value']++;
} else {
    session_destroy();
    session_start();
    $_SESSION['value'] = 0;
}

这段代码首先通过ini_set配置PHP,告诉它不要使用Cookie来传递会话ID,并自动转换输出(如链接和表单)以嵌入会话ID。然后,它启动会话并管理一个简单的计数器变量。

会话ID的传递机制

为了让会话ID能在无Cookie的情况下从一个请求传递到下一个请求,我们需要手动将其嵌入到页面的链接和表单中。PHP的session.use_trans_sid设置会帮助我们自动完成这项工作。

以下是页面中可能生成的HTML代码示例:

<!-- 对于超链接(GET请求),会话ID作为URL参数传递 -->
<a href="page.php?PHPSESSID=abc123def456">点击我(GET请求)</a>

<!-- 对于表单(POST请求),会话ID作为隐藏字段传递 -->
<form action="page.php" method="post">
    <input type="hidden" name="PHPSESSID" value="abc123def456">
    <input type="submit" value="提交(POST请求)">
</form>

通过查看页面源代码,你可以观察到这两种“技巧”:

  • 在超链接(<a>标签)的href属性中,PHP自动添加了类似?PHPSESSID=...的查询参数。
  • 在表单(<form>)内部,PHP自动插入了一个名为PHPSESSID的隐藏输入字段。

当用户点击链接或提交表单时,这个会话标识符就会被发送到服务器。session_start()函数会检查GET或POST数据中是否存在这个标识符,并据此恢复对应的会话。

注意事项与权衡

使用无Cookie会话需要意识到以下几点:

  • 安全性考虑:会话ID会暴露在URL中。用户可能复制、分享或收藏这个URL,导致会话泄露。因此,对于安全性要求高的应用,需要采取额外的保护措施。
  • 可见性:在GET请求的URL中,会话ID对用户是可见的;而在POST请求中,它隐藏在表单里。这是无法避免的。
  • 适用性:它主要适用于无法或不愿使用Cookie的特殊场景。对于绝大多数普通应用,标准的基于Cookie的会话管理(使用$_SESSIONsession_start()session_destroy())是更简单、更安全的选择。

总结

本节课中我们一起学习了PHP的无Cookie会话实现。我们了解到:

  1. 它通过URL参数或表单隐藏字段传递会话ID,而非依赖浏览器Cookie。
  2. 它的核心配置是ini_set('session.use_cookies', '0')ini_set('session.use_trans_sid', '1')
  3. 它适用于需要在同一浏览器中为同一网站维持多个独立会话的特殊场景。
  4. 使用它需要权衡便利性和安全性,因为会话ID会暴露在URL中。

对于99%的应用场景,继续使用标准、安全的基于Cookie的会话管理即可。接下来,我们将深入探索如何在PHP中利用会话来构建更强大的Web应用功能。

094:Cookie与会话详解 🍪

在本节课中,我们将学习Web开发中的两个核心概念:Cookie和会话。我们将通过具体的PHP代码示例,了解它们如何工作、有何区别以及如何在实践中使用它们。

概述

Cookie和会话是Web应用程序中用于在无状态的HTTP协议上维持状态的关键技术。Cookie是存储在用户浏览器中的小段数据,而会话则利用Cookie在服务器端存储用户数据。理解它们的工作原理对于构建需要用户登录、购物车等功能的动态网站至关重要。

Cookie详解

Cookie是存储在用户浏览器中的键值对数据。服务器可以通过响应头将Cookie发送给浏览器,浏览器会在后续的请求中自动将其发送回服务器。

Cookie的工作原理

以下是设置和读取Cookie的基本PHP代码:

if (!isset($_COOKIE['zap'])) {
    setcookie('zap', 42, time() + 3600);
}

  • setcookie() 函数用于设置Cookie。它接收三个主要参数:Cookie的名称(键)、值以及过期时间。
  • $_COOKIE 是一个PHP超全局数组,包含了浏览器在本次请求中发送过来的所有Cookie。
  • 代码逻辑是:如果名为 zap 的Cookie不存在,就将其值设置为 42,并设定一小时后过期。

当用户首次访问页面时,服务器检测到 $_COOKIE[‘zap’] 未设置,于是通过 setcookie 函数在HTTP响应头中添加 Set-Cookie: zap=42 指令。浏览器收到后,会将该Cookie保存起来。

在后续的请求中,浏览器会自动在请求头中包含 Cookie: zap=42。PHP引擎会解析这个请求头,并将数据填充到 $_COOKIE 超全局数组中。因此,当代码再次执行时,isset($_COOKIE[‘zap’]) 条件为真,就不会再次设置相同的Cookie。

用户可以在浏览器的开发者工具中查看、修改或删除Cookie。这意味着Cookie数据并不完全安全,可以被用户操控。

会话详解

会话(Session)提供了一种在服务器端存储用户数据的方法,它通常借助一个Cookie来追踪用户。

会话的工作原理

要使用PHP会话,首先需要调用 session_start() 函数。

session_start();

session_start() 被调用时,PHP会执行以下操作:

  1. 检查请求中是否包含会话ID(通常通过一个名为 PHPSESSID 的Cookie)。
  2. 如果没有,则生成一个唯一、随机的会话ID,并通过 Set-Cookie 响应头将其发送给浏览器。
  3. 初始化 $_SESSION 超全局数组。这个数组的数据存储在服务器上(例如服务器的临时文件或数据库中),而不是浏览器中。

会话数据操作

会话数据通过 $_SESSION 数组进行读写。以下是一个计数器示例:

session_start();
if (!isset($_SESSION[‘pizza’])) {
    echo(“Session is empty\n”);
    $_SESSION[‘pizza’] = 0;
} elseif ($_SESSION[‘pizza’] < 3) {
    $_SESSION[‘pizza’] ++;
    echo(“Added one\n”);
} else {
    session_destroy();
    echo(“Session Destroyed\n”);
}
print_r($_SESSION);

代码执行流程如下:

  1. 首次访问$_SESSION[‘pizza’] 未设置,将其初始化为 0
  2. 后续访问(值小于3):每次访问,pizza 的值增加1。
  3. 当值达到3:调用 session_destroy() 函数。这会清空服务器上与该会话ID关联的所有数据(即 $_SESSION 数组),但不会删除浏览器中的会话ID Cookie。下次访问时,由于会话ID依然存在,会开启一个新的空会话。

会话数据实际存储在服务器的特定目录(可通过 session.save_path 配置项查看)中,以文件形式保存,内容经过序列化处理。这保证了数据的私密性,用户无法直接查看或修改。

Cookie与会话的关系与区别

上一节我们介绍了会话在服务器端存储数据的机制,现在我们来总结一下它与Cookie的关系和核心区别。

虽然会话依赖于Cookie来传递会话ID,但它们是不同的概念:

  • 存储位置:Cookie数据存储在客户端(浏览器),会话数据存储在服务器端
  • 安全性:会话更安全,因为敏感数据不在客户端存储和传输。Cookie中的数据可能被用户查看或修改。
  • 容量限制:单个Cookie大小通常有限制(如4KB),而会话可以存储更多数据,仅受服务器配置限制。
  • 生命周期:Cookie可通过设置过期时间长期存在,而会话通常在浏览器关闭后失效(会话ID Cookie过期),或通过 session_destroy() 手动销毁。

总结

本节课中我们一起学习了Web开发中维持用户状态的两种关键技术。
我们了解到,Cookie是存储在浏览器中的键值对,用于在请求间携带少量信息,可通过 setcookie() 设置,通过 $_COOKIE 读取。
会话则是一种更强大的机制,它利用Cookie传递一个会话ID,从而在服务器端安全地存储用户相关的数据,通过 session_start() 启动,通过 $_SESSION 数组进行操作。

理解Cookie和会话的区别与联系,是构建交互式Web应用程序的重要基础。

095:密歇根州底特律

在本节课中,我们将跟随查克老师,一起回顾在密歇根州底特律市举行的一次特别办公时间。本次办公时间旨在介绍课程中的一些同学,让大家感受学习社群的氛围。

师生介绍

查克老师在底特律的伍德沃德大道向大家问好,并开始介绍在场的同学们。

以下是参与本次办公时间的同学自我介绍:

  • 伊奥拉·西格斯:我是一名学生,来自密歇根州底特律的“聚焦希望阶梯桥梁”项目。感谢有机会与大家见面。
  • 卡伦·曼:我是一个老派极客。同时也是一名大学教授,在奥克兰大学教授编程等相关内容。
  • 戈登:这是我的第一门Coursera课程,我很享受跟着查克博士学习。
  • 阿里:我大约两年前上过查尔斯的这门课。这是我上过的最好的课程之一。
  • :我在这里是为了学习更多编程知识。
  • 凯文·罗尔:我代表MyTT Tech,正在学习Python以提升我的编程技能。
  • 玛丽:我正在探索Python。我恰好在进行一次长途自行车旅行,正好路过这里。

课程总结

本节课中我们一起学习了查克老师在底特律办公时间的记录。通过同学们的自我介绍,我们看到了来自不同背景、拥有不同目标的学习者如何汇聚在同一门课程中。这种社群的连接是学习过程中宝贵的一部分。查克老师预告下一次办公时间将在波士顿举行。

096:重定向路由与身份验证 🚦

在本节课中,我们将学习Web开发中的一个核心概念:重定向。我们将探讨服务器如何通过发送特殊的HTTP响应来“指挥”浏览器跳转到另一个页面,以及这在构建应用程序(特别是处理表单提交和用户身份验证)时为何至关重要。


HTTP状态码简介

上一节我们介绍了模型-视图-控制器模式,并提到会深入讲解控制器中的路由部分。本节中我们来看看路由的一个关键工具:重定向。要理解重定向,首先需要了解HTTP状态码。

大多数情况下,当你成功访问一个网页时,服务器返回的状态码是 200 OK。这意味着“请求成功,这是你要的页面”。另一个广为人知的状态码是 404 Not Found,表示请求的资源不存在。

本节课我们将重点讨论 302 Found(临时重定向)状态码。还有一个类似的 301 Moved Permanently(永久重定向)。例如,访问 drchuck.com(无短横线)会被重定向到 dr-chuck.com(有短横线)。这种机制允许网页地址变更后,仍能引导用户到正确的位置。

重定向的工作原理

重定向是如何实现的呢?关键在于服务器返回的响应头。

当服务器决定进行重定向时,它会发送一个包含 302 状态码和 Location 响应头的HTTP响应。Location 头指定了浏览器应该跳转到的目标URL。

以下是其工作流程:

  1. 浏览器向服务器A发送一个GET请求。
  2. 服务器A响应 302 状态码和 Location: 服务器B的URL
  3. 浏览器接收到这个响应后,不会将其内容显示给用户,而是立即自动向 Location 头指定的URL(服务器B)发起一个新的GET请求。
  4. 服务器B响应 200 状态码和页面内容,这个页面最终被浏览器渲染并展示给用户。

因此,一次用户操作实际上触发了两个完整的“请求-响应”周期

在PHP中实现重定向

在PHP中,我们使用 header() 函数来设置HTTP响应头,包括重定向。

核心代码格式如下:

header("Location: 目标URL");
return; // 或 exit();

重要注意事项:

  • header() 函数必须在任何实际输出(包括HTML标签、空格或 echo 语句)发送到浏览器之前调用。否则会导致错误。
  • 在我们的模型-视图-控制器结构中,这意味着重定向逻辑必须放在控制器部分,即进入视图渲染(输出HTML)之前的代码区域。
  • 调用 header() 进行重定向后,通常应立即使用 returnexit 终止脚本的继续执行,因为后续的输出已无意义。

重定向示例演示

让我们通过一个简单的例子来理解。假设我们有一个页面 reader1.php,它包含一个表单,根据用户输入的数字决定重定向的目标。

以下是 reader1.php 的控制器逻辑部分:

// 控制器部分:处理逻辑和路由决策
if (isset($_GET['num'])) {
    $num = $_GET['num'];
    if ($num == '1') {
        // 情况1:重定向回自身
        header("Location: reader1.php");
        return;
    } elseif ($num == '2') {
        // 情况2:重定向到另一个页面,并携带GET参数
        header("Location: reader2.php?message=Hello");
        return;
    } elseif ($num == '3') {
        // 情况3:重定向到外部网站
        header("Location: https://www.dr-chuck.com");
        return;
    }
}
// 如果未触发重定向,则继续执行,渲染下方的视图(表单)
?>

视图部分(简单的表单):

<!-- 视图部分:呈现给用户的界面 -->
<form method="get">
    <input type="text" name="num">
    <input type="submit" value="提交">
</form>

运行结果:

  • 输入 1 并提交:页面会刷新(重定向到自身)。
  • 输入 2 并提交:浏览器会跳转到 reader2.php?message=Hello
  • 输入 3 并提交:浏览器会跳转到外部网站 dr-chuck.com

在浏览器开发者工具的“网络”(Network)标签页中,你可以观察到两次请求:第一次是对 reader1.php 的请求,返回 302;第二次是对新URL的请求,返回 200

重定向的典型应用:处理POST请求

重定向有一个极其重要的应用模式:Post/Redirect/Get (PRG)

问题: 当用户通过POST方法提交表单(如登录、发表评论)后,如果直接刷新页面,浏览器会提示“重新提交表单”,这可能导致数据被重复提交。

解决方案: 使用PRG模式。

  1. 用户通过POST请求提交表单到服务器。
  2. 服务器处理数据(如存入数据库)。
  3. 处理完成后,服务器不直接返回结果页面,而是向浏览器发送一个 302 重定向响应,指向一个显示结果的页面(通常是GET请求)。
  4. 浏览器遵循重定向,使用GET请求加载结果页面。

这样,即使用户刷新浏览器,也只是重新GET结果页面,而不会重复提交POST数据。这提升了用户体验和数据的完整性。


本节课中我们一起学习了Web开发中的重定向机制。我们了解了302301状态码的含义,掌握了在PHP中使用header(“Location: ...”)实现重定向的方法,并认识了重定向在处理表单提交(PRG模式)中的关键作用。重定向是控制器进行路由调度、管理用户流程和构建健壮Web应用的基础工具。在接下来的课程中,我们将看到它如何与身份验证等概念结合,构建更复杂的应用逻辑。

097:路由与重定向 🧭

在本节课中,我们将学习Web开发中的一个核心概念:重定向。我们将通过一个简单的PHP示例,详细解析重定向的工作原理及其在路由中的应用。

概述

重定向是Web服务器告知浏览器“您请求的资源已移至新位置”的一种方式。它通过HTTP响应头实现,是构建动态Web应用和实现页面跳转的关键技术。

什么是重定向?

当您访问一个网址时,服务器会返回一个HTTP状态码。状态码200表示请求成功。而状态码302则表示“已找到”,但资源位于其他位置。服务器会通过一个名为Location的HTTP头部,告诉浏览器应该前往的新地址。

例如,访问doctorchuck.com(无连字符)时,服务器会返回一个302状态码和Location: doctor-chuck.com的头部,浏览器便会自动跳转到带连字符的网址。您会观察到浏览器地址栏中的URL发生了变化。

在PHP中实现重定向

在PHP中,我们可以使用header()函数来设置HTTP响应头。echoprint用于向响应的主体部分输出内容,而header()则用于在主体内容之前发送HTTP头部信息。

以下是实现重定向的核心代码:

header("Location: new_page.php");
exit; // 确保重定向后停止执行后续代码

示例代码详解

我们来看一个具体的示例。该示例包含一个表单,根据提交的数字将用户重定向到不同的页面。

以下是示例代码的主要逻辑:

if ($_POST[‘value’] == 1) {
    header(“Location: redirect1.php”);
    return;
} elseif ($_POST[‘value’] == 2) {
    header(“Location: redirect2.php?param=123”);
    return;
} elseif ($_POST[‘value’] == 3) {
    header(“Location: https://www.doctor-chuck.com”);
    return;
}

代码执行流程分析

让我们逐步分析不同输入下的执行流程:

输入数字1:

  • 表单将数据POSTredirect1.php
  • 服务器处理请求,发现value等于1。
  • 服务器返回302 Found状态码和Location: redirect1.php头部。
  • 浏览器立即向redirect1.php发起一个新的GET请求,页面刷新。

输入数字2:

  • 表单同样将数据POSTredirect1.php
  • 服务器处理请求,发现value等于2。
  • 服务器返回302 Found状态码和Location: redirect2.php?param=123头部。
  • 浏览器立即向redirect2.php?param=123发起一个新的GET请求,页面跳转至新地址。

输入数字3:

  • 表单将数据POSTredirect1.php
  • 服务器处理请求,发现value等于3。
  • 服务器返回302 Found状态码和Location: https://www.doctor-chuck.com头部。
  • 浏览器立即向https://www.doctor-chuck.com发起一个新的GET请求,最终加载外部网站的全部内容(包括HTML、CSS、图片等)。

通过浏览器的开发者工具“网络”(Network)面板,可以清晰地观察到每次POST请求后,紧随其后的302响应和新的GET请求。

总结

本节课我们一起学习了Web应用中的重定向机制。我们了解到重定向通过HTTP的302状态码和Location头部实现,是控制用户流和实现页面路由的基础。在PHP中,使用header(“Location: url”)函数可以方便地发起重定向。掌握这一技术,对于构建具有良好用户体验和逻辑流程的Web应用程序至关重要。

098:POST刷新重定向 🚦

在本节课中,我们将要学习一个非常重要的Web开发模式:POST/重定向/GET。这个模式用于解决表单提交后刷新页面可能导致数据重复提交的问题。我们将理解其原理,并学习如何通过PHP会话(Session)来实现它。

理解问题:为什么需要POST/重定向/GET

上一节我们介绍了重定向的机制。本节中我们来看看为什么需要使用它。

一个核心原则是:HTTP GET请求用于获取数据,而HTTP POST请求用于修改数据。你不应该使用GET请求来修改数据。问题在于,如果你提交一个POST表单,服务器返回一个结果页面,然后用户刷新这个结果页面,浏览器会询问是否要重新发送POST数据。如果用户确认,就会导致数据被重复修改,例如重复支付100美元。

浏览器为了避免这种危险的双重提交,会弹出一个不受开发者控制的警告。这意味着我们不应该在响应POST请求时直接返回HTML内容。

POST/重定向/GET模式

以下是POST/重定向/GET模式的工作流程:

  1. 用户填写一个表单(method="post")并提交。
  2. 服务器处理POST请求,更新数据库。
  3. 服务器不返回HTML,而是发送一个HTTP重定向响应(状态码302),指示浏览器发起一个新的GET请求(通常是回到同一个页面)。
  4. 浏览器自动发起GET请求。
  5. 服务器处理GET请求,并返回最终的HTML结果页面。

这样,用户最后看到的结果页面是由一个GET请求产生的。此时用户刷新页面,只会重复GET请求,而不会重复提交POST数据,从而避免了双重提交问题。

从错误代码到正确实现

之前展示的代码在处理POST请求后直接渲染视图并输出HTML,这是错误的做法。它会导致刷新时出现浏览器警告。

正确的做法需要解决一个关键问题:POST请求处理后的数据(如成功消息、旧表单值)如何传递给后续的GET请求?因为HTTP是无状态的,$_POST$_GET数据不会在请求间保留。

解决方案是使用会话(Session)

以下是实现POST/重定向/GET模式的核心步骤:

  1. 启动会话:在处理脚本的开头使用 session_start()。这会在服务器端创建一个存储空间,并通过Cookie与特定用户关联。
  2. 处理POST请求
    • 执行数据验证和数据库更新等逻辑。
    • 将需要传递给下一个页面的数据(如提示信息)存储在 $_SESSION 超全局数组中。例如:$_SESSION[‘message’] = “操作成功!”;
    • 不输出任何HTML,使用 header(‘Location: some_page.php’) 函数发送重定向头,然后立即 exit
  3. 处理重定向后的GET请求
    • 同样以 session_start() 开始。
    • 检查 $_SESSION 中是否存在之前存储的数据。
    • 将这些数据从 $_SESSION 中取出,用于构建HTML视图。
    • 重要:使用后应立即清除这些会话数据(如 unset($_SESSION[‘message’])),以防止它们在后续的页面刷新中重复出现。这种临时存储消息的模式常被称为“Flash消息”。

工作流程图示与总结

本节课中我们一起学习了POST/重定向/GET模式。

整个流程可以概括为:一个POST请求,将数据存入Session并重定向;紧接着一个GET请求,从Session取出数据并渲染页面

通过这种方式,我们确保了:

  • 数据安全:防止了表单的意外重复提交。
  • 用户体验:消除了浏览器的重复提交警告,使刷新操作变得自然。
  • 代码结构清晰:将数据处理(POST)和结果展示(GET)清晰地分离在两个独立的请求/响应周期中。

你现在已经掌握了构建更健壮、用户友好的Web表单处理程序的关键技术。在接下来的课程中,我们将运用会话机制来实现用户登录和注销功能。

099:POST重定向代码详解 🚀

在本节课中,我们将学习一个重要的Web开发模式:POST重定向GET。我们将通过分析一个猜数字游戏的PHP代码,来理解为什么需要这个模式,以及如何实现它。这个模式能防止用户因刷新页面而意外重复提交表单数据,从而提升应用的安全性和用户体验。

问题:直接响应POST请求的缺陷

上一节我们介绍了模型-视图-控制器(MVC)的基本概念。本节中我们来看看一个直接处理POST请求的代码示例(guess.php)所存在的问题。

在这段代码中,当用户提交一个猜测数字的表单(使用POST方法)后,服务器会直接处理这个请求,并立即生成包含反馈信息(如“猜对了”或“猜低了”)的HTML页面输出。

核心流程如下:

// 模型部分:处理POST数据
if (isset($_POST[‘guess’])) {
    $guess = $_POST[‘guess’];
    // ... 处理逻辑,生成 $message ...
}
// 视图部分:直接输出结果
// ... HTML模板,其中包含动态的 $message ...

这种模式存在一个严重问题:当页面显示结果后,如果用户点击浏览器的刷新按钮,浏览器会弹出一个警告,询问是否要重新提交表单数据。这是因为浏览器将上一次请求(POST)视为可能修改服务器数据的操作(例如银行转账),为了防止用户无意中重复操作,浏览器会进行拦截。

此时,我们作为开发者已经失去了对用户体验的控制。用户必须手动确认才能看到新页面,这显得很不专业。

解决方案:POST重定向GET模式

为了解决上述问题,我们需要采用一种称为“POST重定向GET”(PRG)的模式。其核心思想是:永远不要在直接响应POST请求时生成页面输出。

以下是该模式的工作原理:

  1. 用户通过POST请求提交表单。
  2. 服务器处理POST数据(模型部分),但不生成任何HTML输出
  3. 服务器在处理完成后,向浏览器发送一个HTTP 302重定向响应,指示浏览器使用GET方法去请求另一个(或同一个)URL。
  4. 浏览器自动发起这个新的GET请求。
  5. 服务器响应这个GET请求,生成并返回最终的HTML页面(视图部分)。

这样,用户最后看到的是GET请求的结果。此时再刷新页面,浏览器只是无害地重复GET请求,而不会触发重新提交POST数据的警告。

实现挑战:在请求间传递数据

在PRG模式中,处理POST请求和响应GET请求是两个独立的HTTP请求/响应周期。一个关键问题是:如何在第一个(POST)请求中生成的消息(如“猜对了”),传递到第二个(GET)请求中并显示出来?

答案是使用 $_SESSION(会话)。会话是一种在服务器端存储用户特定数据的机制,可以在同一个用户的不同请求间共享数据。

数据传递流程如下:

  1. 在处理POST请求时,将需要传递的数据(如$message)存入$_SESSION
    $_SESSION[‘message’] = $message;
    
  2. 然后立即执行重定向并结束当前脚本。
    header(“Location: guess2.php”);
    return;
    
  3. 当浏览器重定向到新页面(GET请求)时,从$_SESSION中取出之前存入的数据。
    $message = $_SESSION[‘message’] ?? false;
    
  4. 使用这些数据来渲染最终的HTML页面。

代码对比:guess.phpguess2.php

让我们对比一下有问题的旧代码(guess.php)和实现了PRG模式的新代码(guess2.php)。

guess.php(有问题的版本)特点:

  • 处理POST请求后,代码直接“贯穿”到下方的HTML输出部分。
  • 消息变量(如$message)直接在同一个请求周期内用于渲染。

guess2.php(正确的版本)关键修改:

以下是guess2.php中模型部分的核心逻辑:

session_start(); // 启用会话支持

// 检查是否是POST请求
if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {
    // 1. 从POST获取数据并处理
    $oldguess = $_POST[‘guess’] ?? ‘’;
    // ... 业务逻辑,判断猜测结果,生成 $message ...

    // 2. 将需要传递的数据存入SESSION
    $_SESSION[‘guess’] = $oldguess;
    $_SESSION[‘message’] = $message;

    // 3. 执行重定向(到自身),并立即结束脚本
    header(“Location: guess2.php”);
    return; // 关键:防止代码继续执行并输出内容
}

// 4. 以下是处理GET请求的部分(也是重定向后再次执行的部分)
// 从SESSION中取出数据
$oldguess = $_SESSION[‘guess’] ?? ‘’;
$message = $_SESSION[‘message’] ?? false;

// 5. 可选:清除SESSION中的临时数据,防止重复显示
unset($_SESSION[‘guess’]);
unset($_SESSION[‘message’]);

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/16bf5c019465d9e3b0ea1a484ea7984c_10.png)

// 6. 使用取出的数据渲染HTML视图
// ... 包含 $message 和 $oldguess 的HTML模板 ...

视图部分(HTML模板) 在两个文件中几乎完全相同,都是动态嵌入$message$oldguess变量。区别在于,在guess2.php中,这些变量的值来源于$_SESSION,而非直接来自POST处理逻辑。

工作流程演示

让我们跟踪一次用户交互,看看guess2.php是如何工作的:

  1. 用户访问guess2.php(GET请求),看到一个空的输入表单。
  2. 用户输入“41”并提交表单(POST请求到guess2.php)。
  3. 服务器端guess2.php脚本:
    • 检测到POST请求。
    • 处理猜测数字,得出“太低”的结论,并将消息存入$_SESSION[‘message’]
    • 发送一个302 Found重定向响应,头部包含Location: guess2.php
    • 执行return,脚本终止,没有输出任何HTML
  4. 浏览器收到302响应,立即自动向Location指定的地址(guess2.php)发起一个新的GET请求。
  5. 服务器再次执行guess2.php脚本:
    • 这次是GET请求,跳过POST处理块。
    • $_SESSION中取出之前存储的message(“太低”)和guess(“41”)。
    • 使用这些数据渲染完整的HTML页面,返回给浏览器。
  6. 用户看到显示“太低”和之前输入数字“41”的结果页面。
  7. 如果此时用户刷新页面,浏览器只是重复第5步的GET请求,安全且无害。

核心模式总结

本节课中我们一起学习了POST重定向GET(PRG)模式。这是一个在Web开发中至关重要的最佳实践,其通用模式可以总结为以下几步:

当处理表单POST请求时:

  1. 验证并处理提交的数据。
  2. 将需要在下一页显示的数据(如成功消息、错误信息、用户输入)存储到$_SESSION中。
  3. 使用header(‘Location: …’)函数发起一个重定向(通常是重定向到自身或一个结果页面)。
  4. 立即执行returnexit确保脚本在重定向后停止执行,避免任何意外的输出。

这个设置SESSION -> 重定向 -> 返回的模式,在需要遵循PRG规则的场景中会被反复使用。掌握了它,你就能构建出更健壮、用户体验更好的Web应用程序。

100:实现登录与注销功能 🔐

在本节课中,我们将学习如何构建一个功能完整且规范的登录与注销系统。我们将使用会话(Session)管理、页面重定向以及一个称为“闪存消息”的概念来实现这一功能。

会话与登录状态

上一节我们介绍了会话的基本概念,本节中我们来看看如何利用会话来管理登录状态。

首先需要明确,会话本身并不等同于登录状态。我们通过在会话中存储一小段数据来指示用户是否已登录。当用户访问网站时,会话启动;当用户登录时,会话数据被修改。应用程序的其余部分通过检查会话中的数据来决定用户是否已登录。而注销操作,则仅仅是将会话中的相关信息移除。

登录流程概述

以下是登录功能的核心流程:

  1. 用户提交登录表单(POST请求)。
  2. 服务器端代码检查提交的数据。
  3. 如果登录成功,服务器修改会话数据以标记用户为“已登录”状态,然后执行重定向。
  4. 浏览器收到重定向指令,发起一个新的GET请求。
  5. 应用程序的其他部分(处理后续GET请求的代码)通过检查会话数据来判断用户的登录状态。

登录后,会话Cookie依然存在,因为它用于确定哪个会话是活跃的。但登录状态本身(例如 logged_in = true)是存储在会话数据中的。

构建登录功能:代码解析

接下来,我们通过代码来具体实现登录功能。我们将处理几种使用场景:用户未登录时提示登录、登录失败时显示错误信息、登录成功时跳转并显示欢迎信息。

视图代码(View)

以下是显示登录表单的视图代码。它包含一个用于显示闪存消息的区域和一个提交账户密码的表单。

<!-- 显示闪存消息的区域 -->
<p style="color:red">
    <?php
        if ( isset($_SESSION['error']) ) {
            echo($_SESSION['error']);
            unset($_SESSION['error']); // 显示后立即删除,实现“闪存”
        }
    ?>
</p>

<!-- 登录表单 -->
<form method="post">
    <p><label for="account">Account:</label>
    <input type="text" name="account" id="account"></p>
    <p><label for="pw">Password:</label>
    <input type="password" name="pw" id="pw"></p>
    <p><input type="submit" value="Log In"></p>
</form>

关键点

  • 表单使用 method="post",这是为了安全,避免密码出现在URL或浏览器历史记录中。
  • 错误消息从 $_SESSION['error'] 中读取,显示后立即用 unset() 删除,确保只显示一次。

模型与控制器代码(Model/Controller)

以下是处理登录逻辑的PHP代码(通常位于 login.php)。它负责启动会话、验证凭证、设置会话状态并决定重定向方向。

<?php
session_start(); // 启动会话,必须是文件的第一行代码

if ( isset($_POST['account']) && isset($_POST['pw']) ) {
    // 登录尝试开始,先清除可能存在的旧会话数据
    unset($_SESSION['account']);

    // 简化验证:此处使用硬编码密码,实际应用中应查询数据库
    if ( $_POST['pw'] == 'secret' ) {
        // 登录成功
        $_SESSION['account'] = $_POST['account']; // 在会话中存储账户名,作为登录标识
        $_SESSION['success'] = 'Logged in.'; // 设置成功闪存消息
        header('Location: app.php'); // 重定向到主应用页面
        return;
    } else {
        // 登录失败
        $_SESSION['error'] = 'Incorrect password.'; // 设置错误闪存消息
        header('Location: login.php'); // 重定向回登录页面
        return;
    }
}
?>

关键点

  • session_start() 必须在输出任何内容之前调用。
  • unset($_SESSION['account']) 用于在尝试登录前清除之前的登录状态。
  • 根据密码验证结果,程序将用户重定向到不同页面(app.phplogin.php),并设置相应的闪存消息(successerror)。这是一个典型的控制器路由决策。
  • 重定向后立即执行 return,确保后续代码不会意外执行。

闪存消息机制详解

闪存消息是一种只显示一次的消息传递模式。其工作流程如下:

  1. 设置:在处理POST请求时(如登录验证失败),将消息存入 $_SESSION
  2. 重定向:执行 header('Location: ...') 重定向。
  3. 显示与销毁:在重定向目标页面(GET请求)中,从 $_SESSION 读取该消息并显示给用户,随后立即将其从 $_SESSION 中删除

这样,即使用户刷新页面,该消息也不会再次出现,因为它已被清除。这种模式在需要向用户传递一次性通知(如操作成功或错误提示)时非常有用。

主应用页面与登录状态检查

用户登录成功后,会被重定向到主应用页面(例如 app.php)。该页面负责检查用户的持久登录状态。

<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head><title>Main Application</title></head>
<body>
<h1>Application</h1>

<?php
// 1. 检查并显示成功闪存消息(如“Logged in.”)
if ( isset($_SESSION['success']) ) {
    echo('<p style="color:green">'.$_SESSION['success']."</p>\n");
    unset($_SESSION['success']); // 显示后删除
}

// 2. 检查持久登录状态
if ( ! isset($_SESSION['account']) ) {
    // 未登录:显示登录提示和链接
    echo('<p><a href="login.php">Please log in</a></p>');
} else {
    // 已登录:显示欢迎信息和注销链接
    echo("<p>Welcome ".htmlentities($_SESSION['account']).", to our cool application.</p>");
    echo('<p><a href="logout.php">Log Out</a></p>');
}
?>
</body>
</html>

关键点

  • 成功登录的闪存消息($_SESSION['success'])在这里被显示并立即清除。
  • 通过检查 $_SESSION['account'] 是否存在来判断用户是否已登录。这个键值对在登录成功后设置,并持久保留在会话中,直到用户注销或会话结束。
  • 根据登录状态,动态显示不同的内容:提示登录或显示欢迎语及注销链接。

实现注销功能

注销功能的实现非常简单,是作者最喜欢编写的代码之一。

<?php
// logout.php
session_start();
session_destroy(); // 销毁当前会话中的所有数据
header('Location: app.php'); // 重定向回主应用页面
?>

关键点

  • session_destroy() 函数会清除当前会话中存储的所有数据。
  • 重定向到 app.php 后,该页面检查 $_SESSION['account'] 将发现其不存在,从而提示用户重新登录,从而完成完整的登出流程。

总结

本节课中我们一起学习了如何构建一个完整的登录与注销系统。我们深入探讨了几个核心概念:

  1. 会话管理:使用 $_SESSION 超全局数组在请求间持久化数据(如用户账户名)和传递一次性消息。
  2. Post-Redirect-Get (PRG) 模式:通过在处理POST请求后立即重定向到GET请求,来避免表单重复提交和改善用户体验。
  3. 闪存消息:一种利用会话实现的、只显示一次的消息传递机制,用于显示操作成功或错误提示。
  4. 状态检查:应用程序的各个页面通过检查会话中特定的键(如 $_SESSION['account'])来判断用户的认证状态,并据此决定显示内容。

通过组合这些技术,我们能够创建出安全、用户友好且符合Web最佳实践的认证流程。

101:登录与注销3 🔐

在本节课中,我们将学习一个简单的登录与注销应用程序。我们将深入探讨其代码结构,理解如何通过会话(Session)管理用户登录状态,以及如何使用“闪现消息”(Flash Message)在页面间传递一次性信息。这个应用演示了模型-视图-控制器(MVC)模式、POST重定向(PRG)模式等核心概念的实际应用。

应用概述与文件结构

这个应用程序由三个核心PHP文件组成:

  • app.php:主应用页面,根据用户登录状态显示不同内容。
  • login.php:处理用户登录的表单和逻辑。
  • logout.php:处理用户注销的逻辑。

应用程序的核心逻辑是:通过检查会话(Session)中是否存在 account 键来判断用户是否已登录。

app.php:主页面逻辑

上一节我们介绍了应用的整体结构,本节中我们来看看主页面 app.php 是如何工作的。

app.php 的模型部分非常简单,主要是启动会话。视图部分则根据会话状态动态显示内容。

以下是 app.php 的核心逻辑步骤:

  1. 启动会话:使用 session_start() 函数。
  2. 检查登录状态:判断 $_SESSION 数组中是否存在名为 account 的键。
  3. 显示内容
    • 如果 account不存在,显示“请登录”的提示。
    • 如果 account存在,显示“欢迎使用酷应用”的提示和一个“注销”链接。
  4. 处理闪现消息:检查会话中是否存在 success 消息,如果存在则显示(通常为绿色),显示后立即从会话中删除该消息,使其只出现一次。

其核心判断逻辑可以用以下伪代码表示:

if (isset($_SESSION[‘account’])) {
    // 用户已登录,显示应用主界面和注销链接
} else {
    // 用户未登录,显示登录提示
}

login.php:登录处理与闪现消息

现在,让我们进入登录页面 login.php,看看它是如何验证用户并管理消息的。

login.php 的代码分为两部分:顶部的控制器逻辑(处理POST请求)和底部的视图逻辑(显示HTML表单)。它引入了“闪现消息”的概念,用于在重定向后显示一次性提示。

以下是 login.php 处理登录请求的详细流程:

  1. 启动会话
  2. 处理POST请求(控制器部分)
    • 无论提交什么,先执行 unset($_SESSION[‘account’]),这相当于注销当前用户(如果存在)。
    • 检查提交的密码是否等于硬编码的字符串 ‘umsi’
    • 如果密码错误
      • $_SESSION[‘error’] 中设置错误信息(例如“密码错误”)。
      • 使用 header(‘Location: login.php’) 重定向回登录页面本身。
    • 如果密码正确
      • $_SESSION[‘success’] 中设置成功信息(例如“登录成功”)。
      • $_SESSION[‘account’] 中设置用户标识(例如用户名)。
      • 使用 header(‘Location: app.php’) 重定向到主页面 app.php
  3. 显示页面(视图部分)
    • 在表单上方,检查 $_SESSION 中是否存在 errorsuccess 键。
    • 如果存在,则显示相应消息(错误为红色,成功为绿色)。
    • 关键步骤:显示消息后,立即使用 unset() 函数将其从 $_SESSION 中删除。这就是“闪现”的含义——消息只在下一次页面加载时出现一次,刷新后就会消失。

这个“POST-处理-重定向-GET显示”的循环,就是POST重定向(PRG)模式的典型应用,它避免了表单重复提交,并允许我们在重定向后的页面上显示消息。

logout.php:注销流程

最后,我们来看最简单的部分——注销。理解了登录状态是如何通过会话管理的,注销就变得非常直观。

logout.php 的代码极其简洁,它的唯一目的就是清除用户的登录状态。

以下是 logout.php 的执行步骤:

  1. 启动会话
  2. 销毁会话数据:使用 session_destroy()unset($_SESSION[‘account’]) 来移除登录标识。示例中使用了 session_destroy() 来清除整个会话。
  3. 重定向:使用 header(‘Location: app.php’) 将用户带回主页面 app.php

当用户回到 app.php 时,由于会话中已没有 account 键,页面将显示“请登录”的提示,从而完成整个注销流程。

总结

本节课中我们一起学习了一个完整的登录/注销应用实例。我们深入分析了三个文件:

  • app.php 作为视图,根据模型(会话状态)决定显示内容。
  • login.php 充当了控制器(处理POST逻辑)和视图(显示表单)的角色,并实践了闪现消息POST重定向模式。
  • logout.php 作为控制器,执行状态清理并重定向。

这个小型应用清晰地展示了如何使用PHP会话管理用户状态、如何在不同的请求周期(POST/GET)间安全地传递信息,以及MVC和PRG模式如何协同工作以构建一个健壮、用户友好的Web交互流程。

102:布莱切利园重聚

概述

在本节附加办公时间中,我们将跟随课程来到计算机科学、互联网历史与技术的发源地之一——布莱切利园。这里举办了一场特别的、创纪录的线下见面会,你将有机会认识来自世界各地的同学和导师。


我们身处布莱切利园,这里是计算机科学、互联网历史、技术以及所有计算领域的起点。我们举办了一场非常特别的、创纪录的线下办公时间活动。现在,我想让你认识一下你的同学们。

以下是参与本次活动的同学和导师介绍:

  • 史蒂夫:我是史蒂夫。我是Python系列课程的所有导师之一。
  • 博诺瓦:嗨,我是博诺瓦,来自法国,现居伦敦。很高兴来到这里。
  • 克雷格:我是克雷格,正在学习Python网络数据课程。我在线上见过你。
  • 史蒂夫(另一位):我是史蒂夫。我完成了第一门Python课程,并打算很快开始学习其他课程。这门课程对我入门这门语言非常有帮助。
  • 珍妮:嗨,我是珍妮,是一名软件开发人员。我使用《Python for Everybody》课程在泰国清迈的一所国际学校教授编程课。
  • 阿尔诺:你好,我是来自法国的阿尔诺。我在大学担任Arduino讲师。因为学习了Python课程,我忘记了Arduino的语法,这在我的工作中有点尴尬。
  • 凯文:凯文,我只从伦敦过来。能听到课程是如何构建的以及你如何制作课程,真的很好,谢谢你。
  • 静和苏:嗨,我们是静和苏,来自中国,是你的学生。我们目前在英国工作。很高兴在布莱切利园见到你,Python课程很棒。
  • 詹尼斯:你好,我是詹尼斯。我使用Python很长时间了,但这是我第一次通过课程来系统地学习这门语言。谢谢你。
  • 伊莎贝尔:你好,我是伊莎贝尔。我用你的课程来刷新我的计算机科学知识。我正在跟你学习Python。我来过布莱切利园至少五次,像你一样,我也是这里的粉丝。
  • 帕特里克:嗨,我是帕特里克。我学习了互联网历史课程。今天对我来说是非常激动人心的一天,因为学那门课时我说过我想来布莱切利园,今天我来了。我还说过我想参加一次查克博士的办公时间,所以今天我一下子实现了两个愿望。
  • 大卫:嗨,我是大卫。我学习了你的Python课程和互联网历史课程,非常喜欢。欢迎你,很高兴你在这里。
  • 保罗:嗨,我是保罗。我学习了Python课程,它们非常棒,谢谢你。
  • :嗨,我是来自剑桥的休。我学习了你的Python课程,目前正在学习Web课程。非常兴奋能在这里和你在一起。我学习它也是为了教孩子们,实际上我学是因为我儿子为了考试正在学Python,这样我就能帮助他。
  • 罗杰:嗨,我是罗杰。我完成了Python入门课程,刚刚开始学习Web课程,非常期待能顺利完成。
  • 达里乌斯:嗨,我是达里乌斯,来自波兰,现居英国。我刚刚开始我的Python学习之旅。我应该非常感谢你所做的一切,真的非常棒。
  • 麦迪:嗨,我是麦迪,住在伦敦北部。我学习了第一门Python课程,主要是为了能跟上我在学校学习Python的小儿子的进度。
  • 安德鲁:你好,我叫安德鲁。我是一名退休人员,学习Python课程是出于兴趣。我希望能把它与我在树莓派上的一些小项目结合起来。我认为Python课程是一门引人入胜的课程。
  • 马达姆:你好,我是马达姆,和家人一起来的。我刚开始学习Python。我原本是学生物学的。以后,我想教我的女儿编程。
  • 爱丽丝:嗨,我是爱丽丝,住在布里斯托尔。我几个月前学习了Python课程,我打算学习你的新课。课程很好,我试过一些别的,但不得不放弃那部分。我是一名……我希望能在工作中使用Python,我会跟着你学习顶点课程。
  • 大卫(另一位):我是大卫,来自利物浦。我有兴趣学习Python来提高我的编程技能。我本身是一名学习技术专家。
  • 辛西娅:嗨,我是辛西娅,正在学习你的Python课程,这门课非常优秀,我强烈推荐给任何想学习编程的人。我现在正处于职业间歇期,在家养育孩子。这门课程对保持思维活跃非常有帮助。
  • 奥拉夫:嗨,我叫奥拉夫,来自伦敦。我学习了互联网历史与技术课程,并期待学习Python课程。

总结

本节课中,我们一起在布莱切利园这个具有历史意义的地点,进行了一次特别的课程重聚。我们见到了来自不同背景、怀着不同目标学习课程的同学们,也从导师那里获得了鼓励。这体现了在线学习社区连接全球学习者的力量。希望这次“重聚”能激励你继续在编程与技术的道路上前进。我们网络上再见!

103:创建读取更新删除(CRUD)操作 🛠️

在本节课中,我们将整合之前所学的PHP、SQL、重定向等技术,构建一个经典的CRUD(创建、读取、更新、删除)应用程序。我们将重构代码,将其拆分为多个职责单一的文件,并实现完整的POST重定向和闪存消息机制。


概述 📋

到目前为止,我们已经学习了PHP、SQL、重定向等知识。现在,我们将把这些知识整合起来,构建一个标准的CRUD应用程序。这个应用程序将围绕一个数据库表,实现数据的创建、读取、更新和删除功能。我们将重构现有代码,将其拆分为多个文件,并确保使用正确的POST重定向和闪存消息模式。


应用程序架构 🏗️

上一节我们介绍了构建CRUD应用的目标,本节中我们来看看具体的文件结构。我们将创建4到5个文件,并将它们放在一个名为crud的文件夹中。

以下是主要文件及其功能:

  • index.php:主页面,显示所有记录的列表。
  • add.php:用于添加新记录的页面。
  • delete.php:用于删除记录的页面。
  • edit.php:用于编辑现有记录的页面。
  • pdo.php:用于建立数据库连接的公共文件。

index.php是应用程序的起点。它将显示数据列表,并为每条记录提供“编辑”和“删除”链接。这些链接是锚点标签(<a href="...">),而不是表单,点击后会导航到新的页面。


主列表页面 (index.php) 📄

现在,让我们深入看看index.php文件是如何工作的。它首先包含pdo.php来建立数据库连接,并启动会话。然后,它会检查并显示可能存在的闪存消息。

以下是处理闪存消息的核心代码:

if ( isset($_SESSION['error']) ) {
    echo '<p style="color:red">'.$_SESSION['error']."</p>\n";
    unset($_SESSION['error']);
}
if ( isset($_SESSION['success']) ) {
    echo '<p style="color:green">'.$_SESSION['success']."</p>\n";
    unset($_SESSION['success']);
}

这些消息并非首次访问页面时显示,而是在从add.phpdelete.phpedit.php重定向回来时,由这些页面设置的。

接着,视图部分会查询数据库并输出一个表格。在表格的“操作”列中,我们为每条记录创建编辑和删除链接,并通过URL参数(GET参数)传递该记录的主键(user_id)。

例如,生成的链接可能如下所示:

  • edit.php?user_id=1
  • delete.php?user_id=1

这样,目标页面就知道要操作哪条记录了。


添加记录功能 (add.php) ➕

了解了主页面后,我们来看看如何添加新记录。当点击“Add New”链接时,会进入add.php。这个文件遵循模型-视图-控制器(MVC)模式,控制器逻辑在上方,视图在下方。

视图部分是一个简单的表单,包含姓名、邮箱、密码字段以及“添加”和“取消”按钮。当用户提交表单时,数据通过POST方法发送到服务器端的控制器逻辑。

控制器逻辑执行以下步骤:

  1. 检查POST数据。
  2. 使用预处理语句执行SQL插入操作,以防止SQL注入攻击。
    $stmt = $pdo->prepare('INSERT INTO users (name, email, password) VALUES (:name, :email, :password)');
    $stmt->execute(array(
        ':name' => $_POST['name'],
        ':email' => $_POST['email'],
        ':password' => $_POST['password']));
    
  3. 在会话中设置成功消息:$_SESSION[‘success’] = ‘Record added’;
  4. 使用header(‘Location: index.php’)重定向回index.php

重要提示:在重定向之前输出任何内容(如调试语句)会导致错误,因为HTTP头部已经发送。调试信息应写入日志文件。

整个流程是:点击添加 -> 填写表单 -> POST提交 -> 插入数据库 -> 设置闪存消息并重定向 -> 主页面显示成功消息。


删除记录功能 (delete.php) 🗑️

接下来,我们看看删除功能是如何实现的。从主页面点击删除链接会进入delete.php?user_id=X,这是一个GET请求。

该页面的逻辑如下:

  1. 首次GET请求(确认页面)
    • 根据传入的user_id执行SELECT查询,验证ID是否有效并获取用户名。
    • 如果未找到记录(例如ID无效),则在会话中设置错误闪存消息并重定向回主页。
    • 如果找到记录,则进入视图部分,显示确认删除的表单,其中包含一个隐藏字段<input type=”hidden” name=”user_id” value=”…”>,用于再次传递用户ID。

  1. 提交表单后的POST请求(执行删除)
    • 检查POST数据,确认删除操作。
    • 使用预处理语句执行DELETE操作。
      $stmt = $pdo->prepare('DELETE FROM users WHERE user_id = :uid');
      $stmt->execute(array(':uid' => $_POST['user_id']));
      
    • 在会话中设置成功消息并重定向回index.php

这种“先GET显示确认,再POST执行操作”的模式,是防止意外删除的良好实践。


编辑记录功能 (edit.php) ✏️

最后,我们来实现之前缺失的编辑功能。edit.php的工作流程与添加和删除有相似之处,也有其特点。

当点击主页的编辑链接时,进入edit.php?user_id=X(GET请求)。页面逻辑首先根据user_id查询数据库,获取该记录的旧数据。如果ID无效,则设置错误并重定向。

获取到旧数据后,进入视图部分。视图是一个表单,其字段(姓名、邮箱、密码)的value属性中预先填充了旧数据。关键点:这些从数据库取出的数据在输出到HTML前,必须使用htmlentities()函数进行转义,以防止跨站脚本(XSS)攻击。同时,表单中包含一个隐藏字段来保存user_id

当用户修改数据并提交表单时,发生POST请求。控制器逻辑执行UPDATE语句:

$stmt = $pdo->prepare('UPDATE users SET name = :name, email = :email, password = :password WHERE user_id = :uid');
$stmt->execute(array(
    ':name' => $_POST['name'],
    ':email' => $_POST['email'],
    ':password' => $_POST['password'],
    ':uid' => $_POST['user_id']));

更新成功后,设置成功闪存消息并重定向回主页。

安全提醒:无论是INSERT、UPDATE还是DELETE,只要SQL语句涉及用户输入,就必须使用预处理语句(prepare / execute)来防止SQL注入攻击。


数据安全与转义策略 🔒

在CRUD操作中,正确处理用户数据至关重要,这里总结一下核心策略:

  • SQL注入防护:永远使用预处理语句(Prepared Statements)来构建SQL查询。不要直接拼接用户输入到SQL字符串中。
    • 正确做法$stmt = $pdo->prepare(‘SQL语句 :placeholder’); $stmt->execute(array(‘:placeholder’ => $user_input));
  • XSS跨站脚本防护:当将数据从数据库输出到HTML页面时,必须使用htmlentities()函数进行转义。
    • 策略:数据存入数据库时保持原始状态(不转义)。数据从数据库取出输出到HTML时进行转义(htmlentities)。这样避免双重转义,也确保了安全。

总结 🎯

本节课中,我们一起构建了一个完整的单表CRUD应用程序。我们学习了如何将应用拆分为index.php(读取)、add.php(创建)、edit.php(更新)和delete.php(删除)等多个文件,并通过URL参数在页面间传递信息。

我们深入实践了POST重定向GET(PRG)模式,利用会话闪存消息在重定向后向用户传递操作反馈。更重要的是,我们贯穿始终地应用了关键安全实践:使用预处理语句防御SQL注入,以及在输出时使用htmlentities防御XSS攻击。

理解这个基础的CRUD模式至关重要,它是构建更复杂Web应用程序的基石。后续学习AJAX、jQuery或其他技术时,你会发现它们常常是围绕这个核心模式进行的扩展和优化。

104:PHP中的CRUD代码详解 🧑‍💻

在本节课中,我们将详细解析一个PHP的CRUD(增删改查)应用程序。这个应用将数据库操作功能拆分到独立的文件中,并遵循了良好的代码组织实践。我们将从概述开始,逐步分析每个核心文件(index.php, add.php, delete.php, edit.php)的功能和代码逻辑。

概述

我们分析的CRUD应用程序实现了对用户数据的基本操作:创建(Create)、读取(Read)、更新(Update)和删除(Delete)。与之前将多个功能混合在单个文件(如user3.php)中的做法不同,本应用采用了更清晰的结构,将每个核心操作分离到独立的文件中。这遵循了MVC(模型-视图-控制器)模式的思路,使代码更易于维护和理解。

应用程序的运行依赖于一个名为misc的数据库。你需要下载代码并在本地配置好数据库连接才能运行它。数据库的SQL设置说明可以在项目文件夹中找到。

主页面:列表与导航 (index.php)

上一节我们介绍了应用的整体结构,本节中我们来看看应用的主入口文件index.php。这个文件主要负责显示用户列表并提供导航链接。

index.php的模型部分非常简单,它只包含数据库连接和会话启动代码。其核心是视图部分,即生成用户列表的HTML表格。

以下是生成表格和操作链接的关键代码逻辑:

// 从数据库获取所有用户数据
$stmt = $pdo->query('SELECT name, email, password, user_id FROM users');
echo '<table border="1">'."\n";
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo "<tr><td>";
    echo htmlentities($row['name']);
    echo "</td><td>";
    echo htmlentities($row['email']);
    echo "</td><td>";
    echo htmlentities($row['password']);
    echo "</td><td>";
    // 编辑链接:通过GET参数传递用户ID
    echo('<a href="edit.php?user_id='.$row['user_id'].'">Edit</a> / ');
    // 删除链接
    echo('<a href="delete.php?user_id='.$row['user_id'].'">Delete</a>');
    echo("</td></tr>\n");
}

该文件还实现了“闪现消息”模式,用于在页面重定向后显示一次性的成功或错误提示。其原理是利用$_SESSION超全局变量临时存储消息,在页面显示后立即清除。

创建用户功能 (add.php)

在了解了如何展示数据后,本节我们来看看如何创建新数据。add.php文件负责处理新用户的添加。它包含一个表单用于输入用户信息,并包含数据验证逻辑。

该文件的结构遵循典型的“控制器”模式:首先处理POST请求(模型/控制器逻辑),然后渲染HTML表单(视图逻辑)。

以下是处理表单提交和数据验证的核心代码:

// 检查是否通过POST请求提交了必要的数据
if (isset($_POST['name']) && isset($_POST['email']) && isset($_POST['password'])) {
    // 数据验证1: 检查姓名和密码是否为空
    if (strlen($_POST['name']) < 1 || strlen($_POST['password']) < 1) {
        $_SESSION['error'] = 'Missing data';
        header("Location: add.php");
        return;
    }
    // 数据验证2: 检查邮箱格式是否包含@符号
    if (strpos($_POST['email'], '@') === false) {
        $_SESSION['error'] = 'Bad data';
        header("Location: add.php");
        return;
    }
    // 数据验证通过,执行插入操作
    $sql = "INSERT INTO users (name, email, password) VALUES (:name, :email, :password)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(
        ':name' => $_POST['name'],
        ':email' => $_POST['email'],
        ':password' => $_POST['password']));
    // 设置成功消息并重定向回主页
    $_SESSION['success'] = 'Record Added';
    header('Location: index.php');
    return;
}

这种模式被称为“保护式编程”或“提前返回”,即在验证失败时立即终止脚本执行并重定向,避免执行后续可能出错的代码。

删除用户功能 (delete.php)

现在我们已经学会了如何添加数据,接下来看看如何安全地删除数据。delete.php文件处理用户的删除操作。为了安全,它包含一个确认页面,并且严格验证传入的用户ID。

删除流程分为两步:

  1. GET请求:显示一个确认页面,询问用户是否确定要删除。
  2. POST请求:实际执行删除数据库记录的操作。

以下是该文件的关键步骤:

首先,验证通过链接传递过来的用户ID是否存在且有效。

// 第一步:验证GET参数中的user_id
if (!isset($_GET['user_id'])) {
    $_SESSION['error'] = 'Missing user_id';
    header('Location: index.php');
    return;
}
// 第二步:检查该ID在数据库中是否存在对应的用户
$stmt = $pdo->prepare("SELECT name FROM users WHERE user_id = :xyz");
$stmt->execute(array(":xyz" => $_GET['user_id']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
    $_SESSION['error'] = 'Bad value for user_id';
    header('Location: index.php');
    return;
}

如果验证通过,则显示确认页面。注意,这里使用htmlentities()函数对用户名进行转义,以防止跨站脚本(XSS)攻击。

// 显示确认删除的视图部分
$n = htmlentities($row['name']);
echo "<p>Confirm: Deleting ".$n."</p>\n";
echo('<form method="post"><input type="hidden" ');
echo('name="user_id" value="'.$_GET['user_id'].'">'."\n");
echo('<input type="submit" value="Delete" name="delete">');
echo('<a href="index.php">Cancel</a>');
echo("\n</form>\n");

最后,处理用户确认删除的POST请求。

// 第三步:处理确认删除的POST请求
if (isset($_POST['delete']) && isset($_POST['user_id'])) {
    $sql = "DELETE FROM users WHERE user_id = :zip";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(':zip' => $_POST['user_id']));
    $_SESSION['success'] = 'Record deleted';
    header('Location: index.php');
    return;
}

更新用户功能 (edit.php)

最后,我们来学习如何更新现有数据。edit.php文件结合了“读取”和“更新”的功能。它首先根据user_id获取并显示用户的当前信息,然后允许用户修改并提交更新。

该文件同样遵循“保护式编程”原则,并使用了预处理语句来防止SQL注入攻击。

文件首先验证并获取要编辑的用户信息:

// 获取要编辑的用户原始数据
$stmt = $pdo->prepare("SELECT * FROM users WHERE user_id = :xyz");
$stmt->execute(array(":xyz" => $_GET['user_id']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row === false) {
    $_SESSION['error'] = 'Bad value for user_id';
    header('Location: index.php');
    return;
}
// 将数据存入变量,并用htmlentities转义以供安全输出
$n = htmlentities($row['name']);
$e = htmlentities($row['email']);
$p = htmlentities($row['password']);
$user_id = $row['user_id'];

然后,在表单中预填充这些值:

<!-- 编辑表单视图部分 -->
<form method="post">
<p>Name: <input type="text" name="name" value="<?= $n ?>"></p>
<p>Email: <input type="text" name="email" value="<?= $e ?>"></p>
<p>Password: <input type="text" name="password" value="<?= $p ?>"></p>
<input type="hidden" name="user_id" value="<?= $user_id ?>">
<p><input type="submit" value="Update"/>
<a href="index.php">Cancel</a></p>
</form>

最后,处理表单提交,执行更新操作:

// 处理更新数据的POST请求
if (isset($_POST['name']) && isset($_POST['email']) && isset($_POST['password']) && isset($_POST['user_id'])) {
    // ... (数据验证逻辑与add.php类似) ...
    // 执行更新
    $sql = "UPDATE users SET name = :name, email = :email, password = :password WHERE user_id = :user_id";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(
        ':name' => $_POST['name'],
        ':email' => $_POST['email'],
        ':password' => $_POST['password'],
        ':user_id' => $_POST['user_id']));
    $_SESSION['success'] = 'Record updated';
    header('Location: index.php');
    return;
}

总结

本节课中我们一起学习了如何构建一个结构清晰的PHP CRUD应用程序。我们分析了四个核心文件:

  • index.php:负责读取和列表显示数据,并提供操作导航。
  • add.php:负责创建新数据,包含完整的数据验证和插入逻辑。
  • delete.php:负责删除数据,采用两步确认流程以保证操作安全。
  • edit.php:负责更新数据,结合了读取原始数据和写入更新数据的功能。

整个应用的核心实践包括:

  1. 代码分离:将不同功能拆分到独立文件,提高可维护性。
  2. POST-Redirect-GET (PRG) 模式:防止表单重复提交,并提供清晰的用户反馈。
  3. 闪现消息:利用会话在重定向间传递一次性状态信息。
  4. 数据验证与保护式编程:在执行任何操作前严格检查输入数据的有效性和完整性。
  5. 安全防护:使用预处理语句(prepare / execute)防止SQL注入,使用htmlentities()防止XSS攻击。

这个CRUD应用虽然功能基础,但它展示了构建安全、健壮Web应用程序的最佳实践和代码组织模式,为开发更复杂的应用打下了坚实的基础。

105:佐治亚州亚特兰大

在本节课中,我们将回顾一次在佐治亚州亚特兰大举行的课程附加办公时间。通过学生们的自我介绍,我们可以了解不同背景的学习者如何参与到这门课程中,并感受学习社群的氛围。

大家好,我是查克。我们现在位于亚特兰大北部的巴克海特区。我们刚刚结束了一次很棒的办公时间。按照传统,我想向大家介绍一些与你一同上课的同学们。

以下是参与本次办公时间的学生们的自我介绍。

  • 摩根:我来自亚特兰大。我和我爸爸一起上了这门课,因为他邀请我一起学,而且课程本身也很有趣。
  • 杰克:我和我女儿一起上了这门课。我们度过了愉快的时光,你的教学非常棒,谢谢。
  • 汤姆:我来自亚特兰大。我认为Python会很有趣。在经历了漫长的计算机职业生涯后,我觉得是时候学点汇编语言之外的东西了。不过说真的,用汇编语言你也能完成所有需要做的事。
  • 乔·康特拉雷斯:我是一名来自佐治亚理工学院的电气工程师。我想探索计算机科学领域,这门课太棒了。
  • 大卫:我住在亚特兰大。我上了Python课程,这是我的第一门Coursera课程。课程很愉快,教学很好。
  • 露西:我来自巴西。我上了Python课程,我认为这将是一个很好的开始。
  • 索亚:我是佐治亚州立大学的认知科学研究生。我上了查克博士的课,课程非常棒,我现在想学习更多。
  • 亚历克斯:我来自亚特兰大和贝鲁特。是索亚带我加入的。我也对认知科学感兴趣。说实话,我还没有开始上课,我的编程经历也比较坎坷,但现在我更有理由深入学习了。
  • 埃德:我刚上完互联网历史课程,我很喜欢它,所以我期待下次开课的Python课程。
  • 特雷克:我来自亚特兰大。我刚完成我的Python课程,并获得了证书。我想感谢查克博士。我是一名IT专业人士。
  • 莫琳:我已经上完了互联网历史课程,我非常期待Python编程课程。
  • :我来自亚特兰大,是一名业务分析师。我还没有上过课,但我丈夫每次在电视上播放课程时,我都会看很多遍。我计划报名参加下一期的课程,非常感谢你们所有人的努力。

以上就是来自亚特兰大的又一次成功的办公时间。我想下周我们会在香槟市见面,那我们香槟市见。

本节课中,我们一起回顾了亚特兰大办公时间的场景,聆听了来自不同职业和背景的学生分享他们学习Python和计算机相关课程的动机与体验。这展示了终身学习和社群交流在技术学习中的价值。

106:Chuck博士扮演新闻主播 📺

在本节课中,我们将跟随Chuck博士,以新闻播报的形式了解几则来自CNN的趣味新闻。我们将学习如何将英文新闻内容准确、流畅地翻译并整理成中文教程,同时练习技术文档的写作规范。


新闻速递:CNN世界总部报道

我是查尔斯·塞弗伦斯,在亚特兰大的CNN世界总部为您带来新闻更新。

上一节我们介绍了本节课程的形式,本节中我们来看看具体的新闻内容。

旅行新闻:美国的“世外桃源”

在旅行新闻方面,美国本土就有许多可以远离文明喧嚣的地方。

当美国人思考世界上最荒凉的地点时,他们通常会联想到南极科考站、西藏山峰或澳大利亚中部高原。事实上,如果你身处美国,无需远渡重洋就能找到与世隔绝之地,甚至不必离开美国大陆。

以下是美国境内一些典型的偏远地区类型:

  • 与世隔绝的沼泽
  • 被遗忘的冻土带
  • 空旷的峡谷
  • 遥远的山脉
  • 荒芜的沙漠

尽管经历了大量开发,美国仍然拥有许多未经开发的偏远内陆地区。其评判标准简单而模糊:最大程度地远离他人及其存在的任何迹象。这些迹象包括但不限于:

  • 道路
  • 指示牌
  • 烟囱
  • 政府监控
  • 电子舞曲
  • Snapchat应用
  • 麦片坚果零食

娱乐新闻:电视剧取景小镇待售

在娱乐新闻方面,佐治亚州一座因《行尸走肉》而出名的小镇衰败的市中心,现在正待价而沽。

格兰特维尔市中心的九栋建筑正在eBay上出售,卖家称这是一笔划算的交易。该镇前市长吉姆·塞尔斯大约四年前在经济衰退期间通过止赎交易购入了这些建筑。塞尔斯和另一位投资者现在将它们挂牌出售,起拍价为68万美元。

这座小镇是热门AMC电视剧《行尸走肉》第三季中一集的取景地,剧中主角瑞克·格莱姆斯返回了他废弃的家乡寻找武器。一个Facebook页面提供该镇拍摄地点的徒步游览,但很快这不再是格兰特维尔唯一的出名之处。前市长塞尔斯表示,未来几个月内计划有四部电影将在格兰特维尔拍摄。

游戏新闻:虚拟现实的未来

在游戏新闻方面,近年来游戏行业见证了数轮热门趋势的兴衰,例如社交网络游戏、3D游戏以及通过身体移动控制游戏角色的体感系统。现在,请准备好迎接下一个趋势:虚拟现实。

这项技术能将头戴设备使用者沉浸于感觉极其逼真的三维虚拟世界中。据两家领军公司称,它即将面向全球消费者推出。但两家公司代表在上周的E3游戏展上对CNN表示,虚拟现实(VR)成败的关键驱动力并非他们的设备,而是为其开发的游戏。

Oculus和索尼都表示已接近发布消费者版本,但均未提供具体时间表。然而,他们一致认为,没有优质内容,头戴设备毫无用处。如果无法用它玩引人入胜的游戏,人们就不会想购买VR头显。并且,这些游戏需要在头显上市时准备就绪。

国际新闻:洪都拉斯丛林发现失落古城

最后,在国际新闻方面,在洪都拉斯丛林中寻找失落城市的考古学家们发现了一处城市遗迹,他们认为这属于一个已消失的古老文明。

一位作家和摄影师跟随一队科学家前往洪都拉斯的莫斯基托地区,追踪传说中的“白城”或“猴神之城”。此次探险是在一项名为激光雷达的空中光探测扫描技术发现雨林下方疑似人造结构后启动的。

与邻近的玛雅文明相比,这个消失的文化几乎未被研究过,至今仍不为人知。探险队发现了土方工程,包括一座土质金字塔,以及一批石雕。洪都拉斯人类学与历史研究所估计它们的历史可追溯至公元1000年至1400年。团队未挖掘这些发现物,并对遗址的确切位置保密,以防止盗掘。洪都拉斯政府承诺保护该区域,但缺乏资金。


课程总结

本节课中,我们一起学习了如何将一段新闻播报内容转化为结构清晰的中文教程。我们实践了删除语气词、使用Markdown格式、保留原意、添加过渡语句等文档编写技巧,并了解了旅行、娱乐、游戏和国际领域的四则趣味新闻。

以上就是我们目前的报道。如需随时查看新闻,请登录我们的网站CNN.com。这里是亚特兰大CNN中心,我是查尔斯·塞弗伦斯。

107:趣味毕业典礼 🎓

在本节课中,我们将一同回顾并整理密歇根大学《面向所有人的Web应用程序》课程结语部分的演讲内容。这段演讲由查克博士和他的同事库尔特共同完成,旨在祝贺完成课程的学员,并分享关于学习与人生的深刻见解。

概述

查克博士欢迎大家参加他的慕课毕业典礼。他感谢学员们坚持学习并获得证书。按照毕业典礼的传统,他邀请了毕业演讲嘉宾——来自印第安纳大学的好友兼同事库尔特。在颁发证书之前,库尔特将进行简短的演讲。

演讲核心:四个P词与四个C词

库尔特表示,他的演讲将围绕“PC”展开。这里的“PC”并非指个人电脑,而是指“可能的课程”,即人生中可能选择的路径。他希望通过四个以P开头的词和四个以C开头的词,来探讨大家未来可能的道路。

上一节我们介绍了演讲的背景,本节中我们来看看库尔特提出的核心概念框架。

四个P词

以下是关于学习动力的四个关键P词:

  1. 热情:这门慕课关乎你的热情与兴趣。你进入了一个可能熟悉或略有挑战的新领域,正如肯·罗宾逊爵士所说,我们都在寻找自己的“天赋”。
  2. 目标:仅有热情不足以成功。你需要一个目标,一个前进的方向。这门课程本身就是一个目标。
  3. 毅力:完成一门慕课的唯一途径是坚持或毅力。你需要推动自己,在课程中 persevere(坚持不懈)。毅力是当今心理学中的首要词汇。
  4. 趣味:既然参加了这门课,你需要在学习过程中保持一点趣味性。查克博士在他的慕课中就加入了趣味元素。

四个C词

以下是关于学习过程的四个关键C词:

  1. 选择:你选择参与并完成这门课程,这是你自主的决定。
  2. 承诺:你不仅做出了选择,更承诺要完成它。当你做出承诺时,你的同伴们都在关注、交流并与你分享。
  3. 连接:一门慕课或任何在线课程都关乎连接与关系。希望你不只是完成了课程,还建立了新的桥梁、关系和可能的人生路径。
  4. 完成:你完成了一门课程,可能会获得证书。这是一个C词。你完成了,然后继续人生旅程。

总结与祝贺

库尔特总结道,如果你能在进入其他课程时牢记这八个原则,或许会发现未来的成功。他祝贺大家不仅注册了课程,更完成了许多甚至全部模块,从而获得了证书,乃至作为开放学习者的“人生证书”。

查克博士感谢库尔特专程前来,并宣布现在是颁发证书的时刻。他幽默地提到,大学毕业时拿到的通常是卷起来的空白纸。现在,学员可以走到电脑前,他将“颁发”证书。他祝贺大家,并希望这仅仅是许多精彩课程的开始。他再次感谢大家的时间、毅力和承诺,并期待未来在网络上继续教学。

本节课中我们一起学习了毕业演讲的核心内容,即推动学习与成长的四个P词(热情、目标、毅力、趣味)和四个C词(选择、承诺、连接、完成)。这不仅是课程的结束,更是持续学习与探索新可能的开始。

108:欢迎学习本课程 🎉

在本节课中,我们将要学习JavaScript、JQuery和JSON。这是我们教授的第四门课程。在上一门课程的结尾,你构建了一个CRUD应用程序,它成功地将PHP与数据库和用户界面连接起来。我们还介绍了模型-视图-控制器的概念,其中模型是数据库,视图是用户看到的内容,控制器则是负责在前后端之间传递数据、重定向用户等逻辑的“魔法”部分。

课程概述

上一节我们介绍了MVC架构。本节中,我们来看看本课程的核心目标:探索现代Web应用如何将MVC架构的各个部分分布在浏览器、服务器和数据库之间。

到目前为止,你所做的一切都是在PHP中渲染HTML。这是传统的方式。在本课程中,我们将探索如何将一部分HTML的渲染工作从PHP转移到浏览器中。这意味着我们将学习JavaScript。

为何学习JavaScript?

构建HTML标记并发送到浏览器是传统的方式。你应该掌握这种方法。但是,要构建酷炫、交互性强、动态的应用,例如无需整页刷新就能弹出新消息的小型聊天应用或通知,就需要交互性。这需要JavaScript,也需要JQuery。

例如,如果你构建一个聊天应用,聊天窗口可以在后台运行,获取新消息并直接显示在窗口中。这种无需完整请求-响应周期、只需重绘网页部分内容而非整个页面的技术,正是我们要学习的内容。这也是现代Web应用有趣的一面。

核心技术栈

因此,我们需要学习:

  • JavaScript:浏览器端的编程语言。
  • JQuery:一个JavaScript库,它极大地简化了与文档对象模型的交互以及前后端的数据通信。
  • JSON:一种数据格式,我们可以让JavaScript从数据库读取数据,然后在浏览器内部进行格式化。

我们将利用这些技术完成各种酷炫的功能,从而实现更丰富的用户交互体验。

学习挑战与价值

这门课程可能比我们之前完成的课程更具挑战性,主要是因为你必须更清楚自己在做什么。课程节奏较快,作业量也稍大,需要你投入更多时间。

你将构建规模稍大的应用程序。但正是在这个阶段,你将迈入一个新的境界。掌握本课程的内容后,你将能够胜任现代Web开发工作。因此,多投入一些时间是值得的。

如果你已经完成了前三门课程并坚持到这里,我为你感到骄傲,也很高兴你依然保持兴趣。学完这门课程后,你将掌握海量的知识。

总结

本节课中,我们一起学习了本课程的目标:探索如何利用JavaScript、JQuery和JSON,将部分渲染逻辑从服务器移至浏览器,以构建更动态、交互性更强的现代Web应用程序。我们了解了学习这些技术的必要性,并认识到本课程在成为一名合格Web开发者道路上的重要性。准备好迎接挑战,开始这段学习之旅吧!😊

109:JavaScript概述 🚀

在本节课中,我们将要学习JavaScript编程语言的基础知识,了解它的历史、特点以及如何在浏览器环境中运行。

概述

现在,我们开始学习JavaScript编程语言。许多人可能会说,你应该学习的第一个编程语言,甚至可能是唯一应该学习的语言,就是JavaScript。我完全不同意这个观点。JavaScript是一门出色的语言,它在浏览器中运行,并且随着Node.js等技术的发展,它越来越多地在服务器端运行。它是一门强大的语言,但并非一门容易学习的语言。你需要真正理解它的工作原理。JavaScript是一门设计精美的语言,我会尝试向你展示它的一些非常棒的特性,我认为只有在你学习了其他几门语言(如Python、PHP、SQL)之后,才能真正欣赏这些特性。希望这是你实际学习的第四或第五门编程语言。

我们正在进入一个全新的领域。之前我们学习了SQL(与数据库交互的语言)和PHP(我们选择的服务器端代码语言)。我们讨论了请求-响应周期、HTML和CSS,以及如何在浏览器中格式化内容并使其美观。我一直在提文档对象模型(DOM),但现在我们将要编写在浏览器中运行的代码。这是一个显著的区别。如果你在使用像Node.js这样的技术,那意味着你在运行基于服务器的JavaScript,但目前我们将讨论JavaScript作为一种基于浏览器的工具。

JavaScript是为浏览器发明的,在其最初的15年里,它主要在浏览器中运行。它从未打算只在浏览器中运行,但在很长一段时间里,它确实是一种仅限于浏览器的编程语言,现在它在服务器端的使用正在逐渐增加。HTML、CSS和文档对象模型的概念,从JavaScript诞生之初就与它密不可分。JavaScript的核心就是关于文档对象模型,以及如何操作文档对象模型,在不依赖请求-响应周期的情况下增加交互性。到目前为止,我们添加的所有交互性都是通过显示一个全新的HTML页面来实现的。有时我们让它看起来页面没有改变,因为所有东西都对齐了,只有一些小东西变了,但实际上这是一个完整的请求-响应周期。现在,在JavaScript中,我们将学习如何操作这个文档对象模型,并动态地改变我们在浏览器中看到的内容。

首先,我们将学习JavaScript作为一种编程语言,然后学习如何在浏览器中使用它。JavaScript的历史与PHP或Python非常不同。我喜欢分享这些编程语言的历史,让你了解它们的起源,因为我认为这有助于你理解它们的本质。

JavaScript的历史与特点 📜

JavaScript现在大约有20年的历史了,它由Netscape公司在1995年推出,开发者是Brendan Eich。如果你看过我关于Brendan Eich的视频,你会发现它与关于PHP创建者Rasmus Lerdorf的视频非常不同。PHP被设计成一个实用且不断发展的工具包,而Rasmus没有接受过构建语言的正式教育。但Brendan Eich拥有物理学博士学位之类的,他是一位数学天才,也是一位语言天才,并且一直是编程语言的研究者。

JavaScript最初被设想为一个“玩具”语言,这也是它名字的由来。当时有另一门叫Java的语言,它比JavaScript难得多。JavaScript本应是Java的简易版本。但在很多方面,Brendan是一位如此杰出的计算机科学家,他在JavaScript中巧妙地加入了许多精彩的设计。当我们谈到面向对象的JavaScript时,你会发现它与我们讨论过的所有其他面向对象概念非常不同,因为它内部有一个不同的基础概念,称为“一等函数”。因此,与一些其他妥协且非常实用的语言相比,JavaScript是一门理论上非常优美的语言。

有一个名为ECMA的标准化组织负责标准化JavaScript,所以你有时会看到它被称为ECMAScript。如果你看到ECMAScript这个名字,不用担心,它们都是一样的。

在浏览器中运行JavaScript 🌐

JavaScript是一种在浏览器中运行的编程语言。与我们已经讨论过的PHP进行对比:在PHP中,我们用<?php ... ?>包裹代码,这些代码在服务器上运行并产生输出,然后发送到浏览器。但在JavaScript中,情况不同。

在JavaScript中,你的HTML标签和JavaScript代码都是来自服务器的实际文本。然后,当浏览器解析HTML时,它会读取并运行这些JavaScript代码。在<script></script>标签之间,我们运行的是JavaScript语言。这不仅仅是关于输出。下面这个例子恰好是在输出内容。

有一个叫做document的东西。这是在浏览器JavaScript中为我们预设的一个变量,它是一个允许我们接触文档对象模型的对象。我一直说DOM,DOM和document是同一个东西。所以这段代码的意思是:向DOM写入一个段落。

<p>Hello from HTML</p>
<script>
    document.write("<p>Hello from JavaScript</p>");
</script>
<p>Back to HTML</p>

这段代码将文本“Hello from JavaScript”放入DOM中。所以,第一个段落来自HTML,“Hello world”来自JavaScript,然后第二个段落又来自HTML。这不像PHP,PHP的输出会自动放入文档。在JavaScript中,你必须明确指定你想要做什么,因为事实证明,在JavaScript中,我们更常见的是操作现有的东西,而不是用JavaScript编写整个文档对象模型。但有时我们也会这样做。当我们开始学习在浏览器中进行渲染时,我们会看到来自JavaScript的文档对象模型发生巨大变化。

还有一个<noscript>标签。你可能有一个应用程序会说:你知道吗,如果没有JavaScript,我希望它能以不同的方式工作。如今,我们不太担心这个。你可能只会说<noscript>,我的应用程序将无法工作,因为现在一切都严重依赖JavaScript。

基础调试:alertconsole.log 🐛

在任何编程语言中,你要做的第一件事就是弄清楚如何打印“Hello world”。我们这样做是为了监控我们的代码,看看发生了什么。

JavaScript中有一个名为alert的函数,你向它传递一个字符串作为参数,它会暂停执行,直到你按下“确定”。在这段代码中,不同的浏览器渲染方式可能不同,但在这段代码中,浏览器实际上正在解析文档对象模型。有时你可能会看到这个段落显示出来,然后它运行这段代码并停止。关键是,当它停止时,它实际上还没有写入下一行。所以下一行不在这里。它必须暂停JavaScript,直到你按下“确定”。你会注意到你的浏览器会一直旋转、旋转、旋转,有时在某些浏览器中,你甚至无法切换标签页或做其他任何事情,因为这个alert不仅停止了JavaScript,还停止了整个浏览器在任何事情上的进展。

作为一种调试机制,它确实是一个强大的工具。如果你曾经看过我在完全迷失和困惑时编码,比如“我的代码到底有没有在运行?”,我会放入alert语句,因为它们就像停止一切。你可以四处看看,弄清楚发生了什么。当你有10个alert时,它们会变得很烦人。这就是我们接下来要讨论console.log的原因。但alert是进行基本调试的好方法。

引入JavaScript的三种方式 🔧

有三种基本方式可以引入JavaScript。

  1. 内联在文档中:就像我刚才展示的那样,你可以把它作为HTML标签的一部分,一个事件如onclickonchange

    <a href="#" onclick="alert('Hi'); return false;">Click me</a>
    

    这是一个onclick的例子。我们之前用location.href做过这个,我用来放一个按钮,然后改变浏览器跳转到另一个地方。但基本上,这是一个锚标签“Click me”。它表示每当这个锚标签被点击时(意味着如果你点击那里),就运行这段JavaScript代码。这段JavaScript代码是一个alert(‘hi’)return false

    看,alert,它只是运行它。这是两行JavaScript代码。JavaScript不关心行尾或空格之类的东西。所以你只需用分号将它们连接起来,表示每个语句的结束。return false就像一个函数调用。如果你返回false,它的作用是抑制默认行为。默认行为是你点击这里,它会跟随这个锚标签的链接。但如果这个onclick运行了,它优先执行,它执行alert,然后说:“哦,实际上不要跟随那个链接。”你可以返回true,它就会跟随链接。你可以运行一些JavaScript,然后如果你的JavaScript运行后返回true,它就会跟随链接。但当你点击这个时,它实际上并不跟随链接,这取决于这个返回值是false还是true

    所以有onchangeonclick,这些是事件。这就像在JavaScript的最早版本中,有一个事件模型被放入各种标签中,或者你可以运行JavaScript,因为它与文档对象模型紧密相连。

  2. 作为HTML标签属性中的事件处理程序:如上例所示。

  1. 从外部文件引入:就像你把JavaScript放在中间一样,只是你有一个<script>和一个</script>,然后你说src=,然后那个文件里有一些代码。这个文件中的代码是JavaScript,开头没有<?php,它只是JavaScript。
    <script src="myscript.js"></script>
    

总结

本节课中,我们一起学习了JavaScript的概述。我们了解了JavaScript作为一门强大但并非入门简单的编程语言的历史和特点,它最初为浏览器设计,但现在也广泛应用于服务器端。我们探讨了JavaScript与文档对象模型的紧密关系,以及它如何实现不刷新页面的动态交互。我们还学习了在HTML中引入JavaScript的三种主要方式:内联脚本、事件属性引入和外部文件引入。最后,我们介绍了alert函数作为基础的调试工具。在下一节中,我们将讨论如何在编写JavaScript代码时检测错误。

110:基础JavaScript

概述

在本节课中,我们将要学习JavaScript编程中的两个核心实践:如何发现和处理语法错误,以及如何使用浏览器开发者工具进行调试。理解这些概念对于编写健壮的JavaScript代码至关重要。

语法错误与脚本执行

与任何编程语言一样,JavaScript中也可能出现语法错误。但JavaScript的运行环境——浏览器——有其特殊性。

在PHP等服务器端语言中,开发者可以通过配置(如在php.ini文件中设置error_reporting,或在PDO中设置错误模式)来确保看到错误信息。然而,JavaScript在浏览器中运行,通常是网页加载的“副作用”。浏览网页的用户通常不是代码的开发者,因此浏览器的默认行为是隐藏所有错误。有时浏览器会在右下角显示一个红色标记或问号,但用户通常不会注意到。

当浏览器检测到JavaScript错误时,它会停止执行当前脚本块,但不会通知用户。没有像PHP那样的错误日志。因此,了解如何在开发过程中捕获和发现错误非常重要,因为在开发JavaScript时难免会犯错。

错误的影响范围

以下是一个包含错误的JavaScript代码示例:

<script>
console.log('I am broken);
</script>
<script>
console.log('I am good');
</script>

第一个<script>标签内有一个语法错误:字符串缺少闭合的单引号。在JavaScript中,单引号和双引号是等效的,但字符串必须正确闭合。

当浏览器遇到这个语法错误时,它会停止执行当前脚本块中错误之后的所有代码。因此,第一个脚本块中的console.log不会执行。但重要的是,它不会停止所有JavaScript的处理。浏览器会继续解析HTML,当遇到第二个<script>标签时,它会尝试执行其中的代码。由于第二个脚本块语法正确,因此‘I am good’会被成功打印。

这意味着,一个脚本块中的错误只会影响该块本身。然而,如果错误发生在定义函数的库代码中,那么这个错误之后的所有函数都将无法被定义。开发者必须意识到,在单个JavaScript文件或<script>标签内,一个错误会导致该错误之后的所有代码被“丢弃”。

使用浏览器开发者工具

既然错误默认对用户不可见,开发者就需要一种方法来查看它们。幸运的是,现代浏览器都内置了开发者工具(调试模式)。

首先,你需要知道如何在你的浏览器(如Chrome或Firefox)中启用开发者模式。启用后,你可以得到一个分屏界面,甚至可以将其分离到另一个窗口。

在开发者工具中,“控制台”(Console)标签页会显示所有的错误信息。一些开发者习惯始终让控制台保持打开状态。你甚至可以通过JavaScript代码主动向控制台发送日志消息。

控制台非常有效。通常,当错误发生时,你可以直接点击控制台中的错误信息链接,浏览器会直接跳转到源代码的对应行。即使代码被“压缩”(Minified)成难以阅读的单行格式,浏览器也通常提供“代码美化”(Pretty Print)功能,使其恢复可读格式。例如,它可能会提示“Uncaught SyntaxError”,并定位到第3行,明确指出问题所在。

随着你编写越来越多的JavaScript,你很可能会一直开着这个分屏界面。你会经常进行“清除日志” -> “刷新页面” -> “查看新错误”这样的循环,以此调试你的代码。浏览器内置调试器是许多人认为JavaScript适合作为第一门编程语言的原因之一,尽管整套工具对绝对初学者来说可能信息量过大。

调试输出:alertconsole.log

之前我们提到了alert()函数,但它在实际调试中并不实用,因为它会弹出一个阻塞一切的对话框。

alert('这是一个提示'); // 这会暂停所有脚本执行

alert通常只在一切都不对劲、需要立即暂停程序以查看执行顺序时使用。

更实用的调试方法是使用console.log()

console.log('这是一个日志消息');

console.log()可以将字符串输出到控制台。你也可以用它来打印变量的内容,这非常有用。与alert不同,console.log不会阻塞脚本执行,你可以在控制台中看到按顺序输出的消息。在实际的生产系统调试中,开发者经常查看控制台日志来定位问题。

需要注意的是,运行在浏览器中的JavaScript没有真正的安全性可言。像Cookie一样,代码对用户是可见的。聪明的用户可以查看、甚至修改正在运行的JavaScript。因此,永远不要完全信任客户端JavaScript代码。

兼容性与高级调试

在更旧的浏览器中,console对象可能只在开发者工具打开时才存在。直接调用console.log可能会导致错误。一种兼容性的写法是:

if (window.console) {
    console.log('调试信息');
}

这段代码会检查console对象是否存在,如果存在则执行日志输出,从而避免在旧环境中产生错误。不过,在现代浏览器(如Chrome)中,你可以直接使用console.log

除了日志,浏览器调试器还支持设置断点。你可以在“源代码”(Sources)标签页中找到你的JavaScript文件,点击行号来设置断点(会出现一个蓝色标记)。有时,你需要在设置断点后刷新页面,才能在下一次请求-响应周期中在断点处暂停。

当代码在断点处暂停时,你可以检查当前作用域内的变量值,然后逐步执行代码。这是一个非常强大的功能,虽然对初学者来说可能需要时间熟悉,但它是诊断复杂问题的利器。

总结

本节课中我们一起学习了JavaScript错误处理与调试的基础知识。我们了解到JavaScript错误默认在浏览器中被隐藏,但可以通过开发者工具的控制台来查看。语法错误会终止其所在脚本块的执行,但不会影响其他独立的脚本块。我们比较了alert和更实用的console.log调试方法,并介绍了浏览器调试器的基本用法,如查看错误、输出日志和设置断点。掌握这些工具和方法是有效进行JavaScript开发的关键。

111:JavaScript核心语言特性 🧠

在本节课中,我们将要学习JavaScript语言的一些核心特性。我们将从基础语法开始,包括注释、语句、变量命名和字符串常量,并了解它们与PHP等其他语言的异同。这些知识是编写任何JavaScript程序的基础。

注释与语句

上一节我们介绍了课程概述,本节中我们来看看JavaScript的基本语法规则。

JavaScript的注释方式与许多C风格语言类似。单行注释使用双斜杠 //,多行注释则使用 /* ... */。多行注释常用于函数文档说明。与某些语言(如Python)不同,JavaScript拥有真正的多行注释语法。

在语句方面,JavaScript与C语言类似。空白字符(空格、换行、缩进)不影响代码执行,它们仅用于提高代码可读性。语句通常以分号 ; 结束。虽然在某些情况下可以省略分号,但为了代码清晰和避免潜在错误,建议始终使用分号。

以下是一个展示空白字符无关紧要的极端例子:

console
    .
log
(
'Hello'
)
;

这段代码虽然格式混乱,但依然能正确执行,打印出“Hello”。然而,为了他人(如你的同事或助教)阅读方便,编写整洁、格式良好的代码是必要的。

变量命名规则

了解了基本语法后,我们来看看如何为变量命名。

JavaScript的变量命名规则相对灵活。变量名可以包含字母、数字、下划线 _ 和美元符号 $。以下是具体规则:

  • 变量名可以以字母、下划线 _ 或美元符号 $ 开头。
  • 变量名不能以数字开头。
  • JavaScript是大小写敏感的语言,因此 myVarmyvar 是两个不同的变量。

关于美元符号 $ 的使用:它被允许是为了让JavaScript看起来更像Perl、Bash等脚本语言,使其更易于上手。但在实际开发中,通常建议避免使用美元符号作为变量名开头,以保持代码风格的一致性。

字符串常量

最后,我们来探讨JavaScript中的字符串常量。

JavaScript中的字符串既可以用单引号 ' 包裹,也可以用双引号 " 包裹,两者功能完全相同。字符串中的转义字符(如换行符 \n)遵循C语言的惯例。

在实际开发中,尤其是在Web开发中,有一个常见的约定:在JavaScript代码中尽量使用单引号 ' 来定义字符串。这是因为HTML属性通常使用双引号 " 来包裹值。当我们混合编写HTML和JavaScript时(例如在 document.write() 中或内联事件处理器里),这种约定可以清晰地区分代码层次,避免引号嵌套混乱。

例如,在编写包含HTML的JavaScript字符串时:

let htmlSnippet = '<p class="highlight">Hello World</p>';

这里,外层使用单引号定义JavaScript字符串,内层的HTML属性值使用双引号,使得代码结构一目了然。


本节课中我们一起学习了JavaScript的核心语言特性。我们了解了其C语言风格的注释(///* */)和以分号结尾的语句规则,知道了空白字符仅用于排版。我们还学习了变量命名的规则,包括允许使用的字符和大小写敏感性,并了解了避免使用 $ 符号的惯例。最后,我们探讨了字符串常量的单双引号用法,以及为何在JavaScript中优先使用单引号以便与HTML的双引号区分开来。掌握这些基础是进一步学习JavaScript编程的关键。

112:JavaScript变量与表达式

概述

在本节课中,我们将要学习JavaScript中的变量与表达式。我们将探讨JavaScript的运算符、数据类型、变量作用域以及函数的基本概念。这些是理解JavaScript编程的基础。

运算符与表达式

任何编程语言的重要组成部分都包括运算符和表达式。JavaScript的运算符与大多数C语言风格的语言非常相似。

以下是JavaScript中的基本算术运算符:

  • +:加法
  • -:减法
  • *:乘法
  • /:除法(与Python 2不同,它总是产生浮点数结果,例如 9 / 2 得到 4.5
  • %:取模(返回整数除法的余数)

此外,还有一些具有副作用的运算符:

  • k++++k:等同于 k = k + 1
  • k----k:等同于 k = k - 1
  • j += 5:等同于 j = j + 5

这些简写形式通常用于使代码更紧凑,但除非有特殊理由,我们倾向于避免过度使用它们。

比较运算符

比较运算符与PHP和C语言非常相似。需要记住的重要一点是:单个等号 = 是赋值语句,而双等号 == 是用于比较的“问号”。

以下是主要的比较运算符:

  • ==:等于(数值相等或在类型转换后相等)
  • ===:严格等于(不进行类型转换,比较值和类型)
  • !=:不等于
  • !==:严格不等于
  • <:小于
  • >:大于
  • <=:小于或等于
  • >=:大于或等于

JavaScript有两种相等性运算符。双等号 == 是数值等价或在类型转换后的等价。三等号 === 是不进行类型转换的比较。

例如:

  • false == 0 的结果是 true,因为经过类型转换后它们被视为相等。
  • false === 0 的结果是 false,因为它们的类型不同。
  • false === false 的结果是 true

在像PHP和JavaScript这样会进行自动类型转换的语言中,必须有一种能阻止自动类型转换的相等性测试。而在像Java这样的严格类型语言中,你无法比较两种不同类型的值,因为那会导致语法错误。

逻辑运算符

逻辑运算符同样直接源自C语言。

  • &&:逻辑与(AND)。两边都必须为真,结果才为真。例如:true && true 结果为 true
  • ||:逻辑或(OR)。只要有一边为真,结果就为真。例如:true || false 结果为 true
  • !:逻辑非(NOT)。例如:!true 结果为 false

这些运算符在C语言家族中都是通用的。

字符串连接与松散类型

在PHP中,我们使用点号 . 进行字符串连接。在JavaScript中,我们使用加号 + 进行连接,并且它会自动进行类型转换。

例如:

let x = 12;
let result = "hello " + x + " people"; // 结果为 "hello 12 people"

JavaScript是一种松散类型的语言。加号 + 既可以是数字加法运算符,也可以是字符串连接运算符,这取决于操作数的类型。

让我们看一些例子:

  • "123" + 10 的结果是字符串 "12310",因为加号被解释为连接。
  • "123" * 1 的结果是数字 123,因为乘号 * 是严格的数字运算符。然后 123 + 10 得到 133
  • "Fred" * 1 的结果是 NaN(Not a Number)。

特殊值:NaN 与 Infinity

当JavaScript尝试进行无效的数字运算时,会产生 NaN(非数字)。NaN 具有“粘性”,任何涉及 NaN 的运算结果通常也是 NaN

例如:

  • NaN + 1 的结果仍然是 NaN
  • 你可以使用 isNaN() 函数来检查一个值是否为 NaN

除以零在JavaScript中不会报错,而是得到 Infinity(无穷大)。从数学角度看,这比直接抛出错误更合理。JavaScript的数字处理实现遵循一定的数值计算标准。

类型检查

typeof 运算符可以告诉你一个值的类型。它返回一个字符串,例如 "string""number""boolean" 等。

你可以在 if 语句中使用它:

if (typeof variable === "string") {
    // 执行某些操作
}

函数与变量作用域

函数是任何编程语言的重要组成部分。JavaScript函数的基本语法与C语言风格类似:

function functionName(arg1, arg2) {
    // 函数体
    return value;
}

参数默认按值传递,函数可以返回一个值。这部分与其他编程语言类似。

然而,JavaScript函数作用域的行为有一个重要的不同点。默认情况下,在函数内部声明的变量(如果没有使用 varletconst 关键字)可能会成为全局变量,或者引用外部的全局变量。

请看以下代码:

let gl = 123;

function check() {
    gl = 456; // 这里修改了全局变量 gl
}

check();
console.log(gl); // 输出 456

为了避免这种意外的副作用,必须在函数内部使用 varletconst 来声明局部变量。

let gl = 123;

function check() {
    let gl = 456; // 这里声明了一个新的局部变量 gl
}

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/766b8216287fa00ba65b491195ac2d72_5.png)

check();
console.log(gl); // 输出 123,全局变量未被修改

在函数内部显式声明局部变量是一种良好的编程实践,可以避免变量意外污染全局作用域。这是JavaScript与其他许多编程语言不同的地方。

总结

本节课我们一起学习了JavaScript变量与表达式的核心概念。我们探讨了算术、比较和逻辑运算符,了解了JavaScript松散类型的特性以及字符串连接的方式。我们还认识了 NaNInfinity 这两个特殊值,并学习了如何使用 typeof 进行类型检查。最后,我们重点讨论了函数中变量作用域的重要性,强调了使用 varletconst 声明局部变量的必要性,以避免意外的全局变量修改。理解这些基础是编写可靠JavaScript代码的关键。

113:JavaScript数组与控制结构 🧩

在本节课中,我们将学习JavaScript中的两种核心数据结构:数组与控制结构。我们将了解如何创建和使用数组,并回顾与PHP语法相似的控制流程语句。

数组:线性数组与关联结构

与我们已经熟悉的PHP和Python等语言类似,JavaScript也包含线性数组和关联结构。

但JavaScript的关联数组实际上是对象。我们将在下一节关于面向对象编程的主要课程中更详细地讨论它们。有趣的是,在JavaScript中,你不得不比其他语言更早地学习面向对象,这也是我不喜欢将JavaScript作为第一门语言的原因之一。

然而,事实证明JavaScript对象非常强大、灵活且优雅,我们之后会讲到这一点。

线性数组相当直接。你可以使用方括号 [] 和逗号来创建。

代码示例:

let a = [1, 2, 3, 4, 5];

这就是一个线性数组,索引为0,1,2,3,4。

你也可以有键值对,但这些是对象,而不是数组,我们稍后会讨论。你可以使用 a[0] 来获取数组 a 的第一个元素,或者使用 b[‘name’] 来获取对象 b 中名为 ‘name’ 的属性。

这部分内容有时会让人困惑,因为它基本上是说“进入对象b并查找属性‘name’”。但这里的键是带引号的字符串,这看起来有点奇怪。

不过,你可以暂且将对象视为关联数组。你甚至可以创建一个不包含方法、只包含一组键值对数据的对象。因此,来自像PHP这样期望关联数组具有键值对功能语言的人,通常会直接创建对象而不必过于担心,我们稍后会更详细地讨论对象。

构建数组的方法

有多种方式可以构造数组。你可以创建一个全新的空数组,然后使用 push 方法将元素添加到末尾。

代码示例:

let arr = [];
arr.push(‘first’);
arr.push(‘second’);
// 结果: [‘first’, ‘second’]

或者,你也可以通过直接为索引赋值来构建数组。

代码示例:

let arr = [];
arr[0] = ‘first’;
arr[1] = ‘second’;
// 结果: [‘first’, ‘second’]

这是另一种从零碎部分构建数组的方法。

你还可以使用构造函数风格,直接列出你想放入数组的元素列表。方括号语法实际上是这种构造函数风格创建数组的一种简写形式。

代码示例:

let arr = new Array(‘first’, ‘second’);
// 等价于 let arr = [‘first’, ‘second’];

控制结构

既然你来自PHP背景,控制结构部分我们不会逐一详述,因为其运算符与PHP相同。

if 语句类似PHP,while 循环类似PHP,for 循环类似PHP的计数循环,breakcontinue 的工作方式也类似PHP。这就是我喜欢先教PHP的原因之一,因为它们都是类C语言,这样我就可以说:“还记得我们在PHP里做过的所有事情吗?”

在确定循环方面有一个区别:在PHP中你使用 foreach,但在JavaScript中我们使用 for…in 循环。

代码示例:

let balls = {‘red’: 1, ‘blue’: 2, ‘green’: 3};
for (let color in balls) {
    console.log(color + ‘: ‘ + balls[color]);
}

在这个例子中,color 是迭代变量,balls 是集合(这里是一个对象,但暂时可以把它当作键值数组)。这基本上是说,迭代变量 color 将遍历对象中连续的键。然后我们可以使用 balls[color] 来查找对应的值。

它没有像Python那样的双迭代变量特性。这个循环会遍历所有键值对。

总结

本节课中,我们一起学习了JavaScript的数组与控制结构。我们了解了线性数组的创建与访问,探讨了对象作为关联结构的本质,并学习了使用 push 方法和索引赋值来构建数组。在控制结构方面,我们回顾了与PHP语法相似的 ifwhilefor 循环以及 breakcontinue 语句,并特别介绍了用于遍历对象属性的 for…in 循环。掌握这些基础是理解后续更复杂JavaScript概念的关键。

114:JavaScript代码详解

在本节课中,我们将要学习JavaScript的基础知识,包括如何编写简单的代码、调试程序以及理解JavaScript在网页中的不同嵌入方式。我们将从最基础的调试语句开始,逐步深入到更复杂的调试工具和技巧。

概述:JavaScript入门与调试

JavaScript是一种在浏览器中运行的脚本语言,用于为网页添加交互功能。学习JavaScript的第一步通常是了解如何输出信息来调试代码。本节将介绍几种输出信息的方法,并解释JavaScript代码在HTML文档中的不同存在形式。

使用alert()函数进行调试

上一节我们介绍了JavaScript的基本概念,本节中我们来看看如何开始编写和调试第一段代码。在编程时,我首先喜欢做的事情之一就是如何输出调试信息。

alert()函数是实现这一目的的一个简单方法。它接受一个字符串作为参数,并将其内容弹窗显示给用户。

alert("这是一条调试信息");

当浏览器执行到这段代码时,它会弹出一个对话框,显示指定的字符串。这个过程会暂停JavaScript的执行,直到用户点击“确定”按钮后,代码才会继续运行。因此,alert()不仅用于输出信息,也是一种强力的调试手段,因为它能完全中断程序的流程。

在HTML中嵌入JavaScript的三种方式

了解了基本的输出方法后,我们来看看如何将JavaScript代码放入HTML文档中。JavaScript可以通过三种主要方式嵌入到HTML中。

以下是这三种方式的详细介绍:

  1. 内联脚本:使用<script>标签直接在HTML文档中编写JavaScript代码。

    <script>
        alert("页面加载时弹出的消息");
    </script>
    
  2. 事件处理器:在HTML标签的事件属性(如onclick)中直接编写JavaScript代码。

    <a href="#" onclick="alert('你点击了链接!'); return false;">点击我</a>
    

    在这个例子中,onclick属性内的代码会在用户点击链接时执行。return false;语句用于阻止浏览器执行链接的默认行为(即跳转到href指定的地址)。

  3. 外部文件引入:通过<script>标签的src属性引入外部的.js文件。

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

    文件myscript.js中只包含纯粹的JavaScript代码,无需再包裹<script>标签。这种方式有利于代码的复用和管理。

处理JavaScript语法错误

在编写JavaScript时,难免会出现语法错误。然而,浏览器通常不会将这些错误信息直接显示给最终用户,因为用户无法修复它们。浏览器会选择静默地停止执行出错的脚本块,然后继续渲染页面的其余部分。

例如,下面这段代码有一个字符串引号不匹配的错误:

alert(‘这是一个错误示例); // 单引号未闭合
alert(“这行代码可能不会执行”);

执行时,第一个alert会因为语法错误而失败,第二个alert也不会执行,但页面其他部分可能正常加载。

作为开发者,我们需要主动去发现这些错误。

使用浏览器开发者工具

为了发现和调试错误,我们必须使用浏览器的开发者工具。所有现代浏览器(如Chrome、Firefox)都内置了强大的开发者工具。

通常,你可以通过右键点击网页并选择“检查”或按F12键来打开开发者工具。在“控制台”标签页中,你可以看到JavaScript运行时产生的所有错误和警告信息。这对于定位代码问题至关重要。

开发者工具还允许你查看网页的源代码、网络请求、以及当前的内存状态等。

更优雅的调试:console.log()

虽然alert()有用,但它会中断程序,不适合在循环或频繁触发的事件中使用。这时,console.log()方法是更好的选择。

console.log()接受一个或多个参数,并将它们输出到浏览器的控制台,而不会中断代码执行。

console.log("当前计数是:", count);

这对于跟踪变量值、程序执行流程非常有帮助。只有打开开发者工具的用户才能看到这些日志,普通用户则看不到。

需要注意的是,在某些环境下(或当开发者工具关闭时),console对象可能不存在。为了避免因此产生错误,一个常见的做法是先检查console是否存在:

if (window.console) {
    console.log("安全地输出日志");
}

使用调试器设置断点

最强大的调试功能是使用JavaScript调试器设置断点。断点可以让代码在指定位置暂停执行,以便你检查当时的变量状态、调用栈等信息。

在开发者工具的“源代码”标签页中,找到你的JavaScript文件,点击行号旁边的区域即可设置一个断点。刷新页面后,代码执行到该行时会自动暂停。

当代码暂停时,你可以:

  • 查看和修改当前作用域内的变量值。
  • 逐行执行代码。
  • 查看函数调用栈。
    在调试器面板中,你可以使用继续执行、步入、步出等按钮来控制代码的执行流程。掌握断点调试是解决复杂逻辑问题的关键技能。

总结

本节课中我们一起学习了JavaScript编程的起点——调试。我们从最基础的alert()函数开始,了解了它如何输出信息并暂停代码执行。接着,我们探讨了将JavaScript代码嵌入HTML的三种方式:内联脚本、事件处理器和外部文件引入。

然后,我们认识到浏览器会静默处理语法错误,因此必须借助开发者工具的“控制台”来发现它们。为了进行更灵活、不中断流程的调试,我们引入了console.log()方法。最后,我们学习了如何使用调试器设置断点,这是深入分析代码执行过程、定位疑难问题的终极工具。

通过掌握这些基础的编写和调试技巧,你已经为学习更复杂的JavaScript概念打下了坚实的基础。

115:JavaScript文档对象模型 🧩

在本节课中,我们将学习如何使用JavaScript来操作文档对象模型(DOM)。DOM是网页在浏览器中的内部表示,通过JavaScript操作DOM,我们可以在不重新加载页面的情况下,动态地改变网页的内容和结构,从而实现丰富的交互体验。

从JavaScript操作DOM

上一节我们介绍了JavaScript的基础语法,本节中我们来看看如何用它来操作DOM。

在请求-响应周期中,DOM是我们最终在浏览器中看到的网页内容。现在,我们将看到JavaScript如何查看、提取DOM中的元素,以及如何将新内容插入到DOM中。我们已经见过如何使用document.write来追加内容,而本节将深入探讨如何交互式地操作DOM,这是实现无需请求-响应周期的交互功能的核心。

DOM的历史与挑战 😡

DOM的概念由来已久。当JavaScript在1995年诞生时,DOM就已经存在。但当时的问题是,不同的浏览器(如IE、Firefox)各自独立发展,它们解析HTML后形成的内部数据结构(即DOM)并不相同。

由于这种不一致性,早期规范并未强制规定DOM的具体形状或结构(例如,某个元素的子元素必须是特定的顺序)。这意味着,开发者无法编写完全通用的代码来直接遍历所有浏览器的DOM。为了解决这个问题,浏览器引入了一种独立于DOM结构的方法来查找元素。

通过ID查找元素

由于不同浏览器的DOM“形状”不同,早期开发者无法编写像document.X.Y.Z.sub4这样依赖固定结构的代码。为了解决这个问题,他们引入了一个在所有浏览器中都通用的函数:getElementById

这个函数的思想是:我们不需要知道一个元素在DOM树中的具体位置,只要给它标记一个唯一的ID,就可以通过这个ID直接找到它。

以下是其工作原理:

  • 在HTML中,我们使用id属性为元素提供一个唯一的标识符。例如:<span id="person">Chuck</span>
  • 在JavaScript中,我们使用document.getElementById('person')来获取这个<span>标签对应的DOM对象。
  • 获取到这个对象后,我们可以通过其innerHTML属性来读取或修改标签内的内容。

代码示例:查找并修改元素

// 查找ID为'person'的元素
let element = document.getElementById('person');
// 读取元素内部的HTML内容
console.log(element.innerHTML); // 输出: Chuck
// 修改元素内部的HTML内容
element.innerHTML = 'Joseph';

执行上述代码后,网页上原本显示“Chuck”的地方会立刻变为“Joseph”,而无需刷新页面。浏览器控制台的console.dir()函数可以帮助我们查看DOM对象的详细属性,是调试代码的有用工具。

简单的DOM交互示例

理解了如何查找和修改元素后,我们可以创建一些简单的交互。以下是一个通过点击按钮来改变文本的例子:

HTML部分:

<span id="stuff">Initial Text</span>
<button onclick="document.getElementById('stuff').innerHTML = 'Back'">Back</button>
<button onclick="document.getElementById('stuff').innerHTML = 'Forth'">Forth</button>

交互逻辑:

  • 页面初始显示“Initial Text”。
  • 点击“Back”按钮,id="stuff"<span>内容会变为“Back”。
  • 点击“Forth”按钮,其内容会变为“Forth”。
  • 这个过程完全在浏览器中完成,没有与服务器进行任何通信。

动态添加DOM元素

我们可以进行更复杂的操作,比如动态地向页面中添加新元素。以下示例展示了一个点击“more”链接,不断向列表中添加新项目的功能。

HTML与JavaScript代码:

<ul id="thelist">
  <li>First Item</li>
</ul>
<a href="#" onclick="add(); return false;">more</a>

<script>
let counter = 1; // 全局计数器
function add() {
  // 1. 创建一个新的<li>元素
  let newItem = document.createElement('li');
  // 2. 为这个新元素设置一个类名和内容
  newItem.className = 'x';
  newItem.innerHTML = 'Item ' + counter;
  // 3. 找到<ul>元素,并将新的<li>添加为其子元素
  let theList = document.getElementById('thelist');
  theList.appendChild(newItem);
  // 4. 计数器加1
  counter++;
}
</script>

执行流程如下:

  1. 每次点击“more”链接,都会调用add()函数。
  2. add()函数会创建一个新的<li>元素节点。
  3. 设置这个新节点的内容和属性。
  4. 通过getElementById找到目标<ul>列表。
  5. 使用appendChild方法将新创建的<li>节点插入到<ul>列表的末尾。
  6. 页面会立即更新,显示新增的项目。

从原生操作到jQuery 😊

上述方法代表了1995年至2005年左右操作DOM的方式。虽然功能强大,但存在两个主要问题:

  1. 代码冗长繁琐:即使是简单的操作,也需要多行代码。
  2. 浏览器兼容性:不同浏览器之间存在差异,开发者需要编写大量额外代码来处理这些差异。

为了解决这些问题,在2000年代中期出现了一系列JavaScript库,其中最为流行和持久的就是jQuery。jQuery主要解决了两个痛点:

  • 简化语法:它提供了一套更简洁、更易读的API来操作DOM。
  • 处理兼容性:它内部封装了不同浏览器之间的差异,开发者只需调用jQuery的方法,它就会自动处理兼容性问题。

因此,在现代Web开发中,对于复杂的DOM操作,使用jQuery或类似的现代框架(如React, Vue)已成为标准做法。当然,对于非常简单的任务,直接使用getElementById等原生方法仍然是可行且轻量的选择。

本节课中我们一起学习了JavaScript操作文档对象模型(DOM)的基础知识。我们从DOM的历史挑战讲起,学习了如何使用getElementByIdinnerHTML来查找和修改页面元素,并实现了动态添加内容的交互功能。最后,我们了解了为什么jQuery等库会被广泛采用,以简化开发并解决浏览器兼容性问题。掌握这些核心概念,是构建动态、交互式网页应用的重要一步。

116:文档对象模型(DOM)代码详解

在本节课中,我们将学习如何使用JavaScript与网页的文档对象模型(DOM)进行交互。我们将通过具体的代码示例,了解如何查找、读取和修改网页上的元素。

概述

文档对象模型(DOM)是HTML文档的结构化表示。JavaScript可以通过DOM来访问和操作网页的内容、结构和样式。本节我们将通过两个示例,学习使用document.getElementById方法查找元素,并使用innerHTML属性来读取和修改元素的内容。

查找与读取DOM元素

上一节我们介绍了DOM的基本概念,本节中我们来看看如何具体地查找并获取一个DOM元素。

假设我们有一个包含以下HTML代码的网页:

<span id="person">Chuck</span>

这个字符串“Chuck”被包裹在一个<span>标签内,并且该<span>标签的ID是“person”。

在JavaScript中,我们使用document对象开始一切操作。以下是查找并操作该元素的代码:

let st = document.getElementById('person');

这段代码的作用是:document.getElementById方法会遍历整个页面,找到ID为“person”的那个元素。

这个表达式的结果(存储在变量st中)就是指向那个<span>标签的一个“句柄”或引用。变量st现在是一个指向页面中那一小部分DOM的对象。

我们可以对这个元素进行各种操作。如果我们只想获取这个标签内部的HTML文本内容,我们需要使用.innerHTML属性。

console.log(st.innerHTML); // 输出: Chuck

st.innerHTML会“深入”到标签内部,提取出实际的HTML文本内容,也就是该标签的子内容。我们可以打印它,也可以改变它。

修改DOM元素内容

我们不仅可以读取DOM元素的内容,还可以动态地修改它。

使用相同的元素句柄,我们可以通过赋值来改变其内容:

st.innerHTML = 'Joseph';

这是一个赋值语句,它将覆盖文档对象模型中这一部分的内容并改变它。

为了清晰地观察整个过程,我们可以在代码中添加一些console.log语句和alert弹窗来放慢执行速度。以下是完整的示例代码:

let st = document.getElementById('person');
console.log(st.innerHTML); // 在控制台记录原始内容
alert(st.innerHTML); // 用弹窗显示原始内容
console.log(st); // 在控制台记录st对象本身
st.innerHTML = 'Joseph'; // 将内容修改为‘Joseph’

当代码执行时,它会先查找ID为“person”的<span>,提取其innerHTML,并在控制台和弹窗中显示“Chuck”。浏览器此时会因弹窗而暂停。点击弹窗的“OK”后,JavaScript继续执行,将内容改为“Joseph”,浏览器会立即重新渲染DOM以显示新内容。如果没有弹窗,这个变化会发生得非常快,肉眼难以察觉。

响应点击事件修改DOM

上一个例子是顺序执行的直线代码。现在,我们来看看如何响应用户的点击操作来动态修改DOM。

以下示例结合了onclick事件处理程序。HTML结构如下:

<span id="stuff">stuff</span>
<br>
<a href="#" onclick="document.getElementById('stuff').innerHTML='back'; return false;">back</a>
<br>
<a href="#" onclick="document.getElementById('stuff').innerHTML='forth'; return false;">forth</a>

页面上有一个ID为“stuff”的<span>,其初始内容是“stuff”。下面有两个链接(<a>标签),分别带有onclick事件。

以下是每个链接被点击时发生的事:

  • 当点击第一个链接(文字为“back”)时,会执行document.getElementById(‘stuff’).innerHTML=‘back’;,将<span>的内容改为“back”。
  • 当点击第二个链接(文字为“forth”)时,会执行document.getElementById(‘stuff’).innerHTML=‘forth’;,将<span>的内容改为“forth”。
  • return false;是为了阻止链接的默认跳转行为。

因此,用户可以通过点击“back”和“forth”链接,让“stuff”这个词在“back”和“forth”之间来回切换。除非刷新页面从服务器重新加载原始HTML,否则内容不会自动变回初始的“stuff”。

总结

本节课中我们一起学习了JavaScript操作DOM的核心方法。我们掌握了如何使用document.getElementById()根据ID查找元素,以及如何使用.innerHTML属性来读取和修改元素内的HTML内容。我们还通过实例看到了如何将这种操作与onclick事件结合,实现与用户交互的动态网页效果。这些是构建交互式Web应用的基础技能。

117:JavaScript配置文件详解 🧑‍💻

在本节课中,我们将详细讲解与基础JavaScript课程相关的作业。这个作业的核心是构建一个个人资料数据库应用。我们将从设置数据库开始,逐步实现用户登录、数据验证、以及利用外键关联用户与个人资料记录等功能。

上一节我们介绍了课程的整体背景,本节中我们来看看具体的作业要求和实现步骤。

概述与作业要求

本次作业要求你构建一个个人资料数据库应用。其设计理念是假设你刚刚完成了包含autos表和users表的“汽车”作业。现在,你需要基于此构建第二个CRUD应用。你可以参考之前的作业获取灵感,但这个应用将是后续几节课作业的基础,因此必须正确完成。如果你在上一个作业中只是勉强让程序运行起来,那么这次应该抓住机会,真正理解代码的运行原理。

这个应用包含一些浏览器端的JavaScript数据验证功能。

数据库表结构设置

在运行代码之前,我们需要先设置数据库表。以下是创建所需表的步骤。

以下是创建users表的SQL语句:

CREATE TABLE users (
    user_id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(128),
    email VARCHAR(128),
    password VARCHAR(128),
    PRIMARY KEY(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

以下是创建profile表的SQL语句。请注意,必须先创建users表,因为profile表包含指向users表的外键约束。

CREATE TABLE profile (
    profile_id INTEGER NOT NULL AUTO_INCREMENT,
    user_id INTEGER,
    first_name TEXT,
    last_name TEXT,
    email TEXT,
    headline TEXT,
    summary TEXT,
    PRIMARY KEY(profile_id),
    CONSTRAINT profile_ibfk_1
        FOREIGN KEY (user_id)
        REFERENCES users (user_id)
        ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建表后,需要向users表中插入初始用户数据,例如用户“umsi”及其哈希密码。这为后续的登录功能提供了基础。

登录与JavaScript验证

应用的第一部分是登录页面,其中包含了浏览器端的JavaScript验证。

当点击登录按钮时,会触发doValidate函数。该函数会验证表单数据,如果有效则返回true并提交表单,如果无效则返回false并阻止提交,同时弹出提示信息。

JavaScript代码运行在用户的浏览器中,属于请求-响应周期中的客户端环节。这意味着任何人都可以通过查看网页源代码看到这些验证逻辑。因此,在课程材料中,这些代码是公开提供的。

例如,如果电子邮件地址格式不正确(缺少“@”符号),点击登录会触发警告:“无效的电子邮件地址”。如果所有字段都为空,则会提示:“所有字段都必须填写”。

如果客户端验证通过,表单数据将被提交到服务器端(login.php)进行进一步检查。服务器端验证包括检查用户名和密码是否与数据库记录匹配。如果服务器端验证失败,会通过重定向和“闪现消息”(flash message)机制向用户反馈错误。

login.php文件的结构与你之前编写的登录脚本类似,包含了重定向逻辑、验证逻辑以及嵌入的JavaScript代码。

CRUD操作与数据展示

成功登录后,用户将进入主页面,可以对个人资料记录进行增删改查(CRUD)操作。

“添加新条目”和“编辑”页面在当前作业中暂时没有加入JavaScript验证(这将是下一个作业的重点)。目前,你需要确保这些页面能够正常工作,例如正确处理表单提交、将数据插入或更新到profile表中。

需要处理的数据字段包括:first_namelast_nameemailheadlinesummary。这与之前的作业略有不同,但实现难度不大。

一个关键点是必须防范安全漏洞。你需要确保:

  1. 使用预处理语句(Prepared Statements)防止SQL注入攻击。
  2. 在将数据输出到HTML页面前,使用htmlentities()函数进行转义,防止HTML/脚本注入(XSS攻击)。

“删除”操作通常包含一个确认步骤,并且同样需要对输出的数据进行HTML实体转义。

会话管理与外键关联

登录过程中一个重要的步骤是会话管理。在login.php中,验证用户凭据后,我们会从数据库查询中获取当前用户的user_id(这是users表的主键),并将其存储在$_SESSION超全局变量中。

$_SESSION[‘user_id’] = $row[‘user_id’];

这个存储在会话中的user_id用于在后续页面(如“添加新条目”)中判断用户是否已登录,并决定是否允许其执行操作。

本作业的核心数据库概念是使用外键。在profile表中,user_id字段是一个外键,它指向users表中的user_id主键。

当用户添加一个新的个人资料记录时,你需要在INSERT语句中,将这个外键字段的值设置为当前登录用户的user_id(即从$_SESSION中获取的值)。

$stmt = $pdo->prepare(‘INSERT INTO profile (user_id, first_name, last_name, email, headline, summary) VALUES (:uid, :fn, :ln, :em, :he, :su)’);
$stmt->execute(array(
    ‘:uid’ => $_SESSION[‘user_id’],
    ‘:fn’ => $_POST[‘first_name’],
    // … 其他字段
));

这样,就在profile表和users表之间建立了一条关联记录。在某些数据库管理工具中,这个外键甚至会显示为超链接,点击后可以直接跳转到对应的用户记录。

理解并实现这种外键关联至关重要,因为接下来的作业将涉及更复杂的关系:下一节是“一对多”关系,最后一节将是“多对多”关系。

总结与建议

本节课中我们一起学习了如何构建一个包含JavaScript前端验证的个人资料管理应用。我们详细讲解了从数据库表结构设置、用户登录与会话管理,到实现CRUD操作并利用外键关联用户数据的过程。

请认真完成这个作业,花时间理解每一行代码。这是构建后续更复杂应用的唯一途径。祝你好运!

118:在Macintosh上安装MAMP 🍎

在本教程中,我们将学习如何在Macintosh电脑上安装和配置MAMP软件。MAMP是一个集成了Apache服务器、MySQL数据库和PHP的本地开发环境,是进行Web应用程序开发的理想工具。我们将完成从下载、安装到基本配置和创建第一个PHP页面的全过程。

下载与安装MAMP

首先,我们需要从官方网站下载MAMP的安装程序。

  1. 访问 mamp.info 网站。
  2. 下载适用于Macintosh的MAMP安装包。

下载完成后,进入“下载”文件夹,找到MAMP的安装文件。双击该文件以启动安装程序。

在安装过程中,请接受所有默认设置,并按照屏幕提示完成安装。

启动与查看MAMP

安装完成后,我们可以启动MAMP并查看其配置信息。

  1. 打开“访达”,进入“应用程序”文件夹。
  2. 找到并打开“MAMP”文件夹。
  3. 启动“MAMP”应用程序。

启动后,MAMP控制面板会显示系统配置信息,例如PHP版本和配置文件位置。这对于后续的配置调整非常重要。

配置PHP以显示错误信息

在开发过程中,我们希望PHP能显示所有错误信息,以便于调试。默认情况下,MAMP可能关闭了此功能。

上一节我们启动了MAMP,本节中我们来看看如何修改PHP配置以开启错误显示。

  1. 在MAMP控制面板中,找到PHP配置文件的路径。通常位于:/应用程序/MAMP/bin/php/php[版本号]/conf/php.ini
  2. 使用文本编辑器(如TextEdit)打开这个 php.ini 文件。
  3. 在文件中搜索 display_errorsdisplay_startup_errors 这两个配置项。
  4. 将它们的值从 Off 修改为 On

核心配置修改示例:

display_errors = On
display_startup_errors = On

注意: 此设置仅推荐用于本地开发环境。在生产环境中,应关闭错误显示以防止敏感信息泄露。

修改完成后,保存文件。为了使新配置生效,必须重启MAMP服务器。

  1. 在MAMP控制面板中,点击“停止服务器”。
  2. 等待服务器完全停止后,再点击“启动服务器”。

服务器重启后,可以通过访问MAMP的“WebStart页面”并查看“PHP信息”来验证配置是否生效。在PHP信息页面中搜索 display_errors,确认其状态已变为 On

创建并运行第一个PHP页面

现在,我们的开发环境已经配置完成,可以开始编写第一个PHP程序了。

上一节我们配置了PHP环境,本节我们将创建一个简单的PHP文件并通过本地服务器访问它。

首先,我们需要知道网站文件的存放目录。对于MAMP,默认的网站根目录是:

/应用程序/MAMP/htdocs/

以下是创建第一个PHP页面的步骤:

  1. 打开文本编辑器,创建一个新文件。
  2. 输入以下简单的PHP代码:
    <?php
    echo "Hello from my first web page!";
    ?>
    
  3. 将文件保存到 htdocs 目录下。例如,我们可以在 htdocs 中新建一个名为 first 的文件夹,然后将文件以 index.php 为名保存到该文件夹中。完整路径为:/应用程序/MAMP/htdocs/first/index.php

文件保存后,即可通过浏览器访问该页面。

  1. 确保MAMP服务器正在运行(控制面板指示灯为绿色)。
  2. 打开浏览器,访问地址:http://localhost:8888/first/

浏览器将自动寻找并打开 first 文件夹下的 index.php 文件,页面上会显示“Hello from my first web page!”这句话。

总结

在本节课中,我们一起学习了在Macintosh上搭建PHP本地开发环境的完整流程。

我们首先从官网下载并安装了MAMP软件。接着,我们启动了MAMP,并通过修改 php.ini 配置文件,开启了 display_errorsdisplay_startup_errors 选项,以确保在开发时能看到所有错误信息。最后,我们在MAMP的网站根目录 htdocs 下创建了第一个PHP文件,并通过本地服务器成功运行了它。

现在,你已经拥有了一个功能完备的本地PHP开发环境,可以开始进行Web应用程序的学习和开发了。

119:在Windows 10上安装MAMP 🖥️

在本节课中,我们将学习如何在Windows 10操作系统上安装MAMP集成开发环境,并配置一个基础的PHP开发环境,以便开始编写和运行Web应用程序。

概述

MAMP是一个集成了Apache服务器、MySQL数据库和PHP的软件包,它允许开发者在本地计算机上轻松搭建Web开发环境。本节教程将指导你完成MAMP的下载、安装、基本配置,并编写第一个PHP程序。


下载与安装MAMP

首先,我们需要从官方网站下载MAMP的Windows版本安装程序。

以下是下载和安装MAMP的步骤:

  1. 访问MAMP官方网站并下载适用于Windows的安装程序。
  2. 运行下载好的安装程序文件。
  3. 在安装向导中,选择“English”作为安装语言,然后点击“Next”。
  4. 在安装选项界面,取消勾选“安装MAMP PRO”的复选框,我们只需要安装免费的MAMP版本。
  5. 阅读并接受软件许可协议。
  6. 选择安装目录,建议使用默认路径 C:\MAMP
  7. 按照屏幕提示完成后续安装步骤,最后点击“Run”启动MAMP。

安装完成后,MAMP控制面板会自动启动。

启动服务器与验证安装

上一节我们完成了MAMP的安装,本节中我们来看看如何启动服务器并验证安装是否成功。

启动MAMP控制面板后,你需要启动Apache和MySQL服务器。

以下是启动和验证服务器的关键步骤:

  1. 在MAMP控制面板中,点击“Start Servers”按钮来启动Apache和MySQL服务。
  2. 当Windows防火墙弹出安全警报时,务必允许Apache HTTP Server和MySQL的通信请求。这是确保服务器能正常工作的关键步骤。
  3. 服务器启动后(状态指示灯变为绿色),点击“Open Start Page”按钮,这将在浏览器中打开MAMP的欢迎页面。
  4. 在欢迎页面,你可以点击链接查看“PHP Info”页面,它显示了当前PHP的详细配置信息。
  5. 更重要的是,点击“phpMyAdmin”链接。如果能成功打开phpMyAdmin的登录或管理界面,如下图所示,则证明你的MySQL数据库服务和PHP环境均已成功安装并运行。

至此,恭喜你已成功安装MAMP。

安装代码编辑器(Atom)

为了编写代码,我们需要一个文本编辑器。虽然任何文本编辑器都可以,但强烈建议使用具备语法高亮等功能的专用代码编辑器,而不是记事本或Microsoft Word。

以下是安装Atom编辑器的步骤:

  1. 访问Atom编辑器官网,下载Windows版本的安装程序。
  2. 运行安装程序并按照向导完成安装。

安装完成后,你就可以使用Atom来编写PHP、HTML等代码文件了。

编写第一个PHP应用程序

现在,我们已经准备好了服务器环境和代码编辑器,可以开始编写第一个PHP程序了。

首先,确保MAMP中的Apache和MySQL服务器已经启动。然后,通过MAMP控制面板打开“Start Page”,以便获取Web根目录等重要信息。

Web服务器的文档根目录通常是 C:\MAMP\htdocs\。所有需要通过浏览器访问的网页文件都应放在这个目录或其子目录下。

以下是创建并运行第一个PHP文件的步骤:

  1. 打开Atom编辑器,创建一个新文件。
  2. 输入以下基础HTML和PHP代码:
    <h1>Hello from a web page</h1>
    <?php
        echo "Hi there.\n";
    ?>
    <p>Some HTML paragraph.</p>
    
    代码说明:
    • <?php ... ?> 标签用于嵌入PHP代码。
    • echo 是一个PHP语句,用于输出文本。
    • \n 是换行符,在HTML中显示为空格,但在查看网页源代码时会体现。
  3. 将文件保存到Web根目录下的一个子文件夹中,例如 C:\MAMP\htdocs\first\
  4. 将文件命名为 index.phpindex.php是一个特殊名称,当浏览器访问一个目录时,服务器会默认寻找并打开它。
  5. 打开浏览器,访问地址:http://localhost/first/index.php
  6. 你将看到浏览器中显示了“Hello from a web page”的标题,以及由PHP代码输出的“Hi there.”文本。

这个例子演示了PHP的基本工作原理:服务器执行 <?php ... ?> 标签内的代码,并将执行结果(如echo输出的内容)嵌入到最终的HTML页面中,一起发送给浏览器。

配置PHP错误显示

在开发过程中,看到详细的错误信息对于调试代码至关重要。然而,出于安全考虑,MAMP默认关闭了在网页上直接显示错误的功能。

上一节我们编写了简单的PHP代码,本节中我们来看看如何开启错误显示功能,以便在代码出错时能快速定位问题。

如果你在代码中制造一个语法错误(例如删除语句末尾的分号),刷新页面可能只会看到一个不明确的“500内部服务器错误”,这对调试没有帮助。

以下是启用PHP错误显示的步骤:

  1. 通过MAMP的“Start Page”打开“PHP Info”页面。
  2. 在页面中搜索“Loaded Configuration File”这一行,找到PHP配置文件(php.ini)的路径。例如:C:\MAMP\conf\php7.1.5\php.ini(版本号可能不同)。
  3. 使用Atom或其他文本编辑器打开这个 php.ini 文件。
  4. 在文件中搜索 display_errors 设置项。
  5. 找到 display_errors = Off 这一行,将其修改为 display_errors = On
  6. 同时,可以将其附近的 display_startup_errors = Off 也修改为 On
  7. 保存对 php.ini 文件的修改。
  8. 重要:由于修改了服务器配置,需要重启MAMP的Apache服务器才能使更改生效。在MAMP控制面板中,先点击“Stop Servers”,然后再点击“Start Servers”。
  9. 服务器重启后,再次访问你的PHP页面。此时,如果代码中存在错误,页面上将会显示具体的错误类型、信息和出错行号,例如:Parse error: syntax error, unexpected ‘echo’ (T_ECHO) in C:\MAMP\htdocs\first\index.php on line 6

根据明确的错误提示,你就可以快速回到代码中修正错误(例如补上缺失的分号)。在开发初期就启用此功能,可以节省大量调试时间。

总结

本节课中我们一起学习了在Windows 10上搭建PHP开发环境的完整流程。我们首先下载并安装了MAMP集成环境,随后启动了Apache与MySQL服务器,并通过phpMyAdmin验证了安装成功。接着,我们安装了Atom代码编辑器,并在MAMP的Web根目录下创建了第一个PHP文件,理解了PHP代码如何与HTML结合并在服务器端执行。最后,我们完成了关键的一步:配置PHP的php.ini文件以开启错误显示功能,这能确保我们在开发过程中获得清晰的错误反馈,极大提高调试效率。现在,你的本地开发环境已经准备就绪,可以开始探索更复杂的Web应用程序开发了。

120:在Windows 10上安装XAMPP 🖥️

在本节课中,我们将学习如何在Windows 10操作系统上安装XAMPP。XAMPP是一个集成了Apache、MySQL、PHP和Perl的免费开源软件包,是本地Web开发环境的理想选择。我们将从下载安装包开始,完成安装配置,并运行一个简单的PHP程序来验证环境是否搭建成功。

下载XAMPP安装程序

首先,我们需要从Apache Friends官方网站下载适用于Windows的XAMPP安装程序。

以下是下载步骤:

  1. 访问Apache Friends网站。
  2. 找到适用于Windows的XAMPP版本。
  3. 点击下载链接,开始下载安装程序。

下载完成后,安装程序通常位于系统的“下载”文件夹中。

运行安装程序

上一节我们下载了安装程序,本节中我们来看看如何运行并安装它。

运行下载好的安装程序,按照向导提示进行安装。建议将XAMPP安装在默认路径(通常是 C:\xampp),以避免潜在的权限问题。在组件选择界面,可以根据需要取消勾选不需要的组件,例如Tomcat、Perl或Fake Sendmail,以简化安装。

安装过程可能需要一些时间,请耐心等待。

启动XAMPP控制面板

安装完成后,我们不会立即启动控制面板。首先,我们需要知道它的位置。通常,XAMPP被安装在 C:\xampp 目录下。在该目录中,可以找到 xampp-control.exe 文件,这就是控制面板。

启动控制面板时,可能会提示选择语言。首次启动时,如果系统弹出任何安全对话框,请务必选择“允许”或“是”,以确保软件能正常运行。

启动Apache和MySQL服务

现在,我们通过控制面板来启动Web开发所需的核心服务:Apache(Web服务器)和MySQL(数据库服务器)。

在XAMPP控制面板中,找到Apache和MySQL对应的行,分别点击“Start”按钮。启动成功后,按钮旁边的状态指示灯会变为绿色。如果启动过程中出现任何红色错误提示,请根据提示信息排查问题。

为了便于日后访问,建议将XAMPP控制面板固定到任务栏。

验证安装与配置PHP

当Apache和MySQL服务都成功运行后,我们可以在浏览器中访问 http://localhost 来打开XAMPP的仪表盘。这证明本地Web服务器已正常工作。

接下来,我们需要确保PHP的配置适合开发。一个关键的设置是 display_errors 变量,它控制是否在页面上显示PHP错误信息。在开发阶段,应将其设置为开启(On),以便于调试;在生产环境则应关闭(Off)。

以下是检查与修改 display_errors 设置的步骤:

  1. 在XAMPP控制面板中,点击Apache一行的“Config”按钮,选择“PHP (php.ini)”。
  2. 在打开的配置文件中,使用 Ctrl+F 搜索 display_errors
  3. 确认其值是否为 On。如果需要修改,将其改为 On 并保存文件。
  4. 保存后,需要在控制面板中重启Apache服务以使更改生效。

修改完成后,可以再次访问 http://localhost 并点击“PHPInfo”页面,搜索 display_errors 来确认设置已生效。

创建并运行第一个PHP程序

环境配置妥当后,我们来创建一个简单的PHP程序进行测试。首先,需要使用一个文本编辑器(如VS Code、Sublime Text或Notepad++)编写代码。

所有需要通过本地服务器访问的网页文件,都必须放在XAMPP的 htdocs 目录下(例如 C:\xampp\htdocs)。

以下是创建和测试程序的步骤:

  1. htdocs 目录下创建一个新文件夹,例如命名为 first
  2. 在文本编辑器中新建文件,输入以下混合了HTML和PHP的代码:
    <!DOCTYPE html>
    <html>
    <head>
        <title>My First PHP Page</title>
    </head>
    <body>
        <h1>Hello World from HTML</h1>
        <?php
            echo "<p>Hello World from PHP.</p>";
            $sum = 6 + 4;
            echo "<p>The sum of 6 and 4 is: " . $sum . "</p>";
        ?>
    </body>
    </html>
    
  3. 将这个文件保存到 first 文件夹中,并命名为 index.php(完整路径如 C:\xampp\htdocs\first\index.php)。
  4. 打开浏览器,访问地址 http://localhost/first/index.php
  5. 如果页面成功显示,并且包含了由PHP计算输出的结果,则说明你的XAMPP开发环境已经完全搭建成功。


本节课中我们一起学习了在Windows 10上安装和配置XAMPP开发环境的完整流程。我们从下载安装包开始,完成了安装、启动核心服务(Apache和MySQL)、验证安装并配置了关键的PHP设置,最后通过创建并运行一个简单的PHP程序,确认了整个环境工作正常。现在,你已经拥有了一个本地的Web服务器环境,可以开始进行PHP编程和数据库操作了。

121:在Windows系统中使用Ngrok连接自动评分器 🖥️➡️🌐

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地计算机上的Web应用程序临时暴露到公网上,以便完成需要与自动评分器交互的作业。

概述

当你完成一个Web应用程序作业后,代码通常运行在你的本地计算机上,地址类似于 localhost。然而,自动评分器位于互联网上,无法直接访问你本地的 localhost 地址。这就像你的电脑有一道防火墙,阻止了外部世界的访问。为了解决这个问题,我们需要使用Ngrok。Ngrok能为你本地运行的服务创建一个临时的、可公开访问的网址,让自动评分器能够与你的代码进行通信。

下载与安装Ngrok

首先,我们需要在电脑上安装Ngrok软件。

  1. 访问Ngrok官方网站(ngrok.com)。
  2. 注册一个免费账户并下载适用于Windows系统的版本。
  3. 下载完成后,打开安装文件。为了方便在命令行中使用,建议将Ngrok的可执行文件放在一个易于访问的位置,例如桌面。

启动本地服务器并运行Ngrok

假设你的Web应用程序已经在本地运行,并监听80端口(Web服务器的默认端口)。

以下是启动Ngrok并创建隧道的关键步骤:

  1. 打开命令提示符(CMD)。
  2. 使用 cd 命令切换到存放 ngrok.exe 文件的目录。例如,如果放在桌面,可以输入:
    cd %USERPROFILE%\Desktop
    
  3. 输入以下命令启动Ngrok,将本地80端口的服务暴露到公网:
    ngrok http 80
    
  4. 命令执行后,Ngrok会启动并在命令行中显示一个控制台界面。其中最重要的信息是 Forwarding 后的网址,例如 https://a1b2c3d4.ngrok.io。这个就是你的本地应用临时的公网地址。

在自动评分器中使用临时地址

现在,你拥有了一个可以公开访问的地址。

  1. 复制Ngrok生成的Forwarding地址(例如 https://a1b2c3d4.ngrok.io)。
  2. 打开你的浏览器,访问这个地址,你应该能看到和本地 localhost 完全一样的应用程序页面。
  3. 如果你的应用有特定路径(例如 guessinggame.php?guess=12),你需要将这个完整路径拼接到Ngrok地址后面。例如:
    https://a1b2c3d4.ngrok.io/guessinggame.php?guess=12
    
  4. 最后,将这个完整的、可公开访问的URL提交到课程自动评分器的相应输入框中。自动评分器现在就能通过这个临时地址访问并测试你本地运行的代码了。你可以在Ngrok的命令行窗口中看到评分器与你的应用之间所有的请求和响应记录。

任务完成后的操作

作业提交并评分完成后,为了安全起见,你应该关闭这个临时的公网通道。

  1. 回到运行Ngrok的命令行窗口。
  2. 按下 Ctrl + C 组合键,即可停止Ngrok服务。
  3. 服务停止后,之前生成的临时地址立即失效,外部无法再访问你的本地应用。

请注意:每次运行 ngrok http 80 命令,系统都会生成一个全新的随机地址。因此,每次提交作业前,都需要获取并使用最新的地址。

总结

本节课我们一起学习了如何使用Ngrok工具解决开发中的常见问题:让运行在本地环境(localhost)的Web应用程序能够被互联网上的服务(如自动评分器)临时访问。关键步骤包括下载Ngrok、通过命令行建立隧道获取公网地址,以及将包含完整路径的该地址提交给评分系统。记住,使用完毕后务必断开Ngrok连接以保障本地计算机的安全。

122:Macintosh系统使用Ngrok连接自动评分器 🍎

在本节课中,我们将学习如何使用Ngrok工具,将运行在你本地电脑(如Mac)上的Web应用程序暴露到公网,以便课程自动评分器能够访问并评估你的作业。

概述

我们当前正在完成“猜数字游戏”的作业评分。问题在于,你的PHP代码运行在本地服务器(如MAMP)上,地址是localhost127.0.0.1。而自动评分器运行在真实的互联网上,无法直接访问你电脑上的localhost。因此,我们需要一个桥梁,让公网上的评分器能够与你的本地服务器通信,这个桥梁就是Ngrok。

理解问题

首先,我们确认本地代码运行正常。例如,访问 http://localhost:8888/guess.php?guess=42 可以正常工作。

然而,如果直接将这个localhost地址提交给自动评分器,评分器会连接失败,因为它无法从互联网访问你个人电脑的内部地址。你会看到类似“连接被拒绝”的错误。

解决方案:使用Ngrok

Ngrok是一个软件,它能创建一个安全的隧道,将你本地服务器的一个端口(如8888)映射到一个临时的、公网可访问的URL(例如 https://abc123.ngrok.io)。这样,互联网上的任何服务(包括自动评分器)都可以通过这个Ngrok URL访问到你本地的应用。

以下是使用Ngrok的完整步骤。

第一步:下载Ngrok

你需要先下载Ngrok软件。

  1. 访问Ngrok官方网站的下载页面。
  2. 根据你的操作系统(Mac或Windows)选择对应的版本进行下载。
  3. 下载完成后,文件通常位于你的“下载”文件夹中,是一个压缩包。

第二步:在终端中启动Ngrok

下载后,需要在终端(Mac)或命令提示符(Windows)中运行它。

  1. 打开终端应用程序。
  2. 使用 cd 命令进入下载文件夹。在Mac上,命令通常是:
    cd ~/Downloads
    
  3. 解压并运行Ngrok。假设你的本地服务器运行在localhost8888端口,你需要告诉Ngrok转发这个端口。命令格式如下:
    ./ngrok http 8888
    
    这个命令的意思是:启动Ngrok,并将所有发送到Ngrok公网地址的HTTP请求,转发到你本机的8888端口。

第三步:获取你的公网URL

运行上述命令后,Ngrok会启动并在终端中显示信息。

你会看到类似这样的输出,其中包含了你的临时公网地址(例如 https://abc123.ngrok.io):

Forwarding https://abc123.ngrok.io -> http://localhost:8888

关键点:这个地址仅在当前Ngrok程序运行期间有效。如果你关闭终端或停止Ngrok,这个地址就会失效。下次启动时,你会获得一个不同的新地址。

第四步:测试与提交

现在,你可以用这个新的Ngrok URL来替代原来的localhost地址。

  1. 在浏览器中访问你的Ngrok URL(例如 https://abc123.ngrok.io/guess.php),它应该能显示和你本地localhost访问时完全相同的内容。这证明隧道工作正常。
  2. 这个Ngrok URL复制到自动评分器的提交框中,然后点击“评估”。
  3. 自动评分器现在可以通过这个公网URL访问到你本地运行的代码,并开始进行测试。

第五步:调试与修改

提交后,自动评分器会运行一系列测试。如果测试失败,它会给出错误信息。

例如,错误可能是“在标题标签中未找到‘Chuck Severance’”,或者“你的猜测太高/太低”。这时,你需要:

  1. 仔细阅读错误信息:它明确指出了问题所在。
  2. 返回你的代码:根据错误提示修改你的PHP文件。比如,将正确的答案从42改为37,或者在HTML标题中加入要求的名字。
  3. 保存文件:你的本地服务器(MAMP)会自动加载修改。
  4. 重新在自动评分器中点击“评估”:无需重启Ngrok。评分器会再次通过隧道访问你已更新的代码。

你可以在运行ngrok http 8888的终端窗口里,看到所有经过隧道的网络请求和响应,这有助于调试。

第六步:完成操作

当你的作业全部通过评分,不再需要使用自动评分器后,可以关闭Ngrok隧道。

在运行Ngrok的终端窗口中,按下 Control + C 组合键。这将停止Ngrok进程,之前生成的公网URL也会立即失效。

总结

本节课中,我们一起学习了如何使用Ngrok工具解决本地开发环境与远程自动评分器之间的连接问题。核心步骤是:下载Ngrok,在终端中运行命令将本地端口转发到公网,获取临时URL并提交给评分器,根据反馈调试代码,最后在完成后关闭隧道。记住,每次启动Ngrok都会获得一个新的临时地址,这足以完成我们的作业评分需求。

123:Brendan Eich发明JavaScript

概述

在本节特别内容中,我们将跟随JavaScript的发明者Brendan Eich的讲述,了解JavaScript在1995年被创造出来的历史背景、设计理念、核心特性以及它如何演变成今天的样子。这对于理解现代Web开发至关重要。


我于1995年4月受雇于网景公司。

当时网景公司已经推出了其“Mosaic杀手”浏览器。如果你还记得,Mosaic是NCSA开发的主流浏览器,直到网景浏览器取代了它。当我加入网景时,公司已经运营了大约一年。实际上,我本有机会在最初就加入,但我错过了。不过,我加入的时间点正好让我能够去做一件吸引我的事:为HTML设计一种编程语言,供网页设计师和程序员直接嵌入网页中使用。

这种语言不是当时正在兴起的、被称为Java的语言。Java更像是一种专业语言,你需要用类型声明来编写真正的代码,并且必须以可编译的方式编写。

我正在编写的JavaScript,可以被那些不知道编译器是什么的人使用。他们只需加载它,就像使用BASIC语言一样。这确实是我们提出的核心理念:两种语言,而非一种

这类似于微软为C++提供的Visual Basic,所以JavaScript就是为Java准备的。Sun公司的Bill Joy实际上喜欢这个想法并同意了。正是他签署了商标许可协议,使我创造的东西得以命名为“JavaScript”。这个名字完全是个谎言,它其实与Java关系不大,更多是继承了共同的祖先C语言的语法。我们努力使其易于使用,成为一种你可以复制粘贴代码、从小脚本开始并逐渐发展为完整程序的语言。

JavaScript取得了巨大的成功。同时,它也是一个极其仓促的产物,因此其中存在错误。

我认为很重要的一点是,我当时就知道会有错误和不足。

因此,我把它设计成一种非常具有可塑性的语言。这使得开发者能够让它成为他们想要的样子。

开发者可以将自己的风格,不仅仅是API风格,甚至是语言模式,投射到JavaScript上。

用Eric von Hippel的话说,他们可以在其上创建自己的创新网络或创新工具包。所以,它不是一种试图将你限制在单一范式的语言,它是一种多范式语言。许多语言一推出就绊倒摔跤,然后需要第二次尝试才能站稳。

但JavaScript并没有真正地绊倒摔跤。你可以说它没有第二个版本,这是事实。因为我曾尝试开发一个可能成为大版本2的东西,即所谓的第四版,但它失败了。JavaScript一直在进化。网络就是关于进化的,身处其中的人们可能不太察觉,因为这是微观进化。但90年代的网页如今并不能全部正确渲染或正常工作,很多已经丢失,只能通过网络档案馆找到。JavaScript在最初就拥有足够多的“好部分”(借用Crawford的说法),或者说从其他语言继承了足够的“遗传物质”。

一等函数和从Self语言借鉴的原型继承。从Scheme语言继承一等函数,这其实有点名不副实,因为Scheme在很多方面都不同,我无法在JavaScript中体现这些差异,无法实现Scheme的特性。我当时的任务是让它看起来像Java,我只有10天时间来制作原型。所以Scheme更多是精神上的,而非实际的影响。但一等函数非常强大,并且它们与事件处理的编程模型很契合。

我受到了Atkinson的HyperCard的启发,这就是为什么你在JavaScript中能看到onclick。HyperCard有一种称为“on”的事件处理模式,比如on page down等。

因此,JavaScript在最初就拥有足够多的优点,得以生存下来。然而,回想90年代中期,JavaScript曾备受诟病,因为它主要被用于制造一些恼人的效果,比如浏览器底部状态栏的滚动消息、闪烁的图片,或者大量弹出窗口。

我们本可以加入控制这些功能的设置,最终也应该这样做。后来,Firefox等浏览器倡导并自动抑制了这些恼人的行为,使情况大为改善。随着摩尔定律的持续作用,以及JavaScript在标准化过程中获得了一些进化性的改进,它在2004-2005年变得足够快、足够好,从而催生了Web 2.0革命。

我认为这与Firefox从IE手中夺回市场份额,以及开发者意识到客户端编程栈可以富有表现力、功能强大并且足够快速(主要得益于更快的计算机)是分不开的。

你一定接受过一些训练,有过一些经验,才能达到可以从Scheme等语言汲取灵感的程度。

我实现过……当我最初进入计算机科学领域时,我算是个语言爱好者。我最初是数学和物理专业,最终在本科毕业时转成了数学和计算机科学。所以我编程……形式语言理论应用于识别语言,如词法分析、语法分析器,从语法自动构建分析器。我喜欢这些东西,因为它们在理论上非常优美和清晰,现在依然如此,自80年代初我上大学以来,这方面的创新只有一两个。这给了我快速构建语言解释器的能力。我可以编写解析器和扫描器,我可以生成字节码,因为网景希望做服务器端嵌入,而Java……尽管它本可以是树遍历或解释解析的东西,我还是为它制作了字节码,这是一种内部字节码,不是后来成为Java负担的Java字节码。

我能快速完成所有这些,因为我以前做过。我在硅谷图形公司做过,用于构建网络监控工具,根据各种协议头字段的表达式来捕获数据包。我也曾出于兴趣为自己创造语言。最后,我得以非常快速地完成它。速度对我来说是个问题,部分原因是我们都感觉微软会来追赶网景,因为他们曾在94年底试图以极低的价格收购网景(这是我加入网景前听说的)。同时,我们在与Java的关系上也处于一种奇怪的博弈论情境中,因为即使在网景内部,也有人认为:“如果我们有了Java,真的还需要第二种语言吗?”他们没有看到为更广泛的程序员、业余爱好者、设计师、初学者提供一个类似Visual Basic的伴侣语言的好处。

在当时,为微软平台编写Java或C++需要大量教育,成本更高。而使用Basic和Visual Basic在微软Windows上,让人们粘合组件、设计页面、填补空白则更便宜、更普及。这也使得这种“用户创新工具包”方法(再次借用von Hippel的短语)成为可能。因为JavaScript具有可塑性,因为有如此多的网页设计师,你会看到关于如何使用它的不同思想流派涌现出来。这在过去10年随着各种JS库的出现变得非常明显。我认为这实际上是JavaScript的一个优势,正如我早先所说,我们没有告诉你“这是唯一正确的写法,这是唯一真正的面向对象范式,这是你创建可重用抽象的唯一方式”。这不是没有代价的,它对初学者来说可能很难,人们会重复发明某些轮子并在此过程中犯错,或者不喜欢必须引入库。但你看,jQuery是一个非常流行的库,因为它给了人们一种非常简洁的“查询并执行”范式。同样,这在JavaScript中不是强制性的,但很多人学习了它,就认为那就是JavaScript,他们认为jQuery是一种语言,或者认为jQuery是主导者。jQuery很棒,John Resig(jQuery作者)曾与我们在Mozilla共事。但现在有太多优秀的库了,而且它们实际上正在变得更小、更具组合性,这是一个好趋势。因此,JavaScript通过其可塑性和对用户创新的培养,我认为扮演了独特的角色。如果我当时做了更死板的东西,我认为这门语言可能已经失败了。

我简直无法想象如何能逃脱C++的面向对象模式。部分原因是我不得不这样做,因为如果我在1995年5月那10天里就在JavaScript中加入了类,我想我会被告知“这太像Java了,你在和Java竞争”。Sun公司可能有人会比当时更严厉地指责Bill Joy,这可能会毁掉整个交易。所以我当时不仅受到时间限制,还受到市场要求的约束:让它看起来像Java,但不要让它显得太庞大,它只是Java的一个有点傻的小兄弟、一个配角语言。但随后你加入了一些原始特性,我加入了一些东西,比如闭包和其他特性,使得你可以构建你想要的东西。是的,这对很多人来说并不显眼,甚至在第一个版本中,并非所有功能都处于良好的工作状态。但在接下来的几年里,它不仅……我会说在接下来的10年里,它变得更标准化,也被更广泛地宣传,比如Crawford大力推广闭包模式以及闭包的良好用法。

人们发现其可塑性、表达力和强大功能足够有吸引力,以至于有些人实际上抵制ECMAScript版本2。他们说:“我不希望你为某些模式添加类或常见的特殊形式,我更愿意自己编写或通过库来获取。”

你创造了一种抽象,实现者可以做疯狂的事情,人们可以重新思考解释器真正应该做什么。他们可以,他们可以说:“好吧,V8来了,带来了以前从未尝试过的不同优化。”我了解这些优化,因为我研究过Smalltalk等语言,但之前没有人投入时间和金钱。谷歌可能是第一个,其他公司也在并行努力,苹果和Mozilla也尽力跟上。但V8……它在推动这方面发展上功不可没,尽管它并不完全像他们声称的那样是第一个出现的,因为大家都在2008年左右集中发力。但它展示了可以做到什么。对我来说有趣的是,然后你对语言施加更密集的工作负载,你会发现应该出现一个新的V8。它可能不会来自谷歌,因为他们可能已经厌倦了优化JavaScript。事实上,我认为Dart(由V8负责人开发)就是对此的回应,他们想做一种不必担心所有这些疯狂兼容性的语言。Dart可能不会成功,它也没有给JavaScript带来下一级的性能。但我相信那个性能级别是存在的,而且仍在显著提升,其性能提升幅度远超像Java这样的语言(在标准基准测试上,Java的提升只有百分之几或更少)。现在,很多所谓的HTML5开发(JavaScript、CSS、超越HTML5规范的Web API)正在兴起。你看到像Zynga这样的公司只做HTML5游戏。它来得比一些人想的要快。我和纽约联合广场的风险投资家Fred Wilson聊过,他说:“是的,它已经来了。”他原以为还需要几年。我们已经跨过了那个拐点。所以你称之为HTML5,它真正的含义是Web技术栈。这是你用来编写网页和托管Web应用的同一套技术栈,你也可以用它来编写在设备上运行的应用程序,这些应用可能是托管的,可能是离线的,可能界限模糊,以至于你可以将它们与一个URL关联,但你也可以在没有互联网连接的飞机上使用它们,而不用担心会丢失任何东西。


总结

本节课中,我们一起学习了JavaScript的诞生故事。我们了解到,JavaScript是在1995年由Brendan Eich在网景公司用极短时间(约10天)设计出来的,其初衷是作为一种易于初学者和设计师使用的网页脚本语言,作为Java的补充。它的设计核心是可塑性多范式,借鉴了C的语法、Scheme的一等函数、Self的原型继承等思想。尽管早期因被滥用而名声不佳,但随着浏览器性能提升、标准进化以及V8等引擎的优化,JavaScript最终成为驱动Web 2.0革命和现代Web应用(包括移动和离线应用)的核心技术。它的成功很大程度上源于其灵活性和开放的生态,允许开发者通过库和框架塑造自己的开发风格。

124:华盛顿州西雅图

概述

在本节附加办公时间中,我们将跟随课程团队,了解在西雅图举行的线下学员见面会。你将看到来自不同背景的学员分享他们的学习体验与目标。


大家好,欢迎来到24小时内的第二次办公时间。我们从波特兰驱车,在夜色中前往西雅图参加这次办公时间,因为我们致力于办好每一次学员交流活动。

我想向你们介绍一些你们的同学,并请他们向其余学员说几句话。那么,我们开始吧。请告诉我们你的名字,并对班级说些什么。

以下是学员们的自我介绍:

  • An: 大家好,我叫An,谢谢邀请我。学习“面向所有人的Python”专项课程很有趣。我希望每个人都享受这门课程。谢谢。
  • Lee: 大家好,我是Lee。西雅图很棒。你们所在的地方也很棒。
  • Charles(教师): 是的,我们还没看到有人扔鱼呢。(注:此处指西雅图派克市场著名的“飞鱼”表演
  • Steph: 大家好,我是Steph。编程让我感到非常快乐。编织也是一种编程,你们知道吗?随着你们教育的深入,会明白这一点。我是一名生态学家和教师,正尝试转向数据分析和报告工作,因为这样我可以同时运用我的统计学知识和教学技能。这正是我们做这些事的目的。
  • Tony: 大家好,我是Tony。我喜欢Python课程,教授也很聪明。我希望每个人都享受这门课程。谢谢。我也喜欢Python课程。😊
  • Nicole: 大家好,我是Nicole。请继续学习Python,并加入我,成为一名数据分析师。我分析的是IP协议数据,也就是网络数据。很酷。
  • Joanna: 大家好,我是Joanna。永远不要放弃,并且在你自己钻研问题15分钟后,一定要寻求帮助。
  • Sha: 大家好,我是Sha。我在Expedia做数据分析,喜欢处理Expedia的旅行数据。我喜欢Python课程,因为它帮助我的女朋友学习了编程。
  • William: 大家好,我是William。我正在学习“面向所有人的Python”课程,直到上了这门课,我才意识到自己之前对字典(dictionary)的操作掌握得如此糟糕。欢迎你。

以上就是学员们的分享。

我们下一次计划举办的办公时间将在英格兰的布莱切利公园。届时我会提前发布详细通知。通常我不会提前太多通知,但像布莱切利公园这样的活动,我会提前几周通知,以便大家安排行程。

那么,我们网络上再见。


总结

本节课中,我们一起了解了西雅图办公时间的现场情况,聆听了多位学员的学习心得与职业目标。从教师到数据分析师,从生态学家到网络工程师,大家因编程而相聚,分享着学习Python的乐趣与挑战。记住,学习路上永不孤单,适时寻求帮助是进步的关键。我们下次活动再见。

125:JavaScript面向对象概念 🧩

在本节课中,我们将要学习JavaScript中的面向对象编程概念。如果你已经学习过Python或PHP的面向对象编程,你会发现核心思想是相通的。本节将介绍类、实例、方法等基本概念,并重点解释JavaScript实现面向对象的独特方式——通过“一等函数”。

概述:面向对象的核心思想

上一节我们介绍了JavaScript的基础知识,本节中我们来看看面向对象编程在JavaScript中的体现。

面向对象编程的核心思想在所有语言中都是相似的。其基本概念是存在一个称为“类”的模板。在类中,我们定义称为“方法”或“消息”的代码,以及称为“属性”的数据。然后,我们使用这个模板(好比饼干模具)来制作出具体的“饼干”。这些具体的“饼干”,即实物而非形状,被称为“对象”或“实例”。

这就是所有面向对象模式的共通之处。因此,在多种语言中学习面向对象编程尤其有益,因为你会意识到:“哦,是的,这只是语法上的一点不同。”

类与实例 🍪

就像在所有面向对象概念中一样,是一个模板,它定义了共享的特征,是一个蓝图。例如,“狗”是一个通用概念,所有狗共享品种、毛色等特征。

实例是我们根据模板“印刻”出来的具体事物。在我们的饼干模型中,所有的饼干都是实例。你会注意到有些饼干以一种方式装饰,有些以另一种方式装饰,但它们最终都源自同一个模板。

“实例”的另一个常用词是“对象”。所以,“类与对象”或“类与实例”是等价的。我认为“实例”更能捕捉这个想法,因为你可以从一个类创建多个实例(对象)。

方法与对象内部结构 ⚙️

对象有点像程序中的一个小程序。程序内部有一些数据结构,代码则操作这些数据结构来解决问题。解决问题是构建巧妙的数据结构和编写巧妙利用这些数据的代码的结合。有时你把更多的巧思放在数据结构中,有时则全部放在代码中。因此,在使用数据结构和代码实现目标之间存在一种平衡。

对象内部也是如此。程序是大的,而对象就像它自己独立的小空间,里面有代码和数据。

方法是存在于类(以及我们创建的对象)内部的函数。它是我们与对象交互、从中获取信息等方式的体现。

JavaScript的独特之处:一等函数 🎯

上一节我们介绍了类与实例的基本关系,本节中我们来看看JavaScript实现面向对象的独特方式。

JavaScript中的面向对象模式有所不同。它更像是一种“存储与复用”的模式。JavaScript核心中的函数既相同又不同。

function 关键字本身是可执行的,并且它有一个返回值。通常你认为 function blah() { ... } 只是创建了一个名为 blah 的新东西。但在JavaScript中,这个函数语句本身是一个可执行语句。

因此,你可以这样写:

x = function() { blah blah blah };

关键是,这定义了一个函数。如果我们将其命名为 y,它就定义了一个函数,但同时它也返回该函数的代码,以便你可以将其放入一个变量中。这非常酷。这是一种非常不同的思考函数的方式。

我最初用Fortran编程,我们称它们为“子程序”,后来也称为函数。你说“子程序”,它就是在命名一个东西并进行存储和检索,是纯粹的“存储与复用”。

在JavaScript中,它也是“存储与复用”,但你还能从中获得一个返回值。这正是我们用来进行面向对象编程的东西。

这在计算机科学中被称为一等函数。这意味着函数本身就是数据,代码和数据更加等价。“一等函数”意味着你可以获取某段代码,将其赋值给一个变量,并携带这段代码。这是JavaScript中一个根本性的架构概念,它深刻地改变了你在JavaScript中的编码方式。

正如我之前所说,JavaScript不适合作为你的第一门编程语言来学习,但作为第四门语言来学习则非常棒,因为你会惊叹:“哇,我能理解这种东西了!”因为如果我刚开始就把函数赋值给变量,你可能会困惑:“不,我不理解。”我热爱将Python作为第一门编程语言。

总结与预告 📝

本节课中我们一起学习了JavaScript面向对象编程的基本概念。我们回顾了类、实例和方法的通用定义,并重点探讨了JavaScript通过“一等函数”实现面向对象的独特机制。这种机制允许函数像数据一样被赋值和传递,为JavaScript的面向对象模式奠定了基础。

接下来,我们将运用所有这些理念,编写一些示例代码,具体看看对象在JavaScript中是如何工作的。

126:JavaScript对象构建 🧱

在本节课中,我们将要学习JavaScript中对象构建的基本概念和实现方式。我们将探讨JavaScript如何通过函数来模拟面向对象编程,理解this关键字的作用,并学习如何创建和使用对象实例。


在开始讲解JavaScript对象示例代码之前,我想先谈谈这件T恤。这是一件地鼠(Gopher)T恤,生产于1992年,如今已是一件稀有物品。万维网(World Wide Web)发明于1980年代,而Gopher协议则由明尼苏达大学的马克·麦卡希尔在同一时期发明。Gopher在1993、94年之前非常流行,直到万维网突然崛起。Gopher就像是被人遗忘的网络技术。但Gopher的关键意义在于,如果没有它,我们可能就不会有今天的万维网。有一段时间,万维网和Gopher相互竞争,Gopher一度领先,随后万维网不断改进,最终成为了主流。

我之所以讲这些,是因为在这门课程中,我教授了许多基础知识,包括基础的JavaScript、PHP和SQL。你可能会想,为什么不教一些更时髦的技术,比如Ruby on Rails或Angular。答案是,基础知识远比时髦的技术重要得多。因为一旦掌握了基础,学习高级技术就会水到渠成。就像今天你不会用Gopher来上网一样,未来你可能也不会用PHP和基础JavaScript来编写Web应用程序,但你总会回归并感激这些基础知识。经典之所以是经典,是因为它们至关重要。即使你最终不会使用某项技术,它也可能对你的理解产生深远影响。


回到JavaScript和面向对象编程。这里有一段非常优美的代码。

在JavaScript中,我们没有像其他语言那样的class关键字来声明一个对象。我们实际上是利用函数的附加特性来实现面向对象模式。

我们使用function关键字来创建一个命名函数。每次调用这个函数(配合new关键字)时,我们都会得到它的一个新实例。记住,类(Class)是模板,而实例(Instance)是我们创建的具体对象。在实例内部,我们会有数据(属性)和代码(方法)。

在几乎所有面向对象模式中,当你有多个实例,并且你正在类的代码内部操作时,你必须使用this关键字。this指向你当前正在操作的特定实例。

当使用new关键字调用这个函数时,例如new PartyAnimal()new关键字会指示JavaScript:让我们根据PartyAnimal模板创建一个新的实例。然后,函数内部的代码会执行。此时,this就指向这个新创建的实例。

this.x中的点号(.)是大多数语言中的对象成员访问操作符。this.x = 0这行代码的意思是:在this指向的实例中,创建一个名为x的变量(属性),并将其值设置为0。这就是你为对象添加属性的方式。

接下来的代码this.party = function() {...}意味着我们要创建一个名为party的方法。这里用到了JavaScript的“函数是一等公民”特性。请注意,这里的function后面没有名字,它是一个匿名函数。因为实际上,这个函数的名字将通过party属性来访问。

现在,这个对象内部就有了属性x和方法party。在party方法内部,this仍然指向当前实例,因此我们可以访问并修改属于这个实例的x属性。

这段代码首先创建了模板(函数定义),但并没有立即执行大量逻辑。当执行var an = new PartyAnimal();时,它不仅仅是调用函数,而是:

  1. 根据模板创建一个新的对象实例。
  2. 将属性x和方法party设置到这个实例中。
  3. 将这个新实例的引用赋值给变量an

现在,an就是一个指向这个实例的指针(或引用)。当我们调用an.party()时,就会执行该实例内部的party方法,将它的x属性从0增加到1并打印出来。连续调用三次,就会依次打印出1、2、3。

这就是JavaScript对象构建的基本机制。其真正美妙之处在于匿名函数的概念,以及可以将函数代码作为值赋值给变量(如this.party)的能力。你甚至可以在对象之外进行函数赋值,这意味着function关键字无论在何处,都能返回构成函数的代码块,我们通过将其存入变量来为其命名。这非常优雅。

其他语言如Python和Java,它们创建了额外的语法结构(如class)。而JavaScript的创造者布兰登·艾奇则让函数成为了一等公民,函数本身就是数据。这个思想受到了Smalltalk和Lisp等语言的影响。在面向对象编程中,有两种思想:一种是更纯粹的(数据与代码等价),另一种是语法糖式的。Python、PHP、Java和C++更偏向于后者,而Smalltalk、Lisp和JavaScript(尽管它最初只是个在浏览器中制造有趣效果的小语言)却拥有更纯粹、更优美的面向对象模式。这也是为什么它最终是一门值得学习的伟大语言,但前提是你已经准备好去理解它。


接下来,我想谈谈对象的生命周期,以及如何处理多个实例。

以下是创建和使用JavaScript对象的核心步骤:

  1. 定义构造函数:使用function关键字定义一个函数,这充当了对象的“蓝图”或“类”。

    function PartyAnimal() {
      this.x = 0;
      this.party = function() {
        this.x = this.x + 1;
        console.log("So far " + this.x);
      };
    }
    
  2. 创建对象实例:使用new关键字调用构造函数,生成一个独立的对象。

    var an = new PartyAnimal();
    

  1. 访问属性和调用方法:使用点号(.)操作符来访问实例的属性或调用其方法。

    an.party(); // 输出:So far 1
    an.party(); // 输出:So far 2
    an.party(); // 输出:So far 3
    
  2. 理解this:在构造函数和方法内部,this关键字指向当前正在创建或操作的那个实例。这是区分不同实例属性的关键。

  3. 创建多个实例:你可以创建多个独立的实例,它们拥有各自的数据。

    var an = new PartyAnimal();
    var another = new PartyAnimal();
    an.party();      // an.x 现在是 1
    another.party(); // another.x 现在是 1 (独立于an.x)
    


本节课中我们一起学习了JavaScript中对象构建的基础。我们了解到JavaScript通过函数和new关键字来实现面向对象编程,使用this来访问实例内部的成员,并且可以创建多个独立的实例。虽然JavaScript的语法与其他语言不同,但其核心的面向对象思想是相通的。掌握这些基础知识是理解更复杂JavaScript模式和框架的关键。

127:JavaScript对象生命周期 🧬

在本节课中,我们将要学习JavaScript中对象的生命周期,包括如何创建、使用以及理解其构造和销毁过程。我们将重点探讨构造函数的概念、实例的创建,以及JavaScript对象模型的独特之处。


构造函数与对象创建

上一节我们介绍了对象的工作原理和创建方式。本节中,我们来看看对象生命周期中的第一个关键环节:构造函数。

构造函数是在从类创建对象的瞬间运行的代码。在JavaScript中,构造函数是隐式的,因为它利用了“一等函数”的本质。构造函数就是定义类形态的所有代码。

function Person(name) {
    console.log('Built ' + name);
    this.x = 0;
    this.name = name;
    this.party = function() {
        this.x = this.x + 1;
        console.log(this.x + ' ' + this.name);
    };
}

当使用 new 关键字时,构造函数中的代码(如 console.log 和属性赋值)会运行。但像 this.party 这样的函数定义代码会被记住并赋值给该属性,而不会立即执行。


创建多个实例

以下是创建多个对象实例的步骤说明。

我们可以从一个类创建多个实例或对象。类本身就像一个“饼干模具”,我们可以用它“盖章”多次,得到多个独立的对象。每个对象都拥有自己的一份实例变量副本。

// 创建第一个实例,传入参数“Sally”
let s = new Person('Sally');
s.party(); // 输出:1 Sally

// 创建第二个实例,传入参数“Jim”
let j = new Person('Jim');
j.party(); // 输出:1 Jim

// 再次调用第一个实例的方法
s.party(); // 输出:2 Sally

在这个例子中:

  1. 我们通过 new Person('Sally') 调用构造函数,创建了第一个对象。参数 'Sally' 被传递给构造函数,并赋值给实例变量 this.name
  2. 方法 this.party 被创建,其内部代码引用了 this.name。对于 s 实例,这个 name 是“Sally”。
  3. 我们再次调用构造函数 new Person('Jim'),创建了第二个完全独立的对象 j,其 name 是“Jim”。
  4. 当我们调用 s.party()j.party() 时,它们分别操作各自对象内部的 x 变量,互不干扰。

这就是面向对象编程的魅力:创建一个模板(类),然后生成多个独立运作的对象。


对象生命周期总结

到目前为止,我们讨论了如何制作一个包含方法(或消息)和属性的类模板。构造函数是我们进行所有初始化设置的地方,而对象或实例则是我们从构造函数中得到的结果。

关于析构函数(对象销毁时运行的代码),需要说明的是:JavaScript没有显式的析构器。这是因为JavaScript的一切都是动态的,对象的销毁时机不如Java或C++等语言中那样可预测。在PHP中,由于请求-响应周期的特性,析构函数的行为相对更可预测一些。在对象生命周期中,构造函数最为重要,析构函数处于次要地位。


JavaScript对象的独特之处

本节内容较为简短,因为大家可能已经对面向对象有基本了解。我们真正想强调的是JavaScript特有的“秘密武器”。

JavaScript对象是动态可扩展的。你可能会注意到,当我们写 this.x = 0 时,我们并没有提前声明这个属性。实际上,可执行代码在运行时创建了新的属性。同样,在运行时你也可以创建新的方法。

这些对象非常强大。回想一下数组,我们甚至可以在不知不觉中使用JavaScript对象。例如,使用花括号 { key: value, key: value } 可以创建一个没有方法、只有一组属性的对象。因此,我们有时将JavaScript对象视为关联数组(或字典)的等价物。


课程总结

本节课中我们一起学习了JavaScript对象的生命周期。我们理解了构造函数是如何在对象创建时隐式运行的代码,并学会了如何通过一个类模板创建多个独立的实例。每个实例都拥有自己的属性和方法副本。我们还了解到JavaScript对象具有动态可扩展的特性,这与许多其他语言不同。接下来,我们将探讨jQuery,这可能是我们在编写JavaScript代码时使用到的最强大的对象库。

128:韩国首尔

概述

在本节课中,我们将跟随课程讲师,回顾一次在韩国首尔举行的线下办公时间活动。我们将看到来自世界各地的学习者如何聚集在一起,分享他们学习编程、特别是Python语言的经历与收获。


我正在前往韩国首尔办公时间的路上。和往常一样,我不知道会见到多少人,可能是一个人,也可能是零个人,或者是二十个人。但这正是其中的乐趣所在。这就像一场与七十万人的未知约会。接下来会发生什么,我们拭目以待。

大家好,我们又迎来了一次办公时间,这是一次规模盛大的办公时间。我们在韩国首尔,距离那座著名的“交叉双臂”雕像不远,这座雕像据说是为了庆祝某个重要的Gangnam Style视频而建。和往常一样,我想向大家介绍你们的同学,让他们打个招呼,说些想说的话。但这需要一点时间,因为我们有很多人。

那么,让我们从你开始吧。准备好了吗?请开始。

我的名字是……(此处为名字)。我是一名博士后。我正在学习Python。谢谢。

你的名字是?
嗨,我是Shelby。我实际上学的是创意写作,所以编程并非我的专业方向,但我对语言学感兴趣。所以我学习了Python课程,并且从今年夏天开始,我已经上了大概三门课了。我只是有很多时间。
看来我们让你上瘾了,对吧?
是的。你可能也同意这一点,编程就像写作,但又有所不同。
我喜欢各种形式的写作和语言。对我来说,这就是我在课程开始时使用的例子。

你好,我的名字是Shiino。实际上,我只是跟着她(Shelby)来的。但我真的在考虑学习这门Python课程,它是一门非常棒的计算机语言。同时,你的课程看起来也非常棒,太酷了。是的,谢谢。

请介绍一下你自己。
嗨,我的名字是Victor Lee。我在韩国翻译Python书籍。我今天……哦,让我们拿一本样书来展示一下。韩文版。你,你把一切都搞定了,好吧,回来吧。签名版。由Victor签名,由Chuck签名。让我们为Victor热烈鼓掌。Victor,我……我非常不擅长自拍,但我真的很感激你所做的一切,为我们大家把这本书翻译成韩文。你今天又接到了翻译Python 3书籍的任务,那么告诉大家,我付了你多少钱来翻译这本Python 2的书?我付了你一杯啤酒的钱。我们付了你一杯啤酒。我会再付你几杯啤酒的钱来翻译成Python 3吗?不,我们非常感激。这一切的一部分就是每个人都贡献一些东西,而Victor为我们贡献了,所以我们非常感谢你。好的,你。

嗨,我的名字是Su Yuin。Python是一门非常有趣的语言。我每天都使用它,所以很高兴认识你,非常感谢。谢谢。

嗨,我是Johnong Gsong。很高兴在这里见到你,我感觉像是在见一位名人。嗯,我很高兴我已经在笔记本电脑屏幕上见过你了,所以能见到真人真是太好了。谢谢。

嗨,我的名字是Chong Min。很喜欢你的课程。谢谢。

你好,我是来自巴西的Honalo。我喜欢Python,我喜欢学习。我和我的未婚妻Priscilla正在首尔度假。Priscilla。这次办公时间是一个非常愉快的巧合。九月份我在拉斯维加斯举行过一次办公时间,当时来参加的人没有一个来自拉斯维加斯,每个人都是去拉斯维加斯度假的。

嗨,我是Amber。我刚开始学习《Python for Everyone》课程。哦,很高兴加入大家。好的,欢迎来到这门课程。你会留下来吗?我们会很高兴有你加入。

嗨,我是Joine。很高兴在韩国大邱见到你。我想感谢你确保每个人都拿到了啤酒。你做了所有的翻译工作,确保我们收到了所有需要的订单。谢谢。

嗨,我是Yo。我非常喜欢Charles Severance博士的课程,我很高兴来到这里。

嗨,我是Shiang。我是通过他的课程开始学习Python编程的,现在我在一个实验室工作,编写生物信息学算法相关的程序。如果没有他的课程,我甚至无法想象我现在在做什么。所以我真的非常……是的,真的。所以我真的想表达我的感激之情。你之前是做什么的?我只是在学习生命科学。哦,是的,然后我通过你的课程学习了Python编程,现在我成为了一名初级程序员。是的。所以,祝贺你,我真的很想感谢你。非常感谢。所以你并不孤单,我真的很想……和你在一起,欢迎来到这门课程。

嗨,我是Hang。能在课堂上见到视频中的Charles Severance博士真是太棒了。Charles Severance博士是我的第一位编程老师,Python是我的第一门编程语言,我真的很感激。欢迎成为一名程序员,谢谢。

我是Michelle,来自芝加哥,但我在韩国从事金融工作。我以前学习过像花园集这样的东西,但实际上我们在金融领域也使用Python,它是一门非常棒的语言。昨天我收到了这封邮件,我当时想,哦,天哪,什么?是的,这很好,事情总是这样发生的,非常随机。无法预测我什么时候会在某个地方。

嗨,我的名字是Huen。我已经注册了即将开始的课程。我在这里感到非常受鼓舞,我等不及要在Coursera上看到我自己的脸了。

嗨,我的名字是C Lee。大约两年前,我开始了这门Python编程课程,那时我对编程一无所知,是零基础。但现在我成为了一名开发者,成为了一名开发者。我真的很喜欢教女孩们Python编程,我也是韩国Django Girls的组织者之一。这是我美丽的搭档。Django Girls in Korea。你们刚刚举办了一场帮助人们的活动。50名参与者,主要是50个人,我们希望在编程世界激励更多女性。所以你从一个完全不懂编程的人,变成了通过Django Girls激励其他女性成为程序员的人。好的,现在我想把我的贴纸拿回来。我要把这个贴纸贴在我的笔记本电脑上,所以在未来的录像中,你们会看到我的笔记本电脑上有一张来自首尔的Django Girl图片。谢谢。

嗨,我是Hassan。我在韩国生活了很长时间。实际上,我说Python。所以这很简单。是的,为了我的办公室工作,我使用其他语言,但在我的空闲时间,我只使用Python。每当有人和我交谈并想学习编程语言时,我就建议他们安装Python,并从你的课程开始。所以,是的,我非常……我真的很高兴见到你,因为我在2014年完成了这门课程,我等了两年才见到你,是的,非常感谢。

多么有趣的时光啊。我们在这里待了大约一个小时,喝了些饮料,我们差点被赶出去,但后来我们重新整理了房间,所以没有被赶走。我不知道下一次办公时间会在哪里,可能在凤凰城或芝加哥。我在芝加哥举行过两次办公时间。所以我不知道下一次会在哪里,但一旦我弄清楚,我会让你们知道的。干杯。


总结

本节课中,我们一起回顾了在韩国首尔举行的一次特别的线下办公时间。我们看到了来自不同背景、不同国家的学习者如何因为对Python编程和Web开发的共同兴趣而聚集在一起。他们分享了从零基础到成为开发者、甚至成为社区组织者的励志故事。这次活动不仅是一次学习交流,更体现了开放课程社区的力量与温暖。无论你身处何方,学习之旅上总有同行者。

129:jQuery基础 🧩

在本节课中,我们将要学习jQuery的基础知识。jQuery是一个强大的JavaScript库,它极大地简化了在浏览器中操作文档对象模型(DOM)、处理事件以及与服务器交互的过程。通过本教程,你将理解jQuery的核心概念、基本语法以及它如何让Web开发变得更加简单高效。

概述

我们已经学习了JavaScript,了解了文档对象模型(DOM),也认识到DOM本身存在一些缺陷。我们还学习了JavaScript的面向对象编程。所有这些知识在一定程度上都是为了让我们能够使用jQuery,因为jQuery让我们的工作变得非常简单。

在Web应用的整体架构中,我们获取HTML,解析响应,一部分内容进入DOM,另一部分实际上是JavaScript。JavaScript与DOM进行交互。jQuery是一个客户端库,它让JavaScript的使用变得更加容易。我们使用jQuery所做的一切,无论是操作DOM还是最终与服务器进行通信,都变得更加简单。你无需担心浏览器的兼容性等问题。

jQuery让我们能够完成那些原本需要复杂方式才能实现的事情。虽然我们可以用困难的方式去做,但现在没有人再愿意那样做了,所以大家都使用jQuery。

jQuery的定位与趋势

目前,我们的重点正朝着浏览器端转移。在接下来的课程中,甚至在其他课程中,趋势将更多地聚焦于这个方向。我们将在浏览器中做越来越多酷炫的事情,而在服务器端做的事情会越来越少。

我们最初是从在服务器端做所有事情开始的。当然,我们仍然需要处理数据库,但总体上我们正朝着浏览器端移动。Web应用普遍在浏览器中实现越来越多的交互性,因为这能提供更好的用户体验。

你可以争论是否所有事情都应该在浏览器中完成,但毫无疑问,很多功能正变得在浏览器中交互,因为它非常动态,并且工作起来更像桌面应用程序。

jQuery的诞生与理念

现在,或者如果你还没有,请观看John Resig的视频访谈。我提供这些访谈是因为我希望你理解创造jQuery背后的理念。是的,jQuery是一段代码,但它也是John Resig个性的表达。John Resig是一位非常聪明的计算机科学家。

在2005年,他厌倦了使用JavaScript构建交互式Web应用,也厌倦了处理非可移植性的代码。同时,他也看到了JavaScript本身的优雅之处,以及JavaScript面向对象模式的工作原理。他认为JavaScript并不是一门糟糕的语言,实际上它是一门令人印象深刻的语言。

作为一名年轻的本科生计算机科学家,他认为这是一个可以进行体面工作的、整洁的计算机科学领域。JavaScript不再仅仅被认为是做一些弹出窗口、滚动等烦人事情的“垃圾”语言。在2005、2006、2007年这段时间,JavaScript在浏览器中逐渐成熟,成为一个有才华的计算机科学家愿意工作的体面领域。

如今,十年过去了,我们看到像Angular和React这样的东西,JavaScript真正成为了焦点,并且通过Node.js等技术,JavaScript也正在接管服务器端。

jQuery解决的核心问题

John Resig思考的是:我们在浏览器中经常需要做哪些事情?一是需要在DOM中找到东西并对其进行操作,比如改变颜色、隐藏或显示等。另一件事是处理事件,也就是我们目前看到的如onclickonchange之类的事件。

像点击这样简单的事情工作得很好,但像“知道文档何时完全加载”以及“所有图像何时完成加载”这样的事情,结果在每个浏览器中都非常不同。因此,jQuery也对此进行了标准化,确保你知道文档何时加载到足以运行JavaScript来开始操作DOM。你希望DOM是完整的,不希望JavaScript去寻找那些尚未完全存在的东西。

jQuery于2006年发布,大约是JavaScript创建10年后。John目前(指课程录制时)在可汗学院工作,他和我一样,是让每个人学习编程的大力支持者,也支持将编程教学下放到高中阶段。他是一位很棒的人,他的访谈很有趣,我鼓励你去看看。

jQuery的成功因素

本节课不会详细列出jQuery的所有功能和细节。John谈到,在2005、2006年,jQuery还有其他竞争对手,比如Prototype和MooTools,它们就像不同的“宗教”,而jQuery只是其中之一。但jQuery在很短的时间内就胜出了。

John认为,并且我也同意,jQuery在05、06年获胜的原因是它拥有更好的文档。他花了很多时间来记录他所做的工作,不仅仅是编写出色的代码并让自己能够使用它,更是为了让其他人也能使用它。

实际上,你会说类似这样的话:“如何使用jQuery让图像从左到右滑动出现?”你输入这个问题,然后就会在Stack Overflow或jQuery页面上找到答案。jQuery的文档非常出色,既全面,又提供了几乎可以满足你需求的、只有几行代码的小片段。

你只需将问题输入搜索引擎,然后去Stack Overflow或jQuery官网,复制粘贴五行代码,放到你的项目中,它就开始工作了。然后你修改其中的两行,就能完全实现你想要的效果。因此,我在这节课中真的只打算讨论jQuery所做的最基本的事情,因为你知道可以自己查找其他内容。

加载jQuery库

以下是第一个jQuery示例。我们需要做的第一件事是加载jQuery库。

这只是一个文件。你需要把这个文件放在某个地方。实际上,你可以从互联网上加载它。谷歌托管了它的副本。有一些叫做CDN(内容分发网络)的东西,它们提供无限、超快的带宽。所以,如果你不想从自己的服务器加载,你甚至不必这样做。

如果你查看jQuery文档,他们会提供几种将jQuery引入你的应用程序的版本,并且有不同的jQuery版本和插件。但现在,我们只是从一个文件中的jQuery副本开始。我们直接把它放在文件里。所以,文件中有一个jQuery版本。我们只需加载它。

包含像这样的东西,它可能不应该产生任何输出。它只是定义函数、函数、函数,或者可能创建一些类,但没有对象,也没有输出。这就是它的作用:它只是定义jQuery。

jQuery的核心:$ 对象

jQuery最有趣的一点是,它做的最重要的事情是定义一个名为美元符号($的全局对象。

当你查看jQuery并看它的代码时,你会看到这种$(...)的写法。大多数人看到这个会说:“哦,这就像是JavaScript的一种魔法。”但事实并非如此。

它实际上是一个名为$的函数/对象。如果你回溯到JavaScript的基础,你会说变量名可以是字母,可以以大写或小写字母、下划线或美元符号开头。我之前说过,美元符号有点俗气,因为它让我们想起Perl和Bash等其他脚本语言。

在JavaScript的头十年里,没有人使用任何带美元符号的东西。然后John Resig出现了,他想:“我应该给我的对象起什么名字呢?哇,没人用美元符号。”结果大约在那时,人们开始意识到这一点,并且还有其他一些东西也开始使用美元符号。

$被使用了,所以它看起来像语言语法,但实际上不是,它只是一个对象的名称。现在我们用括号调用那个对象,并传入一个参数。这帮助我们理解。

我必须承认,当我第一次看jQuery时,我觉得它就像一堆花括号、分号和括号,就像有人扔了一堆标点符号,发生了一场车祸,一盒标点符号撒得到处都是,然后你把它捡起来。这就是jQuery看起来的样子。但当你编写这些东西时,你必须让它工作,所以让我们快速理解这里发生了什么。

理解 $(document).ready()

$是一个对象的名称。我们用一个参数调用这个对象。
这个参数是documentdocument是JavaScript的概念,是文档对象模型,在所有在浏览器中运行的JavaScript中预定义。所以,传入document。这是浏览器中已经存在的预定义对象之一。

我们这样做会返回代码。记住,函数是一等公民。
我们将在那个调用中调用ready方法。
function(){}ready方法。
ready方法有一个参数,我们称之为ready。我们可以叫它x,但我们不这么叫。
我们仍然传递一个参数,但我们传递的是代码。因为在JavaScript中,由于函数是一等公民,可以传递代码。
所以,function(){ ... }这部分实际上是一个常量?它被传递给了ready
我们应该传递给ready的是我们想要执行的代码。
所以这是延迟执行。这段代码直到jQuery确定文档完全加载并准备好被查看时才会执行。
因此,它会在且仅会在那时调用我们的代码。这是在注册一个事件。
事件是“当文档准备就绪时”。

所以我们注册事件,然后我们必须放入要运行的代码。我们传递代码。
在这一点上,当代码运行时,jQuery只是保留这段代码并记住它。
它稍后运行,在<body>完成且其他东西加载完毕之后的某个时间。然后它运行我们的代码。
所以这是延迟执行。这是一个事件
这是另一种说法,延迟执行、事件,它也是异步的
你无法确切知道代码何时运行。
jQuery知道你的代码应该运行的时刻。
你已经交给了它你想要在此事件发生时运行的代码。
这是一等公民函数。如果你理解了它,它绝对是美妙的。
你可以看到为什么像Resig这样早期构建jQuery的人会说:“哦,等等,这是一门美妙的语言。”一等公民函数使这与您体验过的任何其他编程语言都不同。就像我之前说的,Smalltalk、Lisp和其他语言也有这个概念,但JavaScript是第一个让一等公民函数如此直接可用的语言。

基本语法模式

现在,让我解释一下这是做什么的。这就是语法。
但过一段时间,你会进行复制粘贴。你会说:“哦,复制粘贴,复制粘贴。”你会停止思考它。你只会考虑这里的这部分代码。
所以在某种程度上,这变成了我们一遍又一遍重复的神奇惯用语。
我们加载jQuery库。
$(document).ready(function(){ ... });这一行就像一个惯用语,表示“当文档准备就绪时,运行这段代码”。这也是一个完成语句的惯用语,包括分号等。
我们告诉jQuery的是:在<body>完成且其他事情结束后,来运行我们的代码。这就是我们的要求。
当它发生时,我们将运行一个alert,我们将执行一个console.log,这样我们可以看到发生了什么。如果你运行这个,你会看到它发生。

处理窗口调整大小事件

下一个概念,jQuery做的两件事之一是设置事件。我们看到的第一个是设置一个事件。
在这个例子中,我们将要求每当窗口调整大小时被调用。
同样,我们加载jQuery库,但这次我们传递window

让我试着画一下这个图。window是你看到的部分,而文档对象模型(DOM)是所有内容。如果你有滚动条,窗口只看到DOM的一部分。你向上或向下滚动,可能看到这部分,可能看到下面的那部分。所以,并非所有DOM在任何给定时间都显示在窗口中。
window是你可以看到的部分。DOM是所有网页内容。
DOM的一部分显示在窗口中,但它不是窗口本身。所以DOM没有宽度和高度,因为窗口是你正在看的东西。DOM是整个背后的网页。
DOM的一部分正显示在窗口中,但它不是那个东西。所以文档没有宽度或高度。因此,你必须说:“嘿,我现在所在的窗口有多宽?”
所以我们将window传递给jQuery,我们有一组不同的事件可以接入。我们说:“好的,我希望每当这个窗口调整大小时调用我。”调整大小意味着你抓住角落把它变大或变小。当窗口调整大小时,调用我的代码。
所以这是一种惯用的方式,表示我有几行代码,希望在每次窗口调整大小时都运行。

我们将要运行的代码是这段代码。我们将在控制台输出“It has been resized”,然后获取窗口宽度。window.width()是jQuery的window对象中的一个方法,告诉你它有多宽。
现在我们查看那个窗口,我们看到宽度,我们看到高度。
事实证明,如果不使用jQuery,计算这个是很困难的。去Google或Stack Overflow搜索“如何在不使用jQuery的情况下找到窗口宽度”,他们会给你一大堆代码,并且会问:“你想让它工作在哪些浏览器上?IE7?等等等等。这是你在IE7中写的,这是你在某某浏览器中写的。”你会说:“不,别那样做。用jQuery的window.width()。”它准确地告诉你像素数。window.height()也准确地告诉你像素数。
通常你不会打印这个,你可能需要调整DOM中的一些内容,这样如果你把它变小,你可能想隐藏一列之类的,你在这段代码中做类似的事情。
但这是接入文档对象模型和窗口事件结构的另一个例子,意思是“在调整窗口大小时回调我”。这基本上是说,当他们调整窗口大小时回调我,并在他们调整窗口大小时运行这段代码。

这部分和之前的部分都只是你可以从一个地方复制粘贴到另一个地方的惯用东西,尽管你应该知道语法的真正含义。

操作DOM元素

除了接入事件,你能做的另一件事是更改文档对象模型(DOM)。如果你回顾前几节课,我使用了getElementById并更改了DOM,这基本上是做同样的事情。

这是一个例子,比如那些你经常看到的旋转的小动画指示器(spinner),表示正在等待。我们将向你展示如何隐藏和显示这些东西。
我们放置一个<img>标签,并给它一个ID。记住id=,每个文档中只有一个。我们将有spinner.gif,设置高度和宽度。
然后我们说display: none。这意味着默认情况下,这个spinner在页面上,但它是隐藏的。
然后我们将有一个小的切换按钮。
我们将有一个事件,一个onclick。有一种在jQuery中注册事件的方法我没有展示,这是一种老式的onclick,但没关系,因为每当我们点击“toggle”时,这段JavaScript将运行。所以它将是jQuery。
我们说$("#spinner"),而不是发送整个document或整个window"#spinner"表示:去查找ID为spinner的元素,这会抓取整个标签。
然后这是一个jQuery函数,叫做toggletoggle的作用是查看display: none。它检查它。如果当前不是display: none,它就改变它。如果正在显示,它就设置为display: none。所以它是切换状态。如果你在检查元素中观察这个,你会注意到这个在变化。你不需要记住,也不需要写一个变量来弄清楚,你只是说,显示它或不显示它。还有.show().hide().hide()如果你想隐藏它,.show()显示它,你可以这样做。
所以这是一种抓取DOM一部分的方法,然后你可以对该部分执行某些方法。你需要去阅读jQuery文档来查看所有方法。

更改元素样式

我们也可以抓取某些东西并改变它的颜色。
这里我们将有一个叫做“red”的小按钮。我们将抓取这个段落,因为我们要说$("#paragraph"),意思是“给我这个段落”。
这个段落没有CSS,没有其他CSS。
然后一旦我们抓取那个段落,$("#paragraph").css()。这是一个jQuery方法css。我们传入一个CSS变量background-color,并将其设置为red
这意味着无论这个段落是什么,它将有一个红色的背景色。每次你点击那个按钮。
类似地,如果你点击这个“green”按钮,它将去抓取那个,将背景色设置为绿色。所以你点红、绿、红、绿,这个小段落将在红色和绿色之间来回切换。

这基本上就是jQuery如何工作来抓取东西并操作它们,同样,这只是非常基础的开端。

下一步:表单与服务器通信

下一个jQuery示例我们将展示如何实际查看表单、自动提交表单以及在jQuery中与服务器通信。

130:jQuery基础详解

在本节课中,我们将学习jQuery库的核心概念,了解它如何简化JavaScript编程,并掌握其基本语法和常用模式。我们将从jQuery的引入开始,逐步讲解如何响应页面事件、操作DOM元素以及修改CSS样式。

概述

jQuery是一个广泛使用的JavaScript库,它解决了不同浏览器之间JavaScript和文档对象模型(DOM)的兼容性问题。jQuery通过提供简洁、一致的API,使得开发者能够更轻松地编写跨浏览器兼容的代码。本节课将介绍jQuery的基本用法,包括如何引入库、响应页面加载和窗口调整事件,以及如何查询和操作DOM元素。

jQuery的引入与$(document).ready()

我们首先讨论如何在HTML文档中引入jQuery库,并确保我们的代码在页面完全加载后执行。

在HTML文档的<head>部分,我们引入jQuery库。有些开发者倾向于将脚本放在<body>标签的末尾,但这里我们采用放在<head>中的方式。引入后,jQuery库会扩展DOM的功能,并定义一个名为$的全局函数。

$是一个函数,$(document)是其调用方式之一,它返回一个包含多种方法的jQuery对象。.ready()就是其中一个方法。这种链式调用语法是jQuery的特色。

$(document).ready(function() {
    alert("Hello dophP");
    console.log("Hello jQuery");
});

这段代码的核心含义是:当文档(包括所有图片)完全加载就绪时,执行我们提供的函数。函数内部首先弹出一个警告框,然后在浏览器控制台输出一条日志信息。这是一种常见模式,用于在页面加载完成后“注入”交互功能。

让我们运行这段代码。页面加载时,会先获取HTML,然后加载jQuery库,最后在文档就绪时触发我们的函数,弹出警告并打印日志。$(document).ready()确保了我们的代码在DOM完全构建后才执行,这非常重要,因为过早操作DOM元素可能会失败。

响应窗口调整事件:$(window).resize()

上一节我们介绍了如何在页面加载后执行代码。本节中,我们来看看如何响应浏览器窗口大小调整这类动态事件。

除了文档就绪事件,jQuery还可以让我们轻松响应其他事件,例如窗口调整大小。$(window)返回一个代表浏览器窗口的jQuery对象,其.resize()方法用于注册窗口调整大小事件的处理器。

$(window).resize(function() {
    console.log("Window resized to: " + $(window).width() + "x" + $(window).height());
});

这段代码注册了一个事件处理器。每当用户调整浏览器窗口大小时,括号内的函数就会被调用。在函数内部,我们向控制台打印了当前窗口的宽度和高度。在实际应用中,你可能会根据新的窗口尺寸来重新布局页面元素、显示或隐藏某些内容。

运行代码后,当你拖动浏览器窗口边缘改变其大小时,控制台会不断输出新的窗口尺寸信息。这演示了jQuery如何让我们“接入”到各种浏览器事件中。

查询与操作DOM元素:jQuery选择器与方法

我们已经学会了响应事件,现在来看看jQuery的核心功能:查询DOM元素并对其进行操作。这通常被称为“查询-执行”模式。

jQuery使用CSS选择器语法来查找DOM元素。$("#spinner")会查找idspinner的元素。找到元素后,我们可以调用jQuery方法对其进行操作,例如.toggle()用于切换元素的显示/隐藏状态,.css()用于修改元素的CSS样式。

以下是几个操作示例:

  • 切换显示/隐藏$("#spinner").toggle(); 会切换ID为spinner的元素的可见性。
  • 修改CSS样式$("p").css("background-color", "red"); 会将所有<p>段落标签的背景色改为红色。

让我们看一个综合例子。假设页面上有一个段落,里面包含一个初始隐藏的图片(spinner),以及几个按钮:

<p id="mypara">这是一个段落。<img id="spinner" src="spinner.gif" style="display:none;"></p>
<a href="#" onclick="$('#spinner').toggle(); return false;">切换Spinner</a>
<a href="#" onclick="$('#mypara').css('background-color', 'red'); return false;">红色背景</a>
<a href="#" onclick="$('#mypara').css('background-color', 'green'); return false;">绿色背景</a>
  • 点击“切换Spinner”按钮,会显示或隐藏spinner图片(通过切换displaynone属性)。
  • 点击“红色背景”或“绿色背景”按钮,会改变整个段落的背景颜色。

所有这些操作都在浏览器端瞬间完成,没有发起新的网络请求(请求-响应周期),也没有改变原始的HTML DOM结构,只是动态修改了元素的CSS属性。jQuery提供了大量类似的方法,如.animate()用于动画,.fadeIn()/.fadeOut()用于淡入淡出效果等,具体需要查阅jQuery的官方文档。

总结

本节课中我们一起学习了jQuery的基础知识。我们了解了jQuery如何作为一个强大的工具来解决浏览器兼容性问题。我们掌握了引入jQuery库的方法,以及使用$(document).ready()来确保代码在页面加载后执行。我们还学习了如何用$(window).resize()来响应窗口调整事件。最重要的是,我们理解了jQuery的“查询-执行”范式,即使用$()配合选择器来查找元素,然后调用.toggle().css()等方法对其进行操作。jQuery的官方文档非常出色,包含了大量可即取即用的代码片段,是进一步学习的最佳资源。

131: 表单与jQuery 🧩

在本节课中,我们将学习如何使用jQuery处理表单,并实现无需刷新页面即可与服务器通信的功能。我们将通过一个具体的例子,演示如何捕获表单变化、向服务器发送数据、处理响应并动态更新页面内容。


概述

到目前为止,我们主要使用jQuery与文档对象模型(DOM)进行交互,例如注册事件、修改元素样式等。本节我们将迈出重要一步:让jQuery不仅能操作DOM,还能与服务器进行通信。这意味着jQuery可以主动发起请求-响应循环,而不仅仅依赖用户点击链接或提交表单。这种技术是实现现代网页动态交互(如实时消息通知)的基础。

静态HTML表单结构

首先,我们来看一个简单的HTML表单。这个表单没有传统的提交按钮,其交互将由jQuery控制。

<form id="target">
    <input type="text" name="one" value="hello there">
    <img id="spinner" src="spinner.gif" style="display: none;">
</form>
<div id="result"></div>
  • 表单 (#target):包含一个文本输入框,其初始值为“hello there”。
  • 加载动画 (#spinner):一个GIF动画图片,初始状态为隐藏(display: none)。它将在与服务器通信时显示,以提示用户等待。
  • 结果容器 (#result):一个空的<div>,用于接收并显示从服务器返回的数据。

jQuery事件处理与异步编程

上一节我们介绍了静态页面结构,本节中我们来看看如何用jQuery为它添加动态行为。以下是实现交互的核心JavaScript代码。

$('#target').change(function(event) {
    // 1. 显示加载动画
    $('#spinner').show();

    // 2. 获取表单输入框中的值
    var form = $('#target');
    var text = form.find('[name=one]').val();
    console.log('Sending:', text);

    // 3. 向服务器发送POST请求
    $.post('server_script.php', { val: text })
        .done(function(data) {
            // 请求成功时执行
            console.log('Response:', data);
            $('#result').empty().append(data);
            $('#spinner').hide();
        })
        .fail(function() {
            // 请求失败时执行
            console.log('*** Error ***');
            $('#result').html('<b>Failed</b>').css('color', 'red');
            alert('Failed to contact server');
            $('#spinner').hide();
        });
});

以下是这段代码执行流程的详细分解:

1. 注册change事件

代码 $('#target').change(function(event) { ... }); 为ID为target的表单注册了一个事件处理器。这意味着花括号 { ... } 内的代码不会立即执行,而是被“挂起”,等待特定事件发生。

  • 触发条件:当用户在文本输入框中修改内容并移开焦点(例如点击其他地方或按Tab键)时,会触发change事件。
  • 异步特性:这是一种异步、基于事件的编程模式。注册事件的代码会立刻执行,但事件处理器内部的代码则要等到事件触发时才会运行。

2. 事件触发后的操作

change事件被触发后,处理器内的代码开始按顺序执行:

  • 显示加载动画$('#spinner').show(); 让之前隐藏的旋转动画显示出来,给用户一个“正在处理”的视觉反馈。
  • 提取表单数据:通过 $('#target').find('[name=one]').val() 这行jQuery代码,我们定位到表单中name属性为one的输入框,并获取其当前的值(例如用户输入的“X”),存储在变量text中。

3. 发起异步POST请求

接下来,我们使用 $.post() 方法向服务器发送数据。这是整个流程的核心。

  • 方法调用$.post('server_script.php', { val: text })
    • 第一个参数是服务器端脚本的URL(例如 server_script.php)。
    • 第二个参数是要发送的数据,这里是一个对象 { val: text },意味着将表单值以 val 为键发送给服务器。
  • 处理响应$.post() 方法会立即返回,浏览器不会等待服务器响应。我们通过 .done().fail() 方法链式调用来定义服务器响应返回后应该执行的代码。
    • .done(function(data) { ... }):当服务器成功响应(例如HTTP状态码200)时,这里的回调函数会被执行。参数 data 包含了服务器返回的内容。在这个函数里,我们:
      1. 将返回的数据 data 添加到 #result 容器中。
      2. 隐藏加载动画 #spinner
    • .fail(function() { ... }):如果请求失败(例如遇到404或500错误),则执行这里的回调函数。通常我们会在这里进行错误处理,例如显示错误信息。

关键理解$.post() 本身是异步操作。我们在一个异步的change事件处理器中,又发起了一个异步的网络请求。服务器响应可能需要时间(示例中故意等待了5秒),在此期间,浏览器可以自由处理其他任务。当响应返回时,jQuery会自动调用对应的 .done().fail() 回调函数。这就是实现页面无刷新更新的机制。

服务器端脚本示例

为了配合前端,我们需要一个简单的服务器端脚本(例如PHP)来接收和处理数据。以下是一个示例:

<?php
// server_script.php
sleep(5); // 模拟长时间处理,让前端 spinner 保持显示
$receivedValue = $_POST['val'] ?? 'Nothing received';
echo "You sent: " . htmlspecialchars($receivedValue) . ". Server processed successfully.";
?>

这个脚本会休眠5秒来模拟处理耗时,然后回显接收到的数据。

总结

本节课中我们一起学习了jQuery处理表单并与服务器通信的完整流程。

  1. 事件驱动:我们使用 .change() 方法监听表单变化,这是与用户交互的起点。
  2. 异步通信:通过 $.post() 方法,我们可以在不刷新页面的情况下向服务器发送请求并接收数据。
  3. 链式响应处理:利用 .done().fail() 方法,我们优雅地处理了请求成功和失败两种场景。
  4. 动态更新DOM:根据服务器的响应,我们使用 .append().html() 等方法动态更新页面内容,并控制加载动画的显示与隐藏。

这种“前端jQuery捕获事件 -> 异步发送数据到服务器 -> 接收响应 -> 局部更新DOM”的模式,是现代Web应用实现丰富、流畅交互的基石。掌握了这个模式,你就具备了构建像实时消息提示、动态内容加载等高级功能的基础能力。

132:表单与jQuery代码详解

在本节课中,我们将学习一个结合了表单、PHP会话、Ajax和jQuery的异步聊天应用。我们将详细解析其代码结构和工作原理,理解如何在不刷新页面的情况下实现多窗口间的实时通信。

概述

我们将要分析的应用程序是一个简单的异步聊天室。它允许用户在多个浏览器窗口或标签页中发送消息,并利用Ajax和定时器自动更新所有窗口中的聊天内容,而无需手动刷新页面。这个应用巧妙地结合了PHP处理表单提交和会话管理,以及JavaScript/jQuery处理前端的异步数据获取和DOM更新。

代码结构与工作原理

上一节我们介绍了应用的基本功能,本节中我们来看看其背后的代码是如何组织的。

整个应用的核心逻辑分布在两个PHP文件中:index.php(主页面)和chatlist.php(数据接口)。

1. 表单处理与PHP会话

首先,我们来看index.php中处理表单提交和会话的部分。其核心是标准的请求-响应周期。

当用户提交表单(发送消息或重置聊天)时,页面会向自身(index.php)发起POST请求。服务器端的PHP代码会检查请求参数,并更新存储在$_SESSION中的聊天数据数组。

以下是处理逻辑的伪代码描述:

// 检查是否为重置操作
if (isset($_POST[‘reset’])) {
    // 清空会话中的聊天数组
    $_SESSION[‘chats’] = array();
    // 重定向回自身,使用GET请求刷新页面
    header(“Location: index.php”);
}
// 检查是否有新消息
elseif (isset($_POST[‘message’]) && strlen($_POST[‘message’]) > 0) {
    // 如果会话中没有聊天数组,则初始化一个空数组
    if (!isset($_SESSION[‘chats’])) {
        $_SESSION[‘chats’] = array();
    }
    // 将新消息和当前时间戳作为子数组,添加到聊天数组中
    $_SESSION[‘chats’][] = array($_POST[‘message’], date(DATE_RFC2822));
    // 重定向回自身,使用GET请求刷新页面
    header(“Location: index.php”);
}

核心概念:聊天数据被存储为一个PHP会话($_SESSION)中的二维数组。每个子数组包含两个元素:消息内容([0])和日期时间([1])。使用header(“Location: …”)进行重定向是一种防止表单重复提交的常见模式(Post/Redirect/Get)。

2. 前端页面与jQuery

处理完POST请求后,index.php会输出HTML页面。这个页面包含了聊天表单、一个用于显示消息的<div>,以及关键的JavaScript/jQuery代码。

以下是页面的关键部分:

<!-- 聊天表单,提交到自身 -->
<form method=“post”>
    <input type=“text” name=“message” size=“60”/>
    <input type=“submit” value=“Chat”/>
    <input type=“submit” name=“reset” value=“Reset”/>
</form>

<!-- 显示聊天内容的区域,初始时包含一个加载动画 -->
<div id=“chatcontent”>
    <img src=“spinner.gif” alt=“Loading…”/>
</div>

在页面底部,引入了jQuery库,并定义了一个名为updateMessages的JavaScript函数。

function updateMessages() {
    console.log(‘*’);
    // 发起Ajax请求获取最新的聊天列表
    $.ajax({
        url: ‘chatlist.php’,
        cache: false, // 禁止缓存,确保每次获取新数据
        dataType: ‘json’, // 期望返回JSON格式数据
        success: function(data) { // 请求成功后的回调函数
            console.log(data);
            // 清空聊天显示区域
            $(‘#chatcontent’).empty();
            // 遍历返回的JSON数据(一个数组)
            for (var i = 0; i < data.length; i++) {
                // data[i][0] 是消息, data[i][1] 是日期
                var msg = data[i][0];
                var date = data[i][1];
                // 为每条消息创建一个新的段落并添加到显示区域
                $(‘#chatcontent’).append(‘<p>’ + date + ‘: ‘ + msg + ‘</p>’);
            }
            // 设置一个4秒后的定时器,再次调用自己,实现轮询
            setTimeout(updateMessages, 4000);
        }
    });
}
// 页面加载完成后,立即调用一次该函数以获取初始数据
$(document).ready(function() {
    updateMessages();
});

核心概念

  • $.ajax:jQuery的Ajax方法,用于向服务器(chatlist.php)发起异步HTTP请求。
  • cache: false:通过添加一个基于时间戳的查询参数,强制浏览器跳过缓存,每次都从服务器获取新数据。
  • dataType: ‘json’:告诉jQuery将服务器响应解析为JSON对象。
  • success 回调函数:请求成功且数据解析完成后执行的函数。在这里,它用新数据更新DOM。
  • setTimeout:一个原生JavaScript函数,用于在指定的毫秒数后执行一段代码。这里它创建了一个每4秒轮询一次的循环。
  • DOM操作$(‘#chatcontent’).empty()清空元素内容,.append()向元素内添加新的HTML内容。

3. JSON数据接口 (chatlist.php)

updateMessages函数请求的chatlist.php文件是一个纯数据接口。它不返回HTML,而是返回JSON格式的聊天数据。

以下是chatlist.php的代码:

<?php
session_start(); // 启动会话,以访问$_SESSION
// 设置正确的HTTP头部,声明内容类型为JSON
header(‘Content-Type: application/json; charset=utf-8’);
// 模拟网络延迟,便于演示(实际应用应移除)
sleep(5);
// 检查会话中是否有聊天数据,没有则初始化为空数组
if (!isset($_SESSION[‘chats’])) {
    $_SESSION[‘chats’] = array();
}
// 使用json_encode将PHP数组转换为JSON字符串并输出
echo json_encode($_SESSION[‘chats’]);
?>

核心概念

  • header(‘Content-Type: application/json’):这是至关重要的HTTP头部。它告诉浏览器(以及jQuery)返回的内容是JSON格式,而不是默认的HTML。这是API设计的良好实践。
  • json_encode():PHP内置函数,将PHP变量(如数组、对象)转换为JSON格式的字符串。
  • sleep(5):此处仅用于演示,人为制造5秒延迟,让我们能在开发者工具中清晰地观察到请求和更新的过程。

应用流程总结

现在,让我们将以上所有部分串联起来,回顾整个应用的工作流程:

  1. 初始加载:用户访问index.php。页面加载jQuery,并立即执行updateMessages()函数。
  2. 首次数据获取updateMessages()chatlist.php发起Ajax请求。由于sleep(5),请求会等待5秒。
  3. 显示加载状态:在等待响应期间,#chatcontent div中显示着spinner.gif(加载动画)。
  4. 接收并渲染数据:5秒后,Ajax请求收到chatlist.php返回的JSON数据。success回调函数被触发:
    • 清空#chatcontent div(移除加载动画)。
    • 使用for循环遍历JSON数组。
    • 为每条消息生成一个<p>标签,并将其追加到#chatcontent div中。
  5. 建立轮询循环:渲染完成后,setTimeout(updateMessages, 4000)被调用,设定在4秒后再次执行updateMessages函数,从而开始下一次数据获取。这就建立了一个每4秒轮询一次的循环。
  6. 用户交互
    • 发送消息:用户在表单中输入文本并点击“Chat”。表单以POST方式提交到index.php。PHP代码将消息和日期存入$_SESSION[‘chats’]数组,然后重定向。页面刷新,重复步骤1。此时,轮询函数会在几秒后获取到包含新消息的列表并更新所有打开的窗口。
    • 重置聊天:用户点击“Reset”。表单提交一个reset参数。PHP代码清空$_SESSION[‘chats’]数组并重定向。随后的轮询会获取到空数组并清空聊天显示区域。

关键点与学习总结

本节课中我们一起学习了如何构建一个简单的异步Web应用。让我们总结一下其中的关键技术和设计模式:

  • 前后端分离(雏形)index.php负责呈现视图和处理表单动作,chatlist.php作为纯数据API(返回JSON),这是现代前后端分离架构的简单体现。
  • Ajax与轮询:使用jQuery的$.ajax进行异步通信,结合setTimeout实现定时轮询,是早期实现“实时”更新的典型方法(现代更常用WebSocket)。
  • 无刷新DOM更新:通过JavaScript/jQuery操作DOM(empty(), append()),可以在不重新加载整个页面的情况下更新部分内容,提升用户体验。
  • 会话状态管理:使用PHP的$_SESSION在服务器端维持聊天记录的状态,避免了使用数据库的复杂性。
  • 正确的HTTP头部:在API端点(chatlist.php)设置Content-Type: application/json是至关重要的,它确保了数据能被正确解析。

通过这个案例,你可以看到如何将PHP、JavaScript、jQuery和Ajax技术组合起来,创建一个具有动态交互功能的Web应用程序。虽然这是一个基础示例,但它涵盖了构建更复杂交互式应用的核心思想。

133:配置文件定位与jQuery代码详解 🧩

在本节课中,我们将详细讲解如何构建一个带有个人资料和职位信息的自动评分系统。课程的核心是学习如何使用jQuery动态地添加和删除表单字段,并理解如何将这些数据存储到数据库中,特别是处理“一对多”的关系。

概述

上一节我们完成了基本的用户登录和个人资料管理功能。本节中,我们将在此基础上,为个人资料添加多个职位信息。我们将使用jQuery来动态管理表单,并学习如何在数据库中建立“一对多”的关系。

代码结构与数据库设计

首先,我们需要在数据库中创建一个新的position表。这个表将与之前创建的profile表建立关联。

以下是创建position表的核心SQL概念:

CREATE TABLE position (
    position_id INTEGER NOT NULL AUTO_INCREMENT,
    profile_id INTEGER,
    year INTEGER,
    description TEXT,
    rank INTEGER,
    PRIMARY KEY(position_id),
    CONSTRAINT position_ibfk_1
        FOREIGN KEY (profile_id)
        REFERENCES profile (profile_id)
        ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这个表通过profile_id字段与profile表关联,一个个人资料可以对应多个职位,这就是“一对多”关系。

核心功能实现

1. 登录与基础框架

登录功能与上一节相同,它检查用户表,并使用SQL查询和密码哈希验证用户身份。为了代码复用和整洁,我们将一些常用功能(如显示提示信息)提取到了util.php文件中。

2. 添加职位功能 (add.php)

这是本课的重点。表单允许用户为一个个人资料添加多个职位。

以下是表单中动态添加职位字段的jQuery代码核心逻辑:

$(document).ready(function(){
    $('#addPos').click(function(event){
        event.preventDefault();
        if (countPos >= 9) {
            alert("Maximum of nine position entries exceeded");
            return;
        }
        countPos++;
        window.console && console.log("Adding position "+countPos);
        $('#position_fields').append(
            '<div id="position'+countPos+'"> \
            <p>Year: <input type="text" name="year'+countPos+'" value="" /> \
            <input type="button" value="-" \
                onclick="$(\'#position'+countPos+'\').remove();return false;"></p> \
            <textarea name="desc'+countPos+'" rows="8" cols="80"></textarea>\
            </div>');
    });
});
  • 功能:当用户点击“+”按钮时,这段代码会动态生成包含年份输入框、描述文本框和“-”删除按钮的HTML块,并将其插入到页面中。
  • 变量 countPos:用于跟踪已添加的职位字段数量,并限制最多添加9个。
  • 删除功能:每个动态添加的字段都自带一个“-”按钮,其onclick事件会移除对应的整个字段div

3. 数据验证 (util.php)

提交表单时,我们需要验证所有动态添加的职位字段。

以下是验证职位数据的PHP函数核心逻辑:

function validatePos() {
    for($i=1; $i<=9; $i++) {
        if ( ! isset($_POST['year'.$i]) ) continue;
        if ( ! isset($_POST['desc'.$i]) ) continue;
        $year = $_POST['year'.$i];
        $desc = $_POST['desc'.$i];
        if ( strlen($year) == 0 || strlen($desc) == 0 ) {
            return "All fields are required";
        }
        if ( ! is_numeric($year) ) {
            return "Position year must be numeric";
        }
    }
    return true;
}
  • 循环检查:函数循环检查可能存在的year1year9以及对应的desc1desc9字段。
  • 验证逻辑:检查字段是否存在、是否为空,以及年份是否为数字。
  • 返回值:验证通过返回true,失败则返回错误信息字符串。这种利用PHP动态类型返回混合结果的方式很常见。

4. 数据存储逻辑

当验证通过后,代码会执行以下操作:

  1. 首先插入profile表的数据,并获取自动生成的主键profile_id
  2. 然后循环处理每个有效的职位字段,将其插入position表。关键点:在插入职位记录时,需要将上一步获取的profile_id作为外键存入,从而建立关联。
  3. rank字段用于确保职位在显示时保持用户添加的顺序。

5. 编辑功能 (edit.php)

编辑功能比添加更复杂一些,因为它需要预先加载已有的职位数据。

  • 加载现有数据:通过loadPos($pdo, $profile_id)函数从数据库读取指定个人资料的所有职位,并在表单中动态生成对应的HTML字段进行展示。
  • 保存策略:为了简化逻辑,编辑保存时采用了一种“先删除,后重新插入”的策略。即先删除该profile_id对应的所有旧职位记录,然后再将表单中当前所有的职位数据作为新记录插入。这样就能统一处理修改、删除和新增操作,代码可以复用添加时的插入逻辑。
  • 重要细节:在验证失败或处理完成后进行页面重定向时,必须将当前编辑的profile_id作为GET参数传递回去(例如Location: edit.php?profile_id=123),否则页面会丢失正在编辑的是哪条记录。

总结

本节课中我们一起学习了如何扩展个人资料系统以支持多个职位信息。我们掌握了使用jQuery动态操作DOM来增加表单交互性,深入理解了数据库“一对多”关系的实现方式,并通过PHP代码将前后端逻辑串联起来。关键点包括:动态字段的生成与删除、前后端结合的数据验证、利用外键建立数据关联,以及在编辑功能中采用简化逻辑的数据更新策略。通过本课,你构建的Web应用功能变得更加丰富和实用。

134:John Resig谈jQuery

概述

在本节特别内容中,我们将跟随jQuery的创始人John Resig,了解这一流行JavaScript库的诞生背景、设计理念和发展历程。这对于理解现代前端开发工具的演进非常有帮助。


我于2005年开始开发jQuery。在此之前,jQuery并不存在。它最初只是我编写的一系列工具集合。

这主要是因为我当时创建了许多网站和相关资源。我一直希望拥有一些特定的工具来简化我的开发工作。其中一部分原因是为了解决当时存在的浏览器兼容性问题,比如Internet Explorer和Firefox之间的差异。

同时针对所有这些不同的浏览器进行开发非常困难,这是一个主要问题。

另一个问题是,我觉得当时已有的开发工具,尤其是JavaScript开发工具,可以做得更好。

当时最流行的库是Prototype JavaScript库。这个库与Ruby on Rails框架捆绑在一起,因此随着Rails的流行而迅速普及。

我发现那个库极具启发性。那是我第一次看到一个以优美、清晰、面向对象方式编写的JavaScript库,其中内置了许多优秀的功能范式。

我那时并未意识到JavaScript可以如此优美和优雅。看到Prototype后,它激励我想构建更好的东西。我意识到Prototype主要关注JavaScript语言本身,而很少有工具专门关注浏览器环境中的JavaScript,特别是操作HTML和DOM。

这里存在一个空白,一个巨大的可用性鸿沟。

因此,我开始构建不同的工具和库。最终,所有这些逐渐融合成一个单一的库,我将其命名为jQuery。

我原本打算叫它JSelect,但那个域名已被占用,所以我最终选择了jQuery。

我于2006年1月发布了它。需要说明的是,当时我还在上大学。我在大学期间从事所有这些工作,这些不同的项目只是我进行的各种副业,jQuery正是从这些副业中衍生出来的。

可以说,那些副业项目都已不复存在,只有jQuery留存至今。

选择器模式至少来源于一位英国开发者Simon Willison编写的库。他创建了一个名为getElementsBySelector的方法,它允许你编写一个简单的CSS选择器来查找元素。

但它非常原始,只能进行最基本的查询,不支持完整的CSS2和CSS3等功能。

因此,我想要的东西之一是那个库的更好、更全面的版本。

此外,我还想优化在页面中附加事件的过程。因为当你构建交互式JavaScript应用程序时,需要监听用户执行的某些操作。所以,查找元素并为其附加事件这个过程,我想彻底优化。

这确实是jQuery最初的核心。直到后来,我才开始添加其他功能,比如动画。甚至在我发布之后,我才添加了Ajax等功能,那是因为其他人需要它,我个人当时并不使用。

它确实受到了其他开发者及其库的启发。我只是觉得它们还不够完美,我想把它们整合得更好一点。

那么,它是如何从我制作并分享的东西,演变成具有自己生命力的项目呢?因为它很快就独立发展起来了。

感觉它花了很长时间才获得自己的生命力。我在2006年1月发布了它。

从一开始,我就做了几个设计决策,这些决策帮助很大。

一个是我提供了明确的插件架构,以便人们可以编写插件并将其添加到jQuery库中,从而充分利用这个框架的优势。

另一个决策是,在发布的第一天,我就编写了文档。我坐下来,仔细研究了每个方法,记录了它的工作原理,并提供了小例子。

我认为有趣的一点是,从2006年1月我们发布这个库,到2007年1月,jQuery是唯一一个有文档的JavaScript库。其他库都只是说“阅读源代码”或“查看版本控制记录”之类的。这总是让我很惊讶。我想这只是开发者的一个副作用:你想写代码,不想做写文档这类枯燥的工作。

至少就库的发展而言,我做了很多决策。

在其历史进程中,许多决策与代码无关。我一直试图强调的一点是,当你试图管理一个好的项目,尤其是一个好的开源项目时,代码只是整个方程中很小的一部分。

你必须花费大量时间和精力去创造一些人们愿意学习、易于学习的东西,并且确保他们学会后不会因为受挫而离开。你必须确保在每一步,人们都感到满意、快乐,并且在学习。这通常需要做一些事情,比如确保你有一个清晰的网站,方便下载你提供的东西,确保文档非常清晰,有一个非常好的入门指南,此外还要围绕它建立一个社区。

我邀请加入jQuery团队的第一个人,或者说,当时只有我,下一个我邀请的人实际上是来帮助管理社区的,而不仅仅是另一个开发者(尽管他本身也是开发者)。我这样做的原因是,我想确保如果有人遇到任何问题,他们的需求都能得到考虑,并且我们能够帮助解决他们遇到的任何问题。因此,我们在修复浏览器问题、发现库中的一般性问题方面非常积极主动。

从2006年夏天到2006年底,我实际上在Y Combinator,这是Paul Graham在波士顿运营的创业加速器。当时我和一些朋友搬到了波士顿,我们尝试创业,但最终失败了。我们没有获得足够的资金,创业项目没有成功。之后,我加入了Mozilla。

在那里,我主要担任了多年的JavaScript布道师。我的工作是推广JavaScript,让人们了解规范中即将加入的内容、工具等等。但同样,我并不是在工作时间内开发jQuery。我仍然是在空闲时间处理邮件列表、修复bug以及所有这类必须做的事情。

直到大约我在Mozilla的最后一年,也就是2010年到2011年左右,他们才说:“好吧,你可以全职从事jQuery工作。”于是我开始全职投入其中。这很棒,因为在那段时间里,我投入了大量精力来确保基础设施到位,这样即使我不再每天参与工作,它也能继续存在。

其中一部分工作是建立一个非营利组织,并确保有足够的人员负责它的各个方面。这样,当我最终加入可汗学院时,我实际上从jQuery项目中退了下来。自那以后,一切运行都非常顺利,我现在可以作为一个快乐的用户来使用jQuery了。


总结

本节课中,我们一起学习了jQuery创始人John Resig分享的库诞生故事。我们了解到jQuery的初衷是为了解决浏览器兼容性问题、优化DOM操作和事件处理,并受到Prototype等早期库的启发。它的成功不仅源于优秀的代码,更得益于清晰的文档、插件架构、积极的社区管理以及完善的项目基础设施。这段历史揭示了成功开源项目在技术之外所需的关键要素。

135:巴塞罗那

在本节课中,我们将回顾密歇根大学课程在巴塞罗那举行的一次附加办公时间。本次办公时间旨在与学生面对面交流,分享学习体验。

课程概述

这是课程在巴塞罗那举行的第二次办公时间。第一次办公时间的视频不慎丢失,但讲师有幸在另一天再次举办,因此有了这次会面。部分学生参加了两次活动,同时也有许多新同学加入。

与会学生介绍

以下是参加本次办公时间的部分学生自我介绍。

  • Ia:计划开始学习互联网历史课程。
  • el:正在学习Python,并已完成专项课程。
  • Christina:一名语言学家,正在学习Python。
  • co:正在学习Python。
  • Marta:正在学习Python专项课程,认为它非常有趣并大力推荐。
  • Sean:有幸见到讲师,并对大家能拥有这样有趣的师生交流表示高兴。
  • Teresa:正在享受Python课程,认为可以从中学习很多。

活动总结

本次巴塞罗那办公时间圆满成功,讲师与优秀的学生们进行了愉快的交流。我们期待下一次会面。


本节课中我们一起回顾了一次在巴塞罗那举行的课程办公时间,通过学生的自我介绍,我们感受到了共同学习的社区氛围。

136:JavaScript对象表示法(JSON) 📚

在本节课中,我们将要学习JavaScript对象表示法,即JSON。这是一种在服务器和浏览器之间传输数据的通用格式。我们将了解它的重要性、工作原理以及如何在PHP和JavaScript中使用它。

概述

在之前的课程中,我们学习了浏览器中的JavaScript如何与DOM交互,以及如何通过发送POST数据与服务器进行通信。之前我们发送和接收的是HTML。然而,在实际应用中,尤其是在需要与数据库交互并获取记录时,我们更希望发送和接收的是数据本身。JSON正是为此目的而设计的通用数据格式。

请求-响应循环与数据格式

上一节我们介绍了浏览器与服务器之间的基本通信。本节中我们来看看数据传输格式的演变。

早期的Web开发中,当JavaScript向服务器发起请求时,返回的数据格式主要是XML。这催生了所谓的“AJAX”模式。

AJAX 代表 Asynchronous JavaScript And XML。

然而,XML格式在处理上有时显得繁琐。因此,业界开始探讨更优的返回数据格式。问题的核心在于,服务器端可能运行着PHP、Python等语言,而浏览器端是JavaScript。每种语言对“键值对”等数据结构都有其内部表示方式。为了在互联网上传输数据,我们需要约定一种通用的“线格式”。

线格式 是一种已知的语法,它可以被转换成PHP数组、Python字典或JavaScript对象,也可以从这些数据结构转换而来。之所以称为“线格式”,是因为数据本质上是通过网络线缆(或光纤)传输的字符流。

什么是JSON?🔗

那么,什么是当前主流的线格式呢?答案在95%的情况下是JSON

JSON代表 JavaScript Object Notation。它的核心思想是利用JavaScript中定义对象和数组常量的语法,作为通用的数据交换格式。

这个过程涉及两个关键操作:

  • 序列化:将服务器内部的数据结构(如PHP数组)转换为JSON字符串(线格式)以便传输。
    // PHP 示例:序列化数组为JSON字符串
    $php_array = ["name" => "张三", "age" => 30];
    $json_string = json_encode($php_array); // 序列化
    
  • 反序列化:在接收端(如浏览器中的JavaScript)将接收到的JSON字符串解析为本地数据结构。
    // JavaScript 示例:将JSON字符串解析为对象
    var jsonString = '{"name":"张三","age":30}';
    var jsObject = JSON.parse(jsonString); // 反序列化
    console.log(jsObject.name); // 输出:张三
    

Douglas Crockford是JSON的推广者,他幽默地声称自己“发现”而非“发明”了JSON,因为它本就存在于JavaScript的语法中。他创建了json.org网站来规范这种格式,最终使其成为了分布式计算中不可或缺的基础设施。

JSON并非万能,对于复杂的层级结构(如Word文档),XML可能更合适。但对于在Web应用中传输数组或对象这类数据,JSON是绝佳的选择。

JSON的优势与特点

上一节我们了解了JSON的定义和基本概念。本节中我们来看看它为何如此受欢迎。

JSON在PHP和JavaScript中都得到了极佳的支持,使用起来非常简便。

以下是JSON的一些关键特点:

  • 语法简单:基于JavaScript对象和数组的字面量语法。
  • 易于读写:对人友好,同时也易于机器解析和生成。
  • 语言无关:虽然源自JavaScript,但已成为多种编程语言的通用标准。
  • 轻量级:相比XML,JSON的数据结构通常更简洁,冗余更少。

一个简单的JSON对象示例如下:

{
  "name": "李四",
  "age": 25,
  "isStudent": true,
  "courses": ["数学", "物理", "计算机"]
}

总结

本节课中我们一起学习了JavaScript对象表示法。我们了解了从XML到JSON的数据交换格式演变,理解了“线格式”和“序列化/反序列化”的核心概念。我们认识到JSON是一种利用JavaScript常量语法、轻量且通用的数据格式,它极大地简化了服务器与浏览器之间的数据通信。在接下来的课程中,我们将通过具体的示例代码,学习如何在PHP和jQuery中实际使用JSON进行数据交互。

137:JSON与jQuery 🚀

在本节课中,我们将学习JSON、jQuery和PHP如何协同工作,以实现浏览器与服务器之间的数据交换。整个过程看似简单,但其背后蕴含着强大的功能。

JavaScript对象语法回顾

上一节我们介绍了Web应用的基本概念,本节中我们来看看JavaScript中对象的两种访问方式。

在JavaScript中,我们使用对象。它们不完全是关联数组,而是键值对。它们可以通过关联数组语法或对象语法来访问。

以下是两种等价的语法:

x.bob // 对象语法
x["bob"] // 关联数组语法

第一种是面向对象的语法,第二种是看似数组的语法。本质上,后者是实际发生的情况,而前者是后者的快捷方式。在阅读代码时,有些程序员只使用其中一种语法,而另一些则使用另一种。我倾向于尽可能使用点格式,但有时必须使用双引号格式。请记住,JavaScript中的这两种语法含义完全相同,它们不是不同的东西,而是同一事物的两种表达。

JSON语法基础

上一节我们回顾了JavaScript对象,本节中我们来看看JSON的具体语法。

JSON本质上就是JavaScript对象语法。以下是一个创建对象的示例:

{
  "name": "Chuck",
  "age": 64,
  "retired": true,
  "offices": ["3357D", "123Main"],
  "skills": {
    "C++": true,
    "Python": true
  }
}

对象以花括号 {} 开始和结束。内部是键值对,键和值之间用冒号分隔。值可以是不同类型,不一定是数字,也可以是布尔值,甚至可以是一个列表。例如,属性 offices 映射到一个包含两个字符串的双元素列表。属性 skills 则映射到一个嵌套的对象。这些是键值对,而在 skills 内部又有另一个包含 C++Python 的对象。这就像在任何编程语言中构造对象一样。在PHP中,可以有数组中的数组,它们可以是线性数组或键值数组,但在这里,这是一个对象。

需要强调的是,对象和数组并不完全相同。上面示例中的 {} 定义了一个对象,而 [] 定义了一个数组,skills 内部又是一个对象。

从JavaScript到JSON

上一节我们了解了JSON的静态结构,本节中我们来看看如何在JavaScript代码中动态创建和使用它。

以下是一些直接运行此内容的JavaScript代码:

const who = { "all": "done" };

这只是一个可执行的JavaScript赋值语句。它恰好是JSON语法和JavaScript常量语法。这就像执行 who = 42; 一样,只是这里构造了一个带有键 all 和值 done 的对象,并将其赋值给 who。再次强调,这仍然只是语法。我们还没有将其变成网络传输协议,它仍然只是JavaScript常量语法。

实现网络传输协议

上一节我们看到了JSON在代码中的形态,本节中我们来看看如何将其作为数据在网络上传输。

这是一个重要的环节。如果幻灯片上有40行代码,你可能会觉得它复杂且充满挑战。但这里发生的事很神奇,而代码只有寥寥几行。我们将通过一个名为 json.php 的文件来演示。

首先,我们使用 sleep(2) 让程序稍微慢下来,以便在开发者控制台中观察发生的情况。当我们打算向浏览器发送JSON时,需要告诉浏览器内容类型。回想一下,内容类型可以是文本、HTML、JPEG或PNG。这是一个响应头,用于告知浏览器接下来发送的数据块是什么。我们告诉浏览器这是JSON,并指定为UTF-8编码,以支持亚洲字符等。

在PHP中,必须在产生任何输出之前设置所有头部信息,所以这行代码放在最前面。

PHP端的序列化

上一节我们设置了响应头,本节中我们来看看PHP端如何准备和发送JSON数据。

现在,我们有一些PHP代码。在实际应用中,这可能会从数据库读取数据,但我们现在先不关心这个。目前,我们只是创建一个数组。

$array = array("first" => "first thing", "second" => "second thing");

这是在PHP内部创建一个关联数组。然后,我们将进行序列化,接着发送这个网络传输协议,在JavaScript端接收并解析它。这正是我们现在要做的。

序列化PHP数组使用PHP内置的函数 json_encode。你传入一个数组,它可以是线性数组或键值数组,它会自动生成正确的JSON。在本例中,由于是键值数组,它会生成一个带有键值对的JSON对象。然后,echo 输出的就是这个序列化后的字符串。如果你直接访问这个PHP页面,在浏览器中看到的正是这个字符串。它可能看起来是压缩在一起的,但你可以对其进行“美化打印”,使其具有缩进,便于阅读。通常在传输时不会美化打印,因为数据主要是由代码解析,而不是由人阅读。我们往往在需要查看时,才复制粘贴并进行美化打印。

这段PHP代码运行后,表示“我必须发送一些JSON”,创建一个内部结构(这里可以有很多代码),将其序列化并发送回去。这就是PHP端的请求-响应周期。

JavaScript端的反序列化

上一节我们完成了PHP端的发送,本节中我们来看看JavaScript端如何接收和处理这些数据。

我们不会将其放在 onclick 方法中,而是直接展示其工作原理。假设我们有一个页面 index.php,里面有一些内容。我们使用 $(document).ready(),这是一个惯用写法,表示在文档加载完成后运行此代码。可以将其理解为在 </body></html> 标签之后执行。

然后,我们调用 $.getJSON。之前我们使用过 $.post,它发送POST数据并获取HTML返回。而 $.getJSON 将发起一个GET请求,并期望返回JSON。我们指定服务器URL,即运行在服务器上的 json.php 代码。$.getJSON 知道返回的是JSON,因此它会自动进行反序列化,然后将反序列化后的数据传递给我们的回调函数。

请记住,这是事件发生时运行的代码。它将传递反序列化后的数据,这是一个JavaScript对象,而不是字符串。所以,你需要区分网络传输协议(它只是一个字符串)和反序列化后的东西(它是一个活的JavaScript对象)。在本例中,data 就是一个活的JavaScript对象。因此,我可以打印出 data.first,它对应着PHP数组中 "first" 键的值。

这真是太神奇了。让我们梳理一下流程:我们在浏览器中,调用 $.getJSON,发送一个GET请求到 json.phpjson.php 可以做任何事情(例如与数据库对话),这里为了简单,它创建了一个结构,序列化后输出一个包含花括号等的字符串,这就是网络传输协议。时间在流逝,这是异步代码,它在等待响应返回。但在数据返回之前,它已经被反序列化了。所以,运行的回调函数接收到的 data 已经是反序列化后的对象。获取数据、等待返回、序列化、反序列化——序列化发生在PHP端,而反序列化则内置于 $.getJSON 中,它在数据返回给我们之前就完成了反序列化。

整个流程的代码量非常少。正如之前所说,这在10年或15年前可能需要数千行代码。这真的很简单。但我们必须理解这里的每一行代码,因为一旦掌握了这个,我们将构建更复杂的代码。所以不要想着以后再去弄明白,因为后续我们会发送更多代码,进行多次这样的操作,并与数据库交互等等。

构建一个简单的聊天工具

上一节我们完整地走通了数据交换的流程,本节中我们来看看如何将这些知识应用到一个简单的聊天工具中。

现在,让我们简单讨论一下如何将其转化为一个简单的聊天工具。其核心思想是,客户端定期或通过事件(如发送消息)向服务器请求新的聊天数据(JSON格式),服务器处理请求并返回包含消息列表的JSON,客户端再使用jQuery动态更新页面上的聊天内容。这避免了整个页面的刷新,实现了更流畅的用户体验。

本节课中我们一起学习了JSON、jQuery和PHP如何协同工作,实现了浏览器与服务器之间高效的数据交换。我们回顾了JavaScript对象的两种访问语法,了解了JSON的基本结构,掌握了在PHP中将数组序列化为JSON字符串并发送,以及在JavaScript中使用jQuery的 $.getJSON 接收并自动反序列化数据的过程。整个过程代码简洁,但功能强大,是现代Web应用实现动态内容加载的基础。

138:JavaScript对象表示法详解 🎯

在本节课中,我们将要学习JavaScript对象表示法,即JSON。JSON是一种轻量级的数据交换格式,它基于JavaScript的语法,但独立于语言,被广泛用于不同编程语言之间的数据传输。

概述 📋

JSON是一种用于序列化和传输数据的格式。它源自JavaScript定义常量的语法,但因其简洁和易读性,现已成为跨语言数据交换的通用标准。本节将详细介绍JSON的语法、在JavaScript中的使用,以及如何在PHP中生成JSON并通过jQuery在JavaScript中接收和解析它。

JSON语法基础

上一节我们介绍了JSON的概念,本节中我们来看看它的具体语法。JSON语法是JavaScript常量语法的一个简化子集。

JSON支持以下几种数据类型:

  • 字符串:用双引号包裹的文本。
  • 数字:整数或浮点数。
  • 布尔值truefalse
  • 数组:用方括号 [] 表示的有序值列表。
  • 对象:用花括号 {} 表示的无序键值对集合。
  • null:表示空值。

以下是JSON语法的核心结构:

对象(Object)

{
  "key1": "value1",
  "key2": 123,
  "key3": true
}

数组(Array)

["value1", "value2", "value3"]

JSON的强大之处在于可以任意嵌套这些结构。例如,一个对象的值可以是另一个对象或数组。

{
  "name": "张三",
  "skills": {
    "programming": ["PHP", "JavaScript"],
    "language": "中文"
  }
}

JavaScript中的对象与JSON

在深入使用JSON之前,理解JavaScript中对象的特性很重要。JavaScript中的对象本质上就是键值对的集合,这与许多其他语言中的“字典”或“关联数组”概念类似。

JavaScript提供了两种访问对象属性的语法,它们是等效的:

  • 点表示法object.key
  • 方括号表示法object[“key”]

例如:

let balls = { soccer: “round”, baseball: “hard” };
console.log(balls.soccer); // 输出: round
console.log(balls[“baseball”]); // 输出: hard

JSON语法正是受到了JavaScript中定义对象常量(即直接量)写法的启发。在JavaScript中,一段符合JSON语法的字符串可以直接被解析为对象。

在PHP中生成JSON

既然JSON用于跨语言通信,我们来看看如何在服务器端的PHP中生成JSON数据。PHP内置了强大的JSON支持。

以下是使用PHP生成JSON的步骤:

  1. 在PHP中创建一个数组(关联数组或索引数组)。
  2. 使用 json_encode() 函数将PHP数组转换为JSON格式的字符串。
  3. 输出(echo)这个字符串。

示例代码 (json.php):

<?php
// 创建一个PHP关联数组
$data = array(
    “first” => “first thing”,
    “second” => “second thing”
);
// 将数组编码为JSON字符串
$json_output = json_encode($data);
// 输出JSON字符串
echo $json_output;
?>

当访问这个PHP文件时,浏览器会收到一个纯文本响应:{“first”:”first thing”,”second”:”second thing”}。这就是标准的JSON数据。

使用jQuery获取并解析JSON

现在,我们有了一个能输出JSON的PHP接口。接下来,我们看看如何在客户端的JavaScript中,使用jQuery来获取这个JSON数据并自动将其解析为JavaScript对象。

以下是实现此功能的关键步骤:

  1. 使用 $.getJSON() 方法向服务器(我们的 json.php)发起GET请求。
  2. 该方法在收到响应后,会自动将返回的JSON字符串解析成JavaScript对象或数组。
  3. 在回调函数中,我们可以直接使用点表示法或方括号表示法来访问解析后的数据。

示例代码 (index.php 中的JavaScript部分):

$(document).ready(function(){
    // 发起GET请求获取JSON数据
    $.getJSON(‘json.php’, function(data) {
        // 回调函数,`data` 是已解析的JavaScript对象
        console.log(data); // 在控制台查看整个对象
        // 访问对象中的属性
        $(‘#output’).html(data.first); // 将 “first thing” 显示在页面上
    });
});

这个过程非常高效。$.getJSON() 帮我们处理了网络请求和JSON解析的复杂细节,开发者只需关心数据到达后如何处理。

总结 🎉

本节课中我们一起学习了JSON的核心知识。我们了解到JSON是一种基于文本、独立于语言的数据交换格式。我们探讨了其基本语法,并认识到它在JavaScript中既是可执行代码,也是序列化格式。更重要的是,我们实践了如何在PHP端使用 json_encode() 生成JSON数据,以及如何在JavaScript端利用jQuery的 $.getJSON() 方法轻松获取并自动解析这些数据,从而实现前后端之间的无缝通信。这套流程极大简化了现代Web应用中的数据交换工作。

139:JSON聊天应用教程 🗨️

在本节课中,我们将学习如何整合PHP、JavaScript和JSON技术,构建一个简单的异步聊天应用。我们将看到如何将数据模型保留在服务器端,而将视图和控制逻辑更多地迁移到浏览器中。

概述

我们将构建一个聊天应用,它能够异步地从服务器拉取并显示新消息,类似于Facebook等社交平台的消息更新机制。应用的核心数据模型将存储在PHP会话(Session)中,前端则使用JavaScript和jQuery通过AJAX定期获取并更新聊天内容。

服务器端模型与逻辑

首先,我们来看服务器端的代码。在index.php文件中,我们使用会话来管理聊天数据。

如果接收到重置请求,我们会清空会话中的聊天记录。我们的数据模型不是一个独立的数据库,而是一个存储在PHP会话中的聊天消息数组。

处理表单提交的逻辑如下:当用户发送一条消息时,我们首先初始化聊天数组(如果尚未存在),然后将新消息(包含文本和发送时间戳)添加到数组中。最后,我们重定向回页面自身以防止表单重复提交。

// 示例:在会话中存储聊天数组
$_SESSION['chats'] = array();
array_push($_SESSION['chats'], array($message, date('Y-m-d H:i:s')));

这是一种非常简单的“数据库”实现,键chats下存储的只是一个数组。

前端视图结构

接下来是视图部分,即用户看到的HTML界面。

我们有一个表单,包含一个用于输入消息的文本框、一个“发送”按钮和一个“重置”按钮。表单的处理逻辑对应上面服务器端的代码。

在表单下方,我们有一个<div>元素,其ID为chatcontent,用于动态加载和显示聊天记录。初始时,这个div内会显示一个加载动画(spinner),表示正在获取数据。这是一种常见模式:先显示一个加载指示器,然后在数据成功获取后将其清除并填充实际内容。

<div id="chatcontent">
    <img src="spinner.gif" alt="Loading...">
</div>

浏览器端的应用逻辑

现在,我们进入应用的核心——运行在浏览器中的JavaScript控制器代码。

我们定义了一个名为updateMessages的函数。这个函数使用jQuery的$.getJSON方法向chatlist.php发起AJAX请求,以获取最新的聊天记录。

function updateMessages() {
    $.getJSON('chatlist.php', function(rows) {
        console.log('JSON received', rows);
        // ... 处理数据的代码
    });
}

服务器端chatlist.php的代码很简单:它启动会话,读取$_SESSION['chats']数组,然后使用json_encode函数将其编码为JSON格式输出。我们在其中添加了sleep(5)来模拟网络延迟,以便更清楚地看到加载效果。

// chatlist.php 示例
session_start();
sleep(5);
header('Content-Type: application/json');
echo json_encode($_SESSION['chats']);

处理数据并更新视图

当AJAX请求成功返回后,我们在success回调函数中处理数据。

首先,我们清空#chatcontent这个div。然后,我们遍历从服务器返回的rows数组。这个数组的每个元素本身也是一个包含两个元素的数组:第一个是消息文本,第二个是发送日期。

我们使用一个for循环,为每条消息创建一个段落(<p>)元素,并将其追加到#chatcontent中。

$('#chatcontent').empty(); // 清空现有内容
for (var i = 0; i < rows.length; i++) {
    var entry = rows[i];
    $('#chatcontent').append('<p>' + entry[0] + '    ' + entry[1] + '</p>');
}

实现定时轮询

为了让聊天内容自动更新,我们需要定期调用updateMessages函数。这里不能使用简单的同步循环,因为会阻塞浏览器。我们采用异步模式:在每次成功获取数据后,设置一个定时器,在4秒后再次执行自身。

// 在 updateMessages 函数的 success 回调末尾
setTimeout(updateMessages, 4000);

这个模式确保了即使某次请求失败,也不会无限地每4秒重试(因为只有在成功后才设置下一次定时器)。

启动应用

最后,我们需要在页面加载完成后启动整个流程。我们使用jQuery的$(document).ready()方法。

在其中,我们首先进行一个AJAX全局设置$.ajaxSetup({cache: false})。这非常重要,因为它可以阻止浏览器缓存GET请求的响应。如果没有这个设置,浏览器可能会直接返回旧的JSON数据,而不是每次都向服务器请求最新消息。其原理是在请求URL后自动添加一个随时间变化的参数。

然后,我们调用一次updateMessages()函数来加载初始聊天记录,并启动定时轮询的链条。

$(document).ready(function(){
    $.ajaxSetup({cache: false});
    updateMessages();
});

异步编程模式的重要性

这种“启动一个操作,然后定义操作完成后的回调函数”的模式,是JavaScript异步编程的核心。它允许浏览器同时处理多个任务(如更新聊天、响应用户点击、显示通知等),而不会因为等待某个操作(如网络请求)而冻结界面。虽然初学者需要时间适应,但它是一种非常强大且必要的编程范式。

总结

本节课中,我们一起学习了一个完整的JSON聊天应用的构建过程。

我们首先在服务器端用PHP会话实现了一个简单的数据模型。然后,我们创建了前端视图,包括表单和用于显示消息的容器。接着,我们编写了浏览器端的JavaScript控制器,它使用AJAX从服务器异步获取JSON格式的聊天数据,并动态更新DOM以显示这些消息。最后,我们通过setTimeout实现了定时轮询,使聊天内容能够自动更新,并讨论了禁用缓存和异步编程模式的重要性。

通过这个例子,你可以看到,利用JSON作为数据交换格式,配合前端的jQuery AJAX和后端的PHP,我们可以用相对较少的代码实现强大的、交互式的Web应用功能。随着学习的深入,你会发现越来越多的逻辑被移到浏览器端执行,而服务器端则更专注于数据模型和核心业务规则。

140:JSON聊天应用代码详解

在本节课中,我们将学习一个使用JSON、Ajax和JavaScript定时器实现的异步聊天应用。我们将详细解析其工作原理,包括前端如何定时获取数据、后端如何提供数据,以及两者之间如何通过JSON格式进行通信。

上一节我们介绍了异步通信的基本概念,本节中我们来看看一个具体的应用实例。

应用概述与演示

这是一个我十分喜欢的应用,本质上是一个聊天程序。它的核心特点是能够异步更新聊天数据。

首先,我们来运行并体验一下这个应用。基本思路是:我在一个窗口中说“hi”,应用会回复“hi”。我可以在第二个窗口中以不同用户的身份打开应用。

现在我有两个窗口,两个窗口都能看到彼此的聊天内容。我可以在第二个窗口输入“window2”并发送,然后回到第一个窗口,它会看到来自第二个窗口的消息。两个窗口都在异步更新并获取对方的聊天记录。

通过开发者控制台可以观察其运行机制。在网络面板中,可以看到它正在调用 chatlist.php 来获取当前的聊天列表。当我在第二个窗口中发送新消息后,片刻之后,第一个窗口会通过另一个 chatlist.php 请求获取到包含第三条消息的更新列表。这个应用利用JSON、jQuery和一个定时循环来保持所有窗口的聊天内容同步。

这是一个异步聊天应用。我可以重置所有聊天记录,重置操作也无需在两侧窗口都执行,因为几秒钟后,所有窗口的状态都会同步更新。以上就是这个聊天应用的基本功能。

后端处理逻辑

接下来,我们深入分析一下代码是如何实现这些功能的。

首先,代码处理标准的POST请求-响应周期。如果是重置请求,我们会将聊天记录存储在会话(Session)中,因为我们暂时不想使用数据库。我们通过检查 $_POST[‘reset’] 是否存在来判断是否为重置请求,因为HTML中的重置按钮被命名为“reset”。

如果检测到重置请求,我们就清空会话中的聊天数组,然后重定向回页面本身。如果收到的是消息,代码会先检查会话中是否存在聊天数组,若不存在则创建一个空数组,然后将日期和接收到的消息存入数组,最后进行重定向。

这样,POST请求就会处理这个聊天数组并继续执行。在代码中,我们可以看到这个聊天数组。

在代码中,我们看到返回的聊天数组。目前,聊天数组是空的,因为我刚刚重置了它。

以上是文件上半部分的主要逻辑。接下来,我们看看前端如何与后端交互。

前端界面与JavaScript逻辑

在文件中间部分,我们加载了jQuery库。往下看,可以看到聊天相关的代码。这里有一个基本的表单,它会将数据提交回页面自身。表单包含一个文本输入框、一个提交按钮、一个指向 chatlist.php 的链接(用于调试),以及一个重置按钮。

然后,我们有一个id为 chatcontent 的div元素。我们设置这个id是为了方便用jQuery来操作它。初始时,我们在里面放了一个旋转加载图标(spinner),并设置了“加载中”的替代文本,这样在数据加载时用户能看到提示。

继续往下,我们定义了一个名为 updateMessages 的函数。这个函数的核心作用是:启动一个每4秒执行一次的定时器。函数内部首先记录一条日志,然后发起一个Ajax调用,请求 chatlist.php 这个URL。

参数 cache: false 是为了防止浏览器缓存响应,确保每次都能获取新数据。其原理是在请求URL后附加一个基于时间戳的GET参数。

如果请求成功,我们会收到返回的JSON数据,并将其记录到控制台。然后,我们清空 chatcontent div的内容。返回的数据结构是一个数组的数组,每个子数组包含消息和日期。

我们使用一个JavaScript的for循环来遍历所有聊天消息。循环变量 i 从0开始,直到小于数据长度 data.length。在循环体内,我们提取每条消息:data[i][0] 是消息内容,data[i][1] 是消息日期。

在清空 chatcontent div并解析完返回的JSON数据后,我们将每条消息和日期作为一个新的段落(<p>标签)追加到div中。

最后,我们使用 setTimeout 这个JavaScript原生函数(而非jQuery函数),设置在4秒后再次调用 updateMessages 函数自身。因此,updateMessages 函数的工作流程是:获取数据 -> 清空聊天div -> 插入所有聊天记录 -> 计划4秒后再次执行自己。

函数定义完成后,代码立即调用一次 updateMessages() 来启动整个更新循环,这样页面一加载就会立即获取消息。

数据接口 chatlist.php

现在,我们来看看提供数据的 chatlist.php 文件。为了让演示更清晰,我应该始终在 chatlist.php 中保留 sleep(5) 这行代码,这样数据返回会变慢,便于我们观察异步更新的过程。

chatlist.php 的唯一作用就是向我们的应用返回JSON数据。首先,我们设置一个内容类型(Content-Type)头部,这是一种良好的编程习惯。之前我们一直使用 text/html,但这里我们明确告诉浏览器,我们将发送的是 application/json 格式的数据,并且字符集是UTF-8。这个头部必须在任何实际数据输出之前设置。

接着,代码检查会话中是否存在聊天记录。如果不存在,就创建一个空数组。然后,它简单地使用 json_encode() 函数对存储在 $_SESSION[‘chats’] 中的变量进行编码。这个变量是一个数组,其中每个元素又是一个包含消息和日期的两元素数组。json_encode() 将这个PHP数组转换成一个JSON格式的字符串并输出。

运行流程演示

让我再次演示一下整个流程。首先,我取消 chatlist.phpsleep(5) 的注释,这样更容易观察。

清空控制台日志,然后刷新 index.php 页面。我们会看到旋转图标,因为此时 chatcontent div 的内容还没有被更新。我们请求了 chatlist.php,但由于其中设置了5秒休眠,所以需要等待。最终,chatlist.php 返回了数据(一个空数组,因为没有消息),然后前端JavaScript用这个空结果覆盖了旋转图标,图标随之消失。

现在,我切换到第二个窗口,输入“yo”并发送。然后回到第一个窗口等待。我们的4秒定时器会到期,但请记住 chatlist.php 会休眠5秒,所以总共大约需要9秒才会有反应。过一会儿,第一个窗口的定时Ajax请求完成,它清空了 chatcontent div,然后收到了一个包含“yo”消息的数组的数组。JavaScript循环遍历这个列表,将消息和日期插入到div中。

我再在第二个窗口发送第二条消息。回到第一个窗口,再过大约9秒(4秒定时 + 5秒休眠),它会再次获取数据,此时div被清空后重新插入了两条消息。那个JavaScript的for循环清空整个div,然后遍历数据列表,依次输出每条消息及其日期。

技术总结

这就是整个应用的实现原理。如果我们回顾代码,其核心机制是:每4秒调用一次 updateMessages 函数,该函数请求 chatlist.php 获取JSON数据。当请求成功并解析数据后(此时在JavaScript中它已经是一个数组),会调用success回调函数。我们在回调函数中写一个for循环,为数组中的每个条目创建一个段落,其中索引0是消息,索引1是日期。

本节课中我们一起学习了如何组合使用这些技术:利用jQuery驱动JavaScript定时器,通过JSON进行数据交换,然后动态地更新文档对象模型(DOM),而无需完整的页面请求-响应周期。所有这些操作都相当简洁,但清晰地展示了如何将这些基础部件组合成一个功能完整的异步Web应用。

141:JSON CRUD操作详解 🧩

在本节课中,我们将继续探讨jQuery与JSON的结合使用,并对一个基础的CRUD(创建、读取、更新、删除)应用程序进行一个小修改。我们将学习如何利用JSON来动态获取和展示数据,从而理解服务器端渲染与客户端渲染的区别。

概述

我们将分析一个混合了PHP、JavaScript和jQuery的CRUD应用示例。该应用的核心变化在于,它不再由PHP直接生成完整的HTML表格,而是先由PHP后端提供JSON格式的数据,再由前端的jQuery通过AJAX请求获取这些数据,并动态地构建和填充表格。

代码演变:从服务器端渲染到客户端渲染

上一节我们回顾了传统的服务器端渲染方式。本节中我们来看看如何将其改造为使用JSON进行数据交互的客户端渲染模式。

传统的服务器端渲染方式

在最初的版本(index_old.php)中,所有HTML内容都在服务器端由PHP生成。

// 示例:传统的PHP循环生成表格行
$stmt = $pdo->query('SELECT title, plays, rating, id FROM tracks');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo '<tr>';
    echo '<td>' . htmlentities($row['title']) . '</td>';
    echo '<td>' . htmlentities($row['plays']) . '</td>';
    echo '<td>' . htmlentities($row['rating']) . '</td>';
    // ... 编辑和删除按钮
    echo '</tr>';
}

这种方式下,浏览器接收到的响应已经是一个完整的、填充好数据的HTML页面。

新的客户端渲染方式

在新的版本(index.php)中,我们采用了不同的策略。页面初始加载时,表格体(<tbody>)是空的。

<table border="1">
    <tbody id="track-table-body">
        <!-- 初始为空,将由JavaScript填充 -->
    </tbody>
</table>

数据填充的逻辑转移到了JavaScript中。

以下是实现动态数据加载的核心步骤:

  1. 发起AJAX请求:使用jQuery的$.getJSON()方法从get_json.php获取数据。
  2. 处理JSON响应:成功获取数据后,执行回调函数。数据(data)是一个由对象组成的数组。
  3. 动态构建DOM:遍历数据数组,为每条记录拼接出对应的HTML表格行字符串。
  4. 插入到页面:使用jQuery的.append()方法将生成的HTML插入到空的<tbody>中。
$(document).ready(function() {
    $.getJSON('get_json.php', function(data) {
        var tbody = $('#track-table-body');
        tbody.empty(); // 清空现有内容
        if (data.length === 0) {
            tbody.append('<tr><td colspan="4">No entries found.</td></tr>');
        } else {
            $.each(data, function(index, row) {
                // 对数据进行HTML转义,防止注入攻击
                var title = $('<div>').text(row.title).html();
                var plays = $('<div>').text(row.plays).html();
                var rating = $('<div>').text(row.rating).html();
                var id = row.id;

                // 构建表格行HTML
                var html = '<tr>' +
                    '<td>' + title + '</td>' +
                    '<td>' + plays + '</td>' +
                    '<td>' + rating + '</td>' +
                    '<td><a href="edit.php?id=' + id + '">Edit</a></td>' +
                    '<td><a href="delete.php?id=' + id + '" onclick="return confirm(\'Are you sure?\');">Delete</a></td>' +
                    '</tr>';
                tbody.append(html);
            });
        }
    }).fail(function(jqxhr, textStatus, error) {
        console.error("Error fetching JSON: ", textStatus, error);
    });
});

后端数据接口 (get_json.php)

后端的任务变得非常纯粹:查询数据库,并将结果编码为JSON格式输出。

<?php
require_once 'pdo.php'; // 数据库连接

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/4111cd997d6ccf7c84f350ce6a8749ea_19.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/4111cd997d6ccf7c84f350ce6a8749ea_21.png)

header('Content-Type: application/json'); // 设置正确的响应头

$stmt = $pdo->query('SELECT title, plays, rating, id FROM tracks');
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // 获取所有行作为关联数组

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/umich-webdev-evrn/img/4111cd997d6ccf7c84f350ce6a8749ea_23.png)

echo json_encode($rows); // 编码并输出JSON
?>

两种方式的对比与选择

通过查看页面源代码,我们可以清晰地看到两种方式的区别:

  • 服务器端渲染:源代码中包含完整的、已填充数据的HTML表格。
  • 客户端渲染(JSON):源代码中的表格体是空的,数据是在页面加载后由JavaScript动态添加的。

以下是两种技术路径的简单对比:

特性 服务器端渲染 (PHP直接输出) 客户端渲染 (JSON + jQuery)
首次加载速度 可能更快,因HTML已就绪 可能稍慢,需等待JS下载、执行和数据请求
前后端分离 耦合较紧 更清晰,后端专注API,前端负责展示
动态更新 需整页刷新 可局部更新,用户体验更流畅
SEO友好度 友好,内容直接存在于HTML中 需额外处理(如服务端渲染)才能被搜索引擎抓取
适用场景 内容型网站、管理后台初始页 单页应用(SPA)、高度交互的Web应用

没有绝对意义上的“更好”的技术,只有“更合适”的技术。选择取决于你的具体需求:

  • 如果需要简单的页面和良好的SEO,服务器端渲染是可靠的选择。
  • 如果需要构建像实时聊天、动态仪表盘这样交互复杂的应用,采用JSON进行客户端渲染则更具优势。

总结

本节课中我们一起学习了如何将JSON集成到CRUD应用程序中。我们分析了从传统的服务器端PHP渲染到使用jQuery进行客户端动态渲染的演变过程。关键在于理解:

  1. 后端角色转变为提供纯净数据(JSON)的API。
  2. 前端角色转变为消费这些数据并负责构建用户界面。
  3. 这种分离使得前后端开发可以更独立,并能创建出响应更迅速、用户体验更佳的现代Web应用程序。

掌握这两种模式,能帮助你在不同的项目需求中做出恰当的技术选型。

142:配置文件职位信息与JSON详解

在本节课中,我们将深入探讨一个关于个人资料、职位和教育信息管理的代码实现。核心学习目标包括:练习使用JSON数据格式,以及实现一个“多对多”的数据库关系。本教程将逐步解析代码逻辑,帮助你理解如何构建一个功能完整的编辑页面。

概述

我们将分析一个包含个人资料、职位和教育背景编辑功能的Web应用。该应用允许用户动态添加、删除和修改信息,并利用JSON和jQuery UI实现教育机构的自动补全功能。课程重点在于理解“多对多”关系的数据库设计以及前后端如何通过JSON进行数据交互。

代码演示与数据库准备

首先,我们运行代码并准备数据库环境。应用运行在 http://localhost/

我们清空现有的个人资料数据,以确保从干净的状态开始。使用SQL命令 DELETE FROM profile 来删除所有记录。由于设置了级联外键,相关的职位和教育记录也会被自动删除。

接下来,我们创建两个新表来支持教育信息功能。

CREATE TABLE Institution (
    institution_id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(255),
    PRIMARY KEY(institution_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE Education (
    profile_id INTEGER,
    institution_id INTEGER,
    year INTEGER,
    rank INTEGER,
    PRIMARY KEY(profile_id, institution_id),
    CONSTRAINT education_ibfk_1 FOREIGN KEY (profile_id) REFERENCES Profile (profile_id) ON DELETE CASCADE,
    CONSTRAINT education_ibfk_2 FOREIGN KEY (institution_id) REFERENCES Institution (institution_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Institution 表作为查找表,存储机构名称。Education 表则建立了个人资料(profile_id)与教育机构(institution_id)之间的“多对多”关联关系,并记录了入学年份(year)和排序(rank)。

然后,我们向 Institution 表预插入一些初始数据。

INSERT INTO Institution (name) VALUES ('University of Michigan');
INSERT INTO Institution (name) VALUES ('University of Virginia');
INSERT INTO Institution (name) VALUES ('University of Oxford');
INSERT INTO Institution (name) VALUES ('University of Cambridge');
INSERT INTO Institution (name) VALUES ('Stanford University');
INSERT INTO Institution (name) VALUES ('Duke University');
INSERT INTO Institution (name) VALUES ('Michigan State University');
INSERT INTO Institution (name) VALUES ('Mississippi State University');
INSERT INTO Institution (name) VALUES ('Montana State University');

功能界面展示

完成数据库设置后,我们登录应用并查看编辑界面。

编辑界面包含个人资料、职位和教育背景三个部分。职位部分的实现与之前课程类似,允许用户添加多个职位条目,构成“一对多”关系(一个个人资料对应多个职位)。

教育背景部分是本次的重点。它包含一个具有自动补全功能的文本输入框。当用户输入学校名称时,应用会通过AJAX请求查询数据库,并以下拉列表形式显示匹配的选项。这个功能由jQuery UI的Autocomplete部件驱动。

例如,输入“Uni”会触发查询,后端执行类似 SELECT name FROM Institution WHERE name LIKE ‘Uni%’ 的SQL语句,并将结果以JSON格式返回给前端。

用户可以选择已有的机构,也可以输入全新的名称。当提交表单时,如果输入的机构名在数据库中不存在,系统会自动将其插入 Institution 表,并在 Education 表中建立关联。

代码结构解析:编辑页面

上一节我们演示了应用的功能,本节我们来深入查看实现这些功能的代码,特别是 edit.php 文件。

edit.php 文件结构复杂,它依赖于一个共享的 util.php 文件来存放通用函数,以减少代码重复。

头部与安全验证

文件开头引入了必要的JavaScript和CSS库,包括jQuery、jQuery UI(用于自动补全)和Bootstrap。

接着是PHP代码,它首先进行一系列安全检查:

  1. 防止未登录访问。
  2. 确保通过URL参数传递了有效的 profile_id
  3. 验证该 profile_id 对应的个人资料确实属于当前登录用户。这是通过查询数据库,检查 profile 表中的 user_id 是否与当前会话中的用户ID匹配来实现的,防止用户越权修改他人资料。

表单提交处理(POST)

当用户提交表单时,代码会执行以下操作:

  1. 数据验证:调用 validateProfile()validatePos() 函数(位于 util.php)来检查个人资料和职位信息的有效性。值得注意的是,当前代码中缺少对教育信息的验证,这是一个可以改进的地方。
  2. 更新个人资料:使用 UPDATE 语句更新 profile 表的基本信息。
  3. 处理职位信息(一对多关系)
    • 首先,删除该个人资料所有旧的职位记录:DELETE FROM Position WHERE profile_id = ?
    • 然后,调用 insertPositions() 函数(位于 util.php),循环处理提交的职位数据,将新的职位记录插入数据库。
  4. 处理教育信息(多对多关系)
    • 同样,先删除该个人资料所有旧的教育关联记录:DELETE FROM Education WHERE profile_id = ?
    • 然后,调用 insertEducations() 函数。这个函数是处理“多对多”关系的核心,我们稍后会详细分析。

数据加载与页面渲染(GET)

在显示编辑表单(GET请求)时,代码需要从数据库加载现有数据并填充到表单中。

  1. 查询数据:使用PDO的 fetchAll() 方法,一次性获取该个人资料的所有职位和教育记录。fetchAll() 返回一个包含所有行的数组,比传统的 while 循环更简洁。
  2. 生成动态HTML
    • 职位列表:PHP循环遍历职位数组,为每个职位生成一组输入框(如 year1, desc1, year2, desc2),并为每个条目生成一个“删除”按钮。这个按钮的点击事件由JavaScript处理,用于在前端动态移除该条目对应的HTML块。
    • 教育列表:采用类似的循环方式生成教育条目。每个教育条目包含年份和学校名称输入框。学校输入框被赋予特定的CSS类(如 school),以便后续被jQuery选中并启用自动补全功能。
  3. 传递计数到JavaScript:PHP将当前已有的职位数量($countPos)和教育条目数量($countEdu)直接输出到页面的JavaScript变量中。这是为了在用户点击“+”按钮动态添加新字段时,JavaScript能知道下一个新字段的编号应该从多少开始。

前端动态交互

以下是实现前端动态添加字段和自动补全的关键JavaScript代码:

  1. 动态添加字段

    • 当用户点击职位或教育部分的“+”按钮时,JavaScript会递增对应的计数变量(countPoscountEdu)。
    • 对于职位,通过字符串拼接生成新的HTML输入框组。
    • 对于教育,采用了一种“模板替换”的方式:预先在HTML中定义一个 <script type=”text/template”> 标签,里面是教育条目的HTML模板,其中用特殊标记(如 @COUNT@)占位。JavaScript复制这个模板内容,并将 @COUNT@ 替换为当前计数,然后添加到页面中。
  2. 学校自动补全

    • 页面加载后,JavaScript会查找所有具有 school 类的输入框。
    • 对这些输入框调用 .autocomplete({ source: ‘school.php’ }) 方法。
    • 当用户输入时,jQuery UI会向 school.php 发送AJAX请求,携带输入的前缀。
    • school.php 接收参数,执行SQL查询(如 SELECT name FROM Institution WHERE name LIKE ?,参数为 $prefix.’%’),将匹配的机构名称以JSON数组格式返回。
    • jQuery UI接收到JSON数据后,自动显示下拉建议列表。

核心机制:多对多关系的插入

现在,我们聚焦于处理教育信息“多对多”关系的核心函数 insertEducations()

该函数接收个人资料ID和从表单提交的教育数据数组。

以下是其逻辑步骤的伪代码描述:

function insertEducations($pdo, $profile_id, $educations) {
    $rank = 1;
    foreach ($educations as $edu) {
        $year = $edu[‘year’];
        $school = $edu[‘school’];

        if (空值检查) {
            continue; // 跳过空条目
        }

        // 步骤1:查找或创建机构
        $institution_id = false;
        $stmt = $pdo->prepare(‘SELECT institution_id FROM Institution WHERE name = :name’);
        $stmt->execute(array(‘:name’ => $school));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($row !== false) {
            // 机构已存在,获取其ID
            $institution_id = $row[‘institution_id’];
        } else {
            // 机构不存在,插入新机构
            $stmt = $pdo->prepare(‘INSERT INTO Institution (name) VALUES (:name)’);
            $stmt->execute(array(‘:name’ => $school));
            // 获取新插入机构的ID
            $institution_id = $pdo->lastInsertId();
        }

        // 步骤2:插入关联记录
        $stmt = $pdo->prepare(‘INSERT INTO Education (profile_id, institution_id, year, rank) VALUES (:pid, :iid, :year, :rank)’);
        $stmt->execute(array(
            ‘:pid’ => $profile_id,
            ‘:iid’ => $institution_id,
            ‘:year’ => $year,
            ‘:rank’ => $rank
        ));

        $rank++;
    }
}

关键点解析

  • 查找或创建(Lookup or Create):对于用户输入的每个学校名称,首先尝试在 Institution 表中查找。如果找到,则使用现有的 institution_id;如果没找到,则执行 INSERT 语句创建新机构,并使用 $pdo->lastInsertId() 获取新生成的ID。
  • 建立关联:无论机构是已有的还是新建的,此时我们都获得了它的唯一ID。然后,我们向 Education 表插入一条新记录,将个人资料ID (profile_id)、机构ID (institution_id)、年份 (year) 和排序 (rank) 关联起来。(profile_id, institution_id) 共同构成了这个表的主键,确保了同一个人在同一所学校只对应一条记录(年份信息包含在记录中)。
  • rank 字段的作用:用于控制教育条目在页面上显示的顺序,独立于年份。

总结

本节课我们一起学习了如何构建一个管理个人资料、职位和教育信息的复杂编辑功能。

我们深入分析了以下核心内容:

  1. 数据库设计:理解了“一对多”(职位与个人资料)和“多对多”(教育机构与个人资料)两种关系模型,并通过外键和关联表来实现它们。
  2. 代码安全与结构:学习了如何进行用户权限验证,以及如何组织代码(将通用函数放入 util.php)以提高可维护性。
  3. 前端交互:掌握了使用JavaScript动态添加/删除表单字段的技巧,以及如何利用jQuery UI的Autocomplete部件和JSON与后端(school.php)交互,实现高效的自动补全搜索。
  4. 核心后端逻辑:重点剖析了 insertEducations() 函数,理解了在处理“多对多”关系时“查找或创建”模式的经典应用,以及如何使用PDO安全地操作数据库并获取插入后的ID。

通过本课的学习,你应当能够掌握在Web应用中处理复杂表单和数据库关系的基本方法,并了解如何利用JSON增强前端用户体验。

143:魁北克蒙特利尔

在本节课中,我们将回顾一次在加拿大蒙特利尔举行的课程附加办公时间。多位来自不同背景的学习者分享了他们学习编程(特别是Python语言)的体验与动机。


编程教育与学科融合

上一节我们介绍了本次办公时间的背景,本节中我们来看看关于编程在基础教育中角色的讨论。

编程可以与数学和计算机科学紧密结合。计算机科学专业的学生需要学习大量数学课程,其核心内容很大程度上是数学。公式可以表示为:计算机科学 ≈ 数学 + 编程

然而,成为一名技术专家并不必须精通数学。关键在于,在众多高中课程中,可以融入编程教学。这并不意味着要求每个高中生都必须修读12门编程课,而是可以适度安排。例如,学生可能只选修两到三门编程课,同时用一部分化学课的时间,替换为一门入门级的计算机课程。

这种调整的宏观考量在于:我们应该思考,对于13至17岁的学生,什么是可能且有效的教学内容?目标是教授他们能够真正掌握并记住的知识,而不是仅仅作为一种筛选机制。


学习者的自我介绍与动机

在讨论了编程教育的理念后,让我们听听参与本次办公时间的各位学习者的故事。以下是他们的自我介绍和学习Python的初衷:

  • Katya:我修读了两门课程,它们都非常棒,感谢您的教学。
  • Carl Hanz:我是一名网站架构师,正在学习更多知识。
  • Suravvy:我在麦吉尔大学攻读博士学位,计划从事可再生能源领域的研究。我学习编程是因为未来会使用相关软件和随机评估工具,而Python将用于实现这些工具。
  • Bni:我正在享受Python课程。
  • Jamatist:我是一名视觉特效艺术家,感谢Dr. Chuck让我通过学习Python成为一名更好的艺术家。
  • Mcwell:我最近在蒙特利尔成立了一家科技公司,Python课程非常精彩,我计划夏天再学一遍。
  • Andreas:我很高兴从入门开始学习Python,它非常有趣且简单。
  • Sergio:我是一名航空分析师,Python为我完全打开了一些新的大门。
  • Tim Mccherin:我正在修读“Python与信息学”课程,感谢这次美好的体验。
  • Olga:这是我第一次参加Coursera的课程,我感到非常高兴,并希望这能帮助我找到工作。
  • Steve:我正在同时学习互联网历史和Python课程,两门课都让我非常愉快。
  • Nancy:我代表“编程女性”洛杉矶分会感谢Dr. Chuck。
  • Flor:这是我认真学习的第一门编程语言,我非常享受这个过程,也很喜欢课程的设置方式。
  • Joanna:我正在学习Python课程,这是我的第一门编程语言,体验很棒。
  • Ramona:我正在学习互联网历史课程,并计划在夏天学习Python课程。
  • Pierre:我已经完成了Python课程,并希望继续学习其他课程。
  • Matt:我是一名IT管理员,学习Python已有几年,它对我所做的每件事都很有帮助。
  • Saib:我正在跟随Dr. Chuck学习Python。
  • Dominda:我是麦吉尔大学的学生,作为一名初学者,今天的活动让我很感兴趣。

总结

本节课中我们一起回顾了在蒙特利尔举行的一次课程办公时间。我们探讨了编程在通识教育中的定位,并聆听了众多学习者分享他们如何将Python应用于学术、艺术、创业和职业发展等多元领域。尽管参与者众多以至于需要更换场地,但这次交流无疑是一次成功的活动。

144:Chuck的巴黎旅游秀

在本节课中,我们将跟随查尔斯·塞弗伦斯教授,了解他如何通过面对面办公时间与学生互动,并分享他在编程教学中的核心思考方式。


我的名字是查尔斯·塞弗伦斯。我是密歇根大学信息学院的教员。

自2012年起,我还通过一个名为Coursera的平台,教授了全球超过五十万名学生。

当我旅行时,我会邀请我的学生参加我所谓的“面对面办公时间”。大家好,我是查克。

我们现在在法国巴黎,为Coursera进行又一次的面对面办公时间。

我想让你们认识一些你们的同学。是的,我们在意大利米兰。

我们正在进行有史以来规模最大的办公时间之一,我想进行我们的标准介绍,这样你们就能看到每个人。所以请告诉我们你的名字,打个招呼,或者说说我在写作时的想法。

当我在设计一个操作系统时,我会想,哦,这里有一个我需要解决的问题,这里有这些性能和规格要求,我像一个计算机科学家一样思考。但当我编写一个Python程序时,

那就像是在邮件列表中寻找某种特定的邮件,然后将这些邮件与其他东西匹配起来,或者找出你的论坛中谁是最多产的发布者。

你真正在做的是,缓慢但坚定地将自己融入到那个程序中。在与学生们会面之后,

我和莫奥一起去和萨莉共进晚餐,她自2012年以来一直是我在Coursera上的一名志愿助教。

是的。好的,一件事是Nato sha mug,另一件事是来自另一个没有CTA或只是CTA已经到达的。


本节课中我们一起学习了查尔斯·塞弗伦斯教授通过旅行举办面对面办公时间的教学实践,以及他对于编程思维(从系统设计到具体问题解决)的见解。

posted @ 2026-03-29 09:41  布客飞龙I  阅读(3)  评论(0)    收藏  举报