哈佛-CS50-计算机科学导论笔记-二-

news/2024/10/19 1:46:24/文章来源:https://www.cnblogs.com/apachecn/p/18475300

哈佛 CS50 计算机科学导论笔记(二)

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P2:L0- HTML与CSS语法 1 (web编程与HTML) - ShowMeAI - BV1gL411x7NY

[音乐]。

好的,欢迎大家来到使用Python和JavaScript的网页编程。我叫Brian U,在这个课程中,我们将深入研究网页应用的设计和实现。在讲座中,我们将有机会讨论和探索许多核心的想法、工具和语言。

对于现代网页编程,通过动手项目,你将有机会将这些想法付诸实践,设计多个你自己的网页应用,最终以一个你自己选择的项目作为结尾。在整个学期中,我们将涵盖网页编程领域的多个主题,从这里开始。

html5和css3是理解网页的两种核心语言。HTML是一种我们用来描述网页结构的语言,CSS是一种我们用来描述网页样式的语言,包括颜色、字体、布局和使网页看起来的间距。

完成这些之后,我们将关注获取一个工具,这个工具并不是特定于网页编程,但我们可以用它来进行版本控制,跟踪我们对网页程序所做的不同更改,并使我们能够在多个不同项目上工作。

同时也将网页应用的各个部分整合在一起。之后,我们将关注Python,这是我们将要探索的第一种主要语言,这是一种我们将用来构建网页应用的语言,具体来说我们将使用。

Python使用一个叫django的框架,django是用Python编程语言编写的网页编程框架,我们将用它来简化网页应用的设计和开发。特别是django使得设计与数据交互的网页应用变得简单,因此在这之后我们将继续。

接着我们将关注sequel,这是一种我们可以用来与数据库互动的语言,特别是研究django如何允许我们使用模型和迁移来与数据交互,以及如何让用户更方便地与数据互动。接下来,我们将关注主要内容的第二部分。

我们将在这门课上探索的编程语言是JavaScript,看看如何使用JavaScript在用户的网页浏览器中运行,使网页变得更加互动,特别是在用户界面的背景下研究现代用户界面。

接下来,我们将关注测试,看看持续集成(ICD)和持续交付,这些是我们可以使用的软件最佳实践工具,以确保我们能够设计和开发。

更有效地编写代码,特别是在测试中,确保我们在进行更改时,

对我们的代码进行更改时,确保我们没有破坏现有的网络应用程序部分,确保我们有一整套测试,可以用来确保我们的网络应用程序始终如预期那样运行,最后我们将关注可扩展性和互联网安全性。

随着我们的网络应用程序变得越来越大,越来越多的不同用户开始使用我们的网络应用程序,我们该如何在这些人之间进行负载均衡,以及我们需要对数据库做出哪些更改,以确保大量用户能够同时连接到我们的网络应用程序?

我们将探讨设计网络应用程序的安全隐患。如果我们不小心,敌对者可能会做些什么,我们应该如何主动设计我们的网络应用程序,以确保其安全,但今天我们将以HTML和CSS开始我们的讨论,这两种语言是基础。

理解网页以及网页浏览器如何显示这些网页,我们将从HTML(超文本标记语言)开始,这是一个我们可以用来描述网页结构的语言,网页中的所有按钮、文本、表单以及其他部分。

用户最终看到并与之交互的我们第一个HTML页面将类似于这个,它将是我们编写的基于文本的代码,然后像Safari、Chrome或Firefox这样的网页浏览器能够查看、解析、理解并显示给用户,所以让我们来看一下这个页面。

一次一行,逐步理解其工作原理,即使你不完全理解语法的所有细微差别,也许会有几件事情引起你的注意,你可能会注意到“标题”这个词,这可能反映了网页的标题,例如,在这个例子中。

似乎是“你好”这个词,然后进一步往下,我们看到网页主体似乎包含“你好,世界”这几个字。那么这个网页实际上会是什么样子呢?我们来看一下,打开一个文本编辑器,你可以使用任何你想要的文本编辑器。

在这门课程中,我将使用微软的Visual Studio Code,并打开一个新文件,我将称之为hello.html,在hello.html中,我将编写刚刚看到的相同HTML,并将在适当的时候解释这些行,但请记住,我们有一个标题为。

类似于hello的内容,以及页面的主体,我们在这里说了类似hello world的内容,所以这是我们的第一个HTML页面,如果我继续进行。

打开HTML页面,例如打开hello HTML,在网页浏览器中,我会看到页面主体中有hello world的字样,如果你注意到我网页浏览器顶部的标题栏,我看到有我们页面内容的标题。

这个页面在这种情况下仅包含单词hello,所以这是我们能够开发的第一个网页,仅使用HTML,现在让我们更详细地探索这个程序是如何工作的。这里再次是我们刚刚查看的网页,这一行doctype HTML是。

我们可以称之为文档类型声明,这是一种告诉网页浏览器我们在这个特定网页上使用哪个版本的HTML,因为根据HTML的版本,网页浏览器可能会想要显示不同的信息,或者可能需要以不同的方式解析页面。

HTML的不同版本略有不同的方式来指示该版本,但这一行doctype HTML是我们表示该HTML页面是用HTML5编写的方式,这是HTML的最新版本,之后我们的HTML页面结构是由一系列嵌套的HTML元素构成的,每个HTML元素描述。

页面上的某些内容可能会有嵌套在其他元素中的元素,每个元素都由我们所称的HTML标签表示,这些标签用尖括号括起来,这里我们看到HTML标签的开始,这意味着这是HTML的开始。

下方的这条slash HTML意味着这是页面HTML内容的结束,而中间则是页面的实际HTML内容,这可能包括HTML元素。你可能还会注意到在这个HTML标签中,我们指定了一个HTML属性。

我们在这里提供了一些关于这个标签的附加信息。我们为其提供了一个Lang或语言属性,该属性等于en或英语,这只是告诉网页浏览器或任何查看此页面HTML的人,这个页面是用一种语言编写的。

它是英语,这对搜索引擎来说是有帮助的,例如,当它们在浏览许多不同的网页时,试图弄清楚每个网页使用的是什么语言,我们可以告诉搜索引擎或任何其他查看页面的人,这个页面是用英语编写的。

在页面的 HTML 主体中,我们有许多不同的元素来描述我们希望在此页面上展示的内容,从网页的头部分开始,它描述的是不在网页主体中的内容,用户所看到的网页部分,以及关于网页的其他信息。

这对于网页浏览器来说是有用的信息,例如,网页浏览器需要知道的一个重要信息是网页的标题。在这里我们再次看到一个标题标签,用词“title”表示,包围在尖括号中,接着是以斜杠结束的标题标签,表明标题标签的结束。

在两个标题标签之间是“hello”这个词,这意味着这个页面的标题应该是“hello”,这就是我们在页面头部所需的全部信息,之后我们会添加更多的信息,但现在网页所需知道的仅仅是它有一个标题。

标题是“hello”这个词,接下来是页面的主体,再次通过主体标签来表示,并以带有斜杠的主体标签结束,表示这是页面主体的结束。而页面的主体部分就是用户可以看到的可见部分,我们希望在 HTML 页面主体内放置什么呢?

目前页面的主体中我们只想要文本“hello world”,而这是当有人访问这个网页时要显示的信息,这就是这个 HTML 页面所包含的全部内容,我们在头部指定了页面的标题为“hello”,而在主体内我们说明。

页面应该显示“hello world”这几个字。如果你想在视觉上考虑所有这些 HTML 元素的结构,有时可以帮助思考 HTML 页面在树状结构中的形式,这就是我们称之为文档对象模型(DOM)。例如,这里就是这个网页的 DOM。

页面可能实际看起来像这里左边是我们刚才看到的 HTML 内容,右边是 DOM,即文档对象模型,树状结构描述了所有这些 HTML 元素是如何彼此关联的,因此我们从 HTML 元素开始。

所谓的父元素有两个子元素,其中包含一个头元素和一个主体元素。正如我们在这里看到的,我们处于 HTML 结构中,拥有一个头部分和一个主体部分,而我们在 HTML 文本中包含的缩进并不是严格必要的,网页浏览器并不关心这些。

这个缩进虽然不是必需的,但对于阅读页面的人来说,看到缩进可以帮助他们视觉上理解头部是在 HTML 元素内部,主体也在 HTML 元素内部。因此在头元素内我们有一个标题元素,标题元素内部仅包含。

文本“hello”,同样在body元素内我们也有一些文本,文本“hello world”。因此,从这种结构来思考HTML和HTML文档,有助于理解哪些HTML元素包含在其他HTML元素内,这将使我们更容易。

稍后对这些页面进行推理,尤其是当我们过渡到JavaScript的世界时,JavaScript将使其变得更强大,并让我们能够实际修改部分内容。但我们会在适当的时候深入探讨这一点,现在先看看一些。

其他常见的HTML标签和我们可能在网页中交互的HTML元素,我们将首先考虑HTML标题,例如页面顶部的大横幅,一些描述页面内容的标题。因此,我会在文本编辑器中创建一个新的文件。

我将其命名为headings.html,这个页面的结构与我们之前看到的页面非常相似,所以我将开始使用hello HTML文本并将其粘贴在这里,我会将页面标题改为head,而在这个页面的body内现在我。

想要稍微不同一些的东西,我将在页面的body内部使用h1元素,说明这是一个标题。例如,h1是一个我可以用来创建大标题的标签。!

我页面顶部的标题,例如,如果我打开!

headings.html 可能会看到类似这样的内容:页面顶部有一个大标题,写着“这是一个”,h1,其中H代表标题,1代表最大的可能标题,实际上HTML提供了许多不同的标签,我们可以用来创建不同大小的标题。

所以例如,我也可以说h2,里面我说这是一个较小的标题,如果h1是!

最大的标题h2是第二大的标题,因此如果我加载这个页面,例如,我现在看到最顶部的h1,这是大标题,然后在下面我看到“这是一个较小的标题”。!

h2,结果发现还有h3、h4。!

h5一直到h6是最小的标题,因此如果我加载!

现在我有一个大标题,一个较小的标题,还有一个最小的标题。因此,我们通常可以使用这些h1、h2、h3标签来视觉上组织页面内的文本,如果我想要页面的标题,但同时也希望为各种不同的部分和子部分提供标题。

该页面中的内容也是如此,所以那些是。标题,现在我们也来看看一些我们可能想要添加到网页上的其他元素。我们看到的不仅仅是标题,不仅是文本,还有。可能也会看到列表,例如,如果你曾经在网页上使用待办事项列表程序,你可能会看到一个。

需要做的事情或其他网页可能会显示信息列表。事实证明,HTML有两种基本类型的列表,我们有。用于特定顺序的有序列表,比如项目编号一。项目编号二,项目编号三,而我们则有无序列表。

没有任何特定顺序,所以只是项目符号项目符号项目符号。作为例子,两者都非常容易使用。我将创建一个新文件。我们称之为lists dot html,再次在HTML列表中,我将复制来自hello dot HTML的相同。结构,我们将再次使用doctype HTML。

表示HTML的版本大部分,标题是一样的,我只是要。将标题从hello更改为lists,然后我们将替换页面的主体。以显示一些不同的信息,所以让我先展示。一个有序列表可能是什么样子,带有数字1 2 3。

有序列表作为HTML标签就是哦,L o L代表有序列表,所以我可以添加一个。标签说o L,现在在我的哦 L元素内部,我需要为每个列表项添加一个新元素。我们将列表项缩写为。Li,所以HTML中的Li标签是我们。用来指定项目的标签。

在HTML列表内部,所以在这里,例如我可以说Li,然后第一个。项目,然后我可以做同样的事情。

Li第二个项目,然后再次Li第三个项目,所以我在这里有一些。元素,然后元素嵌套在其他元素内,我有一个有序列表。元素,其中有三个其他的HTML元素,三个列表项,分别指示每一个。单独的项目,这些项目都在我的HTML列表内。

可以通过打开lists HTML来打开它,这就是我所看到的,我看到一个有序。列表,其中有项目编号一,第一个项目,第二个项目,第三个项目,注意到我。实际上不需要在HTML中指定数字一、数字二和数字三,当我的。网页浏览器读取时,应该是一个。

在我的网页浏览器Chrome中,这个例子只是为我添加了那些数字。因为它知道有序列表意味着什么,也知道如何处理HTML。

我写的内容并以我意图的方式展示给用户。现在除了所有有数字1 2 3的有序列表,我们还有。无序列表,只是项目符号的信息,所以我可以在上面。再添加一些内容到这个HTML页面,我可以说这是一个无序列表。

正如我们使用O标签表示有序列表一样,O表示有序列表,我们同样可以在HTML中使用UL标签创建无序列表。所以这里我们将添加一个UL标签,我的文本编辑器这里会自动添加关闭标签/UL,表示结束。

无序列表,许多文本编辑器现在会这样做,只是为了让你这个程序员记得添加,现在在这个无序列表中,我们又将有一些列表项,也使用li标签,这里是一个项目,这里是另一个项目,这里还有一个。

另一个项目,如果我继续刷新。

现在我仍然在list.html中,我现在看到在我的有序列表顶部,有一个无序列表,其中每个项目不是编号1、2、3,而是仅用项目符号标记,项目符号、项目符号、项目符号,其中每个这些项目符号以及这些编号项目都是一个列表项元素。

在Li中,希望现在我们可以看到,当我们开始探索这些不同的HTML标签,以及将HTML标签嵌套在彼此内部时,我们能够创建越来越多有趣的网页。因此,让我们现在探索一下我们可以使用其他HTML元素创建的其他类型网页。

列表中你可能想象的事情之一是,网页上重要的事情不仅是显示文本,还包括显示其他类型的媒体,比如图像。那么,我们该如何做到这一点呢?我可以,例如,回到我的文本编辑器,创建一个新的文件,我将称之为。

image.html将包含一些显示图像的代码。我将进入hello.html,并将这段文本复制到页面中,再次将标题改为图像,现在在正文中,我将添加一个新的标签,称为图像,图像标签有几个必需属性,请记住。

属性就是我们之前看到的内容,比如在页面顶部添加Lang equals en以指示该网页是用英语编写的。例如,图片标签有几个我需要添加的必需属性,尤其是在我页面上显示图像时,我需要指定。

实际上我想显示的图像,例如,我可能会指定图像SRC,它是source的缩写,将等于我实际想在此页面上显示的图像。碰巧的是,在我的文件夹中,包含image.html,我有一张名为cat.jpg的图像,所以我将指定cat.jpg作为。

我想显示的图像的文件名,结果是图像除了文件名或我想显示的任何图像的链接外,还需要提供一些替代文本,即图像的文本表示,因为。

在某些情况下,一些网页浏览器可能无法正确呈现图像。你可以想象,如果图像呈现时出错,或者某人使用较慢的互联网连接,或某人正在使用屏幕阅读器,因此无法实际看到图像,我们希望有一些基于文本的表示。

图像也是如此,因此我会提供一些替代文本,以便在某种情况下无法显示图像时,可以用来替代图像,而我在这种情况下使用的替代文本仅仅是“cat”这个词,这就是我需要的全部。请特别注意这里有。

这个图像标签与我们之前看到的其他标签有一点不同,因为它没有关闭标签,意思是主体有开始和结束,顺序列表有开始和结束。

列表中有列表项之间,这对于图像来说并没有真正意义。例如,图像的开始和结束以及一些内容在中间,因为图像只是一个单独的HTML元素,里面并不能有任何东西,所以从这个意义上说我们不需要。

实际上不需要关闭图像标签,图像标签是自闭合的,它是自己的开始和结束,因此我们可以说我们想要在这里放一个图像,那就是cat jpg,替代文本仅仅是“cat”这个词。因此现在如果我打开image.html,会看到加载的是一张相当大的猫的照片。

我可以滚动查看整个图像,当然这张猫的照片可能比我想要的要大,我在用户的业务网页上,可能不希望他们需要向右滚动才能看到整只猫,因此我实际上可以添加额外的HTML属性。

修改我正在显示的图像的大小,稍后我们将看到我们也可以使用CSS来做类似的事情,但现在我可以添加一个额外的属性,并说让我给这个图像标签另一个属性,但在这种情况下我只会称之为宽度,并且我会说宽度是。

将等于300,因为我会。

例如,现在如果我刷新这个页面,我会看到同一只猫的图像出现,除了现在它的显示为!

宽度正好是300像素,所以我可以添加额外的属性,额外的信息来控制HTML元素在这种情况下的显示方式。我想控制它的宽度,并且它会自动缩小高度,以确保图像的比例也合适。现在在互联网上,我希望这个图像的宽度是300像素。

不仅仅是在单一页面上展示信息,页面链接到其他页面也是很常见的。实际上,这是互联网的一个主要价值之一,即通过这些链接从一个页面跳转到另一个页面。因此,我们可能合理地想在我们的页面上添加一些链接,这样如果。

你点击某个东西时,你会被带到另一个页面。因此,让我们看一个例子,我将基于 hello.dot HTML 创建一个新文件,并且我将添加 Lang 等于 English,以确保万无一失,并将这个新文件命名为 link.dot HTML。在这里,我们将练习构建一些链接。

在我们的 HTML 页面中,我将再次复制 hello.dot HTML 的内容,调用这个链接。为了创建一个链接,我将使用一个叫做 a 标签的标签,它是锚点标签的缩写,而 a 标签有一个重要属性,称为 H.ref,即超链接引用,它将指定我想要的页面。

链接到的地方,例如,如果我希望当用户点击这个链接时去 Google.com,那么我会将这个标签的 href 属性设置为 HTTP://google.com。例如,然后在 a 标签内部,我会指定我想要显示的文本,用户应该看到什么文本。

这样,当用户点击那个文本时,他们会被带到网页。在这种情况下,我只是说,比如点击这里!

现在如果我打开 link.html,这就是用户所看到的,他们看到一个蓝色链接,上面写着点击这里,当用户点击那个链接时,他们会被带到 HTTP://Google.com。事实证明,我们不仅可以使用这个 href 属性链接到不同的。

整个网站我们可以链接到同一网站上的不同页面。例如,如果我想链接到我刚刚设计的那个猫页面,而不是链接到 Google.com。

只是链接到 image.dot HTML,现在如果我保存并刷新,或者再次打开 link.html,现在我看到一个点击这里的链接,当我点击这里时,我就被带到了那个页面。

cat image.dot HTML 恰好有之前那只猫的图片。通过使用这些锚点标签和 href 属性,我们能够连接多个页面,以便如果我们有一个有许多不同网页的网站,我们可以通过使用这些不同的组合将它们全部连接起来。

不同的链接,现在我们已经看到了图像、链接和列表,我们还可以在网页上添加哪些 HTML 元素呢?我们可能想要添加的一个东西是表格,就是以其他方式展示信息,所以让我们去创建一个表格,看看我们可以使用哪些 HTML 元素。

所以我将回到我的文本编辑器,创建一个名为table。dot HTML的新文件,使用相同的起始HTML,我们将这页面标题定为我们的表格。在这个页面的主体内部,现在有许多不同的HTML元素。我们需要创建一个表格,因为你可能想象得到。

表格实际上是由多个部分组成,我们有一个大表格,但每个。表格实际上只是一个独立表格行的序列,而每一行。实际上只是一个独立数据单元的序列,在那个。表格中,这就是我们想象的一个由个别行组成的表格。

每一行由个别单元组成,这正是我们将用来在。表格标签中表示这个表格的方式,它将代表整个表格,但在。表格内部我们可能有不同的部分,我们。可能有表格的标题,还有表格的主体,因此在。

为了表示这一点,我将添加T头,代表表格的标题,表格顶部的部分。可能指示每列的意义,例如,我想要什么列。好吧,让我们添加一些表格标题,我可以用。

th标签代表标题,也许我想在。这个网页上展示关于各种不同海洋的信息。比如我有一列用于海洋,另一列是关于。那个海洋的平均深度的表格标题,还有一个表格标题是。那个海洋的最大深度。

这将是该表格的第一行,表格的标题,但除了。表格的标题,我们还有表格的主体。因此在茶头下面,我将包括茶主体,作为表格的主要部分,所有数据将在这里。

由表格的个别行组成,因此我可能有一个TR,这里代表。表格行,在这个表格行内部,我们将添加一些。独立的数据点。所以在我的表格行中,我将有一个表格数据点或TD,表示太平洋。

例如,另一个表格数据是四千二百八十米,还有一个是。太平洋的最大深度,即一万九千一百一十米。实际上,这三个表头也在页面顶部,海洋的平均深度在。

最大深度时,这些部分也应该可能在各自的行中。因为表格的第一部分也是一行,所以我将。添加一个TR,代表表格行,里面放这些标题。我将再添加一行,这样我们可以看看这看起来是什么样的。

然后我们将查看页面,然后回到这段代码,我将添加。

大西洋的平均深度为三千六百四十六米,最大深度为八十米。

米,所以当我打开'table,dot html'时,现在我会看到一个表格,这个数据的表现形式不再是。一个接一个的,而是以表格的形式结构化了。可以说,现在没有任何边框,我可能还可以添加一些颜色。

空间布局使其看起来更美观,但我看到有三列。海洋的平均深度和最大深度,其中第一行被称为表头,表格的顶部,定义了表中所有列的含义,表头中有一个单一的表格行。

三个表格数据单元格:海洋的平均深度和最大深度,然后在。这个以粗体表示的表头下,是表格的主体或者说是茶主体元素。里面有两行,一行代表太平洋,一行代表大西洋,然后每一行中都有数据单元格。

行中代表每一个单独的单元格,这些单元格位于。这个表格中,因此,这就是那个页面,最终的样子。让我们再看一眼HTML,以便了解这些标签是如何相互作用的,不需要记住所有这些标签。

随着你慢慢开始设计HTML页面,你会变得越来越熟悉。可供你使用的HTML标签,当然,这些HTML标签都很容易查找,如果你需要查找的话,查找如何在HTML中创建表格非常有用。

你将能够看到生成所需表格的各种不同标签是什么。但再次回顾一下,我们有一个表格元素,里面有两个子元素:茶头和茶。主体,每个里面都有一个或多个表格行,使用TR表示。

在每个表格中都有三个数据单元格,使用TD表示。因此,利用这些嵌套标签,元素嵌套在其他元素内部。我们能够构建出比简单的项目符号列表更复杂的内容,构建出一个包含信息的完整表格。

最终,我们的网页应该不仅仅是展示。信息,而是让用户以某种方式与这些信息进行互动。举例来说,你可能想象,在谷歌的主页上,它并不是一成不变的,而是有一个可以输入内容的字段,任何时候用户都可以。

用户向网页提供输入,我们通常称之为论坛或某个用户可以填写表单以向网页提供信息的地方。那么现在我们来看看,如何使用 HTML 来创建一个将显示某些信息的表单。我将继续创建。

再创建一个新的页面,叫做表单 HTML,使用之前相同的 HTML,命名该页面为表单。在这个页面的主体内部,现在假设我想创建一个表单,给用户提供填写全名的机会。例如,我该怎么做呢?首先我需要一个表单元素,也就是某种方式。

这里所说的将是一种表单,而现在在这个表单内部,表单的不同部分是什么呢?实际上,你可以想象这个表单有两个部分,一个是供用户实际输入他们的名字的地方,他们可能还需要某种方式来提交表单,比如一个按钮。

说到提交,这样他们就可以点击那个按钮来提交表单。那么我们该怎么做呢?为了创建一个输入字段,我们将使用一个输入标签,在这种情况下,它的类型将是文本。用户可能会以多种方式向论坛提供输入,他们可能会输入文本。

用户可能会从下拉菜单中选择,或者可能会选择单选按钮选项,或者他们可能会通过点击按钮提供输入。例如在这种情况下,我们特别使用类型属性来说明,当用户以这种方式提供输入时,他们提供的输入类型将是。

可能是某种文本,我们可以提供一个占位符,即用户第一次查看页面时,输入字段内的默认文本。例如,占位符可以是全名,这样用户就知道他们应该在这个地方,也就是占位符中输入什么。

他们的全名,最后我们将给这个输入字段命名。虽然用户在访问页面时看不到这个名字,但每当你提交一个表单,我们在网络应用程序中接收到该表单时,这就是我们稍后会探讨的内容,我们需要某种方式。

知道哪个输入字段对应哪个值,因此我们将为每个输入字段命名,这样稍后我们就能引用它们。现在由于用户在这里输入他们的全名,我们可以简单地命名为全名,或者更简洁地说成名字。

这个输入字段的名称之后,我们有一个输入字段,用户可以在其中输入他们的名字,现在我们需要某种方式让用户能够提交这个表单,所以我们可能会说一些类似于!

输入类型等于提交,表示这是用户提交表单的一种方式。类型等于提交意味着这是他们提交表单的方式,当!

他们现在完成了,如果我打开HTML,这就是我们最终在加载这个HTML时会看到的页面,这整页只包含一个单独的HTML表单,但这个HTML表单包含两个部分,第一部分是这个输入元素,允许用户输入。

他们的全名,他们在这个输入字段中输入他们的全名,当他们完成时,可以点击提交按钮以表示他们希望提交这个表单,当然,现在这个表单在我们输入名字并点击提交时并不会做任何事情,因为我们还没有添加。

处理这个表单的逻辑,但稍后当我们过渡到使用Python构建网页应用程序的世界时,我们将看到如何设计一个表单,以便在用户提交后,我们将信息保存到数据库或向用户显示某种结果,所有这些都是通过构建的力量实现的。

这些网页应用程序并将它们连接到这些HTML表单,HTML表单实际上可以变得相当复杂,我们将查看另一个例子,例如让我打开forms1.html,这是我提前构建的一个表单,展示用户的其他多种输入方式。

可以作为输入提供信息的HTML表单,这里我们看到一个输入,其类型为文本,意味着我们希望用户以文本形式输入他们的名字,但你可能还想象到,如果用户登录一个网站,他们可能除了输入基于文本的名字、用户名或电子邮件外,还会提供一个。

密码,通常如果你在一个网站上输入过密码,密码字符不会全部以实际字符显示。出于安全原因,它们通常只会以小点的形式出现在屏幕上,隐藏它们所代表的实际字符,在HTML中我们可以。

我们可以非常简单地做到这一点,只需将这个输入的类型设为密码,如果他们正在输入密码,我们的网页浏览器会知道不实际显示这些字符,除了基于文本的输入,我们还有单选按钮输入,正如我刚才提到的,这里我们有。

多个不同的单选输入,用户可以从多个选项中选择,例如选择他们喜欢的颜色,最后,再来看一个HTML5的其他附加功能,实际上,HTML5的新功能是我们。

可能会称之为数据列表,用户可以从多个不同的选项中选择,但我们希望能很快地根据这些选项进行筛选或自动完成,因此如果用户需要选择他们来自哪个国家,例如,我们可能会有一个输入字段,并指定它是。

这将与一个称为国家的列表关联,然后在下面,我有一个数据列表元素,其思想是这里我要指定的都是我们可以选择的国家选项,每一个都在一个选项元素内部,其值是他们可以选择的国家。

而我们有所有的国家。

列出在这些选项元素中的世界,因此这里的输入将允许我从所有这些可能的选项中选择一个选项,现在如果我打开 form1.html,这就是这个表单的最终样子,我可以在名称字段内部再次看到那个单词名称,因为它是占位符。

我可以在这里输入我的名字,而在密码字段中,我输入的任何内容都将显示为小点,而不是实际字符,因为该输入字段的类型是密码,而不是文本类型。在最爱颜色中,我现在可以选择不同的选项。

最爱颜色选项以单选按钮格式,我可以从多个选项中选择,最后在这个国家下拉菜单中,当我点击它时,我现在能够看到所有的国家,但当我开始输入字母时,比如说你,它会过滤到我实际关心的选项,所以如果我输入。

最终我看到足够的字母。

美国,我也可以点击那个选项,因此 HTML5 内置了这些额外的功能,使得实现像文本字段这样的东西变得容易,它会根据你提供的文本进行自动补全,你只需指定它在这个数据列表内部,然后提供所有的。

可能的值,然后 HTML 以及你的网页浏览器将处理将这些信息渲染为你期望显示的方式,因此这些只是我们最终可以通过使用这些各种不同元素来创建的一些可能的 HTML 元素,它们彼此嵌套。

还有其他 HTML 元素也存在。你可以开始探索,但它们都遵循非常相似的模式,我们将有一些标签可能需要一些属性以及有关 HTML 的附加信息,以便为网页浏览器提供上下文。

元素应该被显示,也许那个元素需要有一个特定的图像来源,或者它需要一个链接以便连接到某个地方或其他信息,然后在那个元素内部,你可能会嵌套其他元素,这样你的表格就有行,而在这些行内部。

行,我们还有其他单元格,你可能会。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P20:L6- web用户接口与交互 3 (React) - ShowMeAI - BV1gL411x7NY

应用程序同时运行,你可以想象,随着网页变得更加复杂,以及你希望使其更互动和动态,必然会需要大量的JavaScript代码,以保持一切同步,确保所有元素在适当的时候被更新。

以此类推,正因如此,近年来许多JavaScript都转向了一些JavaScript库或框架,这些库或框架允许更高效、更有效地创建更互动、更反应迅速的用户界面。

其中一个流行的框架是React,React是一个JavaScript库,它使我们能够设计非常互动的用户界面,其中网页内容会根据某种基础状态自动更新。现在我们来简单了解一下React,以便感受一下。

这样的框架实际上可以发挥作用,并且可以帮助我们设计一些互动性强且对用户有用的界面。React最终基于声明式编程的理念,这是一种不同于你可能熟悉的编程类型的特定编程风格。

更经典的编程风格,如命令式编程。在命令式编程中,你通常会给计算机发出命令,告诉计算机该做什么。例如,如果我们之前有那个计数器程序,并且想要在视图中将计数器从一个数字更新到另一个数字,比如用户的HTML。

看到我们可能会包括类似的内容,比如一个仅包含数字零的标题。在其中,命令式编程的逻辑会采取这样的形式:好吧,首先,使用document.querySelector来获取那个h1标签。获取它的innerHTML,parseInt将字符串转换为整数。

我们可以将其保存在一个名为num的变量中,之后如果我想增加它,我只需取这个变量num并加1。num += 1,然后如果我想更新这个标题,以便将零替换为一,例如,那么我需要说,document

querySelector获取h1,将innerHTML设置为那个数字,例如为了表示好吧,num现在是1,继续在视图中替换它,但这是一段相当多的代码,仅仅为了做一些简单的事情,比如将数字增加1。原因是我们必须非常明确地说明指令。

我们给浏览器的指令是,首先抓取 h1,找出里面的数字,给这个数字加一,然后替换这个标签中的内容。声明式编程将允许我们只是描述页面上应该显示的状态及其形式。

在声明式编程中,我们认为,类似于我们将要编写的 HTML 代码。我们只需要说一些像 h1 的东西,然后用大括号包起来。num 表示在这里填写数字,这就是 React 语法的样子。如果我们想让这个数字加一,那么就加一。

我们只需要说 num 加等于一,将数字加一。这样做的效果是,因为我们在这个标题中声明了它应该是数字的值,所以当我们增加数字的值时,React 实际上会更新视图,以便数字更新。

这也是 React 赋予我们的某些功能,React 允许我们将应用程序分割成许多不同的组件,其中一个组件就像是跟踪某种计数的东西,以及一个可能操控它的按钮,然后基于此创建组件。

一些基础状态,一些表示应用程序状态的变量。像当前数字这样的东西,然后我们可以操作这个状态。当我们操纵状态时,这将影响用户实际看到的内容,React 将处理这个过程。

更新用户界面的方式有很多,但最简单的可能就是在我们的网页中包含这三个 JavaScript 包。因此,我们首先将包含 React 本身,这是一个允许我们...

定义这些组件及其行为,React DOM 是一个特殊的包。它将允许我们将 React 组件插入到 DOM 中,文档对象模型表示整个页面的结构,最后 Babel 是我们将要使用的一个包。

我们将使用它来将代码从一种语言翻译成另一种语言。结果表明,当我们编写 React 代码时,我们实际上并不是在写 JavaScript,而是在写一个被称为 JSX 的 JavaScript 扩展,JSX 看起来很像 JavaScript。

但它还有一些额外的功能,特别是它能够有效地。允许我们在 JavaScript 代码中表示 HTML,以一种更易读的方式。这对我们来说是方便的,另一方面,浏览器并不能自动理解 JSX,因此我们将使用一个工具,比如 Babel 来转换。

了解这种情况的最好方法就是看看它的实际运行效果。所以我将继续创建几个 React 应用程序,以便感受一下你如何在自己的应用程序中使用 React。所以让我们先来看看 react.html。

这里是一个 HTML 页面的开始,在 head 部分你会注意到我已经包含了这三个 script 标签,而这些 script 标签的作用就是引入我们刚才提到的三种 JavaScript 库。我给页面设置了一个标题,简单称为 react,现在让我们开始填充主体部分。

在这个网页中,我将首先添加一个 div,我会给它一个 ID,叫它 app。但我可以叫它任何名字,这就是我们的应用程序将要放置的地方。现在我只会让它保持为空,由 React 来填充这个 div,包含我们的用户界面内容。现在在这个 div 下,我将开始写一些 JavaScript。

但请记住,我不是在写 JavaScript,而是在写 JSX,这是 JavaScript 的一种扩展。因此,在这种情况下,我需要添加一个额外的属性 type 等于 text/babel,这一切只是告诉我的浏览器,它需要将这个 JSX 代码转换成浏览器实际上能够理解的 JavaScript 代码。

要理解这一点,首先在实际开发一个真正的应用程序之前,你需要提前进行这种翻译,在部署应用程序之前,但这里我们只是实时进行翻译。所以我们所有的 React 应用程序都将由组件组成。

