Web-开发实践指南-全-
Web 开发实践指南(全)
原文:
zh.annas-archive.org/md5/f0fc179c65f5670a0e035856ac96b38e
译者:飞龙
前言
我很幸运在加利福尼亚州生活和工作了很长时间。在那段时间里,我大部分时间住在帕洛阿尔托,那里是硅谷的中心,斯坦福大学的家园,也是许多大小公司的诞生地,包括我工作过的太阳微系统公司。我坐在前排观看万维网的发展,也见证了社交媒体的兴起。Facebook 就是在我最喜欢的餐厅的另一边开始的。现在,某个 Facebook 的男孩或女孩正坐在我曾经在 Dumbarton 桥底下的办公室里。作为 Solaris 的产品经理,我的一个任务就是确保 Netscape Navigator 包含在我们的操作系统中。所以,我正站在网络发展的源头。我甚至参加了 Web 2.0 会议,并购买了同名书籍。
然后,我感到有必要拥有自己的网站来展示我的照片,并告诉人们我在州内多次旅行中发现加利福尼亚州的美景和有趣的地方。于是,我创建了一个。有一天,我向一个朋友提起这件事,他试图用手机查看。看起来很糟糕。所以,我买了一部诺基亚手机(与今天我们所拥有的相比就像一块砖头)以便我可以测试自己的网站,确保它在手机上看起来也好。这就是我多年前就接触到了响应式设计的病毒,在有人开始称呼它之前。
当我回到比利时后,我觉得是时候尽可能多地学习(我喜欢学习新事物)关于在创建网站之外还有什么了,于是报名参加了为期 6 个月的 PHP 网络开发课程。由于我之前是 UNIX 和 C 开发者,所以其中很多内容看起来都很熟悉。班上只有 12 个人,他们都是聪明人,我很快发现还有很多东西要学。
由于课程在比利时的列日市(一个主要大学城)举行,我去了当地的大学书店,买了一本又一本关于各种相关主题的书,很快成了 jQuery 的粉丝。顺便说一句,jQuery 甚至没有包含在课程中。我开始思考为什么有人需要 35 本不同的书来学习网络开发,而且写一本综合概述你需要知道什么才能参与网络开发的单本书不是一个坏主意。
自那以后,网络开发发生了很大变化;这次需要更多的书籍,电子书,但概念保持不变。所以,现在你知道我为什么要写这本书了。
这本书以传统方式概述了网络开发的各个方面,使用纯 HTML 创建静态网站,以及当前的方式,使您能够动态创建网页,并确保它们在移动设备上看起来也很棒,通过使用响应式设计。我们通过向您暗示,如果您用自己用 node.js 编写的服务器替换传统网络服务器,接下来会发生什么来结束本书。
本书涵盖的内容
第一章,万维网,为您概述了今天我们所知道的万维网的历史。
第二章,HTML,介绍了 HTML,并为您概述了最常用的 HTML 标签进行网络开发。阅读本章后,您将能够创建一个基本网站。
第三章,CSS,解释了如何使用层叠样式表(CSS)。这用于您网站的展示部分或布局,从颜色到尺寸到字体。这里解释了最常用的 CSS 属性。完成本章后,您将能够使您的基本网站看起来很好。
第四章,JavaScript,首先为您介绍了编程和编程语言的世界。接下来,介绍了 JavaScript 的整体语法以及如何用于客户端编程。
第五章,PHP,解释了 PHP,这是一种另一种编程语言。这个用于服务器端编程。它需要一个网络服务器来开发您的网站并将其部署。您将学习如何动态创建您的网页,而不是必须编写大量的 HTML 文件。
第六章,PHP 和 MySQL,介绍了 MySQL,一个开源数据库。您将学习如何创建数据库,使用 phpMyAdmin 工具管理它,并在 PHP 程序内部执行基本的 CRUD(创建、替换、更新、删除)操作。
第七章,jQuery,涵盖了一个流行的 JavaScript 库。它允许您编写更紧凑、更干净的代码,并为您处理浏览器兼容性问题。有了这个,您将能够更容易、更快地编写遍历和操作网页的 JavaScript 代码。它是通过使用选择器来实现的,您已经学会了如何使用 CSS。因此,使用 jQuery,您可以编写 JavaScript 代码而无需学习很多 JavaScript。
第八章,Ajax,介绍了 Ajax。它代表了一系列技术,使动态更改网站的部分变得容易。通过本章,我们进入了所谓“现代网络开发”的世界。我们用于 Ajax 调用的接口是 jQuery。
第九章, 历史 API—不忘记我们所在之处,解释了网络开发谜题中的一个非常重要的部分。一旦我们在页面上动态更改页面以使其看起来不同,但实际上仍然是同一页面(URL),当访客想要回到他们认为的上一页时,可能会发生奇怪的事情。这里描述的解决方案不仅适用于 HTML5,也适用于 HTML4。
第十章, XML 和 JSON,描述了 XML 和 JSON。它们是两种流行的数据交换格式,例如服务器和客户端。尽管 XML 被用于各种环境中,但 JSON 更接近于网络开发社区。
第十一章, MongoDB,描述了 MySQL 作为数据库的替代方案。这是一个所谓的 NoSQL 数据库和文档数据库。文档以方便的 JSON 格式存在。在这里,描述了如何在 PHP 程序中访问 MongoDB 数据库。
第十二章, 移动优先,渐进增强的响应式设计,是本书中最长的章节标题。它解释了现在越来越多的人使用移动设备而不是传统的计算机屏幕来访问网站时,现代网络开发应该如何进行。
第十三章, Foundation – 一个响应式 CSS/JavaScript 框架,描述了 Foundation 框架的大部分功能,它可以帮助你进行响应式设计。它包含了我一直想写但从未有时间去做的一切。这标志着本书中我所说的现代网络开发部分的结束。
第十四章, Node.js,概述了我所说的网络开发的先锋。它介绍了 node.js,允许你使用 JavaScript 编写服务器端代码,包括你自己的网络服务器,这可以通过使用 Express 框架来实现。
附录, Bootstrap – 一种替代方案,描述了流行的 CSS/JavaScript 框架,它作为 Foundation 的替代方案,可以帮助你进行响应式设计。包含这部分内容的主要原因是为了指出关键的不同点和相似之处。
在线章节,Mono 县网站,提供了一个完整的示例,展示了我们如何应用所学的几乎所有内容。它可在www.packtpub.com/sites/default/files/downloads/B03816_Appendix.pdf
找到。
你需要这本书的
你需要以下软件来使用本书中的示例:
软件 | 来源 |
---|---|
Firefox 和 Firebug | www.mozilla.org |
Apache 网络服务器 | 操作系统的一部分 |
XAMPP(包括 MySQL 和 PHPMyAdmin) | www.apachefriends.org |
MySQL | www.mysql.com |
PHPMyAdmin | phpmyadmin.net |
jQuery | www.jquery.com |
jQuery 历史插件 | github.com/browserstate/history.js |
MongoDB | mongodb.org |
基础 | foundation.zurb.com |
Node.js | nodejs.org |
Bootstrap | getbootstrap.com |
本书面向对象
这本书适合任何想要掌握当今网络开发全貌的人。它非常适合想要入门并学习网络开发基础(如 HTML)的初学者,同时也为经验丰富的开发者提供了一个网络开发路线图,帮助他们扩展能力,并深入了解不同技术如何交互和协同工作。
惯例
在这本书中,你会找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称应如下所示:“我们可以通过使用include
指令来包含其他上下文。”
代码块如下设置:
<body>
<div id="header"></div>
<div id="container">
<div id="left"></div><div id="middle"></div><div id="right"></div>
</div>
<div id="footer"></div>
</body>
当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将以粗体显示:
<tag class="value1 value2">text<?tag>
任何命令行输入或输出都应如下所示:
{ "key" : { "name":"Schwarzenegger","first":"Arnold",
"profession":"governator" } }
新术语和重要词汇以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,在文本中如下所示:“现在,直到用户点击海滩按钮,你好,世界才会变成你好,海滩。”
注意
警告或重要提示如下所示。
小贴士
小贴士和技巧如下所示。
读者反馈
我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。
如要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>
,并在邮件主题中提及本书的标题。
如果你擅长某个主题,并且对撰写或参与书籍感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在,你已经是 Packt 图书的骄傲拥有者,我们有一些东西可以帮助你从购买中获得最大收益。
下载示例代码
您可以从 www.packtpub.com
下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support
并注册,以便将文件直接通过电子邮件发送给您。
勘误
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata
,选择您的书,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的“勘误”部分下的现有勘误列表中。
要查看之前提交的勘误表,请访问 www.packtpub.com/books/content/support
,并在搜索字段中输入书名。所需信息将出现在“勘误”部分下。勘误部分。
盗版
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com>
联系我们,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过 <questions@packtpub.com>
联系我们,我们将尽力解决问题。
第一章。万维网
本书将讨论 Web 开发的过去、现在和未来。从 第二章 开始,我们将介绍 HTML,我们将向您介绍所有您需要了解的技术,以便进行 Web 开发。在我们这样做之前,我们想要设定舞台,以便我们所有人都知道我们在谈论哪个 Web:这将是 万维网 (www)。
万维网
我热爱历史!那么,让我们从万维网的一点点历史开始。我有幸能够在一家公司工作,该公司开发了 UNIX 操作系统的第一个商业版本。他们成立于 1977 年,我十年后加入了他们。UNIX 是一种操作系统(你需要它来让你的电脑做某事),它原本打算在小型计算机上运行(尽管它们被称为小型计算机,但它们无法放入你的公寓,并且需要空气冷却)。这些计算机通常用作一个独立的系统,连接了相当多的基于文本的终端。
今天,UNIX 仍然存在,并构成了 Linux、Solaris、MacOS 等操作系统的基石。我们的公司发现了添加具有今天标准功能和技术产品的机会。其中一些例子包括电子邮件(能够向另一台电脑上的人发送邮件)和 ftp(能够将文件传输到另一台电脑,或者仅仅访问另一台电脑)。是的,如果你想发送邮件,你需要额外付费。多亏了互联网,所有这一切才成为可能。
互联网
互联网是一个全球网络,今天它连接着全球数十亿台电脑。它的起源可以追溯到为美国政府进行的研究,但今天每个人都可以通过使用其标准协议集上网,这些协议通常被称为 TCP/IP(这里的 IP 是指互联网协议)。
每一台连接到互联网的电脑或设备都将有一个独特的互联网地址,也就是 IP 地址。它是一组由点分隔的 4 个数字,例如,192.25.13.90。当然,你永远不会告诉你的朋友你在 192.25.13.90 上买了东西,而是在例如 www.amazon.com 上。这是因为互联网还使用了一个将 IP 地址转换为更容易记住的域名功能。我使用的例子碰巧是一个你可以去购买东西的网站,这种做法我们都熟悉,它导致了万维网成为今天的样子。
在日常用语中,互联网和万维网通常被视为同义词,但实际上它们并不是。互联网上提供了许多不同的服务(由被称为 互联网服务提供商 (ISP)的公司提供),在万维网出现之前就已经如此(例如,提供电子邮件访问)。万维网出现所需要的是(就像在 JavaScript 中一样)一系列异步事件。其中有两个非常重要,不能不提。
HTTP 和 HTML
你可能已经听过这个故事很多次了,但如果没有它,万维网就不会存在。这是蒂姆·伯纳斯-李的故事,他是欧洲核子研究中心(CERN)的一名工程师。该中心拥有许多计算机,当然,它们都连接到互联网。它还产生了大量的数据和文档,几乎无法管理。蒂姆通过开发一种用于编写这些文档的语言,一种在互联网上管理它们的协议,以及一个供用户访问它们的计算机程序来解决这个问题。
HTML
HTML,即超文本标记语言的缩写,是那种语言的名称。超文本是包含超链接的文本,而超链接则是文档中读者可以点击的部分,通过链接将他们带到另一个文档。你们都见过文本中蓝色下划线部分,在不太美观的网页上。这些都是超链接。
HTML 文档由标签组成,标签之间是文本。例如,有开标签和闭标签,如下所示:
<h1>Hello, world</h1>
在这里,<h1>
是开标签,</h1>
是闭标签。我们将学习一种类似的标记语言:XML。尽管 HTML 和 XML 不同,但一个重要的区别是,在 XML 中你可以定义自己的标签,只要关闭你打开的每一个标签。XML 用于传输数据,而标签用于组织数据。
在 HTML 中,标签确实有特定的含义。<h1>
在文档中用于一级标题的文本。<a>
标签——锚标签——是我们刚才讨论的超链接所使用的标签。因此,编写 HTML 的目的不是传输数据,而是向人类用户展示数据。
要做到这一点,这些标签将由我们之前提到的计算机程序解释。这样的程序被称为浏览器。当读者点击超链接时,浏览器也会检测到这一点,并向另一个程序,即网络服务器,发送请求以获取另一个文档。
HTTP
这就是超文本传输协议 HTTP 发挥作用的地方。如果用户点击链接,就像是在说:去获取另一个 HTML 文档。该文档的名称将是一个以http://
开头的更长的字符串的一部分,同时也包含服务器的域名。它被称为统一资源定位符,但我们通常称之为 URL。以下是一个示例:www.paulpwellens.com/examples/secondpage.html
。
你可以用 HTTP 做什么,随着时间的推移已经发生了变化,我们将在本书的后面部分了解到这一点,但就目前而言,我们需要继续我们的历史课程。再给你一个小小的历史趣闻:猜猜我们的朋友蒂姆是如何称呼他的浏览器的,这是第一个浏览器:WorldWideWeb。后来他将其重命名以避免混淆。
万维网联盟(W3C)
在 1994 年离开 CERN 之后,蒂姆·伯纳斯-李创立了万维网联盟(W3C)。该联盟试图确保提供网络组件的厂商之间的兼容性和一致性。不兼容的 HTML 版本会导致浏览器以不同的方式渲染网页;而添加到浏览器中的不兼容特性也会产生同样的意外结果。
如果你访问www.w3.org,即该联盟的网站,你会注意到 W3C 已经发展成为许多技术的标准机构,但即使在那些日子里,拥有这样一个组织也是迫切需要的。
Mosaic
历史在前进,我也在前进!我们的公司被 Sun Microsystems 收购,我成为了我们 PC UNIX 产品的产品经理。哦,对了,在此之前,PC 已经无处不在,从长远来看,这种现象也促进了万维网的爆炸式增长。
有一天,在 1993 年,我的工程经理和我(乔纳森,他的主要程序员)一起走进我的办公室。他们想向我展示乔纳森周末所做的工作。这是一款名为Mosaic的程序为我们 PC UNIX 产品做的移植(将程序的源代码转换为在计算机上运行的二进制文件)。我看着他输入了几条命令,但并不完全理解为什么这些人如此兴奋。我并不知道这个看似无害的小程序将永远改变我们的生活!
第一个浏览器
Mosaic 是由马克·安德森领导的一个团队在伊利诺伊大学香槟分校的国家超级计算应用中心(NCSA)开发的(这是从芝加哥出发穿过玉米田的漫长车程,我曾经走过这条路)。它是第一个支持多种协议(因此得名)并在同一页面上显示图像和文本的浏览器(令人惊讶的是,这在网页上并非易事!)它很快引起了全世界的关注,并且这款浏览器被移植到许多平台上,以便更多的人能够开发或访问网站。
1992 年 11 月,有 26 个网站。在 Mosaic 浏览器中,有一个最新动态部分,每天展示一个新的网站。三年后,网站数量达到了 10,000,再过三年,数量达到了数百万。今天,我认为已经无法再统计它们的数量了。那么我们是如何从几千个发展到几百万个的呢?
Netscape
马克·安德森创立了一家名为 Mosaic 的公司,后来将其更名为网景通信公司。他们基本上重写了 Mosaic 浏览器,并针对网络带宽较低的环境进行了优化,例如那些通过 ISP 从家中上网的个人。这款浏览器被称为网景导航者。这显然是第一个商业浏览器,作为 Netscape Communicator 捆绑包的一部分出现在电脑零售店的货架上。
Netscape 也被认为是第一个包含对 JavaScript 支持的浏览器。有了这种脚本语言,可以为网页添加交互性。只要你有 Netscape,互联网世界就触手可及。从 1994 年到大约 1999 年,Netscape 在浏览器市场占有明显的份额。
随着时间的推移,Netscape 被转移到 Mozilla 公司,一个开源组织,Netscape 的开发也随之结束。今天,用户可以从www.mozilla.org下载Firefox浏览器。
Internet Explorer
另一个 Mosaic 的衍生品,Spyglass Mosaic,进入了微软的代码库,并最终捆绑在微软 Windows 中。我们今天称之为Internet Explorer。这就是我们如何进入所谓的浏览器战争的话题。正如我提到的,Netscape 试图通过进入零售市场并对其浏览器收费来争取尽可能多的客户。另一方面,微软决定免费将其 Internet Explorer 捆绑在其 Windows 操作系统(当然,您仍然需要为 Windows 付费)中。
这导致了许多类似诉讼的情况,因为微软被其他浏览器供应商指责不公平竞争。现在这种情况已经改变,因为所有浏览器都是免费的。计算机用户现在可以选择他们想要使用的浏览器。在平板电脑上,选择有限,但另一方面,平板电脑上的浏览器通常都有所有最新的功能。
在技术层面上,正在发生一场不同且可能更加血腥的战争。尽管有一个标准机构 W3C,它控制和引入了新的功能(包括 HTML 和 CSS),但并非所有浏览器都以相同的速度适应这些功能。因此,当由不同的浏览器渲染时,相同的网页看起来也不同。遗憾的是,最不兼容和不可预测的浏览器——Internet Explorer,在世纪之交时,也是大多数上网的人使用的浏览器。
因此,开发者别无选择,只能推迟使用新酷炫的功能,代价是花费大量额外的时间来确保他们的网页在大多数访客使用的 PC 上看起来和它们在创建的系统上一样。
在开始好转之前,情况变得更糟,因为更多的开发者开始使用 JavaScript 来为页面添加交互和动画,而许多系统管理员则推荐关闭 JavaScript 的配置。有时这会导致访客在页面上什么也看不到。
但不要绝望,我们现在已经是 2015 年了!在这本书中,我们将采取不同的方法,并且始终在浏览器支持的情况下让您使用新功能。
互联网的爆炸式增长
到世纪之交,每家公司都想拥有一个网页。网页是通过链接更多的网页来创建的,这些网页包含有关公司或网站所有者的信息。后者是通过提供网络托管服务的智能 ISP 实现的。人们必须能够在你或你的电脑睡觉时访问你的网站;因此,这些服务提供 24/7 的不间断服务来放置你的 HTML 文件。网络托管公司还负责为你注册域名,例如www.thecoolestphotographer.com。
在某个时候,获取你想要的域名几乎到了另一场浏览器战争的边缘,因为只有一个xyz.com
,如果一些有创业精神的人认为拥有xyz.com
会带来金钱,他们就会抢注。
当我想为自己注册一个时,paulwellens.com
已经被一位英国橄榄球运动员注册了,所以我选择了www.paulpwellens.com(P 是我的中间名首字母)。我既不是英国人也不是橄榄球运动员,所以这对我来说没问题。
因此,全球创建了大量页面,但它们最初只是提供了供你查看的信息,没有其他内容。在某些情况下,它们一旦创建就再也没有更新过。幸运的是,这只是一个例外,证实了规则。许多公司决定他们必须在互联网上有所存在,因此创建了企业网站。CSS 的出现极大地促进了这一点,因为它允许将展示和内容分离。这样,市场营销部门会提供企业标志和外观感觉,而其他所有部门则提供内容。
Amazon.com 和电子商务
一些有创意的头脑意识到,网络不仅提供了一个提供信息的平台。从在网站上提供产品的信息到实际销售它们,只是一小步。电子商务因此诞生。亚马逊就是一个很好的例子,它是一个每个人都能联想到的网络商店。开发一个网络商店当然需要比让公司里的人输入一些内容更多的工作。
这些产品是真实的产品;它们存放在仓库中,有零件号、价格、名称和描述、不同的尺寸和颜色等等。这些信息很可能已经存在于某个数据库中,每次在零售店销售产品时都会更新。要在网上销售东西,你的网页必须与访客互动,向他展示某种屏幕上的购物车,计算小计,检查仓库库存等等。
要做到这一点,需要越来越多的编程知识,而不仅仅是将数据输入 HTML 文件。因此,网络开发者的工作就诞生了。传统的程序员精通一种编程语言(Java 或 C++)和通常一个平台(Solaris或.NET)。网络开发者必须精通至少四种不同的语言,并且对数据库有所了解。我想补充一点,对我来说,这个工作非常令人兴奋——可以参与到设计方面。网络设计师和网络开发者之间的差距正在缩小。所以今天,人们谈论的是前端和后端开发者。
因此,这本书将教授你们的内容是;如何成为一名网络开发者,但在结束我们的历史课程之前。还有一些事情使得网络成为今天的样子。
谷歌和雅虎!
所以你有一个包含信息的网站,或者是一个网店,因为你正在网上销售一些东西,比如 www.mycoolproduct.com
。你期望如何接触到你的潜在客户,给每一个都打电话吗?这就是谷歌或雅虎发挥作用的地方。这些流行的公司开发了所谓的搜索引擎。你想了解你刚刚看过的电影的所有信息,你无法记住名字的歌曲,或者你最喜欢的餐厅的电话号码?你访问 google.com,yahoo.com,或者类似的网站,并输入你想要查找的内容。你可能会找到它。
我们在工作场所进行了一项可用性研究,我们给一组人提供了一套 CD、一摞手册和一台安装的电脑;另一组人没有手册,但有互联网接入。第二组做得更好,因为他们觉得不需要手册,因为他们认为手册可能已经过时了,所以他们立即在网上查找信息。而且这是在 10 年前发生的。
今天,搜索引擎的使用如此普遍,以至于像 Yahoo!和 Google 这样的术语被用作动词。在某些语言中,它们实际上已经成为动词,并进入了官方词典。
社交网络
我认识一些今天不使用 Facebook 的人,但我不知道有谁不知道 Facebook。多年来,我路过 Facebook 办公楼的建筑。我并不真的感兴趣。然后,当我搬回比利时时,我决定加入他们,这样我就可以与加利福尼亚的朋友保持联系,他们生活在不同的时区,相隔数千英里。从其中一个人那里我得知,Facebook 的人已经搬进了我曾经办公的建筑。有时候事情就是这样有趣!
Facebook、Twitter、YouTube 和 LinkedIn 是流行的社交网站的代表。这里没有销售,只有分享。人们分享图片、故事、事件、想法、观点等等。
网络开发
多年前,我参加了一个为期 6 个月的课程,基本上是这本书的第一部分。几个月后,很明显,缺乏一个解释课程所有组成部分之间关系的介绍部分是这门课程的最大缺陷。
经过六个月后,仍有很多人不理解 Java 和 JavaScript 之间的区别。所以我对自己承诺了两件事:有一天我会写一本书,并且这样一个章节将包含在其中。那么,让我们开始吧!
HTML
以 HTML 编写的文件是每个网站的基础。我们在上一节中简要提到了其历史;在这里,我们将更深入地探讨其结构。看看以下示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World example<\title>
<\head>
<body>
<h1>Hello, world<\h1>
<\body>
<\html>
第一行指定了 DOCTYPE
,指的是使用的 HTML 版本,以便浏览器知道如何解释该文件。示例中的 DOCTYPE
是 HTML5 的示例。DOCTYPE
声明过去要长得多。
接下来是主要标签,即 <html>
标签。在其中,我们将找到所有的 HTML,分为两个部分,即 <head>
和 <body>
。主体标签包含您的内容,而头部标签包含其他信息。在我们的例子中,有一个元标签指定了使用的编码。<title>
标签包含将在浏览器窗口顶部显示的文本。不要忘记 <title>
标签非常重要,因为这是搜索引擎将检查的事项之一。
在这个简单的例子中,主体部分包含一个 <h1>
标签。这代表文档中的一级标题,类似于您在文字处理程序中找到的标题。浏览器将决定如何显示该内容,或者如他们所说,渲染它。那么我们如何将 HTML 放入文件,以及如何将其放入浏览器中呢?
HTML 编辑器和其他工具
由于 HTML 文件只是一个文本文件,您最喜欢的文本编辑器就可以胜任。只需确保文件名中有 .html
扩展名,例如 hello.html
。
然而,在某个时候,您可能需要在同一文件中包含一些 CSS、JavaScript,当然还有 PHP,在这种情况下,专业的工具将使您的工作效率更高。
浏览器和 Web 服务器
现在您有一个名为 hello.html
的文件,并想在浏览器中查看它。在现实生活中,这个文件将是您网站的一部分,您必须将其放置在那里。这就是您的网站托管公司告诉您放置文件的地方。他们将提供所有正确传输文件(s)到他们的服务器的信息。
它们最终会进入一个名为文档根目录的文件夹,这是构成您网站的所有文件的根。如果您按照 hello.html
文件中的说明将其传输到那里,您将在浏览器地址栏中输入以下地址时看到结果:
http://www.mycoolsite.com/hello
您也可以在本地查看您的文件,我们将在下一章中向您介绍更多关于这方面的内容。
很重要的一点是,要意识到对于访问你网站的人来说,你的网页可能看起来和你创建的不一样。一个因素——但不是唯一因素——是正在使用的浏览器。因此,我们建议,从早期开发阶段开始,你就要使用不同的浏览器查看你的作品,并且增加浏览器的数量,用于/并且针对不同的设备。
总是在你的 Mac 或 PC 上安装 Mozilla Firefox 和 Google Chrome。选择一个来开发(我喜欢 Firefox,因为Firebug),但在交付之前,总是要对其他浏览器进行一些测试。
因此,从我们的这个小例子开始,你会发现即使是Hello World在不同的浏览器中看起来也会不同。幸运的是,我们可以通过使用 CSS 来控制几乎所有这些。
CSS
层叠样式表(CSS)是一种与 HTML 配合得很好的技术,它允许你,而不是浏览器,来决定你的页面将呈现什么样子。
看看这个稍微修改过的我们的Hello, World网页示例,hello.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World example</title>
<link href="hello.css" rel="stylesheet" ></link>
</head>
<body>
<h1 class="green header" id="hello">Hello, World </h1>
</body>
</html>
在包含<h1>
标签的行中,我们向<h1>
元素添加了两个 HTML 属性,class
和id
。属性是 HTML 开标签内的字符串,格式为 name="value",你可以使用的两个最重要的属性是class
和id
。许多元素可以是多个类的一部分,但id
对于单个元素是唯一的。
现在创建一个名为hello.css
的文件,内容如下:
h1 {
font-family:Baskerville, cambria, serif;
font-size:24px;
}
.green {
color:green;
}
#hello {
font-weight:bold;
font-style: italic;
)
这是我们的第一个 CSS 样式表。文件中的第一个规则意味着我们的文档中的任何<h1>
元素都将使用 Baskerville 字体(如果 Baskerville 在用户的计算机上不可用,则为 cambria),24 像素大小,以及浏览器选择的颜色(通常是黑色)。
然而,当它或任何其他元素(不仅仅是 h1),具有 class="green
"(在 CSS 中,名称中的.
表示类名)时,它将以绿色显示。
最后,我们的特定Hello World标题将因为 CSS 文件中的最后几行而以粗体和斜体显示。在 CSS 中,#
字符用于表示标识符,所以#hello规则意味着针对设置为 id="hello
"的元素的规则。
由于所有这些,任何浏览器都应该将我们的 HTML 文件渲染为包含文本Hello, world的行,以 Baskerville 字体(一种常用于电子书的衬线字体,据我所知与福尔摩斯无关),绿色,24 像素大小,并且加粗和斜体。试试看,它真的有效!
注意,我们没有在#hello 规则中重复 Baskerville 行,该规则只是简单地继承。<h1>
规则会级联到#hello,因为恰好这是一个<h1>
元素,所以得名层叠样式表。正如我们刚才所展示的,我们可以通过使用 CSS 清楚地分离页面的内容和表现。这就是为什么尽早学习如何使用 CSS 很重要的原因。
因此,作为一名 Web 开发者,你已经知道你需要掌握至少 HTML 和 CSS。我们现在将进入语言拼图中的下一部分——JavaScript。
JavaScript
在本书中,当我们谈论 JavaScript 时,除非另有说明,我们指的是客户端 JavaScript。所有代码都由浏览器解释,就像 HTML 和 CSS 一样。
通过使用 JavaScript,我们可以给我们的页面添加动作,与我们的网站访客进行交互,并通过编程改变页面的内容和外观。让我们看看以下示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World example</title>
<link href="hello.css" rel="stylesheet" ></link>
</head>
<body>
<h1 class="green header" id="hello"></h1>
<script type="text/javascript">
var answer = confirm("Do you want to say hello?");
if (answer == true)
{
document.getElementById("hello").innerHTML="Hello, world";
}
</script>
</body>
</html>
如果你在一个浏览器中查看这个页面,你的屏幕上不会显示Hello, World,而会出现一个带有问题的弹出框。如果你回答问题是,我们熟悉的绿色Hello World文本将重新出现。当你使用不同的浏览器时,弹出框本身看起来也会完全不同。
如果你查看代码,你会认出类似编程的东西。有一个if
语句和一个变量(answer)。请注意,变量的名称是一个普通的字符字符串,但在其声明中它前面有var
。所有的 JavaScript 代码都位于一个带有type
属性为text/javascript
的 HTML <script>
标签之间。
有一个对于 JavaScript 来说非常典型的语句,为我们做了所有的工作:
document.getElementById("hello").innerHTML="Hello, world";
第四章,JavaScript将真正教你这个含义。现在,我们将给出这条代码的英文解释:在我们的文档中,将 HTML 标签的内部内容替换为id
为 hello 的字符串Hello, world。
在随后的章节中,我们将介绍 JavaScript 库
,这将允许你编写更紧凑的 JavaScript 代码,其中已经为你做了很多工作。jQuery是这些库之一,将在第七章中讨论,jQuery。
PHP
JavaScript 是一种完整的语言,它允许你做比我们在上一个简单示例中展示的更多的事情。然而,正如我提到的,这是客户端 JavaScript,由浏览器解释。所以一旦你关闭了电脑或平板,它就消失了。好吧,其中一些可能保存在你的机器上。
想象一下,如果我们只使用我们之前提到的语言来创建在线商店,那将不会起作用。商店中有什么信息,以及你特定订单的数据必须存在于其他地方。那将是运行商店的公司计算机,而不是运行浏览器的设备。
因此,亲爱的网络开发者,你已经猜到了,在你能够创建一个在线商店之前,你至少需要学习一种额外的编程语言来处理所有这些,而语言本身可以是多种之一(甚至可以是 JavaScript),但代码存储和解释的关键区别在于:远程应用服务器。这些语言中最受欢迎的一种是 PHP,它在第五章(Chapter 5)中有详细说明,PHP。让我们看看以下例子:
<?php
$hello = "Hello World Example";
$helloheader = '<h1 class="green" id="hello">Hello, World</
h1>';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $hello; ?></title>
<link href="hello.css" type="text/css" rel="stylesheet" ></link>
</head>
<body>
<?php
echo $helloheader;
?>
</body>
</html>
到目前为止,我们能够在浏览器中测试我们的小例子,但这是不同的。要尝试这个例子,你需要安装一个应用服务器,无论是本地还是远程。现在,只需继续阅读。
注意例子中的 <?php
和 ?>
字符串。这是 PHP 代码开始和结束的位置,并且必须由该应用服务器进行解释。代码的第一部分定义了两个变量。请注意,在 PHP 中,变量的名称以 $
符号开头,而在 JavaScript 中则不是。对 UNIX 用户来说熟悉的 echo
命令简单地回显这些变量的值。
因此,一旦应用服务器完成对 PHP 代码的解释,你剩下的就是 CSS 部分中的我们的 HTML 示例。这正是它的工作方式:应用服务器解释 PHP 代码,然后网络服务器将生成的 HTML 代码传递给浏览器。
Apache 是一个非常流行的应用服务器,同时也是一个网络服务器。这是运行在计算机上(我们通常称之为服务器)的软件,你的程序文件就存放在这里:hello.php
。
因此,http://www.mycoolsite.com/hello
将再次成为访问这个网页的方式。这看起来可能是在使用过度夸张的方式使用额外的语言来显示 Hello, World。但有些情况下你可能想要这样做,例如,如果你需要的 HTML 中的数据存储在其他地方。
数据
使用远程服务器和服务器端语言的主要原因之一将是数据的存储和处理。这些数据可以以多种格式存在,从平面文本文件到电子表格,XML、JSON 或一个完整的数据库,这需要数据库服务器。在后一种情况下,你可能需要学习另一种语言,标准查询语言(SQL)并处理另一个(软件)服务器:数据库服务器。本书将介绍几种选择。
摘要
在本章中,我们讨论了万维网的诞生和历史。接下来,我们通过一些例子讨论了网络开发。为了成为一名网络应用开发者,你必须掌握至少四种语言:HTML、CSS、JavaScript 以及像 PHP 这样的服务器端语言。
根据你计划如何组织你的数据,可能还需要学习第五种语言(SQL)。还有更多东西需要学习,比如如何使用库或框架。好消息是,它们都在构建一个完整的 Web 应用程序概念中扮演着各自的角色。
现在我们知道了我们需要学习的内容,让我们去行动吧!我们将从 HTML 开始。
第二章。HTML
在本章中,我们将向您介绍 HTML 的基础知识。在第一章《万维网》中,我们已经介绍了 HTML 的含义,该语言的来源以及它的用途:创建网页的内容部分。我们已经知道,这些内容放置在标签之间:<tag>
用于打开,</tag>
用于关闭。
在本书的范围内提供所有 HTML 标签或元素(我们将交替使用这些词)及其所有属性的完整参考是不可能的。在参考文献中列出了一些很好的参考资料,当然还有一些很酷的在线参考资料。我个人喜欢w3schools.com,但如果你不喜欢,只需在 Google 上搜索“HTML”后跟一个你想要了解更多信息的标签名称,你将找到一些很好的替代资源。
因此,在本章中,我们只描述最常用的 HTML 标签,按它们在文档中扮演的角色分组。例如,所有可以在表格中使用的标签都归类在标题表格
下。
HTML 版本
当然,自从它的创建以来,HTML 已经出现了几个不同的版本和风味。最显著的是 HTML4、XHTML 和 HTML5:
-
HTML4:这是 HTML 一系列版本中的最后一个版本,也是大多数人谈论 HTML 时会隐含指代的版本。
-
XHTML:这是 HTML 的不同定义,以及将 HTML 变成 XML 家族成员的尝试,给它带来了更严格的规则。一个优点是,它将更容易使用旨在操作和转换 XML 文档的工具和语言。然而,维护该标准的兴趣似乎已经减弱。
-
HTML5:这是传说中的 HTML 中的最新版本。关于它的书籍已经出版了很多,如果你读过其中之一,你会发现 HTML5 不仅仅是一个标记语言的版本。诚然,它带来了很多新的标签,如
<nav>
或<section>
标签。HTML5 还引入了自定义数据属性的使用,如data-whateveryouchoose
,你可以在文档中使用这些属性。稍后,你可以使用 JavaScript 来操作这些属性。这是一种在元素内部传递数据的方式;因此,选择了这个名字:data-*
。
我说过 JavaScript 吗?HTML5 中的所有其他新功能实际上都是 JavaScript API,如 HTML5 Canvas。Canvas 允许你在网页上绘制东西,比如饼图。尽管这些 API 可能很吸引人,但它们超出了本章的范围。
语义和表现性 HTML
我们在本章以及本书整体的第一部分所采取的方法是只使用所有三个标准都涵盖的 HTML 元素和属性。在实践中,这意味着我们不会使用任何在 HTML5 中消失的 HTML4 属性,也不会使用任何在 HTML4 中不存在的 HTML5 元素或属性。
另一方面,我们不想阻止大家使用新事物,因此我们将 HTML5 特有的元素列在单独的列表中。我们也会在书的第二部分介绍一个酷炫的 CSS/JavaScript 框架时使用这些新元素。
很容易将 HTML 元素分为两组。第一组包括指向文档各部分的元素:标题、段落、表格、表单、列表等(<h1>
, <h2>
, <p>
, <table>
, <ul>
)。我们称其为语义性 HTML,因为它们指的是事物的名称;它们描述了它们是什么。
另一组包含用于指示事物外观的元素:它们的对齐方式、使用的字体、是否加粗或斜体等(<center>
, <font>
, <b>
, <i>
),我们可以称它们为表现性 HTML。HTML 属性也是如此。class="green"
或id="chapter"
是语义性的,而width="150px"
或valign="top"
则是表现性的。
注意
W3C 建议使用 CSS 进行表现性处理,我们遵循这一建议。这种方式也将避免你学习一大堆新事物,后来却发现它们不再被使用,因为大多数 HTML4 元素和属性在 HTML5 中不再可用,恰好是表现性的。你会在网上找到的表示某事物不再使用的词是过时。
当我第一次遇到这个单词时,我误读为过时了。那个词可能是一个更好的选择。无论如何,如果元素和属性被标记为这样的,请避免使用它们。
因此,我们不会在本章中展示漂亮的 HTML 文档示例,因为使其变得漂亮的 CSS 部分将不得不等待一段时间,直到我们到达第三章,CSS。
HTML 文档的结构和语法
HTML 文档是一个以.html
结尾的文本文件,例如,hello.html
。一个现代的、最简的 HTML 文档看起来像这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Hello, world</title>
</head>
<body>
<h1>Hello, World</h1>
</body>
<html>
Doctype
第一行指定了文档类型(<!DOCTYPE html>
)。今天,这可以简单到只使用单词html
来告诉浏览器这个文件将被解释为 HTML5 文档。按照旧规范编写的文档包含该规范的名称,后跟一个指向文档类型定义(DTD)文件的路径,该文件可以用来检查文档的兼容性。如今,事情要灵活得多。
<html>
这是文档的根。文档剩余部分的所有内容都将位于这个html
标签内。html
标签内的内容分为两个部分,即head
和body
。
<head>
头部部分包含一个或多个<meta>
标签。在我们之前的例子中,它指定了文档文本部分的编码必须为UTF-8。
这个部分也是<title>
标签所在的位置。这个元素内的文本是将在浏览器窗口顶部显示的文本,并且被搜索引擎高度关注。因此,始终包含一个title
标签,并确保其内容正确且有意义。
此外,<head>
部分用于包含浏览器在加载文档的body
部分之前需要读取的更多信息。你最常见的例子将是用于文档的CSS 样式表的路径。这本书中会有很多例子。
<body>
在body
标签内部是我们文档的核心内容。因此,如果你想在整个文档中使用某些样式元素,你可以通过简单地样式化<body>
标签来实现。我们稍后会提醒你这一点。当然,我们首先必须了解我们可以在其中放置什么。
文档内标签或元素的语法
HTML 标签的语法非常简单:
<tag attribute1="value1" attribute2="value2">text</tag>,
这后面跟着next
标签。你可以将一切放在一行上,或者将每对标签放在单独的一行上以提高可读性,因为标签对之间的新行和空格会被忽略。
在text
部分内部,空格不会被忽略,但一行中的多个空格会被缩减为一个。所以如果你想插入更多空格,你必须使用不同的方法(参见本章后面的HTML 实体)。
对于没有内容的元素,存在缩写符号。我们可以使用:
<tag/>
我们使用这个而不是:
<tag></tag>
在我们的例子中,使用了缩写符号来表示:
<meta charset="utf-8"/>
class
属性可以有多个值,在这种情况下,它会被写成:
<tag class="value1 value2">text<?tag>
它不是这样写的:
<tag class="value1" class="value2">text<tag>
这最后一行演示了在未意识到已经存在类属性的情况下添加class
属性时的一个常见疏忽。在这种情况下,第二个将被忽略。浏览器也会忽略所有未被识别为 HTML 标签的元素和属性。
与旧式编程语言的编译器不同,当你输入错误时,你永远不会看到错误消息。事情可能看起来不对,或者你可能甚至得到一个空白屏幕。这就是为什么使用一个识别标签和属性为有效 HTML 的工具或其他工具(最好是显示它们颜色的工具)是极其高效的。
HTML 注释
在 HTML 代码的任何地方,你都可以插入一个注释:给自己或后人的提醒,或者(可能更重要)给需要共享你的代码的团队中的其他人。语法非常简单。任何在 HTML 块内部的都可以通过在前面放置<!--
和在后面放置-->
来注释:
我们强烈建议插入更多的注释而不是更少的。当有人要求你从网站上删除某些内容,而你又有这种感觉,认为它可能会回来时,应用注释也是有用的。因为如果你删除了它:它就消失了!
另一方面,HTML 注释的每一行都会在你的文件中增加一行,并且也会让全世界看到,因为每个浏览器都有一个查看源代码的选项。所以一旦你开始使用 PHP 这样的服务器端语言,你将在接下来的几章中学习,最好将你的注释放在那段代码中。你会发现 CSS 和 PHP 中注释的语法是不同的。
如承诺的那样,我们现在将描述最重要的 HTML 标签和属性,按功能分组。我们将从一切开始的地方开始:链接。
链接
很可能,第一个创建的网页包含了对第二个网页的链接。要在页面上放置链接,我们使用锚点标签 <a>
。
<a>
标签和属性
如果我们仅仅在 <a>
标签内放置一些文本,当你点击它时,实际上并不会发生任何事情,除非你在 JavaScript 中编程了这个事件。然而,你可以从它在屏幕上的外观来判断,它的意图是成为一个链接。默认情况下,<a>
标签的内容以(现在可能臭名昭著的)下划线和蓝色样式渲染。
<a>Click here</a>
href
属性
要使链接工作,您需要使用 href 或 超文本引用 属性。您可以链接到另一个网页,外部或本地,文档或图片,或者当前页面的另一个部分。以下是一些示例:
<a href="http://www.packtpub.com">Visit our website</a>
<a href="index.html">Home</a>
<a href="pdfs/manual.pdf">Click here to download the manual</a>
<a href="#top">Go back to top </a>
前三个例子应该是自解释的。有一个完整的 URL,一个单独的文件名,index.html
,以及到 PDF 文件的相对路径。支持绝对路径名,但建议不要使用。最后一个例子需要更多的解释。你注意到那个尖号了吗?
<a>
标签的 name
属性
当与 <a>
标签一起使用时,name
属性可以用来命名页面上特定的位置。然后可以在页面的其他地方使用这个名称来创建链接。
因此,您可以将此内容放置在页面顶部附近:
<a name="top"></a> <!-- note that there does not have to be any content -->
页面上的另一个位置有一个链接,使用相同的名称,但前面有一个 #
符号,将带我们回到那里:
<a href="#top">Back to top </a>
<a>
标签的 target
属性
当用户点击链接并到达新页面时,他们有时想回到他们来的地方。一些设备和大多数浏览器都有一个后退按钮,甚至还有一个前进按钮,访客可以点击,但浏览器并不总是带他们回到他们期望的页面。或者按钮可能没有任何效果。
在本书的后半部分,我们将用整整一章来讨论这个主题以及“上一页”应该真正是什么的概念。现在,你可以通过向你的锚点元素添加 target
属性来帮助你的事业和你的访客。它允许你确定是否在新的浏览器窗口中打开 target
页面。有四种选项:
-
target="_blank":此页面将在新窗口或标签页中打开
-
target="_self": 此页面将在点击的同一窗口中打开;这是默认设置,但有时也意味着你创建了一个无法回头的点。
-
target="_top": 此页面将在浏览器全窗口大小中打开
-
target="_parent": 此页面将在父窗口中打开
经典文档元素
本节列出了几个对文字处理软件或桌面出版程序用户来说熟悉的 HTML 元素。
,
,
, …
– 标题
这些是标题。数字越小,浏览器渲染标题时字体的大小就越大。
– 段落
这是段落标签。浏览器会自动在每个 <p>
元素前后添加一些空间(外边距)。这些外边距可以用 CSS(使用外边距属性)进行修改。
– span
span
标签本身没有视觉效果,但当你需要仅对文本的一部分进行样式设置时,它非常有用。
你可以这样使用它:
<h3>Example</h3>
<p>This is a paragraph with one <span class="blue">blue</span>word</span>
列表
在几乎每一份文档中,你都会发现需要将多个项目总结成列表。在 HTML 中,你可以选择无序列表(想想项目符号)或有序列表(想想数字)。这些列表的 HTML 元素是 <ul>
和 <ol>
。
这个例子生成一个颜色列表:
<h2>Colors</h2>
<ul>
<li>red</li>
<li>green</li>
<li>blue</li>
</ul>
这将生成一个颜色列表,每个颜色前都有一个(圆形)项目符号。用 <ul>/</ul>
替换 <ol>/</ol>
将得到一个编号列表。曾经有属性可以指定项目符号的形状,但这些属性已经消失了。现在,项目符号样式由 CSS 指定。你甚至可以使用图片文件作为项目符号。
值得一看的第三个列表元素是 <dl>
或数据列表。
图片
很难想象一个没有图片的网站。大多数人认为在网站上添加图片很容易,可能只需要一点 Photoshop 处理就完成了。实际上并非如此,但这都是可以管理的。作为一名摄影师,我在第一次尝试使用 HTML 时发现,在网页上把文字放在图片旁边是非常痛苦的。那是因为我当时对 CSS 了解不够。
实际上,处理图片只需要一个 HTML 元素:<img>
标签。
<img>
元素和属性
包含图片的典型 HTML 代码如下:
<img src="img/lupine.jog" alt="lupine" />
一个 img
标签永远不会包含任何内容,所以我们总是使用缩写形式。始终存在的两个属性是 src
和 alt
。alt
属性的值是当图片文件找不到或设备无法显示图片时将显示的文本。src
属性包含图片文件的路径。图片文件可以有多种不同的格式:.jpeg
, .gif
, .png
, .tiff
, 等等。
当没有提供关于我们想要用于显示图片的屏幕部分实际大小信息时,图片将以其实际大小显示,所以要注意大图片文件。
图片宽度和高度
你可以使用两个属性来做到这一点:宽度和高度。这将导致浏览器以你指定的尺寸渲染图像,但最好不使用这些属性,并在 CSS 中指定宽度和高度。所以给你的<img>
标签添加一个类或一个id
标签来这样做。
你将很快了解到,在讨论响应式设计时,你甚至有机会为不同的屏幕尺寸指定不同的图像尺寸。
无论哪种方式,一旦你知道将要使用的图像的最大尺寸,就创建一个与此尺寸完全一致的照片文件用于你的网站。如果原始文件更大,你不会强迫访客下载他们不需要的大文件。如果原始文件更小,创建一个在较大尺寸下的高质量图像文件,这样它看起来会更好,而不是依赖于浏览器如何外推图像。
输入表单
你都见过并使用过它们,现在你将创建它们:注册表单、订单表单——简而言之:表单。所有表单的共同之处在于用户将输入或输入一些信息。接下来,该输入将被验证——例如,验证电子邮件地址是否确实处于正确的格式——然后以某种方式进行处理。
当然,表单将使用 HTML 和 CSS 编写。验证可以在客户端发生,在处理之前,使用 JavaScript,以及在服务器端处理时。在大多数情况下,处理是在 PHP 中完成的,结果存储在某种类型的数据库中,如 MySQL 或 MongoDB,或者非数据库,如平面文件、XML 文件或 Excel 电子表格。现在,让我们专注于表单的创建本身。
表单元素
我们在这里将要讨论并用于表单的元素有:<form>
、<label>
、<input>
、<textarea>
、<button>
、<select>
以及<option>
。我们将单独处理<select>
元素。
这是一个典型的 HTML 部分示例,描述了一个表单:
<form id="myform" action"processing.php" method="post" class="formclass">
<label for="title">Title</label>
<input type="radio" value="Ms" id="title" "name="title" class="classic">Ms.</input>
<input type="radio" value="Mr" id="title" "name="title" class="classic">Mr</input>
<label for="first">First Name</label>
<input type="text" name="first" id="first" class="classic" />
<label for="last">Last Name </label>
<input type="text" name="last" id="last" class="classic" />
<label for="email">email</label>
<input type="text" name="email" id="email" class="classic" />
<button type="submit">Register</button>
</form>
表单属性
注意form
标签的action
和method
属性。它们指明了将用于处理数据的程序名称以及处理数据的方法。我们将在第五章中详细解释,PHP。
标签属性
label
元素是一个用于标记input
元素的非常有用的标签。for
属性将一个label
标签与一个input
标签关联起来。
输入属性
input
元素是在表单中使用的最灵活的元素。它用于让用户输入信息,无论是通过输入一些文本还是通过勾选复选框或单选按钮。
有几种类型,由type
属性指定:
属性 | 描述 |
---|
|
type="text"
这是默认值,因此无需指定此属性:这是用于文本的。 |
---|
|
type="hidden"
这个选项不会显示,但它对于传递值来说极其有用。 |
---|
|
type="radio"
这将创建一个单选按钮:只能选择一个。 |
---|
|
type="checkbox"
这创建了一个复选框:可以选中多个复选框。 |
---|
|
type="password"
这就像文本,但输入的字符不会显示。 |
---|
|
type="button"
这创建了一个按钮。你还可以使用<button> 标签创建按钮。 |
---|
|
type="submit"
这创建了一个提交按钮。这意味着表单将被发送到服务器。你还可以使用<button> 元素及其type="submit" 属性创建提交按钮。 |
---|
|
type="file"
这创建了一个带有“选择文件”按钮的文件上传对话框。当你使用multiple 属性并且浏览器支持它时,你可以选择多个文件。 |
---|
名称属性
任何在你访客对其进行了操作后将要被处理的输入元素都需要有一个名称。这个名称最终将用于在表单处理时在服务器端创建变量名。属于一起的radio button
输入元素应该有相同的名称。
在checkbox
类型输入的情况下,你应该不仅为每个复选框输入元素使用相同的名称,还希望在名称后面使用方括号。结果是,你将能够访问所有已选中元素的处理侧数组,这将变得非常有用。
值属性
这个用于为输入元素分配默认值,或者分配在表单中之前使用并已存储在数据库中的值,就像在“这是你的选择,你想改变任何东西吗?”这类情况中一样。
已选中属性
使用此属性时,如果radio button
或checkbox
需要默认选中。
只读属性
如果你指定了readonly
,访客将无法输入任何内容。
文本区域
当在比几个单词更长的表单中期望输入时,你可以使用textarea
元素来显示输入框。你可以通过使用rows
和cols
属性来指定框的大小。以下是一个示例:
<textarea row="4" cols="50" id="mytextbox">
</textarea>
下拉列表
经常我们需要让访客从列表中选择一个选项。为此,我们可以使用<select>
元素与每个单独选择的<option>
标签结合。<option>
元素的文本部分将显示出来,而value
属性的值将用于处理。将第一个选项设置为不选择的选项非常有用,如下例所示:
<select name="colorlist" id="colorlist">
<option value="0">Choose a color</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option disabled value="orange">Orange</option>
</select>
禁用属性
<select>
和<option>
都支持disabled
,如果你想要显示一个选项(或整个下拉列表)但不可选择。
已选择属性
如果你想要预选一个选项,请使用<option>
的selected
属性。
表格
表格在 HTML 文档中被广泛使用。如果你想以行列网格的形式展示数据,就像在电子表格中一样,你可能需要创建一个表格。表格中的一个单元格不仅可以包含数字或文字,甚至可以包含一个图像或...另一个表格。
默认情况下,浏览器将根据单元格内容的宽度和表格整体的空间来决定每个列的宽度。
表格元素
以下 HTML 元素用于创建表格:
<table>
这是创建表格的主要标签。每个表格都以 <table>
标签开始,以 </table>
标签结束。
<thead> <tbody>
这些(可选)元素允许你将表格的标题部分和主体部分分开(并随后进行样式化)。就像在电子表格中一样,标题用于描述表格行中包含的内容,而主体用于实际内容。你可以使用多个 <tbody>
部分来更好地组织(并样式化)你的表格。
<tr>
没有行?没有表格。<tr>
或 表格行 是你将用于表格中所有行的元素,包括标题和主体部分。
<th> <td>
这些是表格单元格的元素。<th>
标签用于表格标题中的标签,而 <td>
(表格数据)用于表格主体中的内容单元格。
表格属性
一些表格元素具有仅适用于表格的属性。我们将在下面讨论它们。
colspan (td)
由 x 行和 y 列组成的表格当然会包含 x 乘以 y 个单元格。使用 colspan
属性,你可以指定对于给定的单元格,你想要它在多个列中跨行。以下行将跨越这个表格单元格的三个列:
<td colspan="3">Verylongname</td>
rowspan (td)
这是 colspan
的行版本。使用此属性,你可以指定你想要一个表格单元格比单个行更高。
让我们看看一个表格的例子:
<table>
<thead>
<tr><th>First</th><th>Last</th><th>Organization</th><th>Phone</th></tr>
</thead><tbody>
<tr><td>John</td><td>Muir</td><td>Yosemite</td><td></td></tr>
<tr><td colspan="2">Michael Tilson Thomas</td><td>San Francisco Symphony</td><td>4158885555</td></tr>
<tr><td>Diane </td><td>Nicolini</td><td rowspan="2">KDFC</td><td rowspan="2">415888KDFC</td></tr>
<tr><td>Bill</td><td>Lueth</td></tr>
</tbody>
</table>
<div>
, “uebertag”
最后,是 <div>
,所有标签的标签。当你遇到在页面上想要的位置放置内容的问题时,你很可能会通过插入多个 <div>
元素来解决它。将 <div>
想象为页面上的一个矩形区域。你甚至可以将你的页面组织成一个网格。本书后半部分我们将使用的框架正是如此。它使用一个 12 列的网格。
看这个非常简单,但并不罕见的一个例子:
<body>
<div id="header"></div>
<div id="container">
<div id="left"></div><div id="middle"></div><div id="right"></div>
</div>
<div id="footer"></div>
</body>
只需将此作为新 HTML 页面 minigrid.html
的主体,查看它。你会看到……什么也没有,因为没有任何 <div>
元素包含任何内容,在这种情况下,它们没有大小。<div>
元素是所谓的块元素。我们将在下一章详细讨论这一点。在我们这样做之前,我们将以一个非常重要的话题来结束本章关于 HTML 的讨论:HTML 实体。
HTML 实体
正如我们所知,所有标签都以 <
符号开始,以 >
符号结束。只需想象你想使用其中之一作为你内容的一部分。这可能会让浏览器困惑。这就是为什么我们有 HTML 实体的原因。
HTML 实体是以 ampersand 符号开始并以分号结束的字符串。
-
这代表的是和号本身:
&
-
一个非常有用的 HTML 实体是不换行空格:
它允许您在内容中插入一个或多个空格。要在内容中使用
<
或>
符号,我们有:<
和>
-
非常有用的还有
&eur;
,用于欧元符号,©
用于版权符号,以及®
用于注册商标符号。 -
非英语字符也可以用 HTML 实体表示,例如,
é
表示é,è
表示è,以及ê
表示ê。
如果您想查看完整的列表,我们建议您查找一些在线参考资料。
HTML5 特定的标签
HTML5 引入了许多新的标签,可以帮助您为文档添加结构,因为它们都有文档或网站的常见组件的名称,如标题、页脚或文章。
如果您已经从事 Web 开发一段时间,您很可能已经使用过这些名称,但作为对<div>
元素进行分类的类。所以如果您使用了<div class="header">
,现在您可以使用<header>
。或者,反过来,如果您已经使用了<header>
,您的后备计划以支持非 HTML5 兼容的浏览器可能包含<div class="header">
。
这里简要概述了它们是什么以及如何使用:
-
<header>
: 这用于包含页面或部分的标题。它通常包含公司标志和导航元素。 -
<footer>
: 页脚通常包含指向其他相关信息的链接、联系信息和版权声明。请确保您保持后者的最新状态。人们不会信任那些有两年前日期的网站上的信息。 -
<nav>
: 这个容器可以用来放置您网站的主要导航部分。 -
<aside>
: 这个标签非常有用,可以放置通常放在左侧的组件,紧邻其他所有内容。 -
<article>
和<section>
: 这两个标签非常有用,可以更好地组织您的文档。您可以用它们来写博客文章,或者,正如其名称所暗示的,文章或部分。
摘要
在本章中,您学习了 HTML 的基础知识,这是我们用来进行 Web 开发的几种语言中的第一种。HTML 使用诸如<div>
之类的标签,标签也可以有属性,例如,<p class="blue">蓝色段落</p>
。所有这些标签结合在一个页面上,一个 HTML 页面上,构成了网站的基本构建块。我们不是给出所有可用 HTML 元素和标签的详尽列表和描述,而是向您介绍了最常见和最有用的那些。
HTML 元素用于向网站添加内容,而不是为网站提供特定的外观。颜色、背景和前景、字体类型、图像周围的边框以及网站上的许多其他可见特征都通过样式表和另一种语言来处理。那种语言是 CSS,这就是我们下一章的主题。
第三章。CSS
在上一章中,我们学习了如何使用 HTML 元素和属性创建 HTML 文档。我们甚至可以包括图片和指向其他文档和图片的链接。但是当你看到屏幕上的结果时,你可能感到失望。我希望你是这样的,因为那是故意的(哎呀,我差点说成是有意为之,但设计部分正是本章的主题)。当我写我的第一个网页时,我也感到失望,特别是在发现做一件本应简单的事情有多难:在页面上放一张照片,旁边有一些文本。现在,是时候将失望转化为兴奋了!
在本章中,我们将学习如何使用层叠样式表(CSS)添加演示部分——换句话说,就是布局——到我们的网页中。样式表是桌面出版软件中的常见功能。它们允许你指定(或修改)文档中某一类部分的样式:例如,每个文本段落。当我用 Adobe InDesign 开发我的第一本书时,我知道每个组件应该是什么样子,所以我手动修改了它们的字体和大小。我不想花时间去学习如何创建样式表。然而,我后来后悔了这个决定,因为那时它已经变成了一件耗时的事情。
现在,我喜欢样式表,并且不仅推荐使用它们,还建议在开始一个新项目时,让它们成为你首先创建的东西。样式表就像一个计划的计划,你可以在以后填写细节。
通过使用样式表,你可以将设计部分与内容部分分开。你甚至可以在不同的时间或由不同的人完成,这将使所有页面看起来都保持一致。只需切换两个样式表,你的整个网站看起来就会完全不同。我们现在兴奋了吗?
让我们从一段 CSS 代码的示例开始:
/* selector – by the way this is how to do comments in CSS */
p.red
/* properties */
{
color:red;
font-family:baskerville, cambria, serif;
font-size:12px;
font-style:italic;
}
CSS 中的注释可以在字符串/*
和*/
之间找到,类似于 C 编程语言中使用的。因此,为了鼓励良好的行为,我们在第一个例子中包含了一些注释。让我们分析一下这段代码的其余部分。
花括号之前的部分被称为选择器。它代表我们页面中的一个或多个元素。在我们的例子中,这包括所有具有 class red 的<p>
元素。
在花括号之间,我们可以找到我们想要的 CSS 规则。在这个例子中,我们希望段落内的所有文本都是红色,大小为 12 像素,斜体,并且使用Baskerville字体。在那些没有该字体的系统上,我们希望使用Cambria字体代替。
注意,每个规则都以一个分号结束,由一个冒号分隔成两部分:一个属性和一个值。颜色是一个属性,在我们的例子中,选择的值是red
。
在 HTML 的旧版本中,可以通过在 <p>
标签内放置 <font>
元素来实现相同的效果。想象一下,您的文档中有 40 个 <p>
部分,有人想要将 red
改为 maroon
!您将不得不在 HTML 文件中的 40 个不同位置进行更改,而且全局查找和替换也无法帮助您,因为可能还有其他红色的“东西”。通过使用 CSS,您只需更改一行。
更常见的是找到类似以下的 CSS 代码:
p {
font-size:12px;
font-family:Baskerville,cambria, serif;
}
p.red {
font-style:italic;
color:red;
}
这不仅会对红色 <p>
标签产生相同的效果,而且还会将所有其他段落设置为相同的字体和大小。这是 CSS 中的 C:整体 <p>
的属性会流入红色段落的子集中,就像在真实的水流中一样。
向我们的文档添加样式
那么 CSS 规则是如何成为我们文档的一部分的呢?有三种方式:
-
外部样式表
-
内部 CSS
-
行内样式
外部样式表
这是向您的网站添加 CSS 的推荐方式。您的目标应该是所有网站生产版本的样式表都是外部的。只需在网站的 <head>
部分添加一行,如下所示:
<link rel="stylesheet" type="text/css" href="css/style.css">
<link>
是一个我们尚未介绍的 HTML 元素。其属性如下所示:
-
rel
,用于指示 HTML 文档与链接文件之间的关系。 -
type
指定了文档的 MIME 类型,以便浏览器知道如何加载它。 -
href
用于指定文件的地址。您可能会期望在这里使用src
属性,就像<img>
标签中使用的那样,但指定<link>
元素中文件名的属性是href
。对于文件名,我们建议您始终使用相对路径名。我们建议您将所有样式表收集到一个具有有意义名称的文件夹中,例如css
或styles
。当然,文件本身也应该有一个有意义的名称。
当文件确实存在时,它将被加载。这就是为什么您的 <link>
元素必须位于文档的 <head>
部分中,以便在文档的主体之前读取所有 CSS 规则。
内部 CSS
对于小型项目,或者您希望限制为单个 HTML 文件以便于发送给他人,您可以使用内部 CSS。然后,所有的 CSS 规则都可以放置在文档的 <head>
部分内,在一个 <style>
标签中。该标签至少需要包含一个 type
属性,如下例所示:
<style type="text/css">
p.red {
color:red;
}
</style>
行内样式
可以通过在 HTML 元素内部使用 style
属性来给单个 HTML 元素添加样式,如下例所示:
<h3 style="color:green;">Congratulations</h3>
我们不建议在最终产品中使用这种方法,但在开发过程中立即看到更改的效果是非常有用的。如果由于某种原因,您在页面中留下了一个行内样式属性,那么您可能永远也找不到为什么您的样式表没有按照预期在页面的一行上工作。
另一方面,我每天都在使用它,因为我可以在不干扰网站其他内容的情况下,在页面上引入和测试新元素,就像修改外部的 .css
文件那样。
文档对象模型(DOM)
如我们在上一章中学到的,一个 HTML 文档由一个嵌套标签的树结构组成,HTML 作为根。在编程中,该树的内容可以存储在一个大对象中,子树可以通过使用较小的对象来访问、修改或添加。
所有这些统称为文档对象模型(DOM)。在随后的章节中,我们将学习一种编程语言(JavaScript)来做到这一点,以及一个 JavaScript 库(jQuery)来简化这个过程。在本章中,我们将不会学习如何更改我们的内容,而是学习如何更改内容的样式。在所有情况下,我们需要一种方法来访问我们的文档。这就是选择器发挥作用的地方。
选择器
CSS 规则的第一部分,即开大括号之前的部分,是选择器,或者由逗号分隔的多个选择器。选择器代表页面中一系列元素,后续规则将应用于这些元素。最简单的选择器是一个单一的标签名,就像我们在之前的例子中已经使用过的那样。以下是一个代码片段作为另一个例子:
p {
color:blue;
}
选择器 p
表示整个页面中所有的段落元素。应用此规则将导致整个页面中的所有段落都以蓝色渲染。同样,我们也可以使用类名。参考以下示例:
.blue {
color:blue;
}
选择器 .blue
表示页面中所有具有 class
blue
的元素,无论它们是段落、标题还是其他。现在我们可以将这两个结合起来,如下所示:
p.blue {
color:blue;
}
此选择器表示页面中所有具有 class
设置为蓝色的段落元素的集合。对于那些喜欢集合理论的人来说,这是 p
集合和 .blue
集合的交集。
让我们在下一个简单的例子中继续探讨一些集合理论:
#errorbox {
color:red;
}
与此选择器匹配的元素集合,在最理想的情况下,是一个单例,因为它匹配具有 id
设置为 errorbox
的元素,如果存在的话。我无法经常提醒你,没有两个元素可以共享相同的 id
。同样有效,但稍微更严格的是以下规则:
div#errorbox {
color:red;
}
前一条规则是关于任何具有 id
errorbox
的元素:后一条规则仅适用于具有 id
errorbox
的 div
元素。
多个类
我们了解到,可以通过将一个由空格分隔的字符串分配给其 class
属性来将多个不同的类分配给一个元素,例如:
class="green cool awesome"
如果你想为这样的元素创建一个 CSS 规则,选择器可能看起来像以下这样:
h2.green.cool.awesome
当用作 CSS 规则中的选择器时,该规则将应用于所有具有 class
green
、cool
和 awesome
的 h2
元素。因此,样式将更改为以下内容:
<h2 class="green cool awesome">The Hulk</h2>
这与以下选择器的规则完全不同:
h2 .green .cool .awesome
这将是应用于具有awesome
类、是具有cool
类的元素的后代、这些元素又是设置为green
类的标签的后代、同时自身又是h2
元素的后代的元素的规则的一部分。
这是一个非常重要的区别,因此非常重要要记住:如果允许我改写一首著名的歌曲:空格带来的差异有多大!
后代
我们迄今为止使用的示例选择器都是顶级选择器:一个元素、一个类或一个标识符。现在我们将添加更多细节和复杂性。看看以下示例:
#container h2 {
color:grey;
}
这条规则仅适用于位于具有id="container"
的元素内部的h2
元素,无论它们有多少层级。所以如果这是你 HTML 文件的一部分,这条规则将适用于包含它的h2
元素:
<div id="container">
<div id="header"></div>
<div id="main">
<h2>Title of this Document </h2>
</div>
</div>
以下 CSS 规则也会使标题变为灰色,但如果不幸有其他h2
元素位于#header
div 内部,它们不会受到这条规则的影响:
#container #main h2 {
color:grey;
}
选择子元素或兄弟元素
在某些情况下,你想要在选择上更加具体,并为其他元素的子元素设置规则,而不是在文档树中任意数量的不同层级下的后代元素。为此,你可以使用子选择器,如下面的示例所示:
#main > h2 {
color:grey;
}
h2
是具有 id main
的 div 元素的子元素,因此,根据这条规则,我们小块代码中的标题将以灰色显示。现在考虑以下示例:另一方面:
#container > h2 {
color:grey;
}
在这种情况下,将没有效果,因为h2
不是div#container
的子元素。
存在类似的语法来指定一个与另一个元素相邻的元素,或者称为家族树中的兄弟元素。参考下一个示例:
h2 + p {
margin-top:0px;
}
这不会给紧随h2
之后的p
元素添加顶部边距,但不会将此规则应用于后续的p
标签。看看以下小块 HTML:
<h2>Story</h2>
<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>
第一段不会有顶部边距;所有其他段落会有基于浏览器默认渲染段落的边距。当然,如果这条 CSS 规则是唯一的,并且没有被其他规则覆盖,那么这是真的,这带我们到下一个主题:特定性。
特定性
有时,作为一个 CSS 初学者或经验丰富的网络开发者,如果你只是在你样式表中添加了一条新的 CSS 规则,却发现它没有任何效果,你会感到沮丧。这种情况通常发生是因为有另一个具有更高特定性的规则具有优先权。
我们已经提到内联样式比外部样式表具有优先权。因此,内部 CSS 和外部样式表的链接顺序似乎会影响页面的最终外观。现在考虑以下规则:
p.warning {
color:red;
}
假设前面的规则后面跟着这条规则:
p.warning {
color:orange;
}
在这种情况下,所有具有warning
类的段落元素将以橙色显示,而不是红色,因为橙色规则出现得较晚。
但这两个规则恰好共享一个共同的选择器:它是一个具有warning
类的p
标签。
如果你有的规则选择器如下:
p#error {
color:red;
}
此外,如果你还有下一个规则:
body div.container div.main p.error {
color: orange;
}
让我们现在考虑以下内容:
<div class="container">
<div class="main">
<p id="error" class="error">
What you typed is incorrect
</p>
</div></div>
逻辑或直觉,但不是 CSS 中的顺序,会让我们认为文本你输入的内容不正确
将以红色显示,而不是橙色。实际上,这是正确的,但我们不希望依赖直觉,对吧?幸运的是,有公式可以确定如果有多个规则可能影响特定元素的布局,哪个 CSS 规则会获胜。这稍微有些数学性,被称为特定性。
CSS 规则的特定性是一系列四个数字,其计算方法如下:
-
如果规则是内联样式,第一个数字是 1,否则是 0
-
对于每个出现的标识符,将第二个数字加 1
-
对于每个指定的类,将第三个数字加 1
-
对于每个存在的元素,将第四个数字加 1
当比较两个规则时,首先查看第一个数字。如果其中一个更高,那么该规则就有更多的权重。接下来查看第二个数字,如果需要,再查看第三个,最后查看最后一个。我们两个示例规则的特定性是:0,1,0,1 和 0,0,3,4。
因此,我们的直觉现在得到了简单的算术的证实。
块级元素和内联元素
在我们最终按类别描述最重要的 CSS 属性之前,我们需要就两种元素类别说几句话:块级元素和内联元素。
将块级元素想象成你的屏幕或页面上的矩形区域。它们可以包含文本、数据和其它块级元素,以及内联元素。典型的块级元素是<div>
和<p>
标签。在每个块级元素之前和之后,都会创建一个新的文本行。
内联元素只能包含内联元素,不能包含块级元素。此外,它们不能被赋予宽度。它们从它们所在的容器继承宽度。最流行的内联元素是<span>
元素,通常用于改变更多文本中某段文本的外观。
首先,这可能会让人困惑,因为像<div>
这样的块级元素可能是块级元素,但如果没有内容,它们看起来似乎没有宽度或高度。此外,你可以使用 CSS 的display
属性更改这些元素的显示方式。考虑以下代码行,并让浏览器渲染它:
<p>paragraph1</p>
<p>paragraph2</p>
<p>paragraph3</p>
<p>paragraph4</p>
<p style="display:inline;">paragraph1</p>
<p style="display:inline;">paragraph2</p>
<p style="display:inline;">paragraph3</p>
<p style="display:inline;">paragraph4</p>
前四行被显示为块级元素,并且是真正的段落:它们之间会有一个新行,这些段落之间的距离将由浏览器选择的默认font
和margin
值决定。字体和边距属于最重要的 CSS 属性家族。当我们讨论字体属性以及所谓的margin
、border
和padding
的box
模型时,我们将了解它们。
如果视口有足够的空间,第二组段落将全部显示在同一行,因为我们明确声明它们是内联元素。
在我们讨论box
模型之前,让我们再看一个例子。尝试使用第一组 div 的前半部分,因为它们本身不会显示任何内容。一旦你在<div>
标签之间插入文本,这些文本就会与背景颜色一起显示。第二组中的<div>
元素实际上有宽度和高度,所以你会看到四个完美的彩色方块,但可能不是你预期的样子。我们将它们两两分组,但它们都堆叠在一起。欢迎来到浏览器无所谓的世界!
<style type="text/css">
.redbg {
background-color: red;
}
.greenbg {
background-color: green;
}
.yellowbg {
background-color: yellow;
}
.bluebg {
background-color: blue;
}
.sq200 {
width: 200px;
height: 200px;
}
</style>
<div>
<div class="redbg"></div><div class="yellowbg"></div>
</div>
<div>
<div class="greenbg"></div><div class="bluebg"></div>
</div>
<div>
<div class="sq200 redbg"></div><div class="sq200 yellowbg"></div>
</div>
<div>
<div class="sq200 greenbg"></div><div class="sq200 bluebg"></div>
</div>
颜色
使网站看起来瞬间更加愉悦的是正确使用颜色。这同样也是进行一些调试的有用工具。通过给某些块级元素添加不同的背景颜色,你增加了找到那个缺失的、导致混乱的闭合标签的位置的机会。我今天刚刚就用到了它。
通过专业制造公司发布印刷书籍、文档,甚至包装组件或媒体封面,与网络发布之间有着天壤之别。在印刷的世界里,你可以完全控制你使用的颜色,以及同样至关重要的字体或字型。你应该非常挑剔,并期望最终产品在颜色和字型上与你的指定完全一致。颜色信息以 RGB(红色-绿色-蓝色)或 CMYK(青色-品红色-黄色-黑色)的值进行交换。在 CSS 中,你也可以通过其 RGB 值指定所需的颜色。但比较就从这里开始了,并且结束了。
由于这个原因,我们知道高质量印刷品的颜色应该与我们预期的完全一致。然而,当我们准备网络内容时,我们无法知道我们的网站上的颜色将如何呈现给我们的访客。这取决于太多因素。例如,他们使用什么设备:电脑、平板电脑还是手机?他们查看的屏幕可以是大或小,高分辨率或低分辨率,支持数百万种颜色或只有少数几种。一旦我们意识到这一点,我们就可以放心了。因此,我建议使用足够鲜明的颜色,并且不要麻烦使用奢侈的 RGB 组合。
颜色可以通过几种方式指定。最常见的是通过名称和通过RGB值。名称更容易,但可能更容易受到浏览器的解释。所以我只在使用快速和临时的事情,或者它们是黑色或白色时使用它们。在其他所有情况下,我更喜欢使用 RGB 值,它们以#
符号后跟三个两位十六进制数字的形式书写,例如,#FFDEAD
,这是我最喜欢的颜色之一。它被称为纳瓦霍。
你可以使用颜色和背景颜色属性来设置前景和背景颜色。颜色表示将用于显示元素内部或元素的子代文本的颜色;背景颜色,正如属性名所暗示的,将设置块元素的背景颜色。
字体
我是一个字体爱好者。我甚至花钱购买字体,只是为了个人使用。所以我想指出,与颜色相比,打印出版和网页出版中使用字体的差异更大。
在印刷出版中,你可以完全控制你在工作中使用的字体
或字型
(这不是一本关于字体的书,所以请允许我交替使用这些术语)。你只需确保你计划使用的所有字体都已安装在你持有桌面出版
程序的系统上,这样你就可以用于开发。接下来,确保它们被嵌入到你发送给打印机的最终文档中,并且你和你的印刷公司都有所有使用的字体的合法许可证。然后,所有将阅读印刷副本或甚至在线 PDF 版本的人都会看到它就像你设计的那样。
在网上发布时,访问者将在你的页面上看到的文本只能使用他们使用的设备上实际安装的字体显示。最糟糕的情况,但看起来最好的情况是,你使用了一个非常漂亮、昂贵的字体,这个字体安装在了设计和测试你网站的系统中。它可能安装在其他任何电脑上都没有。
那个网站在你的电脑上看起来绝对棒,但在最关键的地方可能看起来很糟糕。这就是为什么在 CSS 中,我们与字体族而不是单个字体一起工作。
在深入细节之前,关于网络中世纪的一个需要注意的事情是,曾经有一个标签 <font>
——简单想一下——啊!中世纪,不要使用它。它是我们之前提到的那些表现性 HTML 元素之一。
那么,什么是字体?
简单来说,字体是一系列图片,或者说符号,决定了字母、数字和其他字符应该如何显示。在印刷的早期,大约 500 年前,古腾堡、普拉廷或莫雷图斯一次制作一个金属铸模。这些金属字母会被放在木箱里,大的放在上面,小的放在下面。这就是大写和小写这两个术语的由来。
要组成一页文本,必须将金属字母放入一个框架中,以将它们固定在一起,然后插入世界上第一台印刷机。再加上一些墨水和纸张,你就有了:一本书的一页的一个副本。当然,对于每一种不同的尺寸、厚度和样式(例如斜体与正常体),都必须有一个单独的字母框。
那些制作金属字母的人被称为冲床工人,字体以他们的名字命名。一个著名的例子是 Claude Garamond。你可以在安特卫普的 Plantijn-Moretus 博物馆看到原始的 Garamond 金属字体,它们被纸包裹起来以保护后世。那里你会发现,16 世纪的结合建筑师、肖像画家和“网页设计师”是彼得·保罗·鲁本斯,大多数人只知道他是一位画家。
现在,我们谈论的字体不再是小块金属,而是包含字体布局信息的计算机文件。一个字体包含的符号集合越多(更多的大小、样式或粗细),字母的再现就越准确。
字体家族
字体可以被分为不同的类别或家族。其中最重要的三个被称为衬线、无衬线和等宽字体。
衬线字体
衬线字体是具有在字母边缘某些部分带有小装饰或衬线的字型。例如,字母m底部每条腿的短线条。
在上一节中提到的由 Garamond 设计的字体是一个衬线字体的例子。衬线字体因其使阅读文本愉悦而受到欢迎。出版商在他们的书籍的精装版本中使用精心制作的衬线字体,并结合高质量的印刷纸张。Times New Roman,专为英国报纸《泰晤士报》设计,是一种非常常用的衬线字体。你现在正在阅读的文本就是使用 Times New Roman 字体。创作者以一个名为 Plantin 的字体为模型,而这个模型的名字来源于我之前提到的印刷师Plantijn。
Baskerville 是衬线字体的另一个例子,常用于电子书。
在网站的文字部分使用衬线字体是值得讨论的。因为符号必须使用屏幕上的像素来显示,那些在印刷中引起愉悦阅读的装饰在屏幕上可能会产生相反的效果。这是因为,在低分辨率屏幕上,那些相同的装饰可能会看起来过于像素化。我个人建议在做出决定之前,至少尝试在你的网站的主要文本部分实验一些衬线字体。
无衬线字体
无衬线字体是衬线字体的对立面。它们不包含笔画末尾的小装饰或衬线,因此得名无衬线(法语中意为没有衬线)。在印刷中,无衬线字体用于标题、标题等。这与用于正文文本的衬线字体形成对比。由于之前提到的原因,无衬线字体现在更常用于需要在计算机屏幕上显示的文本。常见的无衬线字体包括:Arial、Open Sans和Helvetica。本书的标题使用 Arial 字体。
等宽字体
在我们之前讨论过的字体中,并非所有字符的宽度都相同:字母m
显然比字母i
宽。等宽字体,也称为固定宽度字体,是一种字母和字符确实具有相同宽度的字体。最早的等宽字体是为打字机设计的,因为打字机的字车在每次输入字母时都会向前移动相同的距离。它们也被用于早期的计算机和计算机终端。软件文本编辑器至今仍在使用它们,因为当每个字符都有相同宽度时,这使对齐源代码变得更容易。
网站上显示的源代码通常以等宽字体显示,本书中的代码示例也是如此。最常用的等宽字体可能是Courier。另一个例子是Lucida Console。
让我们现在从对排版科学和历史的研究中回到我们的 CSS 故事。
字体家族属性
要指定元素文本的显示方式,我们在 CSS 样式表中使用font-family属性。例如,考虑以下内容:
p {
font-family: "Open Sans", "Helvetica Neue", Verdana, Helvetica, sans-serif;
}
那么为什么会有多个字体指定:一个字母只能在一个字体中,不是吗?
这就是它的运作方式。当有人访问您的网站时,如果您的样式表中包含这句话,那么一个段落文本将使用Open Sans
字体显示,如果它在访问者的计算机上安装了。如果找不到,将寻找Helvetica Neue
;如果那个也没有,则尝试Verdana
,依此类推。如果所有尝试都失败,将使用一些默认的无衬线字体。对于这个例子,最后一种情况非常不可能,因为我无法想象一个没有安装 Verdana 或 Helvetica 的系统。然而,建议始终以无衬线、衬线或等宽字体结束列表。注意,当字体名称由多个单词组成时,使用引号(例如,对于'Helvetica Neue')。
字体粗细和字体样式
当您使用桌面应用程序中选择字体时,您还可以从选择列表
中选择样式。一个典型的选择列表
可能包括:正常、斜体、半粗体、半粗体斜体、粗体和粗体斜体。列表上的项目数量通常与字体附带的字形集合数量相匹配。您可以访问销售字体的公司网站,以更好地了解单个字体主题上可能有多少种变化。
在 CSS 中,这通过两个不同的属性提供:font-weight和font-style。
字体样式的两个最常见值是正常和斜体。字体粗细的值是正常和粗体,以及数字100
、200
等等,直到900
。400
与正常相同,700
与粗体相同。所有其他值可能取决于字体中是否存在,比如说,半粗体版本以及所使用的浏览器。所以,直到我们达到书的第二部分,信息应该是响亮且清晰的——只使用基本的正常或斜体,以及正常或粗体。
字体大小
最后,就像在我们的桌面应用程序中一样,我们现在可以指定文本需要显示的字体,以及所有这些字母应该有什么大小。
大小可以用像素
、百分比
或ems
来指定。另一个存在的是称为rem
的单位。
当任何地方都没有指定字体大小时,典型的浏览器会将该大小设置为16px
。em
单位,一个来自排版的术语,指的是字体中字母m
的大小,在 CSS 中,它只是当前元素的字体计算大小。强烈建议使用它而不是固定像素大小。让我们用一个例子来说明这一点。看看下面的 CSS 代码:
h1 {
font-family: "open Sans", Arial, sans-serif;
font-size:2em;
font-weight:bold;
}
h2 {
font-family:Arial, sans-serif;
color:#999999;
font-size:1.5em;
font-weight:600;
}
如果没有为 body 元素指定字体大小,我们知道这将是16px
,所有后代都将继承它。这意味着我们的h1
标题将变为32px
,而h2
将是24px
。只需将 body 元素的字体大小更改为,比如说,20px
,就会按比例将我们的h2
和h1
标题的大小分别更改为30px
和40px
。如果我们给它们指定的大小是,例如,20px
和24px
,我们最终会得到与常规文本使用的字体大小相同的标题。
当用户使用浏览器放大或缩小视图时,我们保持比例。为了创建响应式设计,使用比例大小而不是固定大小是最佳选择。当然,你必须小心,并意识到当你更改元素的字体大小时,所有子元素都将继承它,并且当你错误地或不是错误地更改子元素的大小时,em
的大小将不再是刚才的大小。
考虑以下代码:
<div class="insert">
<p> A paragraph of text that represents what could be an insert in a book</p>
</div>
以下是相应的 CSS 规则:
div.insert {
font-family:Baskerville, "Times News Roman", serif;
font-size:0.8em;
color:brown;
}
.insert p {
font-size:0.8em;
}
你刚刚将字体大小缩小了一半,所以这段文字中的字母大小将是0.64
。
通常,我喜欢使用百分比,如下所示:
.insert p {
font-size:80%;
}
行高
对于处理文本还有一个重要的属性:行高。在实践中,这决定了两行文本之间的垂直空间。行高可以指定为一个数字,该数字乘以字体大小,一个像素
值,一个百分比
,或者单词normal
。通常,normal
由浏览器决定,通常在1.2
和1.4
之间。所以每一行的长度是字体大小的1.2
或1.4
倍。这样在字母的上下方就有一些空白空间。
对于大小为16px
的字体,以下三行 CSS 会有相同的效果:
p {
line-height: 24px;
}
p {
line-height: 1.5;
}
p{
line-height: 150%;
}
注意,这指定的是段落行之间的空间,而不是段落之间的空间。那将由边距决定,而边距是 CSS 最重要的概念之一:盒模型的一个方面。
盒模型
所有 HTML 元素都可以被视为盒子。在 CSS 中,当我们谈论设计和布局时使用术语盒模型。它本质上是一个围绕 HTML 元素的盒子,它可以由外向内包括:边距、边框、填充和实际内容。
到目前为止,在这本书中,我们只给出了简短的例子,这样你就可以在没有电脑的情况下学习,我们将尽可能将其作为教科书。然而,为了说明盒模型,以及让你理解它,检查我们的例子并在浏览器中查看是至关重要的。考虑以下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Paul Wellens - California anecdotes</title>
<link rel="stylesheet" href="styles/packtpubch3_1.css"
type="text/css" />
</head>
<body>
<div id="box">
June Lake, often called the gem of the Eastern Sierra, is a beautiful place that I visit as often as I can.
</div>
<div id="box2">
Mono Lake, saltier than the Black Sea, features tufa formations that makes the place look like it could be on the moon
</div>
</div>
</body>
</html>
然后是样式表文件的內容,其中我们使用了一些刚刚学到的知识,并结合引入所有的盒模型属性:
body {
font-size:12px;
background-color: #ffdead;
font-family:Arial, Verdana, Helvetica, sans-serif;
color:#999;
line-height:1.3;
margin:0;
}
#box {
width:150px;
height:150px;
background-color:teal;
color:white;
border: 5px solid orange;
margin:40px;
padding:20px;
}
#box2 {
width:150px;
height:150px;
background-color:blue;
color:white;
border: 5px solid yellow;
margin:40px;
padding:20px;
}
当我们在浏览器中显示这个时,我们将看到两个整齐堆叠在一起的方形盒子。两个盒子中都有文本;一个是绿色的,带有橙色的边框,另一个是蓝色和黄色的。视口的左侧和盒子之间、窗口的顶部和顶部盒子之间以及盒子之间的距离是相等的。你可以自由地更改 CSS 和 HTML 中#box
盒子的值,看看会发生什么。
我们有内容,我们的文本就在那里,我们指定为150
乘以150
像素。如果我们没有指定宽度和高度,我们最终会得到一个厚厚的、绿色的矩形区域,文本和边框跨越我们的视口。如果我们只移除边框和填充,我们只看到有背景色的文本;如果我们只移除文本,我们看到一个有边框和颜色的矩形;如果我们两者都移除,我们最终什么也看不到。最后,如果我们只放回宽度和高度,我们就有了一个精确的 150 x 150 像素的绿色正方形。
所以从内部开始,我们有指定或计算大小的内容。这就是我们元素的大小:在我们的例子中是150
乘以150
像素。
接下来我们可以有填充。这会使用与元素相同的背景色,并增加我们盒子的内部部分。在我们的例子中,元素的所有四边都增加了20px
。
然后,我们可以有一个边框。这会在我们指定的元素周围添加一个具有厚度、颜色和形状的边框。我们在所有边上使用了 5px
的实线边框,因此,到目前为止,我们的盒子已经是(5 + 25 + 150 + 25 + 5 = 210)x 210 像素。
最后,是边距。如果我们要边距,它就是元素盒子外部的区域。它是透明的,所以它有父元素的背景颜色。它仅仅在盒子与相邻盒子之间创建距离。在这个例子中,我们使用了 40px
的边距。这使得我们的盒子总大小为(40 + 210 + 40 = 290)x 290 像素。
如果你尝试了这个并且是一个细心的人,你可能注意到了一些不合理的地方。好样的!我们稍后会解释这个问题。现在,我们将回顾所有你可以使用的盒子模型属性。
填充
你可以指定或取消元素四周的填充。为此,你可以使用以下四个属性:
-
顶部填充
-
右侧填充
-
底部填充
-
左侧填充
这里有一个例子:
.menulabel
{
padding-left: 8px;
padding-right: 7px;
padding-top: 4px;
padding-bottom: 5px;
}
你注意到我改变了顺序吗?我是故意这样做的。我使用的第一个顺序是 padding
属性简写版本支持的顺序。第二个顺序是我设计时考虑的顺序:水平方向上是什么,然后是垂直方向。所以,相同的四行 CSS 可以替换为以下单行:
.menulabel {
padding: 4px 7px 5px 8px;
}
在第一个例子中,我们使用了一个单一值,这意味着四倍相同的值。简写版本也有两个和三个值的变体。例如,以下将顶部和底部设置为 10px
,左侧和右侧设置为 15px
:
.menulabel {
padding: 10px 15px;
}
边框
对于边框属性,你也可以为顶部、右侧、底部和左侧指定不同的值,但不仅仅是宽度。你还可以指定形状、样式和颜色。所以,以下是所有三个的属性:
-
边框宽度
-
边框样式
-
边框颜色
对于这三个中的任何一个,你都可以在任意方向上使用一个属性,例如 border-top-style,所以有很多属性可以选择——虽然拥有这么多并不实用。我不认为每个边都有不同形状、颜色和大小的边框会设计得很好!
你可以选择的最常见的形状或样式有:
-
无
-
实线
-
双倍
当然,还有更多。双倍边框可以提供双重边框,有时甚至可以起到装饰作用。实线是我建议你大多数时候使用的。
myimg {
5px solid white;
}
那为什么你需要没有边框呢?如果我不想有边框,我直接不指定不就行了,对吧?错误! 一些浏览器,例如 Internet Explorer,会自动在任意的 img
元素周围添加一个 1px 的白色边框。所以,很高兴你有选项来处理这种情况:
img {
border:none;
}
我们已经在例子中使用了简写表示法,所以我只需要提醒你包含事物的顺序;我自己也经常忘记:宽度 样式 颜色。
能够在事物周围添加边框是一个非常酷的特性,特别是对于照片来说。网络上的照片“边框”部分可以简单地是一个精心制作的img
元素的边框。
边距
最后,还有边距属性,它清除元素周围的边框。它没有背景颜色,因为它是不透明的。这五个属性是:
-
margin
-
margin-top
-
margin-right
-
margin-bottom
-
margin-left
你可以指定边距大小,无论是像素、百分比还是其他,就像你可以指定填充一样。然而,你可以设置边距的一个额外、极其有用的值:auto。将我们示例中的边距设置更改为:
margin: 40px auto;
就像魔法一样,你的两个正方形盒子将在水平方向上居中。如果你调整浏览器窗口的大小,它们仍然会居中。将边距设置为 auto 时,浏览器会为你计算相对于父元素的左右边距。许多网站都有一个主要的div
元素,它是body
元素的子元素,样式类似于以下内容:
#container {
margin: 0px auto;
border:none;
max-width:980px;
}
经典的网页开发使用一个固定宽度的画布,以及固定的高度,将所有内容放置其中。这个例子使用了980px
。多亏了auto
边距,左右将会有自动边距,计算为剩余水平空间的一半。max-width
是新的,与width
不同。宽度总是给你 980 像素,而max-width
只有在有 980 个水平像素可用时才会生效。如果不是这样,比如在智能手机上,视口的(较小的)完整宽度将成为宽度。这是向响应式设计迈出的一小步。
边距合并
你已经学习了盒模型,使用了我们的示例,可能也在想为什么两个顶部和底部边距为 40px 的正方形盒子之间只有40
像素,而不是 80 像素。
好吧,这并不是一个错误,而是一个特性。W3C 规范规定,当两个元素的垂直边距相接触时,较大的那个将生效,而另一个将被减小到 0。一些其他的 CSS 设置可以改变这一点,但这是默认行为。一旦你做了很多网页开发并习惯了这一点,你可能会觉得这实际上很有道理。我们将通过向 HTML 和 CSS 中添加两个带有图片的盒子来结束本节,基本上创建了一个相册的前两个条目。我们将结合所学内容,并找出仍有哪些不足。
这里是 HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8" />
<title>Paul Wellens - California pictures</title>
<link rel="stylesheet" href="styles/packtpubch3_photo.css" type="text/css">
</head>
<body>
<div class="entry">
<div class="picturebox">
<img class="picture" src=http://www.paulpwellens.com/packtpub/images/junelakefall.jpg alt="junelake" />
</div>
<div class="textbox">
June Lake, often called the gem of the Eastern Sierra, is a beautiful place that I visit as often as I can.
</div>
</div>
<div class="entry" >
<div class="picturebox">
<img class="picture" src="img/monolake.png" alt="monolake" />
</div>
<div class="textbox">
Mono Lake, saltier than the Black Sea, features tufa formations that makes the place look like it could be on the moon
</div>
</div>
</body>
</html>
将以下内容放入你的stylesheet
:
.picture {
width:200px;
height:130px;
border:5px solid white;
margin-left:25px;
margin-top:40px;
}
.entry {
width:600px;
margin:0 auto;
}
.picturebox {
background-color:teal;
color:white;
width:270px;
height:220px;
text-align:center;
vertical-align:middle;
/* float:left; */
border-bottom:1px solid #FFDEAD;
}
.textbox {
background-color:teal;
color:white;
width:250px;
height:180px;
padding:40px 10px 0px 10px;
/* float:left; */
border-bottom:1px solid #FFDEAD;
text-align:left;
}
如果你在这个例子中运行浏览器,你会注意到,尽管用另一个div
标签包裹了它们,图片和与之相关的文本仍然不是并排的,而是堆叠在一起的。解决方案已经存在,但被放在了/*
和*/
字符串中作为注释。取消注释这些行,就像魔法一样,一切都会看起来像你想要的那样。
定位
您可以使用几个 CSS 属性来改变页面元素的位置。最具影响力的一个被称为 float(每次我使用它时,它都会让我想起斯蒂芬·金小说和电影《它!》中的小丑角色,当它说:“它们都飘浮着!”)
浮动
我认为 CSS 浮动属性是 CSS 堆叠元素的另一种方式。如果您给所有这些元素一个:
float:left;
您可以从左到右堆叠它们。使用一个:
float:right;
您可以从右到左堆叠它们。当您想在有空间的情况下将页面的一部分,例如一个介绍,放在右侧,如果没有空间则放在顶部时,这会非常有用。在我们的上述示例中,将左浮动改为右浮动会将图片放在右侧。
position:relative
CSS position 属性可以用来将元素定位在页面上一个不同于它们通常位置的地方。"通常" 与 position:static
相同。看看以下代码:
#redsquare
{
width:100px;
height:100px;
background-color:red;
}
这将在其父元素的左上角产生一个红色方块。现在,尝试以下操作:
#redsquare
{
position:relative;
width:100px;
height:100px;
background-color:red;
left:10px;
top:100px;
}
红色方块向下移动 100px
,向右移动 10px
。
position:absolute
让我们添加:
#container
{
width:700px;
margin:50px;
background-color:teal;
height:500px;
}
和
<body>
<div id="container">
<div id="redsquare">
</div>
</div>
</body>
红色方块现在位于青色方块内部,向下 100px
,向右 10px
。当您将相对改为 绝对 时,红色方块将向下 100px
,向右 10px
,相对于祖先元素。这通常是浏览器窗口本身。
列表样式
您最终会大量使用的一个元素是 <ul>
标签:无序列表。默认情况下,列表中的每个项目都将显示一个圆形项目符号在文本前面。使用 CSS,您可以更改列表的样式。
list-style-type
使用此属性,您可以更改项目符号的形状。您可以使用的某些值包括:none
(根本不显示项目符号),square
(一个方块),circle
(一个小圆圈),或 disc
(默认)。
list-style-image
您可以使用 list-style-image 属性来为项目符号提供自己的图像。默认值是 none
,这意味着项目符号图像由 list-style-type 的值决定。但是,如果您指定 url
,然后是图像的路径,则将使用该图像,例如:
url('smiley.gif')
list-style-position
默认情况下,项目符号出现在内容流之外。如果您将 list-style-position 的值指定为 inside
,则项目符号将移动到内部,文本将更靠右。
锚样式 – 伪类
我们通过介绍一些伪类来结束对 CSS 属性的选择,这些伪类通常但不仅限于与 <a>
标签一起使用,即 锚 标签。锚标签主要用于链接。为了使它们确实看起来像是链接,<a>
的默认样式恰好是蓝色,并且文本被下划线,这并不太吸引人。
使用伪类,你可以根据光标相对于链接的位置以及链接是否已被访问,给锚标签(理论上任何标签)赋予不同的外观。以下是一个示例:
a:link {
text-decoration:none; /* switches off the underline */
}
a:hover {
color:white; /* changes color to white when the curser hangs over it (hover) */
}
a:visited {
color:yellow; /* changes the color to indicate that you already visited that link */
}
Firebug
无论你如何学习这一章节以及关于 CSS 的各种在线参考资料,时不时地你会发现事物并不像预期的那样显示。这时,一个调试工具如Firebug就派上用场了。Firebug 是 Firefox 浏览器的扩展程序。它允许你点击页面上的部分,然后程序会显示相关的 HTML 和 CSS,甚至还会显示一个显示填充、边框和边距的盒模型图片。大多数其他浏览器,特别是 Safari 和 Chrome,都有类似的对应工具。
摘要
在本章中,我们为您概述了 CSS。这不是一个完整的参考,但我们确实包括了所有浏览器都应该支持的常用和有用的 CSS 属性。我们故意没有包括一些在 CSS3 中引入的新属性。本书的第二部分将介绍一个重要的新 CSS 特性:媒体查询。这对于构建响应式设计至关重要,但这个主题至少需要一章来详细讨论。
到目前为止,我们已经学习了创建网站所需的两种语言:HTML 和 CSS。无需进一步延迟,我们现在继续学习下一个,一种真正的编程语言:JavaScript。
第四章:JavaScript
到目前为止,我们已经学习了三个章节,掌握了两种语言:HTML 和 CSS。它们被用来创建网页,为其添加内容,并为其添加样式。在本章中,我们将学习第三种语言,JavaScript,它被用来编程你的网页,并为其添加活力。在相当长的一段时间里,JavaScript 被一些人视为二等公民。如果曾经有这种说法,那么现在肯定不再是这样了。在我们开始学习 JavaScript 之前,让我们先了解一下编程的世界。
编程 101
没有什么是智能计算机。计算机是一种能够执行少量指令的设备,但可以非常快地执行。这样的指令可能包括从一个地方取一个值,从另一个地方取另一个值,将这两个值相加,并将结果存储在第三个地方。程序是一系列这样的指令,以某种逻辑和结构化的顺序写下,并以人类可读的格式。这种格式被称为编程语言。一个智能的程序会将我们描述的无用设备变成计算器。
对于许多编程语言来说,存在一种特殊的程序,称为编译器。它将程序从人类可读的格式(通常是一个称为源代码的文本文件)翻译成计算机可以理解的格式,通常是二进制可执行文件。
你的计算机或平板电脑包含许多不同种类的程序。有一个程序来管理计算机,有程序来管理程序,还有其他程序来创建程序,有一个程序让你输入命令,这些命令反过来又是程序的名称,等等。最后一个碰巧也是一种编程语言,有时被称为shell或命令解释器。
如果你将一些命令组合到一个文本文件中,你又一次得到了一个程序:一个 shell 脚本。这是一个解释型语言而不是编译型语言的例子。
但这是编程 101,而不是计算机 101。看看以下用一种虚构的编程语言编写的源代码行:
a = 1;
b = 2;
c = a + b;
printnumber (c);
字母a
、b
和c
是变量——可以存储值的东西。这些值可以是数字、名称、完整的员工记录等等,以及包含更多变量和值的表达式的值。你可以在=
符号的右侧找到这些值,这个符号是一个运算符。这个运算符将右侧的内容分配给左侧变量的值。+
也是一个运算符。正如你可能已经猜到的,它将两个东西相加。变量必须首先声明,告诉任何或任何将要阅读这个的人你将要使用哪些变量。然后它们需要初始化,这意味着给它们一个初始值。如果你不这样做,可能会发生奇怪的事情。在这个例子中,声明和初始化都在同一行上完成。最后,我们使用了printnumber(c)
。
这是一个只有一个参数的函数的名字,变量c
。假设它会负责显示值:你的程序只有在你也提供了该函数的代码,或者它已经被为你编写并作为函数库的一部分与语言一起提供时才能工作。
因此,我们编写了一个四行程序来计算数字3
。对于这样一个简单的任务,这至少多了 3 行,但我们需要一个例子。
在我们使用的变量中,没有提到变量包含什么类型的值。在一些语言中,例如 C 编程语言,当你声明变量时,你必须指定你指的是什么类型的变量。以下是一个类似的 C 程序:
int a, b, c;
char someletter; /* to hold a letter not a number */
char *name; /* to hold a string of letters */
float someFloat; /* to hold a floating point number */
a = 1;
b = 2;
c = a + b;
printnumber (c);
编译型和解释型语言的比较
我第一次使用 C 编程语言时,对我来说是个启示。我使用全屏编辑器输入我的代码,按了某个地方的按钮,由于没有错误,我可以立即运行我的程序,看看它是否按我预期的那样工作。在大学时,情况不同。我必须使用带有穿孔卡的机器输入我的Fortran
程序,一次一行,然后把穿孔卡留在数据中心,用橡皮筋绑在一起。两天后,我可以和打印列表一起取回它们,那里写着:第 37 行:缺少分号。当然,那已经是很久以前的事了。
使用需要编译的语言有优势,尽管人们的观点可能不同。在你可以运行你的程序之前,必须经过两步过程,一个好的编译器可以彻底检查你的代码,并产生各种错误和警告信息,如语法错误
、未声明的变量
、非法类型
等等,包括行号和有问题的语句。在像 JavaScript 这样的解释型语言中,以及在一定程度上可能是PHP,这些错误通常被默默忽略。在完美运行的程序中加入一行带有无辜错误的代码,可能会让它完全停止运行。
解释型语言能给你即时的满足感。你用你喜欢的编辑器输入它们,就像我用 iPad 上的Textastic
一样,立即打开内置浏览器,看到结果(或者什么都没有)。你也不会遇到当你创建程序并运行它们之前,需要在系统上有一个编译器的问题。这个编译器通常是完整开发环境的一部分。
因此,我们为编程做好了准备,并学习了或重新学习了与编程相关的大量重要术语。这些是:
-
变量
-
值
-
类型
-
关键字或保留字
-
运算符
-
控制流
-
函数
-
编译器和解释器
-
库
-
表达式
-
语法
现在我们将教你们这些是什么:对于 JavaScript 语言,在本章中,对于 PHP 在下一章中。在我们这样做之前,既然我们已经理解了这些词,我们首先需要解决一个重要的话题。
JavaScript 与 Java 不同
我已经多次提到,我多年前参加的那个关于 Web 开发的六个月课程。在课程进行到三个月时,有一个同学还在混淆 Java 和 JavaScript。现在他是一名前端开发者,所以他最终肯定已经明白了。
我们希望你在接下来的三分钟内看到它。Java 和 JavaScript 都是编程语言。它们共同之处在于它们名字的前四个字母,Java,这是一个国家的名字,也让很多人想到了咖啡,以及一些晚期的 Sun Microsystems。那家公司,连同 Netscape,创造了 JavaScript 这个名字,也是 Java 编程语言的创造者和推广者。因此,人们有时感到困惑并不奇怪。
Java
Java 是一种编程语言,由 Sun Microsystems 的詹姆斯·高斯林(James Gosling)开发。它的语法很多都来自 C 编程语言,有一些部分被省略了,比如指针,这恰好是我最喜欢的 C 语言的部分。
与 C 语言相比,C 语言更像是“一次编写,到处编译”的语言,Java 是我所说的“一次编译,到处运行”的语言。Java 编译器将 Java 源代码从文本文件转换成平台无关的字节码文件,因此你可以在任何有 Java 运行时的地方运行它。你见过那些说你需要安装更新版本的 Java 运行时的消息吗?那就是 Java,不是 JavaScript。
Java 应用程序通常在服务器或桌面计算机上运行,但你也可以在 Web 开发中使用 Java。这本书中我们将首先使用 PHP(第五章;
如果您在浏览器中运行这段代码,屏幕上会出现一个包含*Hello, World*文本的弹出窗口。如果您按下**OK**按钮,弹出窗口将消失。如果您在不同的浏览器中尝试此操作,结果将相同,但弹出窗口的外观将完全不同。浏览器会按照它想要的方式渲染,我们对此无能为力。
我们也可以将这一行代码放入一个名为`hello.js`的文件中,放在`js`文件夹里,然后我们的程序可以说以下内容:
```php
<script type="text/javascript" src="img/hello.js">
</script>
让我们简要分析一下这一行 JavaScript 代码。该行以分号(;
)结尾。它以alert
开头,这是 JavaScript 附带的一个函数的名称。弹出窗口的文本作为唯一的参数提供。在这个例子中,该参数是一个包含文本Hello, World的字符串。为了表示这是一个字符串值而不是变量,我们使用了双引号。单引号也可以。
变量
在 JavaScript 中,变量的名称必须以字母开头。还有一些其他字符,如$
符号,也被允许,但我们不会使用它们。这有助于我们尽早区分 JavaScript 变量和 PHP 变量。名称也是大小写敏感的,所以first
与First
不同。
当然,使用非常短的名字,如a
和b
,是可以的,但如果我们想给变量赋予有意义的名字,一些最佳实践指南建议使用如firstName
、lastName
这样的名字。
变量声明
变量使用 var
关键字声明,例如:
var firstName;
var lastName;
var dayOfBirth;
或者简单地一行显示,如下所示:
var firstName, lastName, dayOfBirth;
当变量被声明但未初始化时,它们的值是 undefined
,这意味着没有变量类型。要给变量赋值,我们使用 =
符号。因此,在适当的声明之后,你可以赋值如下:
firstName = "Paul";
lastName = "Wellens";
当然,你也可以一步完成,如下所示:
var firstName = "Paul";
var lastName = "Wellens";
变量的值
正如我们提到的,没有变量类型。作为值,我们可以分配几乎任何东西。以下是简要概述。但请记住,我们不是参考手册,所以它并不完整。
数字
最常见的数字是整数。只需使用组成数字的数字即可,所以 myNumber = 127;
将十进制数 127
赋值给变量 myNumber
。如果添加一个前导 零,结果将不同。行 myNumber =
0127; 将赋值八进制数 127
,这在十进制中等于 164 + 28 + 7 = 87。
十六进制 数字也可以使用,通过在数字前加上 0x
。所以 myNumber = 0x127;
将存储 256 + 2*16 + 7 = 295。
最后,我们可以有浮点数。始终使用点,而不是逗号,即使你在法国或其他将浮点视为浮点逗号的国家(virgule flottante,请原谅我的法语!)。
MyFloat = 123.456;
字符串
字符串包含零个或多个字符,这些字符被单引号 ('
) 或双引号 ("
) 包围。因此,可能的最短字符串是空字符串。考虑以下内容:
emptyString = "";
helloWorld = "Hello, World\n";
notANumber = "345";
twoLines = "First line\nSecond line";
你可能在其他语言中见过 \n
。这是一个转义序列的例子,在这里表示换行符。"345"
这个例子很重要。notANumber
变量确实包含一个字符串,而不是一个数字。
将字符串转换为数字
有一些方便的函数可以用来将字符串转换为数字:parseInt()
和 parseFloat()
。它们将字符串或初始子字符串转换为整数或浮点数,直到找不到更多的数字。如果字符串无法转换,函数返回 Not a Number (NaN)。以下是一些示例,其中我们列出了函数返回的内容作为注释:
/*
Examples using parseInt() and parseFloat()
This is a multiline comment
*/
parseInt("10 yard line"); // returns 10
parseInt("one two three"); // returns NaN
parseFloat("3.14"); // returns 3.14
parseInt("3.14"); // returns 3
parseFloat("3,14"); // returns 3
parseInt("notanumberatall"); // returns NaN
parseInt("0xff"); // returns 255 which is FF hex
/*
parseInt() can also take a second argument specifying the base of the number to be parsed
*/
parseInt("ff",16); // 255, same as above
parseInt("11",2); // returns 3, 1*2 + 1
parseInt("11", 8); // returns 9, which is 1*8 + 1
表达式和运算符
本节解释了在 JavaScript 中 表达式 和 运算符 的工作原理,重点关注我们将使用该语言做什么——编程网站。因此,不要期望对所有位运算符有深入的解释,例如。如果你不理解我刚才说的,那完全没问题。但如果你需要知道它们是什么,你可能还需要另一本书或参考资料。
表达式是 JavaScript 语句的一部分,通常是=
运算符右侧的部分,JavaScript 解释器可以对其进行评估。结果将是表达式的值。我们已知的最简单的表达式:值和变量。在下面的例子中,i + 7
也是一个表达式。它将评估为 8。它中间包含一个运算符+
。它的左右两侧的部分可以被称为操作数。
var i = 1;
var j;
j = i + 7;
我们现在将介绍最基础的运算符。对于那些已经了解 C、C++或 Java 的你们来说,它们看起来会非常熟悉。
算术运算符
这是用来计算的一组运算符。
加法(+)
+
运算符将两个操作数的值相加,也就是说,如果它们是数字,就像上一个例子中那样。如果它们不是数字,程序会挂起吗?当然不会。记住,你将使用 JavaScript 来程序性地修改网页。网页反过来又由 HTML 组成,而 HTML 又是由很多字符串组成的。所以我们的加号运算符已经成为你拥有的最有用的工具之一。看看下面的例子:
var hello = 'Hello, World';
var htmlString;
htmlString = '<h1>' + hello + '</h1>';
所以,在 JavaScript 中,加号运算符可以用来拼接,或者,用更技术性的术语来说,concatenate
字符串。在 PHP 中我们也会遇到同样的功能,但那里的运算符是点(.
)。尽早习惯这些差异永远不会太早,因为你将大量使用它们。
在一个例子中,我们使用了+
运算符的一个特殊情况。counter++;
是人们从 C 语言等语言中熟悉的一种简写符号,JavaScript 和 PHP 都支持这种简写符号。它是一个简写符号,用来简单地将1
加到变量的值上。所以以下两个语句是相同的:
varName = varName + 1;
varName++;
减法(-)
每个使用过计算器的人都知道减号。它用于减去数字。虽然+
符号也可以与字符串一起使用,但在减法(以及乘法和除法)中这样做是没有意义的。还有一个类似于++
的简写符号,看起来像"—
"。
varName = varName - 1;
varName—;
它们确实也是相同的。
到目前为止,我们已经使用了一个包含两个以上操作数的例子,那就是我们用来组合 HTML 字符串的那行语句。但如果我们做以下操作会发生什么呢:
var result;
var a = 7, b = 5, c = 2;
result = a - b - c;
alert (result);
它会是 0,还是 4?答案是 0,因为表达式是按照从左到右的顺序进行评估的,但你可以通过使用括号来消除所有疑虑。看看下面的例子:
result = (a - b) - c; // 7-5 is 2 2-2 is 0
result = a - (b - c); // 5-2 is 3 7-3 is 4
乘法(*)
*
运算符将其两个操作数相乘。
除法(/)
/
运算符将其第一个操作数除以第二个操作数。结果始终是一个浮点数,所以 5 / 2 评估为 2.5。在某些语言中,如果两个操作数都是整数,这将是两个。
模数(%)
%
运算符非常实用。它返回第一个操作数对第二个操作数的余数。例如,5%2 评估为 1,因为 5 除以 2 的整数商是 2,余数是 1。我经常在样式中使用它,当我想要对偶数和奇数做不同的事情时。在代码中使用if (number%2)
意味着奇数,因为如果括号内的值不是零,则为真。
关系运算符
编程中的一个常见错误是使用类似if (a = b)
的东西来比较两个东西,并发现即使a
不等于b
,它总是为真。这是因为许多语言,包括 JavaScript 和 PHP,单个等号是赋值运算符。要检查两个东西是否相等,你需要(至少)两个等号。使用以下运算符的运算符的表达式将评估为真或假,在 JavaScript 中相应的值不是1
或0
,而是实际上true
或false
。它们如下所示:
-
等于(
==
):我们在这个表达式中使用它来检查两个变量是否包含相等的值,例如如果(a == b
) -
不等于(
!=
):当然,这是相反的情况:如果不相等则为真,相等则为假 -
小于(
<
) -
小于或等于(
<=
) -
大于(
>
) -
大于或等于(
>=
) -
非(
!
)
这会将假转换为真,反之亦然,当它放在表达式前面时。这可以帮助使你的程序更易于阅读。
当然,当单独使用时,这样的表达式并不很有帮助。它们需要成为某个结构的一部分,我们添加控制来使我们的程序在表达式评估为假而不是真时执行不同的操作。
控制流
控制流是编程的核心。控制流语句有助于确定随后的代码中哪些部分被执行,哪些部分不执行。在任何语言中最常用的控制流语句可能是if-else
语句。我们将引导你了解 JavaScript 中最有用的那些。
我们学习了什么是表达式。我还把一条 JavaScript 代码(以分号结尾)称为一个语句。你可以通过将大括号({
和 }
)放在它们周围来将语句组合在一起。这样一组语句被称为语句块。即使只有一个语句,当用于下面描述的任何一个控制结构中时,使用大括号也被认为是最佳实践,其中“语句”代表语句块。
如果(if
)
if
语句的格式是:
if (expression)
{
statements
}
你也可以将if
与else
结合起来,如下所示:
if (expression) {
statements
}
else {
statements
}
示例:
if (a < b ) {
alert ("a is smaller than b" );
}
else
{
alert ("a is not smaller than b");
}
当(while
)
而一个if
语句是你的基本控制语句,根据条件或其它东西来做某事,while
语句则非常适合创建循环。循环是一系列在某个条件为真(或while
)时重复执行的语句。确保你的条件不是永远为真非常重要,否则你最终会得到一个无限循环
。
以下是格式:
while (expression)
statement
接下来是一个例子:
var count = 0;
var seriesA = "";
while (count < 100)
{
seriesA = seriesA + 'a'; // can also be written as seriesA += 'a';
count++; // if you forget this one you are in trouble
}
alert(seriesA);
这段代码的结果将弹出一个包含字母 a 重复 100 次的字符串。
switch
我喜欢switch
语句。如果你需要根据超过 2 个不同的可能条件执行不同的代码,使用switch
语句比多个if
/else
语句更易于阅读和维护。通常,将被评估的表达式将是一个变量,然后将有几个在变量值为value1
、value2
、value3
等情况下执行的语句。
它是这样的:
switch (expression) {
case value1:
statement
break;
case value2:
statement
break;
// etc. etc.
default:
statement // or no statement
break;
}
首先,评估表达式。根据表达式的值,执行属于匹配值的语句。如果没有匹配项,则执行与default:
关联的语句。break;
语句很重要,但不是强制的。在switch
语句中,首先执行的语句块是属于第一个匹配的。如果省略了break;
语句,则还会执行后续的语句,而这通常不是我们想要的。我通常首先编写一个switch
语句的骨架,包括case:
和break;
,然后填充实际的代码。
现在,让我们来看一个例子,并对此进行一些有趣的探索:
var plate;
plate = getPlateName();
switch (plate) {
case "first":
Who();
break;
case "second":
what();
break;
case "third":
IDontKnow();
break;
case "home":
today();
break;
default:
strike();
break;
}
函数
我们已经在我们的例子中使用了一些函数。函数是一段 JavaScript 代码,你可以自己编写,也可以由 JavaScript 实现预定义。函数可以传递参数,也称为参数。以下是我们自己编写的函数示例,用于计算作为第一个和唯一参数传递的数字的平方根:
function square (x)
{
var sq;
sq = x * x;
return sq;
}
square
是函数的名称。它由大括号之间的所有代码组成。我们称它为function
块。一旦编写了函数,就可以反复使用。在这个例子中,函数实际上也返回一个值;在这种情况下,是参数的平方根。这是通过使用return
语句完成的:return sq;
。请注意,在return
关键字之后没有括号,这与其他编程语言的情况不同。那将意味着会有一个返回的function
!所以让我们多次使用我们的函数:
var message, result;
result = square(3);
message = "the square of 3 is" + result;
alert(message);
function square (x)
{
var sq;
sq = x * x;
return sq;
}
alert("the square of 4 is " + square(4));
如你所见,我们可以在声明前后使用函数。我们首先可以将消息存储在变量中,或者直接将其传递给alert()
函数。
变量的作用域
在本章介绍函数的这一部分,解释一个非常重要的概念:变量的作用域,以及全局变量与局部变量的概念。
如果你一直在练习,我真诚地希望你已经勤奋地使用var
关键字声明了所有你的变量。但是,如果你在某处忘记了它呢?在这种情况下,你可能是无意中声明了一个全局变量。所以作用域,即变量可以在其中使用的 JavaScript 程序区域,是任何地方,我们称之为全局。
使用var
关键字声明的变量具有局部作用域。局部变量仅在其声明的函数体内定义。作为函数参数传递的变量也具有局部作用域,并且仅在该函数体内定义。
如果你声明了一个与已存在的全局变量同名的地方变量,那么这个地方变量实际上会在函数内部“隐藏”全局变量。但是,如果在函数内部,你给一个变量赋值而没有先声明它,你可能会覆盖掉程序其他地方需要的值。这就是为什么在函数开始时声明所有变量很重要的原因。花几分钟时间研究下面的例子,一切都会变得清晰。
// global variables
outside = "outside";
global = "global";
var same ="outside"
function testfunction()
{
// inside function
global = "local"; // mistakenly modified global variable
var inside = "inside"; // local variable
var local;
var same = "inside";
alert(inside); // prints inside
alert (outside);// prints outside
alert(local); // prints "" because it was not initialized
alert (same); // prints inside
alert(global); // prints local because we modified it
}
// running testfunction()
testfunction();
// after running testfunction()
alert (outside); // outside
alert(global); // global because we had overwritten it
alert(same); // prints outside
对象
在本章的早期,我提到变量可以包含几乎所有东西:数字、字符串,甚至是完整的员工记录。但在我们迄今为止看到的例子中,似乎没有适合如此复杂如员工记录的容器。这就是对象发挥作用的地方。在这里,我们将学习如何创建它们。
对象是命名值的集合,它们是通过一个称为构造函数的特殊函数创建的。JavaScript 提供了几个这样的函数。有一个通用的,Object()
,但也有一些用于创建具有预定义结构的对象的特定函数,例如Date()
或String()
。语法如下:
employee = new Object();
现在我们已经创建了一个名为 employee 的空对象,我们可以用名称和值填充它,如下所示:
employee.firstName = "John";
employee.lastName = "Williams";
employee.profession = "conductor";
在 JavaScript 对象的上下文中,这些名称被称为属性。属性反过来有值。对象还可以包含用于在对象内部执行操作的函数。这些函数被称为方法。例如,String()
对象包含相当多的字符串操作方法。查阅一本好的参考书或在线 JavaScript 对象参考可以减少编写代码的数量,因为所有这些都已经有了。以下是一个String方法的示例:
hello = new String('Hello, World"); // Or simply hello = "Hello, World";
Hello = hello.toUpperCase();
alert (Hello); // This will show HELLO, WORLD in a pop-up box
JSON
创建对象还有另一种方法,不需要构造函数。
employee = {}; // Creates an empty object
employee.firstName = "John"; // same as before
// or do it all at once
employee = { firstName:"John", lastName:"Williams" };
右侧匹配名为JavaScript 对象表示法(JSON)的数据格式。在这本书中,我们将专门用一章来介绍 JSON,我们将说服你这是最酷的数据格式;所以关于 JSON 的更多内容将在后面介绍。
DOM 对象、属性、方法和事件
我们已经向您介绍了 JavaScript 对象的基础知识,现在我们将深入探讨客户端 JavaScript 的核心——DOM 编程。
窗口对象
当你在浏览器中运行网页时,有两个对象可供你使用——Window 对象和Document 对象。
窗口对象为我们提供了访问可以告诉我们用于渲染网页的窗口状态的属性和方法。我们已经在每个示例中都使用了一个这样的方法:alert()
。考虑以下内容:
alert("Hello, world");
这实际上是以下内容的缩写:
window.alert("Hello, world");
类似的有用的创建对话框的方法有prompt
和confirm
。前者显示一个对话框,提示访客输入;后者显示一个带有消息和确定和取消按钮的对话框。请参考下一个示例:
var response = confirm ("Are you sure you want to do this?");
if (response == true) {
// if (response) has the same effect
doit();
} else {
donot();
}
窗口对象也有一些有趣的属性。你想知道当前窗口的宽度是多少像素?你可以通过以下方式访问:
var windowWidth = window.innerWidth;
窗口对象最重要的属性本身也是一个对象——DOM 文档对象。
文档对象
window.document(但你不必写 window 部分)是一个属性,它给你一个对象,该对象包含浏览器加载的整个网页。
write 和 writeln 方法
在我们的大部分示例中,我们使用了 window alert()
方法来显示带有一些文本的弹出框。虽然这对于测试或调试很有用,但它可能会让人感到烦恼,因为你必须点击该框才能看到下一行文本。大多数教科书都会使用文档方法write
和writeln
。使用这些方法,你可以直接将消息文本写入你的文档。writeln
与write
的不同之处在于它在每个语句后添加一个新行。只需尝试以下操作:
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p> The following text is generated using JavaScript </p>
<script type="text/javascript">
document.write("Hello, World");
document.write("On the same line");
</script>
</body>
</html>
节点和 DOM 遍历
我们将使用文档方法来遍历或遍历我们的文档并对其进行更改。你需要将文档想象成一个树状结构,其中包含节点。附着在这些节点上的是你的 HTML 标签及其属性和内部 HTML 文本。通过专用方法,我们可以查找文档的某些部分。一个节点或节点列表就是它们返回的内容(有时只有一个匹配的标签,有时有多个)。然后,通过更改这些节点的属性或使用其他方法,我们可以有效地更改文档的内容或布局。
查找文档树中的三个有用方法是:
-
getElementById
:查找 id 指定的节点 -
getElementsByClassName
:获取 class 设置为参数的节点 -
getElementsByTagName
:获取所有 HTML 标签等于参数的节点
一个有用的属性是innerHTML
属性,它允许你获取或设置 HTML 标签内的 HTML 文本。使用这些元素,我们可以构建以下示例:
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
</head>
<body>
<h1 id="hellotext"> Hello,World</h1>
<script type="text/javascript">
var findhello = document.getElementById('hellotext');
findhello.innerHTML = ("Hello, Beach");
</script>
</body>
</html>
这个简单的 HTML 文档,另一个 你好,世界
示例,通过 JavaScript 立即被修改,而我们看到的浏览器渲染的唯一内容是 你好,海滩
。这并不令人兴奋。然而,如果我们让用户决定何时更改文本,触发一个事件,并让我们的代码对此事件做出反应,那么我们实际上就给我们的网页添加了动作。
事件
事件 是发生在 DOM 内部元素上的事情。它们可以由浏览器或用户触发。用户控制的可以触发的事件包括:
-
更改输入字段
-
点击按钮
-
将鼠标移至某个区域
-
点击链接
你可以将事件处理器附加到包含需要执行代码的标签上。在下一个示例中,我们通过向元素添加 event
属性来实现这一点。你也可以通过使用文档的 addEventListener()
方法达到同样的效果。
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
<script type="text/javascript">
function goToBeach() {
var findhello = document.getElementById('hellotext');
findhello.innerHTML = "Hello, Beach";
}
</script>
</head>
<body>
<h1 id="hellotext"> Hello,World</h1>
<input type="button" onclick="goToBeach()" value="Beach"></input>
</body>
</html>
两个示例之间的区别在于我们添加了一个带有 onclick
属性的按钮,我们将一个 JavaScript 函数的名称作为值,并将我们的 JavaScript 代码放在同名函数中。现在,直到用户点击 海滩 按钮,你好,世界 才会变成 你好,海滩。
概述
到目前为止,你可能认为,我已经到达了困难的部分,我很快就会迷失方向。好消息是!你不会迷失方向,因为你已经到达了这一章的结尾。
我们从对编程语言特性的简要概述开始。接下来,我们阐述了 JavaScript 的这些用途,同时解释了为什么我们需要这种语言以及我们将如何使用它:来编写网页。为了实现这一点,我们需要一种简单的方法来访问网页中的所有元素。
好吧,我们在上一章中已经学习了如何通过使用 选择器 来访问页面元素。
jQuery,一个流行且经过验证的 JavaScript 库,我们将主要用它来处理客户端 JavaScript。它允许你使用 CSS 风格的选择器来指定我们想要访问的元素,甚至可以为其附加事件,这样我们就不需要学习新东西,或者让这本书的大小接近《战争与和平》。jQuery 有其他几个优点,我们将在第七章第七章。jQuery 中描述。这包括一个酷炫的界面,可以在 客户端 和 服务器 之间切换。
因此,我们不会立即深入研究 jQuery。我们需要了解为什么我们还需要服务器端编程。我们将在下一章中学习这一点,同时学习这本书的第二个编程语言:PHP。
小贴士
下载示例代码
您可以从www.packtpub.com
下载您购买的所有 Packt Publishing 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support
并注册,以便直接将文件通过电子邮件发送给您。
第五章:PHP
在上一章中,我们讨论了使用 JavaScript 作为语言进行客户端编程。这一章完全是关于服务器端编程,我们将学习用于此目的的编程语言是 PHP。
PHP 最初是由拉斯马斯·勒德夫(Rasmus Lerdorf)开发的,他将其称为个人主页。如今,这三个字母代表的是PHP:超文本预处理器。它是一种功能齐全的编程语言,是解释执行的,而不是编译的(我们现在知道这意味着什么)。
在我不断提到的那个 6 个月课程中,当我们最终到达 PHP 部分时,我期待着一个全新的、未探索的主题,一个新的前沿。作为一个程序员,我准备大胆地走向没有人——尤其是我自己——曾经去过的地方。然而,所有元素对我来说都是如此熟悉。
当我向人们解释这个经历时,我把这比作遇到一个 15 年未见的女友,并意识到你仍然喜欢她。奇怪的是,去年发生了一件类似的事情,这次是一个女友。
对 PHP 的熟悉不仅涉及语言本身,与 C 编程语言相似,而且还特别涉及与语言一起提供的所有函数。其中许多与你在 UNIX 系统标准 C 库中找到的相同实用函数相同。
PHP 简介
正如我们提到的,PHP,就像 JavaScript 一样,是一种解释执行的编程语言。因此,我们首先需要一个解释器。对于 JavaScript 来说,这很简单,解释器在任何一个浏览器中都有。对于 PHP 来说,你需要一个单独的程序来解释你的代码。假设你在电脑上有一个名为php
的程序(我在我的 Mac 上有一个),你可以在其中输入 PHP 代码或提供包含代码的文件,然后我们就可以编写我们的第一个程序。
我们第一个,不那么有用的例子:使用你最喜欢的编辑器,创建一个名为first.php
的文件,并输入以下文本:
<?php
echo "Hello, world";
?>
注意字符串<?php
和?>
。通常,PHP 代码放置在这两个字符串之间。所以这两个例子实际上只包含了一行 PHP 代码。
因此,如果你在你的电脑上有那个php
程序(如果你没有,不要慌张,你不需要一个),你可以输入php first.php
,你会在屏幕上看到Hello, world
出现。
由于 PHP 是一种功能齐全的语言,你可以用它以这种方式为你的项目创建独立的程序。然而,这并不是你购买这本书的原因,也不是我们将要教你的语言的方式。
我们的第一个真正的 PHP 程序
我们将使用 PHP 来创建动态网页。简单来说,这些是嵌入 PHP 代码的 HTML 页面。PHP 解释器将取走你的代码,进行解释,并用程序的结果替换它。生成的修改后的 HTML 文档将不再包含 PHP 代码;那个纯 HTML 文件将是浏览器看到的。这里有一个很好的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title><?php echo "Hello, world"; ?></title>
</head>
<body>
<?php
echo '<h1>Hello, World</h1>' ;
?>
</body>
<html>
一旦解释器完成了它的任务,这个文件的 内容将会转换成我们第二章中的第一个示例的精确行,HTML,这就是浏览器所看到的。
注意,在带有<title>
标签的行中,我们在 HTML 标签内嵌入了 PHP 代码。你可以这样做。我们例子中的第二行带有 PHP 代码的部分是一个 PHP 语句块的一部分。这使得它更容易阅读。
那么,这有什么用呢?你在开玩笑吧。我们已经走了三个章节的路,而你所能做的只是告诉我用十种不同的方式来写Hello, world
?
当然 PHP 是有用的。嵌入到你的 HTML 文件中的 PHP 代码可以是一个 while 循环(是的,PHP 也有这个),从文件或数据库中提取数据,并使用这些数据构建干净的 HTML 代码来生成多个 HTML 页面。所有这些都可以用几行 PHP 代码完成。但我们如何让它工作呢?好吧,我们首先需要做一些准备工作。
PHP 和网络托管
到目前为止,除了在浏览器的请求下从服务器加载文件到浏览器之外,所有的事情都是由浏览器完成的,这是网络服务器做的——更准确地说,是一个 HTTP 请求。
需要下载的文件由 URL 决定,即用户输入的网页地址,或者他们点击的链接;在这种情况下,www.sitename.com/demo.html
(或demo.php
)。
一旦 HTML 文件被加载到浏览器中,其中的所有内容都会被读取,包括带有href
和src
属性的<img>
、<script>
和<link>
标签。浏览器会列出它还需要更多的文件,并变成一个小奥利弗·退斯特,说“请服务器,我能再要点吗?”
一旦所有这些代码都准备好了,浏览器就知道如何处理它,并在用户的电脑或平板电脑上渲染网页。script
和style
标签帮助你区分哪些是纯 HTML,哪些是 CSS 或 JavaScript。
现在究竟是怎么把这些文件弄到那里的,那里又是哪里呢?它们到了那里,又回来了,因为www.sitename.com
这个名字背后有一台计算机和一个叫做网络托管的服务。
网络托管 101
我们一直推迟解释这部分,因为我们到目前为止可以用我们的例子而不需要真正的网站。使用 PHP,这将会不同,所以这是概述如果你想要一个人们可以访问的自己的网站需要采取哪些步骤的地方。
域名
你首先需要给你的网站起一个名字。实际上你需要的是一个域名。网站不能有你想取的任何名字,而且两个网站不能有相同的名字。所以,如果你想叫你的管道公司网站为joe.theplumber.com
,这不会自动发生。
你希望使用一家网络托管公司来帮你完成这件事。
网络托管公司
网络托管公司不仅能为您提供域名和注册服务,他们还会(收取费用)在他们的计算机上为您设置一个服务器,这些计算机每周 7 天,每天 24 小时运行——简称为 24/7。这样,世界上任何有互联网连接的人都可以随时访问您的网站。
我不能用自己的电脑来设置我的网站吗?是的,您可以,但一旦您关闭它并离开家,就没有人能访问您的网站了,这并不是您想要的。
域名通常是www.
后面跟着代表一个人或公司的字符串,然后是一个表示涉及的组织类型或所在国家的可用结尾。一开始,这些结尾只有几个:.com
、.org
、.net
、.gov
等等。这个列表仍在扩展。
您可以访问一些网站,例如,访问dns.be
查看您选择的域名是否仍然可用。我选择了paulpwellens.com,因为paulwellens.com
已经被一位英国橄榄球运动员占用。嗯,我既不是英国人,也不是橄榄球运动员。
几年后,当我回到比利时后,paulwellens.be
变得可用。我以我的名义注册了一年,但后来我放弃了它,因为我看不出保留两个不同名称的必要性,更糟糕的是,两个不同的托管公司。但如果你愿意,你可以为单个网站拥有多个域名。
服务器端设置
一旦您的网络托管公司为您设置好,它将提供必要的信息,以便您可以将您的网页文件转移到您的网站上。这通常包括:
-
您网站的域名(当然)
-
FTP 地址(一个 IP 地址或简单地是相同的域名)
-
登录名和密码
-
文件夹的名称,例如
public_html
使用这些信息,您可以使用 FTP 协议将您在计算机上开发的网页转移到您自己的网站上。这意味着您需要在您的计算机或平板电脑上有一个可以处理该协议的程序。对于大多数基于 UNIX 的系统(如 Mac OS、Linux)来说,这很自然,平板电脑上有一些很酷的 FTP 应用程序,同时 Windows 也存在 FTP 客户端。
FTP 代表文件传输协议。在您的计算机或平板电脑上使用或安装一个所谓的 FTP 客户端,它将允许您在客户端和主机之间传输文件,通常一次传输一个文件。如果您想批量传输文件,FileZilla 是一个不错的选择。而且,还有更多的好消息,许多酷的 HTML 编辑器和其他工具都内置了 FTP 功能。
服务器上存放您网站主页的文件夹,在我们的例子中是public_html
,通常被称为您的文档根目录。
附加的服务器端服务
我们刚才描述的正是你需要的一切,没有其他东西,来设置一个静态网站。如果你计划使用其他东西,而且你确实会使用,那么检查你考虑的网站托管公司提供的附加服务非常重要。最基本设置将连接你到一个支持 HTTP 和 FTP 协议的 Web 服务器。你还需要确保以下内容可用:
-
PHP - 检查 PHP 的版本
-
MySQL 服务器 - 也请检查版本
-
PHPMyAdmin - 一个易于管理 MySQL 数据库的工具
大多数供应商都会提供某种类型的面板,CPanel 是其中流行的一个,用于访问和配置这些服务。服务器本身通常运行 Linux 或 MacOS 作为操作系统,Apache 作为 web 服务器,并支持 MySQL 和 PHP。在 www
领域,这种组合通常被称为 LAMP 架构。拥有自己的 LAMP 会怎样呢?
PHP 开发环境
我们刚才描述的设置基本上没有问题,但它有一个主要的不便之处:你的网站总是会看起来像是在进行中。在这个环境中,你测试工作结果的唯一方法是通过使用 FTP 上传你的更改到网站,并作为你自己的网站的访客。很可能时不时会出现一些问题;即使是微小的错误也可能造成巨大的损害。
因此,你需要在电脑上设置一个本地开发环境。这样,你就可以在稳定、经过测试的代码里程碑到达时,定期更新你的网站。在此期间,你可以开发新功能,进行实验,学习新事物,以及所有这些美好的事情。
为了能够本地开发和测试你的 PHP 应用程序,你需要:
-
运行 MacOS、Linux 或 Windows 的计算机(在撰写本文时,平板电脑上没有可行的 PHP 环境)
-
支持 PHP 的 Apache 网络服务器
-
一个好的代码编辑器,一个完整的 集成开发环境(IDE),或者介于两者之间的工具
我个人喜欢 Adobe Dreamweaver。它做了我需要的一切,没有多余的东西。它让我可以查看我的页面/应用程序并在本地测试它们,只需点击鼠标即可通过 FTP 连接到服务器,并且它的编辑器可以识别 HTML 标签、CSS 属性以及 JavaScript 和 PHP 关键字。但它不是免费的。有几个 IDE 是免费的。
无论哪种方式,你应该将构成你网站的文件都存储在本地目录中,你的本地文档根目录(例如,$HOME/Sites/mysitename
),并使用以 localhost
开头的 URL 在本地进行测试。在 Mac 上,这将是 http://localhost/~username/sitename/filename.php
现在,让我们回到语言本身。就像我们对 JavaScript 所做的那样,我们将遍历 PHP 中所有可用的成分,你可以使用它们来编写 PHP 程序。为了避免重复,我们不会重复两种语言中相同的内容,但会指出它们之间的差异。在适当的地方,我们将按照相同的顺序进行。
PHP 作为一种网络开发语言
因此,现在我们准备好使用 PHP 编写网络程序,在本地运行和测试它们,并将它们放置在你网络托管公司管理的服务器上;简而言之——开发和部署。
我们专注于使用 PHP 动态生成网页。在 PHP 代码被解释后,剩下的就是 HTML,你应该将其视为在服务器上生成并由客户端渲染的文本字符串。用于生成这些页面的数据要么来自外部文件,要么来自数据库。
当你的网站被邀请时,访客会与之互动。可能会有一个需要填写的表单,一个需要点击的按钮,从几个选择中做出选择,等等。访客的这种干预结果需要以某种方式返回服务器并处理,所有这些都可以再次被视为文本字符串。处理完毕后,它要么返回客户端通知访客,要么需要存储在某个地方。那个地方也将是服务器上的文件或数据库。
虽然我一直在宣称,并且我是认真的,这本书不应该用作参考,但我们包括了一个关于字符串和文件相关内容的简短参考,因为你们每天都需要它们。数据库是下一章的主题。
变量、值、运算符和表达式
好吧,你可以说在 PHP 中,每个变量都会让你损失一美元,因为每个变量名都必须以一个$
符号开始,一个美元符号。如果这个小小的文字游戏能帮助我们记住 JavaScript 和 PHP 变量之间的重要区别,那么我们都会随着时间的推移节省很多美元。
<?php
$counter = 0;
$hello = "Hello, world";
?>
PHP 中没有var
关键字来声明变量。你可以在初始化它们时开始使用它们。就像在 JavaScript 中一样,没有严格的类型,你可以使用数字和字符串值;PHP 也使用函数,并且运算符相同,除非你想拼接字符串。然而,关于变量作用域的故事是不同的。
变量作用域
就像在 JavaScript 中一样,变量的作用域,即变量在程序中已知并可访问的区域,是一个非常重要的事情需要理解。与 JavaScript 相比,使用了不同的关键字。我已经提到过没有var
关键字。相反,我们有两个新的关键字——全局和静态。
局部变量
局部变量可以在声明它们的函数内部访问。这就像在 JavaScript 中,但没有var
关键字。让我们再次在至少三行中生成数字3
:
<?php
function three()
{
$a = 1;
$b = 2;
$c = $a + $b ;
echo $c;
}
?>
这只是简单地产生3
。如果我们想在函数外部使用变量$c
在echo
语句中,它将不会产生任何东西。是的,你听到的,什么都没有——不是未定义的,不是 0,没有错误信息,就是什么都没有,但以某种方式,它是一个空字符串。
这里还有一个例子。这次它甚至不计算3
:
<?php
$a = 1;
$b = 2;
function nothing() {
$c = $a + $b;
echo $c;
echo $d;
}
nothing();
?>
记得我们之前提到的 JavaScript 示例,我们在函数外部声明了一个变量并初始化它,然后在函数内部不小心修改了它的值?函数外部的声明将那个变量变成了全局变量,而无需在程序中使用那个词。函数内部具有相同名称的变量会隐藏这些全局变量,但只有当它们被显式声明时。
在 PHP 中,情况正好相反。当我们在外部声明变量,然后在我们的代码中引入一个函数尝试访问它们时,它们将简单地从我们的程序视图中消失。所以,在我们的例子中,在nothing()
函数内部的语句中:
$c = $a + $b;
$a
和$b
是未声明的变量,当它们在表达式中使用时,它们的值通常变为空字符串。这就是为什么当我们使用以下语句时,屏幕上不会显示任何内容:
echo $d;
然而,上一行将产生数字0
。这是因为它是包含+
运算符的表达式的结果。表达式的操作数首先被转换为数值,所以我们最终什么也没有加到什么也没有上,这和将零加到零一样,结果仍然是零。那么,我们如何在函数内部访问我们在外部声明的$a
和$b
呢?
全局变量
在许多编程语言中,将频繁使用的语句序列取出来,放在一个可重用的函数中,这是一种常见的做法,例如:
getProductList();
根据编程语言的不同,一个函数在技术上可能不被称为函数,而是子程序、过程等等。在一个典型的 Web 开发场景中,你需要从数据库中获取数据,这种操作经常发生,因此你希望以这种方式组织你的代码。
有很多次,我感到非常惊讶,因为当我将代码分组到函数中时,原本正常工作的代码突然停止工作。原因是:我在其他所有内容之外初始化的全局$mysqli
对象在函数内部无法访问。对我来说是全局的,但 PHP 并没有将其视为全局。以下是补救措施:
<?php
$a = 1;
$b = 2;
function nothing() {
global $a, $b;
$c = $a + $b;
echo $c;
}
nothing();
?>
除非你在函数内部使用全局关键字,否则你的全局变量才会被当作全局变量处理。
另一种补救措施是将你的$a
和$b
变量作为函数的参数,但我不会推荐这样做。在需要使用全局变量的函数中将它们声明为全局变量是全局最佳实践。
静态变量
在某些编程情况下,存在一种非常有用的变量作用域类型。考虑以下情况:
<?php
function listNextPrice ($price) {
// Yes, arguments cost a dollar too
$article = 0;
$article++;
echo 'Article number ' . $article . 'costs $' . $price ;
//Analyze this carefully
?>
很可能已经很清楚我们的意图。每次我们使用这个函数时,我们希望列出我们作为参数传递的价格,但我们期望文章编号也要高一个。然而,每次我们重新进入函数时,$article
变量都会重置为 0。现在,请注意这个略有不同的版本:
<?php
function listNextPrice ($price) {
static $article = 0;
$article++;
echo 'Article number ' . $article . 'costs $' . $price ;
}
?>
这将提供预期的结果,因为我们已将$article
声明为静态。变量被赋予初始值的带有关键字static
的行只使用一次,并且从那时起,当程序返回到函数时将被忽略。然而,每次它这样做时,都会记住上一次访问的变量的值。使用static
关键字声明的变量不能使用表达式来初始化。
我们在第四章中使用的相同的选择表达式和运算符在 PHP 中也是有效的,只有一个主要且极其重要的例外。
字符串运算符
在 JavaScript 中,你学习了不仅可以使用+
符号来加数字,还可以用来连接字符串——连接它们,也可以称为连接。在 PHP 中,有一个专门的运算符来做这件事。它有两种形式,我们在这里展示:
<?php
$a = "Hello";
$a = $a . "World";
?>
或者:
<?php
$a = "Hello";
$a .= "World";
?>
你的大部分 PHP 代码将像这样:将字符串连接起来构建动态页面的最终 HTML。通常,你会从一个变量开始,用开标签初始化它,然后从那里构建,直到达到最终的 HTML 标签,然后echo
整个内容:
<?php
$htmlstring = '<div>';
$htmlstring .= '<h1>Hello, World </h1>';
$htmlstring .= '</div>';
echo $htmlstring;
?>
一旦我们开始向 HTML 标签添加需要引号的属性,并开始使用包含美元符号的 PHP 变量,就必须仔细注意,不仅要正确使用引号,还要注意使用哪种引号。
双引号还是单引号,这是一个问题
我是一个单引号爱好者,我总是用单引号开始字符串,然后在需要时切换到双引号。它们之间有什么区别?单引号内的内容将被当作字面意思处理,所以$name
读作$name
。在双引号之间的文本中,变量将被评估,所以$name
将读作该变量的值。如果你使用单引号并需要变量,请将它们放在引号外,并使用连接运算符来组合你的文本,如下所示:
$htmlstring .= '<h2 class="blue">'.$hello.'</h2>';
如果你想使用双引号,相同的语句看起来应该是这样的。
$htmlstring .= "<h2 class=\"blue\">$hello</h2>";
你可以将$hello
放在里面,因为它将被评估,但你必须使用反斜杠转义类属性的引号以取消其特殊含义。
我的偏好是使用单引号。只是避免混合这两种方法。另一方面,如果你必须处理他人的代码,请确保你注意这些,虽然不是总是微妙的,但差异。
控制流
在第四章中,JavaScript,我们列出了该语言的原则控制流语句:if,if/else,while 和 switch。PHP 中也有相同的语法,但不要忘记变量名中的美元符号!
函数
用户定义函数的语法与 JavaScript 中的语法相同。
PHP 还提供了一些预定义的函数。其中一些非常有用,所以我们决定包括它们的描述和语法。大多数都让我想起了 UNIX 时代的 C 库函数。
字符串函数
由于您将始终使用字符串,并且经常需要在最终输出之前对这些字符串进行操作,因此了解 PHP 提供了一组不错的字符串函数是很有用的。为了您的方便,我们在此包括最实用的函数。
strpos()
strpos()
函数在另一个字符串中查找字符串首次出现的位置。如果找到字符串,则返回位置(从 0
开始,而不是 1
),如果没有找到字符串,则返回 FALSE
。该函数接受两个参数:要查找的字符串和要查找的字符串:
<?php
echo strpos("Hello, World","World"); // this wil print 7
?>
strlen()
strlen()
返回该函数唯一参数的字符串长度,如果字符串为空则返回 0
。
substr()
substr()
返回字符串的子串。其典型用法是 substr($string,$start,$length)
。$string
是用于检查的字符串,$start
是该字符串中的起始位置,可选的 $length
用于指定要返回的字符串长度。默认值是字符串的末尾。
我们还可以组合这些函数。您认为以下代码会做什么?
<?php
echo substr("Hello,world", strpos("Hello, world","world"));
?>
日期函数
很难想象一个不需要与时间或日期有任何关系的 Web 应用程序。此外,您将经常需要使用不同的日期格式,这不仅因为世界各地的日期格式不同,例如,07/19 在美国是正确的,但在欧洲必须是 19/07,数据库中的日期也使用不同的格式。有三个非常方便的函数可以帮助您处理这些。
那么,什么是日期呢?嗯,它可以是生长在温暖国家并挂在树上的东西;我过去在死亡谷买枣,然后给我的朋友们吃。日期也可以是约会的另一个词,年轻人和老年人都可以在约会时兴奋起来。
然而,在 PHP 中,日期或时间的定义也来自 UNIX,等于现在与 1970 年 1 月 1 日 00:00:00 GMT 之间的秒数。以下是三个函数及其用法。
time()
这将返回现在的秒数。因此,在以下代码中,您可以将它存储在一个变量中:
<?php
$rightnow = time();
?>
请注意,这将是托管服务器上的日期和时间,它可能位于与您的计算机或网站访问者所在的不同地区。您始终可以使用 date_default_timezone_set
函数指定程序的正确时区,例如:
<?php
date_default_timezone_set('Europe/Brussels');
?>
如果需要确定访问者的日期和时间,那么这将在客户端发生,即使用 JavaScript 的 date
对象。
date()
date()
函数用于获取时间戳,例如,秒数或默认情况下为当前时间,并将其转换为所需的格式。它接受的第一个参数是一个格式字符串。查看一个良好的参考,以获取该字符串中可以使用的完整项目列表。最常用的有 m
表示两位数的月份,d
表示日期,Y
表示四位数的年份。因此,要获取今天的日期,以欧洲格式使用斜杠分隔:
<?php
$eurodate = date('d/m/Y');
//or, same thing
$eurodate = date('d/m/Y', time());
?>
当然,你时不时地需要现在的日期和时间,但更常见的是你需要处理过去的日期(订单日期)和未来的日期(日程事项)。那么,我们如何计算订单时间与 1969 年最后一天午夜之间的确切秒数?你不需要。还有一个函数可以帮助我们做到这一点。
strtotime()
strtotime()
函数将根据传递给它的字符串作为参数返回一个时间戳——例如,来自数据库的格式。查找一个良好的 strtotime
参考,以获取所有有效格式的列表。因此,在实践中,你通常会结合使用这两个函数,如下所示:
<?php
$htmlstring .= '<td>'.date("d/m/Y", strtotime($dbdate)).'</td>';
?>
数组
数组是存储在单个变量中的数据元素集合。在我之前知道的几乎所有编程语言中,这些元素都会通过一个数字,即它的索引来引用,第一个元素的索引从 0 开始。在我提到的培训期间,对我来说是一个启示的是,PHP 中可以使用的一种不同类型的数组:关联数组。关于这一点,很快就会详细介绍。
数字数组或索引数组
具有数字索引的传统数组可以一次声明和初始化,如下所示:
<?php
$beatles = array ("John", "Paul", "George", "Ringo" );
echo $beatles[0]. " Lennon:;
?>
<?php
/* different way to do the same */
$beatles = array();
$beatles[]= "John';
$beatles[]= "Paul";
$beatles[]= "George";
$beatles[]="Ringo";
?>
在这两个例子中,我们创建了一个包含披头士乐队成员名字的首字母缩写的4
元素数组。在第一个例子中,我们一次完成,使用array()
构造函数——类似于我们在 JavaScript 中创建对象时使用的方法。实际上,JavaScript 也有数组,但我们不会像在 PHP 中那样经常需要它们。
第二个例子首先告诉 PHP 先生,$beatles
应该是一个数组,然后我们开始填充它。第一个 $beatles[]="John"
赋值相当于:
$beatles[0] = "John";
下一个将初始化索引高一位的元素。然而,我们可以这样写:
<?php
$beatles[6] = "George";
$beatles[] = "Ringo";
?>
然后,乔治将成为$beatles[6]
的值,林戈将成为$beatles[7]
的值。没有元素具有索引 2、3、4 和 5 是完全可以接受的。喜欢收集东西的人可能不喜欢这样。他们无法忍受有 0 和 1 和 6 和 7 而没有中间的。他们可能会像我一样喜欢关联数组。
关联数组
在 PHP 中所谓的关联数组中,索引可以是一个字符串而不是一个数字。这是它的工作方式:
<?php
$employee = array();
$employee['first'] = "John";
$employee['last'] = "Williams";
$employee['profession'] = "conductor";
?>
我们基本上将上一章中的 JavaScript 对象重新创建为一个关联数组。还有一种方法可以在一个语句中初始化所有这些。它的语法是新的,并且不同,引入了键/值对。当我们开始从数据库(如 MySQL)提取数据时,这些键/值对将非常有用,因为 MySQL 可以将数据以……猜猜看……关联数组的形式返回给我们。
因此,这里有一个使用特殊 =>
操作符的单行版本:
$employee = array(
"first" => "John",
"last" => "Williams",
"profession" => "conductor"
);
关联数组的酷控制语句
控制语句比我们在本章和上一章中描述的要多。foreach
语句在遍历数组时非常有用,因为你可以轻松地遍历键和值。以下语句遍历了我们的示例数组:
foreach ($employee as $key => $value) {
echo $key. ' is '.$value. '<br />';
}
现在你已经了解了关联数组是什么,我们准备解释一些重要的数组变量,我们不是自己创建的,而是由系统生成的:: $_POST
和 $_GET
。我们将能够访问这些变量的最常见场景是当我们的网站访客填写表单并点击按钮时。
将数据发送回服务器 – 表单
你已经知道一种从服务器获取信息的方法,当我们讨论文件和数据库时,你将学习其他方法,但让我们首先反过来。我们将向你展示我们如何捕捉和处理我们的网站访客提供给我们信息。
最常见的方式是当访客填写表单字段并提交时。这把我们带回到第二章,HTML,在那里我们讨论了 <form>
和 <input>
元素。<form>
标签具有比我们讨论的更多属性,因为我们当时无法解释它们。例如,为了验证用户输入,比如在发送到服务器之前查看 ZIP 代码是否只包含数字,我们需要了解 JavaScript。我们仍在推迟这个任务,因为有一个非常棒的 jQuery 插件可以为我们完成这个任务。我们也不能解释 *action*
和 *method*
属性,因为这涉及到 PHP。现在是做这件事的时候了。
看看以下 HTML 片段,这是一个相对简单的、部分表单,人们可以用它来注册通讯。
<form id="myform" action="process.php" method="POST">
<label> First name </label>
<input type="text" name="first" /><br />
<label>Last name </label>
<input type="text" name="last" /><br />
<label>Tell me about your hobbies:</label><br />
<input type="checkbox" name="hobbies[]" value="Photography">Photography</input><br />
<input type="checkbox" name="hobbies[]" value="Music">Music</input><br />
<input type="checkbox" name="hobbies[]" value="Theatre">Theatre</input><br />
<input type="checkbox" name="hobbies[]" value="Tennis">Tennis</input><br />
<button>Submit</button>
</form>
想象这段代码是网站的一部分,访客填写表单并点击提交按钮。用户填写的数据将被发送到服务器,并可以在带有 action
属性的 PHP 模块中处理,例如我们例子中的 process.php
。另一方面,输入字段中输入的值将被存储在一个数组中,名为 $_POST
或 $_GET
,具体取决于选择了哪种方法。
POST 或 GET,我们应该获取什么?
我个人的偏好是始终使用 POST,除非我需要使用 GET。那么,区别是什么?
使用GET
方法,数据会与 URL 一起传递,这是你在浏览器内可以看到的网页地址。你会在地址栏看到类似:process.php?first=John&last=Williams&profession=conductor
这样的内容。问号后面的字符串被称为查询字符串。
使用这种方法,访客(假设你给了他们指示)可以传递参数,然后你可以在 PHP 代码中处理这些参数。如果有一个大型网站中的有趣文章,你可以通过例如查询字符串article=number
来重新访问它,那么这也会很有用。
使用GET
的两个主要缺点是,实现依赖于大小限制(可能是 1024 字节)以及所有内容都是可见的。这不是你想要用来显示登录名和密码的方式:
login.php?login=paul&password=mysecret.
因此,一般来说,你想要使用POST
方法;实际上没有大小限制,并且浏览器地址栏中不会显示任何内容:
\(_POST 和\)_GET 数组
这非常简单;你可以在关联数组$_POST
中找到在表单中填写过的所有值。在输入标签的情况下,键/值对是name
属性的值和value
属性或填写的内容。
我们不能确定用户是否填写了所有必填字段,以及我们是否在客户端进行了验证(我们将在后面的章节中学习这一点),但在服务器端仍然需要进行验证。否则,我们可能会在文件或数据库中填充空或不完整的员工记录。
注意在复选框的名称属性中使用的方括号。它们怎么可能都有相同的名称?它们并不相同。用户选择爱好后的结果将是一个索引数组,它将是$_POST['hobbies']
。
当处理你的POST
变量时,你可能想要使用isset()
函数。这个函数检查变量实际上是否存在,这与检查它是否存在并且具有除0
或空字符串之外的其他值是不同的。
接下来的是我们process.php
模块中可能出现的代码。在其中,我们引入了&&
,这是一个逻辑运算符。把它想象成一个逻辑AND
运算符,如果其左右两边都是真的,则返回 true。它的对应物是||
,逻辑OR
,如果至少有一个操作数是真的,则返回 true。
<?php
if((isset($_POST['first'])))&& ($_POST != "")){
$first = $_POST['first'];
}
if((isset($_POST['last']))&& (4_POST['last'] != "")) {
$last = $_POST['last'];
}
if(isset($_POST['hobbies']){
$totalhobbies = count($_POST['hobbies']);
}
?>
count()
函数是一个函数,它计算数组中有多少个元素,即它的大小,并返回它。在我们的例子中,这将等于所选爱好的数量,而不是可供选择的数量。
文件
在前面的章节中,我们处理的是内联 CSS 或 JavaScript,或者通过使用正确的标签和属性,我们可以引用包含我们代码的外部文件。但是,对于我们的 PHP 代码,没有这样的标签或属性。但是,有另一种方法可以做到这一点。
假设您已经编写了一组您计划在其他项目中重用的酷函数。在您想要使用的每个.html
文件中的<?php
和?>
之间重复它们是没有意义的。将它们保存在一个单独的文件中,您可以引用它,这是一个更好的主意。我们有一些 PHP 关键字可以做到这一点。
include、require 和 require_once
当在 PHP 语句中使用时,这三个关键字都会做同样的事情。包含关键字和文件名的语句将被文件的内容所替换。有一些细微差别:如果使用include
且文件未找到,程序将继续执行,而如果您使用require
,程序将停止。
require
和require_once
之间的区别在于,使用require_once
时,已经加载的文件将不会再次加载。请确保您将 PHP 代码放在您在<?php
和?>
之间包含的文件中。在语句本身中,您可以使用括号或不使用括号,例如:
include "morecode.php"
require ("includes/functions.php");
常规文件
到目前为止,我们一直在使用存储在服务器上的各种文件:程序(.php
和.js
)、样式表(.css
)、纯网页(.html
)和图像,这些图像与<img>
标签一起使用。这些图像可以是不同的格式:.png
、.gif
、.png
,甚至是.tiff
。我们网页中的链接可以引用存储在服务器上的更多文件,例如所有格式的 PDF 文档,甚至 Word 文档。所有这些文件在需要时将由浏览器下载给我们的网站访客,并且它们会到达服务器,因为我们可能使用 FTP 将它们放在那里。
然而,您可以从程序内部访问不同类型的文件,以及创建或修改新文件。毕竟,一个结构良好的文件可以充当一个小型数据库。有一系列现成的函数来处理文件。大多数函数需要一个指向文件的路径,这不过是文件名本身或由一个或多个目录名通过/
分隔的名称。
这些函数随后返回或使用文件句柄或文件指针。这就是我在培训课程中感到似曾相识的部分。UNIX 用户会记得,一切都被当作文件来处理:一个真正的文件、打印机、硬盘、任何设备,而文件的基本操作是:创建、删除、打开、关闭、读取和写入。对于真正的文件、文本文件或二进制文件,有一系列方便的函数,所有这些函数的名字都以一个f
开头。我们在 PHP 中重新发现了这些函数。
文件函数或 f 函数
有函数可以访问现有文件或创建新文件,检查文件是否存在,以及它们实际上是文件而不是目录,还有从文件中读取和写入的函数。第一个函数需要一点学习;其余的都很简单。
fopen
没有名为fcreate!
的函数,所以fopen
用于创建一个新文件或打开一个现有文件,但目的是什么?你必须仔细研究这个函数第二个参数的选项,因为这决定了你的文件会发生什么。这可以从为写入创建一个新文件和为读取打开一个现有文件,到获取一个文件,销毁其中的内容并重新开始。了解文件指针指向的位置也很重要。我们将给你提供这三个选项中的三个,但它们应该足以满足 95%的工作。请注意,我们期望$filename
的值是一个指向文件的路径名。
$fp = fopen($filename, "r");
这将打开文件用于读取,文件指针将指向文件的开始。如果你想要从现有文件中提取信息,这通常是你要做的。
$fp = fopen($filename,"a");
a
代表追加。因此,这将打开一个文件用于写入,从文件末尾开始。所以文件指针指向的位置就是末尾,你写入此文件的所有内容都将添加到末尾。
$fp = fopen($filename, "x");
这是最接近创建的。如果文件已经存在,它将保持完整,并且fopen()
将返回FALSE
而不是文件指针。否则,你刚刚创建了一个可以写入的文件。
如我所述,大多数其他选项可能会通过截断文件内容来破坏你的文件,你肯定不希望这样。这就像在 UNIX 系统上执行命令一样 - 例如:> filename
file_exists(), is_file(), 和 is_dir()
为了避免意外,你可能想要检查你想要打开的文件是否存在。
<?php
if (file_exists($filename)) {
echo "$filename exists";
}
else
{
echo "$filename nowhere to be found";
}
?>
file_exists()
函数如果$filename
存在则返回TRUE
,否则返回FALSE
。它不会告诉我们它是一个文件还是一个目录。为此,我们有is_dir()
和is_file()
函数。
fread 和 fwrite
现在,假设我们已经有文件打开,并且指针指向文件内部,我们可以从中读取和写入。为此,我们使用fread
和fwrite
。假设我们有$fpin
用于读取和$fpout
用于写入;这将是一个简单的文件复制代码片段,使用这些函数:
<?php
$filecontents = fread($fpin, filesize($filename));
$success = fwrite($fpout, $filecontents);
?>
fread
的第二个参数用于指定你想要读取的字节数。当然,我们在这里简化了,说:让我们一次性读取整个文件。为此,filesize()
函数非常有用。使用fwrite
,你可以将你想要写入的字节数作为可选的第三个参数。在我们的例子中我们没有这样做,我们只是简单地使用包含整个文件的字符串作为第二个参数。
逐行读取 - fgets()
我们使用了前两个函数来一次性读取文件的内容,然后将这个块写入另一个文件。我们可能会用到的许多文件,而不是数据库,将会有组织成固定长度记录的信息行,这些记录不过是固定长度的字符串或数字。如果我们能够逐行处理文件会怎么样呢?我们可以。fgets()
是一个一次返回一行内容的函数:
<?php
while ($line = fgets($fp)) {
/* do something with $line */
}
?>
printf 函数族
printf
函数是让我在现在臭名昭著的六个月课程中感到“好久不见”的那个函数。它非常强大,如果你掌握了如何编写好的格式字符串,你只需几个语句就能创建出色的结构化输出。
不幸的是,你可能会发现printf
,PHP 版本,不太可能被大量用于生成 HTML,因为 HTML 与固定长度字段的输出相去甚远。首先,后续空格会被删除,等你看到输出时,HTML 标签也不会显示。
讲师从未使用printf
有两个原因——其中一个是很有道理的。printf
是一个函数,而echo
是内置的。所以,大多数时候,你 echo 的内容会比printf
快。
那么,为什么要提它呢?有一些与printf
非常相似的功能可以读取或写入字符串或文件,并且有一个简单的方法来处理结构化格式是非常有用的。它们是:sscanf
、fscanf
、sprintf
和fprintf
。它们共同的特点是使用格式字符串。首先,我们向您展示语法;然后,我们解释格式字符串本身。
printf
函数族的语法
-
printf($format, $arg1, $arg2, ... );
-
sprintf($string,$format,$arg1,$arg2, ...);
-
sscanf($string, $format, $arg1, $arg2, ...);
-
fprintf($fp, $format, $arg1, $arg2, ...);
-
fscanf($fp,$format, $arg1, $arg2, ...);
在这种语法中,$fp
代表一个文件指针,它之前使用fopen()
和$string
变量来包含字符串创建;$arg1
、$arg2
等,是存储我们检索的数据或持有我们想要使用的数据的变量。$format
包含如何格式化的魔法:
<?php
$product ="Stereo receiver";
$price = 499;
printf("Item purchased: %20s - Price:%7.2\n", $product, $price);
?>
这将输出:
Item purchased: Stereo receiver - Price:0000499.00
这使得产品名称正好是 20 个字符,价格在浮点数前有 7 位,后跟 2 位。我知道,当在浏览器中显示时,这些额外空格将减少到只有一个,但如果使用fprintf
将此写入文件,并在循环中重复相同的语句,我们最终会得到一个所有行都整齐排列的文件。所有这些都包含在带有%
符号的字符串中。通常,每使用一个%
字符串就有一个额外的参数。以下是他们可以包含的内容:
格式值 | 描述 |
---|---|
%% |
如果你需要一个% 符号——使用两个,只显示一个 |
%b |
显示值为二进制数,即只有零和一 |
%c |
根据 ASCII 值显示字符 |
%d |
显示值为十进制数 |
%f |
浮点数 |
%o |
八进制数 |
%x |
十六进制数(小写字母) |
%X |
十六进制数(大写字母) |
| %s
| 将值视为字符串。你将最常使用它,结合额外的格式值,这些值放在%
和字母之间(例如%.2f
):
-
[0-9](指定变量值保持的最小宽度)
-
[0-9](指定小数位数或最大字符串长度)
|
摘要
在本章中,我们介绍了 PHP 编程语言以及你需要做什么才能开始在你的网页中使用它。这正是本章的重点——不是深入覆盖语言能做什么,即使在 Web 开发的情况下。我们没有涉及语言的对象化方面,也没有涵盖特定于网站的内容,如 cookies 和会话。
我们立即聚焦于每个人在 PHP 中都需要的内容。我们以讨论如何在客户端和服务器之间交换数据以及如何使用文件存储这些数据结束本章。这些函数使我们能够为这些文件添加结构。如果你使用 XML 等格式,文件甚至可以有更多的结构,XML 与 HTML 非常相似。有一些酷的 PHP 库可以处理这些,例如SimpleXML,我们将在第十章中讨论,XML 和 JSON。
最终,你希望超越仅使用文件来存储数据的使用,并使用真正的数据库。在基于 PHP 的 Web 开发领域,最常用的数据库是 MySQL,这也是我们下一章的主题。
第六章:PHP 和 MySQL
在上一章中,我们讨论了 PHP 作为服务器端编程语言的使用。它的主要用途是动态生成 HTML 以创建由 Web 服务器发送给客户端的网页,以及存储、检索和操作服务器上的数据。我们已使用文件作为数据的容器,但我们已经给出不止一个暗示,一旦数据量变得很大和/或复杂,我们希望使用数据库。
在本书第一部分最后一章中,我们介绍了 MySQL,这是许多从事 Web 开发的人的选择数据库。你可能听说过 LAMP 栈。这是 LAMP 中的 M(Linux/Apache/MySQL/PHP)。
在深入探讨 MySQL 本身以及如何在 PHP 中与之交互(因此本章的标题)之前,我们想给你一个关于数据库的一般性轻松介绍。
数据库
数据库不过是一组数据的集合,通常以某种结构化的方式组织。我们每天都在使用数据库,尽管我们可能不会把它们当作数据库来想。以电话簿为例。它包含大量数据。数据本身由姓名、地址和当然,电话号码组成,通常按姓氏排序。然而,这个纸质数据库有许多缺点。一旦它被打印出来,就会不完整且过时。如果我们想查找住在同一条街上所有人的电话号码,我们就不知道从哪里开始。但它是数据库,没错。
关系数据库
关系数据库这个术语可以追溯到 1970 年爱德华·弗兰克·科德(Edgar Frank Codd)撰写的一篇文章,当时他在 IBM 工作,这篇文章名为《大型共享数据银行的关系数据模型》(A Relational Model of Data for Large Shared Data Banks)。
它展示了一个显示数据不同元素之间关系的模型,并大量使用表格。在每个这样的表中,都有几个字段或列可以包含各种类型的数据(字符串、数字、日期等等)。这些表中的条目被称为行,每行中的第一列是一个索引或主键,一个数字,通常在创建后不会改变。
因此,可以有一个包含客户信息的表。索引代表客户 ID,而所有其他列或字段都是经典的东西,比如名字、姓氏、地址等等。你可以轻松创建其他表并选择它们可以包含的内容。让我们假设我们打算卖书。我们可以有一个包含书籍信息的表,从书籍 ID 开始,书籍标题、作者、类别(小说或非小说,或更多类别)、ISBN 号码、价格、是否为精装、平装或电子书等等。
你可以有一个单独的表用于分类;一个用于作者,当然还有一个用于你的订单信息的表。如果你这样组织你的数据,并在订单信息中使用客户、分类和书籍 ID,那么当例如客户更改地址时,你只需在数据库中的一个地方更改信息。本章中你要学习的数据库正是这样工作的。
我们提出的流程是一个两步过程。在第一步中,我们设计我们希望我们的表看起来如何,以及它们是否应该属于单个数据库或多个数据库。接下来,我们将编写我们的应用程序,如果你愿意,我们的网站,以使用数据库及其表而不是平面文件。
要实现所有这些,你需要学习很多新事物。其中一个新事物是另一种编程语言:SQL,这并不令人惊讶。
SQL
许多人认为 SQL 代表标准查询语言,但实际上并非如此——它只是普通的 SQL。然而,它是一种语言,是一种用于在数据库上执行查询的标准。它也是那些存在多年且不会很快消失的少数编程语言之一。我刚开始使用 C 语言时,人们还认为它是新的,但即使那时它已经发展了十年。
SQL 用于查询数据库,可以将其视为一种命令行语言,即输入计算机控制台中的内容。以下是一个例子:
SELECT * FROM CRIMINALS WHERE NAME = "JONES" AND BIRTHYEAR > "1965";
这里还有一个例子:
SELECT LAST, FIRST, ADDRES FROM CRIMINALS WHERE BIRTHYEAR > "1965";
如果这让你想起了旧金山街道或其他七十年代的犯罪系列剧的场景,你绝对是对的。他们在这些计算机屏幕上所做的是在数据库中查找信息,而这几乎就是计算机当时唯一被使用的功能。在我们的例子中,CRIMINALS
是表名,SELECT
命令用于查找满足(因此WHERE
)特定条件的所有记录。
没有必要学习完整的语言:只需掌握几个命令和条件语句,我们就能拥有足够的知识来创建程序,这些程序可以在我们创建了数据库和表之后立即执行数据库的基本操作。这组基本操作通常被称为CRUD(创建、读取、更新和删除)。在 SQL 中,我们只需使用五个命令就能处理这些操作:SELECT
、INSERT
、CREATE
、UPDATE
和DELETE
。为了适应从两个表而不是一个表中获取数据,你将学习另一个有用的 SQL 特性,称为INNER JOIN
。
为了避免重复,我们将在 MySQL 部分教授这些命令的基本知识。
MySQL
你不需要是机械师就能开车,但引擎必须在引擎盖下,否则它不会移动。要运行使用数据库的应用程序,引擎盖下必须有一个数据库引擎。数据库服务器过去是专用计算机;据我所知,卡尔·马尔登和迈克尔·道格拉斯在我之前提到的电视剧中使用了数据库服务器。
现在,数据库服务器通常是软件包。Oracle 公司专注于数据库软件;微软有多个 SQL Server 产品。这些软件包可以安装在专用计算机上,仅作为数据库服务器使用,但也可以安装在充当 Web 服务器的同一台计算机上,以及我们的开发系统上。我们将要使用的软件包被称为 MySQL。
MySQL 是开源的,这意味着它是一个由瑞典公司创建并由 Sun Microsystems 收购的数据库服务器,Sun Microsystems 又被 Oracle 收购。
如果你已经为 Windows 安装了如 XAMPP 或 WAMPSERVER (wampserver.com) 这样的捆绑包,那么你已经有 MySQL 了。如果没有,你可以从 mysql.com 下载它。安装非常简单。之后,你可能需要在计算机设置中检查确保每次启动系统时 MySQL 服务器都会启动。像 UNIX 系统一样,MySQL 有用户的概念,其中最强大的是 root。MySQL 安装时 root 没有密码。你应立即更改它。你可以使用 mysqladmin
命令来完成此操作。对于其他所有事情,我们将使用工具或自己编写程序。以下是更改 root 密码的命令:
mysqladmin -u root password yourchoiceofpassword
phpMyAdmin
phpMyAdmin
是一个用 PHP 编写的开源实用工具,用于帮助您管理您的 MySQL 数据库(们)。您不必使用它,因为还有其他选项,但我已经将其作为我的工作流程的一部分来创建用户、数据库和表格,甚至最初填充表格。这相当于 SQL 精通者所说的 模式语句。
我也偶尔使用 phpMyAdmin
来删除或更改表格行。phpMyAdmin
是 XAMPP 的一部分,所以如果你不使用 XAMPP,你将不得不先下载它。一旦掌握了以下概念,使用起来应该很简单。在最坏的情况下,你可以从网上抓取一些文章。甚至有一本关于 phpMyAdmin
的书,由 Packt Publishing 出版。
创建数据库
你可以用一个数据库做所有的事情,只需为每个项目添加表格。然而,如果你的项目足够独特且相当大,为每个项目创建不同的数据库会更好。phpMyAdmin
允许你这样做,无需使用 SQL 命令。
创建和管理用户
我们已经提到了根密码。根用户可以执行所有操作。当你的程序访问数据库时,你不想任何人意外地删除数据库中的任何数据。这就是为什么我们在 MySQL 中创建其他用户,只赋予数据库足够的 CRUD 权限。你只想允许在特定数据库中创建、读取、更新和删除(CRUD 代表的内容)记录。为此,你创建用户,给他们设置密码,并分配权限。再次感谢phpMyAdmin
,你仍然不需要学习 SQL 命令。
创建和管理数据库表
正确规划数据库将包含的表对于成功至关重要。最好首先在纸上草拟一下。一旦你接近你想要的样子,你就可以使用phpMyAdmin
为你的数据库创建表。你为表命名,指定它属于哪个数据库,然后开始列出所有列,或字段,它们的名称、类型,以及可能的最大长度,索引作为主键
首先列出。
一旦设置好,你甚至可以开始传播那些表中的数据。对于一个书店 Web 商店应用程序,你可以使用这个工具输入你出售的所有书籍的信息;应用程序本身将负责将客户数据和订单数据添加到你的数据库中。"customers"和"orders"当然可以作为表的选择。使用phpMyAdmin
,你至少会创建你表的结构,但可能是你的 Web 应用程序填充了数据。以下是有两个表的示例:books和authors。
书籍表将包含以下数据:
作者表将包含以下数据:
那么,我们如何从应用程序到达数据库?这又把我们带回了 PHP 和mysqli对象。
PHP 中的 MySQLi
尽管我们在第五章中故意省略了这一点,但 PHP 具有面向对象编程语言中的特性。所以,你不必编写函数,而是可以创建对象并为它们编写方法。这正是 PHP 编程接口与 MySQL 工作的方式:有一个专门的mysqli
对象和一系列方法。使用这些方法,你可以连接到数据库,提交查询,获取结果,最后关闭连接。
假设我们有一个在线书店的 Web 应用程序。我们的数据库名为bookstore
,我们为它创建了一个用户bookuser
,密码为book**4u
。数据库本身至少包含书籍和作者表。
让我们现在用 MySQL 编写第一个 PHP 程序。
连接到数据库
首先,我们需要连接到数据库。为了做到这一点,我们需要确定我们想要连接到哪个数据库,数据库服务器在哪个主机上运行,以及我们想要使用的数据库用户。主机几乎总是 localhost
。所以,这是我们程序的开始:
<?php
$database = "bookstore";
$user = "bookuser";
$password = 'book**4u'; // I want to make sure asterixes do not get expanded
$host = "localhost";
$mysqli = new mysqli(); // create a mysqli object
$mysqli->connect($host, $user, $password, $database);
$mysqli->set_charset("utf8"); // Make sure we get data back encoded as UTF-8
?>
我们可能想要从函数中调用调用连接方法的语句,这样我们就可以添加一些智能功能,在数据库连接失败时,为用户提供有意义的反馈。
关于字符集的说明非常重要。我是一个 I18N 的人,所以我会省略细节;我甚至不会解释 I18N 是什么意思。UTF-8 是一个代码集,它涵盖了用于许多不同语言的字母如何转换成数字。这一行有助于你确认数据是否以正确的方式存储在你的数据库中。
接下来,当你的数据被检索并需要在屏幕上显示时,它可能需要转换成另一个代码集。但这样,你至少知道你自己的数据是如何存储的。
我们真正的第一个 SQL 查询!
在纯 SQL 中,从存储在我们表 books
中的数据库获取数据的简单查询将是:
SELECT * FROM books WHERE 1;
这是 phpMyAdmin
会建议的默认 SQL 查询。它甚至可以更短:
SELECT * FROM books;
注意到查询以分号结束,并且我们没有提到数据库名称。我们在建立连接时已经做了这件事。所有的 SQL 关键字都是大写的。我们的 WHERE
子句是最简单的,因为它总是为真。我们只是添加它来帮助我们理解示例。*
告诉服务器获取所有列。如果你只想获取标题和价格,你会使用:
SELECT title, price FROM books WHERE 1;
现在,我们将把这个查询翻译成一对 PHP 语句。在你继续阅读之前,我建议(假设你已经创建了一个数据库和书籍表,并在其中放入了一些书籍)你进入 phpMyAdmin
,选择我们的数据库,点击 SQL 选项卡,输入上面的查询,并检查结果。
在 PHP 中编写 MySQL 查询
我们在 PHP 中的第一个 MySQL 查询相当简单。我们将把查询转换成 PHP 字符串,并调用 query
方法来获取结果:
<?php
$sql = 'SELECT title, price FROM books WHERE 1;';
$result = $mysqli->query($sql);
?>
获取结果
现在,如果我们正确地输入了所有内容——一开始你可能不小心遗漏了关闭单引号两侧的一个分号——MySQL 将会返回我们表中每本书的标题和价格。它是通过返回一个我们命名为 $result
的对象来做到这一点的,我们可以对这个对象应用一些方法。
最简单的一个会给我们找到的行数,最强大的是 fetch_assoc()
,它将为我们创建一个关联数组,我们可以在 while 循环中立即使用。所以,这就是我们如何用几行代码生成一个包含所有书籍标题和价格的 HTML 表格的方法:
<?php
$numbooks = $result->num_rows;
$htmlstring = '<table><thead><tr><th>Book title</th><th>Price</th></tr></thead><tbody>';
while ($row = $result->fetch_assoc())
{
$htmlstring .= '<tr><td>'.$row['title'].'</td><td>'.$row['price'].'</td></tr>';
}
$htmlstring .= '</tbody></table>';
echo $htmlstring;
?>
关联数组的键只是表中列的名称。
从多个表中获取数据
假设我们不想列出所有书籍,只想列出单一作者的书籍。我们想要所有 John Muir 的书籍,所以这里的 SQL 命令是:
SELECT title, price FROM books WHERE author_id = ??? ;
等一下!我们如何知道 John Muir 的 author_id
是多少呢?我们不知道。关于 John Muir,我们知道他的名字和姓氏,这些存储在 authors
表中。所以,这就是本章中最困难的一个查询:
SELECT title, price FROM books b INNER JOIN authors a WHERE b.author_id = a.author_id AND aname = "Muir" and afirst = "John";
或者,我们也可以使用:
SELECT title, price FROM books b INNER JOIN authors a WHERE b.author_id = a.author_id AND a.aname = "Muir" and a.afirst = "John";
注意到细微的差别吗?我们使用 a
和 b
作为作者和书籍的简称。因为我们明智地选择了字段名,所以在其他表中没有出现重复的名字。如果那样的话,我们就需要指定表名后跟一个点,就像我们在第二个例子中所做的那样。否则,会出现一个关于歧义的错误信息。
查询结果包括了所有书籍的标题和价格,其中 books
表中的 author_id
与 authors
表下的 author_id
相同,并且在这个表中,它匹配了 aname
设置为 Muir 且 afirst
设置为 John 的那些。请注意,使用这个查询可能会返回两个不同的 John Muir 的结果。
这就是如何从多个表中获取数据的方法。还有一些细微差别,比如 LEFT JOIN
和 RIGHT JOIN
,但这些都超出了本书的范围。
添加数据
我们提到了使用 phpMyAdmin
来传播我们的表。一旦我们的应用程序运行起来,我们需要知道如何使用 PHP 代码添加日期,例如,向系统中添加一个订单。我们将提供一个向 books
表添加书籍的示例。当然,我们使用的所有字符串都可以替换为 PHP 变量。我们这里需要的 SQL 命令是 INSERT
:
<?php
$insertsql = 'INSERT INTO books (title, author_id, price) VALUES ("My new book", "3", "38);'
$mysqli->query($insertsql);
$newestbook = $mysqli->insert_id;
?>
这将自动在你的表中添加一行,创建一个比其他所有值都高一级的主键,插入标题、author_id
并将价格插入该行。所有其他字段将获取你在使用 phpMyAdmin
构建表时指定的默认值。
如果你想要检索新创建的主键的值,insert_id
函数非常方便。我们现在将使用这个函数将价格从 38
改为 39
。
更新数据
假设我们想要更改书籍的价格或添加我们在发出添加数据的 INSERT
命令时没有输入的信息。这就是 UPDATE
命令被使用的地方。其语法如下:
<?php
$updatesql = 'UPDATE books SET price="39" WHERE book_id='"'.$newestbook.'";';
$mysqli->query($updatesql);
?>
这将把刚刚添加的书的定价从 38
改为 39
。
你可以在 mysql.com、php.net 和许多其他网站上找到一个非常全面的 MySQL 命令在线手册;只需在 Google 上搜索 mysql UPDATE
,它就会出现。这就是为什么我们只在本章中提供基础知识的原因。
摘要
在本章中,我们解释了如何将数据库添加到整体网络开发图中。我们选择 MySQL 作为数据库引擎,因为它开源、具有 PHP 编程接口,并且可在所有平台上使用。我们介绍了另一种编程语言 SQL,并解释了它如何与 MySQL/PHP 结合。
为了创建和管理数据库,包括向您的表中添加初始数据,我们讨论了使用phpMyAdmin
,这是一个我们推荐添加到您的工作流程中的优秀工具。
本章也总结了本书的第一部分,这部分内容涵盖了被我称为经典网络开发的内容。我们回顾了所有经典组件,特别是人们用来开发网站和简单网络应用的编程语言:HTML、CSS、JavaScript、PHP 和 MySQL。
在本书的第二部分,我们将更进一步,向您展示如何编写更短的代码,使用单个网页而不是数百万个网页来完成工作,并以更智能、不同的方式编写一切,以便您的网站或应用在所有设备上看起来都很棒,从桌面到平板电脑再到智能手机。
第七章:jQuery
我认为jQuery是网络开发中最酷的事情之一。我正在做一些自学,按照我提到的课程时间,在美丽的卢汶大学城,那里有一个同样美丽的计算机书店。正是在这里我发现了 jQuery。
每一章都依赖于并使用 jQuery,因此从一开始就非常重要的是要很好地了解 jQuery。那么,jQuery 究竟是什么呢?它是一个流行的 JavaScript 库,使用它的总体好处是你可以编写更干净、更紧凑的代码。
那么,什么是库呢?嗯,它可能是一个有很多书的场所。我自己喜欢安特卫普的 Plantijn-Moretus 博物馆的那个,他们在几个世纪前都自己印刷书籍。鲁本斯是插图画家和宅邸肖像画家。UNIX 和 Java 人士认为库是已经编译好的代码,通常包含预定义的函数,这些函数放置在特殊格式的文件中,并且可以与程序本身一起加载。JavaScript 库也可以包含预定义的函数,但除此之外,它们只包含可读的 JavaScript 代码。
大多数 JavaScript 库有两种格式,常规或最小化,通常称为name.js
和name.min.js
。这些最小化版本去除了所有空格、换行符等,以减小文件大小,从而减少下载时间。要使用它们,你可以简单地将它们包含在你的程序中。
注意
jQuery 使用 CSS 样式选择器,这是我们已经在第三章中了解到的,CSS,来访问 DOM 元素。除了让你能更快、更干净地编写 JavaScript 代码外,jQuery 还会处理某些事情,例如解决浏览器兼容性问题,因此你不再需要自己编写这些代码。
获取 jQuery 库
你可以从jquery.com下载 jQuery。一旦进入生产阶段,你可能想使用最小化版本。在开发过程中,我会坚持使用更容易阅读的版本,这样你就可以不时地添加代码进行调试,只要你别忘了稍后将其删除。
在你的页面上如何放置 jQuery 库?
并不是每个人都同意在这里应该做什么。总是会有关于加载文件所需时间的担忧。如果你在文档的<head>
部分或紧接在关闭</body>
标签之前加载 jQuery 库,将会产生差异——例如:
<script src="img/jquery.js"></script>
担忧的是加载库所需的时间。我有时会将所有是库的东西,换句话说,即那些不实际做任何事情但提供功能的东西,放在文件的头部部分。这意味着功能将在 HTML 之前加载。
你将始终与自己的 JavaScript 文件一起使用 jQuery,例如,mycode.js
。此文件将包含以下内容中的 jQuery 代码行:
$(document).ready(function(){
}
您放入那里的所有代码将在页面加载后执行。因此,此文件需要放在您的文件中,在构成您页面的所有 HTML 之后,最好是在关闭</body>
标签之前:
<script type="text/javascript" src="img/mycode.js"></script>
jQuery UI 和 jQuery Mobile
jQuery UI和jQuery Mobile是 jQuery 家族的两个附加库。jQuery 用户界面(UI)为您提供了许多小部件,您可以在网站的用户界面部分使用。我最喜欢的有accordion
和datepicker
。与仅使用 JavaScript 的 jQuery 本身不同,jQuery UI 还附带了一整套 CSS 文件。这意味着,一旦您使用这些小部件之一,它们将具有自己的外观和感觉。
不必担心,jQuery 提供了一个名为Themeroller的酷工具。使用这个工具,您可以生成一组定制的 CSS 文件,以便 jQuery UI 元素的色彩和其他外观感觉功能与您的样式表相匹配。
jQuery mobile 是另一个基于 jQuery 的 CSS/JavaScript 扩展,它为您提供了用户界面元素来创建移动优先的网站和应用。您可以创建将在手机、平板电脑和桌面上工作的网页。我们将在这本书中专门用一章来介绍移动优先,然后在后面介绍响应式设计。现在,请记住 jQuery Mobile 是一个框架,它允许您在您的 Web 应用中利用手机的硬件和设备,例如拥有一个与您在手机上设置闹钟相同行为的日期选择器,通过点击网页上的电话号码自动拨打电话等。
我们在这本书中不会使用 jQuery Mobile。相反,我们将介绍一个名为Foundation的不同的 CSS/JavaScript 框架。
使用 jQuery 选择器和方法
使 jQuery 如此易于使用的原因之一是,您可以使用 CSS 样式选择器在您的页面上查找元素。因此,您不必学习更多的 JavaScript 方法,只需使用您已经知道的方法。在实际代码中,这意味着您不必使用以下:
var content = document.getElementById("content");
您可以使用:
var content = $('#content');
$ ()
是jQuery()
的缩写表示法。如果您同时使用 jQuery 和类似库,如Dojo
,请查看文档了解如何将两者区分开来。
上述语句将创建一个 jQuery 对象,该对象将包含零个或一个 DOM 元素(因为只能有一个具有id
内容的元素)。下一个语句可能包含很多元素,多达具有green
类的元素数量:
var greens = $('.green');
如您所见,这与 CSS 中的用法相同,#
用于id
,.
用于class
。
现在我们可以开始使用方法了。如果我们想将所有具有green
类的元素更改为yellow
类,怎么办?为此,我们可以使用addClass
方法:
$('green').addClass('yellow');
当然,你可能想将绿色替换为黄色,所以类green
也必须被移除,你可以使用removeClass
方法。jQuery 方法的伟大特性之一是你可以将它们嵌套,所以我们可以用一行代码完成所有这些事情:
$('green').removeClass('green').addClass('yellow');
现在,我们将向您介绍一些非常有用的方法,您可以使用它们即时更改您的网页,或者找出页面上现在有什么。这些大多数都是获取器以及设置器。这意味着你可以获取元素的值,或者,如果你指定了一个参数,这将用于设置值。我们开始吧。
html()
使用这种方法,你可以获取或设置一个元素的 HTML 内容:
var contenthtml = $('#content).html(); // Get all the html code inside #content
var newcontent =
'<div><h2 class="unique">This is a header</h2><p>This is a paragraph</p><p>This is a second paragraph</p></div>';
$('#content').html(newcontent);
第二组指令将用前面的 HTML 代码替换#content
的内容。
text()
类似于前一种方法,但有所不同,这个方法获取/设置 HTML 标签内的文本:
var oldheader = $('h2.unique').text(); // Get only the text inside the h2 element
$('h2.unique').text("This is the new text in the header"); // Replace the text
attr()
这种方法让我们可以操作属性的值:
var linkvalue = $('a.unique').attr('href'); // get the href attribute value of an anchor tag
$('a.unique').attr('href', 'http://www.paulpwellens.com');
.val()
这种方法获取或设置元素的值:
var inputvalue = $('input.name').val();
show()
和 hide()
这些是在动态网页中非常有用的方法。非常经常,你想要为屏幕的一部分生成 HTML 代码,然后显示该代码,同时也做相反的事情:让屏幕的一部分消失。实现这一点的简单技术是准备一个<div>
元素的内容,将其插入到<div>
元素中,然后使其可见。你会在自定义 JavaScript 文件中这样做:
$('#content').show();
$('#content').hide();
这些方法也非常有用,可以防止一种称为未处理内容闪烁(FOUC)的现象。我们这是什么意思?还记得关于在哪里放置你的 jQuery 库和自定义 JavaScript 文件的讨论吗?让我们假设你使用一个通过操作图像的无序列表(<ul>
)来创建幻灯片的 JavaScript 插件(<img>
)。所以,你的原始 HTML 可能看起来像这样:
<div id="slideshow">
<ul>
<li>
<img src="img/mono.png" alt="Mono Lake">
</li>
<li>
<img src="img/monumentvalley.png" alt="Monument Valley">
</li>
<li>
<img src="img/centraalstation.png" alt="Central Station">
</li>
</ul>
</div>
你正在使用的 jQuery 插件,例如,OWL Carousel (owlgraphic.com) 将将这段 HTML 代码转换成一个华丽的幻灯片。然而,如果连接相当慢,你可能会首先看到所有图片堆叠在一起,前面有一个愚蠢的子弹,然后几秒钟后幻灯片才出现。你可以通过在你的样式表中放置以下内容来解决这个问题:
#slideshow {
display:none;
}
然后,在你的自定义 JavaScript 文件中,在构建幻灯片动画的代码之后,你包含以下内容:
$('#slideshow').show();
这将导致屏幕的这一部分在幻灯片准备好之前不渲染任何内容。
.find()
.find
方法非常强大。你可以在你的页面上找到几乎所有东西,然后对结果进行操作。以下是一个例子:
var address = $('#record').find('p.address');
这将查找并找到具有 address
类的所有 <p>
元素,在具有 ID record
的元素中。再一次,你可以嵌套或链式调用它与其他方法。下一个例子查找具有 id
属性的元素,然后查找它的值:
var id = $('#record').find('input[name="id"]').val();
.parent()
使用 .parent()
可以更加强大。第一个例子查找指定的 <td>
的 <tr>
元素,下一个例子向上查找三个级别:
var tablerow = $('td.name').parent().html() ;// obtain the html code of the parent table row
var greatgranddad = $('a.threedeep').parent().parent().parent() ;
.next()
.next()
方法是向右查看的。它返回 DOM 结构右侧元素的兄弟元素,如果没有可选选择器匹配,则不返回。.next()
只查找紧挨着的元素,所以如果你想查找更多,你需要链式调用它:
var maybe = $('div.left').next('.middle'); // only returns something if the element to the
//next is of class middle
var threetotheright =
$('#thistable').find('td.first').next().next().next();
// in a table with first last address zipcode city records this would get us the zip
.css()
使用 jQuery,你也可以获取或设置任何给定元素的 CSS 值。在第一个例子中,我们检索 background-color
;在第二个例子中,我们设置 color
:
var bgcolor = $('#content').css("background-color");
$('#color').css("color", "teal");
jQuery 文档
完整的 jQuery 文档以及更多酷方法的描述可以在 jquery.com 找到。
市面上有很多 jQuery 书籍,可能太多以至于难以确定哪一本适合你;这本书不是其中之一。我们将只带你了解所有你需要进行 Web 开发的技术——jQuery 是其中之一。
如果你需要选择你的第一本 jQuery 书籍,我推荐 Packt Publishing 的最新版 Learning jQuery。
事件处理程序和 jQuery
恭喜你,你已经达到了本书的一个重大里程碑。这一页一次性介绍了几个新概念,你将每天都会用到很多。假设你正在构建一个带有菜单的网站。菜单是用有序列表构建的,下面是一个菜单项的代码:
<li id="intnews"><a href="oldsite/intnews.php" class="news">International</a></li>
在你的自定义 JavaScript 文件中,你有:
$("#mainmenu").on("click", "a.news", function(e){
e.preventDefault();
var nav = $(this).parent().attr("id");
updateNewsContent(nav);
});
让我们先讨论 .on()
部分。这是 jQuery 执行 事件处理 的方式。当网站访客执行某些操作时,就会发生事件。一个典型的操作是在按钮或链接上点击鼠标。然后我们可以捕获该事件,并在我们的事件处理函数中执行某些任务。在上一个例子中,我们在名为 updateNewsContent()
的函数中执行了所有这些操作。
重要的是要重复一点,你的 jQuery 代码只能在 DOM 元素加载后访问和操作。所以,如果你在 JavaScript 执行后动态创建 HTML,它们将不会被你的 JavaScript 代码操作。
.on()
方法的用法只能帮助。在例子中,我们将 .on()
方法附加到 #mainmenu
元素上;很可能是一个 <div>
,它从页面初始加载就一直在那里。
.on()
方法的第一个参数是事件本身;在我们的例子中,是 click
事件。第二个参数是可选的,但我经常使用,它描述了我们想要触发事件的选择器。这使得这个事件成为所谓的 委托 事件。我们也可以这样写:
$("#mainmenu a.news").on("click", function(e){
// same code here
});
这使其成为所谓的直接事件。有什么区别呢?如果一个带有 class news 的<a>
标签在页面初始加载后动态地添加到#mainmenu
div 内部,并且被点击,那么委托事件会捕获它,而直接事件则不会。
这是在开发过程中常见的惊喜之一,注意到之前工作正常的东西突然似乎没有做任何事情。通常的补救措施是在你的事件处理程序内部启动一个新的事件处理程序。我们将在后面的章节中提供一个示例。
最后,是函数本身。请注意,它可以有一个参数。在函数内部,你可以访问那个事件对象并应用方法以及访问属性。你将在下一章中看到一些示例。
preventDefault()
样本 HTML 代码包含一个带有href
属性的锚标签(<a>
),基本上创建了一个链接到 PHP 文件intnews.php
。让我们假设我们使用一个古老的浏览器,或者一个 JavaScript 被关闭的浏览器。用户点击<a>
标签会导致intnews.php
被打开。
使用 JavaScript,我们在事件处理程序代码中确定了我们想要发生的事情,所以我们不希望链接发生,我们只想停留在同一页面上。preventDefault()
方法确实会做它名字所暗示的事情。这种技术是所谓的渐进增强的一部分,这将在后面的章节中讨论。
$(this)
而在样本 JavaScript 代码中,你可能时不时地会看到this,但在 jQuery 中我们使用$(this)
。在函数内部,它代表匹配元素(s)的 jQuery 对象。
updateNewsContent()
我们故意没有描述这个样本函数内部的操作。在这个程序的部分,通常会发生的是使用 PHP 和可能是一个数据库从网络服务器检索数据。你已经看到了一些使用经典 Web 开发技术的示例,例如在表单中将 PHP 文件指定为操作参数,这将迫使我们转到另一个页面。
然而,我们将使用允许我们在同一页面上执行 PHP 代码的 jQuery 方法。这些方法所使用的底层技术被称为AJAX,这是下一章的主题。
摘要
在本章中,我们第一次开始远离传统的 Web 开发。我们介绍了 jQuery,这是一个强大的 JavaScript 库,它允许我们编写更干净、更紧凑的 JavaScript 代码。此外,我们将更容易做到这一点,因为它使用 CSS 样式选择器来指定 DOM 元素,而不是我们不得不学习的 JavaScript 方法。
如何下载 jQuery 库以及将其放置在哪里已经解释过了。我们使用 jQuery 最有用和最强大的方法来举例说明本章的其余部分。为了总结本章,我们介绍了 jQuery 创建事件处理程序的方式,这可能是本书这一阶段最重要的概念之一。
在下一章中,我们将继续使用 jQuery,不是用来遍历 DOM 和随意更改一些内容,而是通过执行使用 jQuery 方法调用的 PHP 代码,在 Web 服务器上生成我们页面的整个块。然后我们可以使用这些数据来更新我们页面的部分,而无需离开它。所有这些操作都是通过一种称为 AJAX 的技术实现的。
第八章。Ajax
对一些人来说,Ajax 是阿姆斯特丹的一支荷兰足球队的名字。对网络开发者来说,Ajax(也称为 异步 JavaScript 和 XML(AJAX))是用于客户端以异步方式从服务器检索数据的一组网络技术的总称。我以这个负载很重的句子开始本章,因为我总是喜欢在用到缩写时解释它。
A(异步)几乎总是存在的,J(JavaScript)是肯定的,因为我们谈论的是客户端,但 X(XML)不是强制的。通常 JSON 被用作客户端和服务器之间的数据格式。我们将在第十章,XML 和 JSON中讨论 XML 和 JSON。在我们的示例中,我们将使用已经熟悉的 HTML 格式。
使用这些技术,在数据在后台检索后,可以修改网站,并且可以更新屏幕的部分内容,而无需加载全新的页面。这样,我们的网站将开始更像是一个桌面应用程序,因此我们可以安全地称之为网络应用程序。
如果没有得到妥善管理,Ajax 有其缺点。由于它全部使用 JavaScript 实现,如果关闭 JavaScript,则期望的行为将不会发生。在 2015 年,这不应该是一个问题。还有其他缺点,但并非没有解决这些问题的方法。我们将立即在第九章,历史 API – 不要忘记我们的位置中解决这些问题。
XMLHttpRequest
Ajax 技术依赖于可以在 JavaScript 代码中使用的 XMLHttpRequest(XHR)对象。它用于向网络服务器发送 HTTP 或 HTTPS 请求,并将服务器响应数据加载回脚本中。与其他许多网络技术一样,各种浏览器中的 XMLHttpRequest 实现有所不同。在这里,jQuery 将再次提供帮助。通过使用 jQuery 和它附带的相关 Ajax 方法,这些不兼容性不会引起任何担忧。
Ajax 和 jQuery
jQuery 提供了几个方法,您可以使用它们来执行我们喜欢称之为 Ajax 调用的操作。最完整的一个不出所料地被称为 .ajax()
。我们将从一个简单的例子开始。
许多基于 Ajax 的网站都是这样的——顶部有一个无处不在的菜单,底部有一些其他导航,以及一个不断变化内容的中心部分。我们无法强调得更多,当我们以我们在这里描述的方式使用 Ajax,并且我们的网站被命名为 index.php
时,无论中心部分的内容如何频繁变化,我们的当前网页仍然是 index.php
。
因此,让我们假设我们有一个网站,顶部有一个主菜单,中间有一些基本内容,在一个具有 id varicontent
的 <div>
中。在初始加载时,#varicontent
里面的内容并不重要——通常是一个漂亮的图片横幅——这是解释当我们更改其内容并用不同内容替换它时会发生什么的章节。
jQuery Ajax 方法
我们将向您介绍一些最有用的 jQuery 方法,用于使用 Ajax,从非常简单到更复杂。想象一下一家公司组织的研讨会和展览的官方网站。主页以及所有页面,因为我们从未离开过页面,都包含一个访客可以使用来导航和选择他们所选择主题的菜单。
因此,让我们先展示一些代码——只是一个片段,以说明它是如何完成的:
<div id="mainmenu">
<ul class="dropdown">
<li><a class="htmla" href="oldsite/information.html"
id="information" >Information</a></li>
<li><a id="seminars" class="htmla agenda" href="oldsite/seminars.php">
Seminars</a></li>
<li><a class="htmla" id="exhibits" href="oldsite/exhibits.php">Exhibits</a></li>
</ul>
</div>
<div id="varicontent">
<!-- code for things on the home page, maybe a photo banner -->
</div>
$.load() 方法
我们将使用 load()
方法来加载的不是全新的页面,而是我们需要的精确 HTML,在页面中我们想要替换内容的部分:
$("#mainmenu").on("click", "a.htmla", function(e){
e.preventDefault();
var topic = $(this).attr("id");
updateHTMLContent(topic);
});
function updateHTMLContent(topic) {
var loadfile = "./content/" + topic + ".html";
$('#varicontent').load(loadfile);
}
在刚刚给出的示例代码中,我们处理了任何具有类 htmla
的菜单项。目的是用位于服务器上的文件中的 HTML 替换 #varicontent
内的内容。注意,示例中还有一个 href
属性。我们打算不使用它,但如果需要 JavaScript 不受支持时的后备计划,它也可以存在。我们将在讨论 渐进增强 时再次提及这一点。在示例中,我们包括了一个指向位于 oldsite
文件夹中的文件的链接。
因此,在前面给出的代码中,我们有一个事件处理程序,用于当点击具有类 htmla
的 <a>
标签时。我们首先实际上阻止用户访问 href
标签中指定的链接,这会导致加载一个全新的页面。以下行处理了这一点:
e.preventDefault();
相反,我们将使用 jQuery Ajax 方法 .load()
。我们使用锚点的 ID 值来确定文件名,然后调用 updateHTMLcontent()
函数。
在那个函数内部,我们调用 load()
,这将进行必要的 Ajax 调用来获取文件的内容。然后,我们将 #varicontent <div>
替换为那个内容。现在,屏幕的那部分已经更新,但我们仍然停留在同一个页面上,屏幕的其他部分保持不变。
$.post()
在前面的例子中,我们只需要加载一些 HTML。如果我们想在服务器上执行 PHP 代码以动态创建 HTML,并将其插入到我们的页面中,那么 .ajax()
方法就派上用场了,特别是 .ajax()
的两个特殊情况:.post()
和 .get()
。
如您所猜想的,区别在于,就像 HTML 表单一样,值传递给服务器的方式——是作为 POST 变量还是作为 GET 变量。让我们以第二个菜单项为例,它包含一个类为agenda
的<a>
标签。就像之前的例子一样,我们首先阻止浏览器加载href
属性中指定的文件。这次,我们获取父元素的 ID 值:
$("#mainmenu").on("click", "a.agenda", function(e){
e.preventDefault();
var nav = $(this).attr("id");
updateAgendaContent(nav);
});
function updateAgendaContent(nav) {
$.post("./showagendalist.php", { nav: nav},
function(data){
$('#varicontent').replaceWith(data);
} );
}
现在,我们要求服务器找到服务器上的 PHP 文件showagendalist.php
,并传递一些 POST 变量,或者就像我们的例子中那样,只传递一个。PHP 代码将在服务器上执行,无论它生成什么,我们都可以在函数中捕获它。我们将使用这个函数来使用方便的 JavaScript 方法replaceWith()
将其插入到页面的适当部分。期望返回的默认格式是 HTML,但我们也可以使用其他格式,例如 JSON 和 XML,我们将在第十章中讨论,XML 和 JSON。
下面是一个这个 PHP 代码可能的样子:
<?php
$today = time();
$nav = $_POST['nav'];
switch ($nav)
{
case "seminars":
$sql = 'SELECT * FROM seminars;';
break;
.
/* ... */
default:
break;
}
$mysqli = dbConnect($host, $usr, $pwd, $dbname) ;
date_default_timezone_set('Europe/Brussels');
$mysqli->set_charset("utf8");
$htmlstring = '<div id="varicontent" ><div class="row">';
$result = $mysqli->query($sql);
while ($row= $result->fetch_assoc()
{
$htmlstring .= '<div class="agendaentry">';
$htmlstring .= '<h5 class='title">'.$row['title'].'</h5>';
$htmlstring .= '<div class="summary" >'.'$row['summary'].'</div>';
$htmlstring .= '<div class=" body">'.$row['body'].'</div>';
$htmlstring .= '</div>';
}
$htmlstring .= '</div>'; //row
$htmlstring .= '</div>'; //varicontent
echo $htmlstring;
?>
在前面的 PHP 代码中,我们查看单个 POST 变量$POST['nav']
的值,以确定访问者从菜单中选择了日历的哪个部分。那个部分的名称必须以某种方式与我们的数据库中的表匹配。接下来,我们从数据库中提取所有文章,假设有一个存储为文本的title
列,以及存储为 HTML 的summary
和body
列。然后我们生成所有文章的完整 HTML。最后的语句是一个输出整个生成 HTML 的echo
语句。
这是一个实际示例,用来展示 Ajax 是如何工作的,但在实际应用中并不实用。有两个原因导致#varicontent <div>
中的内容会迅速变得过大:不仅我们展示了每篇文章的全部内容,我们还展示了所有文章。
我们不应该展示每篇文章的全部内容,而只应展示标题和摘要,而不是正文。在我们的生成 HTML 中,我们可以有一个类型为hidden
的<input>
来携带文章的 ID,然后我们将标题转换成锚点标签,使其可点击。这个锚点背后的 jQuery 事件处理器将触发另一个 Ajax 调用,点击后,包含文章标题和摘要列表的#varicontent <div>
将被单个文章的完整内容所替换。但是,我们仍然停留在同一个页面上。
即使这样做也不够。一旦文章的数量变得很大,我们的#varicontent
部分的长度将变得令人不快。为了克服这一点,我们需要应用某种分页,并且一次只显示一定数量的文章。我们将在第十三章中讨论一个具有分页小部件的框架,Foundation - 一个响应式 CSS/JavaScript 框架。在下面的示例代码中,我们已经考虑到了我们需要的一个额外参数,offset
。稍后我们将提供适应这些变化的更新代码。我们在 PHP 代码中省略了switch
语句。现有的 JavaScript 代码的唯一区别是现在UpdateAgendaContent()
将接受一个额外的offset
参数。我们只为额外的 Ajax 调用包括了额外的部分。
更新后的 PHP 代码如下:
<?php
$nav = $_POST['nav'];
if (isset($_POST['offset']))
{
$offset = $_POST['offset'];
}
else {
$offset = 0;
}
$sql = 'SELECT * FROM seminars LIMIT '.$offset.',10;';
$mysqli = dbConnect($host, $usr, $pwd, $dbname) ;
date_default_timezone_set('Europe/Brussels');
$mysqli->set_charset("utf8");
$htmlstring = '<div id="varicontent" ><div class="row">;
$result = $mysqli->query($sql);
while ($row = $result->fetch_assoc())
{
$htmlstring .= '<div class="agendaentry">';
$htmlstring .= '<h5 class="title"><a class="aid">'.$row['title'].'<input type="hidden" name="aid" value="'.$row['a_id'].'"></input></a></h5>';
$htmlstring .=
'<div class="summary" >'.$row['summary'].'</div>';
$htmlstring .= '</div>'; // agendaentry
}
$htmlstring .= '</div>'; //row
$htmlstring .= '</div>'; //varicontent
echo $htmlstring;
?>
额外的 JavaScript 代码:
function updateAgendaContent(nav, offset) {
$.post("./showagendalist.php", { nav: nav, offset:offset}
function(data){
$('#varicontent').replaceWith(data);
$("#varicontent").on("click", "a.aid", function(){
var aid = $(this).parent().find('input').val();
updateArticleContent (aid);
});
} );
}
function updateArticleContent (aid)
{
$.post("./showarticle.php", { aid: aid },
function(data){
$('#varicontent').replaceWith(data);
} );
}
注意,我们现在在updateAgendaContent()
函数内部添加了一个事件处理器,以在访问者点击文章标题时触发正确的事情。这通常是必要的,就像在我们的 HTML 标签示例中,我们想要触发在 Ajax 调用之前页面上不存在的事件。
$.ajax()
正如我们提到的,$.post()
方法(以及还有一个$.get()
方法)是$.ajax()
方法的一个特例。我们使用的参数比.ajax()
少,因为其中一些是预定义的(不出所料,是 POST 或 GET),所以,jQuery 再次使我们更容易编写代码。为了总结这一章,我们将给出.ajax()
方法的使用总结。请查阅完整的 jQuery 文档以获取更多详细信息。这将值得一读。
语法如下:
$.ajax({name:value, name:value, ... })
这些参数指定一个或多个名称/值对。以下是常用参数及其含义的概述:
-
data: 这是发送到服务器的数据。如果它不是一个字符串,它将被转换成一个查询字符串。它可以作为一个对象、一个字符串或一个数组传递。
-
dataType: 这是服务器响应所期望的数据类型。它可以是以 XML、HTML、文本、JSON 或脚本。在我们的示例中,我们假设是 HTML。
-
url: 这指定了发送请求的 URL。默认是当前页面。
-
error(xhr,status,error): 这是一个在请求失败时运行的函数。
-
success(result,status,xhr): 这是一个在请求成功时运行的函数。
-
type: 这指定了请求的类型(GET 或 POST)。
因此,我们最后示例中的$.post()
调用是:
$.post("./showarticle.php", { aid: aid },
function(data){
$('#varicontent').replaceWith(data);
} );
它也可以写成:
$.ajax({
type: "POST",
url: "./showarticle.php",
succes: function(data){
$('#varicontent').replaceWith(data);
}
data:{ aid: aid },
dataType: "html"
} );
摘要
在这一章中,我们介绍了 Ajax,这是一组用于异步从服务器收集数据的网络技术。它用于更新网站和 Web 应用程序上的屏幕的特定部分,而不是每次都加载一个全新的页面。
Ajax 技术基于 XMLHttpRequest 对象,但多亏了 jQuery 和其 $.ajax()
方法,你学会了如何在你的应用程序中使用 Ajax,而无需了解该对象。我们在示例中使用了 $.load
和 $.post
方法来用存储在服务器上或由服务器生成的 HTML 替换屏幕的部分。
Ajax 可以与其他数据格式一起使用,例如 XML 和 JSON。但它也存在潜在缺点,因为现在我们不断更新页面而不实际离开它,这会让访问我们网站的访客将其感知为不同的页面,尤其是在他们按下浏览器的后退按钮时。
这两个主题:使返回键执行预期的功能,以及在客户端和服务器之间使用不同的数据格式,是我们接下来两章的主题内容。
第九章。历史 API – 不要忘记我们身在何处
在上一章中,我们介绍了 Ajax,这是一组用于在 Web 应用程序中更新屏幕部分的技术,无需创建指向物理不同页面的链接,无需浏览器从服务器请求新页面并加载它。现代网站到处都在使用这项技术,它们的开发者也是如此;Ajax 是一种常见的做法,几乎是一种商品。
然而,这个概念对于你必须与之合作的营销人员和你的网站的访客来说都很难理解。你的营销人员不是经常要求你向网站添加一个页面,然后想知道 URL 是什么吗?亲爱的,它是index.php,它总是这样。通过菜单导航的你的网站访客,当他们点击浏览器的后退按钮时,会期望看到他们刚刚离开的屏幕。相反,他们会被带回到他们之前访问的网站,除非你使用本章中我们将教授你的技术。
我们首先将描述我们试图解决的问题;接下来,我们将解释在 HTML5 以及 HTML4 国家中,解决方案是什么。
我们试图解决的问题
想象一个水平导航菜单,访客点击菜单项,就像我们在上一章中使用的例子一样。在一个经典网站上,浏览器会将访客带到另一个页面,例如,galleries.php
,浏览器的 URL 会显示出来。然而,基于 Ajax 的网站将简单地更新屏幕的一部分,每次访客点击菜单项时,都不会对 URL 栏做任何事情。无论他们选择多少项,一旦他们点击浏览器的后退按钮,他们就会发现自己回到了之前的页面,这将是之前的真实页面,通常是一个不同的网站,而这并不是他们期望的地方。
我们将通过一种将网站恢复到其先前状态的技术来解决此问题,因为我们不能将其带到上一页。在我们深入了解细节之前,让我们讨论一下关于推送和弹出状态的一些事情。
自助餐厅
描述我们即将学习的技术最好的方法是将它们与你在任何自助餐厅都能找到的东西进行比较。在这样的餐厅里,你首先拿起一个托盘,吃完饭后,你把它放回去。现在想象一个稍微不同的情况。你打算在放回托盘之前清洗它,这样你就可以把它放回你取它的同一个堆叠上。你必须想象的其他不同之处在于,不是有多个托盘,它们都同样无聊,因为它们看起来都一模一样,每个托盘上都会有一张不同的图片。
因此,所有这些托盘都是单个堆栈的一部分。当你接近堆栈时,你只能看到顶部托盘上的图片。如果你向堆栈中添加一个托盘,那个图片就会消失,因为它被我们放在顶部的托盘上的图片所取代。我们称之为推送。一些老式的堆栈有内置的弹簧,所以你必须真的把托盘推到底部,直到它保持在原位。
当你从堆栈中取出托盘时,情况正好相反。第二个托盘的图片变得可见,整个托盘堆栈略微弹出。这就是我们称之为弹出或弹出的原因。如果没有自助餐厅,可能就没有计算机科学,因为这个想法激励了许多计算机科学家在形式语法领域创建了弹出和下推自动机的理论。我写了我毕业的必读书,关于这样一个主题(不,我们那里没有自助餐厅)。
HTML5 历史 API 和历史对象
浏览器使用一个类似的堆栈,称为history
堆栈。在 JavaScript 中,你可以通过history
对象访问它,该对象有几个可用方法。历史对象是窗口对象的一部分,通过window.history
属性访问。它已经存在多年了。
通常,当用户导航到新页面时,浏览器会将新 URL 推送到其历史堆栈中,并下载和绘制新页面。当用户按下后退按钮时,浏览器从其历史堆栈中弹出一张页面并重新绘制上一个页面。
但如果我们使用 Ajax 调用更新屏幕的部分内容而不需要加载新页面呢?那么,浏览器将不会向那个堆栈中推送任何内容。嗯,这是真的,除非我们自己这样做。使这成为可能的关键是popstate
事件和history.pushState()
函数。
pushState()
每次我们编写代码来更新屏幕的一部分,就像我们在上一章的示例中所做的那样,我们应该使用pushState()
函数将一些相关信息推送到历史堆栈中,并且如果我们想的话,改变浏览器显示的 URL 字符串。pushState()
接受三个参数:
history.pushState(data, title, url);
第一个参数应该是某种结构化数据,例如一个尽可能有意义的键/值对数组。数据应包含足够的信息,使我们能够恢复我们在发出pushState()
时页面的形状。
第二个参数的目的是显示在浏览器历史记录的下拉列表中的某种标题。在撰写本文时,没有单个浏览器实现了这一点。
最后一个参数用于传递一个字符串,这个字符串将成为我们在此页面状态下的替代 URL。它将由浏览器显示,因此,访问者会认为他已着陆在不同的页面上。这个 URL 字符串还有一个第二目的,我们将在本章末尾看到。因此,与一些示例相关的可能的pushState()
语句可能是:
url ="?anchor=agenda&key=" + nav;
updateAgendaContent(nav); // this updates part of the screen
history.pushState({key:nav, anchor:"agenda"}, "", url);
因此,我们刚刚推到浏览器自助餐厅栈顶的盘子将有一个带有变量nav
值的键图片,以及一个标记为agenda
的锚点图片。URL 将与之前相同,并且附加了一个包含这两个键/值对的查询字符串。
popstate 事件
在您使用history.pushState()
函数将伪 URL 推送到浏览器的历史栈之后,当用户按下返回按钮时,浏览器将在window
对象上触发一个popstate
事件。这是我们创造一种错觉的机会,即实际上有不同页面,并且我们将访问者带回到他们认为的上一页。
为了这个目的,我们创建了一个事件处理程序,每次发生popstate
事件时执行一些代码。我们已经有的大部分代码,因为我们使用了一个函数来绘制屏幕的一部分。我们可以简单地重用它。
window.addEventListener('popstate', function(event) {
if (event.state){
switch (event.state.anchor) {
case "agenda":
updateAgendaContent(event.state.key);
break;
..
}
}
else
{
restoreHomePage();
}
});
因此,在前面的代码中,我们通过查看我们的栈来对每个popstate
事件进行操作。如果它不为空但有一个标记为agenda
的锚点图片,我们就查找key
的值并调用带有key
作为参数的函数updateAgendaContent()
,以将屏幕上我们总是更新的部分恢复到其之前的状态。当然,这次我们不会调用pushState()
。如果我们这样做,后续的返回键推送将不会有可见的效果。
还有一种特殊情况,当栈为空时,我们已经用完了托盘。然后,我们需要可能尚未编写的代码。在示例中,我使用了一个名为restoreHomePage()
的占位符函数。这个函数应该做什么?它应该用我们页面首次加载时拥有的初始内容替换屏幕的变量部分。
popstate 和不同浏览器
在撰写本文时,一些浏览器的行为与其他浏览器不同。当前版本的 Safari 在初始页面加载时将触发一个popstate
事件,这可能会非常令人困惑。这也意味着,在 Safari 中,restoreHomePage
函数将在初始页面加载后立即被调用。如果您的函数编写正确,这将用屏幕上已有的确切内容替换变量部分。这不是很愚蠢吗?如果网络速度慢,这可能会导致轻微的延迟,也许会有一些闪烁。
历史插件
popstate
事件和pushState
方法是所谓的历史 API的一部分,该 API 是在 HTML5 中引入的。这意味着我们的魔法只有在使用 HTML5 兼容的浏览器时才会起作用。当然,这还不够好。仍然有很多人使用 HTML4 兼容的浏览器访问你的网站,所以我们该怎么办?
幸运的是,有几个 jQuery 插件允许你使用相同的或类似的 API,并且让你的代码在两种类型的浏览器中都能工作。其中一个甚至有一个名字会让你再次想起自助餐厅(BBQ),但我一直在使用所谓的jQuery history
插件。在撰写本文时,该插件可以在github.com/browserstate/history.js
找到。
书签
让我们从自助餐厅回到我们的市场营销人员,他们想知道他们可以使用哪个 URL 将文档中的链接放入页面,以便人们可以快速访问网站上的信息。也许他们还想了解如果访客使用浏览器的书签功能保存(在我们的案例中,是虚拟)页面的 URL 以便稍后重访,应该怎么做。我们可以通过在pushState
代码中使用的 URL 部分来支持这一点。
在我们的示例中,我们确保在历史堆栈上推送的状态中存储了足够关于我们更新页面的信息,并且你学习了如何使用这些信息将我们的页面恢复到其之前的状态。在我们的pushState
代码中,我们在 URL 上附加了一个查询字符串,它基本上包含了相同的信息。我们可以使用浏览器的location
对象及其href
属性来检索这些信息。以下是如果使用带有查询字符串的 URL,将使我们的单页网站进入预期状态的代码:
$(document).ready(function(){
var url = location.href;
if (url) {
var urlData = url.QueryStringToJSON();
if (urlData.anchor) {
var anchor = urlData.anchor;
var topic = urlData.key;
switch (anchor) {
case "agenda":
updateAgendaContent(topic);
history.pushState({key:topic, anchor:anchor}, topic, url);
break;
}
}
}
});
注意,我们已经走得很远了。在几章之前,我们可能会猜测这只能在包含 GET 变量的大量 PHP 代码中实现,以获取我们的键/值对,但现在这完全是使用 JavaScript 和 Ajax 编写的。
在前面的例子中,使用了一个名为QueryStringToJSON()
的函数。这不是一个标准的 JavaScript 函数。这是你可以自己编写的,或者像我一样,从网上抓取的。它分解查询字符串并将键/值对存储到 JSON 对象中。是的,JSON!关于 JSON 的内容你将在下一章学习。
摘要
在本章中,我们讨论了使用 Ajax 的主要缺点之一。你学习了如何使用 HTML5 历史 API 在访客按下浏览器后退键或将他们之前书签的 URL 拖入浏览器地址栏时创建预期的行为。尽管这个 API 是为 HTML5 兼容的浏览器设计的,但你学习了有 jQuery 插件可以支持 HTML4 兼容的浏览器中的这种魔法。
在最后几章中,我们开始更频繁地使用 Ajax 来仅更新屏幕的一部分,并保持在同一页面上而不是加载新的一页。因此,我们在客户端和服务器之间交换的是更小但更频繁的数据块。到目前为止的示例中,我们数据的形式是 HTML,并且所有内容都是在服务器上生成的。在下一章中,你将学习两种新的数据交换格式:XML和JSON。
第十章:XML 和 JSON
到目前为止,我们一直在使用 HTML。它是网页的格式,因此我们用它来创建静态网页。因此,HTML 是服务器和客户端在.html文件页面加载期间交换的数据的格式。
当我们动态创建网页时,我们的 PHP 代码会在数据发送时组合 HTML 行,这将是我们真正交换的数据,而不是你.php文件的原始内容。当我们使用 Ajax 时,我们没有在我们的 jQuery Ajax 方法.post()
或.ajax()
中指定数据格式作为参数,因为这被隐含地理解为将是 HTML。
在本章中,我们介绍了两种不同的数据格式,XML 和 JSON。在网络开发中,我们可以以不同的方式使用它们。首先,它们可以成为你数据库的替代品。对于简单的项目,你的数据可以存储在文本文件中,而不是完整的数据库中。你甚至可以将其存储为纯文本。
下一个,也是更好的水平,将是使用 XML 文件或 JSON 作为你的数据。它们同样是纯文本,但以特殊的方式格式化。以该格式存储的数据可以轻松转换为 HTML,这使我们回到了之前的场景。
另一方面,我们可以首先将数据发送到客户端,然后在客户端进行处理以创建我们页面的内容。在本章中,我们将涵盖这两种场景。
XML
可扩展标记语言 (XML) 是一种被广泛采用的格式,用于公司、文档和程序之间交换信息。它使用与 HTML 类似的标记符号。实际上,HTML 是 XML 的一个特例。在 HTML 中,标签具有意义,并且旨在由浏览器进行解释。在 XML 中,标签可以代表任何东西,也可以什么都不代表,但规则更为严格。当然,如果你是 XML 文件的创建者,我们希望标签对你来说有一定的意义。
作为一名网络开发者,你可能永远不会使用 XML,但了解它的存在很重要。它被创建成一种既适合机器读取又适合人类阅读的数据格式。人类将能够阅读其中的内容,因为 XML 文件基本上是文本文件;计算机程序可以读取它们,因为它们结构良好。XML 格式在多个领域得到应用。我们在这里只提及其中的一些:
-
网络服务:有些人或公司通过提供网络服务来允许访问他们的数据库中的信息。如何创建网络服务超出了本书的范围。网络服务通常提供给你的能力是能够访问一个特定的网络地址,其中查询字符串表明你正在寻找的信息类型。访问这个(虚拟)网站的结果是 XML 格式的输出流,然后你可以对其进行处理。我们将教你如何进行处理,而不是创建服务。
-
数据库的替代方案:对于简单的应用程序或其部分,使用完整的数据库可能有些过度。正如我们提到的,你可以使用纯文本文件,或者更常见的是
.csv
文件。使用 XML 文件来做这件事会更好,因为它有更多的结构,而且很可能正是客户想要的那个结构。
XML 格式
XML 文件的格式实际上非常简单。它是一个以类似以下行开始的文本文件:
<?xml version="1.0" encoding="utf-8"?>
这行提到了 XML 的版本,到目前为止只有 1.0 或 1.1。规则如此简单,几乎没有空间进行更改和编码。这被称为XML 声明。随后的所有内容都是实际的 XML 文档,它由标签或元素组成,每个开标签都有一个对应的闭标签,以及它们之间的文本。看看这个第一个例子:
<?xml version="1.0"encoding="UTF-8"?>
<!— A list of people that like California —>
<californiapeople>
<person id="1">
<name>Adams</name>
<first>Ansel</first>
<profession>photographer</profession>
<born>San Francisco</born>
<picture/>
</person>
<person>
<name>Muir</name>
<first>John</first>
<profession>photographer</profession>
<born>Scotland</born>
</person>
<person>
<name>Schwarzenegger</name>
<first>Arnold</first>
<profession>governator</profession>
<born>Germany</born>
</person>
<person>
<name>Rowell</name>
<first>Galen</first>
<profession>photographer</profession>
<born>Oakland CA</born>
</person>
<person>
<name>Wellens</name>
<first>Paul</first>
<profession>travel guide</profession>
<born>Antwerp Belgium</born>
</person>
</californiapeople>
如你所见,每个开标签都有一个对应的闭标签,并且有一个标签 <californiapeople>
首次出现且仅出现一次。这是根标签。标签之间可以有其他标签,但它们必须正确嵌套。我已经给第一个人的标签添加了一个属性,并且还包含了一个空标签。就像 HTML 中的 <br/>
标签一样,<picture/>
是 <picture></picture>
的缩写表示。这样的元素被称为自闭合元素。
由于我时不时地使用 XML,我养成了在 HTML 文件中始终使用闭标签的习惯,即使在不强制的情况下也是如此,例如在 HTML5 中。其他明显的区别是,在 XML 文件中,空白不会截断,元素名称的大小写敏感,所以 <Person>
与 <person>
不同。
最后,正如预期的那样,有一些字符你不能在 XML 文件的文本中使用:<
和 &
。XML 会认为它已经到达了开标签的开始。如果你需要 <
符号,请使用 <
代替。你应该记得我们在几章前讨论 HTML 实体时提到的这一点。XML 中使用的其他实体还有 &
,它解决了“先有鸡还是先有蛋”的问题,即如果 <
表示 <
,那么单独使用 &
也是不允许的。>
、'
和 "
也可以使用,尽管 >
、'
和 "
是合法字符。如果你需要它们作为文本的一部分,我们建议你使用实体。顺便说一句,你注意到注释的语法也与 HTML 相同吗?还有更多相似之处,我们甚至可以给 XML 文件添加样式!
显示 XML 文件
第一次查看 XML 文件时,你可能看不到你期望的内容。显示 XML 文件的最佳方式是使用浏览器,浏览器会像处理 HTML 文件一样处理该文件;因此,你将看不到标签,只能看到它们之间的文本。这也是我喜欢使用 Firefox 作为浏览器的原因之一。
Firefox 和 Chrome 会显示整个 XML 文件,包括标签。标签名称以不同的颜色显示,并且所有内容都进行了适当的缩进。Firefox 还会检查它是否是一个格式良好的 XML 文件,如果不是,会生成一个有意义的错误消息,指向文件中的问题行。
有时 Firefox 会显示:“此 XML 文件似乎没有与之关联的任何样式信息。以下显示文档树。”嗯,这个 Firefox 消息暗示了 XML 文件可能有样式表,不是吗?那甚至可能是一个 CSS 文件吗?是的,可以。只需在 XML 声明之后添加以下行:
<?xml-stylesheet type="text/css" href="california.css"?>
然后,创建一个包含以下内容的 CSS 文件。现在,Firefox 将仅显示您文件的正文,而不是标签,并使用一些奇特的颜色。在本章的后面部分,您将了解到可能存在其他不是 CSS 文件的样式表。
由于 XML 的特性,以下的所有代码示例都相当长。您可能需要考虑访问 Packt Publishing 网站上本书的在线页面,并在那里获取它们:
californiapeople {
background-color: #FFDEAD;
}
name, first {
color:teal;
font-size:20px;
margin-top:30px;
}
name {
margin-left:25px;
}
profession, born
{
display:block;
margin-left:50px;
font-size:16px;
font-family:Baskerville, "Times New Roman", serif;
color:blue;
}
XML 编辑器
由于 XML 文件是文本文件,你可以使用任何文本编辑器来创建和编辑它们。Textastic是一个出色的编辑器,只需花费几美元,我很喜欢使用,同时也有适用于 MacOS 和 iOS 的版本。Dreamweaver用户将受益于其验证 XML 文件正确语法的功能。
然而,在 XML 的世界里,验证可以超越简单的语法。
XML Schema
假设您正在与几个需要向您提供数据的客户合作,例如员工记录,您要求他们使用 XML 格式来这样做。您已经给自己找了一个大麻烦!随着数据的到来,您意识到一个客户使用了<name>
元素来表示姓氏,而另一个客户则决定使用<last>
。不仅如此,他们可能使用了不同的顺序和组合。
您首先应该做的是给出您希望 XML 文件看起来是什么样的定义。然后,您就可以开始编写应用程序,在您收到数据之前处理这些数据。
在定义 XML 文件结构时,使用了几个约定。其中一个被称为文档类型定义(DTD),另一个则是XML 模式。XML 模式格式的优点在于,描述你的 XML 文件结构的文件本身也是一个 XML 文件。本书的范围不包括介绍这两种方法。不过,这里提供了一些 XML 模式文件的示例。
作为开始,这里是我们之前示例中结构相似的 XML 模式定义:
<xs:schema >
<xs:element name="californiapeople">
<xs:complexType>
<xs:sequence>
<xs:element name="person" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="name"/>
<xs:element type="xs:string" name="first"/>
<xs:element type="xs:string" name="profession"/>
<xs:element type="xs:string" name="born"/>
<xs:element type="xs:string" name="photograph"/>
</xs:sequence>
<xs:attribute type="xs:byte" name="id" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
如您可能已经推断出的,这是对 XML 文件中可以包含的所有元素的描述,它们可以有哪些属性,它们应该以何种顺序出现,等等。
SimpleXML
假设我们在服务器上有一个包含我们想要用于动态生成网页的数据的 XML 文件,就像之前在 PHP 程序中从 MySQL 数据库中提取数据一样。这该如何实现?
有几种方法。在这个例子中,使用了 SimpleXML。它由几个 PHP 类组成,具有处理现有 XML 文件或创建新文件的方法。以下示例取自我的网站的一个旧版本,其中包含许多照片画廊。照片信息存储在一个 XML 文件中,每个画廊一个。每次需要将照片添加到网站上时,只需向适当的 XML 文件中添加一个 node
即可。
我已经简化了 XML 文件内的文本,否则这部分章节就会变成一本旅行指南。
这是 XML 文件
使用你喜欢的编辑器创建 practical.xml
:
<?xml version="1.0" encoding="utf-8"?>
<photocollection>
<title>June Lake</title>
<overview>
The June Lake Loop begins just five miles south of Lee Vining, on US 395, ...
</overview>
<photo>
<scaption>June Lake in the Fall</scaption>
<caption>June Lake and Carson Peak in the fall</caption>
<id>Juneinthefall-31-21</id>
<story>
Each time that unfortunate day arrives that I have to leave June Lake ...
</story>
<thumbnail></thumbnail>
<smallimg>imagessmall/junelakefall.jpg</smallimg>
<largeimg>imagespng/junelakeinthefall.png</largeimg>
<photoshop></photoshop>
<date></date>
<camera>Nikon F6</camera>
</photo>
<photo>
<scaption>Aspen by Silver Lake</scaption>
<caption>Aspen trees by Silver Lake</caption>
<id>silverlakeaspenfall98</id>
<story>In 1998, I hit the right week of the year for fall colors. I parked by Silver Lake ...
</story>
<smallimg>imagessmall/silverlakeaspenfall98.jpg</smallimg>
<largeimg>imagespng/silverlakeaspenfall98.png</largeimg>
<photoshop></photoshop>
<date></date>
<camera>Hasselblad</camera>
</photo>
<photo>
<scaption>Gull Lake in the Fall</scaption>
<caption>Gull Lake in the Fall - Happy fishermen !</caption>
<id>gullake-648</id>
<story>
If you take the north shore road around June Lake there is a turnoff, ...
</story>
<smallimg>imagessmall/gulllake648.jpg</smallimg>
<largeimg>imageslarge/gulllake648.jpg</largeimg>
<photoshop></photoshop>
<date></date>
<camera>Nikon D1</camera>
</photo>
<photo>
<scaption>Silver Lake</scaption>
<caption>Silver Lake - June Lake Loop</caption>
<id>silver2</id>
<story>
Any time of the year, there can be snow in June Lake. That year there was fresh snow ...
</story>
<smallimg>imagessmall/silver2.jpg</smallimg>
<largeimg>imagespng/silver2.png</largeimg>
<photoshop></photoshop>
<date></date>
<camera>Hasselblad</camera>
</photo>
</photocollection>
XML 架构文件
这是 XML 架构文件 photocollection.xs
,如果你对它的样子感兴趣的话:
<xs:schema >
<xs:element name="photocollection">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="title"/>
<xs:element type="xs:string" name="overview"/>
<xs:element name="photo" maxOccurs="unbounded"/>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="scaption"/>
<xs:element type="xs:string" name="caption"/>
<xs:element type="xs:string" name="id"/>
<xs:element type="xs:string" name="story"/>
<xs:element type="xs:string" name="thumbnail"/>
<xs:element type="xs:string" name="smallimg"/>
<xs:element type="xs:string" name="largeimg"/>
<xs:element type="xs:string" name="photoshop"/>
<xs:element type="xs:string" name="date"/>
<xs:element type="xs:string" name="camera"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
CSS 文件
这是 practical.css
,用于从 XML 文件生成的 HTML 的样式表:
@charset "utf-8";
body {
background-color:#FFDEAD;
margin-top:10px;
color: teal;
}
#overview h1
{
text-align:center;
}
div.simage img
{
border:3px white solid;
align:center;
}
#mysite
{
margin:auto;
width:980px;
}
div.smallmat {
width:300px;
float: left;
}
div.scaption {
padding:5px;
}
div.story {
width:500px;
float: left;
}
div.storybook {
width:850px;
float:left;
margin:20px;
}
PHP 文件
这是生成包含与 XML 中引用的照片数量一样多的照片的小图片画廊的 HTML 的 PHP 文件:
<!DOCTYPE html>
<html >
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>June Lake Gallery</title>
<link href="styles/practical.css" rel="stylesheet" type="text/css" media="screen"/>
</head>
<?php
$xmlfile = "practical.xml";
if (!file_exists($xmlfile)) {
exit('Failed to open practical.xml.');
}
$xml = simplexml_load_file($xmlfile);
?>
<body><div id="mysite"><div id="overview"><h1>
<?php
echo $xml->title;
?>
</h1><p>
<?php echo $xml->overview; ?>
</p></div><div id="gallery">
<?php
$photos = $xml->xpath("//photo");
$photocount = count($photos);
$count = 0;
while ($count < $photocount) {
$htmlstring = '<div class="storybook"><div class="smallmat"><div class="simage"><a href="';
$htmlstring .= $photos[$count]->largeimg;
$htmlstring .= '"><img class="sphoto" src="';
$htmlstring .= $photos[$count]->smallimg;
$htmlstring .= '" title="';
$htmlstring .= $photos[$count]->caption;
$htmlstring .= '"></img></a></div><div class="scaption">';
$htmlstring .= $photos[$count]->scaption;
$htmlstring .= '</div></div><div class="story">';
$htmlstring .= $photos[$count]->story;
$htmlstring .= '</div></div>';
echo $htmlstring;
$count++; }
?>
</div></div></body></html>
如其名所示,SimpleXML 使用简单。simplexml_load_file()
函数返回一个包含传递给参数的文件整个 XML 树的对象。还有一个配套的方法,称为 simplexml_load_string()
,它接受一个包含 XML 的字符串作为参数。这个树中的每个元素都可以使用简单的语法进行访问。
要访问所有照片节点集合,使用了 xpath() 方法。它支持 XPath 语法,这是一种用于 XML 文件的查询语言。你在这里需要记住的是 //photo
的含义。它返回所有照片节点,即使它们不在树的顶层。
使用 SimpleXML 创建 XML 文件
你也可以使用 SimpleXML 在程序内部创建 XML 文件。你从一个字符串形式的根元素开始,然后通过使用 addChild()
方法添加节点来构建你的 XML 文件,该方法接受一个或两个参数。第一个是节点的名称,可选的第二个参数是它的值。当你完成所有操作后,使用 asXML()
方法结束,它将返回包含所有 XML 的字符串,或者如果你提供了参数,它会将其存储到文件中。在下面的示例中,我们将其称为 xmlfiles/new.xml
。
为了总结 SimpleXML 这一部分,这里有一个小程序,它重新创建我们的 XML 文件,但只包含一个照片节点,并将其保存为 xmlfiles/new.xml
:
<?php
$xml = simplexml_load_string('<photocollection></photocollection>');
$xml->addChild("title", "June Lake");
$xml->addChild("overview", "The June Lake Loop begins just five miles south of Lee Vining, on US 395, ...");
$photo = $xml->addChild("photo");
$photo->addChild("scaption", "June Lake in the Fall");
$photo->addChild("caption", "June Lake and Carson Peak");
$photo->addChild("story", "Each time that unfortunate day ...");
$photo->addChild("smallimg", "imagessmall/junelakefall.jpg");
$photo->addChild("largeimg", "imagespng/junelakeinthefall.png");
$xml->asXML("xmlfiles/new.xml");
?>
在客户端生成我们的 HTML
当我们使用 XML 或 JSON 作为数据格式并使用 Ajax 调用时不一定需要始终在服务器上生成最终的 HTML 并将其发送到客户端。随着越来越多的强大设备被用来访问网络并在其中运行浏览器,生成 HTML 代码也可以在客户端完成。
我们可以使用 .post()
jQuery Ajax 调用来执行 PHP 程序,使用 SimpleXML 将整个 XML 文件作为字符串传递,然后进行处理。即使这部分也不是必需的,因为 jQuery Ajax 方法已经知道 XML 和 JSON 是什么。我们只需将 XML 文件的路径指定给 .ajax()
方法,jQuery 就会处理剩下的部分。这是一个非常适合使用 .get()
方法的例子,因为我们只需要从服务器检索数据,即我们的 XML,而不需要向其发送任何数据。
以下示例将生成完全相同的相册,但它全部是通过 Ajax 和 JavaScript 实现的,没有涉及任何 PHP 代码。HTML 文件如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>June Lake Gallery</title>
<link href="styles/practical.css" rel="stylesheet" type="text/css" media="screen"/>
</head>
<script src="img/jquery-1.10.1.js"></script>
<body>
<div id="mysite">
</div>
</body>
</html>
<script src="img/practical.js"></script>
JavaScript 文件如下:
$(document).ready (function () {
$.get( "practical.xml", function( xml ) {
var jQueryxml = $(xml); // Turn XML object into a jQuery object
var html = '<div id="overview"><h1>' + jQueryxml.find('title').text() + '</h1>';
html += '<p>' + jQueryxml.find('overview').text() + '</p></div>';
html += '<div id="gallery">';
jQueryxml.find('photo').each(function(){
html += '<div class="storybook"><div class="smallmat"><div class="simage"><a href="';
var scaption = $(this).find('scaption').text();
var caption = $(this).find('caption').text();
var largeimg = $(this).find('largeimg').text();
var smallimg = $(this).find('smallimg').text();
var story = $(this).find('story').text();
html += largeimg;
html += '"><img class="sphoto" src="';
html += smallimg;
html += '" title="';
html += caption;
html += '"></img></a></div><div class="scaption">';
html += scaption;
html += '</div></div><div class="story">';
html += story;
html += '</div></div>';
}); //close each()
html += '</div>';
$('#mysite').html(html);
}, "xml");
});
XSLT
稍等一下。JSON 就在眼前。只需再添加一种 XML 技术,就可以充实我们的工具库!
想象两家银行,它们像银行一样产生大量数据。两家银行都使用 XML,并且都制作了一个 XML Schema 文件,以确保 XML 文件的格式定义得非常严格。现在,想象一家银行收购了另一家,并继承了所有其他 XML 格式的数据:这正在酝酿一场灾难。不用担心,XSLT 来拯救!
可扩展样式表语言转换(XSLT)是一种可以将 XML 文件转换为另一个 XML 文件的编程语言。所需的是一个 XSLT 引擎和一个 XSL 文件,该文件描述了如何将一个文件转换成另一个文件。在转换过程中可以发生各种计算,因为它是一个完整的编程语言。我关于单一网络开发主题最厚的书是一本 XSLT 指南。
XSL 文件被称为样式表。它们不是 CSS 文件,但“样式表”这个术语一点也不错。如果我们将一个 XML 文件转换成 HTML 文件,那么我们实际上为 XML 文件创建了一个视图,这超出了仅使用 CSS 文件所能做到的。而不是为 california.xml
示例创建一个 XSL 文件,这里有一个将 practical.xml
文件转换为与 SimpleXML 示例完全相同的 HTML 的 XSL 样式表。如果你分析其内部内容,你可以将其分解为三个部分:哪些文件部分要应用规则(模板),规则是什么,以及所有这些规则中,纯 HTML。
这里是一个 XSLT 示例:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
>
<xsl:output method="html"/>
<xsl:template match="/">
<div id="overview">
<xsl:apply-templates select="/photocollection"/>
</div>
<div id="gallery">
<xsl:apply-templates select="/photocollection/photo"/>
</div>
</xsl:template>
<xsl:template match="photocollection">
<h1>
<xsl:value-of select="title"/>
</h1>
<p>
<xsl:value-of select="overview"/>
</p>
</xsl:template>
<xsl:template match="photo">
<div class="storybook">
<div class="smallmat">
<div class="simage">
<a>
<xsl:attribute name="href">
<xsl:value-of select="largeimg"/>
</xsl:attribute>
<img class="sphoto">
<xsl:attribute name="src">
<xsl:value-of select="smallimg"/>
</xsl:attribute>
<xsl:attribute name="title">
<xsl:value-of select="caption"/>
</xsl:attribute>
</img>
</a>
</div>
<div class="scaption">
<xsl:value-of select="scaption"/>
</div>
</div>
<div class="story">
<xsl:value-of select="story"/>
</div>
</div>
</xsl:template>
</xsl:stylesheet>
这是处理转换的 PHP 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>June Lake Gallery</title>
<link href="styles/practical.css" rel="stylesheet" type="text/css" media="screen"/>
</head>
<body>
<div id="mysite">
<?php
$xslt = new XSLTProcessor;
$xmlfile = "practical.xml";
$xslfile = "practical.xsl";
if (!file_exists($xmlfile)) {
exit('Failed to open practical.xml.');
}
$xml = new DOMdocument;
$xml->load($xmlfile);
$xsl = new DOMdocument;
$xsl->load($xslfile);
$xslt->importStyleSheet($xsl);
printf("%s",$xslt->transformToXML($xml));
?>
</div>
</body>
</html>
JSON
JavaScript 对象表示法(JSON)是本章讨论的另一种数据交换格式。像 XML 一样,它是基于文本的,可读性强,但它比其对应物更轻量。以 JSON 格式发送的数据在网络连接中传输时将占用更少的带宽。它在当今的 Web 应用程序中越来越受欢迎。XML 文件更重——只需注意我们提供了多少页的例子,并且需要某种类型的解析器来处理数据。
JSON 是从 JavaScript 衍生出来的,JSON 代码看起来很像 JavaScript 对象,但也有一些细微的差别。然而,JavaScript 可以用来处理 JSON 数据,所以你不需要一个单独的解析器,就像处理 XML 那样。这里是从 california.xml
示例中相同的资料,但以 JSON 格式呈现:
[
{
"name":"Adams",
"first":"Ansel",
"profession":"photographer",
"born":"San Francisco"
},
{
"name":"Muir",
"first":"John",
"profession":"naturalist",
"born":"Scotland"
},
{
"name":"Schwarzenegger",
"first":"Arnold",
"profession":"governator",
"born":"Germany"
},
{
"name":"Rowell",
"first":"Galen",
"profession":"photographer",
"born":"Oakland CA"
},
{
"name":"Wellens",
"first":"Paul",
"profession":"author",
"born":"Belgium"
}
]
首先要注意的是,与它的 XML 对应物相比,这要容易阅读得多。它看起来像是一个包含键:值对的 JavaScript 对象数组,这几乎就是它所代表的。在 第四章 中关于对象的讨论中,我们有一个包含 John Williams 名字的例子,以及对 JSON 格式的提示。关键的区别(这里的 key 是一个关键词)是,在 JSON 中,键也必须被双引号包围。所以,在 JSON 的领域中,name:"Williams"
是不正确的。让我们回顾一下 JSON 数据格式的简单结构。你可能想要拿一张纸,为每条规则画一个小图。
JSON 语法
JSON 数据是纯文本。JSON 文本由一系列字符组成,这些字符以 Unicode 编码,并符合 JSON 值语法。所以简单地说,每条 JSON 数据都必须是一个有效的 JSON 值。在 JSON 中有六个具有特殊意义的令牌:[
、{
、]
、}
、:
和 ,
。还有三个具有特殊意义的名称令牌:true
、false
和 null
。这些令牌用于组成正确的 JSON 值。
JSON 值
一个 JSON 值 可以是一个对象、数组、数字或字符串,或者是 true、false 或 null 之一。这些都是格式良好的 JSON 数据的构建块。
JSON 对象
一个 JSON 对象 以左花括号开始,以右花括号结束。在其内部,将有零个或多个由逗号分隔的键:值对。键或名称是一个 JSON 字符串。键和值之间有一个分号。值本身可以是任何有效的 JSON 值。
JSON 字符串
一个 JSON 字符串 以双引号开始和结束。不能使用单引号来界定字符串。然而,字符串中间可以有一个单引号。如果需要双引号,你必须用反斜杠(\
)转义它。其他两个字符的转义序列不可避免地是 \\
、\/
、\b
、\f
、\n
、\r
和 \t
。你也可以使用任何字符的 4 位 Unicode 码,只需在该字符前加上 \u
。
JSON 数组
JSON 的 数组 是一对方括号,包围着零个或多个值,这些值由逗号分隔。之前看到的例子实际上是一个包含四个 JSON 对象的 JSON 数组,每个对象又包含四个键值对。
JSON 数字
JSON 的 数字 总是十进制,没有前导零。前面可以有一个负号和一个作为浮点数的 .
。它可以有一个十的指数,前面有 e
或 E
。
JSON 和 PHP
如果你使用 PHP 在服务器端,例如,从 MySQL 数据库中检索数据,并且你想将数据转换为 JSON 格式,这可以很容易地完成。只需创建一个包含数据的 PHP 数组。有一个非常方便的函数 json_encode()
,它接受一个 PHP 数组并将其转换为包含 JSON 数据的字符串(注意我没有写 JSON 字符串)。还有一个函数执行相反的操作,将 JSON 数据转换为 PHP 数组:json_decode()
。
这里有一些示例 PHP 代码,它将 PHP 数组转换为包含与我们的 californiapeople
示例中相同的 JSON 数组的 PHP 字符串。如果这是从 jQuery Ajax 调用访问的 .php
文件的一部分,你可以在客户端将其作为 JSON 处理。只需确保指定 json
作为数据类型:
$californiapeople = array (
array
(
"name"=>"Adams",
"first"=>"Ansel",
"profession" => "photographer",
"born" => "San Francisco"),
array
(
"name"=>"Muir",
"first"=>"John",
"profession" => "naturalist",
"born" => "Scotland"),
array
(
"name"=>"Schwarzenegger",
"first"=>"Arnold",
"profession" => "governator",
"born" => "Germany"),
array
(
"name"=>"Wellens",
"first"=>"Paul",
"profession" => "author",
"born" => "Belgium")
);
echo json_encode($californiapeople);
使用 Ajax 和 jQuery 的 JSON
为了结束这一章,我们将重新创建我们之前用来演示如何在客户端动态生成 HTML 的照片库示例。这次,我们将使用 JSON 作为数据格式。让我们从我们的更简单、更精简的数据开始,这些数据将驻留在服务器上的 practical.json
文件中。请注意,它包含一个具有单个键值对的 JSON 对象,其值是另一个具有四个键值对的对象,其中最后一个是一个数组。
再次强调,这比它的 XML 对应物更容易阅读:
{
"photocollection": {
"title": "June Lake",
"overview": "The June Lake Loop begins ...",
"photo": [
{
"scaption": "June Lake in the Fall",
"caption": "June Lake and Carson Peak in the fall",
"story": "Each time that unfortunate day ...",
"smallimg": "imagessmall/junelakefall.jpg",
"largeimg": "imagespng/junelakeinthefall.png"
},
{
"scaption": "Aspen by Silver Lake",
"caption": "Aspen trees by Silver Lake",
"story": "In 1998, I hit the right week of the ...",
"smallimg": "imagessmall/silverlakeaspenfall98.jpg",
"largeimg": "imagespng/silverlakeaspenfall98.png"
},
{
"scaption": "Gull Lake in the Fall",
"caption": "Gull Lake in the Fall - Happy fishermen !",
"story": "If you take the north shore road around ...",
"smallimg": "imagessmall/gulllake648.jpg",
"largeimg": "imageslarge/gulllake648.jpg"
},
{
"scaption": "Silver Lake",
"caption": "Silver Lake - June Lake Loop",
"story": "Any time of the year, there ...",
"smallimg": "imagessmall/silver2.jpg",
"largeimg": "imagespng/silver2.png"
}
]
}
}
这个 HTML 文件与 XML 示例相同:我们只需要更改 JavaScript 文件中的内容。这里我们将使用一个方便的 jQuery 函数 .getJSON()
,它是 .ajax()
的另一个快捷方式。它返回一个 JSON 对象,我们可以用常规 JavaScript 进一步处理,从而使代码本身更简单:
$(document).ready (function () {
$.getJSON( "practical.json", function( json ) {
// For debugging:
//var jsonString = JSON.stringify(json)
//alert(jsonString);
var html = '<div id="overview"><h1>' + json.photocollection.title + '</h1>';
html += '<p>' + json.photocollection.overview + '</p></div>';
html += '<div id="gallery">';
// Use jQuery .each() function to loop through array
$(json.photocollection.photo).each(function()
{
html += '<div class="storybook"><div class="smallmat"><div class="simage"><a href="';
var scaption = this.scaption;
var caption = this.caption;
var largeimg = this.largeimg;
var smallimg = this.smallimg;
var story = this.story;
html += largeimg;
html += '"><img class="sphoto" src="';
html += smallimg;
html += '" title="';
html += caption;
html += '"></img></a></div><div class="scaption">';
html += scaption;
html += '</div></div><div class="story">';
html += story;
html += '</div></div>';
}
);
html += '</div>';
$('#mysite').html(html);
});
});
而不是使用 jQuery 的 .each()
函数,我们也可以使用普通的 JavaScript for
循环:
for (var i in json.photocollection.photo)
{
. . .
var scaption = json.photocollection.photo[i].scaption;
. . .
}
两个有用的 JSON 方法
总结来说,我们将提到两个属于 JSON 对象的 JavaScript 方法,并且大多数浏览器都支持这些方法。
将 JSON 字符串转换为 JavaScript 对象,你只需将 JSON 字符串传递给 JSON.parse()
方法,如下所示:
var jsObject = JSON.parse(jsonString);
JSON.stringify()
可以用来执行相反的操作,将 JavaScript 对象转换为 JSON 字符串。在我们的例子中,我们可以使用以下代码,这对于调试很有用:
var jsonString = JSON.stringify(json)
alert(jsonString);
摘要
在本章中,我们介绍了两种在网页开发中使用的数据格式。XML 在整个行业中用于交换人类和计算机都能读取的数据格式。我们可以将 XML 文件用作小型数据库,并且存在一些基于 XML 的技术来帮助我们,例如 XML Schema,它可以用来严格定义 XML 文件的结构,以及 XSLT,它可以创建样式表将 XML 文件转换为另一个 XML 文件,这可能是网页。使用 SimpleXML,我们还可以在 PHP 中处理和创建 XML 文件。
JSON 是当前网页开发者中流行的更简洁、更高效的格式。它所需的带宽比 XML 少,当数据量增加时这一点极为有益。由于 JSON 数据可以像 JavaScript 对象一样处理,所以在使用 JSON 时不需要额外的处理代码或引擎。
现在,如果我们的数据也能以 JSON 格式存储在服务器上,那么就无需进行这些从一种格式到另一种格式的转换,这不是很好吗?是的,这样很好。我们能这样做吗?是的,我们可以。只需翻到下一章。
第十一章。MongoDB
在本章中,我们将首先比较传统的关联数据库和所谓的NoSQL 数据库,并快速介绍MongoDB,一个文档数据库。你将了解它是什么,如何获取和安装它,以及如何创建数据库以插入文档。然后,你将学习如何使用 PHP 作为语言与之交互。
关系数据库管理系统
在第六章中,我们介绍了 MySQL 数据库以及如何使用 PHP 语言与之交互。这并不是真正与数据库对话的语言。我们使用 PHP 来编写查询字符串,这些字符串是另一种语言的代码行,这种语言是数据库的本地语言SQL。它用于查询数据库,从中提取数据,替换数据或插入数据。我们提到,SQL 是那些存在多年且预计还会存在很长时间的非常少数语言之一。它被用于许多不同的数据库,开源或商业的,小或大的,我们统称为关系数据库管理系统(RDBMS)。
关系数据库使用表格。列代表存储在其中的数据类型,行代表数据本身。你可以有一个包含客户数据的表格,包含如姓名、名、地址、邮编、城市、电话、账户号等字段。所有表格都包含索引,可以在其他表格中使用以显示它们之间的关系。索引本身的值永远不应该改变。因此,如果客户更改地址,地址需要在客户表中更改,并且只在该客户表中更改,就像在其他所有表中一样,客户将通过他的或她的customer_id
被引用。这是一件好事,但随着你的数据变得更加复杂,表格的数量也会增加。
我在使用这个系统的经验是,一旦需要记录包含两种相同类型的信息,比如当某人有两个地址时,你会觉得需要创建另一个表格。
访问数据以组成网页 HTML 的查询可能会变得相当复杂,一个接一个的内部连接(INNER JOIN)。一旦需要添加表格或向现有表格中添加列,你也必须更改你的代码。
在使用关系数据库管理系统(RDBMS)的项目中,这是一个常见的做法:首先定义数据库的表格,然后开始编写代码。表格的定义被放在一个模式或图中。因此,每次需要添加或更改表格时,你的模式也需要相应地更改。
总结来说,随着数据库的增长和数据的复杂化,可以创建大量的开销:添加表格或列、编写复杂的查询或不断更改数据库的模式。如果能够使用不同类型的数据库,其中每个记录都包含你需要的所有信息,而没有其他信息,那岂不是很好?是的,会很好。
NoSQL 数据库
近年来,出现了几种新的数据库技术,它们不是关系型数据库管理系统(RDBMS)。其中大多数,如果不是全部,都试图解决一个特定的问题(而不是想要成为适合所有人的通用解决方案)。它们被统称为 NoSQL 数据库。NoSQL 这一部分可以有两种解释:NO SQL,意味着没有可用的查询语言,或者Not Only SQL,意味着这项技术支持不仅仅是 SQL。NoSQL 数据库在 Web 开发中已经变得相当流行。
有几种类型。对我们感兴趣的 NoSQL 数据库组是所谓的文档数据库。存储在数据库中的是文档。在这个上下文中,文档不是,比如说,Word 或 PDF 文档,而是一个 JSON 对象。文档数据库提供了在文档的任何字段上进行查询的能力,因此它们无疑是 Not Only SQL 数据库。
我们在这里所做的是与 RDBMS 中发生的事情相反。每个记录或文档将包含我们想要的所有信息,没有其他信息。如果需要向一个以前从未使用过的文档中添加项目,我们只需添加即可。不需要更改模式或表。如果一个记录现在需要两种相同类型的东西,我们按照 JSON 的方式行事:我们使用数组而不是单个对象。
令人惊讶的是,这些数据库即使在拥有数千个文档的情况下也能表现出色,并且具有良好的扩展性。我们为项目以及本书所选用的 NoSQL 数据库是 MongoDB。它似乎拥有最多的功能,被最广泛采用——《纽约时报》也在使用它!——并且具有良好的扩展性和性能。
MongoDB
使用 MongoDB,我们只需用文档数据库替换 MySQL,这可能成为我们项目的更好解决方案,并且以 JSON 作为共同的基础。MongoDB 使用集合和文档。这使得它更适合,例如,在线书店应用程序。
由于 MongoDB 中的文档是一个 JSON 对象,因此从 PHP 中向其传输数据就像在 PHP 数组中存储数据一样简单。不再需要多个表和内部连接。我们将解释如何连接到 MongoDB,创建数据库,以及添加和更新文档。我们将从 PHP 程序以及 MongoDB shell 中这样做。
安装 MongoDB
获取和安装 MongoDB 取决于您所使用的平台。请访问www.mongodb.org以获取软件和文档。这将为您提供基本上两个程序。第一个是mongod(被称为MongoDB 守护进程),一个需要一直运行和开启的程序,就像 MySQL 服务器一样。第二个程序简单地称为mongo,它是MongoDB shell。它是一个命令解释器,允许您创建数据库、集合和文档,并对其进行修改。我喜欢将其视为 MongoDB 的phpMyAdmin。
如果你想在程序内部访问和操作你的 MongoDB 数据库,你还需要下载并安装一个适用于你选择编程语言的 MongoDB 驱动程序。所以,对于 PHP,我们需要一个 MongoDB 的 PHP 驱动程序。驱动程序可以从源代码构建,但也有一些来自不断变化的发行版的二进制文件。所以,使用你喜欢的搜索引擎,查找 MongoDB 的 PHP 驱动程序。在撰写本文时,php.net/manual/en/book.mongo.php
有一个。
MongoDB shell
一旦安装好所有这些工具,就是时候开始用文档填充数据库了。在 MySQL 中,我们首先创建所有表格,可能最初是在一张纸上,然后通过使用 phpMyAdmin 来实际操作。在 MongoDB 中,我们使用 MongoDB shell 来首次填充我们的数据库。然而,没有必要创建表格。我们只需添加文档。正如他们所说的法语:简单得就像你好。
为了做到这一点,我们在数据库、集合和文档之间做出区分。每个 MongoDB 实例可以包含一个或多个数据库。虽然你可以用单个数据库做所有事情,但建议在适当的时候使用单独的数据库,例如,每个项目一个数据库。
虽然你可以像那样将所有文档放入数据库,但最好将它们组织为几个集合。与 RDBMS 相比,文档相当于表行,而集合相当于表。
在本节的剩余部分,我们将向您展示如何使用 MongoDB shell 进行基本的创建、读取、更新和删除(CRUD)操作。对于更详细和高级的操作,请参考优秀的 MongoDB 文档,网址为mongodb.org,或者一些关于这个主题的书籍。
要启动 MongoDB shell,只需在命令行中输入mongo
。你会看到类似以下的内容:
MongoDB shell version: 2.6.6
connecting to: test
>
你现在正在与命令解释器对话,它基本上读取 JavaScript 代码。默认情况下,mongoDB 会连接到一个名为test
的数据库。>
符号是你的命令提示符。为了展示它的工作,让我们立即更改它:
> prompt="mongoDB "
mongoDB
创建数据库、集合和文档
我们已经将命令提示符更改为字符串 mongoDB。让我们连接到另一个数据库:
mongoDB use california
你现在已经切换到了名为california
的数据库。你认为我忘记了什么,但我告诉你我没有,我只是想保持悬念。现在,让我们向people
集合添加一个文档。只需输入:
mongoDB db.people.insert( {
"name":"Adams",
"first":"Ansel",
"profession":"photographer",
"born":"San Francisco"
});
让我们找出这里是否真的发生了什么。有一个方法可以找到集合中的所有文档,不出所料,它被称为find()
:
mongoDB db.people.find()
{ "_id" : ObjectId("54ad4336227ad31227c3902d"), "name" : "Adams", "first" : "Ansel", "profession" : "photographer", "born" : "San Francisco" }
哇,我们现在创建了一个名为california
的数据库,在其中,一个名为people
的集合,以及它里面的第一个文档。所有这些事情都发生了;如果数据库或集合尚未存在,MongoDB 将创建它们。与 MySQL 不同,在创建数据库或表之前不需要任何 SQL,也不需要指定数据类型。
如果你输入以下两个命令,你确实会看到它们现在存在:
mongoDB show dbs
...
california
...
mongoDB show collections
...
people
...
有一个find
的替代方法,它提供了一个更友好的输出,称为findOne()
:
mongoDB db.people.findOne()
{
"_id" : ObjectId("54ad4336227ad31227c3902d"),
"name" : "Adams",
"first" : "Ansel",
"profession" : "photographer",
"born" : "San Francisco"
}
当然,我们只找到一件事情,但随着文档数量的增加,我们希望更加具体,所以我们可以这样说:
mongoDB db.people.findOne({"first" : "Ansel"})
这看起来越来越像是一个查询,因为它就是!你可能注意到了一个名为"_id"
的关键字和一个非常长的值。让我们花几行来讨论这个问题。
_id
和 ObjectId
存储在 MongoDB 中的每个文档都必须有一个"_id"
键。它可以任何类型,但默认为ObjectId
。在一个集合中,每个文档都必须有一个唯一的"_id"
值,这确保了集合中的每个文档都可以唯一标识。ObjectId 使用 12 字节存储空间。如前所述,如果插入文档时没有"_id"
键,MongoDB 将自动将其添加到插入的文档中。
加载脚本
mongoDB shell 可以解析 JavaScript,并且还有内置函数。一些你习惯使用的东西可能不起作用,比如alert()
,因为它属于 Windows 对象的方法,而且只有在浏览器中运行时才可用。
load()
是一个非常有用的函数。你可以在事先准备所有命令并将它们存储在一个文件中。为你的项目创建一个文件夹,进入它,并将以下内容(与上一章中我们的一些 JSON 示例非常相似)存储在一个名为californiapeople.js
的文件中:
db.people.insert({
"name":"Adams",
"first":"Ansel",
"profession":"photographer",
"born":"San Francisco"
});
db.people.insert({ "name":"Muir",
"first":"John",
"profession":"naturalist",
"born":"Scotland"
});
db.people.insert( {
"name":"Schwarzenegger",
"first":"Arnold",
"profession":"governator",
"born":"Germany"
});
db.people.insert( {
"name":"Rowell",
"first":"Galen",
"profession":"photographer",
"born":"Oakland CA"
});
db.people.insert( {
"name":"Wellens",
"first":"Paul",
"profession":"author",
"born":"Belgium"
});
然后,输入以下命令:
mongoDB load("californiapeople.js")
这将向people
集合中插入五个更多文档。为了验证它,输入以下命令:
mongoDB db.people.find()
结果看起来可能像这样:
{ "_id" : ObjectId("54ad4336227ad31227c3902d"), "name" : "Adams", "first" : "Ansel", "profession" : "photographer", "born" : "San Francisco" }
{ "_id" : ObjectId("54ad46f6812ce43efb530a07"), "name" : "Adams", "first" : "Ansel", "profession" : "photographer", "born" : "San Francisco" }
{ "_id" : ObjectId("54ad46f6812ce43efb530a08"), "name" : "Muir", "first" : "John", "profession" : "naturalist", "born" : "Scotland" }
{ "_id" : ObjectId("54ad46f6812ce43efb530a09"), "name" : "Schwarzenegger", "first" : "Arnold", "profession" : "governator", "born" : "Germany" }
{ "_id" : ObjectId("54ad46f6812ce43efb530a0a"), "name" : "Rowell", "first" : "Galen", "profession" : "photographer", "born" : "Oakland CA" }
{ "_id" : ObjectId("54ad46f6812ce43efb530a0b"), "name" : "Wellens", "first" : "Paul", "profession" : "author", "born" : "Belgium" }
删除文档
注意现在有两个包含Ansel Adams
的文档,它们通过其"_id"
明显不同。但我们只需要一个,所以现在是时候删除一个了。这是通过remove()
函数完成的。当心,如果你在以下命令中不指定任何参数,集合中的所有文档都将被删除,一旦文档被删除,就无法恢复:
mongoDB db.people.remove( { "_id" : ObjectId("54ad46f6812ce43efb530a07" )} )
更新文档
我们最后的 CRUD 操作是更新。在这个例子中,我们向包含有关已故自然摄影师和攀岩家加伦·罗威尔信息的文档中添加一个键值对。在这种情况下,我们为字段"died"
设置了一个值。
db.people.update ({"name" : "Rowell"}, { "$set": {"died" : "Bishop"}})
$set
是一个修饰符的例子。它允许我们指定我们想要如何更改文档。在上一个例子中,如果该字段"died"
尚未存在,其值将被更新或创建。
其他一些修飾符包括$unset
,用於刪除字段,$inc
和$dec
用於增加和減少字段的值,以及$push
和$pull
用於向數組中添加元素或刪除它:
mongoDB db.people.findOne({"name" : "Rowell"})
{
"_id" : ObjectId("54ad46f6812ce43efb530a0a"),
"name" : "Rowell",
"first" : "Galen",
"profession" : "photographer",
"born" : "Oakland CA",
"died" : "Bishop"
}
MongoDB 數據類型
在 MongoDB 中,我們不需要事先指定數據類型。你只需使用它們,到現在為止,應該已經從我們的例子中認識到構成 JSON 對象的相同數據類型。然而,在 MongoDB 中我們有一些額外的功能。
基礎數據類型
我们可以使用与 JSON 相同的基礎數據類型:null
、true
、false
、string
、number
和array
。
日期
日期總是難以處理,不僅在生活中,而且在數據庫中也是如此。幸運的是,MongoDB 支持 JavaScript 的Date()
類,當使用日期時給我們一個數據類型。日期以自 epoch 以來經過的毫秒數存儲,不包含任何時區信息。當然,如果你喜歡,你可以將時區作為一個獨立的 key:value 對來存儲:
{ "today" : new Date() }
在 MySQL 中,上述代碼的等價操作是使用NOW()
。如果你在添加了"today"
的文檔上執行find()
,你將看到如下內容:
"today" : ISODate("2015-01-14T08:37:17.086Z")
嵌入式文檔
文檔中字段的值也可以是一個完整的文檔。我們稱這些為嵌入式文檔。
{ "key" : { "name":"Schwarzenegger","first":"Arnold",
"profession":"governator" } }
再舉一個例子
現在讓我們用一個例子來結束 MongoDB shell 這一節。讓我們從第十章的practical.json
文件中取我們的practical.json
文件,XML 和 JSON,以及它包含的 JSON 對象。只需將它們包圍在db.junelake.insert()
中,我們就可以將它轉換為 MongoDB 命令:
db.junelake.insert ({
"photocollection": {
"title": "June Lake",
"overview": "The June Lake Loop begins ...",
"photo": [
{
"scaption": "June Lake in the Fall",
"caption": "June Lake and Carson Peak in the fall",
"story": "Each time that unfortunate day ...",
"smallimg": "imagessmall/junelakefall.jpg",
"largeimg": "imagespng/junelakeinthefall.png"
},
{
"scaption": "Aspen by Silver Lake",
"caption": "Aspen trees by Silver Lake",
"story": "In 1998, I hit the right week of the ...",
"smallimg": "imagessmall/silverlakeaspenfall98.jpg",
"largeimg": "imagespng/silverlakeaspenfall98.png"
},
{
"scaption": "Gull Lake in the Fall",
"caption": "Gull Lake in the Fall - Happy fishermen !",
"story": "If you take the north shore road around ...",
"smallimg": "imagessmall/gulllake648.jpg",
"largeimg": "imageslarge/gulllake648.jpg"
},
{
"scaption": "Silver Lake",
"caption": "Silver Lake - June Lake Loop",
"story": "Any time of the year, there ...",
"smallimg": "imagessmall/silver2.jpg",
"largeimg": "imagespng/silver2.png"
}
]
}
});
一旦執行,無論是直接輸入還是先存儲在文件中,然後使用load()
函數,我們現在已經將一份文件插入到我們圖庫數據庫的junelake
集合中。我們甚至可以把它作為一個 JSON 對象來訪問並遍歷它:
var json = db.junelake.findOne()
print (json.photocollection.title)
June Lake
我們可以使用print()
函數來顯示這些值。我們甚至可以進行循環,就像我們在第十章中做的那樣,XML 和 JSON。這也是我選擇json
作為變量名稱的原因,在那裡我們有json
作為變量,它包含了 Ajax 調用返回的內容:
for (var i in json.photocollection.photo) {
print(json.photocollection.photo[i].scaption) }
This will produce the following output:
June Lake in the Fall
Aspen by Silver Lake
Gull Lake in the Fall
Silver Lake
因此,既然我們可以直接從 MongoDB 數據庫中獲取 JSON,我們現在可以使用完全相同的 JavaScript 代碼來生成我們照片相冊的 HTML 頁面,對吧?錯誤!我們的數據庫位於服務器上,也是我們執行 MongoDB shell 的服務器(即使它在我的開發機器上,它仍然具有服務器的角色)。
為了讓生成我們 HTML 的 JavaScript 代碼有作用,它必須在客戶端執行,並由一個不同的 JavaScript 解釋器來解釋——從這本書開始我們一直在使用的:瀏覽器。
用一句著名电影台词来概括:JSON,我们究竟如何才能把数据传到这里来? 好吧,这将会和之前一样发生。我们将使用 Ajax 调用在服务器上执行代码以从我们的数据库中提取数据,这次是一个 MongoDB 数据库,然后客户端处理这些数据以生成我们的 HTML。
到目前为止,我们知道我们可以在服务器端使用的唯一编程语言是 PHP,因此我们需要一种方法来使用 PHP 访问我们的 MongoDB 数据库。
MongoDB 和 PHP
我们将直接进入主题。我们在上一节通过将包含我们照片画廊页面所需数据的文档插入数据库来结束。然后我们发现我们无法立即使用它。然而,如果我们已经安装了 PHP 驱动程序并在php.ini
文件中指定了 mongo 扩展,我们就可以从服务器上的 PHP 程序访问我们的 MongoDB 数据库。它可以非常简短。
获取我们的画廊数据
这就是全部所需:
<?php
$connection = new Mongo();
$db = $connection->gallery;
$junelake = $db->junelake;
$gallery = $junelake->findone();
$jsongallery = json_encode($gallery);
echo $jsongallery;
?>
多亏了驱动程序,我们有了对Mongo()
类的访问权限。在第一行,我们连接到 MongoDB,在第二行,我们选择我们想要使用的数据库,在第三行,我们指定我们正在处理的集合。
接下来,我们使用findone()
方法,这是我们之前章节中提到的。由于 PHP 不知道 JSON 对象,这将返回其他内容:一个数组。现在,记住有一个 PHP 函数可以将数组转换为 JSON:json_encode()
。就在那里。只需echo
它,以便 Ajax 调用能够捕获它,并在客户端完成剩余的工作。这里还有其他部分。我没有在这里重复 CSS 文件。
这是 HTML 文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>June Lake Gallery</title>
<link href="styles/practical.css" rel="stylesheet" type="text/css" media="screen"/>
</head>
<script src="img/jquery-1.10.1.js"></script>
<body>
<div id="mysite">
</div>
</body>
</html>
<script src="img/practicalmongo.js"></script>
这是 JavaScript 文件:
$(document).ready (function () {
$.post( "mongogallery.php", function( json ) {
var html = '<div id="overview"><h1>' + json.photocollection.title + '</h1>';
html += '<p>' + json.photocollection.overview + '</p></div>';
html += '<div id="gallery">';
for (var i in json.photocollection.photo)
{
html += '<div class="storybook"><div class="smallmat"><div class="simage"><a href="';
var scaption = json.photocollection.photo[i].scaption;
var caption = json.photocollection.photo[i].caption;
var largeimg = json.photocollection.photo[i].largeimg;
var smallimg = json.photocollection.photo[i].smallimg;
var story = json.photocollection.photo[i].story;
html += largeimg;
html += '"><img class="sphoto" src="';
html += smallimg;
html += '" title="';
html += caption;
html += '"></img></a></div><div class="scaption">';
html += scaption;
html += '</div></div><div class="story">';
html += story;
html += '</div></div>';
}
html += '</div>';
$('#mysite').html(html);
}, "json");
});
我们使用了.post()
jQuery 方法在服务器上执行我们的 PHP 脚本并收集 JSON 数据。注意.post()
语句中的额外"json"
参数。这非常重要,因为它告诉 Ajax 调用预期数据将以 JSON 格式提供,并且不要尝试将其作为 HTML 处理。如果您忘记这部分,可能会发生奇怪的事情,或者更糟,什么也不发生。
使用 MongoDB 和 PHP 进行 CRUD 操作
我们用 PHP 和 MongoDB 执行一些基本 CRUD 操作的概述来结束这一章。
正如我们提到的,PHP 不支持 JSON 对象,所以我们必须使用数组。在之前的例子中,我们从数据库中获取数据,所以我们甚至不需要查看数组,我们只需将其转换为 JSON。唯一令人失望的部分可能是在处理包含 JSON 对象的文档数据库时,我们获取数据,它显示为数组,我们必须将其转换回 JSON。这看起来似乎不是很高效,但如果您已经熟悉 PHP,编写起来很容易。
我们将在本书的最后一章讨论一个 JSON(以及 JavaScript)的替代方案。
插入文档
让我们在 california
数据库的 people
集合中添加另一个著名的加利福尼亚人。那些熟悉威尼斯海滩的人都知道我在说什么。他是那个戴着头巾在滑板上上下滑行的人。30 年来他几乎没有变化,除了现在他卖 T 恤而不是卡带。
它与 MongoDB shell 中的 insert()
几乎相同,只是我们的 key:value 对现在是作为关联数组的一部分的 key=>value 对:
<?php
$connection = new Mongo();
$db = $connection->california;
$people = $db->people;
$californiadude = array(
'first' => 'Harry', 'name' => 'Perry', 'address' => 'Boardwalk', 'city' => 'Venice', 'state' => 'CA','zip' => '90291', 'song' => 'Invaders from another Planet'
);
$people->insert($californiadude);
?>
更新文档
让我们通过添加一个额外的键值对来更新我们的文档:
$people->update(array('name' => 'Perry'),
array('$set' => array('vehicle' => 'rollerblades')));
到目前为止,在我们的例子中,我们可以使用双引号而不是单引号,但对于 $set
修改符,我们只能使用单引号,因为,正如你所知,$
在 PHP 中有特殊含义——如果没有单引号,$set
就会被解释为一个 PHP 变量。
带条件的查询
就像我们在 shell 中做的那样,我们可以使用 findone()
函数来查询一个文档:
$result = $people->findone(array('name' => 'Perry'));
print_r($result); // in case we want to check our insert was succesful.
再次强调,我们使用数组而不是键值对。但我们可以使这些查询更加复杂。存在几个关键字可以用来细化我们的查询。这里有一个额外的例子:
$result = $people->findone(array('first' => 'John' , 'age' => array ( '$gt' => 50)));
// look for all people named John that are over 50\. This is just an example. Nothing will
// be returned.
findone()
只返回一个文档,并以关联数组的形式返回,就像我们最初的例子一样。如果我们使用 find()
,则会返回多个文档的数据。在 shell 中,我们得到了一个很好的小输出,默认为前 20 个文档,但在这里,使用 PHP,我们将得到一个称为 MongoDB 游标的东西。
MongoDB 游标对象
MongoDB 的 db.collection.find()
返回所有文档作为一个可迭代的对象,或者游标。在 PHP 中,find()
返回所有文档及其所有数据,这可能不是你想要的。但因为我们现在没有太多数据,这里有一种方法可以获取所有数据并在 PHP 中遍历它:
<?php
$connection = new Mongo();
$db = $connection->california;
$people = $db->people;
$cursor = $people->find();
foreach ($cursor as $document)
{
echo $document['name']; // we could do more of course
}
?>
在例子中,$cursor
是 MongoDB 游标类的对象。这个类有几个方法可用,为你提供了遍历游标的不同方式。如果你想将游标转换为数组,可以使用 iterator_to_array()
函数。
在例子中,我们使用 foreach()
循环遍历 $cursor
,这样我们就可以访问每个文档并将其作为数组来处理。我们做的是 echo
名称。
为了总结,我们将向你展示如何细化我们的查询。想象一下,你想执行相同的查询,在 SQL 中它看起来像:
SELECT first, last FROM people WHERE age > 50 LIMIT 10;
在 MongoDB PHP 术语中,这将是:
$cursor = // the variable that will contain the cursor we can iterate through
$people->find( // our collection, equivalent to a table
array('age' => array ('$gt '=> 50 ), // criteria for our query
array ('name' => 1, 'first' => 1, '_id' => 0 ))
// MongoDB calls this a projection: which fields we want
)->limit(10); // cursor modifier
到现在为止,这应该对你来说已经很直接了。游标修改符部分允许你限制或影响返回的数据,例如,有一个 排序 修改符。
projection
数组允许你指定你感兴趣的哪些字段。默认情况下,id
的值总是返回,所以如果你不需要它,请在你的投影数组中添加 _id => 0
。
概述
在本章中,你学习了 MongoDB,这是一种在 NoSQL 数据库中非常受欢迎的文档数据库。存储在数据库中的文档基本上就是 JSON 对象。它们可以被分组到集合中,这相当于 RDBMS 中的表。
使用 MongoDB shell,我们可以从命令行填充数据库和集合。在 Web 应用程序内部,我们可以使用 PHP 访问服务器上的 MongoDB 数据库。这真的很简单,因为 JSON 对象可以写成关联数组。
在最后几章中,有很多示例代码,我承诺你会有一本教科书,这样你就可以在电脑外学习。在下一章,将有更多的阅读内容。
当前 Web 开发的新趋势正在被讨论,这是由于人们使用 Web 的方式与几年前完全不同所引发的。我们将更多地关注你的网站外观,而较少关注其工作方式。
第十二章. 移动优先,渐进增强的响应式设计
这无疑是标题最长的章节。在这一章的前一部分,还有很多文字,正如你将发现那里几乎没有代码示例。在这里,我们讨论了最新的 Web 开发趋势,是什么导致了它,以及它取代了什么。
响应式设计
视口,或者说浏览器所在的屏幕部分,有各种大小。有些非常小,比如你智能手机的屏幕,有些可以非常大。当我看到我在一个比我习惯工作的屏幕大得多的屏幕上查看我的旧网站时,它看起来如此丑陋,以至于我决定完全重新设计它,使用响应式设计。
今天,使用指定像素固定宽度的网页设计真的不可行,同样,为了适应所有这些尺寸而制作你网站的多个版本也是不可行的。
在响应式设计——我总是想称之为负责任的设计——中,你不会从一个宽度为 960 像素的画布开始,然后那样构建一个网站,将其切割成固定大小的<div>
块。一旦视口小于 960 像素,网站的一部分将不可见,也许更糟的是,当屏幕真的很大时,你 960 像素宽的矩形周围的一切都会显得无聊和空旷。这在今天根本不是一种做法。
一个好的设计应该能够调整、响应屏幕尺寸,并且始终保持美观。要有一个好的响应式设计,你需要:
-
使用灵活的网格:当有空间时,让您的构建块并排放置,当没有空间时,将它们堆叠在一起,并按比例调整大小以实现这一点
-
在定义这些尺寸时,在 CSS 中使用百分比而不是像素
-
使用媒体查询来指定不同屏幕尺寸、分辨率、方向等的不同属性
-
使用灵活的图片和字体
这看起来像是一项大量的工作。好消息是,其他人已经为你做了这些艰苦的工作。有几个 CSS/JavaScript 框架可供选择,它们都具备我们刚刚列出的所有功能。你只需添加定制的媒体查询即可。最难的部分可能是选择哪个框架,因为有几个非常好的框架,特别是Bootstrap和Foundation。
我们选择了 Foundation,这将是下一章的主题。这个选择是一个口味或偏好的问题。关于口味,无需争论!
前世今生
当我写这篇文章时,我有一种闪回般的体验。在我软件行业的第一份工作中,我负责将一个软件包,TEN/PLUS,移植到几种不同的 UNIX 版本。其主要组件是一个全屏编辑器。
那时的全屏是一个所谓的哑终端,有 24 行,每行 80 个字符。然后出现了X 窗口系统和图形模式的显示器,有图标和许多实用程序,支持鼠标等。这些实用程序之一是xterm,一个模拟哑终端的 X 客户端。当然,这意味着您可以在其中运行全屏编辑器。但是您可以调整 xterm 窗口的大小,使其有更多的行或更长的行。
因此,我不得不修改我们的软件以适应那个尺寸,并且仍然在 xterm 内部作为一个全屏编辑器,即使它是 64 行乘以 120 个字符。这在 25 年前就是响应式设计!而在 2015 年,人们仍然在终端窗口中使用 vi、emacs、nano 等编辑器。
媒体查询
关键字media自 HTML 和 CSS 一开始就被使用,但它基本上仅限于在指定 CSS 文件的<link>
标签中使用media="print"
或media="screen"
,或者在使用 CSS 文件本身的@media
screen。
从 CSS3 和 HTML5 开始,我们可以以更复杂的方式使用媒体查询,以便在满足某些条件时应用特定的样式。尽管media
属性仍然可以在<link>
标签中使用,但我们建议您在 CSS 文件中使用它们。媒体查询对于响应式设计至关重要,尽管我们承诺您几乎不需要工作,因为有了可用的框架,但了解如何编写或阅读它们是至关重要的。
这里是一个典型的媒体查询:
@media only screen and (orientation: portrait) and (min-width: 480px) and (max-width: 690px) { /* your rules here */ }
在大括号之间,您将编写适用于宽度在 480 到 690 像素之间且设备处于纵向模式的视口的样式。之前的一切都将适用。在大括号之间的一切将覆盖之前的内容。
这里是一些在媒体查询中可以使用的最常见的关键字:
-
宽度:显示区域的宽度
-
高度:显示区域的高度
-
device-width:设备的宽度
-
device-height:设备的宽度
-
方向:设备的方向(纵向或横向)
-
分辨率:像素密度,以 dpi 或 dpcm 表示
除了当然的方向之外,所有这些都可以由最小值或最大值来修饰。
以单词only
开头开始媒体查询是一种处理不支持这些较新媒体查询的浏览器的便捷方法。它将被静默忽略。
宽度和高度之间的区别以及以设备为前缀的等效值应该容易理解:宽度和高度指的是浏览器视口的尺寸,而 device-width 和 device-height 指的是显示器的尺寸。并不是每个人都使用全屏浏览器,所以宽度和高度是您需要使用的测量值。但是有一个大问题。
移动浏览器填充可用屏幕,所以你可能期望宽度和设备宽度相同。不幸的是,情况并不总是如此。大多数智能手机将宽度设置为大约 1,000 像素的标称值(对于 iPhone,是 980 像素)。你看到过几个展示在小型手机上显示整个《纽约时报》全页的手机广告。那就是原因!
随着今天出色的视网膜显示屏,你甚至能够以这种方式阅读报纸。但如果你在为各种设备正确设置媒体查询上付出了很多努力,并且有一个针对max-width:480px
的查询,那么你的美丽响应式设计将不会显示在那个手机上。幸运的是,有一种补救方法。只需将以下行放入你页面的<head>
部分:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
现在,我们真正走上了响应式设计的道路。通过使用媒体查询,我们可以创建一个页面,该页面具有单一的设计,在大屏幕上显示页面上的元素(例如<div>
)并排显示,在平板电脑的纵向模式下堆叠显示,在手机上显示更少的元素。
使用媒体属性
你可能已经看到一些网站在<head>
部分包含这样的行:
<link rel="stylesheet" type="text/css" href="print.css" media="print">
它使用media
属性来指示是否需要使用此样式表。在 HTML4 中,这基本上是screen
或print
。在 HTML5 中,媒体属性可以有更多的值。这些值可以与我们在@media
后放置的内容相同,仅限于我们的示例。因此,如果我们想的话,我们可以将 CSS 代码组织成单独的样式表,每个媒体类型一个,并按需使用它们,如下例所示:
<link rel="stylesheet" type="text/css" href="small.css" media="screen and (max-width:480px)">
以少胜多
这个术语可能让经历过金融危机前后时期的人回忆起不好的记忆,但那不是我们要讨论的。
现在我们已经介绍了媒体查询,我们面临的风险是不得不写更多的 CSS,为每个我们决定使用的媒体查询编写一组规则。假设我们将其分为三种 T 恤尺寸:小号、中号、大号。再加上每种尺寸的纵向和横向版本:你现在有六种。
想象一下,营销部门有人决定更改公司颜色:你现在必须对你的样式表进行六处更改。
这就是 CSS 扩展(如less,SASS是另一个)非常有用的地方。其一个特性是使用变量。创建一个.less
文件来保存你的样式表信息。使用变量来保存颜色信息:
@mycolor: #FFDEAD;
在你的媒体查询中,无论何时需要指定此颜色,请使用:
color: @mycolor;
假设你想将其改为teal
。只需将那一行替换为:
@mycolor: teal;
哈哈!你已经在这六个地方更改了颜色。这不是很好吗?
Less 具有更多功能,如混入(mixins)、嵌套规则、媒体查询等,这使得样式表结构更清晰且易于维护。所有这些都在lesscss.org上描述得非常清楚。网站的名字立即解释了为什么他们称之为 less:你将最终写出更少的 CSS。只需看看这两个例子。以下是一个less
代码示例:
@mycolor: #FFDEAD;
@mygreen: teal;
#content {
background-color: @mycolor;
.greenp {
color: @mygreen;
font-size:12px;
font-family:Avenir, sans-serif;
}
.thumb {
border: 2px solid @mygreen;
width:150px;
}
}
这将被转换为以下 CSS。如果你真的开始嵌套,这可以变得更多:
#content {
background-color: #FFDEAD;
}
#content .greenp {
color: teal;
font-size:12px;
font-family:Avenir, sans-serif;
}
#content .thumb {
border: 2px solid teal;
width:150px;
}
让我们再举一个使用媒体查询的例子。我们可以这样写:
@medium: 16px;
@portraitfont: "Avenir, sans-serif";
.container {
@media screen {
background-color:@mycolor;
@media (min-width:786px) {
font-size:@medium;
@media (orientation:landscape) {
margin:auto;
}
@media (orientation:portratit) {
font-family:@portraitfont;
}
}
}
}
这将被转换为以下 CSS。你不认为less
版本更简洁吗?
@media screen {
.container {
background-color:#FFDEAD;
}
}
@media screen and (min-width:786px) {
.container {
font-size:16px;
}
}
@media screen and (min-width:786px) and (orientation:landscape) {
.container {
margin:auto;
}
}
@media screen and (min-width:786px) and (orientation:portrait) {
.container {
font-family:Avenir, Verdana, sans-serif;
}
}
现在我们如何将 less 文件转换为 CSS 内容?有两种方法可以做到这一点。当我们达到生产阶段时,我们希望使用编译器将我们的 less 文件生成 CSS 文件。在第十四章中,你将学习关于 Node.js 和 node 模块的内容。有一个用于 less 的node
模块。
目前,当我们还在开发和实验 less 时,请从lesscss.org网站下载less.js
。
在你页面的<head>
部分,使用link
标签列出你的less
文件:
<link rel="stylesheet/less" type="text/css"
href="less/mystyles.less"></link>
我喜欢将我的less
文件保存在一个less
文件夹中。例如,在你的页面上的正确位置,比如在关闭</body>
标签之前,添加以下内容:
<script src="img/less.js"></script>
现在每次页面加载时,你的less
文件都会即时转换为 CSS。请注意,当你编辑less
文件时,并非所有编辑器都会提供与编辑 CSS 文件时相同的出色颜色编码和格式化。我使用 Textastic 来编辑我的less
文件。用州长的话来说:你必须看看这个编辑器:它太棒了!
首先考虑移动端
现在我们已经介绍了如何使我们的设计响应式,并使我们的页面在各种尺寸的屏幕上看起来都很好,你可能会认为我们已经涵盖了移动设备。不,还没有。
支持移动设备不仅仅是关于屏幕尺寸。你可能见过我们提到的那个广告,一个家伙正在他的 iPhone 上阅读《纽约时报》,整版。这就是移动设备在没有告诉它们不要这样做的情况下,在高分辨率显示器上渲染网站的方式。在我们继续这个话题之前,我想强调,这次讨论是关于如何设计由移动浏览器解释的Web 应用程序,而不是原生的 iOS 或 Android 应用程序。
为什么首先考虑移动端?
让我们用一个问题来回答:为什么不呢?或者让我们来回答它:因为移动端确实是首先考虑的。智能手机和平板电脑的出货量已经比 PC 或电视多 4-5 倍,现在已经有更多用户使用移动连接访问网络,而不是固定互联网接入。这就是为什么在考虑移动用户访问我们的网站时,首先考虑他们的体验,而不是首先创建一个固定画布版本的网站,然后再对移动用户进行修改,这是一个(不那么)优雅降级的例子。
如果你营销人员又带来设计公司提供的另一个静态设计,你可能没有选择。但营销人员喜欢数字,所以告诉他们检查数字,你可能会很快找到一个志同道合的人。
我们已经走了很长的路
在 2007 年,不是很久以前,我正在开发一个带有我的照片的简单网站。我的一个朋友在智能手机上检查它。看到这个后,我不得不自己买一个,不是因为它有多好,而是因为我需要它来测试我的网站;至少,那是我的借口。尽管我实际上负担不起,但我买了最新的最棒的诺基亚,与今天的 iPhone 相比就像一块砖头,你可以翻开来给你一个更大的屏幕和真正的键盘。但它的网络接入非常糟糕。启动浏览器似乎需要很长时间。小键盘还可以,但与我现在使用坚固的蓝牙键盘在 iPhone 或 iPad 上的体验相比,那完全不同。事实上,这本书的大部分内容都是在火车上坐着用 iPad 写的。
在我之前回到比利时的一年里,我多次往返,因为我的父母家里没有互联网,所以我去了所谓的互联网热点来检查我的电子邮件。每小时 10 欧元,你可以买一张刮刮卡,上面有一个你需要与你的手机号码一起在本地运营商网站上使用的访问代码;然后他们通过短信给你发送密码。这个整个过程有时需要 20 分钟,并且因为我的手机号码是美国的,所以我需要支付国际漫游费用。现在我还有 40 分钟的 Wi-Fi 时间。移动互联网既慢又贵。
现在,这些障碍大多已经消失,带宽提高了,大多数地方的 Wi-Fi 都是免费的,或者你可以安装 SIM 卡。
为了进一步阐述“我们已经走了很长的路”这个说法,让我分享一个小故事。我是东部内华达山脉的大粉丝,已经去过那里很多次,大多数时候都是通过优胜美地国家公园。我知道一条通往公园西入口的不错的替代道路,它穿过科尔特维尔的金矿镇。
当地的杰弗里酒店入口门上方有两个招牌。一个写着:“马格诺利亚酒吧 - 创立于 1851 年 - 加利福尼亚最古老的营业酒吧”。上面的一个写着:“免费 WiFi”。
移动设备有更多的新功能
上文描述的情况——除了酒吧——确实已经发生了转变。移动设备性能良好,互联网接入价格合理,大多数使用的设备都可以访问万维网,所以我们最好确保人们可以用它们访问我们的网站或应用程序。
我不习惯使用很多数字,到目前为止我只用了几个,但在最近的一项调查中,我了解到,在手机用户中,84%的人在在家时使用,63%的人在办公室使用,42%的人在移动中使用。
在移动设备上,没有鼠标点击,也没有在手机或平板电脑上悬停,但存在滑动。有许多新的界面,通常与硬件和 iOS 相关,你不会在桌面上找到这些界面。在手机上选择日期的界面与使用 jQuery UI 日期选择器不同。
如果你阅读了苹果网站上最新最伟大的 iPhone 的规格,我敢打赌他们忘记提到了这个产品的一个重要特性,至少不是以一种非常明显和明显的方式:你可以用它打电话。所以如果你的网站上的联系方式包含电话号码,让用户只需点击那个号码就能打电话。这就像给它加上一个<a>
标签一样简单:
<a href="tel:6505555555">6505555555</a>
这可能是一个微不足道的例子,但它说明了以移动为先的思考方式。手机将完成剩下的工作。所以确保当应用程序在手机上使用这些新功能之一时,它可以用。
移动设备不仅在路上使用
忘记屏幕尺寸,考虑内容。这不仅仅是关于小与大的问题,而是关于移动与本地,关于在路上与在家的问题。当有人旅行并寻找酒店时,他期望在用手机查看酒店网站时能快速找到联系方式,而不是房间的照片和室内外游泳池的照片。但他在坐在家里的沙发上时,可能已经用同样的手机做了预订,因为孩子们正在使用电脑或 iPad。所以那天他对那些照片感兴趣。
因此,如果他正在寻找酒店,如果我们支持的话,可以在他的智能手机上显示一个类似于 GPS 的地图,显示他的位置以及他的酒店位置。
我们不希望做的事情是通过强制在带宽差且昂贵的地区下载 900x600 的 JPG 文件,从而使他的手机运营商变得富有,因为有人决定在任何时候在主页上放置一个照片横幅。
内容优先,导航其次
之前的评论归结为以下几点。在我们的思考中,我们应该把最重要的内容放在最接近的地方。在页面的最右边或更糟糕的是页面的底部有一个带有联系的横向菜单是不会帮助我们的旅行者的。
在一个最近的项目中,我不得不处理一个为桌面屏幕设计的、包含水平导航的设计公司的线框图,我查看所有导航项并重新排列了它们,首先是联系,用响应式图像替换了照片横幅,并用带有下面三条横线的menu
这个词替换了那个菜单。
我把大部分代码放在了max-width:480px
媒体查询中。在menu
上简单地按一下标签,就会在小屏幕上向访客展示包含所有相关主题的菜单。
我这样做的方式是,菜单看起来像是从你手机的左侧某个地方出现的,类似于 Facebook 应用的做法。我使用了Foundation来实现这一点。你将在下一章了解 Foundation。
小意味着大
你可能已经注意到,在许多网站上,甚至是非常好的网站上,当你用智能手机访问它们时,你会被切换到网站的移动版本,一个不同的 URL 如m.site.com
。这不是我们推荐的做法。一旦你决定采用两个版本,那么阻止你创建第三个版本,以此类推的是什么?许多坚持单一网站的网页设计师会对较小的屏幕尺寸做出反应,将所有东西都缩小,以便仍然适应屏幕。
在许多情况下,你可能想要做相反的事情,因为这会变得难以阅读。视网膜屏幕等设备现在非常清晰,这些屏幕上的小字体实际上很容易阅读。你可以通过在媒体查询中处理视网膜或非视网膜问题来实现这一点。
另一方面,这是一个指出,在这数百万通过手机访问互联网的新人中,许多是年长的一代,他们可能会欣赏更大的字体大小,因为他们的视力已经不如以前了。
真正的问题不是文本,而是你没有鼠标像素级别的精确度。当你想要点击某个东西时,你必须用手指。如果这些区域也缩小了,点击错误东西的风险会大大增加。
移动输入
我们提到了阅读,提到了点击,那么填写网页表单呢?许多老式的网站——例如,包含注册表单的网店——有许多<input>
字段,并且设计成可以适应整个屏幕,在最后有一个单独的提交按钮。所以,在手机上这会变得很困难;因此你可能想要考虑将这些表单拆分。如果输入字段不够大,用手指点击可能会让用户点到错误的位置。所以你希望让这些比在桌面上的更大。但关于输入还有另一件事经常被忘记:键盘。
在智能手机或平板电脑上,没有物理键盘。当网络应用程序/浏览器检测到需要输入时,一个软键盘会作为屏幕的一部分显示出来。在竖屏模式的智能手机上,这会让打字变得很困难。
当需要输入数字时,你需要首先将键盘切换到不同的模式,然后为一些符号切换到另一个模式。并不是每个人都会像我自己一样,到处都带着蓝牙键盘。对于这部分,你可以使用 HTML5 中的新<input>
类型。大多数移动浏览器都支持 HTML5,所以当你使用以下内容时:
<input type="email" name="email"></input>
这些浏览器会提供一个至少包含@
符号的键盘布局,在某些键盘上这个符号很难找到。我再次生活在azerty国家。我知道。
移动优先回顾
你可能已经明白了。在这个拥有数百万移动设备的全新世界中,我们需要一种新的思维方式来考虑我们在网站上提供的信息以及如何提供这些信息。在可能的情况下,使用手机的功能;将内容放在导航之前。将你的网站设计成一个网站适合所有人,而不是一个尺寸适合所有人。这不仅仅是一种技术,更是一种哲学。
但我们需要一种技术来确定浏览器/设备组合是否支持某些功能,例如地理位置,甚至是否可以解释 JavaScript,并且只有在存在时才使用它。当我们这样做时,我们正在实践渐进增强。
渐进增强
几年前,一位同事曾告诉我们的项目经理,他 80%的时间都花在修改已经完成的代码上,使其能在旧浏览器中运行。在那些日子里,很多人花了很多时间在 Internet Explorer 上制作圆角,这是一个代价高昂(从开发时间和性能的角度来看)的尝试,目的是让一切看起来在所有地方都完全相同。很多人决定推迟适应 CSS3 和 HTML5 的新功能,因为一些浏览器不支持它们。这只是为了支持少数几位数的浏览器,并确保在关闭 JavaScript 时(优雅降级)网站仍然可用。
今天,有坏消息也有好消息。坏消息是浏览器比你能想象的要多,很多浏览器/设备对支持 API,而其他浏览器不支持。好消息是,已经出现了几种技术来更主动或更进步地解决这个问题。
那么,我们如何处理这个问题,同时还能提供一个网站的单一版本?首先,我们需要另一个哲学上的改变:不要害怕使用可用的功能,让你的网站和应用程序更加酷和有趣,并使用这些功能来增强它们。我们如何处理不支持这些功能的浏览器或设备?
我们提出一个两阶段的过程。首先,如果几乎没有任何现代功能(例如 JavaScript、花哨的 CSS 功能和媒体查询)可用,我们将确定我们网站的最小内容是什么,并据此开始编写我们的页面。这是我们基本网站。接下来,我们将添加诸如 jQuery 和 JavaScript 代码、媒体查询、更新的 CSS 和 HTML 功能、动画等内容,使我们的网站更美观,增强它。这被称为渐进增强。
再次强调,有 jQuery 和另一个名为EnhanceJS的库来帮助我们做到这一点。我们将展示如何做到这一点。然后,我们将展示如何改进这项技术:测试特定功能,如果可用则使用它,并可能使用替换库或 polyfill,将功能添加到不支持这些功能的浏览器中。我们已经在之前的章节中使用过这样的 polyfill——jQuery 的历史插件。
EnhanceJS
EnhanceJS 将对浏览器进行一系列全面的测试。如果它们都通过,我们将加载构成我们网站增强版的文件,如 jQuery、带有媒体查询的 CSS 文件等。如果没有通过,我们看到的网站就是我们的基本网站。
请注意,如果我们想使用 Ajax 动态地将 HTML 注入到我们页面的部分,我们无法在基本版本中这样做。根据我们决定要放置多少内容,可能又回到了“回到未来”,因为我们将不得不提供指向静态页面的链接。这样,所有使用旧设备的访客至少会看到一个具有合理内容的可功能性网站。
看看这个例子:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8" />
<title>Progressive Hello World</title>
<link href="css/basic.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="img/enhance.js"></script>
<script type="text/javascript">
// Run capabilities test
enhance({
loadScripts: [
'js/jquery.min.js',
'js/enhanced.js'
],
loadStyles: ['css/enhanced.css']
});
</script>
</head>
<body>
<div id="content"><h1>Hello, world</h1></div>
</body>
</html>
enhance.js
enhance.js 是一个 JavaScript 库,它执行一系列全面的测试,以查看浏览器是否支持足够的功能来处理你为增强网站所设想的功能。在撰写本文时,你可以在code.google.com/p/enhancejs
找到它。你还可以在那里找到文档和有用的链接。
如果测试失败,enhance()
内部将不会发生任何事情,你的基本网站,在我们的例子中是老式的Hello World,将是访客看到的。如果测试成功,enhance.js 将向<html>
标签添加类enhanced
并尝试执行你放在里面的任务。
loadStyles 和 loadScripts
loadStyles 和 loadScripts 是两个数组,你可以指定你想要加载的样式表和 JavaScript 文件。你也可以指定条件,例如媒体查询,以有条件地加载一个文件或另一个文件。你不需要仅仅在数组中放置文件的路径名,你可以使用 JavaScript 对象,使用属性名作为键。所以在我们第一个例子中,我们可以这样写:
enhance({
loadScripts: [
{ src: 'js/jquery.min.js'},
{ src: 'js/enhanced.js' }
],
loadStyles: [
{ href: 'css/enhanced.css' }
]
});
这里有一个更详细的例子:
enhance({
loadStyles: [
{media: 'screen', href: 'js/mediumlarge.css',
excludemedia: 'screen and (max-device-width: 480px)',},
{media: 'screen and (max-device-width: 480px)', href: 'js/small.css'}
],
loadScripts: [
{media: 'screen', src: 'js/mediumlarge.js',
excludemedia: 'screen and (max-device-width: 480px)'},
{media: 'screen and (max-device-width: 480px)', src: 'js/small.js'}
]
});
enhanced 和 FOUC
存在一个称为无样式内容闪烁(FOUC)的常见问题——请注意如何拼写这个缩写。这是在页面加载期间,由于你的 HTML 尚未被 JavaScript 代码处理而临时显示时,你会在屏幕上看到一些闪烁的现象。这里有一个机会,基于以下知识来解决这个问题:如果 enhance.js 测试成功,它将向<html>
标签添加类enhanced
。
在我们的例子中,你可以分别向你的enhanced.css和enhanced.js添加以下内容:
#content {
html.enhanced display:none;
}
$('#content').show(); // this of course at the end of the processing
上述语句几乎意味着,在增强版的网站上,我们在 enhanced.js 中编写的代码将导致 HTML 被注入到#content
div 内部。另一种方法是拥有#basiccontent
div 和#enhancedcontent
div,并只显示适当的一个。
如此描述的 Enhance.js 给我们提供了一个全有或全无的方法。它运行一系列全面的测试来确定访客应该看到基本版本还是增强版本。它们在文档中描述,当然也在 enhance.js 的代码中。在撰写本文时,enhance.js 自 2010 年以来没有更新。这并没有什么问题:它做的工作没有改变,而且做得很好。
这些测试中没有一项检查对一些较新的 HTML5 和 CSS3 功能的支持,例如 Canvas。Enhance.js 提供了更多的可配置选项,允许你添加自己的测试,并且如果你愿意,可以多次运行 enhance()
来测试不同的条件。所以你可以这样做。
注意
实际上没有必要,因为在网络开发的世界里,通常已经有整个团队为我们做了这件事。
Modernizr
Modernizr.js 与 Enhance.js 类似,它是一个用于测试浏览器功能的 JavaScript 库。但它有更多的测试,可以自定义,并且可以在比 Enhance.js 更细粒度的层面上使用。Modernizr 测试单个功能。根据测试是否成功(是)或失败(否),我们可以加载不同的样式表和 .js
脚本。
你甚至可以将这两个一起使用。首先使用 enhance.js 来确定浏览器是否支持 JavaScript。如果支持,可以与 jQuery 一起加载 modernizr.js,并在你自己的 JavaScript 代码中细化你想要做的事情。你的基本页面可能看起来像这样:
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset=utf-8" />
<title>My first progressive enhancement site</title>
<link href="css/basic.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="img/enhance.js"></script>
<script type="text/javascript">
enhance({
loadScripts: [
'js/jquery.js',
'js/modernizr.js',
'js/enhanced.js'
],
loadStyles: ['css/enhanced.css']
});
</script>
</head>
<body>
<div id="basiccontent"><h1>Welcome to Polbol Productions</h1><p>
Thank you for visiting us. Apparently you cannot use your browser to access all the
cool goodies on our site. Click <a href="basic.html">here</a> for more information on our
company.<br/> The Polbol Productions Team</p></div>
<div id="enhancedcontent"> <!-- Your real home page here --> </div>
</body>
</html>
因此,我们对其进行了小小的修改。我们当然将 modernizr.js
库添加到我们的 JavaScript 系列中,但一个显著的小变化是 <html>
标签的 class="no-js"
属性。而 enhance.js 会给这个标签添加 enhanced
类,Modernizr 在运行时会替换这个类为 js
。所以如果它没有这样做,因为没有 JavaScript,你可以在你的样式表中使用 .no-js
类来考虑这一点。
Modernizr 将为 <html>
标签添加很多类,几乎每个测试通过都会有一个。所以你可以在你的样式表中容纳它们。以我在 MacBook Pro 上使用 Firefox 作为浏览器的例子,我使用 Firebug 检查并注意到 modernizr.js 已经将 no-js
替换为 js
,并添加了以下类到 <html>
标签中:
js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmessage no-websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients no-cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths
Modernizr 对象
一旦加载了 modernizr.js 并执行了所有测试,你就可以访问 Modernizr
对象,并在你的 JavaScript 代码中检查测试是否通过:
if (Modernizr.history) {
// code to use the HTML5 history API
}
else {
// code to use the API of your history plugin or polyfill
}
Polyfills 和 Modernizr
Polyfills是当浏览器缺少您需要的功能时,负责处理这些功能的脚本。我们在第九章中详细讨论的history.js
库,历史 API—不忘记我们身在何处,可以是 HTML5 历史 API 的 polyfill。我说“可以是”,因为在那一章的例子中,我们无论何时都使用了 history.js。真正的 polyfills 仅在功能缺失时使用。所以,上面的代码如果 HTML5 支持存在,可以使用不同的 API,而不是通过 polyfill 提供的 API。
这可能解释了为什么选择了 Modernizr 这个名字。而 enhance.js 允许您照顾使用旧技术的人,并为他们提供一个使用当前技术的界面,Modernizr 则允许您在今天编写您的访客明天或下周将使用的代码,当他们更新他们的电脑或购买新手机或平板电脑时。您现在已经领先一步。
您的网站或应用程序已经使用了 HTML5 和 CSS3 提供的所有酷炫功能,并且有回退代码以备老旧技术涉及。您可以激励您的客户或访客尽早更新,但您的网站或应用程序已经支持了对于他们来说属于未来的功能。这是否很酷?
yepnope.js 或 Modernizr.load
就像您在Enhance.js部分看到的那样,您可以根据测试通过或失败有条件地加载文件。最酷的有条件加载器是yepnope.js。在撰写本文时,它即将被弃用,因此它可能从 Modernizr 中消失。当然,会有新的东西出现。所以,现在,我们提供了一个简短的例子,说明您如何使用 Modernizr 进行静态资源的条件加载:
Modernizr.load({
test: Modernizr.geolocation,
yep : 'geo.js',
nope: 'geo-polyfill.js'
});
摘要
本章是本书中最长的标题,非常适合最重要的章节。在其中,我们解释了昨天和明天网络开发的区别。我们解释了您需要首先考虑移动端,您的设计必须是响应式的,并且您应该通过渐进增强使用酷炫的新特性来奖励那些拥有最新、最棒东西的客户。
在接下来的章节中,我们将引导您如何使用酷炫的 CSS/JavaScript 框架 Foundation 来应用之前所学的一切,这样您就可以使用别人已经为您完成的工作。我们还将向您展示一种方法,让您能够完全控制服务器端如何处理您想要的事情,使用 node.js。
如果您在这里停下来,您已经学到了现代网络开发的基础知识,我为您的成就表示祝贺。如果您继续前进,您将学会使用一个框架,这个框架将节省您大量的时间和工作,并且看起来是一种完全不同的做事方式。随着我们继续解释,您会意识到它最初并没有那么不同。
第十三章。Foundation——一个响应式 CSS/JavaScript 框架
我通常将Zurb Foundation和Twitter Bootstrap描述给人们,就像“我一直想写但从未找到时间去做的事情”。
可以称它们为框架也可以不称,但我建议你检查一下这两个网站,了解它们如何称呼自己。我使用过它们,比较过它们,并决定选择Foundation。这取决于个人品味。我总是喜欢接受更大的挑战。我更喜欢学习芬兰语而不是瑞典语。你可能觉得 Bootstrap 更容易使用,因为它可能包含更多的组件,但在我看来,识别一个网站是否是用 Bootstrap 开发的比用 Foundation 开发的要容易得多。我已经在附录中包含了一个 Bootstrap 的概述。
我们的响应式工具包——Foundation
在上一章中,你学习了我们需要开发一个以移动优先、渐进增强、响应式设计为主的网页应用的所有拼图碎片。移动优先是一种思维方式;对于渐进增强部分,我们使用了enhance.js
和modernizr.js
。因此,我们仍然需要了解如何通过编写灵活的网格、创建媒体查询、支持灵活的图片以及一些其他操作来完成响应式部分。
这是 Foundation 已经为我们完成的部分。此外,它确实有一些酷的用户界面(UI)组件。其中之一是off-canvas,这是一个在移动设备和平板电脑上使用的出色功能,自从我使用它以来,我在每个主要网站或应用上都能认出它,包括 Facebook。查看优秀的 Foundation 文档,以获取完整的功能列表。在本章中,我们将描述我认为特别有用的那些功能。
Foundation 组件
下载 Foundation 后foundation.zurb.com/
(我推荐选择完整版),你会发现它包含 CSS 组件和 JavaScript 组件。我将它们都放在了一个名为foundation5
的文件夹中,以便更容易切换版本,甚至维护一个 Bootstrap 风格的网站。
以下是下载将获得的内容:
-
index.html:这是一个展示 Foundation 最常见功能的网页。
-
css:这是一个包含
foundation.css
和foundation.min.css
的文件夹,是 Foundation 样式表。在你的网站<head>
部分包含其中一个。此外,包含你自己的样式表。我们建议你不要修改foundation.css
。否则,如果出现新版本,你最终会覆盖你的代码。 -
js/foundation.min.js:这是所有的 Foundation JavaScript。将其放在你的
<body>
标签的末尾之前,jQuery 之后。另外,不要修改此文件。 -
js/foundation:这是一个包含 Foundation 功能的单独 JavaScript 源文件的文件夹,以防你想了解它们是如何工作的。不需要在你的网页中包含这个文件夹。
-
js/vendor:这是一个包含更多有用 JavaScript 库的文件夹,例如 jQuery 和Modernizr。
基础框架使用 Modernizr,所以你很高兴我们已经知道这是什么了吧?
在你网站的<head>
部分引用modernizr.js
,以便在页面加载之前执行所有测试。将jQuery.js
放在<body>
的末尾之前,并在foundation.min.js
之前。然后,添加以下行,这将启动基础框架:
<script>
$(document).foundation();
</script>
网格系统
基础框架自带一个网格系统,默认情况下,将你的工作屏幕区域分为 12 列。使用类,你可以为屏幕上的每个块指定你希望它有多宽。有不同的类用于不同的尺寸;这是基础框架使响应式设计变得简单的方法。
想象一下你希望你的网站在各种屏幕和设备上看起来都很棒。让我们先看看较大的屏幕,暂时称它们为画布。想象两条垂直线,大约相隔 1024 像素,位于画布中心。这些将成为我们工作区域的垂直边界。当然,一旦你减小视口大小,或者如果你正在使用平板或手机,你的工作区域将是屏幕的全宽。
现在,为了构建我们网页的布局,我们将将其分为水平行,在其中,我们将放置响应式的内容块(<div>
)。对于这些,我们可以使用类来指定我们正在处理哪种尺寸的屏幕(可以想象成哪种类型的 T 恤),以及我们希望填充多少列。看看下面的例子:
<div class="row">
<div class="small-12 medium-6 large-4 columns">
<h3>First block title</h3>
<p> First block text</p>
</div>
<div class="small-12 medium-6 large-4 columns">
<h3>Second block title</h3>
<p>Second block text</p>
</div>
<div class="small-12 medium-6 large-4 columns">
<h3>Third block title</h3>
<p>Third block text</p>
</div>
</div>
因此,这是我们第一个“行”的内容,包含三个响应式块。现在,我们首先构建移动端,所以第一个类我们使用的是表示我们希望这个块在移动端(比如我们的 T 恤)上有多宽的列数。我们选择最大值:12。如果你没有为中等和大型尺寸指定任何内容,它们将隐式地继承相同的值。始终包含columns
类。
你可能已经猜到了这里会发生什么。当你在一个小屏幕上显示这些内容时,这三个块中的每一个都将有 12 列宽,这是我们能够达到的最大宽度,因此它们将堆叠在一起。当我们切换到中等屏幕,比如将平板电脑从纵向转为横向时,我们将有一行两个块,另一个块在其下方。在大屏幕上,它们将并排排列。
类 end
如果你一行中没有足够的项来填充所有列,最后一个项将被放置在右侧边缘。如果你想覆盖这一点,你可以为这个添加一个名为end
的类,它将出现在你期望的位置。
可见性类
这里有一系列适合的类,我总是在文档中找不到它们,因为它们在网格部分之前就被描述了:可见性类。类似于确定某个东西想要有多宽的列宽,你可能想要根据设备决定是否显示它。最流行的例子可能是你在小屏幕上看到的三个条形菜单图标,它取代了“正常”尺寸屏幕上的水平导航。你可以选择为 T 恤尺寸或更大尺寸隐藏或显示。名字应该能说明一切。这也表明我们在 T 恤店有更多尺寸。
更有用的是,你可以使用这些类来根据屏幕方向和是否为触摸屏设备来决定是否显示某些内容。这也是你不应该忘记包含modernizr.js
的原因之一。以下是列表:
show-for-touch |
show-for-medium-up |
hide-for-small-only |
---|---|---|
hide-for-touch |
show-for-large-only |
hide-for-medium-only |
show-for-landscape |
show-for-large-up |
hide-for-medium-up |
hide-for-landscape |
show-for-xlarge-only |
hide-for-large-only |
show-for-small-only |
show-for-xlarge-up |
hide-for-large-up |
show-for-medium-only |
show-for-xxlarge-up |
hide-for-xlarge-only |
hide-for-xlarge-up |
hide-for-xxlarge-up |
块状网格系统
这让我说出了一些让人困惑的智慧之言,直到你看到例子:拥有相同大小的东西并不等同于东西的大小是一样的。
属于块状网格
系统的类允许你确保无论屏幕大小如何,你的项目块都是均匀分布的。你可以通过指定一行中想要的项目
数量来实现这一点,而不是指定一个项目想要的列宽。
因此,在较小的屏幕上,你的项目会变得更小,所以它们不再是相同的大小,但一行中的所有项目将具有相同的大小,所以它们都有相同的大小。明白了吗?这是一个在简单相册或博客文章中使用的好功能。
你可以通过将类(们)分配给一个<ul>
元素来使用块状网格,你的项目是<li>
元素。一个典型的类名可能是size-block-grid-number
。例如:
<ul class="small-block-grid-2 medium-block-grid-3 large-block-grid-4">
<li><!-- first item here --></li>
....
</ul>
有用的 UI 元素
如前所述,Foundation 有很好的文档,如果你想要阅读和学习更多,还有书籍可供选择。在本章的剩余部分,我们将介绍一些我认为非常有用的 UI 功能,并附带一些结合它们的例子。
之后,我们将以 Foundation 的方式完成导航章节,画布上和画布外。
缩略图 – 用于简单相册
Foundation 正在变化,所以在撰写这本书的时候,有几个 UI 功能用于制作图片滑块或其他与照片相关的酷炫功能,这些功能将被大幅修改或替换。
然而,每个人都需要他们的图片缩略图作为设计的一部分。在 Foundation 中,有一个简单的类名为th
,您可以使用它来生成带有或没有标题的格式良好的响应式缩略图。例如:
<a class="th" href="/photo.jpg">
<img src="img/photo.jpg"></img> <div><h6>Caption</h6></div>
</a>
这将为您的缩略图像和标题周围添加漂亮的样式,但一旦您点击它,较大的图像将以浏览器决定的方式显示。
揭示模态 – 您更好的弹出窗口
如果我们的较大图像能够在一个漂亮的弹出窗口中显示,那就更好了。弹出窗口可以在网站的任何地方使用。Foundation 提供了他们所说的揭示模态。它们非常简单易用。
这是您在网站上放置以提供弹出窗口访问权限的区域。在示例中,它是一个简单的<div>
,甚至不是一个<a>
标签。您给元素一个data-reveal-id
属性,其值为您想要揭示的元素的 ID。
接下来,有一个具有该 ID 的元素,其中包含您想要在弹出窗口中显示的内容。注意其末尾的带有close-reveal-modal
类的<a>
标签。当点击时,这将触发弹出窗口消失。如果您忘记这部分,用户将无法使弹出窗口消失:
<div data-reveal-id="revealid">Click here</div>
<div id="revealid" class="reveal-modal medium" data-reveal >
<p>Text of popup</p><a class="close-reveal-modal">×</a></div>
下拉菜单
也许您想要一个下拉菜单(或向上下拉或向左下拉)而不是弹出窗口。在这个例子中,我们介绍了一个 Foundation 风格的button
。我们还将返回到 T 恤店:按钮和实际的下拉菜单都可以给予一个大小类。当您包含content
类时,下拉菜单将获得一些漂亮的填充。还有一个选项,您可以使用它来使下拉菜单在悬停时显示:
<a class="button tiny" data-dropdown="calpeople"
data-options="is_hover:true">California people</a>
<ul id="calpeople" class="small f-dropdown content"
data-dropdown-content>
<li>Ansel Adams</li>
<li>John Muir</li>
<li>Arnold Schwarzenegger</li>
</ul>
示例 – 一个简单的照片画廊
让我们现在结合一个块网格、缩略图、揭示模态和下拉菜单来制作一个漂亮的响应式照片画廊。我们将使用模态来显示较大的图像,并使用下拉菜单来显示照片的技术细节。
这是第一张照片的代码片段:
<div class="row">
<ul class="small-block-grid-3 medium-block-grid-4large-blockgrid-5" >
<li>
<div class="th" data-reveal-id="tiogalakepf" data-reveal>
<img src="img/tiogalakesmall.jpg" alt="Tioga Lake">
<div class="caption"><h6>Tioga Lake</h6></div>
</div>
<div id="tiogalakepf" class="reveal-modal medium portfolio" data-reveal >
<img src="img/tiogalake.jpg"></img>
<p>© 1997-2015 Paul Wellens</p>
<h3>Tioga Lake in april of 1997</h3>
<a class="button small" data-dropdown="tiogadrop" data-options="align:top">Details</a>
<ul id="tiogadrop" class="f-dropdown content" data-dropdown-content>
<li>Hasselblad</li>
<li> Fuji Velvia</li>
<li> 40mm CF lens </li>
<li> f16 </li>
</ul>
<a class="close-reveal-modal">×</a>
</div>
</li>
</ul>
</div>
手风琴
手风琴在您有信息被划分为逻辑部分,并且想要展开某些内容,然后再折叠回原状时非常有用。我发现它是一个在在线简历中使用的极好工具,例如,用于工作历史部分或常见问题解答。我已经使用 jQuery UI 使用它多年了。自从我发现 Foundation 以来,我就开始使用他们的手风琴。
它的设置非常简单;您可以使用<ul>
元素或<dl>
元素来创建您的手风琴。在我们的示例中,我们使用<dl>
。
<div class="small-12 columns">
<h3>California People</h3>
<dl class="accordion data-accordion=">
<dd class="accordion-navigation"><a href="#ansel">
<h5><i class="fa fa-caret-right green" ></i>Ansel Adams</h5></a>
<div class="content" id="ansel">
<p>I think of Ansel Adams as the best landscape photographer ever. His black and white photographs of Yosemite National Park and New Mexico are legendary </p>
</div>
</dd>
...
</dl>
极佳的 Font awesome
在这个例子中,我们不仅介绍了手风琴,还介绍了一个来自外部 Foundation 的激动人心的特性:Font awesome (www.fontawesome.io). 那些需要为屏幕上的每一张图片准备.gif
、.png
或.jpg
文件,并且为每种大小都准备一个单独文件的日子已经一去不复返了。
您现在可以在代码中使用图标,就像它们是具有特定类的 HTML 元素一样。在底层,它们是矢量图像,因此可以很好地缩放,并且由于它们是具有类的 HTML 元素,您可以使用 CSS 来样式化它们。有超过 500 个图标可供选择。Font awesome 使用<i>
标签,已弃用其原始用途。因此,通过添加两个类,fa
和fa-caret-right
,您的<i>
变成一个酷炫的右箭头,有助于使手风琴更加直观。
在示例中,我们给它添加了一个额外的类,green
,并在我们的 CSS 文件中匹配颜色。如果我们点击一个项目以展开手风琴,我们希望它变成一个蓝色的向下箭头。我们在 JavaScript 中这样做,如下所示:
$("dl.accordion").on("click", "a", function(e){
var faicons = $(this).parent().parent().find('i.fa');
faicons.removeClass("blue").addClass("green").removeClass("fa- caret-down").addClass("fa-caret-right");
if ($(this).parent().hasClass("active")) {
var faicon = $(this).parent().find('i.fa');
faicon.removeClass("blue").addClass("green").removeClass("fa- caret-down").addClass("fa-caret-right");
}
});
基础框架使用一个名为active
的类来指示手风琴的一部分被展开。因此,我们在改变箭头的形状和颜色之前会检查这个类。当然,我们事先并不知道我们在哪里,所以我们首先将所有箭头都改为right
和green
。
平衡器 – 使用两个制作最困难的事情变得简单
我发现很难做到的一点是确保相邻的两个内容块始终具有相同的高度。当然,您可以以像素为单位给它们设置相同的高度,但如果内容的一部分超过了您指定的长度,或者如果这个长度高于设备本身呢?最好不指定固定的高度,让您的<div>
随着内容增长,但这样如何确保它们保持对齐?
平衡器在基础框架中会为你处理这些。这可能听起来像是另一个阿诺德·施瓦辛格电影的标题,但它同样强大。它使用 HTML5 数据属性来完成工作。只需将data-equalizer
添加到父容器中,将data-equalizer-watch
添加到所有希望具有相同高度的容器中。
导航
基础框架还有许多其他功能,您可能想要查看。我的意图是突出那些让我如此着迷以至于我决定使用该框架的功能。其中之一是orbit,一个出色的照片滑块,但现在已被弃用。基础团队推荐了一个相当酷的替代方案,但无论如何,这些事情在教科书中很难解释。我们将在附录中提供整个内容,该附录仅在网上提供。
我们以基础框架提供的两个主要导航组件来结束这一章:一个用于画布上菜单栏,另一个用于画布外菜单。
顶部栏 – 不只是常规菜单栏
可能是菜单栏的顶层,基础框架的顶部栏是一个非常复杂的组件,它为您提供了创建易于导航的水平菜单栏所需的一切。
您的顶层货架分为三个部分:标题区域,左侧和右侧菜单项列表。以下是一个示例。为了简洁起见,我们没有在<a>
标签内包含任何href
属性:
<div class="row">
<nav class="top-bar" data-topbar role="navigation">
<ul class="title-area">
<li class="name">
<h1><a href="#">My Site</a></h1>
</li>
<!-- Here is the spot to add magic -->
</ul>
<section class="top-bar-section">
<!-- Right Nav Section -->
<ul class="right">
<li><a>Overview</a></li>
<li class="has-dropdown">
<a>East California</a>
<ul class="dropdown">
<li><a>High Sierra</a></li>
<li><a>Mono Lake</a></li>
<li><a>June Lake</a></li>
<li><a>Death Valley</a></li>
</ul>
</li>
<li class="has-dropdown">
<a>California Coast</a>
<ul class="dropdown">
<li><a>Venice Beach</a></li>
<li><a>Big Sur</a></li>
<li><a>San Simeon</a></li>
<li><a>Point Reyes</a></li>
</ul>
</li>
</ul>
<!-- Left Nav Section -->
<ul class="left">
<li><a>About</a></li>
</ul>
</section>
</nav>
</div>
如您所见,这相对简单直接。在经典的网站开发中,通常菜单是 <ul>
元素,菜单项是 <li>
元素。如果您想添加子菜单,可以使用名为 dropdown
的类,并在其下方添加另一个 <ul>
。左右顺序并不重要。这非常好,因为这意味着您可以为基本版本使用相同的 HTML。只需将静态 HTML 文件作为 href
属性的值添加,然后使用您自己的酷炫 JavaScript 魔法制作现代、响应式的版本。
Foundation 在顶部栏周围创建了一些漂亮的样式,有几种深灰色(不是五十种),当然,您可以在自己的 CSS 文件中更改。
添加更多魔法
这就是魔法。通过简单地在标题区域添加一个 <li>
元素,您就可以让 Foundation 为您创建一个适用于小屏幕的完整替代菜单。只需查看这个示例。按照指示添加到示例中,看看当您将查看区域缩小到非常小的时候会发生什么:
<li class="toggle-topbar menu-icon"><a href="#"><span>Menu</span></a></li>
您可以省略 <span>
元素,这样只会显示菜单图标,或者省略 menu-icon
类。显然,您不应该同时省略两者,否则人们将无法访问您的魔法。
更多魔法——画布,最酷的东西
将您的主菜单放在手机的左侧,直到您需要它。
我认为这是 Foundation 的精华所在,请原谅我用法语的用法。第一次使用它时,我印象深刻。下次我去 Facebook 时,我觉得我在 Facebook 应用中认出了它。
这个概念相当简单。在您的代码中,将最初不应显示的菜单内容放置在左侧。重要的是要控制它应该位于左侧的哪个部分。当用户点击菜单按钮,通常是菜单栏图标时,菜单将从左侧滑入。
这样,我们可以从上一个示例扩展。我们不再让 Foundation 给我们原始菜单的移动版本,现在我们可以指定一个移动优先的菜单内容。这里有一个小示例。在附加章节中会有一个更大的示例,我们将在网上添加。我们还使用可见性类确保全宽菜单不会出现,而是被带有图标的菜单替换。一旦您点击它,左侧的画布将出现在内容旁边。高度由您放置关闭 <div>
标签的位置决定,用于 off-canvas-wrap
和 inner-wrap
:
<div class="off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<div class="row">
<nav class="tab-bar show-for-small">
<a class="left-off-canvas-toggle menu-icon ">
<span>Menu</span>
</a>
</nav>
<div class="left-off-canvas-menu show-for-small">
<ul class="off-canvas-list exit-off-canvas">
<li><label>Portfolios</label></li>
<li><a>High Sierra</a></li>
<li><a>Eastern Sierra</a></li>
<li><a>June Lake</a></li>
<li><a>California Coast</a></li>
</ul>
</div>
<nav class="top-bar hide-for-small" data-topbar>
<ul class="title-area">
<li class="name">
<h1>
<a>My Site</a>
</h1>
</li>
</ul>
<section class="top-bar-section">
<ul class="right">
...
摘要
在本章中,我们介绍了 Foundation CSS/JavaScript 框架。它允许我们创建一个移动优先、响应式设计的网站或应用程序,而无需编写任何响应式部分的代码。我们只需根据我们的需求进行定制。
这本书涵盖了我想要称之为现代网络开发的这部分内容到此结束。在最后一章中,我们将前进到对我们大多数人来说似乎是前卫的内容,尽管到这本书印刷的时候,它将会非常流行。这一切都是围绕一个叫做node.js的 JavaScript 事物构建的,而这个事物并不是用 JavaScript 编写的。
第十四章 Node.js
恭喜!你现在已经到达了这本书的最后一章。在了解了古典和现代网络开发中使用的多数技术之后,我们现在将讨论我想要称之为网络开发的前卫(请原谅我的法语),的基本知识:node.js 及其朋友。
在我们迄今为止讨论的所有内容中,我们使用了通常所说的LAMP(或MAMP或WAMP,取决于服务器上的操作系统)堆栈:Linux Apache MySQL PHP。即使我们用MongoDB替换 MySQL,这个缩写仍然适用。或者我们可以称之为LANP,其中的 N 代表NoSQL。
因此,我们必须学习所有这些语言:客户端的 JavaScript,由浏览器进行解析;PHP,由 Apache 网络服务器进行解析;还有更多。想象一下,如果你可以替换掉所有东西,甚至是你不会想到替换的东西——网络服务器——而用 JavaScript 来替代?这正是 node.js 为你做的事情。在我看来,node.js 对网络开发的影响,就像 Schoenberg 和 Webern 对古典音乐的影响,Picasso 和 Braque 对绘画的影响,哦,为什么不呢,威尼斯海滩的说唱歌手对流行音乐的影响。
这里有好消息和坏消息。坏消息是我们有重新开始一切并白费很多功夫的风险,但好消息是,基于 node.js 的解决方案性能良好,可扩展,对于那些在我们之后的人来说,只需要了解一种编程语言:JavaScript。这个缩写的名称仍然有待确定:LJMJ或LNMJ(J 代表JavaScript或 N 代表node.js)或MEN(MongoDB,Express,node.js)?已经被广泛使用的一个是MEAN。随着时间的推移,人们将达成一致。谁发明了单词立体主义?
在这一章中,我们将尝试以 node.js 的方式重复你迄今为止所学的所有内容。所以,我们已经知道我们的代码将用哪种语言编写:JavaScript。我们将要编写的代码可能会让你感到惊讶。
Node.js
让我们回顾一下;在第一章《万维网》中,你学习了关于万维网以及人们如何通过发送 HTTP 协议请求到网络服务器的浏览器来访问数百万个页面。嗯,人们仍然会使用发送 HTTP 请求的浏览器,但我们刚刚丢弃了网络服务器,现在我们该怎么办?我们编写一个。可怕吗?不,这将会非常有趣。编写底层代码不是你的强项?没关系,有人已经为你做了。有一个整个社区正在为 node.js 编写代码,其他人都可以使用。这些代码被提供为所谓的模块,当然,我们也有一个可用的 HTTP 模块。
Web 服务器为我们做的另一件事是分析用户输入的 URL,并探索文件系统以查看是否存在物理文件,例如,一个hello.html
文件,并将其发送回客户端。我们也必须编写这个。这很酷,因为它将让我们完全控制我们的 Web 服务器应该能够处理什么,不应该处理什么。正如预期的那样,node.js 也有url
和fs
模块。
我们需要一个数据库,但我们已经知道我们喜欢的一个:MongoDB。我们能否用 node.js 使用它?是的,我们可以。有一个模块或驱动程序可以从 node.js 访问现有的 MongoDB 服务器。由于 MongoDB 是一个文档数据库,而文档实际上是 JSON 对象,所以在全 JavaScript 生态系统中这是一个完美的匹配。
逐渐地,我们开始意识到,在写出一个应用之前,这个 node 应用实际上是要成为一个包含特定代码的 web 服务器,还是一个包含 web 服务器的应用;任你选择。
当我带你通过我们的第一个示例时,你就会意识到我们以前从未需要为简单的Hello, World
程序编写这么多代码。想象一下,如果需要用这么多底层代码编写一个功能齐全的单页 Web 应用会怎样?这正是Express发挥作用的地方。它是一个 node.js 框架,将帮助我们编写更干净、更紧凑的代码。这是服务器端的 jQuery。一旦我们的示例变得过于冗长,我们就会切换到 Express。
我们还丢弃了另一件事:Apache 作为一个应用服务器,这部分让我们在服务器上有了 PHP 语言。我们一直使用 PHP 在服务器上动态生成 HTML,等到浏览器读取时,它已经变成了全部的 HTML。
使用 PHP 的好处是,我们可以在纯 HTML 中嵌入 PHP 代码,在<?php
和?>
之间。用<script>
标签填充 HTML 文件以包含 JavaScript 代码并不吸引人。我们也会看看这个问题的解决方案。
安装 node.js
我们不再拖延,现在就在你的电脑上安装 node。安装方式会根据你运行的操作系统而有所不同。访问nodejs.org/
并获取正确的下载。结果在所有地方都是一样的:它给了我们两个程序:node和npm。
npm
npm,node 打包管理器,是您用来查找和安装模块的工具。每次您编写需要模块的代码时,您可以通过在代码中放入类似以下内容来指定:
var module = require('module');
如果尚未安装,需要使用以下命令进行安装:
npm install
或者你也可以使用:
npm -g install
后者会尝试在全局范围内安装模块,前者在命令执行的目录中安装模块。它通常会在名为node_modules的文件夹中安装模块。
node
node
命令是用来启动你的 node.js 程序的命令,例如:
node myprogram.js
Node 将启动并解释你的代码。输入 Ctrl + C 来停止 node。让我们立即开始我们的第一个程序。
我们不可避免的 Hello, world
示例是最小的可能网络服务器:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8080, 'localhost');
console.log('Server running at http://localhost:8080');
将此文件保存为 hello.js
,或者从 Packt Publishing 网站获取,然后在终端窗口中输入:
node hello.js
此命令将使用 node
运行程序。在终端窗口中,它将成为你的控制台,你会看到文本 Server running
。
接下来,当你启动浏览器并输入 http://localhost:8080
作为 URL 时,会出现一个看起来像网页的东西,包含著名的两字句子 Hello World
。实际上,如果你访问 http://localhost:8080/it/does/not/matterwhat
,会出现相同的内容。可能不是非常有用,但它是一个网络服务器。
添加 HTML
这是一个略有不同的版本,我们明确指定发送 HTML 而不是纯文本:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>Hello World\</h1>');
}).listen(8080, 'localhost');
console.log('Server running at http://localhost:8080');
提供静态内容
我们不习惯无论我们给出什么路径作为 URL,都会出现相同的内容。通常,URL 指向一个文件(或一个文件夹,在这种情况下,服务器会查找 index.html
文件),foo.html
或 bar.php
,并且当存在时,它会被提供给客户端。
那么,如果我们想用 node.js 来做这个呢?我们需要一个模块。有几种方法可以完成这项工作。在我们的例子中,我们使用 node-static
。首先,我们需要安装它:
npm install node-static
你通常可以在 Github 和其他酷炫的地方找到关于方法和属性的帮助文档。在我们的应用程序中,我们不仅创建了一个网络服务器,还创建了一个 fileserver
。它将为本地目录 public
中的所有文件提供服务。将所有所谓的 静态内容
放在一起,放在一个单独的文件夹中是很好的。这基本上是所有将被客户端接收并解释的文件。由于我们最终会得到客户端代码和服务器代码的混合,因此将它们分开是一种良好的实践。当你使用 Express 框架时,它会为你创建这些。
-
在我们的项目文件夹中创建
hello.js
,我们的 node.js 应用程序:var http = require('http'); var static = require('node-static'); var fileServer = new static.Server('./public'); http.createServer(function (req, res) { fileServer.serve(req,res); }).listen(8080, 'localhost'); console.log('Server running at http://localhost:8080');
-
接下来,在子文件夹
public
中,我们创建hello.html
:<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello world document</title> <link href="./styles/hello.css" rel="stylesheet"> </head> <body> <h1>Hello, World</h1> </body> </html>
-
我们可以使用
hello.css
创建背景,如下所示:body { background-color:#FFDEAD; } h1 { color:teal; margin-left:30px; } .bigbutton { height:40px; color: white; background-color:teal; margin-left:150px; margin-top:50px; padding:15 15 25 15; font-size:18px; }
因此,如果我们现在访问 http://localhost:8080/hello.html
,我们将看到我们至今仍然熟悉的 Hello World
消息,并带有一些基本的样式,这证明了我们的文件服务器也传递了 CSS 文件。
-
现在,我们将更进一步,实际上在我们的 html 文件(
hellobutton.html
,仅 body 部分)中添加 JavaScript。我们将重用之前的 CSS 文件,创建一个略有不同的 HTML 文件,并添加一个 JavaScript 文件。我假设你 somewhere 有一个 jQuery 的副本。<body> <div id="content"> <button type="button" id="hellobutton" class="bigbutton">Click here for a message</button> </div> <script src="img/jquery.js"></script> <script src="img/hellobutton.js"></script> </body>
-
要添加按钮,让我们创建
hellobutton.js
:$(document).ready(function(){ $("#content").on("click", "#hellobutton", function(data){ $('#hellobutton').text("Hello World"); } ); });
因此,当我们访问 http://localhost:8080/hellobutton.html
时,我们会看到一个带有按钮的网页;当我们点击它时,其文本变为 Hello World
。这意味着我们的客户端 jQuery 和 JavaScript 正在正常工作。
在 public
文件夹中,创建一个文件 index.html
:
<!DOCTYPE html >
<html>
<body>
<h1>It works!</h1>
</body>
</html>
如果我们访问http://localhost:8080
,我们会看到It Works ! 就像我们点击 Apache Web 服务器的文档根目录一样。这是因为我们的node-static
模块已经将这个文件配置为默认。
但是,还有一些事情我们没有习惯的方式工作。如果我们输入hellobutton
而不是hellobutton.html
,什么也不会发生,因为我们没有编程我们的 Web 服务器去寻找hellobutton.something
。甚至不要想处理hello.html?key=value
。
另一方面,如果你在./public
中放置一个图片文件,例如baywatchstation.jpg
,并输入http://localhost:8080/baywatchstation.jpg
,你将在你的浏览器中看到这张图片。所有这些操作只需要很少的代码和两个酷炫的 node.js 模块。
两个(JavaScript)城市的传说
我们已经到达了一个重要的阶段:我们有两个不同的 JavaScript 文件,它们都位于我们的服务器上,但一个是通过 node.js 解释的,另一个则是由 node.js 提供并由浏览器解释的,换句话说,是客户端。
尝试这个:http://localhost:8080/js/hellobutton.js
。你将在浏览器中看到你的 JavaScript 文件的代码。现在插入alert("Here's Johnny!");
并将其放在<script>
标签中,保存并刷新浏览器。Johnny
会出现,然后 JavaScript 什么也不做,不会给你任何错误信息。
因为我们已经将public
(好吧,是node-static
)配置为我们的迷你 Web 服务器的文档根目录,所以我们甚至无法访问hello.js
,这使我们免于更大的困惑。我确信到现在你已经理解了 JavaScript 文件和 JavaScript 文件之间的区别。这就是为什么一些开发者养成了使用不同扩展名(例如.njs
用于服务器端 JS 文件)的习惯。我相信,像我们开始做的那样,将不同类型的文件放在不同的文件夹中要清晰得多。
但是,到目前为止,在这么短的时间内,仅仅几行代码,我们就能够用 node.js 的方式做到我们在这本书中讨论的几乎所有事情:我们可以处理 HTML、CSS、JavaScript 和 jQuery。我们放弃了 PHP,并用 MongoDB 替换了 MySQL。这让我们只剩下后者和 Ajax,然后我们就可以用 node.js 的方式重写我们的书了。
node.js 和 MongoDB
在第十一章中,我们介绍了 MongoDB,一个文档数据库,并学习了如何从命令行以及 PHP 程序内部访问它。在 node.js 中这样做甚至更容易。首先,让我们不要忘记在一个单独的终端窗口中启动 MongoDB 服务器:
mongodb
接下来,我们当然需要一个 node.js 模块,mongodb
:
npm install mongodb
下面是一个简单的程序,它连接到 MongoDB 服务器,具体来说是california
数据库,并在people
集合中查找一个文档。
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/california',
function(err, db) {
console.log('Connected to MongoDB!');
var collection = db.collection('people');
collection.findOne({name: 'Adams'}, function(err, doc) {
console.log(doc.first + ' - ' + doc.name);
db.close();
});
});
Déjà vu … 再次
当我刚开始使用 node.js 时,我有一种似曾相识的感觉。用 Grace Jones 基于 Astor Piazolla 的《Libertango》改编的歌曲来比喻:奇怪,我以前见过这种情况。
使用 node.js,你只需添加你需要的东西,所以它默认不包括厨房用具。这只能意味着你在性能方面会从中受益。
我是一个 UNIX 用户,但这个故事要追溯到当 Linus 还没有将其重写为 Linux,Mac OS X 还不存在的时候。内存和磁盘空间都很昂贵,UNIX 也是如此,因为制造商必须支付版税。
我曾经是 PC UNIX 产品的自豪的产品经理,我们最酷的价值增值之一是一个名为kconfig的工具,它允许人们自定义 UNIX 内核中的内容,使其只包含所需的内容。这就是 node.js 让我想起来的。而且它是用 C 语言编写的,就像 UNIX 一样。似曾相识。
虽然当时很酷,但今天就不会那么酷了,因为 UNIX 已经添加了太多东西:它将无法管理。
如果我们想用纯 node.js 来模拟 Apache 网络服务器能处理的所有功能,也是同样的道理。只需看看 PHP 的phpinfo()
函数的输出。它显示了所有加载到 Apache 中的模块。如果我们想只用 node.js 支持所有这些,我们需要太多的模块,最终代码将难以阅读。电影《莫扎特传》中的场景浮现在脑海中,皇帝的随从们对莫扎特的《费加罗的婚礼》达成一致意见(我不这么认为):音符太多!
Express
使用 Express 框架是完成工作而无需过多笔记的好方法。在expressjs.com网站上,它被称为最小和灵活的 node.js 网络应用程序框架,提供了一套强大的功能,用于构建网络应用程序。
描述 Express 能为你做什么的最好方法可能没有。它是最小的,因此框架本身的开销很小。它是灵活的,因此你可以添加你需要的东西。因为它提供了一套强大的功能,这意味着你不必自己创建它们,而且它们已经由不断增长的社区进行了测试。
安装 Express
当然,Express 也是一个 node 模块,所以我们像安装模块一样安装它。在撰写本文时,我们使用了Express 4。在你的应用程序项目目录中,输入:
npm install express
或者你也可以使用:
npm install —save express
如果你指定了—save
选项,npm
将会更新package.json
文件。你会注意到在node_modules
内部会创建一个名为express
的文件夹,并在其中还有一个 node-modules 的集合。这些都是被称为中间件的例子。
在接下来的几个例子中,我们假设app.js
是你的 node.js 应用程序的名称,app
是你在该文件中使用的变量,用于 Express 的实例。这样做是为了简洁。最好使用一个与你的项目名称匹配的字符串。
我们的第一个 Express 应用程序
当然,我们还将进行更多的 Hello, World
示例。这是我们第一个 Express 应用程序:
var express = require('express');
var app = express();
app.set('port', process.env.PORT || 3000);
app.get('/', function (req, res) {
res.send('<h1>Hello World!</h1>');
});
app.listen(app.get('port'), function () {
console.log('Express started on http://localhost:' +
app.get('port') + '; press Ctrl-C to terminate.' );
});
嗯,与我们的第二个 node.js 示例相比,行数差不多。但它看起来干净得多,而且为我们做了更多。你不再需要显式包含 HTTP 模块,你也不再需要指定要发送哪个头,而且当你指定不同的 URL 时,你不会得到 Hello, World
,而是一个合理的错误消息。我们使用 app.set
和 app.get
来设置端口。当环境变量 PORT
被设置时,端口将被设置为它的值。
包含 app.get
的另一行告诉我们,当服务器接收到 GET
模式下的 URL 时,我们希望发生什么。就像在 node.js 中一样,有一个以 request
和 respond
对象为参数的函数。在 express
中,它们被扩展了;你可以用它们做更多创造性的事情,因为你可以使用更多方法。
例如,你可以访问 req.body
,它将包含一个对象,包含使用 POST
方法在表单中发送的所有值(使用 app.post
)。
一个使用中间件的示例
我们现在将使用 Express 重写 hello button
示例。public
目录中的所有静态资源都可以保持不变。唯一的变化是在节点 app
本身:
var express = require('express');
var path = require('path');
var app = express();
app.set('port', process.env.PORT || 3000);
var options = {
dotfiles: 'ignore',
extensions: ['htm', 'html'],
index: false
};
app.use(express.static(path.join(__dirname, 'public') ,
options ));
app.listen(app.get('port'), function () {
console.log('Hello express started on http://localhost:' +
app.get('port') + '; press Ctrl-C to terminate.' );
});
这段代码使用了与 express
一起提供的所谓中间件(static
)。第三方提供了更多。在前面提到的 req.body
中,有可用的中间件来解析表单数据(body-parse
)。你也可以编写自己的中间件。在其最简单的形式中,它是一个以 req
和 res
为参数的函数:
app.use (function(req,res) {
res.status(404);
res.send(" Oops, you have tried to reach a page that does not exist");
});
这是你的最小 404 处理器,当人们输入错误的 URL 时,你可以给他们一些有意义的内容在屏幕上阅读。你将这个放在你的 app.js 文件中,在代表成功场景的代码之后。
模板和 handlebars.js
还有另一个 Hello, world
示例要展示!在整本书中,我们大部分时间都在使用 PHP。我们用它来动态生成网页,或者网页的一部分。所以 PHP 代码,通常嵌入到以 .php
为扩展名的文件中的 HTML 代码中,在服务器上执行,浏览器渲染的是纯 HTML。你也学习了如何从单独的 PHP 文件或客户端的 JavaScript 中生成 HTML,使用来自服务器的数据,然后将它注入到网页的一部分(Ajax)中。
通过 <script>
标签和将 PHP 代码放在 <?php
和 ?>
之间,将 PHP 和 HTML 以及甚至一小块客户端 JavaScript 结合在一个文件中成为可能。这就是为什么他们有时把 PHP 称为 模板 语言。
现在想象一个全部由 JavaScript 构成的生态系统。是的,我们仍然可以将客户端 JavaScript 代码放在 <script>
标签之间,但服务器端的 JavaScript 代码怎么办?没有 <?javascript ?>
这样的东西,因为这不是 node.js 的工作方式。
Node.js 和 Express 支持多种模板语言,这些语言允许你分离布局和内容,并由模板系统完成获取内容并将其注入 HTML 的工作。由于我们不再想学习另一种语言,我们决定选择handlebars.js,因为它使用你已经在 12 章前学过的纯 HTML 来定义布局。Express 的默认模板语言似乎是Jade,它使用自己的、尽管更紧凑的、因为没有标签的格式。使用 handlebars.js 的另一个优点是它也支持客户端模板。
我们以一个如何使用 handlebars.js 的示例来结束本章。
本章中的所有示例都是 node.js 示例,我们需要使用模块。要在 node 和 Express 中使用 handlebars,有几个模块可供选择。我喜欢名字容易记住的express-handlebars。如果你在网上搜索 handlebars.js,你会找到用于客户端模板的库。
使用以下命令为 Express 获取handlebars
模块:
npm install express-handlebars
创建布局
在包含public
的项目文件夹内,创建一个名为views
的文件夹,并在其中创建一个名为layouts
的子目录。将public
中可能有的其他静态内容复制到views
中。在layouts
子文件夹内,创建一个名为main.handlebars
的文件。这是你的默认布局。把它想象成几乎所有页面的通用布局:
<!doctype html>
<html>
<head>
<title>Handlebars demo</title> </head>
<link href="./styles/hello.css" rel="stylesheet">
<body>
{{{body}}}
</body>
</html>
注意到{{{body}}}
部分。这个标记将被 HTML 替换。在views
文件夹中创建一个名为hello.handlebars
的文件,内容如下。这将是一个(许多)HTML 示例之一,它将被替换为:
<h1>Hello, World</h1>
我们最后的 Hello, World 示例
现在在项目文件夹中创建一个名为lasthello.js
的文件。为了方便,我们在之前的 Express 示例中添加了相关代码。之前所有工作正常,但如果你输入http://localhost:3000/
,你会看到一个页面,其中包含布局文件中的布局,并且{{{body}}}
被替换为(你猜对了):
var express = require('express');
var path = require('path');
var app = express();
var handlebars = require('express-handlebars') .create({ defaultLayout:'main' });
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
app.set('port', process.env.PORT || 3000);
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false
};
app.use(express.static(path.join(__dirname, 'public') , options ));
app.get('/', function(req, res)
{
res.render('hello'); // this is the important part
});
app.listen(app.get('port'), function () {
console.log('Hello express started on http://localhost:' +
app.get('port') + '; press Ctrl-C to terminate.' );
});
摘要
在本章的最后,我们概述了 node.js 和 Express。得益于 node.js,你可以在客户端和服务器端全面使用 JavaScript。你甚至可以用几行代码就编写自己的网络服务器。因为你只包含真正需要的东西,所以你可以通过这种前卫的网页开发方式获得更好的性能。
当你在代码中将网络服务器和服务器应用程序结合起来时,可能需要编写的代码比你预期的要多。这时 Express 就派上用场了:一个轻量级的框架,它生成的代码既紧凑又健壮。
总结来说,我们通过介绍 handlebars.js 触及了模板冰山的一角。这是一种更好的方法来分离布局和动态内容,让框架将它们结合起来,以便浏览器可以将其渲染为视图。为此,我们通过编写 HTML 布局来结束本章。
这让我想起了安娜·拉塞尔对瓦格纳的尼伯龙根的指环的演绎,她用 20 分钟完成了(通常《指环》需要 16 小时),结论是故事以它开始的方式结束。它有点像这样:
这里有莱茵河,河里有莱茵河女神,河底有……金子……
因此,经过 14 章,我们了解了网络开发的许多方面,我们回到了一切开始的地方:HTML。我希望你们阅读它的时候和我写作它的时候一样享受。
附录 A. Bootstrap – Foundation 的替代方案
在撰写这本书的时候,Bootstrap是 GitHub 上最受欢迎的项目。许多网站和 Web 应用程序都是使用 Bootstrap 构建的。关于这个主题已经出版了多本书籍,而且当 Web 开发者申请工作时,通常需要 Bootstrap 的使用经验。这就是为什么我决定在这本书中包含一个关于 Bootstrap 的附录。
我将向您展示如何使用 Foundation 完成的事情,但将以 Bootstrap 的方式。
Bootstrap 组件
您可以从getbootstrap.com
下载 Bootstrap,有多种风味。最小的下载是一个简单的发行版,包含您部署系统所需的所有组件。源发行版在子文件夹中有相同的内容,但也包含文档、less、JavaScript 源代码(按组件分解)和示例,这些都是很好的学习材料。
在这里,我们将关注最小分布。在我的系统中,我将它们全部放在了bootstrap
文件夹中,以便更容易切换版本,并在网站上保持 Foundation 和 Bootstrap 的风味。
注意
注意 Bootstrap 依赖于 jQuery。与 Foundation 不同,Bootstrap 的发行版不包含 jQuery,因此您需要从其他地方获取您的副本(或者当然可以使用随 Foundation 提供的副本)。
以下是您通过 Bootstrap 下载将获得的内容:
-
css:这是一个包含
bootstrap.css
和bootstrap.min.css
的文件夹,Bootstrap 的样式表。在您的网站<head>
部分包含其中一个。此外,还包括您自己的样式表。我们建议您不要修改bootstrap.css
。该文件夹还包含可选的Bootstrap 主题的样式表。
-
js/bootstrap.min.js和js/bootstrap.js:这包含所有 Bootstrap JavaScript。将此放在您的
<body>
标签的末尾之前。 -
字体:这是一个包含图标字体文件夹。我们坚持使用令人惊叹的 font-awesome 字体。
Bootstrap 网格系统
与 Foundation 一样,Bootstrap 自带一个网格系统,默认情况下,将工作屏幕的实际空间分为 12 列。使用类,您可以指定屏幕上每个块需要多宽的列数。有不同大小的类;这是 Bootstrap 和 Foundation 使响应式设计变得简单且几乎透明的做法。
Bootstrap 和 Foundation 使用不同的断点。在 Foundation 中,小号意味着小于 640 像素,中等是 641 像素到 1024 像素,大号是 1025 像素及以上。有两个可选的 XL 和 XXL 大小,就像 T 恤一样,断点分别是 1440 像素和 1920 像素。最后两个被注释掉了;如果您想使用它们,需要激活它们。
Bootstrap 有四种大小:xs
、sm
、md
和lg
。就像两个 T 恤制造商在小号上可能有不同的大小一样,这两个框架也有不同的大小,断点分别是 768 像素、992 像素和 1200 像素。
注意
Bootstrap 中似乎没有与阻塞网格等效的类。
这是我们在 第十三章 中向您展示的相同示例,Foundation - 一个响应式 CSS/JavaScript 框架,但这次是以 Bootstrap 的方式实现的:
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<h3>First block title</h3>
<p> First block text</p>
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<h3>Second block title</h3>
<p>Second block text</p>
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<h3>Third block title</h3>
<p>Third block text</p>
</div>
</div>
</div>
注意,我们用具有 container
类的 <div>
包裹了整个行 enchilada。这就是 Bootstrap 处理事情的方式。你不需要添加 column
类,就像在 Foundation 中那样。
可见性类
Bootstrap 中有许多可见性类,但比 Foundation 少,用于隐藏或显示网格的部分,如下所示:
-
hidden-xs
-
hidden-sm
-
hidden-md
-
hidden-lg
-
visible-xs
-
visible-sm
-
visible-md
-
visible-lg
按钮
在我们的 Foundation 示例中,我们默默地使用了与 按钮 相关的类,定义了形状、颜色和大小。主要的类是——惊喜,惊喜——button
。Bootstrap 有一个等效的类集,我们将在以下示例中使用。主要的 button
类是 btn
。
其他 UI 元素
Bootstrap 文档齐全;也有很多书籍可供阅读和学习。在本章的剩余部分,我们将回顾与 Foundation 相同的 UI 功能,如果适用的话。
缩略图
在 Bootstrap 中,有一个简单的类叫做 thumbnail
,你可以用它来生成带有或没有标题的格式良好的响应式缩略图。以下是一个代码片段的例子:
<a class="thumbnail" href="largeimgs/photo.jpg">
<img src="img/photo.jpg"></img>
<div><h6>Caption</h6></div>
</a>
这为您的缩略图和标题添加了良好的样式,但一旦您点击它,较大的图片将以浏览器决定的方式显示。
下拉菜单
这是之前相同的下拉菜单示例,但这次是用 Bootstrap 实现的。我相信一个打字错误已经进入了发布,现在无法纠正。作为一个歌剧爱好者,我在 Bootstrap 中找不到任何咏叹调;我认为他们只是想说是区域:
<div class="dropdown">
<button class="btn btn-primary btn-lg" type="button" id="calpeople" aria-expanded="false" aria-haspopup="true" data-toggle="dropdown">California people</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="calpeople">
<li>Ansel Adams</li>
<li>John Muir</li>
<li>Arnold Schwarzenegger</li>
</ul>
</div>
模态框 – Bootstrap 的弹出窗口
弹出窗口可以在网站的任何位置使用。Bootstrap 提供了他们所说的 模态框。这与在 Foundation 中实现的方式非常相似。以下是之前示例的 Bootstrapped 版本。注意关闭弹出窗口的图标是如何处理的:
<div class="row">
<button type="button" class="btn btn-primary btn-md"
data-toggle="modal" data-target="#revealid">
Click here
</button>
<div class="modal fade" id="revealid" role="dialog" >
<div class="modal-dialog modal-sm">
<div class="modal-content">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<p>Text of popup</p>
</div></div></div></div>
注意,modal-sm
类用于确定我们想要的模态框的大小。modal-content
部分可以进一步分为三个 <div>
元素:modal-header、modal-body 和 modal-footer。
结合下拉菜单和模态框
现在,我们将结合下拉菜单和模态框,以展示可以作为相册一部分的内容,如下所示:
<div class="row">
<h3> Modal with drop-down for details</h3>
<div class="col-xs-3 col-md-2">
<div class="thumbnail" data-target="#tiogalakepf" data-toggle="modal">
<img src="img/tiogalakesmall.jpg" alt="Tioga Lake">
<div class="caption">
<h6>Tioga Lake</h6>
</div>
</div></div>
<div id="tiogalakepf" class="modal" >
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-body thumbnail">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></ button>
<img src="img/tiogalake.jpg">
<h3 style="text-align:center">Tioga Lake in april of 1997</h3>
<a class="btn btn-sm btn-primary" data-toggle="dropdown" id="tiogadrop" >Details</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="tiogadrop">
<li>Hasselblad</li>
<li> Fuji Velvia</li>
<li> 40mm CF lens </li>
<li> f16 </li>
</ul></div>
</div></div></div></div>
折叠 – Bootstrap 的手风琴
Bootstrap 的 collapse
功能提供了与 Foundation 和其他框架中的手风琴相同的功能。这是我们重新设计示例的方式。几乎没有样式;因此,我们建议如果您使用 Bootstrap,请将其与 panel
小部件结合使用:
<div class="col-xs-12">
<h3>California People</h3>
<a href="#ansel" data-toggle="collapse">
<h5><i class="fa fa-caret-right green" ></i> Ansel Adams</h5></a>
<div class="collapse" id="ansel">
<p>I think of Ansel Adams as the best landscape photographer ever. His black and white photographs of Yosemite National Park and New Mexico are legendary </p>
</div>
<a href="#muir" data-toggle="collapse" >
<h5><i class="fa fa-caret-right green" ></i> John Muir</h5></a>
<div class="collapse" id="muir">
<p>I think of John Muir as the father of Yosemite National Park </p>
</div></div>
导航
Bootstrap 有一个你可能想要了解的导航栏组件。如果你喜欢 Foundation 顶部栏的外观,我鼓励你使用可选的Bootstrap 主题。
摘要
在本附录中,我们描述了 Bootstrap CSS / JavaScript 框架。与 Foundation 类似,它允许你创建一个以移动端为先、响应式设计的网站或应用程序,而无需编写任何响应式部分的代码。我们只需根据我们的需求自定义样式——当然是在一个单独的样式表中。