组件只是我网络应用程序用户界面的一部分。为了描述一个组件,我可以写一个 JavaScript 函数,所以我将创建一个名为 app 的函数,它将代表这个 app 组件,app 组件内部将返回某些内容。

它将返回的内容是应该出现在该组件内部的内容,这个 app 组件实际上可以是一个简单的 div,上面写着“你好”。这就是 JSX 的强大之处,我可以在我的 JavaScript 代码中写 HTML 类似的语法,而 JSX 能够理解它。因此,这个函数会将 JSX 代码转换成我们网络浏览器最终能够理解的普通 JavaScript。

这里有一个名为 app 的函数,代表一个 React 组件。当这个组件渲染到我的网页上时,它将显示“你好”。所以我在我的 JavaScript 中需要的最后一行,就是实际将这个组件渲染到页面中,为此我将使用 react dom.dot render。

这个函数的第一个参数是reactdom.render,我想渲染哪个组件。组件是我刚创建的应用组件,所以我会再次使用这种类似HTML的语法,然后第二个参数是我希望在页面的哪个位置渲染这个组件,我想在第10行渲染这个组件。

我有一个id为app的div,所以找到那个特定的div,为此我可以只说document.hash app,表示找到一个id为app的元素,这就是我想渲染这个应用组件的地方,所以我首先创建了这个空div。然后我定义了这个表示React组件的函数,之后我们将要。

将该组件渲染到HTML页面本身,所以现在如果我们打开浏览器,查看这个页面的实际样子,我会把文本稍微放大一些。你会看到我们确实看到了“你好”这个词,如果我做出更改。

组件,刷新页面,它也会改变页面。

所以如果组件改为显示“你好,世界”,那么我刷新页面。

现在页面也显示“你好,世界”,因为这是JavaScript,我可以向函数中添加JavaScript代码,就像我可以对JavaScript中的任何函数做的那样,假设我有一些变量,比如我们创建一个变量x,它等于1,还有一个变量y,它等于2。在这个div中,而不是仅仅渲染一些文本,我可以使用花括号。

要插入某个JavaScript表达式的值,我可以插入x + y的值。例如,现在通过在这些花括号中包含x + y,JavaScript会计算出结果。

x + y并将其显示在div内部,所以现在。

当我刷新页面时,你会看到页面上只显示三个,例如。所以这就是React的基础,我们创建这些组件,然后利用JavaScript的力量渲染这些组件,但React开始变得更强大的地方在于我们可以重用组件,组件的整体概念就是这样。

表示用户界面的某个部分,我可以在界面的多个部分重用同一个组件。例如,假设在我的应用组件内部,我要渲染一个包含三个标题的div,每个标题都说“你好”,所以有一个标题,还有另一个,我们也会添加第三个标题。

所以我有一个包含三个标题的 div,在其中,我们可以看到每个标题的样子。它们都说“你好”,但这里有些重复,我不得不使用这个 h1 标签三次。全部都是为了在页面上创建完全相同的 UI 元素,这是一个我可以创建一个单独组件并重复使用该组件,而不是必须重复自己的情况。

多次,我该如何做到这一点呢?好吧,记住在 JavaScript 中我们可以写一个函数来表示一个 React 组件,所以我将在这里添加另一个函数,我称这个函数为 hello,因为它将表示这个 hello 组件,而这个 hello 函数也将返回某些东西,它将返回一个只是说 hello 的 h1 标题。

因此,现在在我的应用组件中,而不是渲染三个单独的 h1,我可以稍微简化一下,只说,hello,这里我说继续渲染一个 hello 组件,我们将渲染第二个和第三个组件。每次我渲染一个 hello 组件,它都会显示。

这个标题只是说“你好”,所以我刷新页面,什么都没有。

更改后我仍然看到三个标题,每个都说“你好”,因为在我的应用组件内部,我渲染这个 hello 组件三次,每次都会显示这个 h1。但是当组件变得更强大时,并不是总是每次,而是当我们可以用属性参数化这些组件时。

或者正如 React 简化它们的 props 简称,表示属性,那么这意味着什么呢?我们看到 HTML 元素可以接受属性,同样,React 组件也可以接受属性。也许我不仅仅想说“你好”,而是想对某人说“你好”,比如对哈利或罗恩或赫敏,所以我可以说 hello name 等于哈利。

使用类似 HTML 属性的语法,那么在这里说 hello name 等于罗恩。最后 hello name 等于赫敏,现在我的 hello 组件正在接受这个属性,这个名为 name 的属性对于这三个组件都是不同的。因此在这个 hello 函数内部,我希望 hello 函数能够利用这些属性。

利用这些属性的优势,所以我将给 hello 函数添加一个参数,这个参数通常称为 props。现在我不再只是说 hello,而是要说 hello,逗号,并且记得插入一个 JavaScript 值,我使用花括号,在这些花括号内部,我可以说 props。

点,然后无论属性的名称是什么,在这个案例中,属性的名称就是 name,所以我可以说 props.dot name,表示无论 name 属性是什么,请继续将其插入到 hello 组件内部。因此,hello 组件将会说 hello,逗号。

然后是某人的名字,这样我就可以保存,刷新页面,现在我看到“你好”。

“哈利,您好,罗恩,您好,赫敏”,这三个不同的组件,每个组件仍然只是这个问候组件,但我们用不同的props进行渲染,一次是哈利的名字,一次是罗恩的名字,还有一次是赫敏的名字。因此,在这里组件可以通过传递不同的props开始变得有些不同。

我们可以决定这个组件最终将如何渲染。但让我们对此进行一些添加,开始在我们的react组件中添加状态。状态意味着我们想要存储在组件内部的任何数据。为了这个目的,让我们尝试重新创建我们首次介绍时创建的计数器应用程序。

JavaScript中,我们实际上只是在计数,从0到1,2,3,4等。因此,为了实现这个功能,让我们创建一个新文件,我将新文件命名为counter.html。我们可以通过将react.html的内容复制到我们的counter.html文件中开始,我们仍然会使用相同的script标签,并且我们仍然可以有一个app组件。

但是这个应用程序内部会有什么不同,我将把页面的标题改为“计数”,而不是“react”,那么应用组件内部应该放什么呢?如果我们要进行计数,我们需要一个div来显示,比如一开始是零。我们还需要一个按钮,这个按钮将只是“计数”,这将是它的标签。

按钮。

因此,一个仅显示零的div和一个显示“计数”的按钮,现在如果我打开counter.html,我会放大一点,你可以看到我这里有一个数字零和一个按钮,上面写着“计数”,当然,现在点击这个按钮没有任何反应,因为我还没有写任何JavaScript代码来说明应该发生什么。

当这个按钮被点击时,但在我们到达那一步之前,让我们稍微修改一下这个程序,现在我已经将数字零直接写入了div本身,但它不总是会是零,最终当我开始通过按计数按钮来计数时,这个数字将会改变。

我现在要做的是将这个零分解为我在我的react组件内部所称的状态。创建状态的一个方法是在react内部使用一个特殊的函数,称为use state。这是一个react hook的例子,它允许我为我的react组件添加一些额外的功能,而react的参数是。

这将是那个状态的初始值,我将开始计数,我希望从数字零开始,所以我将把数字零包含作为这个use state函数的参数,这样我们就可以从零开始计数。这个u state函数返回的实际上是一个包含两个元素的数组。

这将是一个我可以命名的变量,我会称之为count,还有一个函数,我将称之为setcount,这个函数将允许我在将来需要更改状态时设置状态的值。所以这个u-state函数接受零,作为其参数的初始状态。

然后我得到了两个返回值,我得到了状态变量本身,叫做count,还有一个用于在需要时更改该状态的函数。所以现在不再是通过在div中写数字零来渲染零,而是要在花括号中渲染这个值,零,但最终这个数字可能会改变。

我希望我的UI能反映底层状态的变化,所以现在如果我刷新页面。

它仍然显示零,因为初始状态被设置为0,但如果我最初可以改变它。

如果这个初始状态是其他值,我可以刷新页面并看到。

不同的值出现在count上,而无论这个count变量在状态中是什么值,这将是用户在查看我的用户界面时看到的内容,当他们看到我的组件时。所以现在让我们让这个按钮实际上做点什么,因为现在数字从未变化。

为此,我可以添加一个点击处理程序,并注意到onclick和React之间的一个区别,以及我们在JavaScript中传统使用的onclick。我使用了这个大写的C,这只是一个常见的React约定。当我们定义事件处理程序时,在这里我要说on click,然后用花括号。

这是一个函数的名称,我希望在点击这个按钮时运行的函数,我可以随意命名这个函数,我会称它为update count,例如。现在我需要做的是定义一个名为update count的函数,我将在这个React组件中定义该函数,在我的app函数内部。

在JavaScript中,事实证明你可以有在其他函数内部定义的函数。所以我会定义这个函数,叫做update count,我希望这个update count函数做什么呢?我想做的就是让count增加1,你可能会想,我可以通过说count等于count加1来做到,但事实并非如此。

在React中,你不能直接做到这一点,每当我使用这个use state时,如果我想改变状态,我必须使用这个use state为我提供的函数来设置新状态的值,而不是count等于count加一,我必须使用这个set count函数,并且set count的参数是count。

这是一个将改变我组件内部底层状态的函数,参数是新的状态应该是什么,在这个例子中,它只是count加一,比count多一。

在此之前,我可以保存它,然后刷新页面。它从零开始,但每次我点击这个计数按钮时。

你会注意到计数增加了一次,而我没有任何代码在说,进入div并改变div内部的内容,我在这个div里面的,只有对这个状态变量count的引用,每当状态变化时,JavaScript,反过来,React知道,React需要做的。

是重新创建这个组件,重新渲染,显示这个状态变量的新值,当按钮被点击时,我们可以运行这个函数来改变,底层状态的值。因此,通过获取状态,我们可以开始在组件内表示信息,然后定义我们的组件将展示的内容。

通过根据底层状态来表示HTML,决定我们应该如何使用该状态以呈现用户最终将看到的界面,所以让我们现在尝试将这些部分组合在一起,创建一个使用React定义状态的Web应用程序。

并操作该状态,进而更新用户界面。基于正在发生的底层状态变化,我们将创建一个应用程序。它将向用户展示一些简单的数学问题,并对用户进行一些基本加法的测验,比如说,让我们创建这个应用程序。

创建一个新文件,命名为addition.html,在addition.html内部,我将开始。再次通过复制这个counter.html文件的内容,因为这个页面的框架结构将是类似的,但我会清除我的app组件里面的内容,至少现在是这样,所以,来渲染吧,让我们渲染一个div。

如果我想创建一个应用程序,向用户询问一些数学问题,然后提示用户输入答案,至少有两个部分的用户界面,我需要,我需要一个地方来展示加法的答案,比如说,一加二等于多少。

然后我需要一个输入框,让用户可以输入他们对那个问题的回答。接着查看他们的回答是对还是错,所以在这个div里我会开始创建一个显示问题本身的div。

类似一加二,然后在下面,我会添加一个输入框。最终我们会在这个用户界面上添加更多内容,但现在我们真正需要的只是一个显示数学问题的div,以及一个供用户输入他们的响应的输入框。所以现在如果我继续去addition.html,这里是我看到的,我会把它稍微调整一下。

稍微大一点,我看到的是一加二,然后是一个输入框,用户可以开始输入他们的。

响应,但就像我们之前做的那样,我不想在返回的内容中逐字写出数字1和2,而是希望这1和2基于我的应用程序内部的一些基础状态。应用程序将维护有关要相加的两个数字的状态,然后根据该状态显示用户界面。

那么我可以在这里做什么呢?我可以再次使用react的useState。将这个数字设为一,可能命名为num1,然后是一个设置数字一的函数。接着我可以再做一次,创建num2并将其设为react的ustate2,这样我就有两个不同的状态num1和num2,每个都有不同的函数,set num1和set num2。

每个代表我想要相加的两个不同的数字。但这已经开始变得有些杂乱,随着时间的推移,我添加更多不同的状态,状态可能会变得越来越复杂,有更多不同的函数和变量,所以通常将多个状态合并是有帮助的,也是常见的做法。

在react中练习将多个状态组合成一个JavaScript对象,以维护这个特定组件的所有不同状态部分。为了做到这一点,我将再次使用react的ustate,但不是将状态最初设置为一个数字,例如一或二,而是将其设置为一个JavaScript对象。

这有键和值,我可以说让num1是数字一,让num2是数字二,就像Python中的字典一样。例如,在同一个对象中,我有多个不同的值num1和num2,我可以调用这个状态,拥有一个变量,并有一个名为setstate的函数。

这个将更新那个状态的值,因此我可以将所有这些不同的变量简化为状态和一个设置状态的函数,而现在状态有这两个不同的部分:数字一和数字二,所以现在不需要逐字渲染数字一。

使用花括号,我可以说state.num1,而不是直接渲染数字2,我可以说state.num2,利用状态来决定它将在用户界面中显示什么。

决定它在用户界面中将出现什么,因此现在页面看起来没有什么不同。

但是如果我更改状态的初始值,可能将其设置为2和4,然后刷新页面,那么现在它将显示为2加4。因此这很有帮助,我们现在有一个用户界面,数字基于状态。但现在我想做的是添加跟踪用户输入内容的能力。

所以我们可以判断用户是否正确输入了这个数学问题的答案,以及如何做到这一点。好吧,状态代表这个组件内部的任何内容,除了在状态中存储两个数字,我可能还需要跟踪第三个信息,即用户输入的响应是什么。

我会在状态中添加第三部分,称为响应,最初将只是一个空字符串,也就是没有任何内容。然后,我要给这个输入框一个值,这个值将是状态中的response,无论用户作为响应输入了什么,都会存储在状态中。

这将是输入框中显示的值,因此无论输入框中有什么内容,都会有。

通过这个state.response变量访问它,但存在一个问题。这个问题是,我尝试刷新页面,进入这个文本框,假设我知道答案,我知道2加4等于6。现在我将按下键盘上的6,但当我按下6时,什么也没有发生,6没有出现。

即使我在键盘上按下按键,文本框内部也没有任何变化,那为什么呢?

文本框没有更新,原因在于输入框中的值,无论输入框中出现什么,都是这个值state.response,而这个字符串从未改变state.response的值。因此我需要稍微更改一下,我需要将一个属性添加到这个输入框上,表示在输入框发生变化时。

我需要进行一些更改,我将调用一个可以调用的函数,称为update response。但我可以根据需要调用这个更新函数,只要它在输入框中的内容发生更改时运行。让我现在定义这个更新响应函数,我将定义一个名为update response的函数,因为这是。

事件处理程序可以接受一个参数,即事件本身,实际上是输入字段中的某些内容发生了变化。当我访问这个事件时,结果表明,如果我想找出用户在输入字段中输入的内容,我可以通过事件。目标。值来获取这个值,而我只有通过查看它才能知道。

在文档中,我希望事件。目标。值成为这个响应的新值,因此我想做的是像这样设置状态,新的状态值应该是什么呢?我希望响应不再是空字符串,而是事件。事件。目标。值。

这将成为响应的新值,但我还没有完全完成,因为状态不仅包含响应作为状态的一部分,状态还包含num1和num2,而这两个部分并没有真正改变,因此我可以说好吧,num1将保持为原来的状态。num1,这没有改变,num2将是,用户输入的内容。

num2没有改变,唯一改变的是响应。但这开始变得有些冗长,尤其是如果我开始添加状态,就会变得难以管理,因为我必须不断重复自己。为了所有的状态部分,只需指定将要改变的状态部分。

忽略其他所有内容,因此在JavaScript中有一种简便的方法称为扩展运算符,看起来像这样:省略号,然后是状态,这表示仅使用状态的现有值作为其他内容,如num1和num2,唯一要覆盖的是新的值。

这是我更新状态的一种方式,所有内容应该保持不变,除了响应,现在将是事件。目标。值,换句话说。

用户在那个输入字段中输入的任何内容,因此我将继续刷新页面。现在如果我输入一个数字,例如六,你实际上会看到这个数字出现在输入字段中,因此这很好,我们现在展示了一个问题状态,用户可以在其中输入响应,而该响应也存储在状态中。

用户按下键盘上的回车键时,我们检查他们的答案是否正确。或者他们的答案是否错误,我该怎么做呢?第一件我需要做的事情是,在这个输入字段中以某种方式检测,当按下一个键时,如果按下的是回车键,那么我们就可以进行检查。

实际的两个数字之和是多少,并查看用户是否回答正确,所以让我们添加一个事件处理程序,键按下时将等于某个值。我可以随意命名这个函数,我称之为 input keypress,但我可以给它任何名字,现在让我们定义这个 input keypress 函数。

定义一个名为 input keypress 的函数,它在任何键被按下时触发,无论是字母、数字还是回车键,因此我想检查确保按下的键实际上是回车键,只有在这种情况下我才想检查他们是否答对了。

所以我在这里添加一个条件,这只是 JavaScript,所以我可以说如果事件的键等于回车,那么我们就继续检查,否则我们不需要做任何事情,这里不需要 else,因为除非实际按下的是回车键,否则什么都不应该发生。

用户是否答对了,内部状态中 num1 是第一个数字,num2 是第二个数字,因此我可以有一个条件检查,如果 state.num1 加上 state.num2 等于 state.response,也就是用户在输入框中输入的内容,但这并不完全奏效,因为 state.response 是一个字符串,用户不一定需要。

用户输入数字时,可能会输入一些字母或其他字符,因此我首先要做的是将响应转换为整数,如果我们能够做到这一点,我会定义一个变量叫做 answer,使用 JavaScript 函数 parseInt,该函数接受一个字符串并尝试将其转换为整数。

我们将解析 end.state.response,现在我们可以检查 number one 加上 number two 是否等于答案,如果是,这意味着用户回答正确,如果和不等于答案,则意味着用户回答错误,现在我可以处理这两种不同的情况。

在某种情况下,用户回答正确,我们应该做一些事情,而在错误时,我们应该做其他事情,我们通过查看应用程序的状态以及我们应该添加的两个数字来做出这个决定,看看用户输入的响应,所以当用户回答时我们应该怎么做。

如何检查问题是否正确,或者用户是否答错了,可能这个游戏会通过维护一个数字来记录用户回答正确的题目数量,每次用户回答正确时,我们可以将分数增加一,而每当用户答错时,我们可以减少分数。

例如,增加一,那么我们怎么做呢?分数是应用程序中的一部分状态,所以我们现在需要将其添加到状态中。目前在状态中,我们存储一个数字一、一个数字二和一个响应,我将添加一个分数,分数将从零开始。

我们可以在页面上渲染这个分数,如果我向下滚动到我们返回的 div,来渲染,让我们添加另一个 div,上面写着分数是,然后使用花括号插入。

无论状态。分数的值是什么,分数是什么,让我们从状态中找出这个,并在用户界面中显示出来,所以现在这个用户界面显示的不仅是一个问题和一个输入框,还有一个分数。

分数开始时,所以现在回到这个函数,当按下一个键时,如果是回车键,我们就检查他们的答案对错,我们检查用户是否真的答对了问题,如果答对了,我们应该做什么呢?我们应该增加分数。那我们怎么做呢?我们通过调用设置状态函数来做到这一点。

所有状态都将保持不变,所以使用点点状态扩展运算符。唯一不同的是分数将是状态。分数加一,因此我们更新状态以将分数增加一。如果用户答错了,我们将状态设置为点点状态,然后分数将是状态。

点数减去一,所以如果用户答对了问题。

我们分数增加一,否则我们减少分数一。让我们测试一下,看看当我们在用户界面中尝试这个时实际效果如何。我将刷新页面,两个加四,如果我输入正确答案六,按回车,分数增加一;如果我输入错误答案,比如说八,按回车。

分数减去一,因此这似乎取决于我是否答对了问题。分数能够根据结果更新,增加或减少。现在这个游戏很容易获得高分,因为我可以不停地按回车,问题从未改变,我的回答是。

已经存在,因此分数会不断上升。那么我们来让游戏更有趣一点,每当用户答对问题时,我们就显示一个新问题让他们回答,我们该怎么做呢?显示的问题是。

用户基于两个底层组件的状态,它基于状态。num1 和状态。num2,所以如果我想更改问题,我所要做的就是在用户答对问题时更新状态。除了更新分数外,我们还要更新 num1 和 num2,我可以将它们设置为。

具体的值可能像5和10,但让我们更有趣,每次显示一个随机数,我们将生成一个随机数,所以用户每次会把两个随机数相加,他们每次都会得到一个新问题。对吗?我们如何生成随机数呢?well math。random是一个生成0到1之间随机数的javascript函数。

我们可以将其乘以10,现在我们得到的是一个介于0到10之间的数字。但我们不想让数字中出现小数,所以我会取这个数字的上限,math。seal,如果数字是5.8,我们就将其四舍五入到6,举个例子,我们对第二个数字做同样的事情。

我们将取math。random乘以10的上限。所以每次用户正确回答一个问题时,我们将更新num1和num2为新的。

随机数就这样生成,所以让我们回去再试一次,我们看到2加4。我输入正确答案6,问题变化为8加5,我输入正确答案并按回车,分数增加,问题再次变化。这次如果我答错,我输入10作为例子。

看着我的分数下降,从二变成了一,问题没有改变,现在我又有了。一次回答问题的机会,当我正确回答时,分数。再次增加,从一到二,所以这个游戏开始了,我的,分数显示着不同的问题,现在至少有一个用户界面的小问题。

目前的情况是,当我正确回答一个问题并按回车时,我输入6并按回车,6仍然保留在那里。理想情况下,我想得到一个新问题,我希望清空响应,以便用户可以直接输入新的答案,而不是必须删除之前输入的内容再输入新内容。

那么,如何重置输入框中的内容呢?输入框中输入的内容存储在我组件的状态中,存储在,state.dot response中。

所以如果我想改变这一切,我需要做的就是,当用户正确回答问题时,将响应清空为一个空字符串,我们将更新这两个数字,增加分数,同时也清空响应,使其仅为一个空字符串。如果用户回答错误,我也可以做同样的事情,分数减少一。

但也将那个响应清空为一个空字符串,这样就没有任何内容。现在我们得到一个问题,我输入答案,按回车,输入框清空,我得到一个新问题,分数增加了一,四个独立的状态同时改变,这反映在用户界面中。

现在我能够看到,所以我输入另一个值,分数增加了,一切都更新了。再次确认,这绝对是进步。我注意到的另一个用户界面小问题是,输入框默认并没有自动选中,我必须手动点击。

在输入框上,为了突出显示它,以便我可以开始输入我的响应,使用自动聚焦属性并将其设置为true,以便输入框自动。

当我第一次加载页面时,它会聚焦,所以现在我刷新了页面。

输入框已经被高亮显示,我立即可以开始尝试玩这个游戏。所以现在我们有了这个应用程序的基本功能,让我们尝试改进CSS,使游戏看起来更好一点。我会滚动到页面顶部,并在我的HTML页面的头部添加一个样式标签,我希望这个整个应用程序。

使其居中,所以我会说文本对齐将是居中,成为。

因为我喜欢这种字体,所以在这个特定游戏中,我刷新了页面,现在一切都居中,字体与默认字体不同。还有什么我希望改变的呢?这个问题“二加四”,也许我希望它能更大,我希望这个问题能够更突出。

而且下面的分数可以保持原样。

保持目前的大小,那我该怎么做呢?如果我回到这里的HTML。我将为显示问题的div设置一个ID,问题是数字一加数字二。然后如果我向上滚动,我会说,对于ID为问题的元素,让我们。

我继续设置字体大小更大,所以现在我看到一个大的数学方程式“2加4”,例如,输入框和下面较小的分数。所以这是一个不错的用户界面增强,稍微好一点,现在我可以玩这个游戏,答对一个问题,分数增加,答错一个问题,我可以再试一次。

但也许我希望提供更多的视觉指示,当用户答错问题时,也许每当用户答错问题,我希望将这个文本的颜色从黑色改为红色,当用户答错问题时,我该如何做到这一点。

好吧,我们可以通过使用CSS来更改某些事物的颜色,比如说如果我们有一个名为“incorrect”的类。例如,如果我在这里向下滚动,并给这个div一个类名,这就是在React中添加类的方式为“incorrect”,那么我可以使用这个类名来将它的样式设置为红色或不是红色,所以我可以说,任何带有“incorrect”类的东西。

我们继续给它。

一种红色,因此现在因为我将这个问题归为错误的类别。我说将所有错误文本变为红色,我们现在看到这个文本出现为红色,但这并不是我想要的,我并不希望它一直是红色。我只希望在用户刚刚答错问题时变为红色。

他们刚刚在回答一个数学问题时是错误的,那么我该如何表示那条信息呢?

在我的应用程序内部,我需要一些额外的状态。状态再次是我需要在组件内部跟踪的任何信息,现在看来,除了响应、分数和数字外,我还想跟踪用户是否刚刚错误回答了一个问题,所以我会在状态中添加另一部分。

我将其命名为incorrect,起初它是false,他们并没有刚刚答错什么。现在,如果我向下滚动到这个类名,而不是让它一直是错误的,让我在花括号中添加一个表达式,我会说如果state.incorrecttrue。使用三元运算符和问号,那么类应该是incorrect,但是。

否则,它不应该有incorrect类,它将只是一个空字符串。因此这个表达式允许我根据底层状态更改HTML元素的类。如果state.incorrecttrue,那么这个div将有一个incorrect类,否则。

它不会,因此现在当我第一次加载页面时,文本呈现为黑色,我需要做的是当用户答错一个问题时,我。

需要更改状态以表明他们刚刚答错了一个问题,我该怎么做呢?这里是当用户答错问题时的设置状态调用,在这种情况下,我将incorrect设为true,当用户答对一个问题时,我们将incorrect设为false,我们正在根据。

无论用户是答对还是答错问题,现在,如果我加载页面,正确回答一个问题,分数增加,我得到了一个新问题,但如果我错误回答一个问题并按返回键,你会注意到分数减少,输入字段清空,文本颜色改变,因为我更改了那个错误部分的值。

基于底层状态,我们能够看到文本颜色的变化。如果我现在答对一个问题,按返回键,文本颜色又变回黑色,分数增加,现在让我们为这个应用程序添加最后一个状态或最终的界面更改,让我有办法赢得这个游戏,也许一旦我的分数达到10。

通过正确回答10个问题,我们就会赢得比赛。我该如何做到呢?记住,每个React组件都可以只是一个JavaScript函数,这个函数立即返回这个div。但这是一个函数,所以我可以在其中添加额外的逻辑,我可以说,如果状态得分。

如果等于10,例如,那么与其渲染旧的div,不如返回一个新的div。类似于u1,所以我可以为它设置样式,我会给它一个id,id将是winner。如果id是winner,我们就继续把字体大小设为72像素。如果我赢了,就让颜色变成绿色,所以我添加了一些CSS来设置样式。

但实际上唯一的新逻辑是在下面进一步的地方,我在这里说检查状态。如果得分是10,那就意味着我们获胜,所以不返回新问题。

只需返回一个显示你赢得了比赛的div,每次我回答一个问题时,你会注意到得分会增加1,而每次我们生成新的随机数以显示在用户界面上。一旦我回答到第10个问题,如果我答对了,按下返回键,整个用户界面就会改变。

而不是问题和输入。

在字段和得分方面,我只看到绿色的大字显示我赢了,而我能够做到这一点是通过查看这个条件,我们在关注状态的值。如果状态是10,我们正在决定什么是视网膜,而这又是React的一个伟大功能,能够使用这个基础状态,并根据基础状态的值来进行判断。

状态决定用户应该看到的内容。

在他们的用户界面中,React只是众多执行这种类型操作的库之一,其他流行的库包括Angular和Vue,所有这些都是使创建能够响应一些基础状态的应用程序变得简单的Web框架,这样你作为程序员就不必担心不断。

必须操作页面的不同部分,尤其是当你想象像Facebook或Twitter这样的网站时,页面上同时发生很多事情。每当有新推文进来时,你可能会收到通知,并在你的新闻源主区域看到新帖子。

这些都是你可能希望应用程序更轻松处理的类型的事情,你描述状态是什么,描述页面应该基于那个基础状态看起来像什么,让库(无论是React还是其他库)开始为你处理这个过程,在用户界面的世界里。

正在快速变化的是用户界面的很多变化,涉及到的技术和工具相当流行,但它们实际上是基于同一组基本思想。这个思想是我们可以使用javascript来操控用户在页面上看到的内容,以便检测基于特定事件的发生。

比如滚动到页面底部或在输入框中输入内容,然后通过提供某种函数来响应这些特定事件,该函数会在特定事件发生时被调用,并与其他功能混合,例如异步请求外部服务器的信息。

或者基于状态值进行计算,就像我们在react中看到的那样。我们有能力非常快速地创建非常有趣和引人入胜的动态用户界面,这全靠将pythonjavascript结合的力量,这就是用python进行网页编程。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P21:L7- 测试与前端CI/CD 1 (测试与断言,单测) - ShowMeAI - BV1gL411x7NY

[音乐]。

好的,欢迎大家回来参加Python和JavaScript的网页编程,现在我们已经看到了一些不同的技术和工具,可以用来设计网页应用程序,HTML和CSS用于描述我们的页面外观,像Python这样的编程语言则用于实际编程。

像Django这样的框架可以监听请求,处理它们并提供某种响应,最近我们还查看了JavaScript,另一种可以特别用于客户端的编程语言,在用户的网页浏览器中运行,以使我们的网页更加生动。

交互性和用户友好性,现在我们将过渡到今天讨论软件最佳实践,一些开发人员在处理网页应用程序时实际使用的工具和技术,尤其是当这些网页应用程序开始变得越来越复杂时,我们将从中开始。

讨论测试,这种验证我们代码是否正确的想法,然后过渡到ICD,即持续集成和持续交付,以及一些其他最佳实践,确保软件开发人员的工作可以方便地进行测试和部署。

非常迅速,因此我们将以测试开始讨论,测试实际上是关于验证和确保软件开发人员编写的代码确实是正确的,以确保函数按预期工作,网页按预期表现。

理想情况下,我们希望有一种高效有效地测试代码的方法,随着程序变得越来越复杂,确保我们的程序按我们想要的方式运行,因此我们会从简单开始,考虑我们正在进行的基本方法。

我们可能会以一个用Python编写的函数为例进行测试和验证,以确保其按预期工作。为此,我们可以从一个在Python中称为assert的命令开始,assert的作用是断言或声明某事应为真,如果某事不是。

如果不正确,那么断言将抛出某种错误异常,以便运行程序或命令的人知道发生了什么问题。这是利用Python能力来测试函数并验证该函数行为的一种非常基本的方法。

我希望这样做,所以让我们先尝试一个简单的例子,编写一个Python函数,然后进行测试,以确保这个函数按我们希望的方式工作。我们将创建一个新文件,我将其命名为search PI,并让我定义一个新的Python函数,例如,它将接受一个整数。

平方函数,我只想接受一个数字并返回它的平方,所以我将定义一个叫做平方的函数,输入一个数字像X,我想返回x乘以X,这是一个相当简单的函数,但我想验证这个函数是否按我预期的方式工作。

有很多方法可以做到这一点,一种方式就是。

让我们打印出10的平方,例如,看看结果是什么。

等于,那么你可以运行一个程序,类似于Python assert PI,然后直接说,好的,答案是100,我可以对自己说,好吧,这就是我所期待的,但我现在必须进行心理计算,确保10的平方的答案是我预期的值。

如果我能自动化这个过程就太好了,我可以做的事情之一就是打印出10的平方是否等于100。我知道我想要10的平方。

等于100,所以我可以直接打印出这个值,打印出10的平方是否等于100。我将继续运行程序,这次我得到的结果是正确的,例如,因为这两个。

事物彼此相等,如果我另一方面尝试检查。

如果说10的平方等于101那是不正确的,你运行程序,好的,现在它将会是。

这并不是什么新鲜事,也不是我们没见过的,但现在我可以做的是,替代这个,我可以直接说,让我断言10的平方等于100。这里我只是断言这一点。

10的平方等于100的表达式将是正确的,现在我可以运行程序,你会注意到没有任何反应,没有输出,完全没有,因为当某个语句运行时,它检查的表达式如果是正确的,10的平方确实等于100。

它有效地忽略了这个语句,完全继续到下一个内容,没有输出,没有任何副作用,这很有帮助,因为这意味着如果我想要断言某件事情是正确的,我可以断言它,然后继续写我的代码,就好像我没有写过那一行。

只要我断言的事情实际上是,真的。但如果我的代码中有一个错误,例如某种错误,假设我意外地说返回X加X,来计算平方,这就是一个错误。

当我尝试运行Python时,assert PI,我得到的是一个。

异常,而我得到的异常类型是一个叫做断言错误的东西,我在这里看到,有一个断言错误,然后我看到,断言错误发生的原因,发生在第4行,就是我说的,我想断言平方。

10等于100,所以我们可以想象测试我们的代码,就是包括许多不同的断言语句,如果我想验证我的代码是正确的,我可以写,几种不同的断言语句,对于像这个平方函数这样相当简单的函数,可能并不太复杂。

我需要写很多测试,但你可以想象,对于更复杂的函数,具有多个不同的条件分支,能够断言无论程序选择哪个条件分支,代码实际上都是正确的,这将是非常有价值的。

比如说,这在处理较大项目时可能会很有帮助,你想解决项目中可能出现的错误。这涉及到测试驱动开发的理念,在开发时保持测试的概念,其中一项最佳实践是,如果出现问题。

你正在做自己的程序,遇到了一些错误。在程序中,你首先想修复这个错误,之后你会想写一个测试,来验证新行为是否按预期工作,一旦你写完这些测试,这些测试可以,随着时间的推移而增长。

当你继续进行项目时,你可以,始终运行这些现有的测试集,确保你所做的任何新更改,不会破坏之前的任何内容,特别是在你添加的新特性或可能做的更改中,这一点尤其宝贵。

随着程序变得越来越复杂,手动测试一切将变得非常繁琐,因此能够自动化这个过程,运行一大堆我希望程序执行的测试,并确保,这很有帮助,因此断言是一种基本的方法。

说我想这样做,确实,可以继续抛出一个异常。而使用Python,我们知道我们也必须,捕获这些异常以确保我们能够,适当地处理它们,这样我们就可以显示,漂亮的错误信息,例如如果我们,想这样做,但现在让我们继续。

尝试写一个更复杂的函数,比仅仅对一个数字进行平方更复杂,这里有更多不同的情况我可能想要测试,还有更多的空间让我作为程序员可能出错。例如,让我们设想编写一个新的。

文件但我打算称之为prime PI,在这里我会说我希望prime.dot.pi实现一个名为is prime的函数。这个is prime函数应该做的是检查一个数字是否为素数,素数只有1和它本身的因子,我想写一个函数。

验证这一事实。那么我该如何着手呢?如果n小于2,那么它肯定不是素数,因为我们认为0和1不是素数,我们只处理0或更大的数字。现在先处理这些,但让我们从其他数字开始,能被100整除的数字。

2或更大的数字。那么我想做什么呢?我真的想检查每一个可能的因子,比如如果我想检查100是否为素数,我想循环遍历所有可能的因子,如2、3、4、5、6,当我到达一个像2或5的数字时。

那么我就知道这个数字不是素数,因此我可以说对于范围从2到n的每个i,举个例子,让我说如果n模i等于0,那么返回false。那么我在这里说的是什么?我在说继续向上遍历,但不包括n。

例如,如果我在检查10是否为素数,我将检查2、3、4、5、6、7、8、9,对于这些数字中的每一个,检查n(我传入这个函数的输入)模i,想要检查的因子是否等于0。这个模运算符(%)如果你不记得了,给我们的是当你将一个数字除以另一个数字时的余数。

所以如果n模i等于0,那意味着当你将n除以i时余数为零,意味着i整除n,没有余数。这意味着它不是素数,因为它确实有一个因子,无论i是什么因子。如果我到达这个for循环的末尾,那么我可以继续。

并且仅仅在找不到除了1和这个数字本身的因子时返回true。那么我们可以说这个数字将是素数,因此这个例如可以是一个检查数字是否为素数的函数,但如果我试图优化,我正试图。

为了使我的函数更高效,我可能意识到你真的不需要检查从2到数字n本身的每一个数字,我实际上可以只检查到该数字的平方根。例如,对于25,我想检查2、3、4、5,因为5的平方就是25。

但是在5之后,我不需要检查任何更大的数字,超过某个数字后,这个数字的平方根乘以自身,永远不会有一个比这个更大的数字可能是一个因子,而我不会已经知道它,所以我只是想。

从数学角度稍微考虑一下,我们可能能够进行一些优化,而不是从2一直到n,我可以只到N的平方根,如果平方根恰好不是一个整数,我想这样是有效的,我至少让自己相信了这一点。

思考这是一种可能检查数字是否为质数的函数,那么如果我想要做什么呢?

验证这一点,我可以写一些assert语句,另一件我可以做的事情是直接使用Python解释器,我可以说,好的,让我输入Python,我在Python解释器中,可以说从prime导入is primeprime是那个文件的名称,is prime是我想测试的文件中的函数。

那我们就试试吧。像是is prime 5,这是一个质数,希望它会说它是true,好的,它确实这样说了,我们试试is prime 10,看看这是否有效,好的,is prime 10false因为10不是质数,这很好,这似乎也在正常工作,让我们试试。

prime 99,这不是质数,因为例如3是它的一个倍数,好的,false,这很好,这似乎在正常工作,我可以在解释器中测试这个函数,以确保它有效。

这是我希望它工作的方式,但现在让我们看看其他一些我可能用来测试它的方法,嗯,有一种方法是我可以写一个文件,比如tests zero pi,而zero dot pi将做的事情是,不再使用assert,而是像我们之前那样做布尔检查,我将导入。

is prime函数,我定义了一个新的函数叫test prime,这个函数将用于测试,以确保当你平方某个数字或检查某个数字n是否为质数时,你得到的期望值是truefalse

如果它不是质数,那么这个函数在做什么呢?这个函数正在检查我们调用is prime函数在这个数字n上,并查看它是否等于我们期望的值,期望的值要么是true,要么是false,如果我们在N上运行is prime,而它不是。

是否等于我们期望的值,那么我们打印出来,好的,这里有一个错误,我们期望的某个值是truefalse但它。

结果并不是这样,所以现在我有了这个测试素数函数,我可以说,好的,让我回到Python解释器,从test0导入testPrime,现在我可以说,好的,让我测试素数,确保5是素数,所以我传入我的第一个输入,数字n。

我想检查,我想检查5是否是素数,而我提供的第二个输入是我期望的结果,要么是真,要么是假,这里什么也没有发生,这是件好事,如果有错误,它会打印出一些东西,而我看到什么都没有打印,意味着一切都很好。如果我测试。

现在是素数,像是确保10不是素数,确保当你把10传入是素数时,它将给我们假,再次没有任何事情发生,似乎工作得很好,让我现在尝试更多的例子。也许我试试测试素数25,我想确保25不是素数,25不是一个。

素数没问题,我们得到了某种错误,在是素数25上有一个错误,我原本预期输出为假,但由于某种原因,它看起来像是素数。除了假以外的某些东西可能返回了真,并且可能指示我的程序中某种错误,我不认为25应该是素数。

一个素数,但我的程序认为25是一个素数,这个错误可以给我一个线索,告诉我该怎么做,但最终,尤其是当程序开始变得更长时,尤其是我开始添加越来越多的功能,手动测试每一个功能将开始变得繁琐。

我可以做的事情是写一个脚本,让我能够自动运行所有这些测试,所以这里我有一个test0.sh文件。

shell脚本,一个我可以在终端中运行的脚本,这样做是运行Python 3,对于python版本3 - C,这意味着我将给它一个命令,它将运行该命令,所以我可以运行这些,每一行都执行什么,从test0中导入。

我的测试素数函数,这个函数将测试以确保素数函数生成我预期的输出,每次我都在测试一个不同的数字,确保1不是素数,确保2是素数,8不是素数,依此类推,我可以这样写一个。

这一系列测试,然后,与其一个一个地运行每个测试,我可以直接运行测试0,我可以说我想运行./test0.sh,好吧,我看到我有两个错误,我在是素数8上得到一个错误,我原本预期它不是素数,但由于某种原因。

理由是它似乎是素数,然后,关于是素数25的例外,我原本预期它不是素数,但由于某种原因,我的程序认为它是素数。真是对我很有帮助的一种方式。

立即知道这里发生了一些错误。但最终,除了让我自己编写所有这些框架来测试我的代码外,还有库可以帮助我们,其中一个在Python中最流行的库称为单元测试。

而单元测试是一个旨在快速编写能够检查某物是否等于其他东西的测试的库,并且单元测试内置了一个自动测试运行器,可以为我运行所有测试,并验证输出,单元测试得到了。

许多其他库中内置了,可以将这种想法应用于我们的Django应用程序,但现在让我们翻译一下我们自己编写的这些测试,只需编写一个函数来测试素数是否如我们所期望的那样,现在将其翻译为使用这个。

Python单元测试库,所以,为了了解这是什么样的,我现在将打开tests one dot pi,第一件事是我导入单元测试,这是我们在Python中免费获得的,我还导入了我想要测试的函数,现在我定义一个类。

将包含我所有的测试,这是一个继承或派生自单元测试测试用例的类,这意味着这是一个定义一大堆函数的类,每个函数都是我想要测试的。因此,例如在这个非常第一个测试中,这是。

一个测试检查确保一不是素数,因此我做的方法是调用self这个测试对象,它恰好有一个内置的方法或函数叫做assert false,还有一个等效的assert true,但我想断言false,我想要断言的是。

假设一是素数,因此无论一是什么,应该是错误的,我想仅仅断言它是错误的,同样对数字二,我现在想检查数字二是否是素数,我的方法是调用self.dot assert true,我想断言,当我在上面运行is prime函数时。

数字二,我得到的输出将是一个true值self dot a third true,我可以将其余的测试翻译成这些self dot assert true或self dot assert false,然后我说如果你继续运行程序就好了。

并调用单元测试主程序,它将运行。

所有这些单元测试,所以现在当我运行Python test one pi时,这就是我得到的。我看到了一些很好的输出,顶部每次测试都有点。

而一个 F 的字母用于一个失败的测试,它表明运行了六个测试,底部我看到有两个失败,所以它会立即告诉我到底是什么失败了,并且会给我一些理由,说明这些测试失败的原因,所以我们可以看到。

好吧,这里有一个测试,这里有另一个测试,这些测试失败了,检查 25 不是质数的测试,而这句话是我在 Python 文档字符串中提供的,位于函数声明下的三个引号之内,这三个引号也被称为。

文档字符串有多种用途,它们可以作为描述函数所做内容的注释,但它们是特殊的注释,因为查看函数的人可以访问该文档字符串,通常用于记录函数的功能。

并且它们也可以在其他地方使用,因此单元测试所做的是,对于每个函数,它使用文档字符串作为测试的描述,因此如果测试失败,我可以确切看到失败的测试名称,以及它测试的内容,描述清楚。

现在发生的情况是,在这个案例中,我正在测试一个函数,测试一堆不同的数字,这似乎并不是很有用,但再次说,如果想象一下越来越复杂的项目,当你运行测试时能够立即知道程序的哪些部分或者。

你的网站应用程序的某些部分没有按预期工作,这实际上可能是相当有帮助的,因此测试 25,这是在这种情况下触发断言失败的函数,而导致失败的行是 self.dot.a_cert。false 是质数 25,失败的原因是 true,显然成功了。

这个函数的输出不是 false,而我期望它是 false,因此有多种不同方式尝试运行我们的测试,这恰好是其中一种非常流行的方法,但这个提示告诉我,我应该回去修复我的 is prime 函数,我可以回到 prime.dot.Piatt 并说。

好吧,我想弄清楚为什么会出错,如果你仔细查看,也许进行一点测试,你可能会发现有一个稍微偏差的错误,我可能需要检查一个额外的数字,实际上我在检查 25 是否是质数时。

可能需要检查到包括数字 5,以知道 5 是 25 的因数,但之前我只检查到了数字 5,但没有包括数字 5,因此我还需要检查一下。

还有一个数字,现在要验证这一点,我可以自己手动测试这个函数,或者我可以再运行这些测试。我找到测试1 pi,这次所有这些点意味着所有这些测试都成功了。我们运行了六个测试,一切都正常。

所以,这对我来说是一个有帮助的方式,可以立即知道事情似乎运作良好。从这里的要点是,这些测试肯定会帮助你在开始对程序进行新更改时,尤其是在你开始优化函数时,可能会使函数更有效。

高效,但随后运行你的测试,以确保在进行这些改进时,你没有破坏任何东西,你没有改变程序本应如何运行的行为,而现在却不再以那种方式运行。你能够以更大的信心验证这一点。

当然,这只有在你的测试覆盖了你希望函数执行的所有内容时才有效,而且你已适当地覆盖了函数应该如何行为的各种不同情况,因为只有当测试全面时,它们才会对你有用,并指示出失败。

你所做的更改不会破坏任何东西,只有这样才行。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P22:L7- 测试与前端CI/CD 2 (selenium,CI/CD) - ShowMeAI - BV1gL411x7NY

你是否能够对这些更改本身感到自信,所以现在让我们采取这个想法,使用单元测试来编写这些测试,验证一个。函数是否有效,并将其应用于像Jango这样的网站应用程序。我们现在希望使用它来进行。

能够测试我们Jango网络应用程序中各种不同功能是否正常工作,所以我打算实际查看一下。我们在第一次讨论Jango时写的航空公司程序,讨论存储数据在数据库中的方式,我将打开。

模型pi,你会看到我对我们定义的航班做了一些补充,回想一下之前我们首次介绍这个定义航班模型的想法。我们在航空公司中给这个模型三个属性,一个出发地和一个目的地,出发地和目的地都是。

目的地引用了一个机场对象,机场对象是我们单独定义的对象,而航班有一个出发机场和一个目的机场。此外,每个航班都有一个持续时间,以分钟为单位,表示航班将持续的时间,我可能希望有某种方式来验证。

验证航班是否有效,确保数据录入到数据库时没有错误。我想一般确保给定一个航班,我可以检查确保。它是一个有效航班,那么航班有效意味着什么呢?

一般来说,对于这些特定字段,我会说,有两件事情需要为航班有效。条件一,出发地和目的地不应该是同一个机场。条件二,航班的持续时间需要大于零。

如果持续时间为零或持续时间为负数,那可能表示数据录入时有某种错误,或者这些航班的配置出现了问题。所以我想确保持续时间大于零,这就是我的两个条件。

有效航班的条件,我实际上在这里写了一个函数。叫做is valid flight,它在这个航班类上工作,简单地检查。给定一个航班,确保它在,事实上是有效的,而它的检查方式。是通过检查这两个条件,我刚刚描述的内容。

确保出发地不等于目的地,它正在检查。确保航班的持续时间大于或等于零。也许我应该把这个改为,确保它完全为正数,但这就是我对什么是有效航班的定义。

有效的航班,以及我现在想要做的是测试我应用程序的这些不同部分。我有这个有效的航班功能,可能也想测试一下,但我们还有其他所有属性,我想测试这些关系,航班有一个出发地和一个。

目的地,我们有乘客可以与航班关联,所以我的数据之间有很多关系,我想测试和验证,以确保它们按照我们期望的方式工作。因此,每当我们创建像这样的Django应用程序时,例如这个航班应用程序。

还提供了这个test.py文件,我们还没有使用test pilot v phone文件进行任何操作,但它的用途是编写这类测试,以验证我们的应用程序按我们希望的方式运行。那么现在让我们打开test.py。

看看这里有什么,我们可以做的是定义一个测试用例的子类,它的行为与基于相同思想的单元测试非常相似。我将定义一个新类,叫做航班,测试我想在我的航班应用程序上运行的测试,首先我可能要知道的是。

需要进行一些初始设置,以确保有一些数据可以让我实际操作和测试,而当我继续运行这些单元测试时,Django会为我创建一个完全独立的数据库,仅用于测试目的,我们有一个包含所有数据的数据库。

这些信息实际上与我网站服务器上存在的航班有关,但我们也可能希望用一些虚拟航班和一些虚拟机场进行测试,以确保一切正常,然后一旦我们对事情的运行感到放心,就可以部署我们的Web应用程序。

让实际用户开始使用我们为Web应用程序添加的任何新功能。例如,在这个数据库中,我可能需要进行一些初始设置,我可以通过在我的测试用例类中定义一个设置函数来实现,这是一个特殊的函数,Django知道。

运行这些测试时,它应该首先执行我们需要的任何设置步骤。那么我们是如何做到这一点的呢?我们在设置中将添加一些示例数据到测试数据库中,这不会触及用户实际看到和互动的数据库。

仅用于测试目的的测试版本,我们将开始创建一些机场,因此创建机场对象,然后指定这些字段的值,城市A的代码是AAA,城市B的代码是BBB,这些只是虚拟机场名称,它们并不是真实的机场,而是使用。

出于测试目的,我将把这些机场对象保存在这些值 a1 和 a2 中,接下来我将创建一些航班,我使用航班对象创建,不创建三种不同的航班,一个从 a1 到 a2,持续时间为 100 分钟,一个从 a1。

从 a1 出发,持续时间为 200 分钟,从 a1 到 a2,持续时间为负 100 分钟,所以我现在有一大堆这些航班,我想测试这些航班,确保以某种预定或预期的方式工作,现在如果我向下滚动,我们可以看到我有一大堆。

这些不同的测试中,有一个测试只是测试 departures count,因此每个机场都有一个名为 departures 的字段,理想情况下应该像是有多少航班从该机场出发,我想确保 departures count 按照我预期的方式工作,因此我在这里获取。

代码为 AAA 的机场,现在使用单元测试类似的语法,我想说 self dot assert equal,因此 assert true 验证某件事情为真,assert false 验证某件事情为假,assert equal 验证两个数字相等,在这里我想验证 a。

departure is dot count 如果我拿机场 a 并计算有多少航班从该机场出发,这应该是三,所以只是验证它的工作情况,然后如果这个测试通过,我可以自信地说在我程序的其他地方,如果我获取一个机场并调用那个机场的 dot。

departures dot count 我可以非常自信地感觉到这会按照我预期的方式工作。我可以对 arrivals 做同样的事情,获取机场并断言完成的到达数量,如果只有一班航班到达机场 A,那么这将等于一,例如,这就是测试这些。

关系,现在我也可以测试 is valid flight 函数。我得到我的两个机场 a1 和 a2,这个是代码是 a a a,这个人的代码是 BBB。我将获取起点为 a1,目的地为 a2,持续时间为 100 的航班。

让我断言这个航班将是一个有效的航班,因为这个航班是有效的,起点与目的地不同,持续时间是某个正数分钟,所以我应该感觉相当自信,这将是一个有效的航班,我可以通过验证。

调用 self dot a third true 我也可以对无效航班进行测试,测试一个无效航班,因为目的地是错误的。我可以获取航班机场 a1,并获取起点和终点都是 a1 的航班,现在让我 self dot assert false,说这个不应该是。

一个有效的航班,因为出发地和目的地是相同的,航班的另一种无效情况是什么呢?嗯,如果航班的情况是这样的!

持续时间,所以我可以说八,给我出发地是A,目的地是B,但持续时间是负的100分钟,这是其中一个航班,嗯,这不应该是一个有效的航班。所以我会说self.assertFalse(is valid flight),因为当我调用它时,确保这个函数是正确的。

在这个航班上,它不应该有效,因为持续时间使它成为无效航班,所以现在我定义了一整堆测试,下面还有更多,我们稍后会看看,但我现在定义了一堆这样的航班,或者说是这些测试。

我想运行它们,在Gengo中运行测试的方法是通过manage.py命令。manage.py有一堆不同的命令可以运行,我们见过像makemigrationsmigraterunserver,但其中一个是如果我进入airline,我可以说python manage.py test

我将运行我所有的测试,好的,似乎我们运行了十个测试,但其中两个失败了,所以让我们看看这两个测试为什么失败。读取方式是每当测试失败时我们会得到这个标题,因此我们失败了测试无效航班目的地函数。

测试无效的航班持续时间函数,文档字符串本可以帮助我了解这些测试究竟在做什么,但似乎true并不是false,我想要断言这不应该是一个有效的航班,应该是false,但出于某种原因,这些看起来是有效的航班。

有关is valid flight似乎有问题,它在应该返回false时返回了true,所以这给了我一个开始查找的地方。我可以说,好的,让我去看一下is valid

航班,确保这个函数是正确的,所以我回到models.py,再看一下is valid flight,也许我会再思考一下逻辑,好的,我想检查一下self.origin是否不等于self.destination,我想检查一下持续时间是否大于或等于。

零,我可以把这个改为大于,但我认为这不是问题,因为我的持续时间是负的,所以这应该已经是无效的。但我可能意识到的另一件事是,我所用的逻辑连接词不对,我想检查一下。

要使其成为有效航班,它需要满足两个条件:出发地和目的地需要不同,航班的持续时间需要大于零。例如,这里我用了or而不是and,所以我可以简单地更改它,希望这样能修复问题。

一些事情并尽可能多地验证。我重新运行了Python的管理测试,继续按回车键,它将检查已运行的十个测试,一切正常。看起来现在我已经通过了所有这些测试,并且注意到在顶部它创建了一个测试数据库,因此它刚为我创建了一个测试数据库。

为了完成所有这些测试工作,然后在最后销毁那个测试数据库,因此我的测试中添加或删除数据都不会触及Web应用程序数据库中的任何实际数据,Django将处理将所有这些保持分开的过程。

通过首先调用setup函数以确保我的新测试。

数据库拥有所需的一切,所以好吧,我们现在通过使用单元测试能够测试Web应用程序内部的各种不同函数。我们首先看到我们可以测试一个函数,比如is prime函数,这只是我们编写的一个Python函数,但我们也可以测试诸如。

我们模型上的函数,例如检查航班是否有效,确保我们可以接受一个航班并访问所有到达和出发的机场,但我希望做的不止这些,尤其是对于一个Web应用程序,我希望检查。

特定的网页按我希望的方式工作,因此为了做到这一点,Gengo让我们模拟尝试向Web应用程序发出请求并获取响应。因此,让我们继续看看其他一些测试。这里我们有一个名为test index的函数,test index的作用是。

要做的就是测试我的默认航班页面,以确保它正常工作。因此我们首先创建一个客户端,一个将要以请求和响应的方式进行交互的客户端,然后我要调用客户端的get /flights,这就是获取索引页面的路由。

所有航班,我将其保存在一个名为响应的变量中。无论我从尝试获取该页面中得到什么响应,我都希望将其保存在这个名为响应的变量中,现在我可以在同一个测试中有多个断言语句,如果我愿意,有时你可能希望。

以便将它们分开,但在这里我想确认索引页面的工作情况,这意味着几件事,首先是响应的状态码应该等于二百,也就是表示正常。我想确保无论我收到什么响应,这将是二百。

如果出现某种错误,比如404因为页面未找到,或500因为内部服务器错误,我想知道这一点。因此让我首先断言状态码应该等于二百,但Django也让我访问上下文。

响应以及上下文将再次提到,在 Django 中,当我们渲染模板时,例如我们调用像返回渲染,然后提供请求以及我们要渲染的页面,但我们也可以提供一些上下文,即描述我们希望传递的所有值的 Python 字典。

该模板和 Django 的测试框架使我们能够访问该上下文,以便我们可以测试确保它包含我们希望它包含的内容,在我所有航班的索引页面上,我希望看到它包含所有航班的列表,而我们创建了三条示例航班。

在这个测试数据库内部,我应该能够进行验证。

能够断言这两者是相等的响应而不是上下文航班,这将给我在上下文中传递的航班 dot count,那么它最好是 3,因为我想确保当我查看上下文并访问任何发生的内容时,结果确实是三条。

对于那个航班键,我还可以运行其他测试,所以在这种情况下,我已经获取了一个特定航班,这个航班在这个情况下的起点是 a1,终点也是 a1,这并不是一个有效的航班,但我们还是会获取它,因为它在数据库中存在,现在我可以。

获取斜杠航班斜杠那个航班 ID,因为在我的航班页面上,我希望能够访问斜杠航班,斜杠一个,以获取航班号码一,并访问斜杠航班斜杠二,以获取航班号码二,所以如果我带上一些有效的 ID,即某个实际航班 F,并访问斜杠航班斜杠那个 ID。

好吧,这应该有效,它应该有一个状态码 200,然而,如果我测试一个无效的航班页面,这是一个 Django 命令,将获取 ID 的最大值,这个 ID _ _ max,给我获取所有存在于数据库中的航班的最大 ID,如果我继续并。

尝试获取斜杠航班斜杠最大 ID,加一,所以一个比我数据库中已有的航班都要大一的数字,这应该不行,因为不应该存在一个不存在的航班页面,所以在这里我可以断言等于返回的状态码。

等于 404,因为我希望该页面返回 404,最后我还可以检查有关乘客页面的各种不同上下文,因此在这种情况下,我已向数据库添加了一些示例乘客,因此在我的测试中,我可以操纵数据库,添加数据到数据库中。

确保当你计算航班页面上的乘客数量时,这个数量将是,比如说 1,因此我们可以编写多种不同的测试,以验证我们网络应用程序的不同部分,我希望验证的不仅仅是我们的。

数据库按我们预期的方式工作,数据库在我们的模型功能方面是如何工作的,涉及到模型之间的关系,例如航班与机场之间的关系,但我们也可以模拟一个 GET 请求,模拟对页面的请求,并验证状态代码。

返回的内容应该是我们预期的,验证页面的内容是否包含正确的内容,验证传递给模板的上下文是否正确,然后所有这些都可以通过执行类似 Python managed PI test 的命令来验证,然后运行它,我们会看到在这种情况下所有测试都通过了,这意味着。

一切似乎都很好,至少目前是这样,因此,这在我们的网站程序变得越来越复杂时非常有用,因为我们有多个不同的模型和多个不同的路由,能够测试以确保如果我们在程序的一个部分进行更改,不会破坏另一部分的内容。

这部分也非常有用,但我们还没有能够测试的部分是任何完全在浏览器中发生的交互。我已经能够测试很多在服务器端发生的事情,并且要记住,Django 完全是为了编写这个作为 Web 的 Python 应用程序的 Web 服务器。

服务器监听来自用户的请求,使用这些不同的视图和模型处理这些请求,然后提供某种响应,我们可以测试该响应的内容,例如状态代码是否与我们预期的相符,内容是否匹配。

我们期望它的表现,但有时我们确实希望模拟用户点击按钮和尝试在页面上做事情,并确保该页面的行为符合我们的期望,即使我们没有使用 Django,或者即使我们仅仅处理前端。

创建一个我们可能想要测试的示例 JavaScript 网页,使用这些想法,自动化测试我们应用程序各个部分的过程,以验证它们确实正常工作。现在我将退出航空公司目录。

我将创建一个新文件,称为 counter HTML,并回忆一下之前我们使用 JavaScript 创建了一个计数器应用程序,计数器应用程序的功能是让我点击一个按钮,例如增加或计数按钮,它会将数字从 0 增加到 1,再到 2,3,4 等等。

接下来我将在这里做同样的事情,我们将增加一点复杂性。并给自己添加一个增加按钮和一个减少按钮,以便将数字减少一,所以我将继续从我们通常的 doctype HTML 和 HTML 标签开始,我将为这个页面命名为计数器。

现在在这个页面的主体内部,我将开始添加一个大的标题,显示0,然后在其下方创建两个按钮,一个按钮是加号,另一个按钮是。

减号符号,所以现在没有JavaScript,但这实际上不会工作,但我可以。

我会继续打开counter.html。

我现在看到我有0,还有加号和减号按钮,尽管这些加号和减号按钮现在并不实际执行任何操作,所以我们来让它们做些事情,给这个按钮一个ID叫increase,以便我可以稍后引用它,并给这个按钮一个ID叫decrease,以便我可以在点击时引用它。

在我的JavaScript中,现在在网页的头部,我将添加一个script标签,我希望在页面加载完成后开始运行一些JavaScript。为此,你会记得我可以说,文档添加事件监听器DOM内容加载,表示一旦DOM加载完成就运行这个函数。

这页面的内容已加载,以我预期的方式,接下来我该怎么做呢,我首先需要一个变量,比如let counter等于零,然后我可以说,好吧,文档查询选择器增加获取ID为increased的元素,那是加号按钮,当你在工作时。

让我们添加一个事件处理程序,以回调函数的形式,当点击增加按钮时将调用这个函数,我想做什么呢,我想增加计数器,继续说counter加加,然后我将更新这个h1。

当前包含零文档查询,选择器h1获取h1元素。我将继续更新它的内部HTML,并将其设置为任意值,计数器的值就是这样,然后我会对减少按钮的文档查询选择器做同样的事,获取ID为decreased的元素。

那就是减号按钮,点击后将运行这个回调函数,做同样的事,首先将计数器减一,然后获取h1元素,将其内部HTML设置为计数器,我想这应该有效。

我可以通过打开counter.html来验证这一点,我会刷新页面,我可以测试这些按钮,测试加号按钮,好吧,这似乎有效,值增加了一,测试减号按钮,确保那会减少值,这一切似乎运作良好,但当然。

这需要我与这个页面互动,我必须打开页面,我必须点击这些按钮,这不是我模拟像get请求或post请求的方式,没有服务器接收请求并返回一二三四的响应,这一切都发生在浏览器中。

所以我想要的能力是某种浏览器测试,有很多不同的框架可以做到这一点,其中最受欢迎的是selenium,这将让我可以定义一个测试文件,使用单元测试或类似的。

这个库可以有效地模拟一个网页浏览器,可以模拟一个网页浏览器,并模拟用户与该网页浏览器的互动,使用我们称之为的webdriver,这将允许我通过代码控制浏览器正在做什么以及它是如何进行的。

用户正在与这个程序互动,那么这将如何顺利进行呢?我将继续进行测试,打开Python解释器并让。

从tests导入star,这将让我访问几种不同的东西,但你会注意到,首先,它正在做的事情是。因为我正在使用这个webdriver,它将给我一个网页浏览器,我在这里使用Chrome,但你可以使用其他浏览器,请注意这里。

Chrome告诉我,Chrome正在被自动化测试软件控制。Chrome有能力允许我使用自动化测试软件,比如Python代码,控制网页浏览器正在做的事情。所以我在这里能做的第一件事是告诉Chrome打开我的网页。

结果是,为了做到这一点,我需要获取那个页面的URI或统一资源标识符,只是一些字符串,可以识别该页面。我定义了一个名为file URI的函数,它可以获取这个目录中特定文件的URI,所以我将说我想打开counter dot HTML,我需要。

获取它的URI,但现在我可以说driver dot get URI,意味着像告诉这个webdriver,这部分Python程序正在控制网页浏览器,我想获取这个网页,仿佛用户已访问该网页并在输入URL后按下回车。所以我说。

driver Doug get URI,我继续按下那个,你会注意到右侧,Chrome已经加载了这个页面,我实际上是在用我的Python程序控制这个网页浏览器窗口。我说driver Doug get URI,意味着继续打开counter to the HTML页面。

然后在这个测试窗口中,Chrome被打开,在我的Python程序中,使用这个网页驱动程序,我有能力看到用户在打开页面时看到的相同内容。那么用户在打开页面时看到什么呢?他们会看到,例如,页面的标题,所以我可以说。

类似于driver.title,可以看到,这个页面的标题是counter,这实际上是页面的标题,但我可以在我的Python程序中通过检查driver标题来验证这一点,通过获取我的网页驱动程序,查看当前页面的标题。

那就是counter,如果我查看driver.page source并按回车键,我看到的内容以字符串格式呈现,所以有点杂乱,是这个页面的HTML内容,你会注意到像DOM内容加载在这里,我自己的点击处理程序,这是我的h1标签,上面写着0,真的很杂乱。

因为它被表示为一个Python字符串,而这些反斜杠结束符则表示像换行符这样的行断开,但这就是内容,这实际上就是浏览器所获得的全部信息,浏览器接收这些信息并知道如何以更美观的图形化表示来渲染它,这更易于理解。

这对于用户查看是有帮助的,因此能够理解,但这基本上是我的网页浏览器在加载网页时实际上获得的所有内容。那么我可以从这里做什么呢?我想在这个页面上模拟用户的行为,当然我可以获取页面并查看标题,但我。

想要模拟点击增加按钮,例如,所以首先我需要做的事情就是获取增加按钮,为此我可以说driver.get或通过ID查找元素,我可以尝试找到HTML元素的多种方法,但我想。

通过ID查找HTML元素,我知道增加按钮的ID是increase,例如,如果我通过ID找到元素,那么让我找到ID为increase的元素,好的,看起来我为我的网页驱动程序得到了一个网页元素对象,我会继续保存。

这个元素被表示为一个名为increase的变量,因此我现在有一个名为increase的变量,代表我的网页驱动程序在网页上找到的增加按钮,这实际上和你作为人类浏览网页寻找增加按钮是一样的。

驱动程序执行相同的操作,不过它不是根据按钮的外观寻找按钮,而是根据其ID寻找按钮,因此这是另一个理由,为什么给你的HTML元素分配ID是有帮助的,以便在你需要找到该元素时,这非常有用,可以进行引用。

通过它的名称获取该元素,但现在我,有一个按钮,我可以模拟用户与该按钮的交互。我可以说,比如增加点点击,表示我希望模拟用户点击该按钮,以查看用户获得的任何返回结果。

点击那个按钮,所以增加点击,我按回车,你会注意到。发生的是数字增加,从0到1,就像我按下了,按钮。其实我所做的只是说增加,点击,去按增加按钮,让浏览器进行,正常的响应。

我们的响应是获取JavaScript事件处理程序,当点击时。处理程序并运行回调函数,这样可以增加计数器的值并。更新h1,所以我可以说增加点,点击以模拟增加该变量的值。实际上这只是一个,函数调用,这意味着我可以包括。

在任何我想要的其他Python结构中,如果我想重复某个操作,比如说25次并按下,按钮25次,我可以说for I in。range 25,去按增加按钮,很快它将快速点击增加,按钮,我将看到结果。

所有这些交互,所以我可以,仅通过使用Python解释器来模拟用户交互。同样,如果不是增加一个或,两个,而是减少,那么我将执行。相同的操作,我会说减少,等于驱动程序点查找元素通过ID,让。我们获取减少元素,该元素的ID为减少,然后说减少点点击。

将模拟我按下减少按钮,再按一次,我每按一次,都会减少一个。如果我想将其减少到零,那么我只需执行20次for I in range 20,去减少点,点击,这将继续进行。

将此帐户减少到,零,通过模拟用户按下一个。按钮20次,你会注意到,发生的速度非常快。我可以通过说for I in range,100增加点击来模拟100次增加按钮的按下,且你会很快,看到这个数字被增加一百次。

可以比人类快速地点击那个加号按钮,这些,测试不仅可以自动化,而且可以比任何人类都要快,以便测试这种行为。那么我们如何将这个想法,融入我们编写的实际测试中呢?

类似于单元测试框架,允许我定义各种不同的函数,以测试我Web应用程序行为的不同部分。为此,让我们再看看tests PI内部的测试。这里再次是那个文件URI,函数,该函数包含。

唯一目的是获取文件并获取其 URI,我们需要 URI 才能打开它,然后我们继续获取 Chrome 网页驱动程序,它将允许我们在 Chrome 中模拟交互。为了获取 Chrome 的网页驱动程序,你确实需要获取 Chrome 驱动程序。

它与 Google Chrome 本身是分开的,但 Google 确实提供了它,并且其他网页浏览器也提供了等效的网页驱动程序。如果你想测试其他浏览器中的效果,因为不同的浏览器可能表现不同,这可能很有用。

确保所有内容不仅在 Google Chrome 中有效,而且在其他你可能期望用户使用的浏览器中也能正常工作。因此,我在这里定义了一个类,它再次继承自单元测试,一个将定义我希望在此上运行的所有测试的测试用例。

在这里我有一个名为 test title 的函数,它将首先在 HTML 中获取计数器,它将打开该页面,然后仅仅断言相等。让我们确保页面的标题实际上是计数器的值,这正是我所期望的,因此我可以编写一个测试来测试这个情况。

我通过查找 ID 为 increase 的元素来测试增加按钮,并点击该按钮以模拟用户按下加号按钮来增加计数器的值。那么我想检查什么呢?我想检查当你通过标签名 h1 查找元素时,所以通过标签查找元素。

名称类似于通过 ID 查找元素,除了它不是通过 ID 查找,而是查看标签,并且只是一种元素不是 h1。所以在这里我说继续,获取 h1 元素并访问它的文本属性,这意味着它包含在这两个 h1 中的内容。

标签我希望是数字 1,因此我将断言这等于 1。同样,我也可以对减少按钮执行相同的操作,找到 ID 为 decrease 的元素,点击该按钮,然后断言相等,找到 h1 元素,并确保其内容。

等于负1的数量为。

这个最终测试只是测试多次,三次。我将按下增加按钮,并确保在我按下增加按钮三次后,当我检查 h1 并查看其文本内容时,答案确实应该是三。

现在我应该能够通过运行 Python tests.py 来测试这段代码,而这将打开一个网页浏览器,你很快会看到在我屏幕上闪过的所有测试。我们测试增加 1,我们测试减少 1,然后我们测试类似的内容。

在确认标题正确后,我们的数量增加了三倍,然后我们可以看到这是我们在这段时间内运行的四个测试的输出,一切都很好,没有测试失败,但如果其中一个测试失败了,我们将看到不同的输出,所以让我们。

想象一下,如果我的减少函数有一个bug,比如说减少函数实际上没有工作,那这个bug可能是什么样子?也许我忘了说counter--,或者更可能的情况是,我已经写了增加函数,决定。

快速添加减少函数,我想我只是复制粘贴一下,像是复制增加事件处理器,减少事件处理器基本上是一样的,只是我需要查询减少,而我可能只是做了这个,忘记将加加改为减减,这是一个bug。

如果你在从一个地方复制粘贴代码时不够小心,可能会发生一些问题。现在当我运行这些Python测试时,我会看到模拟结果一大堆被模拟,当我回去检查测试的输出,看看实际发生了什么时,我发现我们似乎有。

这里出现了一个断言错误,断言失败是在测试减少函数时发生的,当我尝试断言h1元素中的值为-1时,因为1不等于-1,所以这是这个断言错误的值。这在某种程度上是有帮助的,相比于仅仅断言,它告诉你。

你知道有一个断言错误,但在单元测试中,我们实际上可以看到如果我断言两个事物相等,它会告诉我这两个事物是什么。它告诉我h1的实际输出是1,但我期望的是-1,所以它准确地告诉我差异在哪里,我知道。

出于某种原因,它是1而不是-1,这可能是我解决问题的线索。我可以通过查看我的减少事件处理器来解决这个问题,发现哦,这实际上是在增加计数器,而不是减少,改为加。

加改为减 - 现在重新运行我的测试,查看我在chromedriver内部的所有测试,我们运行了四个测试,这次一切都很好,所以这次我的所有测试似乎都通过了,这些是一些可能性,能够测试我们的代码,特别是利用单元测试,这个库我们可以使用。

Python可以进行各种类型的断言,关于我们希望代码的某些条件是否为真或假,单元测试包含了许多有用的方法,以便执行这些断言,我们可以说,比如我想断言两个事物相等。

对彼此的比较,我们看到有一个对应的概念,用于确保两个事物不相等。我们也看到assert truefalse,但还有其他一些,例如assert inassert not in,如果我想要验证。

某个元素在某个列表中,或者某个元素不在某个列表中。我们还有其他断言方法,可以用来验证我们程序的某个部分或我们网络应用的某个部分确实按照我们希望的方式运行,我们可以将这种想法整合到。

我们看到多种不同类型的测试将其整合到Django中,使用Django的单元测试来验证我们的数据库是否按预期工作,以及我们的视图是否按预期工作,并在用户发出请求后提供正确的上下文。

我们的网络应用程序中还有单元测试的应用。不论是否使用框架——当我想进行基于浏览器的测试时,我会测试用户的网络浏览器内部,当用户点击这个按钮时,JavaScript是否按我期望的那样运行。

我期望它按预期工作,我不需要,特别是使用JavaScript来进行这些测试。我没有使用Python编写这些测试,而是使用单元测试来验证点击这个按钮的想法,以及验证我们得到的结果是否是我们期望的。

我们将继续查看CI/CD,持续集成和持续交付,这两个概念在软件开发领域被视为最佳实践,涉及代码如何编写,尤其是由团队或小组编写的代码如何协同工作,以及最终如何将代码整合在一起。

交付和部署给那些使用这些应用程序的用户,因此CI指的是持续集成,即频繁合并到某个代码库的主分支,例如git代码库。

然后在代码被推送时自动运行单元测试。那么,这通常意味着什么呢?过去,你可能会想象,如果多个人同时在某个项目上工作,而每个人又在处理该项目的不同功能或不同部分。

在每个人完成这些功能后,我们准备发布一个新的网络应用版本或软件产品的新版本,那么每个人都必须将这些不同的功能结合在一起,并最终弄清楚如何将它们整合。

尝试将该程序交付给用户,这往往会导致问题,特别是如果人们同时在进行不同的大改动,它们可能不互相兼容,各种不同的更改之间可能会存在冲突。

等待每个人完成工作后再将功能合并在一起,并交付并不一定是最佳实践,这就是为什么越来越多的团队开始采用持续集成系统,因为有一个在线的存储库。

保持代码的官方版本,每个人都在自己的版本上工作,例如,可能在自己的分支上,但这些更改经常合并回同一个分支,以确保这些增量更改能够进行,从而降低出现问题的可能性。

程序经历了两个非常不同的路径,因此更难将这两条路径合并在一起,除了经常合并到某个主分支外,持续集成的另一个关键思想是自动化单元测试,在此情况下再次进行单元测试。

这涉及到我们程序的想法,我们运行一系列测试来验证我们程序的每个小部分,以确保网络应用程序按预期运行,而单元测试通常指测试我们程序的特定部分,以确保每个组件按预期工作。

还有更大规模的测试,例如集成测试,确保从用户请求到响应的整个路径在某个管道中正常工作,但测试有各种不同类型,重要的是确保每次有新的更改。

在合并到主分支或有人想要将更改合并到主分支时,这些测试会被运行,以确保没有人对程序的某一部分做出更改,而破坏程序的其他部分,在足够大的代码库中,任何一个人都无法准确知道某一特定更改对程序其他部分的影响。

他们会遇到一些不可预见的后果,而该程序员可能知道或不知道,因此,假设单元测试是全面的,并涵盖程序的各种不同组件,这就是单元测试的优势。

程序的好处在于,无论何时有人进行更改并尝试将该更改合并到主分支中,根据持续集成的实践,如果它未通过测试,我们会立即知道,因此该程序员可以回去修复,而不是等待。

直到所有内容合并在一起,然后运行测试,意识到某些东西无法正常工作,并且不确定从哪里开始。我们不知道错误在哪里,哪个更改导致了错误,如果一切以更增量的方式合并,那么发现这些错误会更容易,假设。

有良好的测试覆盖率,以确保我们考虑到这些不同的可能性,持续集成指的就是这个理念,频繁且增量地更新主分支,并确保测试确实通过,这与一个相关理念密切相关。

持续交付涉及软件实际如何发布给用户,网络应用如何部署。有几种模型可以思考,关于某个程序或网络应用如何被部署。

你可能会想象发布周期可能相当长,人们在某个软件开发团队上花费几个月时间进行各种不同的功能开发,当他们对所有新更改感到满意时,他们就会发布网络应用的新版本,但特别是对于正在进行中的网络应用。

不断变化的情况,有大量用户在迅速变化。一个相当受欢迎的概念是,持续交付,指的是有更短的发布周期,而不是在某个漫长周期结束时立即发布某些东西,你可以在短周期内每天发布。

每周进行一次,以便说,继续逐步进行这些更改,无论新的更改是什么,合并到主分支。我们可以立即发布这些,而不是等待更长时间来进行发布,这又促使某些。

好处是能够逐步进行更改,例如,如果出现问题,你可以更快了解出了什么问题,而不是一次性进行大量更改,这样如果出问题就不一定清楚出在哪里,并且这也允许新功能迅速推出给用户。

用户可以更快地获得服务,尤其是在一个竞争激烈的市场中,许多不同的网络应用相互竞争,能够快速发布新功能是相当有帮助的,因此持续交付就是关于短发布周期的理念,而不是等待很长时间。

新版本发布,增量发布,随着新功能的引入,这与持续部署的理念密切相关,CD有时也代表持续部署。持续部署在精神上与持续交付相似,但部署是自动发生的。

这意味着人不必说,好吧,我们做了几处更改。我们可以继续部署这些更改,在持续部署中,只要这些更改被做出,应用程序向用户部署的流程就会自动进行,从而减少人类需要做的事情。

思考并允许这些部署更快地发生。问题是,哪些工具可以让我们实现持续的集成和持续交付。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P23:L7- 测试与前端CI/CD 3 (github与docker应用) - ShowMeAI - BV1gL411x7NY

为了使其更简单,我们可以使用一些技术,市面上有多种持续集成工具,但其中之一是github最近推出的github actions。github actions允许我们创建这些工作流,例如我们可以说每当有人推送到一个github仓库时,我们会自动运行希望在该代码库上执行的任何测试。

在git仓库中,我希望某些步骤能够进行,例如检查代码格式是否良好,如果公司有期望所有程序员遵循的样式指南,你可以设置一个github action。

每当有人推送到一个仓库时,你会有一个自动检查代码是否符合样式指南的动作,以确保代码格式良好、注释完整等。此外,你可能还会有一个github action来测试我们的代码,以确保每当有人推送代码时。

github actions可以通过定义某些工作流来实现这一点,所以我们稍后将使用github actions来自动化这一过程。

运行测试的目的是为了让程序员在编写完代码后,能够测试自己的代码并确保其正常工作,我们可以通过确保每次有人推送到github仓库时,自动运行一些github操作来强制执行这一点。

这将处理在该程序上运行测试的过程,我们会立即通过github可能发送给你的电子邮件知道某个特定测试失败了,而你在每次推送到该仓库时都会知道。那么,这些工作流是如何结构化的,它们的语法是什么?

它们使用一种称为llamo的特定语法,这是一种配置语言,可以用于描述各种不同工具和软件的配置,github actions恰好使用它,其他技术也在使用。

Mille是一种文件格式,其数据结构类似于键值对,类似于JSON对象或Python字典,格式是键的名称后跟冒号,然后是其值,键的名称后跟冒号,再后跟一个值。

值不一定只是一个单一的值,它可以是一个值的序列,比如值的列表,这些用“-”表示,指示列表项1、项2、项3,除了单个值和列表项之外,这些思想和列表也适用。

这些值可以相互嵌套,你可能会有一个键指向另一组与值关联的键,导致其他与值关联的键集合,这与JSON对象的键值对表示方式非常相似。

在JSON对象中也可以有嵌套的JSON对象,同样,我们可以有嵌套的键值对,作为某个键的值 - 所以我们将看看在创建一些将运行一些GitHub Action的GitHub工作流时,这实际上是什么样子的。

比如,让我们回到airline zero,我在一个.github目录中定义了一个名为workflows的目录,在其中有一个CI.yml文件,文件名可以是任何名称,.yml.yaml是YAML文件的传统扩展名。现在我将打开CI.yml,这就是如何。

这个工作流应该如何工作,我给工作流命名为testing,因为我想让工作流做的就是测试我的航空应用程序。然后我指定一个on键,表示这个工作流何时运行,我说在push时,意味着每当有人将代码推送到GitHub时,我们希望。

每次工作流运行时,包含一些作业,那么这些作业是什么?在我尝试推送到这个repository时,应该发生哪些任务。我定义了一个名为test的作业,这是我为自己选择的名称,你可以为作业选择任何名称,现在我需要。

指定两个事项,关于作业应该发生什么。第一件事是需要指定它将在什么机器上运行,GitHub有自己的虚拟机(VMs),我想在其中一台虚拟机上运行这个作业,它们支持不同的操作。

系统,这里我只是说继续在最新版本的Ubuntu上运行,这是我希望这个测试运行的最新Linux版本。接下来,我为作业指定应该发生的步骤,现在我可以指定当有人尝试测试项目时应该发生哪些操作。

当我尝试运行这个作业时,我在这里使用一个特定的GitHub Action,这是一个由GitHub编写的GitHub Action,名为actions/checkout。这将检出我在repository中的代码,并允许我运行在该代码上操作的程序。

如果你愿意,可以编写自己的GitHub Actions,但我们现在需要做的就是检出代码,查看我刚刚推送到的分支,然后我将运行Django单元测试,这只是我知道在这一特定步骤中发生了什么的描述。

我想运行的内容是,我将首先安装Django,因为我需要安装Django来运行所有这些测试,但如果有其他需求,我可能还需要安装那些需求,不过航空程序相对简单。

我们运行测试所需的只是Django,所以我将安装Django,然后运行Python 3 Manish PI test,我想测试所有测试,我可以通过提供这个managed up high命令来运行所有测试。

这个特定应用的配置文件将指定一个工作流程,该工作流程表示每次我推送到GitHub仓库时,我希望的结果是检查我的代码。

系统将检查我的代码,并运行这些命令,安装Django,然后测试我的代码,并在执行后将响应反馈给我,所以让我们测试一下,特别是让它在一个测试将失败的程序上运行。

我可能会说,例如,让我们进入flightsmodel stop pie,回到我之前的is valid flight函数,并将其更改回之前不起作用的版本。

如果目的地或持续时间大于零,我们将其视为有效,但我们知道这是错误的,不应该这样。看看我会怎么做,我会先说get status,看看有什么改变,似乎我已经修改了models pi,这很合理。

我将添加所有可能修改的文件,使用add dot,然后提交我的更改,我将使用错误的valid flight function进行提交,现在我要将我的代码推送到GitHub上。我已添加、提交并推送我的代码。

如果我进入GitHub,查看我的航空公司合成器II,您会注意到我们大多在处理这个代码标签,但GitHub还给我们其他标签,当你开始考虑在较大团队中工作时,这非常有用。

问题是人们报告某些东西不太对劲或对该代码库的功能请求的方式,问题可能会维护该特定仓库所有待处理事项的列表,即我们仍需处理的事项。

这些问题已经解决,可以关闭,所以我这边没有问题。拉取请求是人们试图将代码的一部分从一个分支合并到另一个分支,因此在一个较大的项目上,你可能不希望每个人都同时将东西合并到主分支。

人们在各自独立的分支上工作,当他们对自己的代码感到自信和满意时,他们可以提出拉取请求以将他们的代码合并到主分支,这样就可以实现各种其他功能,例如某人提供代码审核的能力。

代码撰写评论并提出对特定代码部分应进行的更改建议,在它被合并到主分支之前,这是与GitHub仓库或任何其他大型项目合作时的另一个常见做法。

源代码控制中有这样一个代码审核的概念,通常你不希望只有一个人进行更改而没有任何人查看这段代码,而是希望有第二双眼睛能够审查,确保代码是正确的,确保它是高效的,并且符合实践。

应用程序正在使用,因此拉取请求对此非常有帮助。然后这个第四个选项卡代表GitHub Actions,这些是我可能想在这个特定仓库上运行的各种不同的动作或工作流程。我们在这里看到的是,如果我进入动作选项卡。

现在我看到的是我最近的测试动作,所以每次我推送时,我都会得到一个新的测试动作,这个是29秒前的,我会点击它,看看里面的内容,好的,这是我运行的测试项目的作业,你会注意到左侧有一个大红色X。

工作流程意味着出现了问题,因此我想知道是什么出了问题。我会点击测试项目,在这里,这些都是我们实际运行这个特定作业时发生的所有步骤。首先作业设置,然后检出动作继续检出我的代码,因为我们需要。

访问我的代码以便能够运行它,这里是我定义运行Django的步骤。单元测试将要安装Django并运行这些测试,它旁边有一个X,表示出了问题,我在下面的注释中看到一个失败,所以到处都是,github试图告诉我这一点。

出现了问题,它在两分钟前失败了,我会打开这个,我所看到的第一件事是我们安装了Django,似乎运行得还不错,但在下面你会看到运行这些单元测试的输出,显示失败,现在我可以看到。

这里是失败的单元测试,我们失败了无效的航班目的地测试,我们失败了无效的航班持续时间测试,正如之前我在GitHub用户界面中看到的那些断言错误,我可以看到它们是真的。

我尝试运行这个特定的测试套件,现在其他也在这个库上工作的人员也可以看到这些测试的结果,并可以提供建议,给出我可能修复代码的方式,但现在我知道这个特定的测试失败了,如果我继续。

回到这个GitHub库的主代码页面,我会看到下方的情况。

这个提交旁边有一个小X符号,这个小X符号告诉我,我上次尝试提交时出了问题,他们在出现错误时运行了工作流,因此我会立即看到这个提交的情况,并且可以回头查看历史记录。

我会查看提交,看看哪些是正常的,哪些有导致某种问题的倾向,所以这个显然造成了问题,我们知道为什么,因为这个条件或者其他什么原因,所以我可以修复它。我会改变顺序并且添加提交。

我会修复有效航班检查,如果我获取状态来检查现在发生了什么,我的主分支领先一个提交,这正是我所期望的,现在我会推送代码到GitHub。

推送这个更新,希望我们能够通过工作流,现在我回到报告。我刷新页面,这里是我的最新提交“修复有效航班检查”,你会注意到这里有一个橙点,而不是之前的红色X,这个点意味着测试当前在等待中,工作流正在进行中。

因为GitHub需要一些时间来启动虚拟机,初始化工作以检出我的代码并运行所有测试,但如果我回到Actions标签,我会看到这次测试时获得了绿色勾号,一切似乎都正常。

我去项目中查看,现在我注意到“运行Django单元测试”旁边的绿色勾号意味着单元测试也通过了,如果我打开它们,现在我看到底部有与我之前在自己的机器上运行单元测试时相同的输出,我们运行了十个测试,一切正常,这告诉我这些测试通过了。

GitHub Actions能够允许某些工作在你推送代码、提交拉取请求或在GitHub库上发生的各种不同操作时进行,这对实施持续集成的想法非常有帮助。

集成意味着当你将某个开发者分支的代码合并到主分支时,可以确保每个人的代码合并时都能通过测试。你可以添加规则来阻止任何人将未通过测试的代码合并到主分支。

确保合并的任何代码都能通过所有测试,这确实有助于开发周期,使得快速更改变得更加容易,但在快速进行这些更改时,我们不会失去。

在我们的代码中保持准确性和有效性,使我们能够确保代码通过这些测试的方法就是自动化地运行所有测试。因此,除了持续集成之外,我们现在谈论这个持续交付的理念,这种短暂的应用周期。

我们希望能够快速将应用程序部署到某种网络服务器。当我们将应用程序部署到网络服务器时,需要考虑的事项包括确保在我们的计算机上运行良好的程序也能在网络服务器上正常工作。

这可能会引发头疼和各种配置问题,因为你可能想象,所使用的计算机并不一定与云端的计算机相同,托管你网络应用程序的服务器上的计算机可能运行着不同的操作系统。

系统可能安装了不同版本的Python,如果你在自己计算机上运行某些包,那么在服务器上这些包可能并未安装,因此我们会遇到各种配置问题,你可能正在开发代码并意识到。

如果在服务器上不工作,这可能是由于你计算机上发生的事情和服务器上发生的事情之间的某种差异。如果你在一个更大的团队中工作,这种情况会变得更加棘手,你和其他多位开发者一起工作在一个软件项目上。

你们每个人都有不同版本的各种包或库安装,这些不同的版本具有不同的特性,可能并不能彼此兼容,因此我们需要某种方式来高效有效地部署应用程序。

需要对环境的版本进行标准化,确保每位开发者在相同环境下工作。一旦我们部署应用程序,它将在相同的环境中运行,解决方案在于一个。

有很多可能的选项,但一个选择是利用像 Docker 这样的工具,这是一种容器化软件,通过容器化软件,我们所讨论的是,当我们运行一个应用程序时,不是仅仅在你的计算机上运行,而是。

我们将在你的计算机上的一个容器内运行它,每个容器将包含其自己的配置,安装某些软件包,包含某些特定版本的软件,配置方式完全相同,并通过利用一个工具。

像 Docker 这样的工具可以确保,只要你提供了正确的启动和设置这些容器的指令,那么如果你正在开发应用程序,而你的一位同事也在同一项目上工作,只要你们使用相同的指令。

关于如何设置 Docker 容器,你将会在相同的环境中工作,如果一个软件包在你的计算机上安装,那么它也会在你同事的容器中安装。这种工作方式的好处与持续交付的理念相结合,当你。

如果你想将你的应用程序交付和部署到互联网上,你可以在完全相同的容器设置内运行你的应用程序,使用完全相同的一组指令,这样你就不必担心确保所有正确的软件包和版本的问题。

事实上安装在服务器上的 dock 哦,没错可能会让你想起虚拟机的概念,或者说 VM,如果你对这个概念熟悉的话,GitHub 使用。例如,VM 在运行其 GitHub actions 时,实际上是不同的 VM。一个 VM 实际上是在运行一个完整的虚拟计算机,拥有自己的虚拟操作系统。

系统中的库和应用程序运行在你的计算机上,所以虚拟机最终会占用很多内存,占用很多空间,而 Docker 容器则稍微轻量一些,它们没有自己的操作系统,仍然是在上面运行的。

主机操作系统,但在这之间有一个 Docker 层,跟踪所有这些不同的容器,并为每个容器跟踪,以便每个容器可以拥有自己独立的一组库,运行在其上的一组二进制文件和应用程序。

容器化的优势在于这些容器比完整的虚拟机更轻量,但仍然可以保持自己的环境一致,这样你就可以放心,如果应用程序在 Docker 容器中运行,你可以拥有那个 Docker。

在你的计算机上运行的容器,在别人的计算机上,或者在服务器上,以确保应用程序将以你实际预期的方式工作。那么我们如何配置这些不同的 docker 容器呢?为了做到这一点,我们将编写一个所谓的。

docker 文件,所以为了做到这一点,我将继续进入 airline 1,并打开这个 docker 文件。这个 docker 文件描述了创建 docker 镜像的指令。docker 镜像代表了我们可能希望在容器和 Sanon 中包含的所有库和其他已安装项。

基于那个镜像,我们能够创建一堆不同的容器,这些容器都基于同一个镜像,每个容器都有自己的文件,并且可以在其中运行 web 应用程序,所以这个 docker 文件例如描述了我如何创建一个容器,这个容器将运行我的 Django web。

应用程序,所以我首先说 from Python :three,这恰好是我将基于这些指令的另一个 docker 镜像,这将是一个已经包含安装 Python 3 的指令的 docker 镜像,安装其他相关包,这在许多情况下是有帮助的。

编写 docker 文件时,你会基于一些已存在的 docker 文件,所以这里我说继续使用 Python 3。现在我想做什么来设置这个容器呢?我想把我当前目录中的所有内容复制到容器中,我必须决定复制到容器中的位置。

容器我将存储在哪里呢?我可以选择在任何地方存储,但我将其存储在 /user/source/app,只是一个我选择的特定路径,它将带我到一个我将存储应用的目录,但你可以完全选择其他东西。

所以我复制我当前目录中的所有当前文件,这将包括我的 requirements 文件、我的 manage type I 文件、我的应用文件以及我所有的设置文件,目录中的所有内容我都想复制到容器中。然后我说 work der 意味着改变我的工作目录。

和在你的终端中使用 CD 移动到某个目录是一样的,我希望将我的工作目录设置为相同的应用目录。容器内部的应用目录现在包含我应用的所有文件,因为我已经将所有这些文件复制到。

现在一旦我进入这个目录,我需要安装我的所有要求,所以假设我把所有的需求,比如 Django 和我需要的任何其他包,放在一个名为 requirements text 的文件中,我只需运行命令 pip install requirement text,然后最后在 docker 文件内。

我指定了一个命令,这就是当我启动容器时应该运行的命令。所有其他的事情将在我们设置这个 Docker 镜像时发生,但当我启动容器并实际想要运行我的 Web 应用程序时,这里是应该运行的命令,我提供了。

实际上就像一个 Python 列表,每个命令中的单词都用逗号分隔。在这里,我指定了当你启动这个容器时要运行的命令是 Python manage.py runserver,我希望它运行在 8000 端口,但我可以选择其他端口。

我希望运行的端口,所以,当我启动这个 Docker 容器时,如果需要,它会按照这些指令执行,确保根据这些指令设置容器,确保我们已安装所有必要的。

确保我们使用的是 Python 3,任何使用相同 Docker 文件的人都可以生成具有相同配置的容器。我们不必担心我与其他人之间的配置差异,他们可能没有与我完全相同的计算机设置。

这点很好,因为它可以在 Mac、Windows 和 Linux 上运行,即使是不同操作系统的人也可以使用配置相同的容器,所有容器的工作方式都相同,以加速这一过程。到目前为止,在构建 Django 应用程序时,我们一直在。

使用 SQLite 数据库,SQLite 数据库只是一个存储在我们应用程序内部的文件,这个基于文件的数据库允许我们创建表、插入行、从中删除行。在大多数生产环境和真实 Web 应用程序中,处理很多数据。

对于用户来说,SQLite 实际上并不是使用的数据库。当有很多用户同时访问时,它的扩展性远不如其他数据库。在这些情况下,通常你希望将数据库托管在其他地方,在某个独立的服务器上,以便能够处理自己的请求。

我们讨论了一些可以使用的数据库连接,比如说除了 SQLite,还有 MySQL、Postgres 等各种不同的基于 SQL 的数据库。所以想象一下,现在我想部署我的应用程序,但我希望使用 Postgres,而不是 SQLite。

例如,作为我想要运行的数据库服务器,这对我来说似乎很复杂,因为除了在一台服务器上运行我的 Web 应用程序外,我实际上还需要另一台服务器在运行 Postgres,以便我可以与其通信。

使用Postgres数据库,这对其他人来说会更加困难,他们可能无法进行有效的工作,另外,可能会很难让服务器以那种方式运行,但Docker的好处在于,我可以在不同的容器中运行这些进程。

我可以有一个容器运行我的网页应用程序,使用这个Docker文件,同时还有另一个容器运行Postgres,只要其他人也能访问同一个用于运行Postgres的容器,他们就可以在与我相同的环境中工作。

还有一个Docker的功能叫做Docker Compose,它允许我们组合多个不同的服务。我希望在一个容器中运行我的网页应用程序,而在另一个容器中运行Postgres数据库。

我希望这些容器能够相互通信,协同工作,因此当我启动应用程序时,如果我想这样做以便在我的计算机上运行这个应用程序,并安装网页应用程序和Postgres,我可以创建一个Docker Compose文件。

文件看起来是这样的,我在这里指定使用版本3的Docker Compose,这里我再次指定,使用YAML文件,就像我的GitHub工作流格式化在YAML中一样,Docker Compose.yml是一个描述我想要的所有不同服务的配置文件。

每个服务都将是自己的容器,可以基于不同的Docker镜像。在这里,我声明我有两个服务,一个叫DB(数据库),一个叫web(网页应用程序),数据库将基于Postgres Docker镜像。

对于Postgres,我不必担心其他人已经为启动Postgres容器编写了Docker文件,而网页应用程序将基于我当前目录中的Docker文件构建,这个Docker文件是我自己编写的,然后在下面我只是。

指定我的当前目录应对应于应用目录,然后我指定在我自己的计算机上运行时,我希望容器的8000端口与我自己的计算机的8000端口对应,以便我可以通过浏览器访问8000端口,并访问我正在工作的端口8000。

容器让我的计算机能够与容器实际交互,因此我可以在我的网页浏览器中打开网页应用程序,并看到所有这些的结果。于是我创建了两个服务,一个是数据库,一个是网页应用程序。

现在让我们实际尝试启动这些容器,我将首先进入我的航空公司目录,然后输入 docker compose up,意味着继续启动这些服务。我按下回车,你会看到我们正在启动两个服务,我正在启动数据库。

服务,我正在启动 Web。

服务,现在由于这一切,我已经启动了应用程序,并在 8000 端口上启动了它。所以如果我访问 0.0.0.0:8000 或者 :8000/flights,这将带我到航班页面,现在这个应用程序不仅在我自己的电脑上运行,还在 Docker 容器内运行。

现在这个页面内没有航班,因为我还没有实际添加任何内容到数据库中。如果我想这样做,我需要进入 /admin 来登录,并继续创建一些样本航班。

但是我还没有登录,因为我需要创建一个超级用户帐户。我不能像在我的航空公司目录中那样使用 Python 管理的命令创建超级用户,因为这是在我电脑的终端上运行的。而我真正想做的是进入 Docker 容器并运行这个。

我该如何做到这一点呢?有多种不同的 Docker 命令可以使用,docker ps 会显示当前正在运行的所有 Docker 容器。我将这一视图缩小一点,看到两行,每行对应一个容器,一行是我的 Postgres 容器。

这正在运行数据库,仅用于我的 Web 应用程序,每个服务都有一个容器 ID,所以我想进入我的 Web 应用程序容器,以便在该容器内运行一些命令,所以我将复制其容器 ID,并输入 docker exec,意味着在容器内执行一个命令。

在容器上执行命令 - 它将使这个交互式,这里是我想执行命令的容器。我想执行的命令是 - 传递 -L 标志,使用 bash 说我想运行一个 bash 提示符。我希望能够与 shell 交互,以便我可以在这个容器内运行命令。

所以我按下回车,现在你会注意到我在用户源应用程序的目录中。这一目录包含了关于这个 Web 应用程序的所有信息。我输入 LS,会看到这个容器内的所有文件,现在我可以说。

像 Python 管理命令一样,我创建超级用户,现在它将让我创建一个超级用户,所以我将在我的 Web 应用程序中创建一个名为 Brian 的用户。我会提供我的电子邮件地址,输入一个密码,现在我们已经创建了一个超级用户,如果你想迁移,这里还可以运行其他命令。

所有迁移我可以说python manage.py migrate,结果发现我已经做过了,因此我实际上不需要再做一次,但你可以在你的计算机上运行的任何命令,现在可以在Docker容器内运行。我将按下Control D。

现在我已创建了超级用户,可以登录到Django的管理界面,现在我可以开始操作这个数据库,它是一个在单独容器中运行的Postgres数据库,好的地方在于我可以同时启动它们。

通过运行像docker compose up这样的命令将它们组合在一起,Docker可以成为一个强大的工具,帮助我们快速确保应用程序在我们预期的环境中运行,确保所有正确的库都已安装。

确保所有正确的软件包已安装,以及我的开发环境与服务器上运行的环境配置相同。这些都是关于如何开发程序的一些最佳实践。现在我们有了工具去做这一切。

我们有很多工具来开发这些网络应用程序。但随着我们的程序变得越来越复杂,测试它们将变得越来越重要,以确保我们网络应用程序的每个不同组件都按预期方式运行。

特别是在较大团队中,CI/CD(持续集成和持续交付)可以利用这种优势,进行增量更改,并确保每个增量更改在网络应用程序上实际有效,然后是CD(持续交付),以便不必等待一次性全部部署。

让我们在Chrome中部署内容,让用户更快地获取最新功能,并更快地发现问题。如果我们逐步部署内容,而不是等待很长时间再去做,我们可以更好地识别出现了什么问题。

这些是在现代软件应用程序开发中的一些最佳实践,不仅适用于网络应用程序,也适用于软件更广泛的领域。下次我们将考虑在尝试开发越来越多用户使用的网络应用程序时可能会出现的其他挑战。

随着程序变大,扩展性和安全性会面临的挑战,以及我们开始使用Python和JavaScript设计网络应用程序时会出现的安全漏洞。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P24:L8- 拓展性与安全 1 (可扩展性,负载均衡,自动伸缩) - ShowMeAI - BV1gL411x7NY

[音乐],欢迎大家回来,继续学习Python和JavaScript的Web编程。对于我们的最后一个主题,我们将探索可扩展性和安全性。到目前为止,在课堂上我们一直在构建Web应用程序,这些应用程序在我们自己的计算机上运行,但如果我们想把这些。

Web应用程序部署到全世界,让互联网上的人们都可以使用它们,那么我们需要在某种Web服务器上托管我们的Web应用程序,这是一种专用的硬件,能够监听Web请求并用我们想要的响应来回应它们。

Web应用程序需要提供服务,当我们这样做时,这会引入一系列有趣的可扩展性和安全性问题,因此今天我们将讨论这些问题,首先从可扩展性相关的问题开始。这些问题是什么,我们该如何解决它们。

我们通过将Web应用程序部署到Web服务器上来部署它们。我这里用这个矩形代表这个服务器,但服务器只是一些专用计算机,是一块监听传入请求的硬件,因此我们将画一条线来表示来自一个。

服务器接收该请求并做出响应,但最终我们的Web应用程序不仅仅会服务于一个用户,如果它变得受欢迎,可能会有许多用户同时使用它们。

尝试同时连接到该服务器的用户。随着多个用户开始同时连接到该服务器,我们就开始处理可扩展性的问题,一台计算机,一台服务器在任何给定时间只能为有限的用户提供服务,因此我们需要提前考虑如何。

我们将解决这些规模问题,但在此之前,首先的问题是这些服务器实际上存在于何处。如今,这些服务器主要有两种选择。

这些服务可以存在于云中,也可以是本地服务器。本地服务器可以想象成公司在其内部墙内运行自己的Web应用程序,或公司拥有的物理服务器,可能在一些服务器机架上。

这些服务器位于一个房间内,因此它们对所有服务器都有非常直接的控制,确切知道这些服务器是什么类型,正在运行什么软件。他们可以物理上查看服务器并进行调试,如果有需要,以确保解决任何问题。

我们正逐渐进入一个云计算日益普及的世界,我们在云计算中,而不是拥有专用的本地服务器,我们拥有一些在云端的服务器,那里的云计算公司,如亚马逊、谷歌或微软,能够运行。

我们可以使用那些由第三方提供的服务器,比如亚马逊、谷歌或微软等,这里在云计算中存在一些权衡,我们对机器本身的控制不再那么直接,因为它们不在本地,我们无法。

我们在物理上操作那些计算机,但我们有一个优势,那就是不需要担心处理公司内部的物理对象,这些公司是我们想要在云端运行代码的服务器。当它在云端时,一切都由其他公司外部管理,我们可以简单地。

我们需要使用那些服务器,随着我们开始获得更多复杂的网络应用程序,这些应用程序需要更多的用户,这些云计算公司可以让我们创建能够扩展的网络应用程序。

随着用户的增多,我们将跨越多个不同的服务器,但我们将在适当的时候讨论这些规模问题。

我们需要问的问题是,在我们拥有这些服务器后,无论它们是本地服务器还是在云端运行的服务器,用户可以使用多少,服务器在任何给定时间实际上可以服务多少用户,这将因资源的大小而异。

服务器的计算能力将取决于处理特定用户请求所需的时间,如果用户请求非常复杂,可能意味着在任何给定时间可以服务的用户会更少,因此有一个有用的工具是。

我们需要进行某种基准测试,分析服务器在特定时间可以处理多少用户,有许多不同的工具可以让我们进行这种基准测试,Apache Bench(也称为A/B)是一个流行的工具。

进行这种基准测试是非常有用的,这样我们就知道一个特定的服务器可以处理多少用户,可能它可以处理50个用户,也可能处理100个用户,甚至更多,但最终会有一个有限的上限,每台计算机都有其有限的。

资源和服务器也不例外,会有一些用户的数量,超过这个数量,服务器就无法处理。那么在这种情况下我们该怎么办呢?如果我们的服务器在任何给定时间只能处理100个用户,但有101个用户同时尝试使用我们的网络应用程序。

需要进行一些改变,我们需要处理某种扩展,以确保我们的网络应用程序可以扩展。我们可以尝试几种不同类型的扩展。一种方法是垂直扩展,这可能是想象中最简单的扩展方式,如果这台服务器。

这对于处理我们需要处理的用户数量来说还不够好。我们可以选择垂直扩展,直接使用更大更强大的服务器,这样就能处理更多用户。尽管这会增加成本,但如果我们需要处理更多用户,就得这样做。

更多用户会带来更大的问题,这种方法相对简单,只需将一台服务器替换为另一台可以同时处理更多用户的服务器。但这也有缺点,毕竟物理服务器的大小是有限制的,能够服务的用户数量也会受到限制。

处理的能力受到物理限制,无法获得更大、更快或更强大的服务器,因此当垂直扩展不足时,另一种替代方案就是水平扩展。水平扩展的理念在于,当一台服务器无法满足需求时。

单台服务器无法服务所有可能同时使用网络应用程序的用户,因此我们可以采取另一种方法,而不是仅仅使用一台服务器。我们可以将其拆分为两台不同的服务器,现在我们有两台服务器。

运行这个网络应用程序时,我们有效地将这个应用程序能处理的用户数量翻倍。原本单台服务器只能服务100个用户,现在如果我们有两台服务器,就能同时服务200个用户。如果你想象一下,100个用户在使用A服务器。

例如,这里有100个用户在使用B服务器,但这也引发了一些我们必须回答的其他问题,那就是这些服务器是如何获得用户的。例如,当用户请求一个网页时,用户是如何被引导到A服务器还是B服务器的,似乎他们需要某种决定。

无论是选择一个方向还是另一个方向,这就是我们可能引入另一块硬件的原因,而这块额外的硬件就是我们所称的负载均衡器。负载均衡器只是另一块硬件,它将位于这些服务器的前面。

换句话说,当用户请求一个网页时,而不是立即将请求发送给这些网页服务器中的一台,请求会首先通过负载均衡器。在负载均衡器中,请求首先进入,然后负载均衡器决定是否发送。

将该请求发送到服务器A或发送到服务器B,这个过程可能比实际处理该请求的成本要低。因此,负载均衡器实际上只是充当调度员,它等待请求的到来,当请求到来时,负载均衡器。

将这些请求导向某台服务器或另一台服务器,你可能会想象一个场景,我们有的不止两台服务器,也许有很多台,负载均衡器将平衡所有这些不同的服务器,而决定将请求发送到哪台服务器的过程。

这被称为负载均衡,而负载均衡器最终就是在执行这个操作。你可能会使用各种不同的方法来执行负载均衡,因此可以直观地想象,负载均衡器如何决定在某个请求到来时,是否应该将请求发送给。

到这台路由器或服务器,还是应该将请求发送到其他服务器?我们的负载均衡器可能采取许多不同的方式,这里只是几个随机选择可能是最简单的选项。假设有一个用户出现并试图向我们的web服务器发起请求。

负载均衡器首先查看用户,并随机将他们分配到处理该请求的不同服务器之一。如果有10台不同的服务器,它会在这10台服务器中随机选择一台,决定哪一台将处理该请求,这有一个优点。

这个过程非常简单,计算机可以快速生成随机数,基于这个随机数,计算机可以将用户分派到一台或另一台服务器,但这可能不是最佳选择,因为如果我们运气不好,可能会导致许多。

在一台服务器上的用户比另一台多,或者可能会导致某些服务器完全未被使用,如果刚好我们没有随机选择到那台服务器。在实际情况中,许多用户都在使用这种负载均衡,概率很高,最终所有的。

这些服务器将被使用,但分配可能不是完全均匀的,因此,另一个你可以采取的方法是轮询方法,在这种方法中,第一位用户会被分配到服务器一,接下来的用户则被分配到服务器二。

比如,如果有五台服务器,你可以说第三个用户去服务器三,第四个用户去服务器四,第五个用户去服务器五,然后第六个用户又回到服务器一,基本上是循环选择1到5的服务器,一旦你为每台服务器分配了一个用户,就会回到起点。

这也是一个相对容易实现的事情,因为你可以在负载均衡器中简单地进行计数,记录我最近分配给用户的服务器是什么,下次请求到来时就将其分配给下一个服务器,然后是下一个服务器。

采用循环方式的方法,在这种方法中,你会遍历所有服务器一次,然后再遍历这些服务器。现在,这可能看起来比随机选择更好,因为它更公平地决定是否将任何特定请求分配给任何特定服务器,但它也存在某些缺陷。

问题在于,轮询可能很好,但如果某些请求比其他请求耗时更长,我们可能也会遇到不幸的情况,耗时更长的请求可能会全部转到某一台服务器,而不是其他服务器,因此我们可能还想尝试其他方法,例如。

比如说连接数最少的情况,这种方法是,当用户发出请求时,负载均衡器应该选择当前连接数最少的服务器,即其他用户和请求中与之连接的活动连接数最少的服务器。

通过选择连接数最少的服务器,你可能更好地平衡各种可能在你的网络应用中发生的请求。虽然这可能在某种程度上表现得更好,但仍然存在一些问题。

这里也有权衡,但计算哪个服务器连接数最少可能会更昂贵,而仅仅说随机选择一台服务器或采用轮询方法,即一二三四五,一二三四五,重复进行,这要简单得多。

然而,所有这些方法天真地还有另一个问题,与会话有关。你会记得,我们在想要存储有关用户当前与网络应用互动的信息时使用会话。当你登录一个网站,比如登录你的电子邮件或。

例如进入亚马逊,然后返回到那个网站或访问该网站上的另一个页面,发出另一个请求。例如,情况并非你必须再次登录,而是当我回到我的邮件账户或再次访问亚马逊时,网页浏览器并没有完全忘记你是谁。

第二次访问亚马逊时,我的邮件账户或亚马逊记住了我上次访问的记录,我有某种会话在跟踪谁登录了,也许还有我在页面上所做的事情的信息,并允许我继续与网络应用互动,即使我正在进行。

多次请求,你可以想象这对这种类型的负载均衡可能会造成问题,但如果我有多台不同的服务器,想象一下如果我尝试登录一个网站,第一次请求时我被指向第一台服务器,我现在在第一台服务器上登录,但随后我又发出另一个请求。

我被重新指向负载均衡器,也许这次负载均衡器决定把我发送到第二台服务器,但如果会话存储在第一台服务器上,第一台服务器会记得我是谁以及我在做什么,那么第二台服务器就不知道我是谁,因此也不会记住我。

我已经登录了这个网络应用,结果可能会提示我再次登录,如果我们再发出另一个请求,我可能会进入另一台服务器,这样我可能会再次被注销,并需要第三次登录。因此,当我们的负载均衡发生但我们没有这样做时,问题就出现了。

负载均衡器并不关心用户在访问同一网络应用的某一页面后又访问另一页面的情况,因为我们希望记住用户上次在这里时的信息。那么我们如何解决这个问题呢?我们如何确保在下一次请求时能够记住我上次被发送到哪个服务器,并再次将我发送到那里?

这种在多台不同服务器之间的负载均衡我们是在会话中进行的,而这里有多种不同的会话感知负载均衡的方法,其中一种方法是这种被称为粘性会话的一般理念。其想法是,当我再次回到负载均衡器时,负载均衡器会记住我是谁,不会让我再次登录同一个网站。

例如,如果我登录到一个网站一次,并被指向第二台服务器,那么下次我访问这个网络应用时,即使我应该根据随机选择或最少负载被指向第三或第四台服务器。

在使用这些其他负载均衡方法时,负载均衡器应该记住上次我访问这个网站时被指向了第二台服务器,因此这次负载均衡器将再次把我指向第二台服务器,这样第二台服务器就会再次包含信息。

因此,粘性会话是解决这个问题的一种方式,但与所有这些方法一样,这将是我们在谈论可扩展性和安全性时反复出现的主题。

在这里存在权衡,粘性会话的权衡是,其中一台服务器可能会比另一台承受更多的负载。如果某台服务器恰好有很多用户不断返回该网站并请求额外页面,而其他页面则以会话感知的方式进行处理。

服务器可能有一些用户决定不再回来。例如,因此在利用率上存在差异,我们的一些服务器可能比其他服务器更繁忙,而我们在它们之间的平衡做得不太好。因此,一种方法是将会话存储在。

数据库而不是在服务器内部存储关于会话的信息,这样如果我被指向另一台服务器,那台服务器就不会记得我是谁,也不会记得我与这个网站的互动信息。如果我们选择在。

在特定内部的数据库中,所有服务器都有能力良好地访问该数据库。那么,无论我被指向哪个服务器,以及负载均衡器决定将我发送到哪个服务器都没有关系。因为无论我最终被发送到哪个服务器,session。

数据库中存储信息,每台服务器可以连接到数据库以查找我是谁,找出我是否已登录该网站,进行识别。因此,这可能是一种方法,另一种方法是将会话存储在客户端。我们稍微谈到了这个关于可以存储的cookie的想法。

网页浏览器可以设置一个cookie,以便在下一次向同一网页应用程序发出请求时,您的网页浏览器能够呈现该cookie。在这个cookie中,您可以存储大量信息,包括关于会话的信息,您可能在cookie中存储。

当前登录的用户信息,例如或其他与会话相关的信息,但如果不小心,这里会有缺陷。有人可能会操纵这个cookie,假装成其他身份。因此,出于这个原因,您可能想要进行某种加密或某种形式的签到。

确保您无法伪造一件事,您不是,但另一个关注点是,随着您开始在这些cookie中存储越来越多的信息,这些cookie在每次请求时都会在服务器和客户端之间来回发送,这可能会变得越来越昂贵。

在客户端和服务器之间来回传递,因此有很多可能的方法,没有一种方法在任何情况下都是唯一正确或最佳的,但有一些事情需要注意和思考,因为我们开始处理这些规模问题,以确保我们有。

有多个可供使用的服务器,以防我们需要它,但同时确保在这样做时不破坏用户体验,不会导致用户登录后突然无法登录。因此,水平扩展给我们提供了这种容量。

拥有多台不同的服务器,这些服务器都可以处理用户请求并作出响应,但合理的问题是,我们现在需要多少台服务器。我们可以使用基准测试来尝试估计这一点,如果我们估计有多少用户。

我们可以基准测试并查看在任何给定时间,单台服务器可以处理多少用户,并根据这些信息推断出我们可能需要多少台服务器来服务所有这些不同的用户,但这可能是。

我们的web应用程序并不总是有相同数量的用户,有时用户可能会比其他时候多得多。例如,你可以想象在新闻机构的网站上,例如报纸应用程序,在发生突发新闻或重大事件时,会有更多用户。

在某些时候,试图同时访问网站的人数会比其他时候多得多,因此一种方法可能是考虑最大值,最大用户数量是多少,可能会在任何给定时刻尝试使用我们的web应用程序,并根据此选择服务器数量。

无论用户数量如何,我们都会有足够的服务器来服务所有这些用户,但如果在绝大多数情况下,用户数量要少得多,这可能不是一个经济实惠的选择,在这种情况下,你会有很多闲置的服务器。

在不需要那么多服务器的情况下,你却仍在支付所有这些服务器的电费,这可能不是一个理想的选择。因此,一个相当流行的解决方案,特别是在云计算的世界中,就是自动扩展的概念,或者你可以使用自动扩展器。

可以说,我们从两个服务器开始,但如果网站的流量足够,如果有足够的人向网站发起请求,也许在高峰期,人们正在使用网站,那么可以扩展,添加第三台服务器,负载均衡器可以处理。

在这三台服务器之间进行负载平衡,如果更多的流量进入网站,更多用户试图同时使用此应用程序,那么我们可以继续添加第四台服务器,并且我们可以继续这样做,大多数自动扩展器将允许你进行配置。

例如,最少和最多的服务器数量取决于在任何给定时间使用你的web应用程序的用户数量,自动扩展器可以根据用户的增减来扩展或缩减,添加新服务器,或者在用户减少时移除服务器。

这样可以为扩展问题提供一个不错的解决方案,你不必担心有多少服务器,它会自动完全扩展。不过,这个自动扩展过程可能需要时间,如果大量用户同时访问你的网站。

同时添加所有这些额外服务器需要时间,因此可能会出现一些权衡,可能无法立即服务所有用户,另一个需要考虑的问题是,随着你添加越来越多的这些服务器。

引入多个服务器就带来了失败的机会,但这总比只有一台服务器好。如果那台服务器失败,那么整个web应用就会完全失效,这就是我们通常所说的单点故障,若此处失败,整个系统就会崩溃。

拥有多个服务器的一个优势是,我们不再有一台作为故障点的服务器,如果其中一台服务器宕机,理想情况下,负载均衡器应该能够根据这些信息知道不再向该特定服务器发送请求,而是平衡负载。

在其余三台服务器之间,现在有一个有趣的问题,即负载均衡器如何知道该服务器不再响应,可能因为某种原因发生了错误,无法适当地处理请求,确实存在多种情况。

你可以通过多种方式实现这一点,但最常见的一种是简单称为心跳,有效地说,每隔一段时间,负载均衡器就会对所有服务器进行ping操作,快速发送请求到所有服务器,并且所有服务器应该能够响应。

根据这些信息,负载均衡器对每个服务器的延迟有了一定了解,即服务器响应请求所需的时间。此外,它还可以获取有关服务器是否正常工作的信息,如果其中一个服务器未能响应ping,那么。

负载均衡器知道服务器可能出现了问题,因此我们可能根本不应该将更多用户指向该服务器。这样可以通过允许多个服务器来解决单点故障的问题,如果其中任何一个服务器失败,负载均衡器可以重新分配负载。

负载均衡器通过心跳了解情况,然后根据这些信息开始将流量重定向到其他服务器。你可能注意到,即使在这个图中,负载均衡器似乎也是一个单点故障,如果负载均衡器发生故障,那么。

没有什么会正常工作,因为负载均衡器负责将流量引导到所有这些不同的服务器,因此即使没有单个服务器存在故障点,这个负载均衡器也似乎是一个单点故障,这绝对是正确的。

你可以想象,如果有多个负载均衡器,而如果一个负载均衡器宕机,另一个负载均衡器可以迅速介入,充当热备份,接管所有原本指向第一个负载均衡器的流量;如果它宕机,第二个负载均衡器就准备好接替它。

执行这种心跳过程,检查第一个负载均衡器,如果一切正常,第二个负载均衡器就不需要做任何事情,但如果第一个负载均衡器出现故障,那么第二个负载均衡器可以介入并开始处理那些请求,将它们引导到所有这些。

单个服务器也是如此,因此,- 这是一个思考单点故障在哪里的另一个机会,并思考我们如何解决这些单点故障,以确保我们的网络应用程序是可扩展的,这样就能处理关于我们可能如何进行的各种问题。

扩展这些服务器,但最终服务器并不是整个故事,实际上在我们的应用程序中,我们大多在编写与数据以某种方式交互和处理的网络应用程序,我们讨论过的多个不同数据库,其中 SQLite 一直是默认选择。

Django 提供给我们的一个解决方案,它仅将数据存储在文件中,但当我们开始扩展我们的应用程序时,通常会将数据库完全分开,拥有一个单独的数据库服务器在其他地方运行,所有服务器都与之相连。

与那个数据库通信,无论是运行 MySQL 还是 Postgres 或其他数据库系统,以及所有有权访问那个数据库的服务器,因此我们需要考虑的问题是如何扩展这些数据库。

这张图片举个例子,你可以想象一个负载均衡器正在与两个服务器通信,但这两个服务器例如都需要与这个数据库进行通信,而任何服务器在任何给定时间只能处理一定数量的请求和用户,数据库也是如此。

可能只处理一定数量的请求和同时连接,因此我们需要开始考虑如何扩展这些数据库的问题,以能够处理越来越多的用户。现在,一个方法是我们可能会尝试的第一件事被称为。

数据库分区,实际上是将大型数据集拆分成多个不同的部分,我们已经看到了数据库分区的一些示例,例如,当我们谈论 SQL 时,我们查看了一张航班表,每个航班都有。

一个出发城市、出发城市的机场代码、目的城市、目的城市的机场代码以及一些。

表示该航班的持续时间的分钟数,我们决定将所有这些数据存储在一个单一表中可能不是最佳主意,因此我们想要将数据拆分成一种分区类型,我们说,好吧,我们只需有一个表,包含所有的机场。

因此,每个机场在这个机场表中都有自己的行,我们还有另一个仅包含航班的表,它不存储所有这些列,而是将两个机场相互映射,对于任何给定的航班,它有一个来源 ID,意味着哪个对象、哪个行在来源中。

airports 表由航班表示,而机场表的哪一行将代表该航班的目的地,因此我们将一个表有效地拆分成多个表,每个表的列数更少,这可能是我们所称的。

数据库的垂直分区,其中我们不仅仅有单一的大长表,而是将它们拆分成多个列数较少的表,这样可以以更关系化的方式表示数据,这也是我们以前见过的,但除了垂直分区外,我们还可以。

我们还可以进行水平分区,理念是我们将一个表拆分成多个存储同样数据的表,因此相同类型的数据但在不同的表中,我们可能原本有一个航班表。

然后我们将其拆分为国内航班表和国际航班表,这些表仍然具有完全相同的列,它们仍然有目的地列、出发列,它们仍然有持续时间列,例如,但我们现在只是将。

原本在一个表中的数据拆分到两个或更多不同的表中,一个用于所有国内航班,一个用于所有国际航班,其优势在于,如果我们只在寻找一个国内航班,我们无需在整个数据集中搜索。

例如,如果你知道你在找的航班是国内航班,那么搜索国内航班表可能更高效,而不必去搜索国际航班表,因此在选择将一个表拆分成多个表时,我们要聪明一点。

不同的表,这样的效果是我们可以经常提高搜索的效率,我们的操作效率,因为我们处理的是多个较小的表,这些操作可以更快。一个缺点是,随着我们开始将数据分散到多个不同的表中。

如果我们需要将这些数据重新连接在一起,并连接所有国内和国际航班,运行单独的查询,这样就变得更加昂贵,因此在这种情况下,我们需要考虑尝试以这样的方式分离数据,以便通常我们只需要。

在任何给定时间,只处理一个表,因此国内和国际可能是拆分航班表的合理方式,因为我们的大部分时间机场只关心搜索国内航班,如果我们知道我们在寻找某种航班或仅仅是。

如果有不同的人或不同的计算机处理这些不同类型的系统,那么对搜索国际航班的关心就很重要,因此分区数据库有时可以帮助解决规模问题,使搜索大量数据变得更快,并能够。

更清晰地表示数据,但这仍然似乎表示了一个单点故障,因为现在我们有多个服务器都连接到同一个数据库,如果数据库因某种原因失败,那么我们的网络应用将无法。

工作是因为所有这些服务器都连接到同一个数据库,因此我们可能会尝试增加更多服务器,以解决单点故障的问题,同时我们也可以尝试数据库复制,而不是。

在我们的网络应用中只需一个单一的数据库,以防潜在的故障,我们可能会将我们的数据库复制成多个不同的数据库,从而降低我们的应用完全失败的可能性,关于数据库我们可以使用几种方法。

两种最常见的复制方法是称为单主复制和多主复制,在单主数据库复制中,我们有多个不同的数据库,但其中一个数据库被认为是主数据库,这就是我们所指的。

主数据库是一个我们可以读取数据的数据库,这意味着可以从表中选择行,同时也可以写入数据,这意味着插入行或更新行或删除这些表中的行,因此在单主复制中,我们有一个单一的数据库可以进行读写操作。

还有其他多个数据库,在这种情况下,有两个其他数据库,我们只能从中读取数据,因此我们可以从这些数据库获取数据,但不能更新、插入或删除这些数据库中的数据。现在我们需要一些机制来确保所有这些数据库保持同步,最终。

这意味着每当数据库发生变化时,所有数据库都会被通知。现在唯一能够改变的是我们的主数据库,这是唯一一个可以写入的数据库,也是唯一允许数据发生变化的数据库。其他数据库是只读的,因此每当这个主数据库更新或发生变化时。

以某种方式,它需要通知其他数据库该更新,因此它会通知其他数据库该更新,现在所有数据库都保持同步,如果你尝试在这些数据库中运行查询以选择和获取一些信息,你会从所有这些数据库中获得相同的结果。

各种不同的数据库,现在单一主数据库的方法有一些缺点,它的缺点是只有其中一个数据库可以进行写入,因此如果有很多用户同时尝试向数据库写入数据,可能会出现一些问题。

这个数据库将承担所有可能尝试更新和更改该数据库的人的负载,它还存在稍小版本的相同问题,即单点故障的问题。对于从数据中读取而言,不再存在单点故障,如果你想要。

从数据中读取时,如果其中一个数据库出现故障,你可以从其他任何数据库读取数据,它们工作得很好,但它确实有一个缺点,如果这个数据库失败,如果我们的主数据库失败,那么我们将不再能够写入数据,如果我们想要更新数据。

数据库这个数据库不再可用,其他数据库也不允许我们进行新的更改,因此我们可以尝试几种方法来解决这个问题。不过一种方法是,不再拥有一个单一的主数据库,也就是一个可以读写的单一数据库。

采用多主数据库的方法,在多主数据库的方法中,我们有多个数据库,所有数据库都可以进行读写,我们可以从所有数据库中选择行,也可以向所有这些数据库插入、更新和删除行,但现在同步过程变得有些复杂。

这是权衡,现在我们通过拥有多个可以读取和写入数据的数据库,复制了可以进行的读取和写入的数量,但每当这些数据库中的任何一个发生变化时,每个数据库都需要通知其他所有数据库这些更新,这肯定会。

需要花费一定时间,它为我们的系统引入了一些复杂性。同时,它也引入了可能的冲突,想象一下,如果两个人同时编辑相似的数据,可能会遇到多种不同类型的冲突。

冲突的一个例子是更新冲突,如果我在一个数据库中尝试编辑一行,而另一个人在另一个数据库中也尝试编辑同一行。当它们通过此更新过程进行同步时,我们的数据库系统需要某种方式来决定如何解决这些不同的冲突。

另一个冲突可能是唯一性冲突,我们在设计数据库表时看到的情况。我可以指定这个特定字段应该是一个唯一字段,常见的例子是ID字段,其中每一行都会有自己的唯一ID。

如果两个人同时尝试将数据插入到两个不同的数据库中会发生什么?他们各自会被分配一个唯一的ID,但在两个数据库中ID是相同的,因为没有一个数据库知道另一个数据库已经添加了新行。因此,当它们重新同步时,我们可能会遇到唯一性冲突。

两个不同的数据库为多个不同的条目分配了完全相同的ID,因此我们需要某种方式来解决这些冲突。此外,还有许多其他冲突,比如删除冲突,一个人尝试删除数据时。

如果一个人删除一行,另一个人尝试更新那一行,那么哪个应该优先?我们应该更新行,还是删除行?我们需要某种方式来做出这些决策,因为在对数据库进行更改和该数据库能够进行通信之间存在一些延迟。

随着我们开始处理与越来越多此类数据交互的程序,这些规模问题和同步问题总是会出现。因此,我们需要设计越来越复杂的系统,能够应对这些情况。

现在处理这些规模的问题,最终我们理想的目标是减少不同的数据库服务器数量,因为每增加一个数据库服务器都会消耗时间、资源和资金来维持所有这些服务器的运行,因此理想情况下。

如果不需要与这个数据库进行交互,我们希望不必与其通信。你可以想象,例如新闻组织的网站,类似于《纽约时报》的首页。如果你访问《纽约时报》网站的主页,它会显示当天的所有头条新闻及其图片。

有关每个故事的具体信息,例如,你可能会想象,他们的做法是,他们有某种数据库,存储所有这些新闻文章,当你访问《纽约时报》的首页时,它会进行某种。

数据库查询,选择所有最近的头条新闻,并将所有这些信息呈现在你可以看到的HTML页面上,这当然可行,但如果很多人同时请求首页,那可能。

每次网络应用都在进行数据库查询以获取最新文章并将这些信息展示给所有用户,这样做似乎没有太大意义,因为。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P25:L8- 拓展性与安全 2 (缓存,安全,https) - ShowMeAI - BV1gL411x7NY

文章可能不会经常改变,如果一个人发出请求,一秒后另一个人又发出同样的请求,那么从数据库重新请求所有信息以再次生成该模板可能没有什么用,因为这是一项昂贵的过程。

从数据库请求数据或生成模板时,我们理想上希望有某种方法来处理这个问题,而我们可以解决这个问题的方式是某种形式的缓存,缓存指的是我们可以在不同地方使用的一系列不同类型的想法和工具。

在我们的系统中,一般来说,当我们谈论缓存时,我们是在谈论以某种方式存储某些信息的已保存版本,以便我们能够更快速地访问,因此我们不需要继续向数据库发出请求。例如,有许多方式可以进行缓存。

我们可以在客户端进行缓存,通过客户端缓存,想法是你的浏览器,无论是Safari、Chrome还是其他浏览器,都能够缓存数据,存储信息,这样浏览器下次就不需要重新请求相同的信息了,举个例子。

假设该页面加载了一张图片,并且你重新加载该页面,那么你的网页浏览器可能会尝试再次请求同样的图片,然后显示给你,但另一种可能是你的网页浏览器可以在缓存中保存该图片的副本以供本地存储。

存储该图片的一个版本,这样下次用户向网站发出请求时,用户就不需要重新加载整个图片,这也适用于整个网页和网络资源。如果某些页面不常改变,那么如果网页浏览器缓存了已保存的版本。

如果用户下一次访问他们的网页浏览器时尝试访问该页面,而不是重新向服务器发出请求并进行新的请求,如果浏览器缓存了该页面,浏览器可以直接显示缓存的已保存页面。

这可能根本不需要与服务器交互,因此这肯定有助于减少特定服务器的负担,因为如果用户在网页浏览器中缓存了信息,用户体验会更快,因为他们可以立即看到信息,而不需要发出请求并等待响应。

响应回来的好处在于服务器不需要处理那么多请求,如果其中一些请求得到了缓存。因此,尝试解决这个问题的一种方法是在HTTP响应的头部中添加一些内容,当你的web服务器响应某些请求时。

网络服务器可以在响应中包含类似这样的行:cache control max-age 86,400,实际上是指定你应该缓存此资源的秒数。但如果我在10秒后尝试访问这个页面,那就少于86,400秒,因此不必重新加载。

请求整个页面时,我们只是使用网页浏览器中缓存的版本。因此,这有几个优势,我们在减少查看页面内容所需时间方面已经讨论过,因为它已经保存。

这减轻了对任何特定服务器的负载,但也有缺点,例如如果在这段时间内资源发生了变化,假设在60秒内页面发生了变化,如果我再次加载页面,加载的是缓存的版本,我可能会看到过时的内容。

我看到的网页版本是旧版本,因为我的网页浏览器恰好缓存了该特定资源。这种情况可能适用于网页,尤其适用于其他静态资源,例如CSS文件或JavaScript文件,但网页的CSS。

页面可能并没有经常变化,因此很自然的是,网页浏览器不会一次又一次请求完全相同的CSS文件,而是可能只会保存这些CSS文件的副本,将其缓存,以便能够重复使用缓存版本。

如果网站更新了他们的CSS,你可能看不到最新的更改。如果你在自己开发网页应用时更改CSS并刷新页面,你可能不会总是看到这些更改被反映出来,如果你的网页浏览器正在缓存这些内容。

因此,在大多数网页浏览器中,你可以进行硬刷新,忽略缓存中的内容,实际上发出新请求并获取一些新数据。但最终如果你不这样做,你就会受到缓存控制的限制,网页浏览器将会说,除非经过的秒数达到了这个数字。

一旦过去了一段时间,我们将重新使用现有版本的页面。因此,这种方法的替代方案(这确实有效且相当流行),我们可以通过添加称为e-tag的内容来扩展这一方法。资源(如CSS文件、图像或JavaScript文件)的e-tag就是。

一些独特的字符序列,标识特定版本的资源,比如标识一个CSS文件或JavaScript文件的特定版本。这允许程序(如网页浏览器)在请求资源时,例如请求一个CSS文件。

JavaScript文件,它们会收到返回和相关的etag值。因此我知道这是与这个版本的CSS文件相关的值,如果网络浏览器的网络服务器曾经更改了那个CSS文件,并用一个更新的CSS文件替换它,对应的etag也会改变。

那么这有什么好处呢?这意味着如果我在考虑是否应该加载资源的新版本,或者是否应该尝试另一个请求以获取最新的CSS,首先我可以问一下etag值,这个短小的序列可以在浏览器中快速回答。

很快,我们可以回应说,如果etag值与我上次记住的相同,那么我就不需要获取整个新版本的资源,因此这很常见 - 我们的网页浏览器会说,嘿让我请求这个资源,但我已经有一个版本。

资源上指定这个特定etag,如果该etag仍然是某个特定资源(如CSS或JavaScript文件)最新版本的etag,那么网络服务器就不需要发送该文件的新版本,只需回应并说你拥有的版本,那个完全可以使用,但如果有。

是一个新版本,那么网络服务器可以用新资产进行响应。例如新的CSS文件,还有新的etag值,因此这两种方法可以相辅相成,你可以说继续缓存这些,持续一段时间,因此在这段时间内你不会。

决定请求该资源的新版本,但即使你在这段时间过后请求新的版本,如果etag值没有更新,那么就不需要重新下载特定文件的整个新版本,你可以直接重用缓存的版本。

所以缓存和浏览器可以是一个极其强大的工具,来加速这些请求,减轻特定服务器的负担,但客户端并不是我们开始进行这种缓存的唯一地方,我们也有能力进行。

服务器端缓存,在服务器端缓存中,我们将向我们的概念介绍一个缓存,拥有多个服务器与数据库进行通信,而这些服务器也可以与某个地方的缓存进行通信,在那里我们存储可能想要重用的信息。

而不是做所有的重新计算,结果发现Django有一个完整的缓存框架和一整套功能,允许我们利用这个能力使用缓存来加速请求,因此有按视图缓存,可以在。

特别的观点是,与其每次有人请求这个特定视图时都运行所有的Python代码,不如缓存该视图,这样在接下来的30秒或30分钟内,当下一个人尝试访问相同视图时,可以直接重用上次的结果。

上次加载该视图时的时间,这不仅适用于单一视图,也适用于模板中的片段,模板可能包含多个部分,在网页上,你可能会根据当天的信息渲染导航栏、侧边栏和页脚。

可能第二天会改变,但如果你预期页面的侧边栏在同一分钟或同一小时内不会频繁更改,那么你可以考虑缓存模板的这一部分,这样当django尝试加载整个模板时,就不需要重新计算。

如何为你的网站生成侧边栏,它只需知道我们可以使用上次加载该网站时保存的侧边栏版本,jingle还提供了访问较低级缓存API的能力,方便你缓存和存储任何信息以供后用。

你可以将信息保存在API中,进行一次耗时几毫秒或几秒钟的数据库查询,你可以将这些结果保存在缓存中,以便下次访问同样的数据时更容易获取。

这使我们能够通过减少服务器和数据库的负载来处理这些规模问题,而不是每次对特定网页应用程序发出新请求时都与数据库进行交互,我们可以重用缓存中已有的信息,从而提升我们的网页应用。

应用程序可以变得更加可扩展,因此我们会看一些与可扩展性相关的问题,然后将注意力转向安全,确保在构建和部署我们的网络应用程序时,随着更多用户的使用,我们要确保安全。

他们确保安全,还有一大堆安全考虑因素需要在我们所研究的所有主题中加以考虑。我们已经看过多个不同的主题,每个主题都有安全漏洞,需要注意相关的想法。

确保我们的应用程序是安全的,我们的故事实际上可以从git和版本控制开始,git的核心在于帮助我们跟踪代码的不同版本,而与git密切相关的是开源软件的概念。

在像GitHub和其他托管git存储库的服务的网站上,越来越多的软件正变成开源,任何人都可以查看并贡献应用程序的源代码,这在某种意义上是很棒的,因为它允许很多人能够合作共同工作。

为了尝试发现可能存在于网络应用中的错误,但这也带来了缺点,如果应用程序中有一个错误,现在查看我们程序源代码的人可能会发现这个错误,或者你可能会想象,因为 gate keeps。

跟踪我们代码的不同版本,每次我们向存储库提交时,你必须非常小心关于凭证或可能泄漏到源代码中的东西。通常情况下,你绝对不想把密码或任何安全信息放入git存储库中,因为。

git存储库可能会与其他人共享,可能向任何人开放以供查看,因此这些都是需要注意的安全考虑因素,如果你进行提交并意外地提交了你的代码。

如果你暴露了这些凭证,你可以删除这些凭证并再次提交,因此你程序的最新版本不包含这些凭证,但拥有访问git存储库的人可以访问的不仅是你代码的最新版本,还有你代码的每个版本。

而那个人理论上可以回顾存储库的历史记录,找到凭证暴露的提交,并查看那些凭证。因此,虽然git是一个非常强大的工具,但这也是需要注意的,任何你所做的更改可能会被保存在一个。

提交因此可能会在稍后被访问,所以如果存储库中暴露了凭证,你要确保删除所有之前的提交,而不仅仅是进行一些新的提交来试图隐藏之前可能暴露的凭证,因为它们可以。

仍然可以在任何特定存储库的历史记录中检索到,因此那时我们查看了一些可能出现的问题,我们还在课程开始时讨论了HTML,以及我们可以用HTML做什么,以及我们如何使用这种语言来设计。

网页的结构,以决定所有段落将放在哪里,页面上将包含哪些表格,我们讨论了链接以及如何使用锚标签将一个页面链接到另一个页面。现在一个关注点是这种攻击类型,称为网络钓鱼攻击,与HTML有关,网络钓鱼。

攻击实际上只涉及一小段HTML,像这样非常简单的代码,我有一个锚标签,它将用户引导到URL 1,但看起来是引导用户到URL 2。这种情况的例子是什么呢?

link.html,在linked out HTML中,我写了一个网站,看起来有一个指向谷歌的链接,但如果我点击那个链接,我突然被引导到了这个课程网站。那是怎么发生的,为什么会这样?

看起来它在链接谷歌,如果你查看代码,打开linked on HTML,你会看到这里有一个锚标签,它实际上链接到课程网站,但看起来是链接到谷歌。

这是一个非常常见的攻击向量。

特别是在电子邮件中,例如,你可能会看到一封电子邮件告诉你点击某个链接,但那个链接实际上会把你带到完全不同的地方,因此有人可能会不小心分享他们的银行账户凭证或其他敏感信息,这里也要引起注意。

互动网络时,可能并不一定在你自己的网站上,但在其他网站上你可能会互动,务必注意链接实际上将你带到哪里。大多数网页浏览器在你悬停在链接上时会显示该链接可能实际指向的地方,因为它可能是。

与文本中该特定锚标签所显示的内容不同,它可能链接到其他地方。因此,HTML存在各种不同的漏洞,因为你可以决定页面的结构,这就留下了有人试图欺骗你的可能性。

你正在访问一个实际上并不在的页面,这个问题更为普遍,因为任何人都可以查看任何页面的HTML。HTML来自服务器,因此网页浏览器可以访问所有这些HTML,并可以使用这些HTML来渲染页面,这留下了开放的可能性。

其他漏洞也是如此,例如,让我去美国银行(Bank of America)的官网,如果我想创建一个假冒的美国银行网站,以欺骗他人认为他们正在访问美国银行。

实际上,他们将访问我的网站。那么我可以查看这个网页的源代码。

我将查看页面源代码,这里是美国银行网站的所有HTML,没有什么可以阻止我复制所有这些内容,放入HTML文件中,创建一个新文件,然后我将其命名为Bank HTML,然后我会粘贴进去。

那个 HTML 文件的内容在这里,那么这是全部的美国银行的 HTML,现在如果我打开 Bank HTML,这个我现在写的 HTML 文件其实只是。

从美国银行复制的,我打开它,现在我的页面上是一个看起来像美国银行的网页,它使用了美国银行的所有 HTML,但实际上是我的 HTML 页面,而不是美国银行。因此,你可能想象将这些结合在一起,创建一个更加令人担忧的攻击。

向 Google com 链接的方式,试试链接到美国银行 com,但实际上我。

我将链接到的是 Bank dot HTML,我的美国银行网站版本。

现在如果我打开链接 HTML,这里似乎有一个链接指向美国银行的网站。如果我点击那个链接,我会进入一个看起来像美国银行网站的页面,但它并不是!

美国银行的网站是我的银行,我写的 HTML 文件正好看起来像美国银行的网站,因为我复制了所有的底层 HTML。因此,HTML 能够描述我们网页的结构,但每当你写这 HTML 时,记得这一点是很重要的。

任何人都可以复制你的 HTML,从理论上讲,可以假装成 Yumi 的网站,这些安全漏洞值得我们在开始开发网络应用程序和与网络应用程序互动时注意。因此,最终我们在设计使用 Django 的网络应用程序时使用了 HTML。

框架到底是如何工作的,这些网络框架如何创建监听请求并响应这些请求的网络服务器?最终,互联网的许多内容都是围绕客户端与服务器之间的通信这个理念构建的,或者更一般来说,是任一台计算机之间的通信。

正在使用 HTTP 与另一台计算机通信,尤其是 HTTP 的更安全版本。因此,你可能想象这些协议实际上是关于如何将信息从一个人传递到另一个人,以及我们与这些信息一起存储的内容。

试图与另一台计算机通信,为此信息通常会通过这些路由器流动,你可以想象信息在一台计算机和另一台计算机之间来回传递,通过这些中间路由器沿途。

因此需要谨慎的一件事是,你如何知道这信息在来回传递时是安全的?理想情况下,当我向另一台计算机发送消息时,我就是在给其他人发送电子邮件,发送消息,或者发出请求。

比如,我不希望任何拦截请求的路由器能查看我的银行账户等敏感信息。我希望确保这些路由器无法看到我的请求及其内容。

我希望在网上发送的密码信息能够被加密,因此我们将讨论密码学,即确保能够与他人沟通而不被中间窃听者监听的过程。

显然,如果我直接发送未加密的消息文本,那么任何看到该消息的人都会知道内容,因此我需要某种加密方式。

对消息进行加密,以防有人在传输过程中能够解密。如果中间的路由器或其他人能够拦截消息,那么我们首先要考虑的是所谓的对称密钥密码学

这里涉及的不是明文,而是某个密钥,一个用于加密或解密信息的秘密信息。我将使用密钥和明文生成称为密文的加密版本。

我可能想要通过互联网发送密文,而不是明文,以防发送明文导致内容泄露。因此,密文被传送,另一方也需要密钥。

如果其他人拥有密文和密钥,那么他们就可以利用这些信息使用密钥解密密文,得到原始明文。这个密钥可以称为对称密钥加密和解密密钥,用来加密和解密消息。

为了完成解密过程,我和交流对象都需要同样的密钥,只要我们都能访问这个密钥,就可以加密和解密消息,而仅拥有密文但没有密钥的人则不太可能解读出内容。

但在互联网环境中存在一个问题,即我和另一方都需要访问这个密钥。密钥用于加密和解密,但我不能直接将密钥通过互联网发送给他人。

如果我做得很好,那么中间的某个人拦截了我的请求,他们就可以同时获取密文和密钥,因此他们能够解密消息,因为他们同时拥有密文和密钥。如果我能够与另一个人当面交换密钥。

只要我们都有密钥,而且我没有将密钥公开分享给可能拦截消息的任何人,这个方案就能奏效。只有我和另一个人拥有密钥,但通常在互联网上交流时,你并不是在与你已经通信的服务器沟通。

在我尝试向一个新网站发起请求时,我们需要达成一个协议,让我可以加密消息,而只有对方能够解密这些消息,因此这种加密方式可能并不适合初步创建。

互联网的安全连接,对于密码学的一项重大进展是公共密钥加密的概念,而在秘密密钥加密中,密钥必须是秘密的,因为如果每个人都知道密钥,那么任何人都能够。

在公钥密码学中,我们能够创建一个安全的加密系统,其中密钥是公开的,或者至少是其中一个密钥。如你所见,这里的想法是我们使用两个密钥而不是一个,我们同时拥有一个公钥和一个称为私钥的密钥。

私钥是你应该绝对不与他人分享的东西,以保持加密方案的安全,而公钥则可以与他人分享,二者的区别在于公钥用于加密信息。

私钥将用于解密由公钥加密的信息,公钥与私钥在数学上是相关的,我们可以想象几种方法来实现这一点,但现在的想法是,如果我想与另一个人交流,那个人会把。

他们的公钥发送给我,公钥可以在互联网中传输。任何人都可以看到公钥,因为公钥仅用于加密数据,因此我可以使用明文和公钥生成密文。

我将密文发送给我想要沟通的另一方,而另一方现在使用密文,再用他们没有分享的私钥,私钥能够解密使用公钥加密的信息,因此使用这种方式。

密文与私钥的组合,使得我所沟通的那个人能够解密信息,恢复出原始的明文内容,因此这就是我们如何通过使用公钥和私钥对在互联网上进行大量通信的方式。

可以使用公钥进行加密,使用私钥进行解密,而现在两个之前从未互动过的计算机,无需见面以交换某些秘密信息,可以使用这种技术安全地互相沟通。

我们可以来回发送消息,而中间没有人能够拦截消息并识别消息的内容。一旦你具备这种能力,能够与他人秘密交流,那么你可以想象达成某种秘密密钥的共识,然后使用对称密钥加密。

以便能够加密和解密消息,因此这是你在尝试与他人通过互联网沟通时可以采取的一种方法。加密的理念使得HTTPS得以实现,这是HTTP协议的安全版本,以确保你在沟通时。

例如,你银行的网站上,途中不会有人能够拦截信息并识别你在沟通的内容,而只能得到加密版本的信息,以及一个公钥,用于加密信息,但没有可以解密信息的私钥。

最终,这种方法使我们能够在互联网上实现这种安全通信,并使我们的网络应用程序变得安全。此外,我们的网络应用程序不仅是监听请求,并提供某种响应。

在本课程中,我们引入了SQL数据表的概念,表中有代表信息的行和列,我们还创建了网络应用程序。

在本课程中,我们有用户使用的应用程序。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P26:L8- 拓展性与安全 3 (数据库,JS) - ShowMeAI - BV1gL411x7NY

举个例子,用户和密码的相关信息可以用一个这样的表来表示:每个用户都有一个ID,一个用户名和一个密码,但这被证明是非常不安全的存储方式。

将密码存储为所谓的明文,字面上就是将密码存储在数据库中,这种做法在实践中应当避免,因为存在安全漏洞,如果有人未经授权获取。

如果他们访问这个数据库,就能够看到所有用户的密码,所以如果这个数据库因为某种原因泄露,所有这些密码将会被公开,这种情况确实会发生,如果公司不小心处理用户名和密码。

在他们的数据库中,如果发生某种数据库泄露,突然会有一大堆密码可能会被泄露。因此,推荐的方法是存储密码的哈希版本,而不是实际的密码。

哈希函数在这个上下文中是一个将密码作为输入并输出某个哈希、一系列字符和数字的函数,这个哈希代表特定的密码。

是因为它是一个单向哈希函数,从密码可以得到一系列字母和数字,但要反向推导原密码是非常非常困难的。因此,这意味着公司实际上不会知道任何密码的真实内容。

对于特定用户的密码,当用户尝试登录时,我们会取他们的密码并进行哈希处理,然后将这个哈希与我们在数据库中存储的哈希进行比较,如果哈希匹配,那么这意味着用户可能正确输入了他们的密码。

我们可以让用户登录,反之则意味着用户没有正确输入密码,因此这就是为什么公司如果遵循这些最佳实践,通常无法告诉你你的密码是什么。如果你忘记了密码。

公司会让你重置密码,他们可以更新表中的数据,但公司无法告诉你你的密码实际上是什么,因为公司并不知道你的密码,只知道某个哈希版本。

一个哈希函数的存在使得他们能够知道你是否成功登录,使用的是正确的凭证,而实际上并不知道你的密码是什么。因此,如果不小心存储这些数据,这将是另一个需要注意的领域。

如果你的程序内部存在安全漏洞,无论数据在哪里泄露,密码都会突然变得可知,并且还有其他更微妙的方式,网络应用可能会泄露信息,而你作为网页开发者需要决定是否能接受。

比如说,如果你有一个可以让你重置密码的地方,你可以想象如果你输入你的电子邮件地址并点击重置密码,可能会发送给你一个链接。

你可能会收到一条消息,比如“好的,密码重置邮件已发送”,但你也可能想象输入一个电子邮件地址后收到“没有该电子邮件地址的用户”之类的错误消息,这同样是一个潜在的安全漏洞,泄露了信息。

如果你忘记密码,发送给你一封电子邮件现在泄露了关于哪些用户在你的网站上拥有账户的信息,以及哪些用户没有,因为任何人只需输入一个电子邮件地址,看看是否会导致错误,以了解某用户是否存在。

不论你是否在网站上拥有账户,如果你不在意保护这些信息,那么这可能不是大问题,但如果你在乎确保某人是否有账户,或者没有账户,这些信息应该保持私密和安全,只对用户开放。

除非他们想分享,否则这种类型的页面和数据库接口可能会泄露这类信息,信息可以通过各种不同的方式泄露,甚至仅仅基于数据库响应所需的时间。

假设你发出一个特定请求,你可能会想象,如果你针对一个用户发出请求并且响应时间较长,这可能会告诉你需要运行的数据库查询数量或关于该用户存储的信息量,而如果请求时间较短则不会如此。

类似于一个网络服务器响应请求所需的毫秒数,可以揭示或泄露关于存储在数据库中的数据的信息,已经有研究者尝试仅通过观察这些信息获取相关数据的例子。

信息看似不会泄露,但实际上可能会揭示信息。处理 SQL 和数据库时,另一个关注点是 SQL 注入的背景,这种威胁在于如果你不小心运行 SQL 代码。

可能不小心执行你不希望执行的代码。就像在用户名和密码字段的情况一样,我们以前见过这个例子,或者如果用户尝试登录,你可以想象这样的查询正在运行,从用户表中选择,用户名等于用户输入的内容。

当用户名和密码等于用户输入的密码时,我们看到,对于普通用户,输入如 Harry 和一二三四五作为用户名和密码的查询可以正常工作。但如果黑客尝试登录网站,可能会包含一个。

例如,双引号和两个连字符,其中两个连字符表示 SQL 中的注释。如果我们将这些值字面替换到 SQL 查询中,你可能会不小心替换成黑客————,创建一个注释,从而有效忽略查询的其余部分。

我们可能希望我们的 Web 应用程序进行的密码检查类型。因此,这是一种在处理数据库中执行 SQL 代码时可能出现的另一种脆弱性。为了解决这个问题,我们要确保对这些潜在危险的内容进行转义。

在我们的 SQL 查询和 Django 的模型中,可能会出现这些字符。当我们使用 Django 进行这些查询时,比如使用 dot objects dot filter 来过滤特定版本的模型时,它将处理确保的过程。

这不会受到这类 SQL 注入攻击的影响,但如果你正在编写一个直接执行 SQL 代码的 Web 应用程序,你可能会想象到这一点。在这方面,你需要小心,以确保你的应用程序不易受到这些威胁的影响。

当我们讨论服务器上发生的事情时,潜在威胁就会出现。但我们也可以考虑与其他服务器交互时可能发生的情况,比如与 API 的交互。因此,我们讨论了 JavaScript,以及如何使用 JavaScript。

对于 API 或其他能够返回特定类型信息的服务的额外请求,存在一些潜在的威胁。

我们可以在 API 中使用的一些技术可以使它们更具可扩展性和安全性。其中一种是速率限制的概念,我们可能希望确保没有用户能够在特定时间内向 API 发送超过一定数量的请求。

响应与系统的可扩展性相关的安全威胁。这被称为拒绝服务攻击,如果您。多次向单个服务器发出大量请求,您可能会潜在地关闭。该系统,因为您发出如此多的请求。

许多请求,它无法同时处理这么多请求,出于这个原因,因为。发出API请求是如此简单,您可以仅使用一行。像Python或JavaScript等语言。例如,api通常会实施某种速率限制,以限制数量。

要求您可以提出,以便您不会压倒服务器或。压倒需要被查询的数据库,以回应这些。请求,因此这种限制可能有效,api也可能希望。添加某种路由身份验证,您可能不希望每个人都能访问。

通过API访问相同的数据,也许有某种权限模型,只有。特定用户能够访问API中的特定。数据,因此您可能想象用户需要一个API。密钥,例如有效地像一个。密码,他们需要在每次发出API请求时传递。

您的API,这样您就可以查看该密钥并验证。它们是否如其所称。现在,随着API密钥的存在,还有其他潜在的。安全漏洞需要注意,其一是您绝对不应该。将密码放入您的源代码中,包括您的git。

例如,您通常也不应将API。密钥放入您的Web应用程序中,作为。那些Web应用程序的源代码,因为这样任何拥有。Web应用程序源代码访问权限的人都可以看到您的API。密钥,可能会使用API密钥来。

假装是您,因此访问潜在的API路由,这些路由您。无法访问。一个常见的解决方案是使用所谓的环境变量,实际上您和您的程序声明。您的API密钥不会是某个预定义的字符串,而是。

您程序中的文本,而是从环境中提取。在程序运行的环境中,您在服务器上运行。Web应用程序时,首先确保服务器已正确设置所有。环境变量,以便而不是拥有API密钥。

实际上在程序的源代码中,API密钥只是存储在。运行Web应用程序的服务器环境中,服务器。可以从环境中提取该信息,以便它知道。API密钥应该是什么,而不需要API。密钥实际出现在。

网页应用的源代码本身,当我们开始处理API时,你可能会注意到许多API会要求你拥有一个API密钥,这通常是为了确保我们能够有效地验证用户,并限制用户的请求。

确保他们在任何特定时间没有向服务器或数据库发送过多请求,但这开始引出其他潜在的漏洞,尤其是与JavaScript相关的漏洞。JavaScript是一种编程语言,用于编写运行在我们网页浏览器中的代码。

代码在我们的网页浏览器内运行,比如Chrome或Safari,因此,JavaScript具有强大的能力来操作页面上的内容,它可以模拟按钮点击,可以改变页面上内容的显示,因此。

处理JavaScript时会出现许多漏洞,其中一个漏洞是跨站脚本的概念,通常在你的网页应用中,你只希望JavaScript在你自己编写时运行。跨站脚本是一个潜在的威胁,其他人可能会在你的网页上运行JavaScript代码,而这段代码不是你自己写的。

这是一个潜在的漏洞,因为如果其他人可以编写JavaScript代码,他们就可以操控你网页上发生的内容,故意造成改变。

操作用户体验以获得实际上并不期望的结果,所以我们来看看一个跨站脚本的例子。好吧,我提前准备了一个网页应用,它叫做security,里面有一个名为XS的单一Django应用,用于跨站脚本。

我们首先查看URL,只有一个URL允许我们提供任何路径,然后它将加载索引视图,在索引视图中,我们将展示一个HTTP响应,表明刚才请求的路径是什么,所以你可以想象这是一个简化的示例。

你在其他网站上可能看到的内容版本,例如,网站可能在特定页面上显示你正在访问的路径,以指示如何到达该页面的一些标识。

在这个网页应用中,我将进入security并运行服务器Python。

我已经运行了服务器,现在我将进入我的网页应用,例如/hello,我看到的是请求路径hello,这正是我所期望的,我可以把它改成其他的,比如hi,所以请求路径现在是hi

  • 例如,无论我访问哪个页面,它都会给我一个显示请求路径的页面,然后是我正在访问的路径,但注意当我尝试访问这个 URL 时会发生什么,我将访问 URL /script alert hi 然后 end script,所以我执行它,结果突然在我的页面上出现一个显示 hi 的警告。

然后按确定,系统显示我。

请求的路径那个警告是一个 JavaScript 警告,它是在我的网络应用程序上运行的 JavaScript 代码,但并不是我应用程序内部的代码,而是基于 URL 运行的特定 JavaScript 代码,是其他人写的,所以有人链接到了我的网页。

应用程序并在 URL 中作为部分传递这个脚本标签,点击该链接的人可能会被带到我的网络应用程序,但最终运行了由其他人创建的 JavaScript,这最终是潜在危险的,它留下了其他人可以在我的网页上运行 JavaScript 代码的可能性。

页面可能不仅仅是你想象中的某个脚本,不只是显示一个警告,而是修改 DOM 内部的内容,改变网页的内容,进行 API 请求,执行其他你可以用 JavaScript 在网页浏览器中完成的任务,这最终。

让我页面面临潜在的安全漏洞,这些都是在设计这些页面时需要谨慎考虑的情况。如果有可能有人能以某种方式将自己的 JavaScript 注入到你的页面中,你需要检测到这一点或以某种方式转义它。

采取其他预防措施,确保这种跨站脚本攻击不会发生。你可以想象,在一个消息应用程序中,如果你互相发送消息,你不希望当你向其他人发送一些 JavaScript 代码时,接收者会执行它。

这段代码实际上作为某个 JavaScript 在你想要的特定页面上运行,你需要确保转义这些信息,以便他们只看到 JavaScript 代码的文本,但代码并不会真正执行,这与 SQL 注入的威胁类似,一切又回到了。

不希望允许其他人将自己的代码注入到你的程序中,你不希望其他人能够将 SQL 代码注入到你在数据库上运行的查询中,也不希望有人能够将 JavaScript 代码注入到你的网页中,因为这留下了。

潜在的安全漏洞,其中一种安全漏洞 Django 在防御上相当有效,这种情况我们之前见过,但我们会更详细地探讨它,跨站请求伪造,在你并不打算向网站发起请求时伪造一个请求。

您可以想象,如果您的银行有一个允许您从一个人转账到另一个人的网址。我们稍微讨论过这个想法,但想象一下如果这真的是一个可以访问的URL,比如/transfer,并说作为获取。

参数我正在将钱转给谁,以及我转账的金额。那么其他网站上的某个人可以在其页面的主体中有一个链接,链接文本为“点击这里”,并链接到您的银行,或者无论您的银行是什么,像是给我转账。

金额,如果某个用户在不知情的情况下点击了那个链接,而不知道它将带他们去哪里,这个网站可能能够伪造对银行的请求,让它看起来像用户已经访问了银行并尝试启动某种转账,最终尝试转账,而这甚至没有。

不一定需要在链接中,您可能想象一下,虽然将其放在图像中可能有些奇怪,图像源是这个特定URL,银行的转账页面现在看起来似乎没有任何意义。

转账页面不是图像,但这没关系,所有图像标签将尝试请求这个源URL以获取该图像,然后尝试在用户的网络浏览器中显示它,但第一部分是重要的,事实是这个源最终被浏览器请求。

用户无需点击或做任何事情即可请求。来自您银行的快速转账,这个特定请求可能会发起某种银行转账,用户甚至没有意识到。这就是我们通常建议的原因,任何时候您重建一个网站时。

将允许某种状态的操作,以便发生一些变化,比如转账。您不希望通过GET请求来实现此操作,您可以通过加载图像或点击一个链接来实现,您不希望那样。

这样就很容易让其他人伪造请求,只需创建一个图像或以某种方式链接到一个网站,如从一个用户转账到另一个用户。因此,解决方案如我们所讨论的,通常我们只希望POST请求能操作某些内容。

数据库能够实际从一个用户发起转账到另一个用户。但即便如此,这也不是完全安全的,您仍可能被欺骗提交一个POST请求,想象一下一个对抗性的网站,具有这样的表单,表单的操作是您的银行comm/transfer。

方法是 POST,这里有两个输入字段,其类型为隐藏,意味着当用户查看页面时,实际上看不到这些输入字段,只有在检查这个特定 HTML 页面源代码时,才会知道这里有一个名为 - 的隐藏输入。

我想转移资金给的人,这里是我想转移的金额,用户将看到的只是一个写着“点击这里”的按钮,他们不会看到任何输入字段,因为它们是隐藏的按钮。好吧,突然间,他们将会向银行提交一个 POST 请求。

当他们没有意图时启动了一些转移,可能这看起来没什么大不了,因为用户仍然需要点击一个按钮,而用户不应该在不知道按钮将会做什么的情况下点击按钮。首先,合理的想法是。

对手可能会将这个按钮嵌入到一个看起来非常安全的页面中,以便能够点击按钮,但更重要的是,用户甚至不需要点击它就能提交表单,我们只需添加一点 JavaScript,你可以想象一个对手可能会这样做。

为页面的 body 添加一个 onload 属性,表示当页面的 body 加载完成时,转到文档表单,意味着这个网页的所有表单获取第一个并提交它,提交表单,这将会导致即使用户什么都不做,甚至不点击,也会提交请求。

在这个页面加载后,按钮一出现,这个表单就会提交。提交一个 POST 请求到银行,并尝试将资金从一个用户转移到另一个用户,这就是我们可能称之为跨站请求伪造的情况,其中某个对立网站伪造了对我们网站的请求。

理想情况下,我们不希望这种情况发生,那么我们是如何防范的呢?好吧,Django 允许我们做的一种非常常见的方法是添加一个 CSRF 令牌,跨站请求伪造令牌,该令牌将在每个会话中重新生成,只有在该令牌存在时,转移才会被执行。

能够通过,因此在我们的网站上,我们可以在这个 HTML 表单中包含 CSRF 令牌,因此确保只有在 CSRF 令牌存在时才能转移资金,但如果其他网站尝试伪造请求,他们不知道 CSRF 令牌应该是什么,因为它会变化。

每个会话,因此他们实际上无法伪造从一个用户到另一个用户的请求。所以在我们使用的各种不同工具和技术中,我们用 Python、HTTP、Django、HTML 来创建这些网络应用,使用 JavaScript 和 API,因此我们可能会进行交互。

安全性考虑贯穿始终,我们这里只触及了其中几个,但这显示了在考虑网络编程实践时,关注你将要添加到网页应用中的内容以及你的网页应用的功能是多么重要。

支持思考潜在的脆弱性,以及有人可能如何利用你的网页应用做一些他们可能不应该做的事情。当你将网页应用从仅在你本地计算机上运行的应用转变为可公开访问的应用时。

这些正在某些网络服务器上运行的应用程序,许多人开始使用。这些都是开始询问的类型问题:你如何确保你的网页应用是可扩展的?你如何确保你的网页应用是安全的?所以,现在我们已经探讨了很多网页编程,接下来会是什么呢?

在本课程中,我们探讨了多种不同的工具、技术和语言,但还有许多其他的网页框架和构建网页应用的方法。我们大部分时间都在研究用Python编写的Django网页框架,但你也可以使用其他编程语言来构建网页应用。

例如,Express.js是一个非常流行的JavaScript框架,用于构建网页应用,而Ruby on Rails是一个流行的服务器端网页框架,使用Ruby构建,还有许多其他框架,以及主要与JavaScript一起使用的客户端框架,以构建用户界面。

我们已经看到了如何使用React构建动态和交互式用户界面,其他一些流行的客户端框架包括AngularJS和Vue.js,以及其他一些框架。一旦你使用任何这些服务器端框架和客户端框架构建了这些网页应用。

框架方面,你可能会想要将这些应用部署到网络上,为此,我们可以通过多种方式来实现,也包括像亚马逊网络服务(Amazon Web Services)、谷歌云(Google Cloud)和微软Azure等多种不同的服务。

这些网页应用程序使用Heroku,这是一个利用AWS的服务,试图简化部署网页应用的过程。如果你的网页应用仅仅是静态的,只有HTML、CSS和JavaScript,那么你可以使用像GitHub Pages这样的服务。

在GitHub的Zone服务器上可以免费托管网页应用,还有许多其他方式可以想象部署网页应用,使用不同的服务来处理你已经构建或可能构建的网页应用。

未来并将其在互联网上提供给其他人使用。回顾我们探索的网页编程各种主题,我们看到许多工具和技术可以利用,以构建有趣的网络应用程序。我们开始时简单入手。

更深入地了解HTML和CSS,探讨我们如何利用它来描述页面的结构,然后利用像Sass这样的工具,生成更复杂的CSS样式。仅用CSS实现这一点会更加困难。

构建更大型的网页应用,我们查看了可以使用的Git版本控制工具,以确保跟踪我们对代码所做的版本和更改,允许多个人同时协作在一个项目中。然后我们研究了Python,关注不同的方面。

语言提供的功能、条件和循环,正如我们在许多其他编程语言中看到的,还有面向对象编程,能够表示对象、方法以及作用于特定对象的函数,这一点非常重要。

特别是在处理数据的上下文中,Django是一个用Python编写的网页框架的例子,使我们能够快速启动一个能够监听请求并作出响应的网页应用。Django内置了许多功能,确实使得开发更加便捷。

轻松开始构建网页应用,尤其是便于编写处理数据的网页应用。Django让我们能够构建与SQL交互的模型,而无需编写任何SQL代码,Django可以为我们生成SQL。

使用这些模型和迁移,允许我们不断应用对数据库的更改。随着我们添加新表以及添加和修改现有字段,Django可以处理所有这些。正如你所记得的,我们将注意力转向了主要的第二个。

编程语言的过程中,JavaScript有很多用途,非常流行,但我们主要在客户端使用,以构建有趣的用户界面,利用JavaScript操控DOM结构,改变用户看到的内容。

还可以添加事件处理,以便当用户点击按钮、鼠标悬停或以某种方式与页面互动时,我们的代码能够响应。我们看到了React,这是一个客户端框架,使用JavaScript来帮助我们。

创建非常有趣和互动的用户界面,所需代码并不多,而在最后几节课中,我们也在探讨一些最佳实践,如何设计测试来测试服务器和客户端,以确保我们的代码正常工作。

以及一些行业实践,如持续集成和持续交付,这有助于确保我们在对代码进行更改时,能够快速有效地部署和交付,并确保我们能够对代码库进行增量更改,而不是。

需要等待更长的发布周期,然后今天我们终于讨论了可扩展性和安全性的问题,特别是在我们开始将应用程序移至网络时,我们希望确保这些应用程序是可扩展的,能够处理多个。

不同的用户,并确保它们是安全的,我们不想让自己暴露于潜在的漏洞中,比如有人可能会将SQL注入或JavaScript代码注入到我们的页面中,或试图访问一些他们无权访问的数据,我们希望确保当。

在设计这些网络应用时,我们能够以可扩展且最终安全的方式进行设计,因此希望你喜欢这次对Python和JavaScript网络编程世界的探索,祝你在使用这些工具构建网络程序时好运。

今天我们在这里看到的,以及其他一些工具,它们受到启发或使用了与我们最终在这里讨论的内容相似的工具、技术和想法,特别感谢课程教学人员和制作团队,使整个课程成为可能,我期待看到。

你可能创建的网络应用。

哈佛 CS50-WEB | 基于 Python / JavaScript 的 Web 编程(2020·完整版) - P3:L0- HTML 与 CSS 语法 2 (CSS 语法) - ShowMeAI - BV1gL411x7NY

目前为止,所有的网页都相对简单。我们只是描述了页面的结构,并说明我们希望在这里列出、在那形成的内容。我们可能真的希望有某种方式来指定我们希望以某种方式对网页进行样式设置。

希望添加颜色,添加间距,还希望为我们的页面添加其他布局。为此,我们将使用一种称为 CSS 的第二种语言,它是级联样式表的缩写,特别是我们将使用最新版本。

CSS 3 让我们能够将 HTML 页面告诉网页浏览器,我们希望它以何种方式进行样式处理,而不仅仅是在白色背景上显示黑色文本。我们可以开始指定特定的 CSS 属性,以便确保页面的外观。

使其看起来符合我们的期望,因此让我们现在看一个简单的例子,将一些 CSS 代码添加到我们的页面。我将创建一个新文件,命名为 style.dot HTML,以演示一些为页面添加样式的基本想法,并将之前的 hello 代码复制到其中。

也许除了 hello world 之外,我还希望在 h1 中显示一个大标题。!

顶部显示例如“欢迎来到我的网页”。所以现在如果我打开 style.dot HTML,这就是我看到的:顶部有一个大标题,上面写着“欢迎来到我的网页”,下面是“hello world”文本。现在想象一下,我想为页面顶部的标题添加一些样式。

也许我希望它不再是左对齐的。!

我希望它居中,也许除了黑色文本,我想改变颜色。为此,正如我们过去使用属性来向特定 HTML 页面添加额外信息一样,我们可以用非常类似的方法使用 CSS。我们可以指定要给这个 h1 元素。

有一个样式属性,它将等于,然后用引号括起来。我们将提供所有希望添加到特定元素的 CSS 属性。CSS 样式的工作方式是,我们可以为元素赋予各自的 CSS 属性,其中属性就像颜色一样。

元素的样式或元素的对齐方式,每个属性都有一个默认值,但我们可以将其值更改为其他值。例如,如果我想改变这个标题的颜色,使其从黑色标题变为蓝色标题,我可以说,我希望这个 h1。

给它一个颜色属性,然后赋值,我说颜色为 Cohen,然后我想给它的值,所以颜色:蓝色,例如后面跟一个分号,将把这个 h1 元素的颜色改变为蓝色,而我的文本编辑器自动给我显示一个小方块。

这让我看到这个蓝色的实际效果,这不是文本的一部分,只是我的文本编辑器在帮忙,让我可以看到。

在我写这段代码之前,就可以知道颜色实际会是什么样子的。

所以现在如果我打开 style.html,这里是我看到的,顶部不再是黑色的标题,我们将颜色改为蓝色,还有许多其他内置的颜色。

存在于 HTML 中的,可以用来将颜色改变为任何颜色。

如果我说我想要颜色为红色,例如,我可以刷新页面,现在标题是红色,还有许多其他颜色,我可以把颜色改为像春天绿色这样的颜色,这将显示出这种特定的绿色阴影。

我可以为单个元素添加各种不同的样式属性,我可以说拿这个标题,改变它的样式,使颜色不再是黑色,而是显示为蓝色,如果我想为同一个 HTML 元素添加多个 CSS 属性,我也可以做到这一点。

在样式属性中,我可以说,除了颜色是蓝色外,我还想给这个元素一个第二个 CSS 属性,我想说文本对齐属性应该是居中,例如,文本对齐属性控制如你所想象的那样,特定 HTML 元素的。

文本对齐是完全在左边,完全在右边,还是居中的,如果我将文本对齐属性更改为具有某个值。

中心对齐,好的,现在当我刷新这个页面时,我看到“欢迎来到我的网页”现在是蓝色并且居中,我已改变了这个特定元素的颜色和对齐方式。

HTML 元素不仅可以直接样式化,它们还可以从父元素获取样式信息,因此如果你再次回忆 DOM 结构,我们有一个 HTML 元素,其中包含这个 body 元素,而 body 元素内部是这个 h1 元素以及这个文本,你可以想象。

我们希望这个样式不仅适用于这个标题,还要适用于“你好,世界”文本。我可以移动样式信息,将这个样式属性从h1移动到正文,如果我把样式移到正文中,那么正文内部的所有内容都会以这种方式进行样式化。

现在让我们来看一个例子,看看这如何运作。如果我将样式信息移动,而不是将其与h1关联。

如果我将其与正文关联,那么当我刷新页面时,我会看到正文的两个部分,即顶部的大标题“欢迎来到我的网页”和文本“你好,世界”,都应用了这些CSS属性。我已将它们的颜色更改为蓝色。

还将它们的文本对齐方式更改为居中,而不是左对齐,但如果我希望它只是那个标题,我可以将其移出,并说我只想将样式应用于那个单独的标题。我们可能想象,随着时间的推移,这可能会变成一个问题。

想象一下,如果我有多个标题,我想用相同的方式进行样式化,例如,假设我有一个第二个标题,这是第二个标题。

但我也想让它们的样式变成蓝色并居中。我可以刷新这个页面,现在可以看到这并不是。

我不想让整个页面都变成蓝色并居中,我只想让这些Q标题变成蓝色并居中,所以我可以从h1中提取样式代码,并将其应用于这个h1,这样我的两个h1都可以。

这些元素现在具有完全相同的样式代码,我将继续刷新,现在我们看到这是预期的行为,我有两个标题,它们都是居中的。

蓝色,但我们想要开始思考的是,当我们开始构建网页应用程序,尤其是当我们的网页应用程序变得更复杂时,设计我们如何构建网页和网页应用程序的方式,特别是在任何时候。

我们发现自己从一个地方复制大量相同的信息,这可能不是最佳设计,因此你应该开始考虑如何可以更好地设计,虽然这里有一些冗余,可能并不需要。

这也使得页面变得更难以更改和更新,如果我想把这两个标题的颜色改为红色而不是蓝色,那么我突然需要在两个地方更改我的代码,我需要更改上面这个标题的样式属性。

我还需要更改第二个标题的样式属性,我想要做的是能够只编写一次样式代码,然后将其应用于这两个标题,实际上有一种方法可以做到这一点,我们可以做的是,而不是进行所谓的内联样式。

如果我们将CSS代码直接作为HTML元素的属性放置,我们可以将样式代码移动到网页的完全不同部分。请再次回忆,我们的HTML页面顶部有这个head部分,只包含关于网页的有用信息,但并不是。

实际上,网页的主体部分是用户看到的内容,而头部部分是一个很好的地方,我们可以开始放置一些样式信息,关于我们希望这个网页如何被样式化,所以我可以在这里做的是,而不是将这些样式属性直接放在行内。

在我网页的head部分,我可以添加一个样式元素,在打开的样式标签和关闭的样式标签之间,我可以添加我想要的任何样式信息,这里是语法的样子,我首先需要指定我要样式化的元素类型。

我想要样式化所有的H1,所以我可以。

只需说h1,然后所有的样式代码将放在一对大括号内,在这里我可以说我希望颜色为蓝色,文本对齐属性为居中,所以现在我所做的是,我已经将以前位于页面主体内的CSS代码拿出来了。

实际上,作为这些h1元素的一个属性,我已将样式相关代码移动到页面的不同部分,现在样式信息位于页面的头部的样式元素内部,指明了每个h1元素的样式。

在这里的打开和关闭大括号之间,我已经指定每个h1应该有蓝色,并且每个h1的文本对齐属性应该是居中,这将应用于我的网页中所有h1元素,优势一是我们。

刚才提到的,我不需要在这两个h1元素中重复相同的代码,我可以一次性编写,并指定将这种样式应用于页面上显示的所有H1,优势二是我们能够将样式代码抽取到其他地方,以便稍微简化。

更干净一点,因此与其有一行非常长的代码,你可以想象,如果我们不仅有两个,而是五个、六个或七个不同的CSS属性,那将在一行中占用大量空间,我可以以更可读和更有组织的方式将样式相关代码移动到样式元素中。

在页面的开头这样做是为了使其更易于阅读,更容易理解视觉效果,并且为了清理网页的主体。这将是本课程中反复出现的另一个关键主题,即将事物分开,使得每个部分。

每个部分可以相对独立。网页主体的结构与样式是分开的,当我们开始尝试良好地设计网络应用程序时,这种理念会反复出现。所以现在,如果我取这个完全相同的页面并刷新style.html。

我们会看到完全相同的效果,两个标题都居中显示,且都显示为蓝色,但现在我们有了只写样式代码一次的优势。

不需要以相同的方式多次编写完全相同的样式代码,但事实证明我们甚至可以做到更好。因为你可能会想象,如果我有一个具有多个不同网页的网络应用程序或网站,那它很可能会。

每个网页可能需要以类似的方式进行样式设置,如果我在一个网页的顶部有一个大横幅,那么在与该网页相关的其他页面上,我可能希望使用类似的样式信息以相同的方式对其进行样式设置。而现在我们的CSS代码是特定于某个页面的。

如果我想将相同的样式应用到另一个页面,就不容易。我需要复制完全相同的CSS代码并将其放入另一个页面,但这样我们又会遇到重复的问题,即我不得不在多个不同页面上重复自己,放入完全相同的。

在所有不同页面中共享CSS代码,因此我们可以进行改进,我们能做的改进是将这些CSS代码移动到一个完全不同的文件中,因此不再将样式代码放在这个HTML页面的样式元素内,而是创建一个新文件。

调用styles.css,其中包含我关心的所有CSS。我想改变每个h1的颜色为蓝色,现在我想将其文本对齐属性更改为居中,现在在我的HTML页面中,我不再需要包含任何CSS,而是完全不需要这个样式元素。

我可以将我的CSS代码链接到这个特定的HTML页面中的styles.css文件。我该如何链接styles.css文件呢?我可以在网页的头部部分使用链接标签来实现,在那里我可以说我希望这个链接的关系是,它将是一个样式表,意味着我即将。

链接将成为这个页面的样式表,它将描述我希望这个页面上的元素如何呈现。

样式文件,然后就像链接到另一个页面的情况一样,我使用 href 指定一个超链接引用,我想要链接到的内容,现在我要使用 href 属性指定我想链接的 CSS 文件,在这种情况下,我将链接的 CSS 文件是 Styles.dot CSS。

恰好包含了我想应用于这个特定文件的所有 CSS,现在如果我刷新页面,我再次看到最后两个修订没有任何变化,页面依然保持与之前完全相同的样子。

用户关心的是,他们仍然看到两个标题,它们都居中且都是蓝色的,但现在的好处是,我的 HTML 比之前更简洁,特别是这个 HTML 文件中没有任何嵌入的 CSS,所有样式都被提取到这个单独的 styles CSS 文件中。

现在如果我有多个 HTML 文件,它们都使用相同的样式,我可以将它们全部链接到同一个 Styles.dot CSS 文件,这样它们都使用相同的样式信息,我无需重复自己,如果我需要对所有页面进行更改,我只需更改一次样式。

CSS 文件的样式,然后所有链接到该样式表的网页也将更新,以反映这些更改,因此再次,我们能够将一些样式信息提取到一个单独的文件中,以便让我们的生活变得更加轻松。好的,到目前为止,我们已经。

看到我们可以用多种不同的方式使用 CSS 来添加一些基本的样式,我们看到我们可以获取一个元素并更改它的颜色,我们看到我们可以获取一个元素并改变其对齐方式,将其从左对齐移动到右对齐或居中。例如,实际上有很多。

我们可以添加到 HTML 元素的不同 CSS 属性,以便以各种不同的方式对其进行样式设置,远比我们在这次讲座中有时间讨论的要多,但现在让我们看看几种最流行的、最常见的 CSS 属性,以便使我们的网页看起来更好。

我们希望的方式,而 CSS 最强大的工具之一是控制各种不同元素的大小,默认情况下 HTML 对页面上的所有内容使用默认大小,但如果我想更精确地控制任何特定元素的大小,我可以使用 CSS 来实现,所以现在让我创建一个。

新文件我将称为 size.dot HTML,我们将从相同的 HTML 代码开始,并称页面为 size,现在在我页面的主体部分,让我只创建一个垂直部分,页面中的某个部分将包含一些内容,我将把这些内容放在一个 div 标签内。

第一次看到HTML中的div时,可以将div视为页面的一部分,即将包含一些内容的某个部分。我们使用它是因为它便于引用特定的div或将信息嵌套在其他信息中,或仅仅是为了分隔。

并将我们的页面分成多个不同的部分,在主体内部,我将有一个单一的div,显示“hello world”,现在我将为这个页面添加一些样式,以控制这个div的大小,控制这一部分的大小。

在我的网页中,我可以使用内联样式,我可以将一些内容提取到另一个文件中,但由于我现在只处理一个文件,我将为我的网页顶部添加一个样式部分,以便您更清楚地看到页面的样式将如何映射到我们修改这些HTML。

我希望以几种方式为这个div添加样式,其中一件事是颜色。让我将其背景颜色更改为蓝色,然后我可以说好吧,我想给这个div一个宽度和高度的一些大小信息,我可以说继续给这个div一个100像素的宽度,也许。

高度为400像素,所以现在当我去!

打开大小HTML,这就是我看到的,我看到的是一个100x400像素的垂直或矩形区域,我看到“hello world”这几个字,因此,您可能想象一下,如果您的页面上有多个不同的元素,随着您的网页变得越来越复杂,您可能希望有一些。

对于任何特定元素的宽度或高度有更精确的控制,这些宽度和高度属性可以!

非常有帮助,因为我可以很容易地将宽度改为例如500像素,现在当我刷新页面时,我看到这个div的宽度,页面的这一部分实际上变得更宽了,因此我们能够控制。

使用CSS去控制大小,继续关闭一些我不再需要的页面,现在看看我们可以做的一些其他事情,除了控制大小,还让我更改。!

将这个颜色变得稍微浅一点,比如橙色,这样可以!

现在如果我打开这个尺寸,它看起来像这样,我会将其缩小一点。!

让我们试试200乘200像素,现在看起来是这样的,你可能会想要做一些其他的更改,比如这个“你好,世界”就非常接近这特定的边框,紧贴着这个区域的左上角,我可能想通过添加一些东西来改变它。

我们可能会给这个特定的HTML元素添加一些填充,留一些空间,以便元素的内容不会太靠近元素的边框。

例如,我可以在这个div内添加。

一些填充,假设我想要在元素内部添加20像素的填充,这样当我刷新页面时,我们看到在元素外侧有一些填充,所以“你好,世界”现在显示的不再是紧贴元素的边缘,而是稍微有一点距离。如果我们。

如果有特定的HTML元素,可能距离屏幕边缘太近,或者离屏幕顶部太近,我们也可以添加空间。

通过向元素添加我们称之为边距的空间。

我可以说在这个div内给它20像素的边距,然后刷新页面,现在我们看到这个div与屏幕左上角的边缘之间的距离变成了20像素!

所以它在四周都有一些空白,这样我们就可以使用边距和填充,让页面看起来对用户更加友好。这样对象不会太过靠近或距离过远,填充又是在元素边框的内部,例如我可以添加20像素的填充。

以确保元素内部的内容,在这个例子中是“你好,世界”,与边框之间有一点空间,这就是填充。与此同时,边距是在元素的外部,我们在边框的外侧添加一些边距,以便给元素周围留出空间。

让元素与其他可能在附近的元素分开,确保元素边框与屏幕顶部、左侧、底部和右侧之间有足够的空间,尽管目前我们没有在这些地方创建空间。

宽度、高度、边距和填充,现在我们可以利用CSS确保页面布局如我们所愿,元素之间有适当的间距,并且尺寸合适。那么现在我们来看看CSS将提供的一些其他功能。

除了仅仅更改特定元素的位置,例如居中文本或添加大小、边距和填充,我们还可以使用CSS来更改元素的实际外观。我们已经看到它可以更改文本颜色,从黑色变成蓝色,但我们也可以。

使用CSS可以更改我们用来显示文本的字体。现代网页并不总是以相同的字体显示所有内容,通常是设计师为特定网页选择字体,所以让我们尝试一下这些可能性,我现在将创建一个新文件。

调用font.html,里面是一个名为font的HTML页面。在body中,我将再次有一个显示“hello world”的div,和之前一样,但现在在我的网页的head部分的style标签中,我想为这个div添加一些字体信息,特别是有很多。

可以添加不同的与字体相关的CSS属性,以控制任何特定HTML元素的字体。我可以指定我想使用的字体族。

为了显示这段文本,也许我想以Arial显示,例如,这是一种在互联网上常用的字体,现在如果我打开font.html,我看到的字是“hello world”,字体为Arial。

这与我之前使用的文本不同,你还可以指定多个。

不同的字体并不是所有计算机都支持所有字体,因此我可以指定以防Arial不被支持,可以回退到任何无衬线字体,任何没有小图标的字体。因此,现在如果我刷新页面,因为我的网页浏览器支持Arial,我。

你可能不会注意到有什么不同,但如果你使用更复杂的字体而不是所有网页浏览器都有或支持,你可能需要添加一些备用字体,以防你想要的字体实际上不可用。此外,我还可以指定字体大小,即我希望字体多大。

在这个div内部,我可以指定这个div的字体大小,例如28像素!

现在我刷新它,这个div出现了更大的文本,我还可以像文本编辑器一样,让你指定是否希望它是正常文本或粗体文本。我可以为这个div指定字体粗细,并说明除了使用字体:

arial字体大小。

28我还希望字体是粗体,因此现在我刷新它,现在字体显示为粗体,使用这些CSS样式表,我们能够。

有选择地将样式应用于网页的特定部分。如果在这个 div 下面还有其他文本,例如。

这个 div 之外的额外文本不会受到 CSS 样式的影响,因此现在如果我刷新这个页面,会出现更多文本,这些文本使用的是我网页浏览器提供的相同标准默认字体,而不是我指定的仅应用于 HTML 特定部分的自定义字体。

页面现在有能力为我们的页面添加字体——我们可能想做的另一件事是能够为我们的 HTML 元素添加某种边框,所以也许我想要一条线来分隔这一整部分。

从页面的另一部分,我可以通过转到这个 div 并说让我给这个 div 加一个边框,也许我想要的边框是 3 像素的实线黑色边框,例如,我可以指定边框的大小。

我可以指定边框是实线、虚线还是点线,并且可以指定我想要的边框颜色。所以现在当我刷新这个页面时,我看到这个整个区域周围都有一个边框,围绕着我的网页中的这个 div,你可以想象这些边框。

对于我页面中各种不同部分的样式设置是非常有帮助的,因此,例如,如果我们回到刚才查看的那个表格,当我们处理海洋时,我有一个海洋,太平洋和大西洋,这个表格是以行和列的格式结构化的。

现在看起来并不太好,我可能想添加一些样式来改善这个表格的外观。

例如,让我们尝试一下,我将继续回到之前使用的 table.html 文件中,在那里我有这个表格,现在让我为这个表格添加一些样式信息。

我可能会说对于这个表格,我希望给它一个大约 1 像素的实线黑色边框,这样当我刷新页面时,整个表格周围都会有一个 1 像素的实线黑色边框,好的,这很好,但是我也真的想要在行和列之间有一个边框。

对于每个表格数据项,我可能希望给这两个添加一些额外的 CSS,因此我可能会说,对于每个表格数据单元格,TD 代表表格数据,这些是我表格中的单独单元格。

我可能会指定希望它们也有一个 1 像素实心黑色的边框,这样现在我刷新页面,看到我的每个表格数据单元格周围都有一个边框。现在这应用于页面中的表格数据单元格。

我页面的主体,但尚未应用到这些标题单元格上,这是因为那些是 TH 元素表头,因此我们有几个选择,我可以再指定一次表头的样式。

给一个 1 像素实心黑色的边框。

但当我这么做时,你会看到它确实在那些表头周围创建了一个边框,但现在你应该想到的一件事是,这里有相当多的冗余,一些重复的样式代码在多个不同的地方出现,表格数据单元格在我页面的主体中。

我希望能以一种非常相似的方式对表头单元格进行样式设置,如果我能将这两种不同的 CSS 选择器整合在一起,那就太好了。这是我对表格数据样式和表头样式的表述,我希望将它们合并为一个,实际上在 CSS 中是可以做到的,这里有多种不同的 CSS。

选择器是选择元素的方式,其中一种叫做多个元素选择器,如果我想选择表格数据单元格和表头,可以通过说 TD,逗号 TH 来做到这一点,然后删除下面的这三行。现在这三行所说的是我希望。

以相同的方式为所有表格数据单元格和表头设置样式,如果我愿意的话,我甚至可以结合表格以便更好地衡量,但我想给它们所有的。

一个 1 像素实心边框,这样现在我刷新页面后看到它们都有这个边框了。现在,互联网上大多数表格都没有表格的边框,或者表格中每个单元格的边框。

单元格通常只是被压缩成一行,结果表明,CSS 为你提供了简单的方法。好吧,我可以添加一个 CSS 属性到。

一个称为边框合并的表格,并且说我希望合并表格中的所有边框,我将向你展示,实际上有许多不同的 CSS 属性,远远超过我们今天所要看的,但它们都是容易引用的,因此你可以轻松查找如何合并表格中的边框。

然后找到像这样的 CSS 属性,你可以用它应用到你的网页上,所以现在我刷新一下,看到我有一个单一的边框围绕所有这些单元格。接下来我可能想做的是在这里添加一些空间,因为看起来这些。

这些文本紧贴着表格的边框,因此为了做到这一点,我想添加一些间距,再次回顾,我想要边距还是内边距?边距是特定 HTML 元素边框外部的间距,而内边距是在边框内部,所以如果我想在边框内部留出一些空间。

只是为了将文本与边框本身分开,然后我想要的是。

在所有表格单元格内的内边距。

所以我可以说,让我在所有表格数据单元格和表格头单元格内添加五个像素的内边距。

刷新页面,现在结果表格看起来就是这样,只需添加一点 CSS 来指定我想要的边框,围绕页面的边缘,指定每个单元格内的一点内边距,我的表格现在看起来比几行代码之前好得多。

当我只有页面的 HTML 结构而没有 CSS 来描述我实际上想要的样式时,再次注意,在这样做时,我们能够使用这些 CSS 选择器,我可以说我想使用多个元素选择器,就是这个逗号。

为了指定我希望将这个样式应用于不仅是 TDs,而也是 THs,我们将稍后再看看一些额外的 CSS 选择器示例,但接下来,让我们将注意力转向一些更棘手的情况,在这些情况下,我们可能想要将样式应用于多个元素。

现在让我们想象一下,回到 style dot HTML。在那里,我有一些样式代码,我将其称为标题一。让我们给自己两个其他标题,标题 2 和标题 3,现在这些都是 h1 元素,将以相同的方式显示,如果我进行样式设置。

并说我希望所有的 h1 显示为蓝色,那么当我打开这个页面时,会看到三个 h1。

每个标签都有蓝色,当我打开 style HTML 时,我看到的将是这样的三个标题,每个标题恰好都有蓝色,但如果我只想为第一个标题设置样式,想让标题 1 是蓝色,但我不想为标题 2 和。

标题 3 设置样式,我该怎么做呢?好吧,我们可以回到刚才的内联样式,在每个标题内,我说样式颜色为蓝色,这样就会让这个第一个标题是蓝色,但不影响其他元素,但这又是我们。

决定这不是最佳设计,内联样式将HTML和CSS混合在一起会变得有些混乱,能够将我们所有的样式代码分离到页面的其他部分会很好,那么我们该如何做到呢?我们需要某种方法来唯一引用这个特定的HTML元素。

为此,我们可以为HTML元素赋予一个ID,ID只是我们为HTML元素指定的某个唯一名称,以便我们能够更轻松地引用它。让我直接为这个元素赋予ID为foo,您可以使用任何您想要的ID,但这里foo只是一个通用名称,现在我们已经为这个标题赋予了。

命名,以便在我们页面的其他部分或其他代码中可以引用并找到这个特定的HTML元素,特别是在我的网页的样式部分,而不是样式化所有h1元素,我只想样式化具有ID为foo的元素,ID按定义是唯一的。

页面中只能有一个ID为foo的元素,否则它不是有效的HTML,因此为了做到这一点,我们将使用#,井号符号只是CSS选择特定ID的方式。因此,如果我想选择,而不是仅仅选择所有h1标签。

通过它的ID来选择某个元素,我说#foo。

只需样式化具有ID为foo的元素,并将其颜色设为蓝色。例如,这段样式代码现在将找到一个特定的ID,并为其对应的样式提供支持,因此现在如果我重新加载此页面,只有标题1被样式化,而标题2和标题3则没有,我已经能够命名。

标题1赋予名称foo和ID为foo,然后在我的样式代码中只样式化HTML的那个特定部分。

页面当然,如果我想要样式化多个但不是所有的标题呢?比如我想要同时样式化标题1和标题2,现在我可以使用第二个ID,也许将其命名为bar,然后同时样式化ID为foo的元素和ID为bar的元素,但现在我们开始不必要地添加ID,我有。

名称太多会导致混乱,尤其是当我的网页开始变得更大时,因此,虽然ID是一种为HTML元素赋予唯一名称的方法,但有时我想为一个不是唯一的HTML元素命名,某些名称可以应用于多个。

不同的HTML元素,当我们这样做时,我们称之为类,ID是一种为HTML元素赋予唯一名称的方法,而类是一种为可能不唯一的HTML元素赋予名称的方法,它可能适用于0个、1个、2个或更多不同的HTML元素,那么这可能看起来像这样。

给这些h1元素每个一个不同的ID,我可以给每个元素一个类,给这个类一个Baz的名字,再次是我们选择的另一个任意名称,我还会给这个h1一个Baz的类,它们都属于同一个名为Baz的类。

我喜欢说只为类为Baz的元素进行样式设置,就像我们有一个用于样式设置的特殊符号哈希标签,仅用于样式设置具有特定ID的内容。我可以使用一个点来样式化所有具有特定类的元素,所以在这种情况下,点Baz将仅样式化具有Baz类的元素。

这里我现在可以说,取所有类为Baz的元素并进行样式设置。

继续给那些元素一个蓝色,所以现在我有两个属于类bass的h1,而另一个h1则没有,我只样式化了属于该类的元素,现在当我返回时。

刷新页面后,我的前两个标题的确被样式化为蓝色,但第三个标题没有,因为它应用了一个类到这两个元素,而这个类没有应用到第三个元素。因此,通常在设计更大网页时,这会非常有帮助。

在你有多个不同元素的情况下,其中一些可能以某种方式被样式化,而其他元素可能以相似的方式被样式化,你可以为你的HTML元素添加ID和类,以清理你的CSS书写方式,以能够非常具体地指向某个元素。

现在你想要为整个类的元素应用样式。现在你可能会想象一件棘手的事情,就是现在我们有多种方式来引用完全相同的元素。例如,如果我只想象有一个h1,它的ID是foo,那么我该怎么做?

如果例如我说所有的h1我希望它们是红色的,所有ID为foo的元素,或者唯一的ID为foo的元素,我希望它是蓝色的,那么可能会发生什么?这些似乎是冲突的,现在我们突然在h1中有了。

样式标签说明我应该以这种方式样式化每个h1,但我应该以另一种方式样式化ID为foo的元素。如果我有一个ID为foo的h1会发生什么呢?

我该如何选择样式化该元素?为了处理这个问题,我们必须开始处理CSS的特异性问题,即当我有多个不同的CSS选择器可以应用于同一个元素时,会发生什么。

相同的HTML元素,这通常发生在我们开始为元素添加ID和类时,所以当我们处理特异性时,特异性按照特定顺序进行,有一个优先顺序,我们可以遵循,以确定应该最终应用于任何特定元素的样式。

引用元素的第一种、最强大、最具体的方式是内联样式,字面上将style属性添加到我们的HTML元素,就像我们开始学习CSS时那样。如果我们将内联样式与HTML元素关联,它将优先。

在我们网页头部的style部分或单独的.dot CSS文件中,任何样式都无法覆盖,因为原因在于如果你将样式代码直接附加到元素本身,那么我们可能想要在特定性之后将其应用到该元素。

识别元素的准确顺序,ID是唯一识别元素的方式,只有一个元素具有特定的ID,因此如果我给特定ID添加了样式,这在特异性方面将会非常有价值。接下来我们看类,如果没有ID。

我们查看的选择器是,我们是否通过类引用了元素,如果是的话,那么这将优先于其他情况,否则我们将退回到HTML元素的类型,是否为h1,有序列表或表格,简而言之,类型的特异性最低,类稍微更具体,ID则更高。

我们能提供的最具体的方式就是将CSS与HTML元素本身内联,所以让我们来看一个例子。

让我们来看这个代码的例子,例如我有一个ID为foo的div,里面只说了“hello”,而我在这里包含的CSS代码是,我想给所有的div设置蓝色,显然在这种情况下,没有冲突。

我们将看到“hello”这个词,并且它将以蓝色显示,但如果我们添加任何带有ID的内容,你应该看到它呈红色,因为ID的特异性高于单个标签,所以接下来我们会说这个“hello”将以红色显示,ID更为重要。

因此,这个元素将显示为红色,而且这些样式的顺序并不重要,不是说后面的优先,如果我将这些顺序颠倒,比如ID foo颜色红色,div颜色蓝色的顺序,仍然会显示为红色,因为这个ID选择器更为重要。

比仅仅是div的名称更具体,而div是那里存在的HTML元素名称,因此当你开始开发更复杂的样式表时,你可能会发现一些CSS代码会相互冲突,这时要牢记这些特异性规则是很重要的。

了解当你为元素添加样式时,它们实际会如何呈现。现在我们已经看到了几种 CSS 选择器,用于选择单个元素、选择 ID、选择类以及选择多个元素。结果是,还有其他许多 CSS 选择器。

也可以使用,所以我们看到了多个元素选择器,比如 TD 逗号 th 用于选择表格数据和表格标题,但还有其他一些选择器,这里只是一些样本,比如后代或子项,所以如果我只想样式化在表格内部的 div 或者我只。

想要样式化在。

某些类我可以使用这些后代和子选择器来以特定方式添加样式,还有许多其他 CSS 选择器,我们也可以添加,并且我们将探索其中的几个,以给你一个示例,展示这些 CSS 选择器如何实际工作。

我们将开始查看后代选择器,它用于选择某个元素的所有后代元素。例如,让我们创建一个新文件,我将称之为 descendant.html,然后再次从相同的代码开始,并在此文件的主体中。

页面上我想要一个有序列表,或许是某个项目,结果是,在 HTML 中的列表可以嵌套其他列表,也许你见过子项目,在其他项目里面有嵌套的子弹点,我可以在这里做到。我可以添加一个无序列表并创建一个子列表,比如子列表项一和子。

列表项二,或许在这里。

这是另一个列表项,现在我有几个项目,其中一些在无序子列表内部。让我们打开后代 HTML,看看那是什么样子,所以这是我们得到的,我们有列表项一,列表项二。另一个列表项,实际上可能是列表项三,所以我们有三个。

列表项,但在列表项二里面我有一个无序列表,让我们想象一下,比如说我只想样式化。

这些子列表项作为一个特定的颜色,也许我想让它们变成蓝色。例如,如果在我的网页的样式部分,我说我想要。

所有列表项样式化为蓝色,那么不是样式化为蓝色,而是当我刷新时,我将看到的内容。

页面上的所有项目都将变成蓝色,而不仅仅是两个子列表项,但我可以说,我只想要无序列表的子项,使用这个大于符号来表示,只有当有一个 ul 直接包含时。

在它内部的一个 Li,我希望它的颜色是蓝色的,现在如果我刷新,你会看到有序项目列表项目 1 2 3 并没有着色,只有无序列表中的项目才会着色。

那些直接是无序列表的子元素实际上会应用 CSS 样式,这个大于号指定了直接子元素,我可以像这样去掉 ul Li,这样也可以,你仍然能看到子列表项目 1 和 2,但这是一个更一般的选择器,称为后代选择器。

选择所有后代元素的选择器,它们可能不是子元素,而可能是孙子元素,如果那些子元素有其他附加的子元素,对于这一切来说,有助于开始考虑文档对象模型(DOM)的相关内容。

结构的树表示了我们各种不同 HTML 元素之间的关系,接下来我们可以开始查看一些我们可以使用的其他选择器,其中一个选择器可能是修改特定 HTML 元素的特定属性。

特定 HTML 元素的属性选择器,因此我将创建一个名为 attribute.html 的新文件,在这里让我们创建一个无序列表,将包含多个不同网站的链接,这里是一个将链接到 Google 的列表项。

我会链接到 Google,并称之为 Google,然后我会继续添加一个链接到 facebook.com,称之为 Facebook,我想象一下,如果我只想为 Facebook 链接添加样式,真的想强调 Facebook 链接告诉人们。

点击那个链接,因为通过将其颜色更改为完全不同的颜色来突出显示它。通常为了样式链接,我会说链接应该是蓝色,例如,它们默认是蓝色,但我可以明确说,链接的颜色应该是蓝色。

这样现在当我打开属性时。

HTML 中所有的链接都是蓝色的,但我也可以说,我希望 href 属性为 facebook.com 的链接,那些链接的颜色应该是红色。因此这个方括号表示法可以用来指定 HTML 元素的特定属性,只有那些 href 等于 facebook.com 的锚标签。

这些应该是唯一的红色链接,所以现在当我刷新时,我看到 Facebook 是一个链接,现在是红色的,而不是蓝色的,因为我很具体地选择了一个我想要使用的属性,以便引用那个特定的。

HTML元素,我们可以以更强大的方式使用CSS选择器,也许不仅仅是为了样式化特定元素,而是仅在特定条件下或仅当元素处于特定状态时样式化元素,这通常是在悬停时完成的。

当你悬停在某个按钮上时,会有东西弹出,或者你悬停在某物上,它的颜色会稍微改变。我们可以通过给CSS选择器添加所谓的伪类来开始实现这一点,让我们来看一个例子,看看如何在用户悬停光标时修改元素。

例如,我将打开一个新的文件hover HTML,在这里我将在页面的主体内给自己一个按钮,这个按钮是!

要说点击我,我们来为按钮默认按钮添加一些样式。

显示为相当简单的按钮,外观类似于这个。我想为这个按钮增加一点,告诉你我们来给按钮添加一些样式,宽度200像素,高度50像素,字体大小24像素,背景色可能是绿色。

例子中,我指定了一些大小信息,比如我希望字体的大小和按钮的背景色,现在这个按钮看起来是这样的,缩小一点,它显示“点击我”,但许多按钮,尤其是现在的按钮,有点意思,它们给你一点。

当你悬停在它们上面时,它们的颜色会稍微改变,怎么做到的呢?通常是使用CSS伪类,我可以这样说:按钮:

悬停的感觉,当我悬停在按钮上时,我希望你把背景色改为橙色,例如其他颜色。所以我指定了默认情况下,按钮的背景色应该是绿色,但当按钮被悬停时。

现在将背景色改为橙色,这样当我打开这个页面并点击时,如果我悬停在按钮上,按钮的颜色通常是绿色,改为橙色,这是一项非常强大的功能!

也可以访问,好吧,所以现在我们已经看到如何使用各种不同的CSS选择器。

哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P4:L0- HTML与CSS语法 3 (响应设计,Booststrap) - ShowMeAI - BV1gL411x7NY

我们可以精确地定义我们希望网页的样式,但我们可以使用CSS的另一项功能,这非常强大,那就是响应式设计,响应式设计的关键在于确保我们的。

无论你如何查看网页,网页看起来都很好,如今人们并不总是在他们的电脑上查看网页,而是在他们的手机或平板电脑上查看网页,因此在我们开始设计网页时,重要的是要以响应式的方式设计我们的网页。

因此,我们将探讨多种不同的方法,在网页中实现响应式设计,从讨论视口开始,视口是用户实际可以看到的屏幕的视觉部分,因此视口就是整个网页区域。

向用户显示内容,所以你可能会问,当你将这个页面转换到移动屏幕时会发生什么呢?许多移动设备默认情况下会将其视口视为与电脑屏幕相同的宽度,因为并不是所有网页。

页面是针对旧设备和移动设备优化的,你要确保在移动设备上可以看到所有内容,因此许多手机会将这样的网页缩小到适应移动屏幕,可能看起来有点像那样,现在当然这可能不是很好。

我们理想中希望的样子是,我们希望页面能够适应不同尺寸的屏幕,也许我们希望标题、图像和文本(如果它们就是这些)稍微增大,以填满整个屏幕,因此我们可以做的一个简单的事情就是在HTML的头部部分添加一小行代码。

我们控制视口的页面,这行代码提供了一些元数据给我们的HTML页面,并且表示我希望你将视口更改为设备的宽度,默认情况下,许多手机会使用一个实际上比设备宽度更宽的视口。

在电脑上加载一个页面,然后将其缩小到移动设备的大小,如果你和你的网页指定希望视口仅为设备宽度,那么页面在移动设备上看起来会好得多,但除了仅添加这一行代码之外。

我们可以对页面进行其他实际更改,以使其在不同屏幕上看起来更好,其中一项涉及媒体查询,媒体查询主要是关于控制我们的页面将如何显示,具体取决于我们如何渲染特定页面或屏幕的大小。

渲染页面,那么我们来看一个例子,看看我们如何使用媒体查询来控制页面的实际外观,这取决于我们使用的页面类型或浏览器类型。因此,我将打开一个新文件,称之为响应式HTML,因为我们。

现在我要尝试构建一个响应式网页,我将在页面的主体内包含一个大标题,写着“欢迎来到我的网页”,例如。为了展示现在响应式设计可以做什么,我可以说,让我添加一个样式标签。

这里我想说的是,如果屏幕的大小是某个特定宽度,那么我想以一种方式来设置页面样式;如果页面的大小是不同的宽度,那么我可能想以另一种方式来设置页面样式。你可以想象,当你缩小屏幕时,想要移动元素的位置。

为了重新排列它们,使页面在移动屏幕上看起来更好。因此,我们将做一个非常简单的例子,仅根据屏幕大小来改变颜色。现在让我在媒体查询中指定一下,媒体查询的语法看起来像这样,我将使用@符号和。

说媒体,然后指定我希望应用此查询的媒体类型。我可以说,页面的最小宽度为600像素,换句话说,如果页面的宽度为600像素或更大,那么就继续将主体的背景颜色设为。

红色,但我也可以添加另一个媒体查询,并说你知道吗,对于这个媒体查询,我希望最大宽度为599像素,这意味着如果屏幕的大小为599像素或更小,那么我可能希望将主体的背景颜色设为蓝色。

例如,现在让我们看看当我将这个页面拿出来时会发生什么。

实际上打开它,看看发生了什么,我会打开responsive.html,正常情况下我看到一个红色网页,因为我的屏幕更长。

宽度小于600像素,但请注意,当我缩小这个网页时会发生什么。如果我继续缩小它。

当屏幕变小时,如果宽度超过600像素,它将变为红色;如果低于600像素,则会变为蓝色。

宽度为像素时,颜色变为蓝色,因此我们现在能够使用这些媒体查询来真正微调如何在各种不同类型的设备上显示页面。如果是在大屏幕上,可能希望元素看起来某种方式;如果是在小屏幕上,可能它们看起来就会不同。

你不仅需要控制背景颜色,还可以通过使用这些媒体查询来控制你想要的任何CSS属性。你可以说在大屏幕上希望有一定的间距或填充,甚至可以在小屏幕上隐藏元素,前提是使用特定的CSS属性。

这个称为显示属性,它控制元素是否可见,最终组合在一起,这可以帮助让你的页面更加响应式。我们还可以应用多种不同的媒体查询到我们的页面,以检查移动设备的状态。

在竖屏或横屏的情况下,我们可以检查用户是通过电脑屏幕查看页面,还是尝试打印出页面内容。因此,我们有许多不同的选项来真正控制页面的外观,还有一些工具可以帮助我们处理移动响应。

我们的工具箱中也有一些工具来处理移动响应,最新版本CSS中内置的工具之一是称为Flexbox的东西。如果我们有多个元素,同时在同一页面上显示,而不溢出,Flexbox非常有用。

在进行响应式设计时要小心,如果我们不小心,想象一下我在电脑显示器上显示六个元素,当你将其转到移动屏幕时,你可以想象它们都缩小到几乎不可见的状态,这可能不是我们想要的效果。

如果我们试图设计一个移动响应页面,例如,你可能会想,我们该如何稍微调整一下呢?

我们可以做的另一件事是,将这些元素保持相同大小,但需要你进行滚动,这不是稍微更好。元素至少仍然可见,且在屏幕上足够大,但如果不必滚动那就更好了。

而且考虑到我们有这么多额外空间,如果没有足够的空间,我希望能够将元素包裹起来。这样,如果我将这六个元素转到移动屏幕,它们会转化为两行,例如顶部三行。

Flexbox是一个简单的方法,可以在我们的网页中实现类似的功能,所以让我们看看添加Flexbox到页面的实际效果。我将创建一个新文件,命名为flexbox HTML,我们将从相同的HTML代码开始。

我将首先创建一个名为container的div。我们创建一个容器是因为我们将明确指定,容器内的所有内容我希望添加Flexbox,以便能够包裹元素,使其能够换行。

现在让我添加一些示例文本,这是一段在 div 内的示例文本。用来演示 flexbox,我将重复这个文本大约 12 次。它会给每个编号,这里是一个,二,三,四,其余的我也会编号,这只是为了展示这些元素实际上可能是什么样子的。

理想情况下,这些应该是不同的内容部分,所以现在在我的 style 标签内,我将说,为了演示,我会说获取 ID 为 container 的元素,哈希标签指定获取某个特定 ID 的内容,我希望使用 flexbox 显示它,特别是我想使用。

Flex wrap 属性来指定如果行末没有足够的空间容纳所有元素,就将这些元素换到下一行。现在我可以为容器内的 div 指定一些额外的 CSS 属性,所以我可以说,对于容器内的所有 div。

通过这个子选择器再次获取容器内的所有 div,我可以给它们添加一些 CSS。我可以说让它们的背景色是某种绿色的阴影,给它们设置特定的字体,可能是 20 像素的字体,并添加一些边距和内边距。

空间可能每个都是。

宽度为 200 像素,所以只是为这些单独的 div 添加一些间距。重要的是我在这里说这个整个容器将是一个 flexbox 容器,如果你到达末尾,我希望换行。现在打开 flexbox HTML,我现在看到 12 个元素。

这些元素有多行,但请注意,当我缩小页面时,如果没有足够的空间容纳它们,会发生什么。

元素移动到其他行,现在每行只有三个元素。

特定行上,现在每行只有两个元素。

我可以很好地使用 flexbox 适应不同大小的屏幕,无论你是在大屏幕还是小屏幕上查看,这些内容仍然会看起来不错,因为我能够响应式地适应任何可能发生的情况,这是一种存在的布局范式。

在 CSS 中,这种 flexbox 布局还存在其他布局。一个常见的就是网格布局,适用于任何想要在特定网格中排列的东西,或者某些列需要是特定的宽度,而其他的可能会更灵活,我将展示一个示例。

这也是为了给你展示网格布局可能是什么样子的。所以这里是网格 HTML,我将在页面的主体内创建一个 div,给它一个 ID 为 grid,然后我将添加 div class = grid item,同样是一个类,因为可能会有多个项目。

继续创建一堆很棒的项目,并为每个项目编号,比如2 3 4 5 6 7 8 9 10 11 12,所以我有一堆很棒的项目在一个ID为grid的div中,现在我想在这里添加一些样式,我会说对于grid,给它一个背景颜色,也许会是某种背景色。

应该是绿色,显示为grid,我希望它作为一个网格显示,现在有几个属性,我将要指定,也许我想在网格周围添加一些内边距,但重要的网格属性是网格列间距,决定列之间的空间。

每列可能我会说大约20像素,同样还有一个网格行间距,决定网格中每行之间的空间,我可能会说这里是10像素,最后网格模板列是我用来指定将会有多少列,以及每列的宽度。

列的宽度,因此如果我想要三个列,我可以指定第一列应为200像素,第二列也应为200像素,第三列可以自动调整大小,正好填满屏幕,我会说为自动,所以第一列200像素,第二列200像素。

第三列自动处理,现在对于所有的网格项目,我可以为它们添加一些样式,我会给它们一个白色的背景色,以便区分它们,并且还会设置一些字体大小和内边距,也许还会让它们居中,所以只是添加一些额外的CSS。

属性以便于按照我想要的方式显示,但这里需要关注的重要属性是显示为grid,然后我正在指定网格的布局方式,列之间的空间有多少,行之间的空间有多少。

行和每一行的宽度,现在如果我打开grid.html,这就是那个网格的样子,我现在有一个宽200像素的第一列,一个宽200像素的第二列,还有一列将会自动调整大小。

动态地基于我的屏幕有多宽或多窄,因此当我。

当屏幕缩小时,第三列也随之缩小,而当我扩大屏幕时,它。

也随之增长,取决于这个。

窗口的情况,所以flexbox和grid是我们可以使用的一些非常强大的工具,可以让我们更轻松地实现这些功能。

为了能够使用移动响应设计,确保我们的页面在任何浏览器或设备上都能美观展示,但事实证明,存在许多库可以为我们处理很多这些问题,有些人可能会。

已经写好的 CSS 代码让我们的文本看起来不错,让我们的按钮看起来不错,以确保东西。具有移动响应性,其中一个叫做 bootstrap,bootstrap 是一个非常流行的 CSS 库,我们可以用它来。使用他们已经写好的样式,这样我们就不需要写。

所有样式从头开始,所以这是 bootstrap 网站的样子。我会继续给你展示,现在只是给你一个在像 bootstrap 这样的库中可用的样本。如果我去 get bootstrap calm,这里是 bootstrap 的网站,如果我去他们的。文档,在这个第一个链接里,我。

可以查看所有给我访问的 bootstrap 组件。这些东西,比如警报,例如,这里有一个以非常特定的方式样式化的警报,它有特定的字体,有一定的。填充和某些颜色,如果我想。复制这个警报,使用后。

bootstrap 的代码,我可以简单地对一个 div 应用某些。类,bootstrap 会为我处理应用正确的。样式的过程,我不需要从头开始编写所有。的这些样式,bootstrap 已经写了很多样式。那如何实际使用 bootstrap 呢?要开始使用。

bootstrap,你需要做的就是复制。

CSS 链接,bootstrap 提供的链接,放在你文件的顶部,所以如果我拿这个 CSS。链接,然后返回到像。

hello dot HTML,你会记得,它最初看起来像这样。

就是 hello world,我可以加一些 bootstrap 使其更好看。

好吧,我想把这个 HTML 文件应用 bootstrap 样式,让它看起来更好看,所以我刷新页面,现在你会。注意到 bootstrap 为我选择了一个自定义字体。让事情在 bootstrap 自己眼里看起来更好,如果我想添加 bootstrap 元素,我可以。

说好吧,让我去他们的组件,我想添加一个警报,我可以直接复制他们的警报代码,这里是。

他们的主要警报代码是一个,看起来是蓝色的警报,我可以。

在我页面的主体内继续添加一个警报,也许改变文本。这里是我的警报,例如现在当我重新加载 hello dot HTML,我看到一个根据 bootstrap 样式显示的警报。再次,我可以通过更改这些。类来改变那个样式,所以一个主要的警报显示为。

成功警报显示为绿色。

危险警报是红色的,如果我想给用户一个危险警报,告诉他们在网页上做错了什么,我可以把警报改为主要的。

将我的HTML中的内容改为警报危险,然后在刷新时。

现在我看到警报显示为红色而不是蓝色,所以bootstrap让我们可以使用许多不同类型的组件、不同方式添加面包屑、警报、轮播以及其他元素到我们的页面上,便于快速美化页面。

而无需过多担心自己编写CSS,因为bootstrap为我们编写了很多内容,bootstrap甚至包括了一种确保网页在移动设备上响应式的方法,使用称为bootstrap列模型的东西。我现在给你展示一个例子,bootstrap将其页面分成十二个单位。

明确的列,所以我可以做的一件事是,我在一个名为列零的HTML示例中拉出了这个,注意在我的页面主体中,现在我有一个类为container的div,还有一个类为row的div,bootstrap将每行分成十二个单位的列。

例如,这里我有一大堆每个宽度为三的div。

所以如果我有四个三单位的列,它们将占用总计为12的空间,也就是填满整个页面。

屏幕,所以如果我现在打开源代码,columns zero dot HTML,看看这个。

看起来我有四列,每列宽度为三,因此。

当我缩小时,这些列将自动调整大小,以确保。

现在它们的大小总是合适的,只要加起来是12,它们不需要都是相同的大小。

例如,如果我只想要三列而不是四列,我可以通过删除那些行来去掉第四列,或许将第二列的大小改为三,改为大小六的列。现在如果我刷新页面,突然我就得到了。

我看到三个列,中间的列是两边列的两倍大,随着我缩小这个页面,我也能看到它随之缩小。使用bootstrap列的一个优点是它们也可以是移动响应的,也可以换行。

如果需要的话,可以转到其他行,例如让我拉出列。

在一个HTML示例中,我有一行,让我们看看里面发生了什么。请记住,每行在bootstrap中被分成十二个列单位,但bootstrap除了让我指定列应该占多少单位外,还允许我指定该列的单位。

这应该取决于屏幕的大小。因此,如果我在大屏幕上,如LG所示,这表示在大屏幕上,这个div应该占三个单位的空间,而这个div也应该占三个单位的空间。这四个div在大屏幕上每个都占三个单位。

总共有十二个单位的空间,因此它们都会显示在同一行上。但是在小屏幕上会发生什么呢?这里叫做SM,每列在小屏幕上我说每列应该只占六个单位的空间,即总共十二个中的一半。

所以这里用掉六个,那里也用掉六个,总共十二个,意味着接下来的两个六个需要放到第二行,而bootstrap是***********。确切地说,这些元素应该最终如何布局。因此,现在如果我打开columns one.dot HTML,看看大屏幕上有什么。

四个列全部在同一行,但随着。

当我找到一个更小的屏幕时,最终我们会看到一些变化。我现在看到第三和第四部分移动到第二行,因为在小屏幕上,窗口变小时,我只有。

我有能力在第一行显示两个元素,然后在下面的行显示两个元素。总之,有很多不同的方法可以使用CSS,以确保我们的页面在移动设备上是响应式的。我们可以使用bootstrap的列模型来确保列在屏幕大小变化时会移动。

当窗口缩小或扩大时,我们也可以使用像flexbox和网格模型这样的东西,编写自己的CSS,以确保我们的页面是响应式的。这取决于用户正在使用的屏幕大小,以便访问我们的网站。因此,这些是我们仅使用CSS即可获得的一些非常强大的功能。

你可能会想象,随着我们开始编写越来越多的CSS,会有更多的重复内容不断出现,我们已经在某种程度上看到了如何最小化CSS中的冗余。我们已经看到如何将CSS移动到网页的样式部分。

我们甚至看到如何将CSS移动到一个完全不同的文件。然而,我们尚未看到如何处理其他类型的冗余。因此,让我们现在来看一个例子,假设我想以不同的方式为多个不同的元素设置样式,但使用一些共同的属性。

例如,让我创建一个新文件,这里称为variables HTML,你马上就会明白为什么。我会继续复制hello.dot HTML,但我会去掉里面的所有bootstrap。假设这里我有两个列表,一个有序列表和一个无序列表。

列表中我的无序列表有可能是三个无序项。

项目,我的有序列表也有三个有序项,再次只是为了演示,我展示了我们有。

这两个列表现在我将打开variables.html给你一个关于它可能看起来的感觉,我们有三个无序项在无序列表中。

在有序列表中列出三个有序项,假设我想稍微以不同的方式来为这些项添加样式,也许在页面的样式部分,我想将无序列表的字体大小设置为14像素,并可能将颜色设置为红色。而我的有序列表我希望它的字体大小也许设置为更大的18。

像素和红色的颜色,我想保持所有文本的颜色相同,但我希望字体大小有所不同。现在如果我刷新这个页面,我看到它们确实是不同的大小,有序列表项的大小大于无序列表项,并且它们都是红色,但有一些冗余。

在我写CSS代码时引入了重复。当我写CSS代码时,我重复使用了红色。如果我想将颜色从红色更改为蓝色,我就得在两个不同的地方修改我的代码,最终我希望我的CSS能够。

变得更强大,因此这引出了我们今天的最后一个主题,一个叫做sass的语言,sass本质上是CSS的扩展,它为CSS提供了额外的功能,只是为了让我们能够更强大地使用和操控。

以一种更快的方式来使用CSS,并消除我们在以前的CSS中可能存在的一些重复,而sass的一个关键特性是能够拥有变量。因此,让我们来看一个示例,现在我要创建一个新文件,通常我们创建CSS文件时会称它们为。

像variables.dot CSS这样的命名来表示CSS文件,然而sass是另一种语言,所以它需要不同的扩展名。我们通常使用.dot s CSS来表示这是一个sass文件,因此现在这是variables.dot s CSS,现在我可以在sass中实际。

以我们在像Python这样的编程语言中创建变量的方式来创建变量,尽管CSS通常不支持变量,但sass将赋予我们这种能力。在sass中,所有变量都以美元符号开头,所以我可以创建一个变量$dollar sign color来创建一个变量。

创建一个叫做color的变量,我可以说这个变量color的值将是红色。所以这一行,即第一行,是我告诉sass我想创建一个叫做color的变量,并希望它的值为红色,现在我可以添加之前的样式,我只需使用普通的CSS,并说对于一个。

无序列表中,我希望字体大小为 14 像素,但颜色不说红色,我可以使用变量的名称,可以说美元符号 color,意味着使用变量 color 的值作为这个无序列表的颜色。然后对于有序列表,我也会说字体大小为 18 像素,并且。

颜色也应该是这个叫做 color 的变量。通过使用变量,我消除了重复,而不是在多个地方出现红色这个词,如果我需要更改它,我只需要更改一次,现在我只需定义一次变量。

如果我需要对这个特定文件进行修改,可以在一个地方进行更改。现在,让我们尝试链接这个文件,而不是返回到变量 HTML,而是放置样式代码。

这里我将继续链接样式表,并说 href 应该是 variables s CSS,因为这是我的样式所在的文件。现在让我尝试打开链接后的变量 HTML。

CSS 和所有的东西似乎没什么问题,但我指定的字体大小。 我指定了一切应该是可读的,但它并没有显示出所有内容。

显示的是黑色,我没有看到任何大小的差异。原因是,尽管网页浏览器如 Chrome、Safari 和 Firefox 可以理解 CSS,但它们默认无法理解 s CSS 或 sass。sass 是。

一个浏览器默认不理解的 CSS 扩展。因此,为了解决这个问题,一旦我们有了它,我们需要将其链式转换,也就是将 sass 翻译成纯老式的 CSS,以便我们的浏览器能够理解。为了做到这一点,您需要安装一个程序。

叫做 sass 的程序可以安装在您的电脑上,无论是 Mac、PC 还是 Linux。现在在终端中,为了进行这个编译,我将输入 sass。variables dot s CSS,这是我想编译的文件:colon variables dot CSS。因此,variables dot SCSS 是我想编译的文件。

我希望生成的是 variables dot CSS,我想把我的 sass 文件转化为一个。

纯老式的 CSS 文件,我将继续按回车并写下。

编译过程现在完成了,所以,现在在变量数据 HTML 中。不是代表 CSS 文件,我将要将 CSS 文件作为样式表引用。因为我的网页浏览器只理解 CSS,它不理解 sass。现在,当我加载页面时,我看到了预期的结果,一切都显示出来了。

还是红色,字体大小不同,因此最终这是一个两步的过程。我首先需要将我的Sass代码编译成CSS,然后我可以将CSS链接到这个特定的页面,但现在的好处是,如果我想进行某种更改,我想更改颜色而不是。

在两个地方进行更改,或者你可以想象在一个更复杂的页面中,可能有十几个或几十个地方。我只需去CSS文件,将颜色从红色更改为蓝色。现在,如果我刷新页面,一切仍然是红色的,这因为我忘记了一个步骤。我更改了Sass文件,但这不会自动更改CSS文件,我需要。

通过说 sass variables.des CSS variables.dot CSS 重新编译CSS文件。

更新后的Sass文件,现在我看到了更新的变化。如果你想知道更新后的文件看起来怎么样,我其实可以查看变量.dot CSS文件,看看那里有什么代码,虽然它的样式有点奇怪,但你可以看到我有一个ul,字体大小为14,颜色为。

蓝色,因此他们用这个变量替代了蓝色这个词,并且他们对有序列表也做了同样的事情。现在在实践中,如果我在构建网页,使用Sass时,如果我每次都需要回去重新编译我的Sass为CSS,这将相当烦人。我希望能够。

我所要做的就是自动化这个过程,而Sass使得这一切变得简单。我可以直接说 sass --watch variables.s CSS variables.dot CSS,这样你就会看到Sass正在监视变化,Sass会监控variables.dot CSS文件。如果我改变了我的Sass文件,Sass会知道。

它会自动重新编译对应的CSS文件。现在你可以仅使用单个文件来做到这一点,但。

如果你有多个不同的Sass文件,也可以对整个目录进行处理。那么现在我可以做的是,如果在变量的CSS文件中更改颜色,而不是。

蓝色,我现在想要将其更改为绿色。例如,我现在保存了variables.s CSS文件,现在在没有做任何事情的情况下,看看我的终端,Sass检测到了对variables.s CSS的更改,因此它给。

给我一个我原始CSS文件的新版本,如果我现在回到我的网页浏览器。

现在刷新页面,所有文本都是绿色的,这正是我所期望的。这是Sass给我们带来的一个非常强大的特性,它让我们能够将变量添加到我们的CSS代码中,以提取共同点。如果有重复的字体、常用的颜色、常见的边框或样式,我可以。

想要应用于很多不同的东西,使用sass变得更简单,最后,我们将看看sass给我们能力的其他几个特性,其中之一是能够嵌套。CSS选择器在其他CSS选择器内部,所以你已经看到的一件事是。

例如,如果在CSS中,我想样式化所有在div内部的无序列表,我可以说像div箭头无序列表,以便样式化在div内部的无序列表。sass会给我们一种更好的语法。

特别做这种事情,通过允许我们在其他CSS片段内部嵌套CSS,作为一个示例,我会打开一个我已经带来的文件,叫做,嵌套HTML,所以这是我们可能会在嵌套HTML中看到的,页面的主体在主体内部。

页面中我有一个div,里面是一个段落和一个,列表(无序列表或ul)也在这个div内部,我们还有一个段落在div外部,还有一个列表在div外部。

除此之外,还有许多不同的元素,其中一些位于其他元素内部,我想做的是非常精确地样式化这些页面,sass将允许我们编写一个看起来像这样的sass文件。

让我们看看它在做什么,它说对于整个div,我想给这个div一个18像素的字体大小,然后CSS通常不允许我们做的,但我们现在可以用sass的力量做到的是,对于在该div内部的任何段落,继续给这些段落一个。

蓝色的颜色,对于任何在div内部的无序列表,给这些无序列表一个绿色的颜色,通过将这些CSS选择器嵌套在其他选择器内部,我们能够表达出,我只想样式化段落,如果它在一个div内部,这只是一种更好的方式。

进行一些更复杂的样式任务的更干净的语法,可能会出现,那么如果我像这样把这个CSS文件,转换为普通CSS,结果会是什么呢?让我们来看看,我们可以尝试一下,让我进入,我的嵌套文件夹,里面有所有这些文件。

位于的位置,如果我运行sass嵌套。

数据CSS将其转换为嵌套的点CSS,现在让我们打开嵌套CSS,看看。它在这里的样子,变成了,给所有div一个18像素的字体大小,然后我们,使用之前看到的相同后代选择器。表示法,所有在Dizz内的段落应该是。

所有未排序列表的颜色应为绿色,并且这确实有效,我们可以从一开始就写这个 CSS,但使用 sass 可以更干净,更易于阅读,真正表达段落应在。

以这种方式结构化,未排序列表以其他方式进行样式,使用这种嵌套方法通常使我们更容易查看这个 CSS 页面,并且。

不同样式代码彼此之间的交互,所以经过这一切,如果我打开。

在嵌套 HTML 时,我们可能会看到像这样的东西。段落位于 div 内部,列表也在 div 内部,这两者的样式都发生了变化,但与之不同的是,这些段落位于 David 外部,列表也位于 div 外部,它们的样式。

样式有所不同,所以现在我们在 sass 中看到的两个功能。我们首先看到了拥有变量的能力,以确保我们在代码的许多地方不重复自己,现在我们看到了将 CSS 选择器嵌套的能力。

通过利用 sass 之间的关系,最后我们将看一下 sass 将给我们的最后一个功能,这被称为继承。如果我们有某些 CSS 选择器与其他 CSS 选择器相关,但它们可能添加了一些额外的信息,在这种情况下。

我实际上要向你展示。

首先完成产品,让我们进入继承,并打开 inheritance.html。在这里你可以看到,我几乎尝试实现了 bootstrap。样式警报消息在 HTML 中,我有一个位于顶部的成功消息,一个警告消息,然后是每条消息中的错误消息,你会注意到我们的。

以不同的方式进行样式,尤其是它们。

每个颜色不同,但尽管如此,它们有很多共同点,它们共享一个公共边框,共享一个公共字体,共享一个共同的大小,以及许多其他属性都在这些元素之间共享,只是它们之间有一些不同的地方,我本可以写三种不同的 CSS。

选择器处理所有这些情况,但可能会有一些重复。所以我可以利用 sass 提供的功能,让我们看看 inheritance 的 CSS,查看代码。虽然一开始看起来有点晦涩,但这就是我所做的。

我在这里定义了一个%,这就是通用消息将是什么,我可以稍后扩展以添加额外信息到所有消息,无论是成功消息、危险消息还是警告消息。它们都会有相同的边框。

在它周围添加填充和边距,但每个特定消息会略有不同。它们有什么不同呢?让我们来看这里。任何具有成功类的内容,我将说它扩展了这个消息,通过扩展这个消息,我的意思是,任何具有成功类的内容。

将会继承所有这些CSS属性,包括字体、边框、填充和边距,但它会添加额外的信息。特别是,我们将添加一个颜色,成功消息的背景颜色将是绿色,我扩展了。

消息是什么,但特别说这个消息有一些额外的样式,我们也将分配给它,其他两个消息的行为非常相似,我的警告消息扩展了消息,但背景颜色应该是橙色,最后错误消息。

这也扩展了消息,但这次,给我们一个红色的背景颜色。因此,现在当你将所有这些编译到 inheritance.dot CSS 中时,预先编译的就是这个,最终会看起来像这样。它将我写的内容翻译为成功、警告和错误。

应该有所有这些属性,但成功还应该有这个背景颜色,警告应该有这个。

背景颜色错误应该有这个,背景颜色所以我们又可以。我们可以写这段CSS,SAS没有做我们自己用CSS不能做到的。SAS只会让我们更容易做很多相同的事情,所以我们可以用更好的语法写东西。

通过说成功消息从消息中继承,但添加了背景颜色,警告和错误消息也是如此,但使用更简单、更好的语法,这样我们就可以让计算机将SAS代码编译成CSS。

这就是我们在使用HTML和CSS构建网页程序时所见的一些基本知识。我们看到如何使用HTML描述网页的结构,决定页面上要显示什么,然后我们看了CSS,如何用不同的方式来美化我们的页面。

自定义样式,比如颜色和布局,但也考虑响应式设计,比如在手机屏幕或平板上的表现,确保我们的网页在这些屏幕上看起来也不错,最后我们看了一下Sass,作为CSS的扩展,增加了许多额外的功能。

具有变量、嵌套和继承等特性的功能,使我们更容易编写可以应用于网页的样式。

接下来我们将过渡到如何在更大的网络应用中使用HTML和CSS,同时开始整合其他工具,如Python、JavaScript和其他语言及框架。这就是使用Python和JavaScript的网络编程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/817172.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

斯坦福-CS106A-B-L-X-编程入门笔记-九-

斯坦福 CS106A/B/L/X 编程入门笔记(九) 【编程抽象方法 cs106x 2017】斯坦福—中英字幕 - P13:Lecture 13 - Pointers and Nodes - 加加zero - BV1By411h75g 很高兴再次见到大家,嗨,现在是第五周,我们快完成一半了,本周我们将学习一个叫做指针的新概念,我们还将学习如何…

斯坦福-CS106A-B-L-X-编程入门笔记-八-

斯坦福 CS106A/B/L/X 编程入门笔记(八) 【斯坦福大学】CS106B C++中的抽象编程 2018年冬(完结中英字幕机翻) - P6:【Lecture 06】CS106B Programming Abstractions in C++ Win 2018 - 鬼谷良师 - BV1G7411k7jG 好吧,今天是星期一。我们自由了。我们今天被邀请到这个区域…

基于WOA鲸鱼优化的GroupCNN分组卷积网络时间序列预测算法matlab仿真

1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a3.部分核心程序 (完整版代码包含详细中文注释和操作步骤视频)for t=1:Itersfor j=1:Dif rand_flag<0.5 if abs(K1)>=1RLidx = floor(Num*rand()+1);X_rand = xwoa(RLidx, :);D_X_r…

Mann–Whitney U test R语言检验

001、set.seed(123)group_A <- rnorm(30, mean = 5, sd = 1) # 药物A的效果 group_B <- rnorm(30, mean = 6, sd = 1) # 药物B的效果u_test_result <- wilcox.test(group_A, group_B, alternative = "two.sided")print(u_test_result)。

RabbitMQ 工作队列(Work queues)模式示例

总结自:BV15k4y1k7Ep模式说明Work queues与简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。消费者之间是竞争的关系。 应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。 代码 Work queues与简单模式的代码是几乎一样的…

定时中断基本结构

打开时钟-->配置 时钟源-->配置 时基单元-->配置 中断输出-->配置 NVIC-->启动 定时器 程序 void Timer_Init(void) {RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);/*配置时钟*/TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitTypeDef TIM_TimeBaseIni…

定时器-输出比较PWM

打开时钟-->配置 时钟源-->配置 时基单元-->配置 输出比较单元-->配置 GPIO口 代码 void PWM_Init(void) {RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/*配置 时钟*/TIM_InternalClockConfig(TIM2…

专题二:操作系统基本原理

1. 操作系统概述 操作系统:管理系统的硬件、软件、数据资源 控制程序运行 人机之间的接口 应用软件与硬件之间的接口进程管理 存储管理 文件管理 作业管理 设备管理 2. 进程管理 2.1. 进程状态(三态模型、五态模型) 2.2. ★★★信号量与PV操作★★★ 2.2.1. 前趋图 2.2.2.…

C++内存模型实践探索

C++对象模型是个常见、且复杂的话题,本文基于Itanium C++ ABI通过程序实践介绍了几种 简单C++继承 场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局。前言 C++对象模型是个常见、且复杂的话题,本文基于Itanium C++ ABI通过程序实践介绍了几种 简单C…

乘风破浪,扬帆出海,专门为英语学习者设计的在线学习平台之English Pod

什么是English Podhttps://learnenglishpod.comEnglish Pod是一个专门为英语学习者设计的在线学习平台,提供各种各样的英语学习播客(pod cast)和教学资源。其目标是帮助不同水平的学习者通过日常对话和实用内容提高英语听力、口语、词汇和语法能力。EnglishPod的课程通常包括对…

课堂练习

Complex.h中的代码:#include <iostream> #pragma once class Complex { public:Complex(double x=0, double y=0);Complex(const Complex& p);~Complex();void add(const Complex& p);double get_real() const;double get_imag() const;friend Complex add(cons…

乘风破浪,乘风出海,学习英语之English Pod

什么是English Podhttps://learnenglishpod.comEnglish Pod是一个专门为英语学习者设计的在线学习平台,提供各种各样的英语学习播客(podcast)和教学资源。其目标是帮助不同水平的学习者通过日常对话和实用内容提高英语听力、口语、词汇和语法能力。EnglishPod的课程通常包括对…