哈佛-CS50-计算机科学导论笔记-一-
哈佛 CS50 计算机科学导论笔记(一)
哈佛CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P1:介绍与入门 - ShowMeAI - BV1gL411x7NY
[音乐]。
你好,世界,这是CS50,这是使用Python和JavaScript的网络编程。与CS50区的布莱恩一起,这门课程将从CS50的基础上继续,深入探讨使用Python的网络应用的设计和实现,使用像Django、React和Bootstrap这样的框架。
首先,仔细查看HTML和CSS,这些语言可以用来描述网页的结构和样式,之后我们将介绍获取版本控制工具,以便跟踪我们对代码所做的更改,并允许多个人在同一项目中合作。
同时,之后我们将更深入地探索Python,探索这门编程语言的一些高级特性,特别是我们如何利用它创建动态网络应用,使用一种称为Django的网络框架,我们将充分利用Django,特别是它处理。
数据,与SQL模型和迁移一起,创建使用数据库的网络应用,以便创建交互式用户体验。之后,我们将更深入地探索另一种编程语言JavaScript,探索如何使用JavaScript创建动态和交互式用户。
界面,编写响应事件的代码,并根据某种用户交互操作网页,之后我们将探讨一些行业最佳实践,包括测试,以确保我们的代码按预期工作,以及持续集成和持续交付,以便我们能够。
快速进行更改,并在我们能够更新代码时部署这些更改,最后,我们将考虑可扩展性和安全性的问题,因为我们将我们的应用从个人电脑转移到网络上,让任何人都能访问,我们将讨论如何实现。
确保这些应用能够扩展,以及我们如何确保这些应用在安全方面的能力,沿着这条路,你将有机会通过构建你自己的网络应用来实践所有这些,这是CS50。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P10:L3- Django网络编程 2 (模板) - ShowMeAI - BV1gL411x7NY
现在我需要做的就是创建那个模板,所以让我在hello目录内创建一个新文件夹,命名为templates。在这个文件夹内,我将创建一个名为hello的文件夹,因为模板名称是hello/index.html,所以我需要在里面创建一个名为hello的文件夹。
一个名为index的文件,HTML我可以简单称其为index而不加前缀。你好,但我们常常想要为模板添加目录名的前缀。这样做是为了给它们命名空间,以确保如果我们在多个不同的应用中有多个不同的index.html文件,可以确保它们不会互相冲突。
所以Django的最佳实践是使用hello/index.html或你的应用名称,并在那个hello目录内创建一个名为index.html的文件,这个index.html可以包含任何HTML内容,和我们之前看到的一样,所以我可以添加doctype,HTML标签,标题为hello,也许在body内我会。
有一个h1标签显示“你好,世界”,例如,所以我可以有一个完整的HTML。
就像这样,当我访问我的Web应用程序的默认路由时,通过返回到我的Web浏览器并访问/hello,例如。现在我看到的是我在hello/index.html中定义的HTML,它是一个大的h1标签,上面写着“你好,世界”,结果是这些HTML页面这些。
我可以使用Django渲染的模板也是参数化的,没错。也许我想实现hello/。
赫敏作为某种HTML页面,一个显示"你好,赫敏"的HTML页面。使用现成的HTML,你实际上无法做到这样的事情,HTML是一种标记语言,而不是编程语言,这意味着它默认不支持代表变量的功能。
某人的名字,但利用Django的能力将HTML页面视为模板,我们可以渲染,Django在现有HTML上添加了自己的模板语言,可以说,我可以利用这一点来渲染一个实际上包含变量或条件的HTML页面。
在其中或者在其中循环,就像我们稍后看到的那样。所以,让我们继续回到视图stop pie,我想更改的函数是这个greet函数,它现在只是返回一个HTTP响应,但我想渲染整个页面,例如,我可能会怎么做。
渲染,让我们渲染一个名为hello/greet.html的模板,然后这个render函数可以接受一个可选的第三个参数,称为context。context是我想提供给模板的所有信息,所有我希望模板拥有的变量。
访问和我可能想要的一个东西,我的模板可以访问的,例如名字,这里是一个Python字典,只是一系列键值对,这个名字可能与某个值相关联。因此,名字是键,我想要的值是名字的大写形式,那么。
现在,当我渲染这个模板hello/greet HTML
时,我为这个模板提供了一些额外的上下文,一些额外的信息,由这个字典表示,我提供了一个键为名字的信息,实际上是给这个模板访问。
将一个变量名为名字,它的值是name.capitalize()
。这个名字就是greet函数的参数,所以现在我可以返回这个模板,并在greet.html内部使用这个名为名字的变量。我该如何做到呢?好吧,首先让我们创建这个greet。
HTML文件,因此在模板的/hello目录下,我已经有了index.html,我将创建一个新的文件,名为greet.html。在greet.html内部,我将编写一些HTML,并在HTML的主体中,之前我可能说过“你好,世界”,我。
我不想说“你好,世界”,我只想说“你好”,无论名字是什么,包含在那个名为名字的变量中的内容。那我该如何做呢?
关于怎么做,我可以使用这对双大括号,双大括号是Django模板的一部分,这允许我说我想将一个变量的值插入到模板的特定位置。因此,如果我在这里包含单词名字,那么我想。
这里所说的是,当你在页面的主体中渲染这个greet.html模板时,我希望那里有一个h1,一个大标题,上面只写着“你好,{”。我在说在这里插入变量名字的值,他的名字是赫敏,如果是哈里,那么就是“你好,哈里”。
如果是大卫,那么就是“你好,大卫”。现在我们来试一下。现在当我访问 /hello/Harry
时,我看到一个大H1标签,上面写着“你好,哈里”。如果我访问 /
。
你好 /Ron
,我看到一个大的H1标签,上面写着“你好,罗恩”。所有这些发生的原因是,因为当我有一个URL,就像是某人的名字,我们会调用greet函数,greet函数内部的内容将名字作为一个。
参数渲染greet HTML模板,并将名字作为上下文的一部分传递,这样模板就能访问到。在greet.html
内部,这里是实际的HTML文件,这里我们再插入。
变量名称的值,所以有很多不同的文件在运作,但这些不同文件的原因是为了帮助保持各自的独立性,有一个文件专门负责URL,并将人们引导到点击这些URL时应该发生的事情,然后我们有一个。
文件views.py完全负责决定这个特定视图应该渲染哪个模板,应该传递什么信息作为上下文,然后我会单独为每个HTML模板创建一个文件,说明页面的实际外观。
如果你开始考虑这个网络应用程序内部组件的分离,它可以帮助使Django应用程序的结构更清晰。因此,我们现在能够使用Django模板语言在HTML模板中放入变量,从而创建无限数量的。
不同的路由,我可以访问/hello/某个名字,以便向该人显示问候,但Django模板语言甚至比这更强大,还有很多附加功能,模板语言将为我们提供访问权限,我们将看一下。
现在开始,我将向你介绍一个你可能熟悉的网站。我认为它是互联网上最简单的网站之一,真实的网站叫做“现在是圣诞节吗.com”。
如果我访问“现在是圣诞节吗”,并按下回车,网站会说“不是圣诞节”,你必须相信我的话。如果你在圣诞节那天访问这个网站,它会说“是的”,所以这个网站非常简单。
你可能会想象,这其实只是一个HTML页面,可能包含一个大标题,这个标题在这种情况下仅仅说“否”,但在圣诞节那天则说“是”。那么,像这样的页面是如何实现的呢?一种可能的想法是,在圣诞节那天,维护网站的人会进入。
HTML将“否”改为“是”,然后之后再将“是”改回“否”。但如果我们意识到我们可以使用Python逻辑来做这一点,我们就可以更聪明一些,使用条件逻辑来决定网页最终将如何被渲染。
例如,条件可能是简单的逻辑,比如如果今天的日期是圣诞节,则渲染“是”,否则渲染“否”。因此,我们将使用Django,看看Django模板语法的一些特性,以便能够创建这样的一个网站。我们不会创建“现在是圣诞节吗”,而是。
我将创建的是一个新的应用,它检查当前日期是否是 1 月 1 日。这将是一个独立的应用,与我们的 Hello 应用有所不同。Hello 应用只是向人们打招呼,而新年应用则是检查是否恰好是新年。
现在继续创建一个新应用,我可以通过在 Python 管理中输入命令来做到这一点。python manage.py startapp
,我将把这个应用称为新年,例如我想创建一个名为新年的新应用。我现在已经创建了这个新应用,如果我输入ls
,你会看到我不仅有一个 Hello 目录,代表 Hello 应用。
我现在有这个新年的目录,表示这是一个新的。
名为新年的应用,与之前一样,我需要进入项目目录中的 settings.py
,并将新年添加为一些已安装的应用。这现在是我网络应用中存在的一个新应用,所以它也需要成为一个新的已安装应用。对于新应用,我们还需要做什么呢?我需要进入 urls.py
,为第 3 讲。
和之前一样,我有一个路径,表示当我访问 /hello
时,你应该去访问所有 Hello 应用的 URL。让我添加一个新的路径,表示如果我访问我的应用程序 /newyear
,那么你应该去访问新年文件的所有 URL,转到 newyear.urls
,代表新应用中的 urls.py
文件。
新年应用,现在这个文件默认没有给我们,所以我们需要创建它。我需要进入我的新年文件夹,创建一个名为 urls.py
的新文件。在这个文件中,我将从 django.urls
导入 path
,从 .
导入 views
,然后定义一些 URL 模式,与之前一样,我将有一个。
单一路径加载在 views.py
中的索引功能,并且它的名称是 index
。再次反映我们在 Hello 应用中已经看到的结构,这个应用只会有一个路由,空路由加载索引功能,现在只剩下。
实际上要创建那个索引功能,所以我进入视图间谍,然后在这里。现在我将定义一个索引功能,它接受一个 HTTP 请求作为参数。在这个索引功能内部,我想添加一些逻辑来检查。是否是 1 月 1 日,那么我该如何做到这一点呢?
在 Python 中,事实证明有一个日期/时间模块,你可以通过阅读其文档了解它的工作原理。日期/时间模块。
让我访问关于日期和时间的内容,例如,我可以在 Django 之外玩弄日期/时间模块,以便感受它是如何工作的。如果我在命令行中输入 Python,实际上我得到的是 Python 解释器,它让我只需编写 Python 代码。
现在进行实验和测试,看看运行这段 Python 代码的结果是什么,我可以尝试像导入 datetime,例如,创建一个新变量叫 now,它是 datetime.datetime.now,恰好在 datetime 模块中有一个名为 datetime.now 的函数。
当前的日期和时间示例,因此在现在我可以访问像 now.dot year 这样的变量,举个例子,它告诉我年份,now.dot month 和 now.dot day 则提供关于当前***的信息,这些信息基于获取的当前时间。
你可以想象我们构造一个布尔条件来检查今天是否实际上是新年,这个条件可能看起来像 now.dot month 等于一,now.dot day 等于一。我可以按返回键,看到这个条件的结果是 false,它并不为真。
这意味着月份和日期都为 true 或者为一,因此通过使用这种条件,我现在可以将其适应到我的 Django 视图中。
用来尝试渲染是否是新年。那么我该如何做到呢?我希望我的索引函数返回渲染一个模板,这个模板将被称为新年斜杠索引点 HTML,然后我想给这个模板提供什么上下文呢?
那么,对于这个模板选项卡,我希望访问哪些变量呢?我想有一个变量,叫做 New Year,为了访问它,我需要获取当前日期,因此我会在文件的顶部导入 datetime,并在我的索引函数中给自己一个变量,叫做 now,等于 datetime.datetime.now。
我传递给模板的这个新年变量将等于 now.dot month 等于一,now.dot day 等于一,所以如果在运行 datetime.datetime.now 获取当前日期和时间后。
将结果保存到这个名为 now 的变量中,如果月份和日期都等于一,那么当模板访问它时,这个新年变量的值将为 true,否则像今天和大多数日子那样,该变量的值将为 false。
我们需要做的就是实际创建这个,但新年斜杠索引点 HTML,并以某种有趣或有意义的方式使用这个新年变量,那么我们该如何做到呢?如果你还记得我们在 hello 应用中所做的,当我们想要一个模板时,在我们的应用中,这个新年。
应用程序将需要一个新的文件夹,称为 templates,在其中我会创建一个名为 new year 的新文件夹,在里面我将创建一个名为 index.html 的新文件,这里将是此新年应用程序的 index.html 文件,结构将非常相似,我将给它一个标题,问它。
新年在主体内部,这里是有趣的逻辑,有时我可能想要一个大的 h1 说是,其他时候我可能想要一个大的 h1 说不,我需要做的是有条件地决定何时说是和何时说不,在 Django 的模板语言中,再次强调你只会。
我们可以通过阅读 Jana 的文档来了解这一点,就像我们使用双大括号插入变量值一样,Django 模板中的逻辑语法是大括号 %,所以我们使用大括号 % 一些逻辑语句,然后 % 大括号来包含任何类型。
逻辑,在这种情况下是一个 if 语句,这非常像 Python,我会说如果新年,New Year 是我传入这个模板的变量名,如果是新年,我想显示一个 h1 说是。
一个大的 h1 只是说不,所以如果是新年就说是,否则说不,并且 Django 还要求我给出一个 N 如果。
语句,不像 Python 本身使用缩进来表示 if 语句的开始和结束,在 Django 中模板中的缩进是可选的,但为了区分 if 语句发生的时间和 if 语句结束的时间,我们需要在最底部有这个 end if 标签。
所以这里是一个条件,在我们的 Django 模板中,我们说如果某个变量为真,则在 HTML 中渲染这个内容,否则渲染其他内容,如果是新年就说是新年,否则说不是新年,所以我们现在可以试一下。
路由 / hello / wrong 如果我不去 / hello,而是去 / new year 例如,这个站点无法访问,好吧,为什么会发生这种情况。
27001 拒绝连接,所以发生的情况是我的 Web 服务器出于某种原因没有运行,结果发现为了创建新应用程序我停止了 Web 服务器,因此如果发生这种情况,请重新启动它,我们可以重新运行 python manage。
pi run server 来说我想现在重新启动这个 Web 应用程序,好吧,看起来现在我又有一个错误,出现了一种语法错误,语法错误是 new year / views a dot PI,所以让我回去查看视图停止我和。
看看我能否找出语法错误在哪里,好的,似乎render
是一个函数,函数参数需要用括号括起来,而我在这里有一个开始的括号,但忘记了结束的括号,所以我会继续添加它,现在这将完成render
函数。
现在我应该能够加载/new year
,事实上我看到的是,不,它不是新年,所以发生了什么呢,我们运行了一些Python逻辑,计算了当前的日期和时间,检查当前月份是否为一,当前日期是否为一,如果不是真的,那么我们在这里显示“没有”,如果我们看看。
这个页面的实际HTML,实际上构成了这个特定页面的HTML,我可以通过右键单击或控制单击,然后点击查看页面源代码来做到这一点,这将显示我这个页面的HTML,这是来自网络服务器的HTML,我的网页浏览器Chrome(在这种情况下)正在渲染。
你可以看到,它看起来与我们之前写的index.html
模板非常相似,但它只包含“没有”,不包含任何if逻辑,也不包含“是”,它只包含我们想要返回给用户的HTML内容。
你可能基于此想象,Django实际上是在获取index.html
模板,然后根据输入进行操作,我们基于是否是新年,那么在主体中我们应该做的就是显示一个h1标签,上面写着“没有”,而用户得到它时,他们并不会看到。
条件下他们看不到,还有另一条分支可以采取,他们只看到渲染模板的最终结果,无论是什么变量、逻辑和条件在这个渲染过程中,用户看到的只是“没有”,如果我们是。
在新年那天运行这个程序,它确实会说“是”,我们可以测试一下,只是看看如果那是这种情况会是什么样子,稍微作弊一点,而不是现在月份等于一和日期等于一,让我去替换这个条件为“新年等于true”,仅仅为了测试。
让我们尝试传入true
作为新年的值,看看会发生什么,现在当我运行这个。
现在页面上确实显示“是”,正如我们在新年时可能期待的那样,这可以是一个很好的方法,仅仅测试一下如果你用特定的值替换特定变量会发生什么,我们可以在上下文中替换,仅仅出于开发目的,我们想要这个变量的什么值。
现在我们已经渲染了 HTML,但我们还没有为这个网站添加任何样式,实际上 Christmas.com 的文本是居中的,使用的是无衬线字体,而不是每个字符边缘的小衬线,所以我可能想要添加一些。
还可以向这个文件添加自定义 CSS,我们可以像之前看到的那样以传统方式进行,但只是简单地包含一个 CSS 文件,这就是我们仍然要做的,但 Django 有一个特殊的构建系统来处理所谓的静态文件,这些文件不会改变这个 HTML。
页面并不是一个静态文件,因为它会根据是否是新年而变化。如果我在新年访问,它会显示“是”,而在非新年时则显示“否”,所以这是一个动态页面,而静态文件则不会变化,比如我们的 CSS,无论是否是新年,CSS 都不会变化。
而且因为这些文件是不会变化的,Django 称之为静态文件,这意味着 Django 可以稍微聪明一些。如果你开始考虑项目和规模,你可能会把静态文件存放在一个单独的地方,这样可以方便访问并缓存以加快速度。
稍后我们将讨论得更深入一些,随着课程的进行,我们将开始探讨可扩展性等主题,当你开始在互联网上构建更大的 Web 应用程序时,但简单来说,Django 包含了很多让我们轻松处理静态文件的功能,这些文件不会变化,比如 CSS 文件。
通常我们添加静态文件的方式是在新年文件夹内,除了有 templates/New Year/index.html 之外,我们还会创建一个名为 static 的新文件夹,其中包含我们希望在此应用程序中包含的所有静态文件。
在 static 内,我将创建一个名为 New Year 的新文件夹,里面有一个名为 styles.css 的新文件,因此在 styles.css 内,我可以写下我希望之前编写的所有 CSS,也许我想要所有的 h1 使用无衬线字体,字体大小为 90 像素,并且文本对齐为居中。
一些 CSS 属性和值与我们之前看到的相同,只是我想给 h1 标签这个特定的样式,现在在 index.html 中剩下的工作是在页面的顶部添加一个命令,告诉它加载静态文件。
特定的 HTML 页面,现在我将链接一个样式表,你会记得在网页的 head 部分,这种命令是我想链接某个特定 URL 作为样式表的方法,这就是我如何将 CSS 添加到页面,而我将在这里包含的链接是。
与其硬编码一个 URL,我可以遵循 Django 的最佳实践,干脆说它将是一个静态文件,而这个静态文件名为 New Year/styles.css,所以我并没有具体指定 URL,但我说明这是一个在 New Year 文件夹内的静态文件,名为 styles。
CSS 和 Django 将会弄清楚这个 URL 应该是什么,这通常比硬编码一个特定的 URL 要好,因为你可能想象在大型 web 应用程序中,静态文件的位置可能会改变,你可能会将静态文件移动到不同的域或不同的路径,因此为了能够。
处理这个静态关键字仅意味着 Django 会找到你的静态文件的位置,并会在这里的花括号中替换这个命令。
用大括号和百分号符号替换此特定文件的实际 URL。所以现在我已经说明我想将这个特定的静态文件 styles.css 链接到这个网页上,我可能首先需要做的就是让 Django 加载静态文件,重启服务器,所以你可能需要控制+C,然后再去。
继续运行 python manage.py run。
服务器将重新运行服务器,现在如果我回到我的网页浏览器中的 /new year 路径,我会看到样式看起来更接近我实际想要的样子,居中,采用无衬线字体,字体更大,这里仅显示“没有”。如果我们查看这个页面的底层 HTML,我们可以看到。
Django 实际上为我们填充了这些特定静态文件的静态 URL,并且默认情况下。
Django 只使用 slash static slash,然后是任何文件,所以每当我们处理静态文件(例如 CSS 文件或后面课程中将要讨论的 JavaScript 文件)时,这通常就是它们的去处。
最终将链接到这个特定页面。所以现在我们已经看到了使用 Django 可以制作的一些 web 应用程序的例子,我们看到了 Django 中的 hello 应用,它可以根据访问的 URL 参数化 URLs,可以对布赖恩说你好,或者对大卫说你好,或者对其他人说。
我们已经看到使用 Django 添加一些条件的能力,可以有条件地判断如果某个条件为真则渲染此页面,如果另一个条件为真则渲染另一个页面。现在让我们使用这些功能加上一些额外功能,开始构建一个更复杂的 web 应用程序。
或者是类似待办事项列表的应用,我希望有一个 web 应用程序,可以给我一个任务列表,让我可以将新任务添加到我的待办事项列表中,并让我查看我当前列表中的所有任务,我希望逐步构建这个应用。所以我该从哪里开始呢,这又将是一个新的应用。
在我的 lecture 3 项目中,我目前有两个应用程序,一个是 Hello 应用,另一个是待办事项应用。还有一个我将称之为任务的第三个应用,这将是我的任务管理应用,所有这些都在这个 lecture three 项目下。所以我们将继续运行 Python men in shop I。
启动任务应用程序,我想创建一个名为 tasks 的新应用程序。每次我创建一个新应用程序时,都需要遵循几个步骤。请记住,我需要进入 lecture three 中的 settings.py,除了安装 hello 一个新年,我还想安装任务应用程序。
然后我需要进入 lecture three 中的 URLs.py,告诉它除了 hello 一个新年,我还想包括任务的 URLs。这个 URLs 再次是我整个 Web 应用程序的目录,其中如果我访问 slash hello,那么我们去任务的 URLs。
如果我进入任务的 URL,然后访问任务应用程序的 URL,这会告诉我所有不同的 URL,但我现在有访问权限。和以前一样,URL 文件没有在我的应用程序内为我创建,所以我需要进入任务目录,并在任务内。
我将继续在目录中创建一个新文件,我将称之为 URLs.py,这个格式将与我们已经从 Django 看到的非常相似。导入路径从点导入视图,现在我定义我的 URL 模式,我希望有一个路径,就是空字符串,空路径。
加载名为 index 的索引函数。例如,现在让我们实际编写这个 index 函数,在 used-up 内。我想定义一个名为 index 的函数,它接受一个请求,我想做的是渲染一个页面,显示我所有任务的列表,因此在我们开始之前。
添加任务的想法,让我们看看能否让程序显示一个任务列表。例如,我将在顶部创建一个全局变量,我会称之为 tasks,整个应用程序都能访问这个变量。现在我会先添加三个任务,我们将使用 foo、bar 和。
一些无意义的名称,仅仅是用于测试的有用字符串。将这些作为我可能想要在应用程序内的示例任务,现在我想做的是渲染一个模板,我想渲染的模板叫做 tasks/slash index.html,然后我将提供。
为它提供一些上下文信息,index.html 需要什么信息?index.html 需要访问我所有的任务,而我的所有任务就在这个变量内,而这个变量碰巧也叫 tasks。我们将在 Django 中经常看到这一点作为一种范式。
看一下键和值,它们看起来有相同的名称。键的区别在于冒号后面的内容,这就是值。这个是一个Python变量,像这个Python变量tasks。在左边,这个键字符串tasks就是变量的名称。
当Django渲染时,HTML模板将有访问权限,所以Django可以访问这个左侧的变量名,右侧有这个值。如果你看到这个范式,这最终意味着什么,而我现在需要做的就是创建这个indexed的HTML文件,并使其使用这个tasks。
变量怎么做,我该如何去做呢?我可以返回到我的tasks目录,创建一个新的文件夹用于我的模板,所以我将创建一个templates目录,其中有一个tasks目录,因为我正在渲染的模板是tasks/index.html,在tasks内部我将创建一个名为index的新文件。
html,里面将包括一些HTML,我将只包括标准的开始。HTML的标题将是任务,现在,我们将有HTML页面的主体。我想要在一个无序列表中显示所有任务,针对应用程序。如果你记得从HTML创建一个无序列表,它看起来有点像这样。
ul开始无序列表,每个列表项是一个li标签,像item 1,然后是item 2,再然后是item 3,类似的内容给我一个包含三个元素的无序列表,但我现在不想这样做,因为我不想硬编码或精确指定所有任务是什么。
我真正想做的是遍历这个任务变量,循环遍历其中所有任务,并为每个任务创建一个列表项。就像之前那样,我们可以在大括号内和百分号中使用if条件,同样我们可以使用for来表达某些内容。
像tasks中的任务一样,我想显示一个列表项,然后使用这些双大括号,我在说在这些列表项之间插入任务!
无论任务变量的值是什么,然后我将结束for循环。因此我在这里做的事情是,与之前看到的在模板中添加条件不同,我现在有能力在HTML模板中使用Django添加循环,表示对于tasks中的任务,我想循环遍历。
所有的元素在这个名为tasks的序列中,对于每一个单独的任务,我想在HTML中显示一个列表项,包含任务的值是什么,并且for像if一样在S语句中,for将结束for循环,因此这个语法现在。
与其每次给我完全相同数量的列表项,内容也完全相同,不如让它动态化,无论任务的值是什么,我们现在在这个页面上会看到每一个都是一个列表项,我们可以立即测试这一点。如果我通过运行Python manage.py runserver
来运行这个应用程序,我将前往。
这个URL,默认的URL没有页面,但如果我去斜杠tasks,那么我看到的是一个无序列表,其中有foo。
bar和Baz每一个都是一个列表项,如果我查看页面源代码,看看实际的HTML是什么,以下是我看到的。我只在我的模板中写了一个Li标签,但因为我把它放在一个遍历每个任务的for循环中,所以最终返回给用户的HTML是一个。
列表项对应于原本在该列表中的每个元素。因此,我无法循环一个列表来生成任务列表,当然也没有办法修改这些任务。
哈佛CS50-WEB | 基于Python/JavaScript的Web编程(2020·完整版) - P11:L3-Django网络编程3(表单与session) - ShowMeAI - BV1gL411x7NY
这恰好是通过一个循环来渲染的,我可能希望有另一个页面,这样可以有效地让我添加新的任务,一个表单让我输入新任务并按添加以便添加新任务,所以我们在内部进行这项工作。
add函数将要做的是渲染tasks/add.html,以便知道何时运行,我需要给这个视图一个URL,所以我进入URL间谍并添加一个新路径,当我访问该路径时,我想去视图add函数,我将把这个函数称为add,例如,现在当我去。
斜杠任务/添加,将调用视图中的add函数,在这里将运行这个函数,渲染add.html,所以我们现在来写一个add.html,我们将进入templates,创建一个名为add.html的新文件,你知道add.html的语法。
与index.html的语法非常相似,就HTML内容而言,所以我将复制index.html的整个页面,粘贴到add.html中,唯一不同的是页面的主体,我们而不是无序列表。
列出所有任务的列表,而不是一个表单,这个表单有一个类型为文本的输入框,也许这个输入字段的名称叫做tasks,这样我就可以在后面访问这个输入数据,还有一个类型为提交的输入,例如,我可能会在顶部给它一个大的标题,比如添加。
例如,任务,所以现在我有一个新的路由来添加任务,我的默认斜杠任务路由只是显示所有任务的项目符号列表,如果我去斜杠任务斜杠添加,这里我可以输入任务,按提交,当前什么也不做,但我最终希望它能。
实际上添加一个新任务,当然,我刚刚做的事情应该让我们觉得不是最佳设计,特别是我做出的决定是因为这个HTML页面的总体结构是如此相似,它有HTML标签,有head,页面标题是任务,最终我只是复制了index的所有内容。
html并将其粘贴到这个新的HTML页面add.html中,每当你发现自己在复制粘贴时,这应该是另一个你开始思考的地方,可能有更好的方法来做到这一点,单纯用HTML时确实没有,如果我们想要多个不同的HTML页面显示。
我们需要在所有不同的页面上使用相同的HTML,但在Django的世界中,我们可以使用模板继承。现在我将定义一个名为layout的HTML文件,其他文件add.html和index.html将从这个文件继承。
它们将从我的布局中继承页面的所有结构,在这两个页面中都是相同的,而我需要写的只是页面之间的不同之处,正如我之前提到的,add.html
和index.html
之间唯一不同的就是页面主体的内容。
所以我现在能做什么呢?我将在我的template/tasks
目录中创建一个新文件,叫做layout.html
,布局将包含这两个页面共有的基本布局。我有一个标题,标题是tasks,我有页面的主体,也许这两个页面之间还有更多的共同点。
这些页面我也可以添加,但在这里,在body标签之间,这就是页面的主体,将在不同页面之间变化,所以在Django的新布局中,我将再次使用大括号和%来表示一个块,然后我会给这个块一个。
我会称它为body,因为它是页面的主体,但我可以给它任何名称,然后在底部添加一个end block,所以我在这个布局文件中所说的是,这个布局文件在页面的结构中有一个body,而在body内部有这个叫做body的块,我在这里说的是。
这个块可能会根据我们使用的文件而变化,比如add.html
或index.html
,结构的其余部分不会改变,但这个块的内容可能会改变,这个块称为body。
我可以去掉除了页面关键部分以外的所有内容,关于index.html
,唯一不同的就是这个无序列表,而我现在在index.html
顶部包含的是,我会说这个HTML页面扩展自tasks/layout.html
。
现在这个模板继承的概念,我从layout.html
模板中继承,基本上是说使用这个布局模板,除了在body块内部我想包含所有这些内容,也许我还会给它一个h1,标题就是tasks,所以现在index.html
所说的是。
与其包含所有那些HTML,我只需要说这个HTML文件是基于layout.html
文件,但不同之处在于在页面的主体中,它将是这个特定内容,而对于add.html
我可以做完全相同的事情,我只需添加这一行扩展自tasks
。
将layout.html
放到add.html
的顶部,然后我可以去掉所有这些模板代码。
只包含在页面主体内部不同的部分,所以看起来我们为这两个页面做了相当多的工作,但如果你想象更复杂的网站,它可能有几十个或上百个不同的页面,能够将HTML提取出页面间的共通之处。
这对于良好的设计肯定非常有帮助,以确保我们没有重复自己,如果我们需要更改结构,而不是在数十个或数百个不同的地方进行更改,我们只需在布局文件中的一个地方进行更改,其结果是,它将在每个继承该页面的页面中进行更改。
我们可以通过回到/tasks/flush_atom
来测试这一点,外观良好,回到任务页面也看起来不错,这两个页面现在都继承了基本布局。现在有点烦人的是,每次我想在这个页面和另一个页面之间切换时。
添加页面,我必须访问URL,并知道我需要去/tasks/ad
,以便来回切换。
在它们之间,我可能想添加一个链接,从一个页面跳转到另一个页面,反之亦然,如果我进入index.html,你可能想象我可以在这里添加一个链接,a href
来创建一个链接,让我们去/tasks/ad
,也许添加一个新任务,链接的名称除外。
问题在于,或者说原因在于,这种设计不一定好,因为Django的设计使得改变页面结构(在URL之间的关系)非常容易,而我在这里硬编码了,当你点击这个链接时,我们就去/tasks/ad
。
每当我想更改那个URL,也许不是/tasks/ad
,而是/new
,那么我需要在两个地方进行更改。我需要返回到urls.py文件,更改实际的URL,说明不再是ad,而应该叫new,但然后我需要找到每个相关的地方。
如果我在其他地方使用那个URL,我也需要在那里进行更改。为了解决这个问题,Django有一个额外的功能,可以让Django自动判断应该使用什么URL,我们通过使用给每个路由的名称来实现这一点,所以这就是名称变得重要的地方。
我可以在这里直接说链接到特定的URL,链接到名为ad的URL,所以我只是说链接到名为ad的URL,Django是如何根据我的urls.py内容来判断的。
在我的urls.py文件中,我定义了多个不同的路径,并给每个路径起了个名字,这个叫index,那个叫ad,所以当我这样做时,我能够说,如果你链接到一个叫ad的URL,Django会找到一个名称为ad的URL,并直接链接到该路由。
将路由更改为其他内容,Django会自动判断新的URL应该是什么,我不必担心,Django会为我解决问题。现在如果我回到任务网站,我可以点击添加新任务按钮,这将带我到。
添加任务,也许现在我想添加一个可以返回的链接,以便我可以去 add.html,可能在底部添加一个链接,a href 等于,那个我想链接到的 URL 是什么。
我默认的页面仅名为 index,所以我会包含单词 index,我想链接到 URL index,然后可能将查看任务作为链接的名称,所以现在如果我只去默认的任务页面,我看到一个链接可以去创建一个新任务,现在我看到一个链接,将带我回到。
查看任务的能力,当我点击那个链接时,我看到了没有,这可能不是我想要的,我想回到我的任务应用的索引页面,但似乎当我点击查看任务时,我被带到了没有,并发生了什么。
如果你查看 URL,URL 是斜杠新年,不知怎么的,我在任务应用上,点击了一个链接,我又回到了新年应用,怎么会这样?原来这是一个命名空间冲突的例子,我有两个名称相同的东西,而在这种情况下,发生的事情是。
我有一个用于我的任务的 URL 间谍文件,应用程序中有一个名为 add 的路由和一个名为 index 的路由,但恰巧在新年文件夹内,如果我进入新年并查看新年的 URL 顶部,我的文件中的新年 URL DUP pi 文件也有一个路径,名称为 index,所以我所说的。
创建一个链接,我希望那个链接指向一个名为 index 的 URL,结果发现有多个名称为 index 的东西,因此 Jane Doe 不知道选择哪个,它只是选择了新年那个。你可以想象,在应用之间链接是。
有些事情你可能会合理地想要,想要 - 比如从亚马逊的购物页面能够点击一个链接,带你到亚马逊视频,或者从谷歌搜索能够点击一个按钮,带你到谷歌地图,但在这种情况下,这不是我想要的,有命名空间冲突。
有两个东西有相同的名称,我现在想修复它,修复的简单方法是在我的任务应用程序的 URL 中,让我给每个 URL 一个名为 tasks 的应用名称,这有助于唯一识别所有的 URL,因为现在在 .html 中,而不是仅仅。
关联到一个名为 index 的 URL,我在这里插入一张图片。
现在要改为链接到任务:索引,意味着从任务应用获取索引 URL,同样在 index.html 内部链接到任务:添加,以便从这个特定的应用名称获取特定的路由,所以现在如果我回到网站,回到我的任务页面,现在链接按预期工作。
我可以进入新任务,并且我可以返回我的所有任务列表,所以现在这部分工作正常。我知道有这两页,一页显示我的任务列表,一页显示我添加新任务的能力,但添加新任务的表单现在实际上并没有任何作用。我输入一个任务,比如 foo,如果我。
我想添加一个叫 foo 的任务,我按提交,但似乎什么也没有发生。
这个网站上没有什么有意义的变化,所以我希望这个表单能做点什么。我们已经看到可以为表单添加一个动作,以便将该表单提交到某个地方,这正是我在添加新任务时想要做的。我将为这个表单添加一个动作,当我提交表单时。
我希望将其提交到哪个 URL 呢?好吧,我会将其提交回任务的 URL:
我会将其发送回那个添加的 URL,当我提交表单时,我会给这个表单一个特定的请求方法,它的方法将是 POST。我们已经看到 GET 请求方法,每当你在 URL 中输入或点击链接以转到另一个页面时,如果与该请求隐式关联的请求方法被称为。
在任何时候你提交可能会改变应用程序内部某些状态的数据时,比如改变存储在应用程序中的任务列表状态,我们通常会使用一种不同的请求方法,称为 POST。
POST 通常用于提交表单数据,它不包含像 GET 请求那样在 URL 中的参数,正如我们在 Google 的例子中看到的。但这个 POST 的功能将让我们有能力通过不同的请求方法将数据发送到我的添加路由。所以现在我们来尝试一下。
我将去任务/添加,我看到可以添加任务,也许我会添加一个任务,比如检查邮件,然后按提交。好吧,我得到了一个错误。被禁止,这个 403 在括号中意味着,这是响应代码的返回。这是 Django 为我生成的错误,所以这个 403 如我们之前所见。
被禁止了,我出于某种原因没有权限去做这个,为什么我没有权限提交这个表单呢?
显示 CSRF 验证失败,所以 CSRF 代表跨站请求伪造。这意味着它是一种固有于某些表单的安全漏洞,如果它们没有以安全的方式设计,这意味着某人可能会使用自己独立的表单伪造对特定网站的请求。
例如在网站上,你可能会想象有人在另一个网站上可能会欺骗用户提交一个表单,将数据提交到我们的添加任务功能,这将新的任务添加到他们的任务列表中,也许这对任务来说不是大问题,但在更安全的上下文中可能会更敏感。
例如,银行可能在其网站上有一个用于用户之间转账的表单,如果他们易受这种攻击的影响,跨站请求伪造可能会使其他网站上的某人欺骗用户提交表单。
数据会发送到银行的网站,以请求从一个用户转账到另一个用户,因此我们希望设计出不易受到这种特定安全漏洞影响的表单,防止请求被其他网站伪造,那么我们该如何进行呢?
为了妥善处理这些攻击,可以采用一种策略,即在我们的表单中添加一个隐藏的跨站请求伪造令牌或 CSRF 令牌,这将是为每个会话生成的唯一令牌,因此每次不同用户访问时。
在特定的表单中,他们会看到不同的 CSRF 令牌,当用户提交表单时,他们将与表单一起提交该令牌,我们的 Web 应用程序将检查该令牌是否确实有效,如果有效,则允许表单提交。
这意味着对手无法伪造请求到我们的网站,因为对手并不知道生成的令牌是什么,因此他们会失败 CSRF 验证,而 Django 默认启用了 CSRF 验证。
Django 中间件指的是 Django 介入请求和响应处理的能力。如果我查看设置文件,如果你对这个特定的 Web 应用程序感兴趣,在设置文件中向下滚动,可以看到有一整套内容。
在 Django 应用程序中默认安装了一堆中间件,以确保我们有不同的功能挂钩到请求响应处理中,其中之一是 CSRF 视图中间件,这个 Django 的特性确保我们的请求是安全的。
每当我们通过 POST 提交数据时,这可能会更改应用程序的状态,因此我们需要进行 CSRF 验证,我们需要在表单中添加某种令牌,以确保 Django 能够验证此表单的有效性。
他们知道表单确实来自于 Web 应用程序本身,并且将这个令牌添加到我们的 HTML 页面中非常简单,Django 自带支持。
在花括号和 % 内,我们可以说,我想在这个页面中添加 CSRF 令牌,例如直接填充 CSRF 令牌。如果现在返回页面并刷新,我就可以看到广告任务,但如果我们好奇,可以查看页面源代码。
在这个页面内,现在有了表单,这与我们之前看到的表单相同,但你会注意到 Django 插入了这个额外的输入字段,这个输入字段的类型为隐藏,意味着我们通常看不到它,其名称为 CSRF 中间件令牌,这里是它的值,一串长字符。
Django 为我生成了这样的内容,当我提交这个表单时,它会检查以确保这个令牌是有效的,如果没有找到这个令牌,它将不会接受我的表单提交。如果其他人访问这个网站,他们也会看到一个不同的令牌。
这有助于确保没有人可以伪造这些请求。因此现在,如果我输入一个想要添加的任务,比如检查电子邮件并按下提交,现在论坛当然可以提交,而没有错误,但如果我返回我的任务列表,它仍然是空的,但至少我现在能够提交这个表单。
值得注意的是,在一个 HTML 文件中,我们几乎是从零开始创建这个表单。我创建了一个类型为文本的输入字段,其名称为任务,但创建表单在网页编程中是非常常见的,通常涉及许多不同的字段。
随着时间的推移,Django 添加了多种方式来简化创建表单和验证数据的过程,以便让我们在处理和交互时生活得更轻松。因此现在我们将探索一种替代的方法来完成相同的事情。
我们在这里所做的就是使用原始 HTML 创建一个表单,正如我们之前所见,但 Django 也有为我们创建表单的能力。因此为了做到这一点,我将进入 views.py 文件,并在顶部添加 from Django import forms
,然后我要创建一个新的类来表示这个表单。
我将创建一个 Python 类,称之为 new_task_form,因为我们将用它来创建一个新任务。它将从 forms.Form
继承,而现在在这个类中,我需要定义我希望这个表单具有的所有字段,即我希望用户提供的所有输入。
因此,我希望他们提供一个任务名称,这将是一个字符字段,或者说是一个 char 字段,意味着我希望用户输入字符。我可以给这个字段一个标签,比如称之为新任务。现在,当我渲染 add.html
时,我可以添加一些上下文,并说让这个模板访问一个名为 form
的变量,这将是一个新任务表单。
我要创建一个新任务表单,将其传入这个 add.html
模板。现在在 add.html
中,不用手动编写类型为文本的输入字段,名称为任务,我可以只使用双大括号,并说在这里插入表单,这将自动处理,Django 会为我做好。
生成必要的HTML,使表单工作,所以如果我现在刷新这个页面,我看到这里是表单,Django。
为我创建了一个输入字段,给它一个“新任务”的标签,以便我知道这是新任务的输入位置,但现在不需要每次想更改表单数据时都去编辑HTML,我只需更改这个新任务表单,如果我想的话。
最终对我的应用程序进行升级,除了指定一个文本框让我输入新任务之外,也许我还想能够指定一个数字,表示这个任务应该具有的优先级。我可以额外给这个表单访问一个优先级变量,它是一个整数。
标签为优先级的字段,我可以。
甚至在这方面添加约束,以确保这是有效的数据。我可以给它一个最小值1和一个最大值10,例如,以添加所有这些。而现在不触碰我网页中的任何内容,我只改变了表单本身。
字段,我看到一个机会让我输入一个新任务,我看到一个机会让我指定一些优先级,当然你可以添加CSS来让这个看起来更好一点,但现在Django将自动进行客户端验证,如果我输入一个新任务,比如检查邮件,但。
我不指定优先级并提交,它会告诉我填写这个,如果我输入。
一个在无效范围内的数字,我只想要从0到10的两个数字。它填写了这个,这被称为客户端验证。服务器并没有收到任何这些数据,只是网页已经被编码以了解有效值,并将限制我。
确保我输入的内容符合这些值,但一般来说,当我们进行表单验证,确保表单是有效数据时,我们希望确保包含不仅是客户端有效,还有服务器端验证,我们还想在服务器上检查,以确保。
输入的值也是有效的,因为有很多原因我们可能想要这样做,一个原因是很容易禁用那种客户端验证,或者只是提交请求而不做任何客户端验证,也许如果用户查看的是旧版本的。
页面上,服务器进行的验证比这个客户端验证更新,Django的表单将使我们很容易同时处理这两种验证,客户端验证和服务器端验证。那么这怎么运作的呢?我们是如何做到的?
在 add
函数内部,这个 add
函数现在会根据请求方法以两种不同的方式被调用。如果我尝试通过点击添加新任务的链接或访问 /add
的 URL 来获取添加页面,我只想呈现一个新的空白表单,但如果我向其 post
数据。
这个页面通过使用 post
请求方法而不是 get
,这意味着我正在提交表单,我现在想要提交一个新的任务以添加到我的任务列表中。因此,我想要添加一个检查条件,我将添加一个条件,说明如果 request.method
等于 post
,那么这里是我想要做的。
这是请求结果的处理,在 Django 表单中实现这个的方法是创建一个新的变量叫 form
,这将是一个新的任务表单。如果我像之前那样仅使用 new task form
及两个括号,那么它会创建一个空白表单,但你也可以用一些数据填充这个表单。
我用请求的 post
方法填充它,所要做的是请求停止 post
。包含用户在提交表单时提交的所有数据。因此,我现在所做的是,我通过提取所有数据来创建一个表单变量,并将其填入这个新的任务表单中,这个表单现在将包含所有内容。
用户提交的数据,您可以想象手动检查这一点,但我可以直接调用 if form.is_valid
,在这个 if
语句内使用一些逻辑,利用这个表单的清理数据。因此,在名为 form.cleaned_data
的变量中,这将让我访问到所有数据。
已提交的,因此如果我想获取他们提交的任务,因为我在一个新的任务表单中有一个名为 tasks
的变量,我只需访问 form.cleaned_data
,然后 task
,我会将其保存在一个名为 tasks
的变量中,现在我想做的事情是将这个任务添加到我的。
任务列表通过 tasks.append
方法添加这个新任务,所以如果表单有效,我们就这样做。如果表单有效,我们从表单中获取数据,获取任务,将其保存在一个名为 tasks
的变量中,并添加到我不断增长的列表中。但是如果表单无效,那又怎么办呢?
那我们应该怎么做呢?那么我应该再次返回 add.html
文件,但不是将表单提供回去,而是将新的表单提供给他们。我将把现有的表单数据返回给他们,以便我们可以显示可能出现的任何错误的信息。那么这是什么意思呢?
现在看看,让我们举个例子,然后我再回到代码中,你可以更详细地看到它是如何工作的。这里是任务/原子,记住如果我输入一个任务,比如检查电子邮件,优先级不在有效范围内,比如 11,并按下提交,它会说值必须小于或等于 10。但现在我们来想象一个情况。
客户端和服务器正在验证不同的内容,我可能已经决定,优先级从 1 到 10,现在只能从 1 到 5,这就是优先级的有效范围,但这个客户端仍然是页面的旧版本,所以它认为。
优先级为 8 仍然有效,它将通过客户端验证,但现在我按下提交,服务器将处理它,因为它无效,它会返回表单并给我一个错误,确保这是有效的。
值小于或等于 5,这就是为什么我们通常希望客户端和服务器端验证,以确保我们最终得到的数据将是准确的,并且将是干净的,符合我们在第一次创建表单时设定的任何规范。
因此,目前我们并不太担心优先级,因为我们真的只关心任务是什么,但请知道如果你想要一个包含多个字段的表单,你可以在这个表单输入中添加额外的字段。因此,我们为我们的应用程序逻辑添加了一些内容。
路由检查表单是否有效,如果我们看看 add 函数实际上在做什么,我们在检查请求方法是否为 post,意味着如果用户提交了一些表单数据,我们就会弄清楚他们提交的所有数据并将其保存在这个表单变量中,我们检查是否。
表单是否有效,他们是否实际提供了任务,或者他们可能提供了所有必要的数据,格式是否正确。如果是这样,我们就会获取任务并将其添加到任务列表中,否则如果表单无效,我们将继续并将相同的添加 HTML 文件呈现给他们,但我们传入他们的表单。
提交它,以便他们可以看到他们所犯的所有错误,他们可以对自己的表单提交进行修改,如果他们愿意的话。否则,也就是说,如果请求方法是。
如果用户只是尝试获取页面而不是提交数据,那么我们只会向他们呈现一个空表单,这种范式在处理请求和响应时其实是相当常见的,通常表单页面希望你首先。
能够通过 get 方法获取该表单,只是获取页面以显示内容,但这些路由通常也会支持 post 方法,你可以将数据发布到这些路由,以便说我现在想提交数据到特定路由以获得某种结果。
除了任务列表之外,转账,例如在银行中从一个账户到另一个账户,或者完全不同的事情,但看看如果我现在尝试在任务内部添加一个有效的新任务,比如检查邮件,会发生什么。
例如,我按下提交,好的。
并没有发生什么,因为我只是收到了这个新的任务表单,但是如果。
我回去查看任务,现在我看到检查邮件已被添加到我的任务列表中,原来的任务列表只包含fubar Baz,而现在我们添加了检查邮件,但这并不是我预期的行为。也许我希望在我提交任务表单后添加新任务。
喜欢被重定向回这个页面,对我们来说很简单,以便能够将用户从一个页面重定向到另一个页面。为此,在我们将新任务添加到我的任务列表后,我将返回一个HTTP响应重定向,并将用户重定向到一个特定的路由。我可以将他们重定向到类似于斜杠任务的地方。
例子,但我们通常尽量不在应用程序中硬编码URLs,因此更好的设计是让我给你路由的名称,并进行逆向工程得到路由的实际内容。因此,为此我们可以使用Jango内置的reverse函数,来获取tasks:
索引以找出任务应用的索引URL是什么,并将该URL作为我们最终重定向的URL,以便返回这个HTTP响应重定向。为此,我们需要导入这两个。因此,从顶部我会说从Jango HTTP导入HTTP响应重定向和。
Jango URLs 过去在一个导入反转,所以现在我已经导入了这两个,效果是,在我提交一个新任务并将其添加到我的任务列表后,我将被重定向回我的任务应用的索引页面,并且为了确保万无一失,我会先让我们从一个空的开始。
列表中去掉我们最初看到的foo bar Baz。因此,任务最开始是一个空列表,现在刷新页面,我看到这里没有任务。默认情况下,但如果我添加一个新任务,我输入类似检查邮件的内容,按下提交,现在我看到它被添加到任务列表中,我被重定向回。
在任务页面,我可以添加一个新任务,比如说做洗衣,按下提交。这将被添加到我的任务列表中,因此通过维护一个名为tasks的全局变量并在每次提交表单时更新它,我能够动态地增加这个任务列表,并展示所有内容。
这些任务在我的HTML页面中,但是应用程序仍然有一个大问题,那就是我将这些任务存储在一个全局变量中。这个变量是我和haier应用程序可以访问的,这意味着任何访问我网站的人。
将能够看到完全相同的任务列表,我们可以通过想象其他人访问来模拟。
我可以在Google Chrome中通过打开隐身窗口来模拟这个URL。
模拟一个不同的会话,一个与页面交互的不同人,访问相同的URL,在隐身窗口中访问相同的URL时,他们看到的就是完全相同的列表。
任务的显示使我和另一个人看到相同的列表,因为整个应用程序中只有一个任务变量,这个变量在所有进入该特定应用程序的请求中共享,这可能不是我在处理任务列表时想要的。
我想要的任务可能是按用户划分的,这样如果不同的用户访问页面,他们也会有自己的任务列表。因此,为了实现这一点,我们将在Django中引入会话的概念,或者更普遍地说,在网页上使用会话。
在后续访问中,它记住了你是谁,并知道你是谁。但更重要的是,它能够存储有关你特定会话的数据,能够存储你的用户ID或关于你的信息,或者在这个例子中,它能够存储你所有的任务。因此,为了利用这一点。
会话中,我们不再使用名为任务的全局变量,而是将任务存储在用户的会话中。因此,在索引路由中,我会包含这样一行,我会检查任务是否不在请求的会话中。
你可以将会话视为一个大字典,代表我们在会话中关于用户的所有数据。如果任务不在那个会话中,那我就来添加请求停止会话或会话任务,并将其设置为空列表。因此,我在这里做的是查看。
在会话中,我正在查看会话,看看该会话中是否已经有任务列表。如果没有,如果会话中还没有任务列表,那我想创建它。我想将请求会话的任务等于空列表,如果用户没有。
已经有一个任务列表,就给他们一个空的任务列表。
现在在任务中,不再渲染不再存在的变量任务,而是渲染请求会话的任务,以将任务列表传递给这个特定模板。现在在index.html中,我们仍然在循环访问任务列表,现在如果我返回任务。
我没有看到这样的表Jango_会话,所以这有点奇怪。这里发生了什么,没有这样的表Jango_会话?事实证明,正如我们将来看到的,Jango倾向于将数据存储在表中,而我们还没有了解这究竟意味着什么,或者如何进行操作。
如何与存储在表中的数据进行交互,但 Django 默认在一个表中存储关于会话的数据。现在你可以更改 Django 存储会话数据的位置,但最终 Django 保留关于你是谁和你的任务是什么的数据,这些数据需要被。
存储在某处,默认情况下 Django 想要将其存储在一个表中,而现在这个表还不存在,因此我们需要创建它,而给 Django 访问该表的方法就是在终端中运行此命令。
Python 的 pi migrate
将学习更多关于迁移的意义以及将数据迁移到数据库中的含义,但目前要知道的是,Python 管理的 pi migrate
允许我们在 Django 的数据库中创建所有默认表格。下次我们将看看。
实际上,我们自己创建数据库,添加存储自定义数据的表格,但 Django 有一些初始默认表格需要创建,运行 Python 管理的 pi migrate
允许这些表格被创建。现在它们存在,我首先。
需要运行 Python 的服务器。
启动服务器后,我加载页面,现在这是我的任务列表,这里没有任何内容,因此当我循环遍历所有列表项时,它有点空,这可能不是最佳用户体验或用户设计,所以我可能想要添加一个额外的条件,结果在 index.html
中每当我有一个 for 循环。
在 Django 模板中,我也可以添加一个空条件,如果我运行 for 循环但根本不运行,因为序列是空的,那么我就说像“没有任务”这样的内容,这只是 Django 语言的一个附加特性,使我们能够更轻松地处理。
在迭代一个列表时,遇到该列表中没有任何内容的情况。所以现在我刷新页面,正如我预期的那样没有任务。
看,现在这个索引路由似乎工作正常,接下来需要改变的是什么。
添加新任务时,而不是将任务附加到我的任务列表中,让我直接说请求会话中的任务,因为那是我的任务列表。让我将这个新任务添加到请求的会话任务中,并将其添加到列表末尾,只包含这个新任务。
我从表单获得的,所以当表单提交时,我们检查表单是否有效,获取用户提交的任务,并将其附加到已存储在会话中的任务列表中,然后再将用户重定向回索引页面。让我们试试,回到之前的几步。
到任务我看到我最初没有任务,现在我可以继续添加新的任务类型,比如检查电子邮件,点击提交,现在已经添加了添加新任务,比如洗衣服。
提交,现在这两个任务都在那里,但现在重要的是,如果你想象一下,在隐身窗口或者在另一台计算机上访问相同的
web 网站回到此页面时,他们看到的是完全不同的任务列表,因为他们有不同的会话,这些会话是由 cookies 决定的,这些小手 *** 帮助浏览器能够向 Django 的 Web 服务器提供一些信息,告诉它这里是我是谁,这样 Django 就知道了。
要显示给你的数据是什么,在这种情况下,我的原始情况,Django 知道我是谁,知道要显示给我这些任务,在这种情况下是不同的用户在隐身窗口中,在这种情况下他们看到的是一个列表。
它根本没有任务,现在我们真的只是刮了一下表面,看看 Django 有什么可以提供的,但是我们现在看到它具有的能力是,能够创建动态的 Web 应用程序,而不仅仅是每次使用相同的 HTML 和 CSS 显示。
能够自动生成程序化的自定义 HTML 和 CSS,根据 URL 中提供的名称或者根据当前日期进行条件性显示,如果日期是一种而不是另一种,以及在会话中存储数据的能力。
基础是能够存储关于用户待办事项清单的信息,例如在后续访问中,他们可以看到他们需要做的事情清单,对于这些可能的用户,每个用户都有一个不同的列表,这里真的只是提供。当 Django 在存储数据方面变得非常强大时,这是它的一部分。
数据库并操作这些数据以及以各种不同的方式与这些数据进行交互,这最终是像这样的 Web 框架的许多强大之处,我们将在下次探讨更多内容,但现在这只是看一看 Django 以及我们如何使用它来能够。
构建这些类型的 Web 应用程序,这是使用 Python 进行 Web 编程和。
哈佛 CS50-WEB | 基于 Python / JavaScript 的 Web 编程 (2020·完整版) - P12:L4- 数据库、SQL 与集成 1 (数据表与 SQL) - ShowMeAI - BV1gL411x7NY
[音乐]。
好的,欢迎大家回到 Python 和 JavaScript 的 Web 编程课程中。所以下一次我们看了一个用 Python 编写的新 Web 框架,叫做 Django,Django 使我们能够构建动态的 Web 应用程序,这些应用程序现在不仅能够展示。
每次向用户提供相同的 HTML 和 CSS,但要动态生成 HTML,以便用户能够与页面进行更动态的交互,例如在某个条件为真时仅渲染页面上的内容,我们正在循环遍历一个值的列表并显示一个 HTML。
对于每一个可能的值,Django 的强大之处在于,Web 应用程序会变得更加有趣,尤其是当我们开始深入数据的世界,尝试让 Web 应用程序将数据存储在数据库中。为此,我们将介绍一个。
今天我们特别要介绍几个主题,我们将引入 SQL 模型和迁移的概念,SQL 是一种数据库语言,我们可以用来与数据库进行交互,Django 将允许我们在 SQL 之上有一个抽象层,以便通过不直接编写 SQL 来进行交互。
通过与我们称之为模型的 Python 类和对象进行交互,迁移将是一种技术,使我们能够根据对基础模型所做的更改更新我们的数据库。因此,在深入更具体的 Django 内容之前。
让我们先讨论一下 SQL,更广泛地说,就是数据以及我们希望存储的数据类型。实际上,我们可以尝试通过多种方式在计算机系统中存储数据,但通常在数据库中,尤其是一种类型的。
被称为关系数据库的数据库,我们将在一个表中存储数据,每个表将有行和列。因此,今天我们将设想开始构建一个数据库和一个 Web 应用程序,例如航空公司可能使用的,用于跟踪各种。
不同航班的航空公司,并跟踪哪些乘客在这些航班上。因此,我们这里有一个示例表格,展示了你可能如何表示一系列航班相关的数据,这里我有三列,用于跟踪我可能希望记录的三条信息。
任何给定航班的特定航班,我可能想知道该航班的起点,哪个城市出发,目的地是哪个城市,以及持续时间,即从起点到目的地的时间,以分钟计的值。因此,这些列。
表示我可能想要跟踪的一个字段,每一行则代表一个个别航班。例如,这里有一行代表从纽约到伦敦的一次航班,这个航班恰好需要415分钟。因此,SQL将允许我们。
它将是一个数据库语言,旨在与关系数据库管理系统进行交互,这些数据库将数据组织到表中,每个表都有行和列,事实证明,有许多不同的数据库管理系统实现了部分功能。
SQL标准,这种语言的一些更流行的类型包括MySQL、PostgreSQL和SQLite,但还有其他的,每种都有不同的特性和适用场景,其中某些可能更适合MySQL和PostgreSQL,后者也称为Postgres。
更加复杂的数据库管理系统,换句话说,它们通常运行在其他地方的服务器上,许多大型公司会使用这样的服务器来存储数据并以某种方式在单独的数据库中表示它们的数据。这样做的目的是,您可能希望您的网络应用程序在一台服务器上运行,而希望您的数据库作为一个完全独立的进程运行。
使得它们可以独立运行,您可以独立调试和测试它们,如果一个崩溃,另一个不会崩溃。SQLite是一个更简单、更轻量的数据库实现。
SQL标准和SQLite将会做的是,不是一个完整的服务器,它将会监听请求并做出响应,而是SQLite将所有数据存储为一个单一的文件,因此我们将把SQL数据存储在一个SQLite文件中,这样会使得。
对我们来说稍微容易一点,因为我们刚开始写查询,向我们的数据库添加内容,并从数据库中检索内容。但请知道,许多相同类型的SQL语法在使用Django的默认SQLite时也会出现,这些语法同样适用于其他语言。
以及其他数据库管理系统。所以,当我们开始在SQL数据库中存储数据时,最终每个数据片段都有一个类型,就像在Python中,我们对各种不同类型的数据有类型,我们有整数、字符串、列表和元组等等。
SQL有类型,代表您可能想要存储在数据库中的不同类别的信息,每个数据库管理系统都有自己不同的类型集。例如,SQLite支持相对较短的基本类型列表,它支持文本类型。
像文本字符串,例如某个城市名称,可能是我们支持的文本,我们支持整数,只是0,1,2,3,4加上负数负1,负2,负3等,我们支持实数,实数不一定只是整数。
它们可以在小数点后有一些东西,比如2.8或其他可能包含小数的数字,然后数字是另一类数据,更一般地说是某种数字,像布尔值或日期。
例如,可能使用这种数字类型存储,它不是真正的整数或实数,虽然你可以将其表示为这样的其他数字类型数据,最后,blob表示二进制大对象,是你可能想要的任何类型的其他二进制数据,只是零和一。
将存储在数据库内部,因此,例如,如果你要存储文件,可能是音频文件或图像或其他类型的文件,你希望在数据库中跟踪这些文件,你可以在一个sequel
数据库中存储纯二进制数据,所以这些现在是sequel Lite
支持的基本类型。
但其他数据库管理系统,比如MySQL,支持更长的其他类型列表,除了支持任意长度文本的文本类型外,MySQL还有一种叫做char的类型,它接受一个大小参数,用于表示我想要的字符大小数据。
将存储在数据库中,你可能想象这在某些情况下会很有利,因为你知道你的数据将在某个字符数之内,因此,如果你在美国存储邮政编码,例如,美国的邮政编码可能只有5个字符。
因此,你可以为存储每个邮政编码分配5个字符的空间,为了效率的考虑,如果你知道最大长度,并且可以在数据库中固定该长度,那将是一个高效的选择,varchar
的大小是相似的。
这是一个可变长度字符,如果某些内容不一定是确切的字符数,但可能达到某个字符数,你可以使用varchar
来表示,你知道可能有时会少于字符,但最多可以达到一定数量的字符。
这也是一种权衡,还有各种不同类型的整数,因此不仅仅是单一的整数类型,而是small int、int和big int,每种都使用不同数量的字节来存储整数数据,因此如果你想能够存储更大或更小的数据。
对于数字,您可能需要一个更大的类型,比如int
或big int
,但如果您的数字相对较小,也许您只需一个小的int
。同样,浮点数也有类似的权衡,就像在您熟悉的编程语言C中一样。
float
和double
类型,其中double
使用更多的内存字节来存储信息。在这里,我们有float
和double
,它们允许我们存储可能是实际数值的浮点数,其中double
允许我们以更高的精度表达一个数字。
比浮点数更高的类型可能是可以的,还有许多其他类型。但一般来说,即使在SQL中,就像在Python这样的语言中,我们为每种不同类型的数据都有类型,因此现在我们理解到每种数据都有类型后,我们想要。
我们要做的是能够在数据库中实际创建一个表,并且有各种不同的SQL命令可以在我们的数据库上运行,以执行各种不同的操作,而我们可能想要执行的第一个命令就是创建一个表。例如,我们该如何去做呢?
关于如何做这一点,事实证明创建表的语法大致如下。这是我们在SQLite中创建表示航班的表的语法。例如,我们以关键字create table
开始,以表示我想创建一个新表。
在这个数据库中,接下来我给表命名。每个表都有一个名称,以便于引用,这里表的名称就是flights
,接下来是括号中的以逗号分隔的所有列的列表,我还没有添加任何数据。
创建表的查询将决定表的结构,列有哪些,列的类型是什么。在这里我们看到,在每对用逗号分隔的子句的开头,我们有列的名称,所以我们有一个ID列,因为虽然我们之前没有看到这个。
在SQL中,通常会有某种方法来唯一引用表中的特定元素。您知道,也许从纽约到伦敦有多个航班,我想要一种区分它们的方法,一个简单的方法是给每个航班一个。
表中有一个唯一的条目,一个唯一的标识符或唯一ID,由这个ID列表示,这样我们就能确保始终唯一地访问某个特定航班,因此我们有一个ID列,一个起点列,一个目的地列和一个持续时间列。
列具有类型,因此这里的ID是一个整数,它只是表示航班的某个数字,从1、2、3、4等开始,而出发地和目的地则标记为文本。如果我们使用MySQL,我可能会将其设置为可变长度字符。
如果我知道某个城市的字符长度不会超过某个数量,我可以限制某列的字符数。此外,我有一个持续时间,这里是一个整数,表示航班所需的分钟数。
从出发地到目的地,在列的类型后,我可以为列添加额外的约束。因此,主键是整数,主键意味着ID将是我唯一识别航班的主要方式。
这里的优化使得通过航班ID查找航班变得非常快速,并且我不希望有一个约束,限制某一列始终为空。我不希望有航班没有出发地或目的地。
我可以为特定列添加约束,说明出发地、目的地和持续时间列不应允许为空。我希望始终有出发地和目的地以及持续时间,最后这个主键是自增的。
在后续中自动更新ID,每次添加新行到表中时。因此,如果我添加一个新的航班,我可以为航班指定出发地、目的地和持续时间,但我不需要担心给它设置ID,后续将自动处理这个问题。
新表中的下一个可用ID号将是新的行。因此,这里是通过给表命名,列出每列的名称和类型,然后是我们想要放置在这些列上的任何约束来创建表。
我可以为特定列添加多种不同类型的约束。我们之前见过的一个是默认值,它会为特定列添加默认值。主键我们也见过,唯一约束保证每个值都是唯一的。
如果要允许某列出现相同值两次,可以为特定列添加唯一约束。此外,检查约束可以用来确保某个值遵循特定条件,比如某个数字在特定范围内,例如电影评分。
例如,如果你在数据库中存储电影评分,你可能会关心确保这些评分在 1 到 5 或 1 到 10 的范围内,或你认为对数据库有效的任何范围,因此通过约束,你可以开始确保,当你向表中添加数据时,这些数据。
将在某种方式上有效,需要遵循特定的约束,否则如果你尝试将其添加到表中,根本不会被接受,因此自然引出了下一个问题,我现在有了所有这些不同的表,可以使用这些 create table。
命令,但是我实际上怎么将数据添加到这些表中,我们要用 insert 命令来插入数据到一个 SQL 表中,那么这个命令可能看起来像这样,通常我们会说 insert into,来说明我。
想要向一个表中添加新行,接下来我们提供表的名称,例如 flights。我要向 flights 表添加新行,并在表名后需要提供括号内用逗号分隔的所有列名。
我将提供值,所以在这里我说我想提供这次航班的 origin、destination 和 duration 的值,请注意我没有提到 ID 列,我不需要担心添加 ID 的值,因为我说过 ID 应该自增,它将自动分配。
下一个可用的 ID,以及我要提供什么值,好的,在值这个词后我再次在括号中提供一个用逗号分隔的所有值的列表,这些值将与那些列对应,顺序相同,因此 origin 是第一个,所以 origin 对应的是纽约,destination。
将对应于伦敦,duration 将对应于 415,因此这个查询让我可以从我已经创建的航班表中添加一些数据到该表中,所以你可以使用这些插入查询,一旦你有一个表,就可以在每次有新航班时向该表添加新行数据。
当航空公司出现时,航空公司可能在后台运行一个 insert into flights 命令,该命令将关于该航班的数据添加到表中,但当然一旦我们向表中添加了数据,我们理想情况下希望有某种方式来从该表中提取数据,但一旦我们在里面存储了数据。
我们想从数据库中检索数据,为此我们将使用一种特定类型的查询,称为 select 查询,select 的作用是从已经存在的表中获取数据,而不会修改表中的数据,它只是。
将检索可能在特定表中的数据,那么这些查询看起来像什么呢?最简单的查询可能看起来像这样,选择星号从航班中选择,从航班中选择意味着我想从航班表中选择数据,这个星号是一个通配符。
这意味着选择你可以的所有可能列,所以如果这是我的航班表,其中每个航班都有ID、起点、终点和持续时间,那么从航班中选择星号将选择所有的数据,所有的行和所有的列都会返回给我。
当我现在运行这样的查询时,可能会发生在我运行这些查询时,当我访问数据时,我并不关心所有这些列,尤其是如果我有一个列数远多于这个的表,也许我只关心特定的列,而这将是浪费。
查询数据库,请求返回比我实际需要的更多数据,因此我可以运行这样的查询,我选择而不是星号。我说选择起点,逗号,终点,从航班中选择,所以我再次从航班表中选择所有的行,但区别在于。
而不是选择星号,这意味着选择表中所有的列,选择起点,逗号,终点,意味着我只会选择这两列,起点列和终点列,所以这里以蓝色突出显示的是如果有人约束返回给你的列时,将返回的数据。
现在运行这个特定的SQL查询尤其重要,因为表开始变得更大,并且行数越来越多,这里我只有六行,但你可能想象一下一个有成千上万甚至数百万行的表,你可能不想每次都返回每一行,所以就像你可以。
同样,你也可以在执行查询时约束返回给你的行,因此我可以说类似于选择星号从航班中选择,条件是ID等于三,这再次读起来非常像英语。我说从航班表中选择所有的列,但不是所有的行,我只想选择。
只有当这是一个SQL关键字时,ID等于三,ID是一个列的名称,三是值,它将仅在这个表中查找,找到ID等于3的航班,结果我只会得到那一行,它找到那一行。
具有ID为3的航班,它将返回给我,如果我说选择星号从航班中选择,条件是起点等于纽约,我可以看到我不仅需要在主键ID上过滤,我还可以在任何其他列上过滤,我可以说给我所有起点是纽约的航班,所以所有离开的航班。
从New York,你可能想象在New York的机场,他们可能需要某种查询来查找所有从New York出发的航班,以便在某种屏幕上显示该机场的所有出发信息,因此你可以运行这样的查询。
只获取数据库中这个表里具有某个Origin的行,比如说New York。让我们看看如何在SQLite数据库中运行这些查询。为了创建一个SQLite数据库,这实际上只是一个文件,我可以开始。
通过创建文件,终端中有一个命令叫touch,可以为我创建一个全新文件。我将创建一个名为flights.sql的文件,它将是一个SQLite数据库文件,默认情况下什么都没有,但假设你已经安装了SQLite,我可以运行SQLite 3。
现在我在SQLite提示符中,我可以开始写将在SQLite数据库上执行的命令。我可能会做的一件事是创建一个名为航班的表,这个表我希望有一个ID列,它是一个整数,应该是主键,主要用来识别一个。
航班,我想自动递增它。我还希望航班表有一个Origin字段,这将是一个文本字段,它是未知的。我还会添加一个目的地字段,也是一个未知的文本字段,最后我将添加一个持续时间字段,它是一个整数,也应该是未知的。
我将以一个结束括号和一个分号结束,按回车键执行该命令,我现在已经创建了一个名为航班的表。要验证这一点,在SQLite提示符中我可以输入.dot tables,这将显示我数据库中当前存在的所有表。
恰好我有一个名为航班的表,当然这个表里什么都没有,我可以通过运行select star from flights来验证,意思是获取航班表中的所有数据。如果我按回车,好的,什么也没发生,我没有看到任何数据,这里没有数据,但现在我。
我可以说插入到航班中,然后我将提供括号内的列名。刚才的幻灯片上我展示了多行,这只是为了视觉效果,这些命令完全可以在一行中完成。因此我可以说origin、destination和duration。
然后提供值,我可以说添加从New York到London的航班,并且我希望这个航班的持续时间是四百一十五分钟。我输入那个命令并按回车,这会向我的航班表中添加一行新数据,我可以通过运行select star from flights来验证这一点,我看到现在我有了这个。
表的ID为1,这就是从纽约到伦敦的ID列,持续时间为415。因此,我可以再次执行一系列插入查询,以给我们提供更多的数据,每一个都是不同的插入查询。
我将向我们的数据库中添加一条新航班记录,所以我将复制这些,然后在数据库中粘贴所有这些插入命令,以将大量航班插入到这个数据库中。现在如果我运行“从航班中选择所有记录”,我得到的是所有的航班,而这并没有格式化。
SQLite特别好用,有一些额外的技巧,如果你想把它格式化得更好,我可以说将其放入列模式,并给我一些标题,是的,标题是的,现在如果我。
从航班中选择所有记录,数据被组织得更整齐。
事物被真正放入列中,并且每行都有它们的标题,我可以看到,好的,我有一个文本基础的表示,显示了我刚刚图形化展示的内容,在我的航班表中,我有四列,ID列、出发地、目的地和持续时间,每个航班都是。
这是这个表中一行,现在如果我想执行额外的选择查询,比如从航班中选择所有记录,其中出发地等于纽约,那么我只会得到出发地是纽约的航班,而不是所有航班。
因此,我可以非常快速地过滤这些数据,只获取我实际关心的信息,还有其他与SQLite交互的方式,但这个SQLite提示是一个非常直接的方式,可以快速编写查询并查看查询结果。
结果显示,我们可以运行其他类型的查询。因此我们会看看其他一些查询,我们不只是需要说某个东西等于另一个东西,其他类型的布尔表达式也是被允许的。
我可以说类似于“从航班中选择所有记录,其中持续时间大于500”,这里的想法是,我们来看一下持续时间列,它是一个整数,因此我们可以对这些值进行算术运算。我可以取一个值,看看它是否大于500。
我获取的是所有持续时间大于500的行,因此我可以开始查询,不仅仅是一个值等于另一个值,还可以进行其他类型的比较,就像在Python中,当我们有布尔表达式时,我可以将多个表达式结合在一起,使用类似的词。
和' 或者' 的续集在它自己的查询语法中也有相同的类型,我可以说类似于 select star, from flights where duration is greater than 500 and destination equals Paris
的话,正如你可能逻辑上直觉到的,这意味着获取所有前往巴黎的航班。
持续时间超过 500 分钟,结果发现这个表中只有一行满足这两个约束,当然,我可以使用 or 来获得不同的查询,现在我在寻找所有持续时间超过 15 分钟或超过 500 分钟的航班。
目的地是巴黎,所以只要满足其中任何一个条件,我将获得结果,因此当我得到结果时,有些行的目的地是巴黎,有些行的目的地在巴黎,但持续时间超过 500 分钟,当然任何满足条件的行。
这两个约束特别是航班 ID 编号 2,当我在这个表上运行这个特定查询时,会得到结果。此外,我还可以执行其他类型的查询,所以我可以说类似于 select star from flights where origin is in
,然后在其中。
括号中的纽约,逗号,利马表示检查 origin 是否在这个可能值的序列中,即纽约和利马,只要它是这两个之一,我希望结果返回,所以你会得到来源于纽约的行,以及来源于利马的行。
因此,你可以开始构建更复杂的查询,能够检查一个 origin 是否在可能值的列表中,甚至是否匹配某个特定模式,可能你知道某一列看起来是某种方式,或是按照特定结构格式化的,你不知道。
我可以运行一个查询,如 select star from flights where origin not equals
,但在 origin 是 like 的情况下,然后用引号括起来有一个奇怪的表达式百分比 a 百分比,这个百分比再次代表一个通配符,这次通配符是在查看特定值。
在这种情况下,origin 列的值意味着百分比代表零个或多个字符,无论那些字符是什么,我们正在寻找某个字符的数量,可能是零,也可能更多,后面跟着一个 a,后面跟着其他字符,可能是零,也可能更多,最终。
这个查询将查看 flights 表,返回所有结果,其中 origin 中有一个 A,只要 origin 列中有一个 A,无论前面或后面是否有字符,所有返回的行将是。
这恰好包含字母 A,因此我们可以开始运行选择查询,还有额外的函数可以添加到选择查询中,如果我不仅仅是选择列的值,而是想计算特定类别航班的平均持续时间,或者我想统计从纽约出发的航班数量,或者我想找出前往伦敦的最短航班。
或者我想加总一堆航班的持续时间,也许是彼此连接的航班,我想加总这些航班的持续时间。
总旅行时间可能需要的 SQL 具有一些内置函数,允许我们执行这些类型的计算。因此,我们现在已经看过的几条不同的 SQL 命令包括创建表,允许我们创建一个新表,插入命令让我们添加数据。
到一个表格,然后选择让我们能够获取表格内的数据并检索它,但当然,当我们处理数据库时,数据并不只是被添加,数据以某种方式发生变化,所以我们也需要一种方法来更新已经在数据库中的数据。
还有另一个查询,即我们可以在数据库上执行的更新命令,该命令看起来大致如此,尽管显示在多行上,但你也可以将其完全放在一行上。SQL 实际上并不在乎你是否添加换行符,它只知道在命令末尾看到分号时,该命令结束。
这里我所说的是使用更新关键字表示我想更新一个表,我想更新哪个表,我想更新航班表,然后我想做什么呢?我想将持续时间设置为 430。
不管当前持续时间的值是什么,改为 430,但我当然不想将其更改为 430 的每个航班,就像在选择子句中一样,我可以说“某个值等于某个值”或者使用其他条件来指定我想选择的行。
同样,对于更新,我可以说将持续时间设置为 430,条件是某个特定条件为真,因此在这里,我将查看航班表,找到所有出发地为纽约且目的地为伦敦的航班,对于这些行,我将更新持续时间的值。
列设置持续时间列等于 430,因此使用这种语法,我可以获取数据并更新它,将一个值更改为另一个值,通过准确定位我想要更改的行。如果我只想更改一行,我可能会执行像“更新航班设置持续时间等于某个值,条件是 ID 等于特定值”。
能够精确定位一个ID,然后对那一行进行一些修改,除了插入数据、选择数据和更新数据之外,我们最后要关注的主要命令是删除数据的能力,即能够从一行中删除,或者。
删除多行并去掉它们,所以像从航班中删除目的地为东京的命令,正如你想象的那样,会删除航班表中所有满足这个条件的行,即目的地等于东京。
从特定表中删除的能力,只在条件为真的情况下更新表,基于特定条件选择数据,并将数据插入表中,还有其他一些子句可以用来添加SQL查询。
添加额外的功能,如果我不想从某个特定的SQL查询中返回所有行,我可以限制返回的结果,通常从航班中选择所有航班会得到表中的所有航班,但我可以说从航班中选择,限制为五条,仅仅是说你知道吗。
我只想从中得到五个结果,这个表的排序让我可以决定结果的顺序,我可以说选择从航班中返回所有结果,按目的地排序或按持续时间排序,以便按时长对所有航班进行分组。
将一大堆行组合在一起,所以如果我想按起点将所有航班分组,我可以选择从航班中选择,按起点分组航班,并将其作为。
我可以在分组上施加约束,比如我想选择所有的航班,通过按其起点分组,但它们需要至少有三条航班记录,意味着至少需要有三条航班从那个特定城市出发,对于所有这些特定子句。
这些知识对你直接编写SQL很有帮助,但我们在这里不需要太过担心,尤其是因为很快我们不再自己编写SQL,而是将编写Python代码,Django会在后台操控数据库。
创建将要在底层数据库上运行的SQL命令,因此我们会看到我们其实不需要担心编写特定的语法,而是Django会为我们处理大部分内容,所以现在我们有了一个航班表,一个跟踪我们所有航班的表。
按其ID、起点和目的地及持续时间组织它们,但在处理数据时,尤其是在较大数据集时,常常需要这样做。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P13:L4- 数据库、SQL与集成 2 (表关联,django模型,集成) - ShowMeAI - BV1gL411x7NY
数据中我们有多个数据表,这些多个表可能以某种方式相互关联,让我们看看一个示例,看看这可能是如何产生的,我们将介绍一个概念,我们称之为外键,我们稍后将看到它的含义。
这里再次是我们的航班表,航班表有四列:ID、起点、目的地和持续时间。但是在纽约,当然有多个机场,比如说,因此仅仅通过城市名称标记每个起点或目的地可能没有意义,可能我还想提供。
三个字母的机场代码对应我所提到的机场,那么我该如何在这个表中编码,不仅是起点,还有那个城市的机场代码,以及目的地城市的名称,还有那个机场的代码呢。
我可以添加更多的列,比如说,好吧,现在我们有这个表,包含一个ID,一个起点,起点代码,一个目的地,目的地代码和一个持续时间,但现在这个表开始变得相当宽,有很多列,尤其是我重复了一些冗余数据。
与此特定的三个字母代码相关联的*****,对于纽约和其他机场也是如此,这个数据的结构中存在一些混乱,因此,当我们开始处理数据和越来越大的数据集,拥有越来越多的列时,我们通常会想要。
我想要规范化这些数据,将它们分隔到多个不同的表中,这些表以某种方式相互引用,所以与其仅仅有一个航班表,我们可能考虑的是说,航班是一种对象,但还有另一种我关心的对象。
类似于机场,所以我可能只会有一个单独的机场表,这个表有三列,一列是机场的ID,一个唯一数字可以识别特定机场,一列是那个机场的三个字母代码,还有一列是城市名称。
关于那个机场所在的城市,现在这是一个更加直接、简单的所有机场的表示,问题变成了我的航班表会发生什么,我的航班表在这里有一个ID、起点、目的地和持续时间,而起点和目的地的类型。
在这种情况下,仅仅是文本数据,表示航班出发或到达的城市名称。现在我有了这个单独的机场表,其中每一行都有其独特的ID,那么在这种情况下,我可以做的是,避免存储冗余数据。
出发地和目的地以文本形式存储。我可以存储我们称之为外键的内容,即对另一个表中键的引用,并将这些列重命名为出发地ID和目的地ID,而不是存储文本,而是存储一个数字,其中出发地ID 1意味着航班1的出发地是机场1。
我可以查找机场表,找出哪个机场的ID是1,这将告诉我该航班的出发地。如果我去机场表,查找哪个机场的ID是4,这也会告诉我该航班的目的地。因此,通过结合两个不同的表。
用于表示机场的一个表和用于表示航班的一个表。我能够通过外键将这两个不同的表连接起来,我的航班表中的某些列,即出发地ID列和目的地ID列,使我能够引用存储在其他表中的信息。
另一个表,以及你可以想象这个航空公司数据库的增长和存储更多不同种类的数据,将表之间的关系变得非常强大。因此,你可能想象的是,除了存储机场和航班,航空公司可能还需要。
还需要存储有关乘客的信息,比如谁在哪个航班上。因此你可以想象,构建一个乘客表,其中有一个ID列来唯一标识每位乘客,一个名字列来存储每位乘客的名字,以及一个姓氏列来存储他们的姓氏。
姓名和航班ID列,以存储该乘客正在乘坐的航班。因此在这种情况下,我可以说,好的,哈利·波特在航班号1上。我可以在航班表中查找,以找出航班的出发地和目的地。
它的持续时间是什么。现在当我们开始设计这些表时,我们必须考虑这种设计的影响。在乘客表的情况下,确实似乎存在我创建的表设计的局限性,换句话说,如果你仔细考虑一下,你会发现。
这个表设计的局限性在于,任何特定行只能关联一个航班ID。哈利·波特只有一个航班ID列,并且只能存储一个值,这似乎使我们无法表示一个人可以有多个航班的情况。
可以在多个不同的航班上。因此,这开始涉及到表中行之间不同类型关系的想法。关系的一种类型是多对一关系或一对多关系,在这种情况下,我可以表达一个航班可以关联。
例如,有许多不同的乘客,但我们可能还想要一个多对多的关系,其中许多不同的乘客可以与许多不同的航班关联,一个乘客可能有多个航班,一个航班可能有多个乘客。为此,我们需要另一个表。
对于这种特定类型的表格有稍微不同的结构,一种方法是创建一个单独的表来存储人员,我可以有一个人员表,每个人都有一个ID,有一个名字和一个姓,与之前相同,但我不再在表中存储航班信息。
我创建的设置只存储人员在这个表中,航班信息不在此表中,然后我会有一个单独的表来处理航班上的乘客,并将人员与他们的航班关联起来,我们可以考虑这个表应该是什么样子的。
将人们与他们乘坐的航班关联起来。因此,我们很可能需要一列外键,引用这个人员表,另外一列外键,引用航班表,以便我能够将这些表关联在一起。
表格可以看起来像这样,现在这是简化后的乘客表。这个表只有两列,一个是人员ID列,另一个是航班ID列。这个表的想法现在是,它被称为关联表或连接表,旨在将一个表中的一个值与另一个表中的另一个值关联起来。
这里的这一行1和1意味着ID为1的人在航班1上,我可以在人员表中查找那个人。在航班表中查找该航班,弄清楚那个人是谁以及他们乘坐的航班,而这里的2 + 4意味着无论那个人是谁。
ID为2的人是在ID为4的航班上。因此,现在这使我们能够表示我们想要的关系类型。我们有一个机场表和一个航班表,任何航班都将映射到两个不同的机场,一个是目的地,一个是起点。
机场可能出现在多个不同的航班上,这是一种一对多的关系。然后在这里,当涉及到乘客时,我们将人们存储在一个单独的表中,并且在人员和航班之间有多对多的映射,任何人都可以乘坐多个不同的航班,就像这里。
例如,乘客编号二在航班1和4上,同样一个航班可以有多个乘客。因此,在这种情况下,航班编号六有乘客。我们能够表示这些关系,当然,做到这一点的副产品是,现在我们的表看起来有点杂乱。
这种情况有些混乱,因为当我查看这个表时,不明显我在看什么数据。我看到这些数字,但不知道它们的含义。我已经将所有这些表分开,现在更难判断谁在乘坐哪个航班。
要查看人员表中的人员,在航班表中查找航班,并以某种方式将所有信息关联起来,以得出任何结论,但幸运的是,SQL使我们能够轻松地从多个不同的表中提取数据并进行连接。
我们可以使用一个连接查询,将多个表结合在一起,因此连接查询的语法可能看起来像这样。这里我们将回到只有两个表的设置,我有航班和乘客,每个乘客都与一个航班相关联。
扩展这个,连接多个表来处理更复杂的示例。在这里,我想选择每个人的名字、出发地和目的地,并从航班表中选择,但我需要与乘客表连接,然后我会说出如何。
这两个表是相关的,在这种情况下我想说。它们之间的关系是,乘客表的航班ID列与航班表的ID列相关联。航班表有一个唯一标识每个航班的ID。
乘客表有一个航班ID列,唯一标识我们所指的特定乘客的航班。因此我可能得到的结果是这样的表,显示每个人的名字,以及他们的出发地和目的地。
我将从航班表中提取信息,名字将从乘客表中提取,但通过使用连接查询,我能够从两个不同的表中提取数据,并将它们结合在一起。我可以运行多种不同类型的绘制查询。
我看到的是默认的连接,也称为内连接。我们有效地进行内连接,将两个表交叉比较,基于我指定的条件,仅返回在两侧都有匹配的结果,即将乘客的航班ID与ID匹配。
在航班表中,有各种不同类型的外连接。如果我希望允许左侧表的某些内容与右侧表的任何内容不匹配,或者右侧表的某些内容与左侧表的不匹配,但请记住。
我也可以运行其他类型的连接查询,其他策略在处理序列表时可能会有帮助,我们可以进行优化,使查询更高效,因此,我们可以在特定表上创建一个索引,你可以将其视为。
索引有点像书籍最后的索引,例如,如果你想在教科书中搜索一个主题,你可以逐页翻阅教科书,寻找每个主题,试图找到你所寻找的主题,但通常情况下,如果表格有一个。
索引,你可以去书的索引,找到你正在寻找的主题,这将快速给你一个参考,告诉你如何到达相关页面,而表格上的索引运作方式非常相似,它是一个附加的数据结构,可以构建,确实需要时间。
需要内存来构建这个数据结构并维护它。每当你更新表中的数据时,但一旦它存在,它就会使在特定列上的查询变得更高效!
更高效地,你可以非常快速地在索引中查找某些内容并找到相应的行。因此,在这里我们可以有一个命令,例如创建一个索引,我们将其称为乘客表上的名称索引,特别是在姓氏列上,可能会说我期望。
当我查询这个表时,我会经常根据乘客的姓氏进行查找,所以我想在这个表上创建一个索引,以便能够更高效地根据姓氏搜索乘客。这只是关于序列语法的一个总体概述。
这是我们可以使用的一种语法,用于创建数据表,存储数据行,每行由一些列组成,每列都有一个类型。你可以创建表格,向它们添加数据,更新、删除以及从这些表中获取数据,但随着我们。
开始引入这些新技术,总是存在与这些技术相关的风险和潜在威胁,而在序列中,关键是要意识到所谓的序列注入攻击,这是一种安全漏洞,如果你不注意实际操作方式。
执行你的序列命令,可能会出现这种情况,例如,如果数据库中有一些用户信息,你可能会在数据库中存储这些用户,例如在一个用户表中,其中有一个用户名列和一个密码列,虽然在实践中你可能。
我们不希望以明文存储密码,假设你在一个表中存储用户名和密码,我们有一个看起来像这样的登录表单,你可以在其中输入你的用户名和密码。如果有人输入了他们的用户名。
可能发生的情况是,网络应用程序可能会执行类似于 SELECT * FROM users WHERE username = '这个特定的用户名'
的查询。我们只需将用户名替换到那里,然后将密码替换到另一边,如果有人尝试登录到我们的网站。
比如 Harry 使用密码 1 2 3 4 5
登录,我们可能会运行选择查询,执行 SELECT * FROM users WHERE username = 'Harry' AND password = '1 2 3 4 5'
。我们的逻辑可能是,如果我们得到了结果,那么意味着存在一个用户名为 Harry,密码为 1 2 3 4 5
的用户。
我们可以继续为该用户签名,但想象一下,如果输入用户名的用户是一个黑客,可能会发生什么。似乎这个用户名有点奇怪,输入的密码无论是什么,结果可能就是他们输入的内容。
用户名是 WHERE username = 'hacker'
,然后 --
后面会出现的情况是,--
在 SQL 中表示注释,这意味着忽略其后的一切,就像在 Python 中使用井号符号意味着这一行的其余部分是注释一样,编译器会处理它。
运行程序时应该忽略它,因此在 --
之后的所有内容都会被忽略,这样我们就有效地绕过了密码检查。某人可以绕过密码检查并登录到一个他们无权访问的账户,这里就是 SQL 语法中的漏洞,如果我们不注意。
当我们运行 SQL 语句时要小心,我们可能在运行一些不受信任的 SQL 命令,而这些命令是某个黑客能够注入到我们程序中的。那么,我们该如何解决这个问题呢?一种策略是转义这些字符,转义只是意味着添加一些反斜杠,使其变得安全。
确保 SQL 知道将这些视为字面上的引号和破折号,而不是任何特殊的 SQL 语法。另一种策略是使用一个抽象层在 SQL 之上,这样我们根本不需要编写 SQL 查询,这正是我们接下来要做的。
当我们过渡到 Django 的世界时,看看当我们开始使用像 Django 这样的网络框架时,我们现在能够不必担心 SQL 语法的细微差别,而是更高层次地处理我们的模型,处理我们正在使用的对象类型。
与这个应用程序内部交互的另一点需要注意的是关于SQL的潜在竞争条件,竞争条件是指在你有多个事件在并行线程中同时发生时,可能会发生的事情。
发生了一件事情,同时还有另一件事情发生,你可以想象在社交媒体网站的情况下,比如你可以在Instagram上点赞一条帖子或在Twitter上点赞一条推文。例如,如果两个人同时试图点赞同一条帖子,会发生什么?如果我们不小心处理这些特定的SQL查询,就可能出现竞争条件的问题。
我们最终可能会尝试查询一条帖子有多少个赞,而另一个人也试图做同样的事情,当我们尝试更新时就会发生冲突,结果可能不是我们所预期的,并且在处理与竞争条件相关的问题时,可能会出现许多意外结果。
当多个事情同时发生时,我们该如何解决这些问题?一个策略是对数据库进行锁定,表示所有。
好吧,我在处理这个数据库,其他人无法触碰这些数据,让我完成这个事务,完成对这个特定事务的操作并进行我需要进行的所有更改,只有在我完成后,我才能释放锁,让其他人去修改。
数据库也是如此,在我们开始处理SQL和与数据库打交道的过程中,有许多需要注意的问题。所以现在我们已经看过SQL的语法,理解这些表如何工作、如何结构化以及我们可以在这些表中添加什么,接下来我们就来继续。
特别关注Django模型,这是在Django应用程序中表示数据的一种方式,因为Django在设计我们的网络应用程序时,真正强大的地方就是能够通过这些模型表示数据,因此我们将继续。
并尝试创建一个网络应用程序,以代表航空公司可能希望在其自己的网络应用程序中存储的内容。好的,首先我想做的是创建一个Django项目,所以我将输入Django admin start project,项目的名称将是。
我创建的项目叫做航空公司,我为航空公司的网站创建一个项目。例如,我将进入航空公司目录,然后在我的代码编辑器中打开它。但在我实际开始编辑任何代码之前,请记住,每个Django项目都需要有一个或多个应用程序,因此我将为。
这家航空公司,我将启动一个应用程序来跟踪航班,因此跟踪航班相关的信息,比如出发地、目的地和持续时间。
还有哪些乘客在这些航班上,当我创建一个新应用时,我需要做的第一件事是进入 settings dot PI,在航空公司内部,将这个应用添加为已安装的应用,因此,flights 现在是我已安装的一个应用,然后我想要做的是说,进入 URLs PI。
这又是目录,对于我能获取的所有URL,这个特定的网络应用程序我将导入,包含因为我想做的是当某人访问 flights slash 的路径时,我想带他们到 flights dot 的 URL,将它们映射到我 flights 中的 URLs PI 文件。
当然现在我需要在我的 flights 应用中有一个 URL spy 文件,所以我进入 flights,创建一个新的文件,我将其命名为 url-step PI,然后我们可以从 Django 的 URLs 中导入 path,从 dot 导入 views,然后我的 URL 模式将放在这个列表中。
在我开始处理可处理的 URL 之前,我首先要做的是创建一些模型,模型将是创建一个 Python 类的方式,该类将表示我希望 Django 存储在数据库中的数据,因此当我创建一个模型时,Django 将会弄清楚。
找出需要使用的 SQL 语法,以创建该表,然后可以操纵该表,随时在我对这些模型做出更改时选择、更新和插入。因此,在这个名为 flights 的每个应用程序中,我可以做的是,每个应用程序都有一个 models dot PI 文件。
我们之前没有查阅过,但这是我们定义模型将存在于我们的应用程序中的地方,每个模型将是一个 Python 类,你可以将其视为每个我们关心存储信息的主表都有一个模型,因此让我定义一个。
新的类叫做 flight,将继承自 models model,因此我正在创建一个新的类叫做 flight,它将是一个模型,然后我需要在这个类中提供航班的所有参数,航班有什么属性是我可能想要跟踪的。
航班有一个起点,起点将是一个模型字符字段,这一切在 Django 的网站上都有文档,关于各种不同类型的字段,我可以包含在 Django 模型中,这里我说,好的,这里是一个最大长度的字符字段。
假设是 64,我假设大多数城市名称不会超过 64 个字符,这似乎是航班起点的合理最大长度。例如,每个航班还将有一个目的地,它将是一个最大长度为 64 的字符字段,每个航班将有一个持续时间。
这将只是一个整数字段,现在这是我非常第一个Django模型。它是一个名为flight的类,我定义了航班的所有属性,然后使用Django语法来确定它们应该有什么类型。每个航班都有一个起点、一个目的地和一个持续时间。
当然,这里实际上并没有修改Django正在使用的数据库在!。
为了存储关于我的网页应用的信息,我们可以查看是否确实能够返回航空公司,并输入ls
,你看到的是还没有存在的数据库,我只有一个航空公司航班目录和一个管理的PI文件,所以我想要做的是以某种方式告诉Django。
应该更新数据库以包含我刚刚创建的模型的信息,这是我们在Django中称之为迁移的过程,我创建一个迁移,说明我想应用于数据库的一些更改,然后我迁移它们,告诉Django好吧,接受。
那些更改并实际应用到数据库中,因此这是一个两步过程。第一步是创建迁移,即如何实际操作数据库的指示;第二步是将那个迁移的步骤应用到基础。
我们可以通过命令进行迁移,再次我们将使用管理脚本,它有许多不同的命令,可以让我们控制应用程序的各个部分。我将使用Python管理的PI,然后进行迁移,现在我们看到的是,我们在0001_initial数据中创建了迁移。
在这个迁移中,它创建了一个名为flight的模型,所以如果我继续查看迁移目录,我会看到!。
这个文件是为我创建的,我不必自己创建它。这个文件是对Django的指示,如何操作数据库以反映我对模型所做的更改。这是对Django的一个构建,创建一个名为flight的新模型,具有这些特性。
特定字段在其中,它基于我对模型所做的更改,而我添加的模型现在在这个迁移中得到了反映。如果我想将迁移实际应用到Django的数据库中,我可以运行Python管理的PI迁移来继续应用这些更改。
迁移中有一堆默认的迁移也会被应用,但请注意,其中一个迁移是将flights.0001_initial应用,表示让我们继续应用那个迁移,创建将表示航班的表。
如果我现在输入 ls,你会看到我现在有一个 DB.sqlite3 文件,这是一个 SQLite 数据库,将包含一个存储我所有航班的表,那么我该如何实际开始操作这些数据呢?我该如何与这些模型进行交互?我可以使用直接的 SQL。
通过打开这个数据库文件并运行命令来进行语法操作,但 Django 提供了一些很好的抽象层,这样我就不需要自己执行这些命令,我可以开始更通用地使用 Python 类、变量和我熟悉的东西,在 Python 语言内部。
进入 jingoes shell,我可以通过运行 Python manage.py shell 来直接运行 Python 命令,这样做的目的是打开一个 shell 或控制台,我可以开始编写在这个 web 应用程序上执行的 Python 命令,首先我想从 flight 模型中导入 flight。
我应用的模型名称是我从中导入 flight 类的文件的名称,就是我刚创建的那个模型文件,现在我可以做的是创建一个新航班,我可以说类似 f 等于一个航班,起点是纽约,目的地是伦敦,持续时间是四百。
十五分钟后,我可以说 F.dot save 来保存我创建的新航班,这种语法,现在我会把它做得大一点,以便我们更容易看到,是我插入数据到这个表的方式,我不需要在 SQL 中使用插入查询,我只需写一个。
Python 命令和 Django 知道,当我创建一个新航班并保存时,应该在基础的 SQL 表上运行插入命令,在这里我创建了一个新航班,具有特定的出发地、目的地和持续时间,我也已经保存了这个航班,如果我想。
查询该航班,获取有关该航班的信息,我可以说类似 flight.objects.all 的内容,相当于选择所有的内容,获取我数据库中存在的所有航班,在这里我看到我得到了一组查询结果,其中有一个航班。
返回航班对象一,因此航班已经为我创建,现在航班对象一的名称可能没有太大帮助,如果这个模型有更干净的方式来看特定对象的名称,那就更好了。
特定的航班,例如,结果证明我们可以这样做,任何模型,我会回到 model.py 中,任何模型都可以实现双下划线 str 方法,它返回该特定对象的字符串表示,这不仅适用于 Django 模型,还适用于 Python。
类更一般地说,如果这个函数返回对象的字符串表示,让我们返回一个格式化的字符串,那就是 self.dot ID。我会说 self.dot origin 到 self.dot。
destination 所以在这里,我所说的是任何航班的字符串表示将是一个字符串,提供它的 ID,然后说明从起点到目的地,这是一个干净的名称,用于表示这个特定的航班,所以现在如果我回到交互式解释器,我可以说从 flights.models 导入。
flight 我可以说好的,让我们将一个变量叫做 flight 等于 flight 对象的集合,现在 flight 将是这趟从纽约到伦敦的航班,它现在有了一个更漂亮的字符串表示,这使得与它交互稍微容易一些,如果我想获取。
那个航班,我可以说 flight 等于 flights.first,这是一个查询集。first 获取我第一个航班,所以现在我有了这个从纽约到伦敦的航班,就像在任何 Python 对象中,我可以开始访问那个对象的属性,我可以说好的,flight 你的 ID 是多少,flight 你的起点是什么。
flight 你的目的地是什么,flight 你的飞行时长是多少,我可以访问所有我最终关心的这个航班的属性值。如果我想删除这个航班,我可以说。
类似于 flight.dot delete 现在,最终虽然这并不是我实际想要表示的航班模型,因为在这里我再次使用字符字段来表示起点和目的地,而实际上我可能希望使用另一个表来表示机场。
每个航班和一个机场之间存在某种关系,所以让我们继续尝试实现这个想法,现在我可以回到模型中并创建一个新的类,我将创建一个名为 airport 的类,这也是一个模型,我希望这个机场类有一个代码,这是一个字符字段。
机场代码的最大长度为 3,城市则是一个最大长度为六十四的字符字段,我们还为这个机场提供一个字符串表示,字符串表示将是机场所在城市,然后在括号中是机场代码。
所以它会是像纽约这样的名称,然后在括号中是 JFK 来表示特定机场,现在我们的航班模型需要稍作更改,不再将起点和目的地作为仅存储文本的字符字段,而是起点将成为一个外键,一个外键。
引用另一个表,比如机场表,然后我可以提供一些额外的参数,这样单独这些就足够了,但我可以添加一些额外的参数,比如 on delete 等于 models.dot cascade,那么这意味着什么呢?当我有相互关联的表时。
SQL中以某种方式知道如果你删除某个东西时会发生什么,如果我有一趟从JFK到伦敦的航班,而我在稍后的时间决定从我的数据库中删除JFK机场,那么这趟航班会发生什么,就像当它引用的东西消失时航班会发生什么。
被删除时,models dot cascade
的意思是如果我从机场表中删除一个机场,它也将删除任何相应的航班,还有其他在删除时的参数,你可以设置,例如,如果还有航班的话,甚至不让我删除一个机场。
从那个机场起飞或前往那个机场,这称为模型,不保护,但有其他方法可以实现类似类型的约束,而我将提供的另一个参数被称为相关名称,正如我们稍后将看到的,这将是我。
以反向顺序访问关系,从航班我可以使用航班的原点调用dot origin
,但我想问的另一个问题是,如果我有一个机场,如何获取所有以该机场为出发地的航班,因此在这里如果。
我给这个外键一个相关名称,Django会自动设置相反方向的关系,因此在这里,如果我们有一个机场,我想知道所有以该机场作为出发地的航班,相关名称的合理名称类似于出发。
所以如果我有一个机场,我可以访问所有的出发,这样我就能获取所有从那个机场起飞的航班,我在目的地这里也会做同样的事情,取而代之的是,它将是一个外键,它将引用机场,当我们删除它时,它会继续进行。
和级联,相关名称将是到达,因为如果我有一个机场,我可能想访问所有的到达,所有与在特定目的地到达的航班对应的航班,因此现在我已经做了两件事,我添加了一个新类,称为机场,并且我在我的。
现有的航班模型,这是在我的Python代码中更改的,但还没有!
在我的数据库中进行更改,因此为了在数据库中进行更改,再次是一个两步过程,第一步,Python managed PI make migrations,说找出模型CI中进行的任何新更改,并继续创建如何对数据库进行这些更改的迁移指令,在这里。
我们看到我们创建了一个新的迁移文件,这个迁移将创建一个叫做机场的模型,并且它还会改变目的地字段和我航班模型中的出发地字段,因为我们知道我们已经将目的地和出发地不再是字符字段。
而是引用特定的机场。因此,这需要在数据库中进行更改,要进行更改,我可以运行类似Python managed PI migrate的命令,继续应用这些更改。我们已经应用了刚创建的迁移,我们的数据库也更新了。
现在是最新的,那么我们能做什么呢?我可以继续回到显示页面,我将导入航班模型,从中导入所有内容。我现在可以创建一个机场,可以说JFK是一个机场,代码为JFK,城市为纽约。
然后我可以创建一个伦敦的机场,代码为LHR,城市为伦敦,并保存。你知道的,你可以创建更多,我可以说CDG是一个机场,代码是CDG,城市是*****。或许我们再做一个,NRT是一个机场,代码是NRT。
例如,东京的城市,所以我创建并保存了机场,这些机场会被添加到我的机场表中,现在我可以添加一个航班F,航班的出发地为JFK,目的地为伦敦希思罗,持续时间为四百十五分钟,我会继续保存这个。
我已经创建了四个机场,创建了一个航班并保存了它。如果我输入F,代表我的航班,我看到这是一个从纽约到伦敦的航班,但我也可以说F的出发地是什么,写下F的出发地,现在这是一个机场对象,特别是JFK。我可以进行F的操作。
使用origin.city获取出发地的城市,也就是纽约,F的origin.code获取那个机场的代码,即JFK。如果我从出发地开始,比如JFK或伦敦希思罗,我可以说LHR的到达航班到,伦敦希思罗,看起来只有一个,就是这个。
我刚刚创建的航班从纽约出发。
纽约的航班前往伦敦。因此,这使我们能够仅通过使用这些Python模型来操控SQL,现在我有Python类来表示各种不同类型的数据,而不是运行SQL查询,比如从航班中选择所有。
我可以与这些类和类中的属性进行交互,Django为我处理确定底层SQL查询的过程,并将结果返回给我。现在我们可以围绕这个想法设计一个网络应用程序,我可以进入URL stop PI并保存。
让我们添加一个URL模式,说明默认路由将继续加载索引视图,给它一个名称为索引,这与我们上次看到的类似。那么我们该在索引视图中做什么呢?索引视图中,我想做的是显示所有航班的列表。
可能从点模型导入航班和机场,或者我只是需要航班。我只想要一个所有航班的列表,所以,我将从所有的模型中导入航班,现在我想做的是,返回让我们继续渲染一个。名为航班的模板,斜杠索引点,HTML,并给索引。html访问一个。
一个叫做航班的变量,这个变量将等于什么,它将等于航班对象的所有,以获取我想放在这里的所有航班,好吧,那么我现在可以做什么,我需要做的是实际创建那些,单独的模板,所以在。
我将创建一个名为模板的新文件夹,在其中创建一个名为航班的新文件夹,在其中继续创建一个布局。点HTML,就像我们之前做的那样,该布局将包含我们的HTML页面的基本结构,因此,头部分将标题设置为航班,并且。
正文部分将有一个块体和块的结束,就像之前一样,这是该页面的默认布局,然后我将添加一个新的模板叫索引。html,这将扩展航班斜杠布局点。HTML,然后在页面的主体部分,我将显示一个h1。
只是说航班,现在让我们创建一个无序列表,我可以在其中循环,航班中的航班,结束循环,但是在循环内让我创建一个列表项,我只是打印,像航班,也许我会打印一个。航班,然后航班ID打印出,像航班一航班二航班。
然后我将打印航班起点到航班目的地,所以我所做的是创建一个模板,我将给予一个变量的访问权限,叫航班,其中航班将是一个代表所有的。航班的变量,这些航班是我通过运行航班对象的所有查询得到的,这就是我的方式。
Django的API使用它所提供给我的功能来获取航班,并获取存储在Django数据库中的所有航班。然后在模板中,我对每一个航班进行循环,打印出一个列表项,让我可以访问该航班的属性。
航班这个ID从起点到特定目的地,现在我将进入我的终端,运行Python,管理pi运行服务器,这也是如何。
我们运行Django网络应用程序,现在如果我去那个URL斜杠航班。
这次因为这是URL,我看到的正是我所期望的。看到一个无序列表,正好有一个从纽约到伦敦的航班在显示,正在从我的数据库中获取数据,现在在这个模板中显示出来,如果我添加新的航班,它也会更新在。
这个页面也是如此,所以如果我继续,回到 Python 管理的 shell。我要从 flights models 导入 star,接下来我们继续,好吧,让我们。例如,我该如何获取的机场,在中呢?结果表明,如果我想获取像这样的内容,我可以说像*****等于 airport.dot。
objects.dot,然后我可以说如果我做 airport.objects 所有,那会获取到所有的机场。例如哦,似乎我并没有实际拥有一个*****。但是如果我想的话,可以添加一个,但如果我做 airport.objects.dot.all,那会再次给我所有的机场。如果我想要过滤我的机场列表,而不是获取所有的。
机场中的一些,但只获取其中的一部分,我可以说 airport.dot.objects.dot.filter,并且我可以说像获取所有城市是纽约的机场。例如,这将会给我一个查询集,只包含我关心的结果,所以再次 airport.object.stop.filter 让我们。
限制返回的结果,不获取所有的机场,而是只获取城市是纽约的机场,例如,这样只会返回一个,所以我可以说这个过滤条件是 City 等于纽约,然后取这个查询集的第一个,得到我只想要的查询集中的第一个和唯一的项。
这给我纽约机场,简化了同样的操作方法,如果你知道你只会得到一个结果的话,我可以说像 airport.objects.dot.get,这将只获取一个结果,如果它知道在纽约只有一个机场的话,这也会返回。
纽约JFK机场,但如果航班超过一个或没有航班时会抛出错误,例如,我们继续把它保存在JFK中。接着我们创建一个从纽约到的航班。例如,我可以设定 C DG 等于机场对象,获取城市等于,现在我。
我有这个变量 CDG,它代表机场,如果我想创建一个从纽约到的新航班,我可以说 F 将是一个航班,其起点是 JFK,目的地等于 C D G,持续时间等于 435,我也可以保存这个航班,所以我添加了一个新的。
航班,所以现在如果我运行服务器,Python 管理了一下,我刷新了页面,现在看到有两个航班,一个是从纽约出发的。
从纽约到伦敦的航班,是从纽约出发的*****,但当然每次我想更新数据时,添加新数据或操作数据时,必须进入 shell 以运行直接命令,这样才能添加新航班、新机场等等。
所以,我真正想要做的就是通过网络接口非常简单地添加它。通过网络,我能够说好,让我添加一个新的航班。这个航班从位置1到位置2,并且可以使用这些信息。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P14:L4- 数据库、SQL与集成 3 (用户管理) - ShowMeAI - BV1gL411x7NY
我们现在知道如何构建一个网页,只做这件事,但Django是基于。这个理念,即它不希望你,程序员必须重复其他人已经做过的工作,而这个,试图定义模型的过程,以及非常快速地创建和编辑,和操作模型是如此普遍,Django已经提供了所有。
已经为我们构建了一个专门用于操作。模型的完整应用程序,这被称为,Django管理应用程序,这个应用程序。我们已经看到了一些痕迹,如果我们,记得在我们的应用程序内部的URL配置文件,我们看到我们为自己的应用程序添加了,一个路径,但还有。
已经为我们默认提供了一个路径。
斜杠管理也将我们带到管理,应用程序,因此,为了使用这个。管理应用程序,我们需要在我们的Django网络应用程序内部创建一个,管理账户,方法是通过命令行。我可以运行Python manage.py create superuser,它会问我我的名字,我会继续。
我继续输入我的用户名和电子邮件,地址,它也会询问一个,密码,我可以随意设置一个我想使用的密码,再输入一遍。以确认现在Django为我在。这个网络应用程序中创建了一个超级用户账户,以便我使用,凭证拥有。
访问管理的网页界面。
应用程序并实际上操作这些,基础模型,因此为了。做到这一点,我需要做的第一件事是,获取我的模型并将这些模型添加到。管理应用程序,因此在模型的,models.py
中,我有一个名为airport的类和一个名为flight的类,如果我们查看,我有的文件,还有另一个文件我们。
还没有真正查看过的,名为admin.py
的文件,在我的应用程序内部,在admin.py
中,我首先从我的模型,导入flight和airport,现在我要说admin.site.register。
我们的机场和管理网站点注册,航班和这个将要做的事情是。它将告诉Django的管理应用程序,我希望使用管理应用程序。来操作机场和航班。因此,让我们看看这个管理,应用程序,看看它实际上是如何工作的。
我可以运行Python manage.py runserver,这将启动网络服务器。我会访问这个网址,而不是去,斜杠航班,我会去斜杠管理。这将打开这个Django,管理应用程序,这不是我写的。Django写的,它要求我登录,我将继续。
使用刚才的凭证登录,我输入了我的用户名和密码,我得到的是 Django 的站点管理界面。这个界面是由 Jenga 构建的,我完全不需要设计这个界面,但重要的是,如果我们注意到这里,我现在有能力添加和管理机场。
通过这个网页接口的 Django 管理界面来管理航班,所以现在使用这个界面我可以操作底层数据库,修改模型,添加和修改已经存在的数据。所以如果我点击机场,我在这里可以看到我已经。
已经添加到我的数据库中,包括东京、巴黎、伦敦和纽约,我可以再添加一个新的机场,我可以说让我们添加 PVG,也就是上海,我可以选择保存、继续编辑或保存并添加另一个。我会添加几个,所以我将保存并添加另一个,让我们再添加伊斯坦布尔机场,再添加莫斯科。
这是一个机场记录,可能还会再添加一个利马机场,我将点击保存,现在我通过这个网页接口添加了很多机场。Django 最初是为新闻机构创建的,他们很快想要发布文章和新帖子。
网站通过这样的界面让我们很容易地快速添加新文章,并展示文章内容在页面上,现在我们也能很快地将新机场添加到我们的网站,所以如果我们想添加航班,我们可以继续进行。
继续回到主页,我点击航班,我看到我的数据库中已经有两个航班:从纽约到伦敦和从纽约到巴黎。我将添加一个新的航班,它让我选择出发地、目的地和时长。而且出发地必须是一个机场,所以它将给我提供。
我可以选择一个机场,比如说上海是出发地,目的地是巴黎,时长大约是 760 分钟。例如,使用 Django 的管理界面,我已经能够添加多个不同的航班和多个不同的。
如果我不回到管理应用程序,而是回到我自己编写的航班应用程序,回到 /flights,现在我可以看到通过 Django 的管理界面添加的所有新航班。我将它们添加到管理界面,现在我看到这个从上海到。
我看到从巴黎到纽约的航班,所以现在我想开始为这个网页应用程序添加更多页面,让这个网页应用程序更复杂,可能给我能力点击特定航班查看关于该航班的详细信息。
我希望每个航班都有自己的页面,不仅仅是 /flights 显示所有航班,而是 /flights/1 显示航班 ID 为 1 的航班。
斜杠到 482,等等。我可以为此返回到 URL 间谍,创建一个新路径,我们将创建一个路径,我将在其中指定一个航班 ID,这将是一个整数。完成后,继续加载航班视图,其名称将为航班。现在我只需去视图间谍。
并添加一个叫航班的函数,所以我会返回到视图间谍,除了索引函数外,我们还将定义一个接受航班 ID 作为参数的航班函数。那么这个航班函数将要做什么呢?第一件事我实际上需要做的是,获取该航班。
航班等于航班,该对象获取我航班 ID 等于航班 ID 的航班,例如,或者说 PK,替代 ID,作为一种更通用的方式来引用主要键。无论主要键被称为什么,在这种情况下,PK 只是。
ID,但我能做的是渲染一个类似航班航班的 HTML 模板,并将航班作为输入传递给它,所以,我们把这个航班传递给航班 HTML。现在我可以创建一个模板,创建一个新的文件,叫做航班 HTML。这个文件也将扩展航班/布局 HTML,使用相同的 HTML。
页面布局和主体内部,我们可以简单地说一些类似于大的内容。我们会提到航班航班 ID,然后,也许是一个无序列表。可以说起点是航班起点,目的地是航班目的地。
目的地和持续时间是航班持续时间。所以现在我有一个页面,显示关于任何特定航班的航班信息。如果我继续在我的网页浏览器中加载,而不是加载斜杠航班,而是斜杠航班/一,比如说。现在我有关于航班编号一的信息,而斜杠航班/- 则让我得到。
关于航班编号二的信息,查询该特定航班,然后打印它的起点、目的地和持续时间。现在有一些错误检查,我们可能应该在这里做一下。如果我尝试访问一个不存在的航班,比如航班 28,我将会得到某种错误。
不存在与航班匹配的查询。
如果不存在,我可能想稍微控制一下这种情况会发生什么。你可能会想象添加一些额外的错误检查来处理这些情况,但现在我们就暂时先不管这一点。现在我们继续添加不仅仅是拥有机场的航班的功能。
与它们相关联,但我们也来为航班添加乘客,以便能够表示可能实际在这些航班上的乘客 - 所以我将继续回到模型间谍,在模型间谍中,除了机场类和航班类,我还要创建一个新类,叫做乘客。
成为一个模型,乘客有什么属性呢?乘客有一个名字,我们将创建一个最大长度为六十四的字符字段,姓氏的最大长度也为六十四。乘客也正如我们之前描述的,他们与航班之间有一个多对多的关系。
一个航班可以有多个乘客,乘客可以在多个航班上,最终我们需要一个额外的表来跟踪这一点。但我们可以在Django中更抽象地思考,只需说每个乘客都有与之相关的航班,这些航班都是模型。
多对多字段与航班相关,因此每个乘客可以与多个航班关联。我们将设置blank=True,以允许乘客没有航班的可能性,或许如果他们没有注册任何航班。我们还将为此提供一个相关名称,命名为乘客,意味着如果我有一个乘客。
乘客,我可以使用航班属性来访问他们的所有航班。同样,如果我有一个航班,我可以使用这个乘客的相关名称来访问所有在该航班上的乘客,我们稍后会看到这会有多有用。乘客的字符串表示,我们将只是。
接下来是他们的名字和姓氏,这似乎是一个合理的表示方式。
特定乘客,现在我需要应用这些更改,我需要说Python。Manish,运行迁移因为我对我的模型进行了新的更改。我创建了一个特定的乘客模型,现在如果我运行Python管理,
现在迁移,我已将这些更改应用到我的实际数据库中,如果我进入admin.py,我们将进入admin.py,并注册航班、机场和乘客管理。
注册乘客,现在通过管理界面,我可以操作。
我也可以说Python管理,运行服务器以运行我的网页服务器,通过访问斜杠admin进入我的网页服务器管理视图,进入乘客部分,让我们添加一个乘客,我可以说,好的,名字哈利,姓氏波特,我们将把他放在航班一上。
航班三,可能他在两个不同的航班上,例如,你可以按住命令或控制键,以便选择多个航班。我们将继续保存,哈利·波特已成功添加。让我们再添加几个其他乘客,将添加罗恩·韦斯利。
另一个将添加赫敏·格兰杰和。
我们还会添加金妮·韦斯利,所以我们添加了许多不同的乘客,它们现在都存在于Django的管理界面中。现在我想做的是在航班页面上显示关于在任何给定航班上乘客的信息,所以我可能会这样做,通过进入视图文件并在。
航班页面除了提供给乘客访问权限外,这个模板将会获得乘客的信息,我们可以通过说flight.passengers.all来获取乘客,而我们之所以能够这样做,是因为乘客是相关名称,它是我们获取航班及其上所有乘客的方式。
因此现在在flight.html中,我可以添加一些东西,比如添加一个h2,叫做乘客,我们在这里,我将循环遍历乘客,显示该乘客。将该乘客打印在一个列表项中,在Django中我可以说如果。
列表为空,我们只需要一个列表项,写着没有乘客的意思。
目前没有人乘坐这趟航班,所以,现在我的网络服务器仍在运行。我可以返回到斜杠航班,这里是所有的航班,如果我去斜杠航班,斜杠一,我现在看到的是航班一,哈利·波特是这个航班的乘客。
但是如果我去航班2,好的,那个航班上也没有乘客。现在有点烦人的是,我不得不通过使用URL来来回回,这样才能在页面之间导航。如果我想的话,我可以链接到那些页面,我可能会这样做,让我们在航班页面上添加。
一个链接,指向一个URL索引,上面写着类似“返回航班列表”的内容。因此,现在这里有一个链接可以带我到索引视图,同样,我可以进入index.html,对于每一个列表项,这些列表项实际上都会是一个链接,指向一个特定航班的URL。
航班路线作为参数需要一个航班ID,因此在这个URL替换中,我可以指定使用航班ID作为我想使用的航班的ID,因此现在我把每一个航班都放在一个链接里,这个链接可以带我到航班路线,但因为航班路线需要一个。
参数航班ID,我可以在这里指定航班ID,因此现在如果我返回到斜杠航班,我现在看到一份航班列表,每个航班实际上都是一个可以带我去其他地方的链接,因此现在我可以点击任何一个链接,比如从纽约到巴黎,这会带我到航班页面,我可以点击。
返回航班列表,这会带我返回航班列表,点击另一个航班并转到那个航班,所以我现在能够通过在各个页面中添加链接的方式,将这些页面连接在一起。
不同的页面也将带我到其他路由。因此,现在我可能想要做的是,除了显示任何特定航班上的所有乘客外,还给自己添加乘客到航班的能力,这似乎是我可能想在其中做的合理事情。
web 应用程序,所以我该如何进行呢?为了做到这一点,我需要一些新的路由,让我可以为特定乘客预定航班。因此,我将继续回到 URL,并在 URL 的 API 内添加一个新的路径,这将是航班 ID / 预订一个。
在航班 ID / 预订将让我为此特定航班 ID 预订航班,例如航班 1、航班 2 或航班 3,当我执行时,我们将继续转到预订视图,并将其命名为 book,因此现在我需要实现预订视图,这个视图将如何工作,我将定义一个。
函数,名为 book,它将不仅接受请求作为参数,还接受航班 ID。首先,如之前所述,我想获取航班 ID,但记住之前提到的,我可以通过多种方式请求网页,我可以通过 GET 请求方法请求网页。
这意味着我只是想获取此页面,或者我可以通过 POST 请求方法请求一个。这意味着我想将数据发送到页面,一般来说,每当你想要操作某个事物的状态,尤其是操作我们的数据库时,这应该在 POST 请求中,我提交一些表单。
数据以及对该 POST 提交的响应,你应该操作数据库中的内容。因此,当调用此预订路由时,我们会检查,如果请求方法是 POST,那么我们想执行某种操作,相关的航班将是 flight.objects.get,获取主键为该航班的航班。
航班 ID,然后我还想与表单关联,当有人提交此表单以预订新乘客时,他们应该告诉我乘客的 ID,也就是我应该在这趟航班上预订哪个乘客,因为这是你需要知道的两条信息。
为了实际预订航班,你需要航班和乘客的信息,所以现在假设信息将在 request.post 中,然后在方括号中是 passenger。这意味着关于我们想要在此航班上注册的乘客 ID 的数据是。
将通过一个输入字段传递,该字段的名称为 passenger。在任何特定的输入字段上的名称决定了我们收到的内容是什么。当像这样的预订路由能够处理来自用户的请求时,我们将继续获取该信息,因为默认情况下。
这可能是一个字符串,让我们继续并将其转换为整数,以确保我们在处理整数。让我说这个乘客将会是乘客对象,通过主键获取,这整个过程。现在我所做的是,如果请求方法是POST,意味着有人提交了。
通过POST请求方法,这里我首先说航班对象获取,获取特定航班,给我那个航班ID的航班。然后我获取一个乘客,我正在获取哪个乘客,他们的主键,也就是ID,等于通过这个提交的内容。
提交表单的名称是乘客,我们还没有创建那个表单,但我们马上就会做到。最终,我们希望在这方面添加更多的错误检查,比如说,如果有人请求一个不存在的乘客或者一个不存在的航班,这肯定会有一些问题。
我们可能应该在这里进行错误检查,但为了简单起见,暂时假设我们能够获取航班并获取乘客。那么我们如何访问乘客的航班呢?我可以直接说乘客的航班。为了向一些集合中添加新项,比如航班。
我可以直接说乘客的航班不添加航班,这将相当于向一个表中添加新行,以记录该乘客在该航班上。但Django的抽象优点在于我不必担心那些底层的细节。
表的结构我可以在更高的层面思考,只需说拿这个乘客,带上他们的航班集合,并继续向那个航班集合添加一个新航班。当这一切完成后,我可能想要做的是返回某种重定向,将用户重定向回。
航班页面,所以我们将继续并返回一个HTTP响应来重定向。那么,我希望把他们带到哪个URL呢?我希望把他们带到航班路由,而反转又获取了特定视图的名称,并告诉我这个URL是什么,我们上次看到过,航班路由接受一个参数。
需要作为参数传递航班ID,所以我需要将其提供给航班路由,航班ID被结构化为一个元组,这将把我重定向回航班路由,以便我可以再次查看航班页面。而我需要在顶部添加的是,从Django HTTP导入HTTP响应。
除了Django的URLs外,还需要从中导入反转,这些都需要添加,以便我可以在提交表单后将用户重定向回航班页面。反转接受在URLs中定义的特定视图的名称,比如index或flight或book,并给我这个视图。
实际的 URL 路径应该是什么,我们上次讨论过,这样我就不必在我的 Django Web 应用程序中硬编码 URL。我可以通过名称引用 URL,如果我需要更改 URL,我可以只在 URLs.py 中更改一次,而该更改将反映。
所以现在我需要做的下一件事是实际创建。这个表单到目前为止,我只有一个名为 book 的函数,等待发出 post 请求,当对它发出 post 请求时,我们将继续提交,这个表单并继续添加。
对于这个特定乘客的航班,但我现在想做的是实际添加。那个表单,所以我将回到模板,进入航班 HTML,我想在这里添加一个表单,我将其标记为一个 h2,称为添加乘客。我们将创建一个表单,其操作将是,预订的 URL,所以我们要。
转到预订路由,再次如果我们回忆 URL 中的预订路由,通过名称为 book 的路由,这个视图需要作为参数的一些航班 ID,所以我需要提供航班 ID,作为我为乘客预订的航班的参数,正好是 flight.ID,因为这个模板有。
访问一个名为 flight 的变量,此提交的方法同样是 post,回忆之前提到的,每当我在 Django 中有一个表单时,我需要。提供 CSRF 令牌,仅出于安全考虑,以确保 Django 知道,确实是这个应用程序在提交这个表单,我们将继续。
添加一个下拉列表,你可以使用选择字段在 HTML 中创建。这个选择字段的名称将是乘客,原因是。当我在请求的 post 数据中获取乘客时,我要寻找的是一个名为乘客的字段。
所以这就是我希望这个下拉框的名称,而在一个选择下拉框中,我们有一整堆,供我们选择的选项。而且将会有一个选项,供每一个不是航班乘客的人。那么我该如何获取所有不是航班乘客的人呢?
目前航班页面似乎只访问实际乘客,并且还没有访问不在航班上的人。因此,听起来我需要向 stemplot 添加一些额外的上下文。我们想要访问的附加信息,所以我将继续给这个。
航班访问额外的信息,我们将调用不在航班上的人,如何获取非乘客呢,只需过滤。仅获取与特定查询匹配的乘客对象,在 Jango 中也有一种方法,可以说乘客对象.exclude,以排除乘客。
满足特定查询,因此我想排除那些在他们的航班中包含这个航班的乘客,那么这实际上意味着什么呢?它意味着当我渲染flight dot HTML时,应该有几条信息,它需要知道正在进行的是哪个航班。
渲染时需要知道谁在航班上,谁是乘客,但如果我想要一个下拉菜单,可以从所有未在航班上的人中选择,比如我想为你注册这个航班,我也需要所有的非乘客,除了已经在航班上的人。
获取所有这些的点,最终就是在说的内容。因此,利用这个我现在有一个变量叫做非乘客,在构建此页面时可以使用。因此在航班HTML中,我可以说对于每个非乘客,允许我创建一个可供选择的选项。
选项的值将是乘客的ID,因为最终在我提交表单时,我关心的是我从下拉菜单中选择的这个乘客的ID,但当然,查看此页面的用户不想看到人们的ID,他们想看到。
人们的名字,因此在选项标签内部,我们将直接打印出乘客的名字,稍后我们会看到这一切在HTML中实际上是什么样子。现在我已经创建了这个表单,底部还需要添加一个类型为提交的输入,以便让我提交这个表单。
现在让我们尝试运行这个应用程序,似乎有一个小错误。
我说的是查看预订而不是查看,预订视图是模块的名称,因为它在一个叫做view spy的文件中,现在看起来我的服务器运行正常。我可以返回到斜杠航班,让我获取一个航班,比如从纽约到伦敦的航班号一和正确。
名称错误,命名的乘客未定义,这是Django呈现给我的。发生的错误以及我的Python代码看起来只是意味着。我在用完的pine内部引用。
乘客但我从未导入它,因此在顶部我会导入乘客,现在我的网络应用似乎运行正常,希望有信息。我看到乘客,而且我现在在底部看到一个添加乘客的部分,带有一个下拉列表,可以点击并查看这三个人。
人们如果不在此航班上,因此如果我想将像Ginny Weasley添加到此航班,我可以点击Ginny Weasley,点击提交,这样就提交了表单。我被重定向回同一航班页面,现在Harry和Ginny都在。
在航班上,然后在添加乘客列表中,我看到了Ron和Hermione,这就是我的选择,因此,利用Jano的模型,我已经能够非常快速地构建一个相对复杂的应用程序,一个能够向我展示这些信息并让我进行操作的应用程序。
这些数据最终存储在一个sequel数据库中,Jango的一个重要优势就是,它为我提供了这个管理界面,通常我可能需要花很多时间来设计一个。
这个网络界面让我能够做一些事情,比如更新某个人的信息,例如他们的姓名、他们搭乘的航班,快速添加、删除和编辑模型,这在一个网络应用程序中从头开始构建可能需要相当多的时间。
但幸运的是,Jango将这一切直接提供给我,尽管这个管理界面是由Jango设计的,但在我可以在这个管理界面上进行的操作方面是非常可定制的。
如果我进入admin.dot PI,这里是我为jenga的管理界面配置的内容,我可以说我想以特定的方式配置管理界面,例如在航班中,默认情况下我只看到航班的起点和终点。
想要查看更多关于航班的信息,我可以说继续,并给我一个名为flight admin的类,它将是model.admin的子类,在这里我可以指定任何特定的设置,以应用于航班ad.Paige的显示,这一切都记录在jenga的网站上。
只需要阅读就能知道有哪些配置选项可供你使用,但我可以在列表显示中,列出所有航班并显示给我,我应该访问哪些字段,像起点、终点和持续时间等信息。
也许还想让我看到ID,因此我想在加载航班时看到所有这些信息,当我注册航班时,我会说注册这个航班,但在这样做时使用flight admin的设置。
我希望在查看管理界面时使用这些特定的设置,因此现在如果我返回并继续点击航班,那么在这个列表显示中,以前我只看到ID和起点、终点,而现在我可以配置它以显示所有的ID和起点、终点。
我已经能够配置这个显示,以我想要的方式工作,还有其他的。
你可以进行的配置也有很多,我比较喜欢使用的一种是,如果我想更新我的乘客管理界面,当我编辑乘客时,可以以特殊的方式操纵多对多关系,使用一个名为filter horizontal的属性。如果我在航班上使用水平过滤器,这将。
只是让操作乘客所搭乘航班的界面变得更好看一些,再次提到具体的。
语法并不是最重要的,关键是这些都是Django记录的可配置设置,你可以查看如何配置管理界面,使其完全按照你想要的方式工作。所以现在如果我回到首页,去乘客那儿,或许点击一下。
乘客像哈利·波特,现在我看到这个水平过滤器,这是一种非常好的方式来操纵乘客所搭乘的航班,我在左侧看到他们可添加的航班列表,在右侧看到他们已经选择的航班。
我只需双击一个航班,就能轻松地将其从可用航班移到他们所搭乘的航班,反之亦然。能够快速控制。
并且操纵这些模型,这些都是Django开箱即用提供的功能。所以,现在简·多给了我们很多特性,能够非常简洁地表示模型,迁移方法能够快速地将这些更改应用到我们的数据库,最后我们将关注的是。
这个认证的想法在许多网站上,我们需要某种认证方法,给用户提供登录和登出的能力,让Django记住特定用户的身份。我们现在要做的是引入一个可以与此互动的应用。
认证方法,因为Django框架内置了一系列认证功能,我们可以利用这些功能,这样就不需要重写登录逻辑,以及如何表示用户。Django为我们做了很多工作,所以我们将。
继续创建一个应用程序来实现这一点,好吧,所以让我们回到我的终端,现在我有这个航空公司项目,其中有一个名为flights的应用,我现在想创建另一个应用来维护用户。
这将是一个允许我表示用户的应用,就像之前一样,当我创建一个新应用时,我需要进入设置,添加用户作为这个项目中已安装的应用之一,然后我会进入URLs.py,表示我还希望在访问用户时包含用户。
不是URL,所以所有与我的用户应用程序关联的URL,现在我需要实际创建这些URL,因此我会继续进入我的用户应用程序,创建一个新文件,名为URLs,其中的内容与我们通常在这些URLs文件中看到的相同,我需要导入。
路径导入我的视图并定义一些URL模式,这里我想做的是定义一个路径,将我带到视图index,我们将称之为index,然后我会创建另一个路径,将我带到login,称为login view,名称将是login,我们将有另一个路径称为logout。
对于一个名为logout view的函数,将与之关联,因此我们将有效地有三个不同的路由,一个主index路由将显示当前登录用户的信息,一个路由用于登录某人,一个表单将显示他们可以输入用户名和密码的地方。
用于登录,然后一个路由允许用户能够从此应用程序注销,所以现在让我们继续并实际编写这些函数。我们需要一个名为index的函数,一个名为login view的函数和一个名为log out view的函数,所以我将进入views.py并从index开始。
index函数需要做什么?它将显示有关当前登录用户的信息,我登录到这个网站后,就会呈现出index页面,因为我们从程序的角度考虑,我们首先需要考虑如果有人尝试访问这个。
页面但他们没有认证,我们如何才能发现这一点?在这种情况下我们该怎么办?假设,如果请求对象的user.is_authenticated
为False,传入的请求对象在Django中每个用户自动都有一个与之关联的user
属性。
该用户对象具有一个is_authenticated属性,告诉我们用户是否已登录。如果他们未登录,我们将通过HTTP响应重定向他们到登录视图。为了使这一切正常工作,我将需要从Django.http导入HTTP响应重定向,以及从。
Django.urls,让我们继续导入reverse,如果用户没有认证,那么我们将把他们重定向到登录视图,登录视图将做什么呢?暂时让我们渲染users/login.html
,一个用户可以登录的某种形式。
自身,我们需要创建一些模板。我会创建一个模板文件夹,其中包含一个用户文件夹,在这个文件夹内我们将创建一个基本的布局,正如我们多次所做的,这将再次成为该应用程序中页面的通用结构,标题将是用户,正文将是。
只是有一个叫做 body 的块,我可以在稍后用其他内容填充,现在。既然我有这个 HTML 布局,我可以继续创建一个新的,名为 login dot HTML 的文件,其中 login。not HTML 将扩展用户斜杠布局,HTML,在 body 块内我可以。只需以 HTML 表单形式显示,所以我可以说,想要的内容是。
这将是一个表单,当我提交表单时,让我们继续前往。登录 URL,但我们使用 post 请求方法,我在登录,我。正在提交一个表单,通常在你,执行此操作时,你会想要通过 post 提交表单数据,尤其是在,用户名和密码的情况下,因为如果你这样做。
这种情况你不希望,用户名和密码以。get 参数的形式传递,因为这些会出现在,URL 中,我们的表单将会有我们的 CSRF。令牌以确保安全,与之前一样,一个类型为文本的输入,名称为。用户名,出于用户友好性,让我们给它一个占位符,还会有。
用户名,这样用户就知道在这里输入他们的用户名,我们还会有一个。输入框,其类型为密码,名称也是密码,当输入框类型为。密码时,这意味着我们的 HTML,在浏览器中,无论是 Chrome 还是。Safari 等等,我们将知道以点的形式显示密码,而不是。
字符,然后我们会给它一个占位符为密码,然后一个类型为提交的输入,其值为登录,这样我们就。
现在有了登录的能力,所以如果我们,继续运行这个程序。
python managed by run server,我们应该,看到用户的东西使用没有属性。登录视图,好吧,看起来我,调用了这个函数登录请求,它。实际上应该叫登录视图,我还需要一个名为注销的函数。
视图,但我还没有实现,所以我就先说 pass。但我会在稍后回来,实现注销视图。好吧,所以看起来我的 Web 服务器现在在运行,在我实际上去登录。页面之前,先回到管理员,页面并实际创建一些用户。
我可以去用户,然后添加,让我们添加一个用户,用户名可以是,比如说,Harry,然后我们继续给 Harry 设置一个密码,并且我们继续。保存并添加另一个用户,也许再添加,Ron,继续添加他,我们会继续保存这些用户,他们可以有,附加的信息与之关联。
然后我可以给 Ron 起个名字,比如 Ron,Weasley Ron Weasley 在 example。com 的邮箱地址。这是他的电子邮件地址,还有一堆 Django 为你提供的默认字段,用于操作用户信息,如果我想的话,可以将这些用户添加到。那些字段中,给他一个,名字,姓氏,电子邮件地址和。
你也可以自定义这些字段,如果你想添加自定义字段以跟踪个别用户的情况。现在我将从Django管理员注销,因为我不再需要它,但如果我去/users,我没有经过认证,所以。
我看到的登录表单看起来像这样,有一个地方让我输入用户名和密码,当然现在你可以输入比如Harry的用户名和密码,但我还没有实现数据处理,所以让我们继续。
现在我们将返回到登录视图,登录视图函数可以通过两种方式被调用,一种是通过get请求方法,意味着只显示登录表单,另一种是通过post提交数据到登录表单,所以如果请求方法是post,那么让我。
首先获取用户名,它将在名为username的字段中,接下来获取密码,它将在请求的post数据中,名为password的字段中,现在我想做的是尝试认证该用户,如何做到这一点呢,结果表明这里。
有几个Django提供的功能,我可以导入,因此我将从Django的认证模块中导入三个功能,我们最终会使用的,一个是authenticate,用于检查用户名和密码是否正确,一个是login,另一个是logout,我。
在获取到用户名和密码后,我可以在这个登录视图中使用这些功能,我想验证用户,检查用户名和密码是否正确,因此我将说用户等于authenticate请求,用户名是username,密码等于password,所以。
authenticate函数仅接收请求、用户名和密码,如果用户名和密码有效,它会返回用户的身份,只要用户不是none,这意味着认证成功,我可以继续登录用户,如何登录用户呢,我使用login。
Django给我的函数是在此请求中登录用户,现在我可以进行HTTP响应重定向,将用户重定向回。索引路由,也就是用户最初开始的路由,如果用户不是none,且认证成功。
否则如果认证失败,我该怎么办呢,那么让我重新渲染相同的用户登录页面,但添加一些额外的上下文,上下文将是一个消息,提示凭据无效,现在在login.html中,我可以添加一些逻辑,如果有消息,则继续。
在一个div中显示该消息,然后结束IFTA,如果有的话。
消息将会被打印出来,否则我们根本看不到它。因此,如果我继续刷新登录页面,似乎没有什么变化,但假设我输入一个不存在的用户名赫敏和某个密码,然后我登录,那么我会收到这个错误消息,凭证无效,我们没有。
能够登录用户,那么会发生什么。
如果我们成功登录,那么用户将被带到这个索引路由,现在看来我们需要完成这个索引的领域,索引路由做什么呢,让我们去返回渲染一个名为用户/用户.html 的模板,在用户.html 中,我们会继续使用用户。
我们仍然会扩展用户/布局,因为我们将使用相同的基本布局,但在这个页面的主体部分,我想展示的信息是,我想说欢迎,然后像哈利或欢迎罗恩,或者无论用户是谁,事实证明在 Django 模板内部,我可以访问到请求。
用来发出这个 HTTP 请求,这意味着我也可以访问请求.user,即与该请求关联的用户,如果用户有名字,我可以访问请求用户.first_name,除此之外,我还可以显示其他信息,也许像他们的用户名是请求用户.username。
也许他们的电子邮件地址是请求用户电子邮件,所以我可以展示给用户。
关于他们的信息,如果例如哈利登录,签名就是。哈利用哈利的凭证登录,点击登录。
那么哈利看到的页面上写着欢迎哈利,哈利已登录,他们是请求.user,利用这些信息我们可以访问名字、用户名和电子邮件,只需访问请求.user 的属性。现在最后一件我们需要添加的东西,仍然还不存在,就是一个方法。
实际上注销用户,事实证明,正如 Django 有登录功能,Django 也有注销功能来处理注销,因此我们不需要自己实现。所以我们注销视图需要做的就是调用这个注销功能,然后确定用户应该去哪里。
在他们注销后,你知道吗,让我们把他们带回登录页面,显示注销的消息,以指示用户现在已注销,然后在 user.html 中,我们会添加一个链接。
将会转到注销路由,仅仅说注销,例如,当哈利返回哈利的页面时,如果哈利看到一个说注销的 URL,哈利点击注销,哈利被注销并被带回这个页面,因为现在用户未经过身份验证,因此他们现在看到的只是默认的登录页面。
如果现在Ron使用他的用户名和密码登录,Ron将看到与他相关的信息,因此Django为我们提供了许多开箱即用的功能,让我们能够表示这些模型,并有一个管理界面来操作它们,以及一个迁移系统。
这个系统让我们可以非常快速地对模型进行更改并应用到数据库中,同时还有一个内置的用户身份验证系统,能够让用户快速登录和登出我们的网络应用程序。这些都是使我们能够轻松操作的功能。
很快利用一些东西,例如序列、模型和迁移,来构建动态有趣的网络应用程序,支持数据。这个就是用Python进行网页编程。
哈佛CS50-WEB|基于Python/JavaScript的Web编程(2020·完整版) - P15:L5- JavaScript编程全解 1 (事件,变量) - ShowMeAI - BV1gL411x7NY
[音乐]。
好的,欢迎大家回到Python和JavaScript的网络编程课程。今天我们将注意力转向第二种主要编程语言。
我们将重点关注这一类,特别是JavaScript,为了理解JavaScript实际上是如何有帮助的,我们回顾一下关于互联网通信的图示。
通常我们会有一个用户,也称为客户端,他们使用计算机上的网页浏览器,无论是Chrome还是Safari,或者其他网页浏览器,向某种网页服务器发送HTTP请求。该服务器处理请求,然后返回某种响应,返回给客户端。到目前为止,所有的代码。
我们编写了Python网络应用程序,代码在Django网络应用程序中运行。例如,所有代码都在某种服务器上运行,该服务器正在监听请求,进行一些计算以处理该请求,然后生成某种响应,通常以HTML的形式。
JavaScript将使我们能够开始编写客户端代码,JavaScript将允许我们编写实际在用户的网页浏览器中运行的代码。这对于多个原因都很有用,首先如果有。
我们想要进行的计算,但不需要去服务器进行计算,我们可以仅通过在客户端独立运行代码来更快地进行计算。此外,我们可以开始使我们的网页更具互动性,JavaScript将会。
HTML页面仅描述页面的结构,并在这方面提供了直接操作DOM的能力。DOM是文档对象模型,代表用户所查看的网页的树状层次结构,因此JavaScript将使我们能够编写代码,直接操作网页上的内容,我们将看到。
这很快就会变得非常强大。那么,我们如何在网页中使用JavaScript以添加一些代码,增加一些编程逻辑呢?到目前为止,我们已经看到了HTML,这是一种描述网页结构的语言。
嵌套标签,我们在页面顶部有那些头标签,描述页面结构的主体标签,以及可以嵌套其中的其他标签。为了将JavaScript添加到网页中,只需包含一些通常位于HTML页面内部的脚本标签。
我们使用这些脚本标签来告诉网页。
浏览器中,这些脚本标签之间的任何内容都应该被解释为JavaScript代码,网页浏览器将执行。因此,我们的第一个程序,例如,可能看起来像是脚本标签内部的一行代码,类似于这样,其中alert就是一个。
一个函数将会产生一个警报,和Python中的函数一样,JavaScript中的函数也可以接收参数,所以在这对括号之间,我们有一个参数,类似于字符串“你好,世界”,这是我们希望使用的文本。
向用户显示,所以我们来试试看,看看我们如何实际上使用这段代码来编写将运行在用户网页浏览器中的JavaScript。我将创建一个新文件,称为hello.html,并在其中包含我们已经使用的基本HTML结构。
之前已经见过的地方,我有一个头部部分,其中有一个标题,然后一个主体,可能只说“你好”,例如,现在我想在这个网页中添加一点JavaScript,所以在我的网页头部部分,我要添加一个脚本标签,在这对脚本标签之间。
现在我可以编写JavaScript代码,这些代码将在用户实际打开这个页面时在网页浏览器中运行。现在我只需说alert,然后“你好,世界”,事实证明,在JavaScript中你可以使用单引号或双引号。
引号用于表示字符串,我通常在这里使用单引号,这只是一个惯例。所以在这里,我正在运行一个名为alert的函数,它将向用户显示一个类似于“你好,世界”的警报,这将在。
我想实际打开页面,我可以打开hello.html,或者你可以直接在你的网页浏览器中访问,无论是Chrome还是其他浏览器。而现在在页面顶部,你会注意到我得到了一个小警报,一些互动,显示这个页面说“你好,世界”。
让我选择一个选项,比如按下一个按钮,例如“你好,我想要”。确认按钮会显示“好的,关闭警报”,这是我们第一个JavaScript示例,我们有一个名为alert
的内置函数在JavaScript中,用于我们的网页浏览器,并且我们的网页浏览器知道,当我们调用alert函数时。
浏览器应该显示一个看起来像这样的警报消息,如果我点击“确定”按钮以关闭。
警报,然后我们返回到最开始的原始页面,因此现在我们可以开始想象,利用这种能力来以编程方式显示警报,我们可以为我们的应用程序添加其他功能,而JavaScript可以非常强大。
是基于事件驱动的编程,而事件驱动编程的核心就是思考在网络上发生的事情是以事件的形式发生的。事件的一些例子包括用户点击按钮或用户从下拉列表中选择某项。
用户滚动列表或提交表单,用户的任何操作都可以一般视为事件,我们可以用JavaScript添加事件监听器或事件处理程序,这些操作会在事件发生时运行特定的代码块或函数。
我们可以开始让我们的JavaScript代码响应用户实际与网页的交互,比如当用户点击按钮时,我想运行这个特定的JavaScript函数。因此,让我们试一下,我现在要做的是。
警报hello world,让我把这个警报放在一个函数里面。在JavaScript中创建一个函数,只需使用关键字function后跟函数名称,我会称这个函数为hello,然后在括号内放入任何该函数接受的输入,这个hello函数将不会。
要接受任何输入,我将使用一个空的括号,然后在花括号内包含我想在此函数中运行的任何代码。因此,我现在所做的是创建一个名为hello的函数,然后在花括号内定义了函数体应该执行的代码。
运行hello函数,hello函数应该做的是显示一个警报,在这种情况下显示“hello world”。现在我想让这个函数在页面上发生某事时运行,例如当用户点击按钮时,为此我需要的第一步。
实际上要做的是创建一个按钮,添加一个仅显示“点击这里”的按钮。
例如,现在如果我刷新此页面,我看到有hello,还有一个按钮显示“点击这里”,但当我点击时,没有任何反应。
比如,当我点击“点击这里”按钮时,但它没有改变任何东西,因为我还没有说明当用户点击此按钮时应该发生什么。一种方法是向此HTML元素添加一个属性,称为onclick,这将添加一个事件。
这个按钮的点击处理程序将说明当用户点击这个按钮时应该发生什么,我将把 onclick 属性设置为运行 hello 函数。在 JavaScript 中运行函数的方法与在 Python 中运行函数的方法一样,你使用函数名后跟一个。
设置一对括号,表示去运行这个函数。使用这个括号调用函数,另一个表达运行函数的方式,括号之间没有内容意味着我们没有向 hello 函数提供任何输入,尽管如果 hello 函数确实接受。
我们当然可以在输入中添加这一点,在括号之间,所以现在我把这一页的两个部分结合在了一起。我有一个按钮,上面写着点击这里,并且我添加了一个 onclick 处理程序,说明当你点击按钮时,应该运行 hello 函数,然后在上面我定义了 hello 函数。
那么 hello 函数应该做什么呢?当函数被调用时,我们将显示一个提示,这个提示在这种情况下说 hello world。现在我们应该能够刷新页面,至少最初看到相同的内容,它只是显示 hello 和一个告诉我点击这里的按钮,但现在如果我去。
点击那个写着点击这里的按钮,那么我会收到一个提示,显示这个页面说 hello world。我可以按 OK,这个事件处理程序将始终有效。我再次点击按钮,我会第二次收到提示,因为每次我点击按钮,它都会调用 hello 函数。
再次,当我点击 hello,当 hello 函数运行时,它将显示这个特定的提示。所以这似乎给了我们相当大的能力,正如在其他编程语言中一样,例如 Python 或者你可能用过的其他语言,JavaScript 拥有所有这些相同类型的语言特性。
我们已经见过数据类型,比如字符串,但我们还有其他数据类型,我们也会很快查看。我们已经见过一些内置于 JavaScript 的函数,比如 alert 函数和我们自己可以编写的其他函数,比如 hello,但我们也可以包含。
比如在我们的代码中使用变量。我会创建一个新文件,它将被称为 counter.html,counter 将有一些类似于 hello 的代码,所以我现在就复制过来,但我会清除脚本部分,把标题从 hello 改为 counter,现在我会去掉。
我会保留这个按钮,但它不再说点击这里,而是会说计数。我想创建一个程序,简单地从零开始计数到一、二、三、四等等。为了做到这一点,我们需要某种重复计数的方法:零、一、二。
三、四、五,我需要在我的程序中有某种变量,跟踪数据,比如我当前计数到的数字。为了做到这一点,在JavaScript中我可以说,let counter等于0,这就是在JavaScript中我。
定义一个新变量时,我首先说let counter,意思是让有一个新的变量叫做counter,我将初始设置计数器的值为数字0。现在当我点击按钮时,不再运行hello函数,而是运行count函数。
还不存在,但我现在会写一个,我会定义一个名为count的函数,这个count函数首先会增加计数器的值。我可以用几种方式做到这一点,其中一种是说counter等于counter加1,表示继续重置计数器的值为。
如果计数器加1,并且还有几种简写方式。我可以等价地说counter加等于1,表示对计数器加1,或者在添加1的情况下,JavaScript与像C这样的语言类似,如果你以前见过它们,支持这样的写法,counter加加。
意思是取计数器的值,然后将其增加1,所以我会将1加到计数器的值上。接着我会显示一个警报,内容就是计数器当前的值。
然后显示一个警报,告诉我计数器的内容。因此现在如果我继续访问counter.html而不是hello.html,我会看到仍然有一个按钮,上面写着“计数”,如果我点击这个按钮,我会得到一个警报,这次显示的是1,我们已经增加了计数器的值。
从0到1,警报现在显示1,我可以按确定,如果我再按一次“计数”,计数现在变成2,我按确定,再按一次“计数”,变成3,每次我点击“计数”,它都会增加我JavaScript网页中变量counter的值。
然后它会显示一个警报,里面包含那个变量的值。因此,现在使用警报,我们能够在函数内部操作变量的值,并显示显示这些变量的内容的警报。但是最终,当用户与页面交互时,它将会。
如果我们与用户交互的唯一方式是通过显示这些警报,那将非常烦人,相当于打印一些内容,只不过不是打印到终端,而是通过在屏幕上出现的警报打印出来。更有趣且更强大、更加。
在网页上下文中更有用的是,如果我们可以以编程方式更新。网站,改变用户实际看到的网页的某些内容。更改页面上发生的内容,结果是 JavaScript 将。使我们能够做到这一点,因为 JavaScript 允许我们。
操作 DOM,即文档对象模型,表示该页面上所有的。元素,所以为此,让我们回到 hello HTML,这又是这个 paté,网页,只是说你好,并给我一个按钮,如果我点击那个,按钮,它会显示一个警告。
说你好,世界现在我想要做的是,而不是让 hello 函数。显示一个警告,我想让它,操作页面上的某些东西。我可能想要操作的是什么呢,页面的主体中,我这里有。这个标题,这个只是说,您好,例如,它在一个。
h1 元素,我可能想要做的是实际更改,那个元素我该怎么做呢,结果是,在 JavaScript 中,我们可以,访问一个叫 document dot。query selector 的函数,document query,selector 将要做的是,它将。给我们提供能力,浏览一个 HTML 页面,提取出一个元素。
在页面上,这样我们就可以使用 JavaScript 代码操作这个 HTML 元素。如果我想选择一个 h1 元素,我可以说 document query。selector,然后作为参数,输入到 document 的 query selector。我将继续说每个一个意思,遍历页面,找到我一个 h1。
元素,query selector 只会返回一个元素,因此如果有多个,它将返回第一个,找到的东西,但在这里我们只有一个。h1 元素,所以没问题,我要说在这个文档中查找,h1 元素,当你找到时,我想要,操作它,我想要的方式是。
操作它的方法是通过说点 innerhtml,等于比如说再见。因此,好吧,这里发生了什么呢,现在,最初当我们第一次加载。页面时,我们有一个 h1,一个大的标题在。
顶部只是说了你好,现在,当这个 hello 函数被调用时。它是在点击这个按钮时被调用的,因为它有一个 on-click 属性。这个属性等于 hello 调用 hello 函数,hello 函数将做的是,它会说,document query selector h1 找到我。
h1 元素将返回这个元素,右边是这个 HTML 元素的 JavaScript 表示。它只是一个 h1,其内部的 HTML 说你好。如果我想更改那个 HTML,我可以通过,修改 inner HTML。属性来做到这一点,更新任何东西的属性。
在 JavaScript 中,通常会使用 this
,点表示法来访问某个特定对象的属性,因此我有这个元素,这个 h1,点 inner HTML 意味着获取该元素并访问其 inner HTML 属性,我想更新它的内部。
HTML 在这种情况下只是单词 goodbye,后面跟着一个感叹号,例如。所以现在我们看到的是,当我们运行这个页面,打开 hello HTML 时,我仍然看到一个 h1,上面写着 hello,仍然看到一个按钮,上面写着 click here,但现在当我点击按钮时,显示 goodbye。
我们已经运行了查找页面上 h1 元素并操纵它的 JavaScript 代码,将它更改为与最初不同的内容,现在每次我点击这里,没有其他事情发生,因为每次我点击这里,它都将找到相同的 h1,并将其 HTML 更新,从 hello 更改为 goodbye。
所以也许我真正想要的是切换回和切换去的能力,而不是每次点击按钮时只是将它从一种状态改为另一种状态,我希望它交替回去,有很多方法可以想象。
这可以通过利用条件来实现,类似于像 Python 这样的语言具有条件 if 和 else。JavaScript 也有 if、else if 和 else,允许我们描述条件,以便我们只能运行特定的代码块,当某个布尔表达式为真时。
在某些代码块中,当特定的布尔表达式为真时,比如说,好的,我们在这个 hello 函数中可以问一个问题,如果 document query selector H1 的 inner HTML 等于 hello,那就继续设置它为 goodbye,否则设置为 hello。
然后继续更新这个 h1 元素的 inner HTML,继续设置它为。
这个 hello 函数在做什么?现在它更复杂了,它有一个条件,我说 if,关键字 if 后面跟着我想要检查的条件,我想看看这个条件是否为真,我在检查的是让我运行 document query。
选择器 h1 通过页面查找 h1 标签并为我获取该元素,如果我查看该 inner HTML,如果它等于 hello,那么我想做某件事,而这个三重等号是 JavaScript 检查严格相等的方式,以确保这两个值是相等的,并且类型也是相同的。
它们的类型是相同的,如果这个是一个字符串,这也必须是一个字符串,结果表明在 JavaScript 中,还有一种较弱的检查相等的方法,只使用两个等号,这会检查值是否相同,但允许在类型上有一些差异。
可能有不同类型,但只要它们的基本值相同,双等号通常会返回 true。通常如果可以,你会想使用这个三重等号,严格相等,确保类型和数值都相同,而这个三重。
等号会检查这两件事。因此,如果我找到 h1 元素并且它的内部 HTML 是“你好”,那么请继续找到 h1 元素并更新它的内部 HTML,将其设置为“再见”,例如,否则继续找到同样的 h1 元素,更新内部 HTML 设置为。
你好,再次就像在函数中一样,我们使用这些花括号来包裹函数的主体,所有位于函数内部的代码行,JavaScript 在条件内部做了同样的事情,当我们想要表示这在 if 条件的主体内部时,我可以使用花括号。
花括号来表示这一行代码和那一行代码位于 if 表达式内部或位于 else 表达式内部。例如,让我们现在试一下,我可以打开 hello.dot HTML,刷新一下,当前显示“你好”和一个按钮,上面写着“点击这里”,现在,当我点击这里时,“你好”会变成。
“再见”,而当我再次点击这里时,“再见”又会变回“你好”,每次我点击这个按钮,它将会在“你好”和“再见”之间交替。因为我们要么在 if 中被捕获。
表达式,或者我们被捕获在 else 表达式中。现在有几个地方你可能会看到这一点,并注意到这段代码的效率可能没有那么高。请记住,每次我们运行 document.querySelector 时,都会说“去查找一个特定的 HTML”。
对我而言,它会在该页面上查找 h1 元素,结果现在我们有三次对 querySelector 的调用,尽管在任何给定的函数实例中,只有两个会运行。但我们 document.querySelector,然后在 if 表达式内部再次调用它。
可以通过提取出来来改善这个页面的设计,先查找一次 h1 元素,然后使用我们找到的元素进行操作和检查。为此,我们可以以变量存储一个元素,方式与变量存储数字相同。
计数器或像“你好,世界”这样的字符串,它还可以存储其他值,比如从 document.querySelector 获取的 HTML 元素。因此,我可以说一些类似于 let heading,heading 只是变量的名称,等于 document.querySelector h1,例如,找到 h1 元素并保存它。
在一个名为heading的变量内,现在不再每次都使用文档查询选择器。我可以简单地说,如果heading的inner HTML是你好,再见。否则,我将heading的inner HTML设置为你好,因此我提高了程序的效率,也减少了我必须写的代码字符数。
我的代码行现在短得多,更容易阅读,我们会认为这是设计上的改进,结果发现这里还有一个其他的改进,我们可以像让某物等于其他东西一样定义变量,但事实是JavaScript给了我们几个。
定义变量的方法,如果我们要创建一个值永远不会改变的变量,我们就不会将变量名重新分配给其他东西,那么我们可以通过将其称为常量来强制它永远不会改变,所以常量heading等于文档查询。
选择器h1意味着我将创建一个。
一个名为heading的变量,将其设置为文档查询选择器h1的结果,之后我将不再改变heading的值,我不会再有一行代码说heading等于其他东西,因为它是常量,JavaScript将强制这个变量不应该。
改变它,如果我真的尝试改变它,javascript会给我一个错误。这可以帮助防止潜在的意外行为。如果你知道一个变量是永远不会改变的,通常最好把它标记为一个常量,这样你的代码就知道它永远不会有变化。
之后改变的值,所以这将以相同的方式表现,它说你好,但我可以来回切换,将其改为再见并。
再次将其改回你好,因此现在利用这种能力。
哈佛 CS50-WEB | 基于 Python / JavaScript 的 Web 编程 (2020·完整版) - P16:L5- JavaScript 编程全解 2 (DOM 操作) - ShowMeAI - BV1gL411x7NY
我们的计数程序实际上正在改进,目前计数器的状态。
计数程序显示一个警报,显示“1”,我计数时显示一个警报,显示“2”。我可能可以做得更好,而不是显示警报,实际上以某种方式操作 DOM。我希望将这个 h1 大标题放在顶部,而不是从。
开始时说“你好”,比如说初始值为 0,现在当我增加计数器的值时,而不是显示一个仅告诉我计数器值的警报,我将改为使用 document.querySelector h1.innerHTML。
等于计数器,我在说找到 h1 元素并更新 innerhtml。
将其等于计数器变量的值,这样如果我刷新此页面,h1 的初始值就是 0。这是初始值,但每次我点击计数,我们将更新该 h1 元素的内容,这次设置为 1、2、3、4 等等,每次我。
点击计数按钮,它会增加变量的值,并操作 DOM。
进行更改以产生我想在这个实际页面上看到的效果,我们可以开始添加额外的逻辑,也许我确实偶尔想要一个警报,但我不想每次都出现警报。我可以添加一个条件,比如说如果我只想显示一个警报。
每次我计数到 10 的倍数,比如 10、20、30、40、50 等等,我可以添加一个条件,判断如果计数器对 10 取模,取模运算只会在你除以 10 时得到余数。如果将计数器除以 10,如果余数为 0,当我除以 10 时,这意味着计数器是一个。
是 10 的倍数,所以我现在可以显示一个警报,显示像“计数器现在是 10”或“计数器现在是 20”这样的信息。为了做到这一点,我真正想做的是在 JavaScript 中将变量插入字符串中。
在 Python 中,我们会通过在字符串前添加 f4 来创建格式化字符串,然后我会说类似“计数现在是”之类的话,然后用花括号来打印出计数器的值,所以这个 f4 格式化字符串后面跟着。
在 Python 中,字符串是我们会使用这种方式的。事实证明,JavaScript 也做同样的事情,只是语法略有不同。它不使用 F,而是使用常规的引号,无论是单引号还是双引号,JavaScript 使用反引号。
通常位于标准美式键盘上的 tab 键上方。这将创建 JavaScript 所称的模板字面量,在这里我可以替换模板中的变量值。然后为了实际进行替换,Python 使用双。
大括号来表示在这里插入 counter 的值,JavaScript 做了类似的事情,它也使用双大括号,但前面需要有一个美元符号,美元符号和双大括号,我们可以直接插入一个变量的值。所以这样。
是一个模板字面量,每次我们到达十的倍数时,我们将显示一个警报,说明计数现在是,然后这个美元符号大括号意味着实际上插入任意。
counter 的值正好在这里,所以现在如果我继续刷新。counter 从零开始,我可以计数 3,4 5 6 7 8 9,但当我到达 10 时,我就会收到一个警报,说计数现在是。
10 然后我看到结果在页面上更新,因此这个模板字面量。可以让我们结合变量和字符串,以生成新的字符串。它们能够代表内部的数据,因此现在我们可以开始做哪些改进?我们可以做哪些更改来改进这个。
设计它的方式,特别是当我们的程序变得更复杂一些,更复杂一些时,我们通常不想将 JavaScript 代码与 HTML 的内容交杂在一起。这里我们有按钮 on click 等于 count,这是这个函数的名称。
尤其是当我们的页面变得更复杂时,如果我们不断维护一点 JavaScript 代码,比如在 HTML 中调用一个函数,这会变得有点烦人。稍后我们会看到,还有其他范式实际上鼓励。
这种类型的做法,但现在如果我们真的想将所有 JavaScript 相关的代码与 HTML 的一般结构分开,它可以开始设计得稍微糟糕一些。因此,我们可以开始做到这一点,我可以在 JavaScript 中添加一个事件监听器,我可以说。
类似于 document dot query,selector button 然后我可以说 dot。on click 等于 count,那么这里发生了什么,我可以这样做而不是这个。on click 等于 count 我现在所说的是 document query selector button。找到页面上的按钮,结果发现这里只有一个按钮,所以。
不过,如果有更多,我可能需要稍微具体一些。但一旦我找到了那个按钮,我将访问它的 on click 属性。并将其设置为 count,count 是函数的名称,count 本身就是一个函数,所以我这里所说的是我会。
我希望将按钮的 onclick 属性的值设置为计数,计数是当按钮被点击时应运行的函数。注意,我实际上并没有调用该函数,而不是 "count" 然后加上括号,这样就不会运行计数函数并获取其返回值。
值并将其用作值。
onclick 我只是将 onclick 设置为计数函数本身,而不是真正调用该函数,这样做的目的是在按钮被点击时,仅在那时才应该运行这个计数函数,而我们不会运行计数。
直到那个按钮被点击,函数才会被执行。在 JavaScript 中,函数可以被视为自己的值,就像你可以将一个变量设置为字符串一样,甚至可以将变量设置为整数,就像你可以将变量设置为 HTML 元素一样,例如 document 的结果。
使用 querySelector 你还可以将变量设置为函数,例如 count,并将其作为值传递,就像你可以使用任何其他值一样。这是我们通常称之为函数式编程的范例,在那里我们将函数视为自己的值,可以重新分配。
我们可以操作的事物就像我们可以拥有任何其他值一样,所以现在我可以尝试通过刷新页面来运行这个程序。我将其刷新,初始值为零,我按下计数,好的,似乎什么也没有发生,仍然是零,我希望它能计数,但它。
这似乎不工作,那为什么呢?每当你在 JavaScript 中遇到问题而未得到想要的行为时,查看 JavaScript 控制台通常是有帮助的,JavaScript 控制台相当于终端窗口,当你运行你的代码时。
Django 应用程序在 Python 中会显示任何错误消息,JavaScript 控制台也会显示任何 JavaScript 日志信息和错误消息。在 Chrome 中,我可以通过先点击检查,然后打开控制台标签来查看,一切正常。
这里似乎有某种错误,是一个未捕获的类型错误,表示我们无法在未遇到的情况下设置属性,HTML 第 18 行会告诉你错误来自哪里,确实是来自计数器 HTML 的第 18 行。问题似乎是我正在尝试访问 onclick 属性。
"no" 在 JavaScript 中表示“没有”,是表示不存在某个对象的方式,所以不知为何我正在尝试设置 "no" 的 onclick 属性。让我们看看第 18 行发生了什么,看看能否弄清楚那里发生了什么。好的,第 18 行是我说的 document.querySelector button。
设置它的onclick属性等于计数,现在看起来这里的问题是什么呢?错误信息是,我试图修改onclick属性为null。那么这里的onclick属性是什么呢?那么为什么这个document query selector按钮会返回或给我输出null呢?
结果发现如果document.querySelector无法找到某个元素,它将返回null。如果我试图查找一个按钮,但无法找到这个按钮,这似乎有点问题,我希望我的JavaScript代码能够在运行querySelector时找到按钮。
结果发现这是浏览器工作方式的一点怪癖,但如果浏览器从上到下运行我们的代码,从第一行开始不断读取,它将在第18行到达我说document.querySelector按钮onclick等于计数的地方,并试图找到一个。
按钮在第18行,但是按钮在我的HTML页面中直到页面更下方才显示,因此在这一点上,当我们到达这行JavaScript时,JavaScript正在寻找按钮,但文档的主体尚未加载完成,DOM的内容尚未加载。
结果是我们无法找到这个按钮。那么我们如何解决这个问题呢?我们如何才能让它能够询问按钮并真正获取到按钮呢?结果发现我们可以使用几种策略。一种是将脚本标签移动到底部。
这是文档的一部分,所以在我们已经定义了说“计数”的按钮之后,我们有了脚本标签,然后说好吧,继续,现在找到按钮,结果发现另一种方式,也许是更常见的方式,是再添加另一个事件监听器,我们将要。
将事件监听器添加到整个文档上,而不是按钮,其中document是一个内置于我们在网页上访问的JavaScript变量,指的是整个网页文档,我已经使用document.querySelector来查找整个网页文档,试图找到一个。
我正在尝试查找一个h1标签,例如,但我也可以将事件监听器添加到文档本身。为了做到这一点,我可以说document.addEventListener,当我调用addEventListener时,我可以在任何HTML元素上执行此操作,不仅仅是文档,我可以在一个按钮上运行这个函数。
在一个h1元素或任何其他HTML元素上,添加事件监听器通常需要两个参数,第一个是我想要监听的事件。这个事件可能是点击,当我点击文档时,它可能是类似于我滚动页面时的某个事件,但在这个事件上,我。
我将要监听的是一个特别的事件,称为DOM内容加载。DOM内容加载事件是在文档对象模型(DOM)页面结构完成加载时触发的事件,当页面上的所有元素都加载完成时,DOM内容就被加载了。
如果我为它附加一个事件监听器,你知道运行我想运行的代码,只有在DOM完全加载后,即页面上的所有内容都加载完成后,事件监听器的第二个参数是事件实际发生时应该运行的函数。
加载确实发生时,我可以传入一个函数的名字,如果我有想传入的函数名字,但另外JavaScript允许我直接在addEventListener的参数中写一个函数。我可以直接说function,然后是一对括号,表示这个函数不接收任何输入,然后在花括号中,我可以包括这个函数的主体。
这是一个有点棘手的语法,如果你以前没见过的话。但大体上可以理解为addEventListener接收两个参数。
第一个参数是事件,第二个是函数,这里第一个是事件DOM内容加载,第二个参数是一个函数,我声明的函数和之前一样,只是使用function,我没有给这个函数命名,因为严格来说它不需要名字,我也不会再引用它。
通过名称调用函数,这就是我们可能称之为匿名函数的函数,一个没有名字的函数,但我仍然将它作为参数传递给addEventListener函数,因为我想在DOM加载完成后运行函数内的代码。
函数的内容是DOM完成加载时应该运行的代码,然后在最后我再次使用这个结束括号,这个结束括号与这里的括号对齐,这包围了addEventListener的所有参数,其中第一个是DOM内容加载,第二个是整个内容。
可能跨越多行的函数,所以你会在JavaScript中看到这样的语法,但现在我想做的是为按钮添加事件监听器。我可以直接这样做,想要的只是替代说点点击等于计数,你可以使用相同的addEventListener语法。
eventlistener点击,然后计数,意味着当点击事件发生时,去执行计数函数,但你也可以简化这段代码,直接说点点击等于计数,这样也可以。所以现在我们所说的是,等到DOM加载完成,等到所有的内容都加载完成。
页面上的内容加载完成后,接着运行这个函数,这个函数将为这个按钮添加事件处理程序,这将有效,因为现在我们能够找到按钮,因为DOM的所有内容都已加载,所以现在如果我回去刷新。
在counter.html中,你会注意到JavaScript错误消失了,我似乎不再有那个错误,现在如果我按计数,我们能够看到计数像之前一样递增,因此这相较于我之前的情况是一个改进。
但是我代码的其余部分也如此,比如在CSS的情况下,我们能够将原本位于页面头部的CSS移动到单独的文件中,你也可以对JavaScript做同样的事情,这在有多个团队成员在不同文件上工作时是有帮助的。
有人在处理HTML,另一个人在处理JavaScript,如果你预计其中一个会比另一个更频繁地更改,这会很有帮助,因此你可能不需要那么频繁地加载另一个,所以将HTML代码与JavaScript分开会更有价值。
通过将我们的JavaScript移动到一个单独的文件中,为此我可以创建一个新文件,称为counter.js,它将包含我counter.html文件的所有JavaScript,因此为此我可以说,让我们继续复制所有的JavaScript代码并剪切它。
将这个页面中的内容复制到counter.js中,移除一些缩进,现在我有一个名为counter.js的文件,仅包含我想在counter.html页面上运行的所有JavaScript,而不是在这些script标签之间包含实际的JavaScript,我可以说,script src。
等于counter.js,例如如果我继续引用counter.js并使用。
在页面的头部有JavaScript,这将会工作。
以完全相同的方式,我仍然可以计数到我想要的任何数,我仍然会在计数达到十的倍数时收到警报,但我的HTML现在更简单,它只是主体,只是h1和按钮,然后我的所有JavaScript现在都位于一个单独的文件中,这让我能够做到这一点。
这让我能够将HTML代码和JavaScript代码彼此分开,这在几个方面是有价值的,其中之一是如果我有在多个不同HTML页面中使用的常见JavaScript代码,多个HTML页面都可以包含相同的JavaScript源,而不需要重复自己。
我可以在多个不同的页面中使用相同的 JavaScript,只需在所有页面中使用相同的 JavaScript。这样也会很有帮助,因为我们稍后会看看一些由其他人编写的 JavaScript 库,我们可以直接在自己的代码中包含其他人的 JavaScript。
通过在页面顶部添加一个指定特定来源的脚本标签,可以让网页实现功能。你可能已经与 Bootstrap 进行过交互,它有自己的 JavaScript 代码。你可以通过在 HTML 页面的顶部包含一个脚本标签来包含 Bootstrap 的 JavaScript,从而实现这一点。
在我们的页面中也使用 JavaScript。那么,现在我们有什么其他可以做的呢?既然我们有能力获取 DOM 元素并实际操作它们的内容。我们可以开始让我们的页面变得更加互动,实际上响应用户在页面上的操作。
用户可能正在输入一些内容,或者填写表单。因此,让我们尝试一个例子,假设用户正在填写表单,我们希望我们的代码以某种方式响应他们输入的内容。我将返回到 hello.html,现在在里面。
在页面的主体部分,我在顶部说了“您好”。我将用一个表单代替按钮,这个 HTML 表单看起来像我们之前见过的 HTML 表单。我有一个输入字段,会添加自动聚焦属性,让用户能够立刻开始输入。
我将给这个输入字段一个 ID,比如名称,然后设置一个占位符,名称的首字母大写,类型为文本。所以,我在这里做了什么呢?我创建了一个输入字段,用户可以在其中输入一些文本,占位符是用户看到的东西,最初将仅为“名称”。
我告诉他们应该在这里输入他们的姓名,并且给这个输入字段赋了一个 ID,这是一个唯一标识符,以便我稍后能够引用并找到这个特定的输入字段。然后,我有输入类型为提交,这样我也可以提交这个表单。所以,如果我加载这个页面,加载 hello.html。
这是我所看到的,您好,一个可以输入我姓名的字段,占位符“名称”显示在那儿,然后是一个按钮,我可以提交这个表单。现在,我想在我的 JavaScript 中,替换掉这个“您好”函数,我要做的是,首先运行一些 JavaScript。
DOM 加载完成后,我会使用之前的那一行代码,你会看到它很多次。我们将说 document.addEventListener DOMContentLoaded,然后这个函数意味着当 DOM 加载完成时运行这段代码。我想运行的代码是,当我提交表单时,我希望。
当我提交表单时,可能我想显示一个警告,比如如果我输入 Brian,它会说 hello Brian,或者如果我输入 David,它会说 hello David,例如,那么我该怎么做呢?首先的问题是如何获取这个表单。
在 HTML 页面上,通常我们会使用 document dot query selector 来获取一个表单元素,并且页面上只有一个表单,所以我不必担心歧义,我只是说获取那个表单,然后我可以说 dot on submit,当你提交表单时,应该运行什么代码。
如果我有一个函数的名字,比如 f,我可以简单地说在表单提交时运行函数 f,或者,像以前一样,除了提供函数名,我还可以直接提供函数本身。我可以说函数,然后在这些大括号之间精确地指定。
当表单提交时,应该运行什么代码,通过提供这个匿名函数,而不是使用这个匿名函数作为这个表单的 on submit 属性的值。因此,现在我想做的是以某种方式访问用户在输入字段中输入的内容,无论用户的名字是什么。
我可以通过 document dot query selector input 来获取输入字段,这次是可行的,但我们需要开始稍微小心一点,因为在这个页面上有多个不同的输入元素,这里有一个输入元素用于输入姓名,还有第二个输入元素用于其他信息。
给我一个按钮,让我可以提交这个特定的 HTML 表单,所以我可能想做得更具体,结果是。
在 query selector 内部,我可以使用所有在 CSS 中标准的选择特定元素的方法,所以在 CSS 中,如果你还记得,我们可以运行 CSS 并简单地说样式所有的 h1,或者我们可以说样式所有的。
关于这个特定 ID 或这个特定类的内容,document 的 query selector 以相同的方式工作,我可以说 document query selector 并传入一个标签,比如说获取我 h1 元素,或者获取我按钮,或者获取我表单,但如果有多个 h1 元素或多个按钮的话。
如果一个元素有一个 ID,那么我可以说 document query selector,然后在引号中写上井号,再加上 ID 的名字来获取具有那个特定 ID 的元素,再次使用与 CSS 相同的语法,如果我想只应用一组特定的样式。
对于只有一个 ID 的元素,同样,对于两个元素,我可以说 document query selector,然后使用点跟着类名,如果有特定的元素类,我想仅获取其中一个,并且说让我获取具有这个特定类的元素,以便对其进行操作。
所以我们可以在 CSS 中使用的相同类型的语法来引用和获取特定的 HTML 元素,我们可以在这里用 document query 做同样的事情,因此去尝试基于标签名、ID 或类名获取特定元素,这就是在 HTML 中的原因。
对于我的输入框,我给输入框一个 ID 为 name,我想要某种方式来唯一引用它,我可以通过 ID 为 name 的元素唯一引用它,那就是我想在 JavaScript 代码中提取的元素。
一旦我有了那个 HTML 元素,我想要的是用户实际输入的内容,结果是,如果你在 HTML 中有一个输入字段,我可以通过访问它的值属性获取用户输入的内容。
所以我可以说点值,我将把它保存在一个变量中,可以说像 let name 等于用户输入的内容,但如果我不打算在这个函数内部重新赋值给名称,我只会获取名称一次。
我不打算在函数内部改变它,所以我可以使用常量,这样更好,我有一个名为名称的常量变量,它等于 document query,选择器获取 ID 为名称的元素并获取其值,现在我可以显示一个警报。
例如在反引号中使用 hello comma,然后使用美元符号的花括号语法,我可以插入名称,后面跟着感叹号。因此,我从表单中提取了名称,获取用户输入字段的值,并显示一个警报。
我要向那个人打招呼!
例如,现在我刷新页面,输入我的名字,按提交,我得到一个警报,显示 hello Brian,按确定,我可以再试一次,输入类似 David 的内容,按提交,现在页面显示 hello David。
所以这里我们能够结合事件监听器、函数和查询选择器,既可以读取页面的信息以获取特定 HTML 元素,又可以访问用户输入的内容,点值属性获取用户输入的内容。
这是输入字段中输入的内容,我们能够将其与事件监听器和警报结合,实际上对用户提交表单或页面内容加载完成时做出动态响应,从而产生一些有趣的效果。
不仅仅是改变元素内的 HTML,我们也可以改变 CSS,改变特定元素的样式属性。那么让我们先看一个例子,我将创建一个新的文件,叫做 colors.html,然后在 colors 中我将包含这。
这是我们通常从一个标准的 HTML 模板代码开始的,包含一个头部和一个主体部分,在这个页面的主体中,我将展示一个标题,仅仅写着你好,举个例子,也许我会给它一个 ID,以便我能通过名称引用它,也许它的 ID 是 hello,然后我将。
三个按钮,我会有一个叫做红色的按钮,一个叫做蓝色的按钮,还有一个。
例如有一个叫做绿色的按钮,现在如果我打开 colors.html,看到的是一个大标题,写着你好,然后我看到三个按钮:红色、蓝色和绿色,但当然现在。
这些按钮实际上什么也不做,我该如何让按钮做点什么呢?这就是 JavaScript 将派上用场的地方,我将把一个脚本标签添加到页面的顶部,我只希望在 DOM 加载完成时运行这个 JavaScript。因此,我们将使用与之前相同的语法:document.addEventListener。
DOM 内容加载完成后,运行这个函数,表示在这两个花括号之间的所有代码将在页面加载完成后运行,我真正想做的是获取这三个按钮,并说当你点击每一个时,做不同的事情,比如改变。
特定 HTML 元素的颜色,为了做到这一点,我需要某种方式唯一引用这些按钮。为此,我将给它们所有的 ID。这个按钮将有一个 ID 为 red,这个按钮将有一个 ID 为 blue,这个按钮将有一个 ID 为 green,唯一的名称我可以给按钮。
在 HTML 中,以便在 JavaScript 中我能够稍后引用它们。那么我现在在我的 JavaScript 代码中应该包含什么呢?好吧,让我说 document.querySelector 选择器 hash read,获取 ID 为 read 的元素,当你被点击时,点击后运行这个函数,这个函数应该做什么呢?我想。
将这个 h1 元素的颜色改为红色,我想将字体颜色改为红色,并且我会在 JavaScript 中给自己留一个注释,方法是使用这两个斜杠。两个斜杠表示页面上之后的所有内容都将是一个。
注释浏览器会忽略它,但对程序员和阅读你代码的人来说,它可以是有用的,能够看到你在页面上描述的内容。现在我想做的是 document.querySelector hello,获取 ID 为 hello 的 HTML 元素,然后继续。
修改它的样式属性,现在在这个样式对象中,我可以修改任何CSS样式属性,例如颜色,所以我可以更新颜色属性并将其设置为红色。所以现在我在说,当你点击红色按钮时,运行这个函数,而这个函数。
我应该找到hello元素,更新它的样式,具体要更新哪个部分的样式呢?我们应该将颜色更新为红色,我会为其他两个按钮做同样的事情,代码非常相似,我会在这里复制粘贴。
点击蓝色按钮时,颜色应该变为蓝色;再做一次,当你点击绿色按钮时,字体颜色应该变为绿色!
将颜色改为绿色,所以当我刷新时,当前的颜色是黑色,标准的HTML字体颜色。我点击红色,hello变为红色;我点击蓝色,变为蓝色;我点击绿色,变为绿色!
绿色取决于我点击哪个按钮,这会触发某个事件监听器。点击按钮时,会运行这个函数,而这个函数的作用是抓取这个ID为hello的h1元素,修改它的样式,将颜色属性更新为红色或其他。
蓝色或绿色,这显示我们可以修改样式,而不仅仅是修改页面内容。但是,正如你看到我编写代码时,应该引起你注意的是,这种设计可能并不最佳。一般来说,每当你发现自己反复编写相同的代码时。
而且,特别是如果你在复制粘贴时,这通常是一个坏迹象,通常表明有更好的方法来修改或实现我试图创建的行为,事实证明确实存在,且有多种方式可以做到这一点。
我可能希望将这三个事件监听器合并为一个函数,来处理将颜色更改为按钮上所显示的颜色,但一个问题是,如果我将相同的事件监听器附加到三个按钮上,就不会。
为了明确,当我点击按钮时,按钮是如何知道我们应该将文本的颜色更改为什么的?为此,我们可以为特定的HTML元素添加一些特殊的属性,称为数据属性,数据属性是我表示希望。
将一些数据与这个特定的 h2 HTML 元素关联,我可以说数据 - 颜色等于红色,数据 - 颜色等于蓝色,数据 - 颜色等于绿色。数据属性总是以数据开头,后面跟一个短横线,然后我可以为想要存储的信息指定任何名称。
存储关于 HTML 元素的信息,我想存储的是当按钮被点击时,你应该将文本更改为什么颜色。因此我们现在要做的事情是,如果我有权限访问这个元素,这个按钮,我可以访问。
它的数据颜色属性用来判断我们应该将文本更改为红色、蓝色或绿色,通过为这些 HTML 元素添加这些数据属性。那么我现在想要的是某种方式来获取所有这些按钮。文档查询选择器正如你所记得的,只获取一个元素,它将只会。
为我获取一个单一元素,它会获取找到的第一个元素。如果我想获取多个元素,我可以做的是像 document.query selector all。查询选择器 all 将返回与查询选择器相同的东西,但。
匹配查询选择器的单个元素。查询选择器 all 将返回一个数组,包含所有与我特定查询匹配的元素。所以如果我想选择的不仅仅是找到的第一个按钮,而是所有找到的按钮,我可以在这里说查询选择器 all for button,这样就会。
给我返回一个 JavaScript 数组,等同于一个表示所有按钮的列表,我们实际上可以通过查看 JavaScript 控制台来测试这是什么样子。如果我继续刷新这个页面,我可以打开检查器,进入 JavaScript 控制台,就像。
你可以在这里看到错误信息,你也可以在这里实际编写 JavaScript 代码。所以我可以说类似 document query selector button 的东西,好的,当我试图在这个特定页面上选择按钮时,会发生什么,我得到的就是好的,我只得到这个第一个按钮。
找到数据 - 颜色等于红色的按钮,这正是我所期望的查询。选择器只会找到一个元素,并且它会找到我找到的第一个元素。同样我可以说,好的,别用查询选择器,我们来用查询选择器 all,我得到的是一个节点列表,你可以把它看作是一种数组或 Python 中的列表。
这是一个包含三个按钮的数组,按钮 0、1 和 2,正如在 Python 中的列表一样,你可以索引数组,而 JavaScript 中的数组也可以被索引。如果我有像常量 Eames 等于 Harry、Ron 和 Hermione 这样的东西,就像一个包含三个名字的数组,我可以说名称方*括号。
括号 0 获取第一个名称,name,方括号 1 获取第二个,举个例子,这样可以给我数组中的每个单独元素,如果我想获取数组的整体长度,我可以使用 names.length 来获取。好的,names 数组的长度恰好是三,所以只是一些。
我们可以访问的附加特性,如果你碰巧有一个东西的数组。结果表明,querySelectorAll 返回给我一个节点列表,这有点像数组,这将很有用,因为当我说 document.querySelectorAll button 时,我是在说获取页面上的所有按钮。
我想做的是有效地遍历所有这些按钮,并对每个返回的按钮列表添加事件处理器,当按钮被点击时,对于每个返回的按钮来说,继续说当你被点击时改变 h1 元素的颜色。
这有很多方法可以做到,但一种方法是使用一个叫 forEach 的特定属性,forEach 是一个接受另一个函数作为参数的函数,想法是我想为数组中的每个元素运行一个函数。
或者在节点列表内部,例如,在这里我可以说对每个按钮继续运行这个函数,这将是一个现在接受输入的函数,它将接收列表中的一个元素作为输入,像一个按钮这样的东西,现在对于每个按钮,我想对那个按钮做什么。
好吧,当按钮被点击时,button.onClick 然后继续运行一个将要文档的函数,querySelector 获取 ID 为 hello 的元素,改变它的样式,在样式中改变它的颜色,我想将它的颜色改为什么呢,我可以访问这个按钮,这个参数。
这个函数是我当前试图添加事件监听器的按钮,为了访问它的数据属性,我可以访问 HTML 元素的一个特殊属性,叫做 dataSet 属性,然后我可以说像 button.dataSet 这样的内容。
颜色以获取数据 - color 属性,所以这里发生了很多事情。让我们继续尝试阅读整个内容,感受一下发生了什么,因为我们有函数嵌套在其他函数中,嵌套在其他函数中。在最顶部,我说添加一个事件。
当文档的 DOM 内容加载时,意味着页面的所有内容都完成加载,继续运行这个函数。这是函数的主体,那么,当页面加载完成时,我想做什么呢,我将使用 document.querySelectorAll 查找所有的。
按钮,如果我想要的话,如果可能会有更多按钮,我可以给这些按钮添加一个类,然后只需查找特定类的东西,查询选择器all仅仅返回所有匹配特定查询的元素,然后我在对每个按钮说,对于这些按钮中的每一个。
我想在每个按钮上运行一个函数,我基本上是在说,如果我有三个返回的按钮,让我在第一个按钮上运行一个函数,然后在第二个按钮上运行同样的函数,接着在第三个按钮上运行同样的函数,这个函数是什么呢?它就是这个函数。
这个函数以按钮为输入,首先会把第一个按钮作为输入,然后是第二个按钮,接着是第三个按钮,这个函数的作用是为按钮添加一个onclick处理程序,它说当按钮被点击时,继续运行这个其他的函数,而这个函数就是。
将在按钮被点击时运行,来说明当按钮被点击时,获取hello元素并将其颜色更改为按钮的数据集颜色,按钮数据集的颜色获取一个按钮,像这里这个HTML元素,并访问它的数据集,所有的数据属性,特别是访问颜色数据。
这个属性在这种情况下等于红色,这就是我们想要设置颜色的值,所以稍微复杂一点,我们之前见过,但现在我们能够把三个不同的事件处理程序减少为一个,接下来这个将会以同样的方式工作,将其改为红色。
蓝色和绿色都是通过使用我们可以访问的数据属性。因此,当对这些事情的工作原理或查询选择器的返回结果感到疑惑时,JavaScript控制台可以是一个非常强大的工具,实际上可以操作事物,你可以运行查询。
你甚至可以修改它们的变量值,比如我回到counter.dot HTML或head,这个计数器在计数0 1 2 3 4。如果我想的话,我可以说像counter等于27这样的话,正如改变计数器的值一样,页面上似乎没有任何变化,我没有说。
更新页面上的任何内容,但现在下次我运行计数时,它将更新计数的值为28,因为我在JavaScript控制台中更新了值,它将增加那个值并在h1元素中显示这个值,因此你可以修改变量,可以运行。
你可以运行文档查询的函数,选择器来找出特定的元素将会返回所有,通过使用javascript控制台,这可以是一个非常强大的工具,尤其是在你开始调试这些程序并试图搞清楚是什么的时候。
可能有什么地方出错,事实证明,我们可以做其他更改,以进一步优化我们的代码,使其更加简洁。最近的JavaScript版本引入了一种特定的函数符号。
函数的箭头符号,我会给你展示,因为它会出现,但通常在引入函数之前,你会看到像按钮这样的东西,然后是箭头、大括号,严格来说,你甚至不需要在箭头周围加上括号。
输入按钮箭头,然后在这些大括号中写一些代码,这意味着这里将是一个接受名为按钮的变量作为输入的函数,并在该函数运行时执行这段特定代码。同样,如果一个函数没有输入,就像上面这个函数,你可以。
使用箭头符号来表达,只需括号、箭头和代码块。这种符号常在JavaScript中看到,如果你看到它,知道这只是创建函数的简写,箭头左侧的内容是函数的输入,而箭头右侧的内容则是输出。
当函数被调用时执行,那么我们可能想要做其他什么更改。为了使colors.html
正常工作,如果我们回顾colors.html
,现在的样子是:我们有一个标题,写着你好,然后为了将颜色改为红色、蓝色或绿色,例如,我们可能会想让用户从中选择一个。
决定用户界面的选项数量,选择使用下拉菜单而不是一堆按钮,例如,JavaScript对此也支持,我们将利用这个机会来探索一些其他的事件处理程序。
所以,例如,我可以把这些全部放在按钮里,使生活更轻松,制作一个下拉菜单,其中选择的默认选项是黑色,我会说黑色,然后添加另一个值为红色的选项。
然后我们说红色,值是我们在JavaScript中尝试提取特定下拉菜单值时会得到的。在选项标签之间,是用户在页面上实际看到的内容,因此我将其大写仅仅是为了他们,选项值为蓝色。
这将是蓝色,选项值为绿色,那将是绿色,因此现在用户看到的是他们看到你好,然后是一个下拉菜单,他们可以从颜色列表中选择,而不是点击按钮来做到这一点。当然,这个选择下拉菜单并不。
目前我不能做任何事情,但我可以修改它,使其现在可以这样做。与选择所有按钮并对其进行操作不同,我们不再有任何按钮,我现在有的是一个选择下拉菜单。
我们看到DOM内容加载作为一个事件,还有另一个事件叫做on change
,适用于像选择下拉菜单这样的内容,当选择下拉菜单中的某项内容发生变化时,当用户选择不同的内容时,我可以运行一些代码,比如我可以运行一个函数,这种情况下是。
将使用文档查询选择器获取hello
,意味着获取hello
的HTML元素,改变它的样式,样式的哪个部分改变了颜色,我想将其改为我想要的下拉菜单的值,但我该如何访问这个特定的下拉菜单呢?在JavaScript中。
正如我们所看到的,在点击某个东西时,我们可以访问一个特殊的值,叫做this
,在JavaScript中有特殊的意义,其含义基于上下文而变化,但在事件处理程序的上下文中,当特定事件发生时被调用的函数,这将是一个特殊的关键字,始终指向接收到事件的对象。
所以接收到事件的是选择下拉菜单,接收到事件的是正在变化的内容,因此这将是一个特殊的关键字,始终指向我所做的不同选择的下拉菜单。如果我想获取该下拉菜单的值。
用户实际选择的内容,我可以直接说this.value
,意味着获取那个下拉菜单,并获取用户选择的值。
这个想法,现在比之前更简洁,我能够实现同样的想法,现在以黑色打招呼,但如果我从下拉菜单中选择将其颜色更改为红色,例如,颜色变为红色,如果我选择蓝色。
当它变成蓝色时,绿色变为绿色,我选择黑色时,它又变回黑色。因此,现在我有能力检测这些其他类型的事件并对此作出响应,而在JavaScript中存在许多不同的事件,比如我们已经看到的点击事件,鼠标悬停事件可以检测。
当你将鼠标悬停在某个特定元素上时,如果你曾经看到某些网站在鼠标移动时以某种方式响应,这可能是如何实现的。按下键和释放键可以响应键盘事件,比如当你按下键时。
当你按下键盘上的某个键时,这就是按键按下(key down),而当你抬起手指时,这就是按键抬起(key up),还有许多其他事件,这里列出了一些,你可以监听这些事件并做出响应。这样,你可以根据用户的操作进行真实的交互。
这样做,现在让我们看看如何使用这些事件的一个示例,以开始创建一个稍微有趣一点的应用程序。我们将继续构建一个待办事项列表应用程序,这次仅使用 JavaScript。我们之前见过待办事项列表,涉及与服务器的通信。
进行请求和响应将构建一个只使用 JavaScript 的待办事项列表。因此,我将创建一个名为 tasks.html 的新文件,并创建一个标题为“任务”的头部部分,以及一个主体部分,在页面主体内部,我会有一个标题,上面写着“任务”,然后在下面我想要一个。
所有任务的无序列表,所以我会有一个无序列表,我将其 ID 设置为任务,以便稍后引用,但最初这里不会有任何内容,而我将在无序列表下面放置一个表单,一个我可以提交新任务的表单。例如,我会给自己。
输入字段也会有一个 ID,这个 ID 将会是新任务的单数形式。我在占位符中输入的内容将是“新任务”,以便用户知道该输入什么,而输入字段的类型将是文本,因此我会有一个输入字段,用户可以在其中输入一些新任务,我还会添加一个输入。
为了确保准确,输入字段的类型是。
提交一个允许用户提交新任务的表单,一旦他们创建了它,所以如果我打开 tasks.html,这就是我看到的。大的标题,下面技术上有一个无序列表,但目前这个无序列表是空的,因此它只是显示为空,然后是一个我可以输入任务的文本字段。
然后是一个提交按钮,让我可以。
提交我的新任务,所以现在我希望一些 JavaScript,在我提交这个表单时,实际上可以做一些事情,因此添加一个脚本标签,我希望这个 JavaScript 在 DOM 内容加载后运行,所以我们继续添加通常的 DOM 内容加载。事件监听器,现在我想在表单提交时运行代码。
说 document query selector form.dot on,submit 等于,然后我想运行一个。函数,我可以再次使用关键字函数,但我可以直接使用一个箭头函数,这样输入会快一些。就说好吧,这就是我想在表单提交时运行的函数。
我想首先弄清楚用户实际上输入了什么,用户输入的内容是通过 document.querySelector(task).value 获取的,获取 ID 为 task 的元素,input 字段的值就是用户输入的内容。
实际上我输入了内容,可以将其保存为像 Const tasks 这样的变量,这是用户输入的内容。如果我对用户输入的内容感到好奇,可以将其打印到 JavaScript 控制台,方法是使用一个名为 console.log 的特殊函数,它会将某些内容记录到控制台。
控制台相当于在 Python 中打印某些内容,它会在 Python 终端中显示。在这里,这将显示在 JavaScript 控制台中,还有一点要补充的是,默认情况下,表单在按下提交按钮时会尝试提交,通常会带你到另一个页面,我们已经见过这种情况。
在 Django 的上下文中,提交表单时它会尝试并提交另一个网页请求。如果我想阻止这种行为,可以在我的表单提交处理程序的最后返回 false,以防止表单提交。
所有操作都在客户端进行。
一切都在浏览器内部进行,所以现在这还不能完全工作,但这是进展。我刷新页面,打开 JavaScript 控制台,看看如果我添加任务会发生什么。
像 foo 这样的内容按下提交后,这将记录到 JavaScript 控制台中,这相当于提供调试信息,让我知道我现在可以访问这个值 foo,并且它还告诉我是哪行代码记录的,任务 HTML 的第 9 行。
记录 foo 的那行代码在调试程序时非常有用,可以查看变量的值,只需使用控制台日志打印出来,就可以弄清楚在任何特定时刻程序发生了什么。但是,我想做的是不使用控制台日志。
我想真正创建一个新的元素,然后将其添加到 HTML 的主体中。那么我该如何做到这一点呢?为了为我的文档创建一个新元素,我可以运行一个名为 document.createElement 的函数,后面跟着我想创建的元素类型,比如无序列表 ul。
无序列表中的每个项目都是一个 Li 列表项,所以我将创建一个 Li 元素,并将其保存为一个我称之为 Li 的变量。因此,我创建了一个新的列表项,这个列表项的内部 HTML 内容将是“任务”。
变量来自上面,正好是用户输入的内容,所以我现在创建了最新的项目,并指定 GML 应该放入列表项中,它应该是用户输入的任何任务,现在我要说 document dot query selector tasks,获取 ID 是 tasks 的元素,这将是这个。
无序列表在这里是无序列表,其 ID 是 tasks,如果我有一个 HTML 元素,我可以在里面添加一个新元素。
通过说 dot append Li,现在要做的事情是获取无序列表,其 ID 是 tasks,获取通过查询选择器得到的元素,一旦我有了那个元素,就将这个值 Li 附加到该元素内部的末尾,这个值正好是这个。
我创建了一个新的元素和一个新的列表项,因此我能够添加一个新的 HTML 元素,这行代码将会说把它添加到 DOM 中,添加到我现在正在构建的无序列表中,所以现在我重新运行它,我看到我输入了类似 foo 的任务,我按下提交,好的,foo 现在显示出来了。
我输入了类似右的内容,留下那条类型的 bar bar 现在显示出来。我曾经输入 Baz Baz 现在显示出来,现在一个小的用户界面烦恼是,每次我提交一个新任务时,这个输入字段保留了以前的值,这。
可能不是我想要的,因为我希望它能清空,我已经提交了任务,没有必要让它保留在那,但这在 JavaScript 中很容易处理,如果我想清空这个输入字段,ID 为 task 的输入字段,那么我只需要说 document dot query selector task 获取我。
这个输入字段将其值更改为等于空字符串,也就是等于无,仅仅是为了清除这个值。
现在它正好在那个输入字段内,如果我刷新页面,输入 foo 按下提交,输入字段就会清空,现在我可以输入类似 bar 的内容,然后输入类似 Baz 的内容继续添加任务,现在一个可能稍微令人烦恼的事情是,如果我不小心按下提交,它。
提交了空字符串作为任务,因此我只得到一个空的项目符号在这里显示,按下提交后,我只得到所有这些空的项目符号,从用户体验的角度来看,如果我不允许用户这样做,不允许他们提交任务如果他们没有输入任何内容,那可能会更好。
针对新的任务字段,我们可以通过修改元素的属性来做到这一点,结果发现 HTML 元素有一个名为 disabled 的属性,可以。
true 或 false 允许我们禁用某些东西,比如按钮。如果我想禁用 submit 按钮,我可能首先想给这个 submit 按钮一个 ID,我会给它一个 ID 为 submit,这样在我的 JavaScript 中我可以引用 submit 按钮,而现在在这个 JavaScript 代码中。
当 DOM 内容加载时,默认情况下,submit 按钮应该是禁用的,就像我第一次加载页面时,我不希望 submit 按钮被启用,因为我希望用户先输入一个任务,然后我再启用这个。submit 按钮,我该如何做到呢?我可以 document query selector 获取。
ID 为 submit 的元素让我获取一下,submit 按钮并且说它的。disabled 属性等于 true,javascript 有布尔值 true 和。
false,我将 disabled 值设为 true 来禁用 submit 按钮。现在如果我刷新页面,我可以输入一个新任务,但 submit 按钮是禁用的。它不做任何事情,现在显然我不想保持这种状态,我希望它是这样的,当我开始输入内容时,submit 按钮停止被。
disabled 将从 true 设置为 false,而我真正想要的是。
所要做的是监听我在键盘上按下的键,因此我可以通过添加另一个事件监听器,document query selector 来做到。我要将事件处理程序添加到什么上呢?我想在我输入内容到这个。
输入框,而这个输入框的 ID 是 tasks,所以让我去获取。ID 为 tasks 的元素,并添加一个 on key up 处理程序。key up 事件是当我抬起手指时,运行这个函数,而这个函数应该做什么呢?它将会说。
document query selector submit 设置 disabled 属性等于 false。所以现在我们所做的是,默认情况下,当我第一次加载页面时。
页面获取 submit 按钮并禁用,设置它的 disabled 属性等于 true。那么每当我按下一个键,手指离开键盘时,意味着在 key up 上是。触发事件运行这个,函数,而这个函数将做的是获取那个同样的 submit 按钮,并设置它的 disabled 属性等于。
false,所以现在不是禁用的,而是启用的。如果我回到页面,去这里,我现在输入内容,默认情况下 submit 按钮是禁用的。但一旦我开始输入内容,submit 按钮现在就变得活跃了,我实际上可以点击它。好的,这并不好,因为我点击了它。
但 submit 按钮仍然是启用的,所以我可能想做的是,好的。在我提交表单之后。
我添加了一个新任务,让我们回去禁用提交按钮,所以在我提交表单后,添加新任务到我的任务列表后,清空输入框的值,让我把提交按钮的禁用属性设置为true。
现在,即使在我提交表单后,提交按钮仍然会被禁用。我现在输入foo,提交是激活的,我按下它,但提交按钮又回到不活动状态。事实证明,即使现在仍然有一点小错误,如果我回到这里,输入。
类似于栏的东西,但我按了退格,返回到像什么都没有的状态,提交按钮仍然是活动的,所以从技术上讲,我仍然可以提交一个没有内容的任务。
里面的内容,所以我可能想有某种方式来防止这种情况,这只是一种我们现在可以访问的额外逻辑。JavaScript有条件,有循环,有函数,所以如果我想做一些条件检查,我可以说如果文档查询。
选择器任务点值点长度大于0,意味着在任务字段中实际上输入了一些内容,那么就继续设置禁用为false。继续设置禁用为true,所以现在我们检查用户输入的长度是否大于零,他们实际上输入了一些内容。
然后,是的,给他们访问提交按钮,但否则不要给。
给他们访问那个按钮,现在我刷新页面,默认是禁用的。我输入了一些内容后,它被启用。我删除,删除,删除,然后它又回到禁用状态,所以JavaScript让我们真的能让页面变得更加互动,立即根据用户的互动进行响应。
随着用户开始输入内容,开始删除内容,按下按钮,我们能够让页面响应,要么通过向DOM添加内容,实际向HTML页面添加部分,或改变样式,改变元素的特定属性,这就是。
JavaScript的强大之处就在于不再仅仅让自己能做这些事情。到目前为止,我们只能在用户做某事时才让事件发生。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P17:L5- JavaScript编程全解 3 (逻辑存储,API) - ShowMeAI - BV1gL411x7NY
用户按下一个键,比如说,但事实证明JavaScript还有其他方式。允许函数运行,实际上是独立运行的,我们可以设置所谓的。间隔,在某个时间间隔内,特定函数每隔一些毫秒。运行一次。所以如果我们回到之前的计数器示例。
现在在这个计数器示例中,我必须。自己按下计数按钮。这样就会增加。
每次都计数,但我可以把。这个放在间隔里面。所以让我回到counter,dot HTML,JavaScript在counter j/s里面,现在我想说的是,当DOM内容加载时,让我为count设置一个间隔,间隔为1000。那么这会做什么呢?设置。
interval是JavaScript内置的一个函数,在这里我说我。想要创建一个新的间隔,周期性地去运行一个。特定的函数,我想要运行计数函数,记得。计数函数将增加count的值,我会去掉这个。
目前只是为了简化,我只想让计数函数更新。h1标签,显示新的计数器值,并且。每1000毫秒去运行这个计数函数,换句话说。
每一秒都去运行计数函数,所以现在当我打开。
counter dot HTML现在是零,但每。秒现在它会每秒变化一次。每秒更新一次,我不需要点击按钮。当然,我可以点击,事件处理器仍然会工作,但这个间隔表示。现在每一秒去运行计数函数,并且有这个。
特定的结果。所以如果你曾经见过,一个网页像是显示当前的。时间(秒数)和倒计时器,或者显示当前时间。带秒数,它可能是在做某些事情,像这样:有某种。间隔每秒去计数和增加。
更新数字为更大的一个,比那个更大的一个。
当然,如果我关闭页面并返回,如果我关闭这些页面然后再回来。
再次打开counter dot HTML,我又回到了零,就像JavaScript并没有。保存关于我的页面的任何状态,每次我们运行页面都是。
将返回到counter j/s,它会说,好吧,我们在定义一个。
一个名为 counter
的变量,我们将那个变量设为零,所以每次加载页面时,它都会重置为零,这可能不太好,具体取决于你试图创建的页面类型,也许你希望页面能够存储信息。
在后续访问中,我们能够利用已经存储的信息。在后来的 JavaScript 版本和更现代的浏览器中,现在允许我们做一些类似的事情,这叫做本地存储,而本地存储就是我们能够。
能够在用户的网页浏览器内存储信息,页面可以请求在浏览器中存储特定的值,并且在后续访问该页面时,我们可以回来提取那些之前存储在本地存储中的值,本地存储会给。
我们可以访问两个关键功能。
我们将使用本地存储来操作这个,第一个是本地存储的 getitem
。我们想根据某个名称从本地存储中获取一些东西,那个名称是我们给那个值的。
在本地存储中设置这个键等于一个特定的值,这样我们就可以使用本地存储,允许我们的网页使用 JavaScript 能够存储信息并从浏览器中检索信息。现在让我们尝试使用它来解决这个 counter
的问题。
这似乎总是重置为零,所以现在在 counter
的部分,我想知道的是本地存储中是否已经有 counter
的值,所以我可以问一个问题,比如 if local.storage.get item counter
,意思是去本地存储尝试获取 counter
。
这个 if
表达式中的任何内容将会发生,如果本地存储中有 counter
的值,但如果我想做反向操作,简单来说,我可以使用感叹号,和 C 语言一样。在 JavaScript 中,感叹号仅表示否,所以意思是如果没有。
在本地存储中有一个名为 counter
的东西,那么我们就设置本地存储中的值,使用 local storage.set item counter
并将其设为零。这在做什么呢?在其他任何事情发生之前,我们会检查本地存储,看看是否已经有 counter
的值。
如果本地存储中还没有 counter
的值,我们需要确保在本地存储中有一些东西用于 counter
,所以我们将 counter
的值设为零,例如,现在需要改变的是什么。我将去掉间隔,这样它只在我。
点击它,但这个count函数到底应该做什么呢?首先让我获取计数器的值,计数器将是local storage中获取的item counter的值。我将从本地存储中获取计数器。我将增加计数器。
等于该计数器,但最后一步是我也会执行local storage dot set,item counter等于计数器的新值。所以我在这里做的是,点击按钮时,count函数将会运行。我们首先进入本地存储获取键counter的值。
counter的值恰好保存在一个叫做counter的变量中。我们继续增加该变量,将其设置为自身加一。更新h1的innerHTML和之前一样,只是更新我们实际在页面上看到的内容,然后继续将local storage的item counter设置。
计数器,例如,所以现在让我们去。
接下来看看如果我打开counter dot HTML会发生什么,我们看到0,我们数1 2 3 4 5,到目前为止一切正常。现在看看如果我刷新这个页面会发生什么,我刷新页面,好吧,这似乎有点奇怪,显示为0,让我试着计数看看会发生什么。我按下计数,好吧。
计数变为6,所以看起来它记住了我数到5(0 1 2 3 4 5),但当我刷新页面时,它仍然显示0,但让我数到6,我可以继续数7 8 9 10。如果我现在刷新页面,会发生什么呢?我可以试试,刷新页面后,我回到了0。
我数到11,所以不知怎么的,我仍然记得,但第一次它仍然给我0每次。
为什么会这样呢?如果你回头看看counter dot HTML,你会注意到,原因就在页面的主体中,h1的初始值总是0,所以如果我想修复这个问题,我需要在DOM内容加载时,获取document query selector h1。
并将innerHTML更新为local storage中获取的counter的值。因此,每次我打开页面,即使在点击按钮之前,甚至在事件监听器触发之前,我都想说,去替换这个标题,将其innerHTML更新为获取结果。
例如,从本地存储中获取计数器,所以现在刷新页面时,它的值是11。我可以更新更新更新,点击几次,比如说变成18,刷新页面后,它保持在数字18,无论我得到什么值,它都会存储在本地。
本地存储这样,当我刷新页面时,这个数字会保持在那里。我们实际上可以在本地存储中看到这个值,但再次进入Chrome的检查器。我进入Chrome,如果我去应用程序标签,进入本地存储,在左侧我可以看到,我有一个键为counter的值。
在这种情况下,其值恰好是28,你可以进入本地存储。你可以操作这个值,如果需要的话可以删除它,但这只是表明我们现在已经在浏览器中存储了这个值,以便在后续访问时,如果该页面再次加载,我们可以访问到这个值。
也可以从应用程序内部传输数据,因此我们现在可以看到我们的页面如何存储数据,以便使用户体验稍微好一些。如果我们希望用户能够记住上次访问某个特定页面时的信息,我们有时可以做到这一点。
事实上,这可能会非常有帮助。好吧,现在我们已经看到了JavaScript的许多特性,看到了一些不同的数据表示方式。我们将数据存储在变量中,这些变量有类型,比如整数。有些变量是字符串,有些时候是HTML元素。
有时这些变量是我们的项目的数组或列表,有时甚至是函数,因此我们可以将一个变量设为一个函数,但也许在JavaScript中最有用的数据类型是JavaScript对象。所以进入JavaScript控制台只是为了演示这个JavaScript。
对象实际上就像Python字典的等价物,是一组键和值的关联,可以通过键或属性查找某个东西,看看它的值是什么。如果我有一个变量叫做person,我可以将person设为一个JavaScript对象,像是名字为Harry。
而且他的姓是Potter,再次使用与Python中字典语法非常相似的语法,现在我有了这个变量person,名字是Harry,姓是Potter,我可以以多种方式访问person的特定属性。我可以说person.dot。
首先获取这个特定对象的first name属性,我看到它等于Harry。我也可以等效地使用方括号表示法,就像Python那样,方括号先出现,这样也会得到Harry。但这证明能够以这种方式表示数据是相当强大的。
以这样的结构化方式,我有一组键(也称为属性)与特定值的关联,然后我可以通过一个JavaScript对象访问某个特定的值,事实证明,这种方式最有用的之一是在数据交换中,移动数据。
从一个服务到另一个服务,所以在这里我们将介绍被称为API的内容,也就是应用程序编程接口,在网络上下文中,你可以将其视为互联网服务相互通信的一种定义明确的结构化方式。
如果你希望你的应用能够与某个其他服务进行交互,可能你希望你的应用能够与谷歌地图或亚马逊等天气服务互动,以获取当天的天气,那么你可能能够访问某个API,通过某种机制与其他服务进行通信。
通过发送请求并以某种结构化良好的格式接收数据,这种结构良好的格式通常是被称为Jason的特定类型数据,即JavaScript对象表示法,这是一种以JavaScript对象的形式传输数据的方式。
这些对象具有相关的属性和值,那么JavaScript对象表示法看起来是什么样子呢?如果我们回想一下我们创建的这些应用,这些能够表示航空公司和飞机动态的应用。
表示特定目的地的JavaScript对象可能看起来像这样,一个具有origin、destination和duration属性的JavaScript对象,这些都是我们之前见过的,但你可以想象,如果我们希望我们的航空公司能够。
使其数据可供其他服务使用,以便其他网络应用或其他程序可以以编程方式访问航班信息,我们可以以这种格式将数据传递给其他应用,以便它们可以将其视为JavaScript对象,从而获取有关航班的信息。
这个特定的表示法的好处在于,它既易于人类阅读,也易于机器阅读,我们作为人类可以查看这些内容,直观理解其中的含义,同时计算机也知道如何访问冒号前出现的特定属性。
并访问这些值是什么。
在冒号后面出现的内容也是如此,因此JavaScript对象表示法,也称为Jason,提供了一种非常方便的表示方式,而这并不完全是JavaScript对象语法中的内容,JavaScript对象中不需要严格地在键周围加引号,你可以直接写origin:。
origin用引号括起来,因此JavaScript对象表示法使用稍微不同的语法,但最终与我们在JavaScript对象中看到的非常相似,JavaScript知道如何以这种形式接收数据并将其转换为JavaScript对象,结果发现,在Python中也有实现这种功能的方法。
在其他编程语言中,能够解释JSON数据,以便以某种有意义的方式使用它,JSON表示的另一个优势是它非常适合表示事物的结构。因此,这些值不需要只是字符串或数字,它们可以是列表。
或者数组可能具有一系列可能的值,或者它们甚至可以是其他JavaScript对象,如果我们想表示的不仅仅是城市名称,而是例如城市名称和机场代码,如我们之前想做的那样,我可以将origin设置为一个字符串。
喜欢纽约的来源等于另一个包含城市属性和代码属性的JavaScript对象,其中城市是城市名称,代码是机场代码,重要的是,只要我和我沟通的人对这一点达成一致。
如果我们达成一致,确定这些键的名称以及这个JSON有效载荷(JSON对象)的结构是什么,那么接收方可以获取这些数据并编写一个能够解释它的程序,并以某种有意义的方式使用这些数据,因此我们将看到这个例子的一个例子。
现在使用JavaScript能够与另一个在线服务通信,特别是用于访问有关货币兑换率的信息。货币兑换率总是在变化,我们希望访问最新的货币兑换率数据,如果有一个在线服务的API。
以JSON格式提供该数据的访问权限,格式类似于这种机器可读的方式,那么我们可以使用这些数据来编写货币数据,以便进行转换。这些数据可能看起来像这样!
可能看起来像这样,我们请求访问什么是。
从美元转换的汇率是西纽顿的USD,我们得到的JSON对象看起来像这样,它有一个基础键USD,然后有一个汇率键,里面有一堆汇率。因此,转换为欧元和日元,以及英镑等。
澳大利亚元和各种不同的货币兑换率。例如,这些不同货币的汇率,并不一定要以这种方式构造数据,但这恰好是一种方便的方式,只要提供数据的人和我都。
一旦我们了解这个结构,就可以开始编写能够使用该数据的程序,因此我们现在将看到一个API的例子,即汇率API。我所欠的,如果我们访问API点汇率API点/最新,并提供一个参数base等于美元,那么我们得到的数据看起来像这样。
现在看起来有点杂乱,不像之前那么整洁,但它确实是完全相同的东西,只是没有空格。我们有一个JavaScript对象,里面有一个rates键,它告诉我,“好吧,这里是美元与加元、英镑、欧元之间的兑换率”。
还存在其他货币,下面我们有基本的货币,也就是我们转换的基础货币。如果我仅仅发起一个HTTP请求,这些数据就会返回给我。我向这个特定的URL发起网页请求,然后可以获取到所有这些货币兑换率的信息。
然后我将在我的应用程序中使用这些数据。那么,我该如何做呢?我现在如何开始在应用程序中使用这些信息?现在让我们创建一个新页面,我将其命名为currency HTML。在currency HTML中,我们将包含一个常规的HTML标题,标题为货币兑换,以及一个主体,主体内部我们就要开始。
暂时不包含任何内容,我真正关心的是能够进行网页请求的JavaScript,以便获取额外的数据。到目前为止,我们的JavaScript代码只是在我们的计算机上运行,运行在网页浏览器内,一切都在进行中。
在网页浏览器中,我们并没有与某个外部服务器进行通信。现在我们要看看的是Ajax,这与异步JavaScript有关,意思是即使页面已经加载,我们也可以使用JavaScript发起额外的网页请求以请求更多的信息。
信息来自我们自己的网络服务器或一些第三方网络服务器。如果我们想要在页面上获得额外的信息,这里我们想要的是让我们的页面发起一个异步请求,以请求当前货币兑换率的额外数据,例如。
我该怎么做呢?我想在DOM内容加载后再做这个。因此我们通常会在这里添加,接下来我们将利用一个内置于较新版本JavaScript中的功能,并且现在大多数主流浏览器都支持这个功能,那就是fetch。
这样做是发起一个网页请求,它将查询某个网站,可能是我们自己的,也可能是其他人的,然后从该页面返回一些HTTP响应,而我将要获取的页面是这个URL API,即exchange rates API dot io / latest base equals。
关于美元,我之所以知道这个API是怎么工作的,仅仅是因为我阅读了API的文档,它描述了URL参数是如何工作的,以及我获取的返回数据的结构是什么。因此我在这里要说的是,继续从这个URL获取,发起一个HTTP请求以请求额外的。
从这个URL获取信息并查看结果会是什么。而fetch给我们的结果是JavaScript中称为“promise”的东西,promise是一种表示某件事情将要回来但可能不会立即返回的方式,我们不会继续等待。
关于这些promise具体如何工作的细节,但事实证明有一种特定的语法来处理它们,我可以在fetch之后添加一行称为.then()
,这表示当promise返回后我应该做什么。一旦我得到像响应这样的东西,
我想做的是将响应转换为JSON,将其视为JavaScript对象,以便我可以进行操作。因此,我可以使用这个函数来让程序返回response.json()
,这意味着去获取最新的汇率,然后。
一旦完成,这是一个异步过程,可能需要一些时间,但一旦我得到这些结果,就运行这个函数,获取响应并返回响应的JSON版本,将响应转换为原始JSON数据,以便我可以使用这些数据来访问货币。
汇率结果显示,使用箭头函数时,如果你有一个非常简单的函数,它只是将某个东西作为输入并返回其他东西,我可以进一步简化这个函数,只需说我可以返回。我可以直接说response => response.json()
,这是一种简化的方式。
这里我定义了一个函数,它以响应作为输入,并返回将响应转换为JSON的数据。所以在这里,我让程序去获取来自这个特定API的最新汇率,然后将响应转换为JSON数据。一旦你有了数据,这就是我希望你对这些数据所做的事情。
现在让我们先控制台打印一下这些数据,只是将它打印到终端上,所以我们现在还没有做其他任何事情,我所做的就是让程序获取汇率,将汇率数据转换为JSON,然后我们打印出这些信息。
数据,所以我会打开currency.html
,这是一个空白页面,但如果我查看JavaScript检查器,我看到记录的是一个JavaScript对象,这里用单词“object”表示。如果我点击左侧的三角形,我可以展开,看到这个对象内部包含的所有汇率数据。
对于一大堆不同的汇率,从美元转换,我们这里的美元意味着一美元就是一美元!
例如,现在我得到了这些数据,让我们实际上尝试在程序内部使用它,也许假设我想要在美元和欧元之间进行转换,以确定美元和欧元之间的兑换率。好吧,如果我们回忆一下数据的样子,数据是一个JavaScript对象,其中有一个。
键名为rate,rates中有这个对象,在那个对象内部我可以访问EUR属性,以获取一美元等于一些欧元的汇率,例如,所以它在rates键内部,然后在EUR键内部,这就是我知道要访问什么。
在我的数据中,所以我真正想要做的是访问data.rates.EUR。它说获取我返回的所有数据,访问rates键,然后访问欧元键,我们将继续将其保存到一个名为rate的变量中。现在我将只需document.querySelector.body.innerHTML。
等于汇率,就像把那个汇率放入主体中。所以现在如果我刷新currency.html,我看到的只是这个值0.908843,这意味着现在一美元恰好等于。
关于零点九一欧元,例如,这很有用,我可以通过把它放在一个模板字符串中,使其更人性化。我可以说一个美元等于,然后再加上欧元汇率,例如等等。所以现在如果我刷新页面,我看到一美元等于这么多欧元。
即使这有点烦人,我可能不太在意这么多小数位,我真的很想精确到这些汇率。如果我只关心三位小数,例如,事实证明JavaScript有一些可以在数字上使用的函数,比如rate.toFixed。
传入三作为参数意味着我想把这个汇率四舍五入到三位小数,例如,所以现在我刷新页面,我看到一美元等于零点九零九欧元,而有趣的是,这一切都是由于异步请求的结果。
正在请求最新的汇率,当我收到汇率数据时,JavaScript会把这些信息插入到页面的主体中。我现在正在与一个API通信,获取该API的数据,以JSON格式返回,然后使用这些数据更新我的HTML页面。当然在实践中,如果。
我真的想要一个货币兑换网页,我可能不仅仅想显示美元和欧元之间的汇率,我可能想让用户选择他们想要兑换的货币。因此,我可能会在页面主体内这样做,而不是。
只有一个空的主体,让我们继续添加一个表单,这个表单将有一个ID为currency
的输入,这样我就可以稍后引用它。占位符将只是currency
,它的类型是文本,然后我会有一个类型为submit
的输入,我们将其值设为convert
。
按钮上写的内容是convert
,然后我可以转换为特定的货币,然后我需要一个地方来放我的结果,所以我将添加一个ID为result
的div,这就是在我完成所有货币转换后,我将放置那些结果的地方。
转换,所以现在而不是获取,立刻这里是我需要做的事情。我需要在表单提交时做一些事情,因此我可以通过说document.querySelector
表单的onSubmit
等于这个函数来获取表单,并且我会提前在函数的最后返回false
,这样我们就不会实际尝试。
并将表单提交到另一个页面,我只想在同一页面上本地运行所有内容,但现在在这个表单内部,一旦你提交,这就是我想运行这个将要获取新数据的代码的时刻,所以我将从汇率API获取数据,将数据转换为JSON,和之前一样,然后。
我继续访问那些数据,但现在我想要做的是弄清楚用户在输入字段中实际输入了什么,而这将是我关心的货币,以便获取访问,因此我会创建一个名为currency
的变量,它将等于document.querySelector
,而我。
如果我向下滚动输入字段,它的ID为currency
,所以如果我想要获取那个输入字段,我将说获取ID为currency
的元素并获取它的值,因此现在这是用户希望我访问的货币,然后我可以说data.rates[currency]
而不是data.rates.currency
,而且重要的是我不能做data.变量
被称为undefined
,这意味着那里没有值。
rates.currency
这将确实尝试访问一个名为currency
的属性,如果我使用方括号替代,它允许我使用一个变量,比如在这里第13行定义的currency
变量,即用户输入的货币,我想要访问它。
特定的货币在汇率中,因此我现在可以问一个问题,这里有两个可能性:用户输入的货币是有效的,或者不是。事实证明,如果你尝试访问一个不存在的对象的属性,返回的是一个特定的JavaScript。
例如,如果我有像let person = { firstName: 'Harry', lastName: 'Potter' }
这样的东西,就像我们之前做的那样,我可以访问person.first
并得到Harry
,我可以访问person.last
并得到Potter
,但是如果我访问person.
中间将会是JavaScript中的一个特殊变量或特殊值称为undefined,意味着没有值,这与null略有不同,虽然它们的意思相似,但在略有不同的上下文中使用。所以在这里我可以说如果汇率不是。
undefined,那么我们来更新,而不是更新主体,而是更新结果,显示1美元等于这个汇率,不一定是欧元,而是货币所对应的汇率,否则,让我们把document.querySelector结果的innerHTML设为无效货币,以便让用户知道。
知道他们尝试提供的货币实际上并不是有效的货币。因此,我们需要尝试另一种货币以获得结果。
所以现在我们可以做的就是,如果我再次打开currency.html,我现在看到一个表单,可以输入货币,我可以输入一些像欧洲的东西,按下转换,我看到好的,1美元等于0.900多一点,像英镑,按下转换,1美元等于0.77。
我输入日元时,1美元等于109.852日元,所有这一切都在发生,每次我提交表单时,它都会发出另一个请求。因此,如果在我提交表单的过程中汇率发生变化,下次我提交表单时。
我们将根据汇率API获取最新的汇率,结果将返回到这里,当然,如果我输入一个不存在的货币,比如输入foo并按下转换,结果将是无效货币,它将告诉我无法。
找到那个货币,所以它告诉我我需要输入有效的东西。因此,我可以输入有效的东西,也许我尝试一下美元本身,它告诉我1美元等于1美元,正是我期望的样子。现在这里有一些优化和改进。
现在用欧元与欧盟的我们的按键转换,但如果我搜索小写的欧元,例如,结果是。
认为那是无效货币,原因是如果你查看API返回给我的数据,这是我从汇率API收到的数据。你会注意到所有货币都是全大写的。
这意味着我只能访问那些实际上具有大写字母的键,因为这些是API提供给我的唯一键。所以如果我想在美元和小写欧元之间转换,我可能想先获取货币的值。
用户输入并首先对其调用大写,这是一种JavaScript函数,它接受一个字符串并将其转换为驼峰式。我想把用户输入的内容先转换为大写,这样如果我回到这里,输入欧元小写并按下转换,我仍然。
能够获取正确的转换率,另外一件我们不会明显注意到的事情是,目前我假设这一切都会成功进行,我们将能够成功地发出网页请求,成功地将响应转换回Jason,但你永远不会。
我们知道API可能会宕机,API可能会发生变化并做出意外的事情,因此每当你处理这些类型的承诺时,获取某些内容并说接下来做这个,再做那个,添加一个最后的案例,基本上说明如果。
如果出了什么问题,我可以说捕获错误,我可以做的就是说。
比如控制台输出错误,然后在那记录错误,这实际上是说,如果上述任何内容在获取和处理响应时出现问题,它将捕获错误,然后我们只需像打印出控制台中出现了什么错误消息一样。
这可以是一个有用的附加功能,以确保当事情崩溃时,它们以可预测的方式崩溃,你能够准确看到错误是什么,只需查看JavaScript控制台,因此现在我们有一个完全工作的网页,能够与外部API进行通信。
能够向互联网的另一个服务请求信息,使用这些结果并将它们放回页面,实际上只是展示了我们通过利用JavaScript语言所获得的力量,我们现在不仅能够使用JavaScript在。
客户端,我们以前无法做到的,之前我们只有在web服务器上运行的Python代码,但使用JavaScript,现在强大的地方在于操控能力,使用JavaScript能够通过更新内容读取页面并更改页面的内容。
页面上发生的事情,无论它是在特定元素内,还是用户输入的内容,结合事件处理程序使用,我们可以监听用户点击某个东西、用户滚动某个内容或用户输入键时的情况。
响应,因此使我们的网页更加互动。下次我们将继续讨论JavaScript,看看我们如何利用JavaScript的功能,继续构建更加有趣和引人入胜的用户界面。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P18:L6- web用户接口与交互 1 (用户接口,单页面应用) - ShowMeAI - BV1gL411x7NY
大家欢迎回到使用Python和JavaScript的Web编程,上一节我们关注了在用户的Web浏览器中运行的JavaScript,这让我们能够做很多事情,使我们的网页更具互动性,JavaScript让我们能够显示警报,操纵DOM,调整网页结构。
添加内容或查看已有的内容,还能让我们响应用户事件。当用户点击按钮、提交表单或在输入字段中输入内容时,我们可以运行JavaScript函数,响应这些事件,以使我们的网页更具互动性。今天我们将继续这个话题。
特别是查看用户界面设计,关注一些常见的用户界面范式,以及我们如何利用JavaScript实现这些目标,创建与用户互动时有价值的互动用户界面,因此更常见的范式之一。
特别是在现代Web编程中,单页面应用的理念尤为重要。如果我们想创建一个有多个不同页面的Web应用,通常是通过在我们的Django Web应用中使用多个不同的路由来实现,例如你去某个地址可以获取一个页面,而去另一个地址则可以获取另一个页面。
为了获得另一个页面,常用JavaScript的方式,我们可以创建单页面应用,整个网页实际上就是一个单页面,然后我们使用JavaScript来操纵DOM,用我们想替换的内容替换页面的部分,这有很多优势。
其中一个是我们只需对实际变化的页面部分进行修改,例如如果你有五个不同的页面,但页面的一般布局和结构相似,当你在页面之间切换时,而不是加载一个全新的页面。
你可以只加载页面的一部分,这对于频繁变化的应用非常有帮助。那么现在我们来看看如何实现一个非常简单的单页面应用,假设我们想要一个只显示三个不同页面的单页面应用,但这些内容都是包含在一个页面中。
在同一页面上,我将创建一个新的文件,称为singlepage.html。在这个文件中,我们将包含我们的常规内容,并在这个页面的主体中,我将包括三个不同的部分,以代表我可能希望向用户展示的三个不同页面,因此我将有一个id为page one的div,里面可能只是包含一些内容。
有一个标题,写着这是页面一,你可以想象这些页面上还有更多内容。同样有一个 id 为页面二的 div,我们称之为页面二,然后是一个 id 为页面三的最终 div。
它有一个标题,写着这是页面三,例如,现在如果我打开。
single page.html,我们看到的是同时显示所有三个页面,这可能不是我们想要的。我们真正想要的是默认情况下。
将这些页面隐藏,直到我们想要查看它们,例如一次查看一个页面。所以我可以做的一件事是使用 CSS 来切换,某个内容是否可见。添加一些样式标签到我的页面,默认情况下,我的所有 div 都是不可见的。
现在在屏幕上显示,如果我刷新页面,我实际上看不到三个标题中的任何一个。
我之前有的,但是我真的希望现在有一些按钮,让我在这三页之间切换。所以我会给自己三个按钮,一个按钮写着页面一,一个按钮写着页面二,一个按钮写着页面三。例如,我需要某种机制,让这些按钮知道当你点击这个。
按钮应该显示哪个页面,因此我将继续使用数据属性,我们上次在 JavaScript 中看到过,给这些特定的 HTML 元素添加一些额外的信息,我会给第一个按钮一个 data dash 页面值为页面一,第二个按钮的值为页面二。
第三个是页面三的 data dash 页面值,这里同样只是提供信息。这样在我稍后写一些 JavaScript 时,可以让 JavaScript 代码查看这个 data dash 页面属性,来判断当你点击这个按钮时,应该让我看到 id 为页面一的 div,这就是我们要实现的。
所以现在让我们继续,写代码,做的就是能够说我想显示页面一,隐藏其他两个,或者显示页面二,隐藏其他两个,或者例如显示页面三。为此,我首先会写一个函数,让我实现这一点,我会写一个名为 show page 的函数,它的参数就是要显示的页面。
我想显示的页面,所以这个函数应该做什么,我们将要做的是使用 document.queryselector,我想获取具有特定 id 的元素。这个页面的 id 将代表我想显示的 div 的 id,所以我会说获取这个 id 的元素。
然后使用模板字面量,我会说好的,获取页面上任意元素的id,并且我想要更改它的样式属性,具体是更改它的显示属性,而不是默认的none。我希望更改为block,这样它就会显示出来。
块在页面上实际上。
函数,我可以在浏览器中刷新页面进行测试。现在我看到三个按钮,这些按钮暂时没有任何作用。但是我可以在控制台中运行这个函数,像是运行显示页面函数,举个例子,显示页面一。
按下回车,现在页面一,然后页面二,那么页面二将变得可见。好的,这完成了我想要的一半,页面二现在可见,但页面一也可见。因此我可能想要这样,如果我显示一个页面,首先隐藏其他页面,像是隐藏。
所有页面,然后显示页面二,或者隐藏所有页面,然后显示页面三。那么我该如何进行呢?首先,当我显示一个页面时,我想先隐藏所有其他页面,隐藏所有页面。为了获取所有页面,我会用document.querySelectorAll获取所有的div,这是我用来封装页面的。
现在对于每一个,再次有效地创建一个循环,我在遍历每个div,对于每个div,我们就这样设置。
div的样式属性设置为none。所以这个显示页面的函数现在首先查询所有的div,它们模拟着我的单页面应用中的页面,对于每一个div,我们将其作为输入传递给这个函数。
每个使用这个箭头函数的语法,这只是一种简写方式,用于表达一个函数。在这里,我说对于每个div,我们将修改它的样式属性,将显示设置为none,意味着不显示任何div,然后只显示被请求的div,现在这应该解决这个问题。
同时出现多个页面,如果我回到这个页面并点击或输入显示页面一,那么页面一会出现,但如果我运行显示页面二,那么页面二会出现,但页面一。
消失,同样,当我显示页面三时,它显示页面三而不显示其他两个,所以我可以通过控制台操控哪个页面是可见的。但现在我希望这些按钮能够真正起作用,当我点击其中一个按钮时,它能实际显示请求的页面。因此,为了做到这一点,我想要。
我需要为这些按钮附加一些事件监听器,这意味着我需要等待这些按钮加载到页面上,所以我们将使用document.addEventListener。等待直到页面上所有内容加载完成,然后我才会说让我们查询选择器所有。
对于所有按钮及每一个按钮,让我们为每个按钮附加一个事件监听器,因此我正在查询所有按钮,并说对每个按钮我想这样做,我想要做的是在按钮被点击时执行这个函数。
要显示页面,我想显示哪个页面呢?我想显示按钮数据集中页面部分的内容,而要获取当前按钮,即被点击的按钮,请记住,当我们在事件处理程序内时,我们可以利用javascript关键字this。
该关键字指的是接收到点击事件的元素,因此我可以说this.dataset。
点页面意味着,对于每个按钮,当按钮被点击时,我们会为每个按钮运行这个函数。当按钮被点击时,我们想要显示哪个页面,我们将取这个按钮,即接收到事件的按钮,访问它的数据属性。
页面属性,这里有页面一、页面二或页面三。然后我们就可以调用我们刚才写的show page函数。所以现在我们已经完成了,为按钮附加了这些事件处理程序,所以现在如果我刷新页面。
页面上我可以点击这些按钮,切换到任意三个页面之间。现在有趣的是,我们现在可以在一个单一页面中模拟多个页面的概念,所有内容都封装在一个html文件中,但不需要持续向服务器发出额外请求来获取访问权限。
然而,有时当你需要一个页面的新信息时,联系服务器可能是合理的。例如,你可能想象每个页面包含大量文本,如果我们立即将所有数据加载到html中并显示和隐藏,那将是低效的。
需要这样做,因为我们可能加载的信息超过了用户实际上关心的内容,尤其是如果他们从不查看第2页或第3页。所以我们可能想象的一个做法是动态加载这些数据,上次我们讨论的javascript中,我们看到如何使用fetch来请求一些额外的信息。
上一次是来自一个网络服务器,货币兑换率,但我们利用返回的数据填充了我们页面上的内容,同样,我们可以在这里做类似的事情,如果我们有一个单页面的总体结构,并且希望加载新内容,而不是完全加载新的HTML内容并重新加载整个页面。
我们可以询问自己的网络服务器,页面的哪个部分需要更改,然后替换页面的那部分,这就是我们现在要看的内容,现在结合Django作为我们的网络服务器,以及JavaScript用于编写客户端代码,以生成单页面应用。
我们将进入一个我提前准备的示例,叫做单页面一,里面只是一个Django应用程序,包含一个叫做单页面的应用。我们会注意到,首先要查看URLs,里面有两个URLs,一个是默认URL,仅加载索引函数,另一个是加载不同内容的URL。
就像是我可能想动态加载的页面部分,例如,我有 /section/
加上某个特定数字,如果我们看看这些URL的视图,实际上索引函数只是返回 index.html
,然后部分函数做的是,它首先确保数字在1到3之间,如果是的话。
响应的是这些字符串之一。
那么这实际上是如何工作的呢?如果我进入。
如果我访问这个URL /section/one
,我得到的是这一段文本,如果我去 /section/two
,我得到的是另一段文本,而 /section/three
则是完全不同的文本,所以只是不同的文本。
我想将这段文本纳入一个现有的index.html
模板中,该模板在我访问默认路由时加载,而在index.html
中,我们会看到我有一个显示部分的函数,其行为与我们刚才看到的显示页面函数非常相似,但不同的是,显示部分将要做的事情。
它将从我的网络服务器获取我应该在页面上显示的文本,我从 /sections/
获取数据,填入一个数字,比如一、二或三,当我收到响应时,过去我们已经看到如何将该响应转换为JSON数据,如果它是一些结构化数据,我们也可以直接转换响应。
转换成纯文本后,我将获取该文本并在控制台中记录它,以便我们在日志输出中看到,但接着会查询选择页面内容,某个ID为content的元素,更新其innerHTML并将其设置为该文本,所以现在这个整个函数正在做的是,它将联系我的服务器,弄清楚哪些文本内容应该在。
新部分将填充页面相应的部分,并根据来自该 HTTP 请求的文本进行填充,然后在页面的下方,我们会看到我有一个“你好”标题和三个按钮,它们在不同的部分之间切换,每个按钮都有一个 data-section 属性。
用于确定应该加载哪个部分,然后是一个最初为空的 div。
现在把这一切放在一起,如果我访问默认路由,我会看到“你好”,加上三个按钮,给我提供在三个不同部分之间选择的机会,如果我点击第一部分,发生的事情是 JavaScript 将请求 section/1
的文本,它会返回文本。
它将填充到页面的第一部分、第二部分和第三部分,所以与之前非常相似,但不同于之前我们所有文本一次性加载到 HTML 页面中,现在我们使用异步 JavaScript 仅在需要时动态加载信息,当我们点击某个部分时,它将发出请求。
请求需要填充的内容,它将进行填充,并生成标题。你可能想象,在一个更复杂的网站中,网页边缘的内容要多得多,所有这些内容保持不变,我们不需要重新加载任何这些信息,我们仅重新加载实际改变的页面部分。
当我们在这些不同的部分标题之间切换时,这在某些方面似乎是一种优势,也许我们可以更高效地运行这样的单页应用。但是,我们似乎失去了在 URL 中保持状态的概念,通常 URL 会给你指示当前页面的信息。
如果你在第一部分,你的 URL 是 /1
,如果你在第二部分,URL 是 /2
,第三部分是 /3
,但当然在所有这些示例中,我们都停留在同一页面,每当我点击一个按钮,无论是第一、第二还是第三部分,URL 永远不会改变,URL 始终保持不变,结果是 JavaScript 中有一种方法。
以更新 URL 的方式操控该 URL,利用所谓的 JavaScript 历史 API,我可以将某些内容推送到历史中,这意味着更新 URL 并实际将其保存到用户的浏览器历史中,这样用户稍后可以潜在地返回到那个位置。为此,我将在类似的单页应用中展示另一个例子。
除了在 index.html
内部,我还添加了一些额外的东西。一个是当我点击一个按钮时,也就是说当我点击第一部分、第二部分或第三部分时,我在这里添加了这一行 history.pushState
,history.pushState
的作用是,基本上会向我的浏览历史中添加一个新元素。
我首先指定任何与状态相关的数据,特别是。我存储了一个表示这里所表示的部分编号的javascript对象,下一个是一个标题参数,大多数网页浏览器实际上会忽略,因此通常可以是空字符串。但这里的第三个参数是应该放在url中的内容,我希望放在这个url中。
情况是类似于部分,后跟部分编号,例如我可以输入二。或斜杠部分三,例如,当我点击不同的页面时,这些内容将出现在url栏中,然后我希望能够支持的是,当我回顾我的历史时,如果我在网页浏览器中点击后退按钮。
如果我是之前访问的页面,我想从第3部分返回到第2部分。而且确实有一个事件处理程序,window.onpopstate,这意味着当我从历史记录中弹出某个内容时,比如返回我的历史记录。我们可以将一些事件作为参数,如果你查看事件.state。
我在控制台上运行的部分。日志记录,以便我们可以在稍后查看它。我们将看到存储的状态。
与用户历史的那部分相关联,我可以继续显示该部分。因此,总的来说,当我运行这个网页应用时。
我看到三个部分的按钮,当我点击其中一个按钮时。我不仅看到文本,而且在url栏中也看到我现在在斜杠部分一,这已经被推入我的历史中,我也更新了url以反映这一点。我点击第二部分,更新了url,第三部分也更新了url,当我将内容推入我的部分时。
这样我就可以在需要时返回,事实上,如果我现在打开javascript控制台。如果我返回,例如返回到。
第二部分你会看到的是记录的内容是数字。二当我打印出当前与这个url相关的部分时。它保存了我应该加载第二部分的状态。因此,它确实加载了第二部分,这里当然没有任何问题。
原始范式是仅仅动态加载不同的页面。使用django进行请求并获取响应,但通常当你开始想象许多内容在同一页面上同时变化的应用程序时。你可能会想象社交网络网站,其中许多内容保持不变,你可能会。
查看同一页面的不同部分,能够动态加载信息,请求额外的信息,然后在页面上显示,这实际上可以是一种非常强大的方式,使你的网页更加互动。因此,这就是我们可能构建单页应用程序的方式,利用javascript来。
异步加载新数据,然后利用这个历史 API,让我们可以向 URL 添加内容,增加用户的浏览历史,以便我们可以稍后通过监听窗口的 onpopstate
事件回到它们。而且,事实证明,在 JavaScript 中我们访问到的窗口对象。
功能非常强大,它表示计算机屏幕上显示所有网页内容的物理窗口,还有一些窗口的属性可以查看,这使我们能够启用一些有趣的功能。例如,窗口的高度确实由用户的。
实际上看到的是他们在 Google Chrome、Safari 或任何他们使用的网页浏览器中的窗口,还有一些可能有用的属性,比如 window.innerWidth
,它表示窗口的宽度,比如用户屏幕的大小,以了解窗口的宽度是多少像素。
窗口的内宽就像有一个 window.innerHeight
,它表示窗口的高度。现在窗口表示的是他们实际上看到的物理部分,我们还看到另一个变量,JavaScript 让我们访问的,就是这个文档对象。那么它们之间有什么区别呢?
窗口和文档,文档通常代表整个网页。但是如果网页很长,网页可能不会完全适合窗口,你通常需要滚动整个网页,窗口在任何给定时刻只显示页面的一部分。
可以表示文档,像是这个大垂直部分,超出了窗口,文档中可能有一部分在窗口上方,还有一部分在窗口下方,所以 window.scrollY
是你可以访问的另一个变量,window.scrolly
表示滚动了多少。
滚动了多少像素,所以如果你在页面顶部,window.scrolly
为零,你根本没有滚动,但当你开始滚动时,如果你想知道用户在页面上滚动了多少,你可以查看 window.scrolly
来计算用户在 y 方向上滚动的像素数量。
整个页面的高度在 document.body.offsetHeight
中表示,表示文档的整体高度,我们谈论所有这些内容,此外还有 window.innerHeight
和 window.innerWidth
等,因为结合使用所有这些值,你可以开始进行一些有趣的计算。
所以你可能想检测的一件事情,例如,用户是否滚动到页面底部,这可能是你想知道的事情,结果是没有自动执行这个的事件监听器,但我们可以计算来尝试弄清楚,如果 innerHeight
是窗口的高度,而 scrollY
。
这是用户垂直滚动的距离,而 document.body.offsetHeight
是文档的整个高度。你可以问自己,如果用户滚动到页面底部,什么情况需要为真?如果用户已滚动到底部,那么 scrollY
加上 innerHeight
(也就是他们已滚动的量)。
加上窗口的高度,必须至少等于 document.body.offsetHeight
,也就是说已滚动的量加上窗口的高度,将你带到页面底部,直到文档的高度,利用这个数学比较。
页面底部,我们实际上可以尝试将其付诸实践。所以我将打开一个示例,叫做 scroll.html
,现在 scroll.html
中只有 100 个段落,位于 body 标签内,我有一个 p 标签,表示段落一、段落二,以此类推。
我在这个 HTML 页面的 body 内有 100 个段落,这就是全部内容。
现在真的在这里。
这样,如果我打开 scroll.html
,我看到有 100 个段落可以滚动,而我想做的就是检测我何时到达页面底部,或许在那时做点什么,比如改变颜色。
例如,我该如何做到这一点呢?我将需要一些 JavaScript,因此我将添加一些 JavaScript,并为 window.onscroll
添加一个事件监听器。滚动是一个监听我在窗口中滚动的事件,当我在窗口中滚动时,我们将运行这个函数。
我将使用箭头函数作为简写,我想计算什么呢?我想计算如果 window.innerHeight
(窗口本身的高度)加上 window.scrollY
(我已滚动的量)是否至少等于 document.body.offsetHeight
。如果是,那么我必须已经滚动到页面底部,或者可能更远。
如果有一点点余地可以滚动到页面底部,那么如果这是真的,我就已经到达页面底部。接着我们会执行 document.querySelector
,选择 body,并改变它的样式,特别是改变它的背景颜色,改为绿色,否则如果我们还没有到达底部。
页面,并将其背景颜色改为白色。因此我们现在做的事情是利用我们对窗口对象的了解,说明当我们滚动窗口时,检查加起来是否至少等于整个文档的高度,如果到达了页面底部,就改变页面的样式。
根据情况更改背景为主体,否则将背景更改为白色,如果它已经是白色的话,所以现在如果我看看这个实际的。
HTML页面重新加载scroll.html,我们将看到背景最初是白色,但当我向下滚动,直到我到达底部时,页面将变成绿色。
在我到达底部之前是白色,但一旦我到达页面底部。
它变成绿色,原因是窗口的高度,加上我从页面顶部滚动到现在的高度,这两者之和等于文档的整个高度。这意味着我们能够检测到我已经到达页面的底部。
作为结果,我们可以将背景颜色更改为绿色。现在这本身并不是一个特别实用的用途,因为我们通常并不关心在到达页面底部时更改背景颜色,但实际上是有真实的应用场景,你可能会想象到。
在允许无限滚动的网站的背景下,如果你在一个社交网站上,有很多帖子,你滚动到帖子列表的底部,然后它会生成一组新的帖子,或者你在查看新闻文章时滚动浏览新闻文章。
一旦你到达底部,它会加载一整套新的新闻文章,而无需你去另一个页面,它是如何做到的呢?这结合了两个要素,首先是使用JavaScript检测你已到达页面底部的能力,第二。
能够异步加载内容,使用JavaScript加载额外的内容,获取一些有附加内容的页面,一些额外的新闻文章、帖子等等,然后利用这些信息来操作DOM,将这些信息添加到现有网页上。
这最终赋予我们支持无限滚动的能力,所以现在我们来尝试一下实现无限滚动的样子。我已经开始创建一个名为scroll的示例应用,在这个应用中有一个名为posts的应用。
帖子应用的功能是,它有几个网址,它有一个默认网址,可以加载一个索引行,然后是一个帖子路由,加载这个帖子视图,所以让我们来看看这些是做什么的。索引的作用就是加载一个名为index.html的文件,这个模板。如果我向/posts发送请求,我需要提供两个参数。
我需要提供一个开始的帖子编号和一个结束的帖子编号,然后它将生成一些示例帖子,比如帖子编号一、帖子编号二,依此类推。在实际操作中你可以。
实际上可以使用社交网络帖子来代替这个,但这只是为了演示目的。那么,如果我进入这个。
如果我访问/posts并说开始等于1,结束等于10,比如这样,我会得到一个看起来像这样的javascript对象。回想一下,javascript对象只是一个方便的格式,用于在json格式中来回传递信息。我们这里有一个json对象,带有一个名为posts的键,给了我所有的帖子。
从第一帖子到第二帖子,一直到第十个帖子,它给了我这些帖子,因为我说从一开始,到十结束,但如果我说什么,比如从20开始到28结束,它会给我。
我可以指定我想要的帖子范围,从帖子编号20到帖子编号28。所以现在这是一个我有效实施的api,允许某人通过访问这个特定的url,传递参数,获取多种不同的帖子,从哪个帖子开始。
他们希望以什么帖子结束,然后他们将所有这些数据以json格式返回给他们,而这点非常好。现在,当我们加载帖子时,不必仅仅猜测需要加载多少个帖子,然后要求某人去另一个页面,我们可以直接加载前20个帖子。
现在我们想做的是,如果他们到达页面底部,继续通过访问这个api端点来加载接下来的20个帖子,获取接下来的20个帖子,然后将其填充到html页面中。让我们看看在实际操作中这是如何工作的,看看index.html中的模板。
html中有相当多的javascript,但首先看一下主体。主体中有一个用于所有帖子的div,最初将是空的。接下来,javascript将如何执行,我们将逐步解析它。我们从第一帖子开始,所以计数器将跟踪我们需要加载的下一个帖子。
默认情况下,我们将从加载帖子编号一开始,我们有一个变量叫做数量,它将告诉我们每次要加载多少个帖子。我们就说每次加载20个帖子,所以从1到20,然后21到40,41到60,依此类推。当dom内容加载完成时,就调用这个函数。
这称为加载,而加载函数的作用是确定开始和结束应该是什么,它获取所有新的帖子,然后对于每个返回的帖子,我们异步请求新帖子,而添加帖子函数则负责。
创建一个新的div,填充其中的帖子并将其添加到dom中。因此,现在我们有了这些部分,能够通过获取来加载新帖子。
我们能够动态加载所有这些帖子,所以如果我不去斜杠帖子而只是去这个默认路由,我会看到大约20个帖子全都显示出来,但只有20个帖子。
因为每次我调用加载函数时。
这将加载下一组帖子,例如,我可以在控制台中,如果我试着通过自己调用加载函数,按回车,过了一秒钟左右,下一组帖子出现了21,一直到40。我再调用加载,下一组帖子出现了41到60,每次20个帖子。
全部使用异步JavaScript,但现在我想要发生的事情。
所以这一切都能自行发生,而不需要我干预和手动编写JavaScript调用,我只想说与之前相同的逻辑。窗口在滚动时,让我们说,如果窗口的内高度加上窗口的滚动y至少是文档的主体偏移高度,意味着我已经滚动到了页面的底部。
然后我们就继续调用加载函数,这些行每次我滚动时都在做的就是检查我们是否滚动到页面底部,如果是,那就去加载。
下一组帖子,现在我刷新页面时看到帖子1,一直到帖子20。现在,当我到达帖子20时,注意发生了什么,如果我滚动到底部,过了一秒钟,下一组帖子出现了,我再滚动到底部,我已经到了40。然后过了一秒钟,下一组再次出现。
更多帖子将在之后加载,使我能够有效地实现无限滚动的想法,通过利用一些JavaScript技术,在我到达页面底部时检查,然后动态执行某些操作,比如加载额外的页面到屏幕上,这里也有很多的力量。
在JavaScript内部,用户界面与用户的互动以及页面如何互动,这里有很多力量,类似于用户滚动到页面底部。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P19:L6- web用户接口与交互 2 (动画与交互) - ShowMeAI - BV1gL411x7NY
为了让事物在某种方式下移动并改变其属性,结果显示CSS已经支持动画。CSS已经为我们提供了支持,允许我们为元素设置样式,比如我们希望这个元素是什么颜色,以及多大,例如,但它也赋予我们动画这些属性的能力。
改变某物的大小或在某段时间内改变某物的位置。那么现在我们来看看一个实际的例子,我将创建一个新文件,命名为animate.html。在animate.html中,我想添加一点动画。
将CSS应用到这个特定页面,我将从一个标题开始,一个像“欢迎”这样的标题,它将显示一个欢迎消息,因此现在如果我打开animate.html,我看到的只是一个消息。
说欢迎,但现在让我们为它添加一些CSS。让我们。
放入样式标签中,对于这个标题h1,我想要。
为其应用特定的动画,我首先需要指定动画的名称,我可以为动画选择一个名字,比如说“增长”。我会将动画的持续时间设置为两秒,然后动画填充模式就像动画应该朝哪个方向移动,应该向前还是向后。
向前还是向后,我们通常希望我们的动画向前进行,以便根据我们将要指定的一些规则取得某种前进的进展。所以在这里我说我们将使用一个叫做“增长”的动画来为所有标题动画,现在我实际上做到了这一点,并且在样式上方。
我会说,在关键帧“增长”,这将允许我为这个特定元素指定一些关键帧,这意味着元素应该从哪里开始,样式属性应该是什么,然后在最后样式属性应该是什么。CSS将负责确定需要什么。
发生在所有那些中间的几分之一秒内。例如,我可以说像“开始增长”,这意味着它的初始属性应该是什么,可能一开始我想让它的字体大小为20像素,然后我们说字体大小为100像素。所以总体来说,这表明我。
我想将名为“增长”的动画应用于我所有的标题,这个动画应该持续两秒并向前进行。
而“增长”动画将会做什么呢?嗯,它意味着在开始时,任何遵循“增长”动画的东西将以20像素的字体大小开始,而在结束时将增长到100像素的字体大小,我现在已经定义了这个动画的含义,所以如果我继续刷新这个页面 animate.html。
你会看到“欢迎”在两秒钟内改变大小,它从小变大,遵循那些我告诉它遵循的关键帧,这个特定步骤是一组指令,它从一个特定的字体大小变为另一个字体大小,因此我们。
在页面上查看效果,结果你可以做的不止是操控大小,你可以操控几乎任何你想要的CSS属性。所以如果我告诉标题它应该有一个相对的定位,这意味着它的位置应该相对于其他元素或其父元素中的其他事物,我可以说你应该将你的位置从。
从屏幕左侧的零百分比,移动到距离左侧50%的位置。在这一点上,“增长”可能不是这个动画的最佳名称,我会称它为“移动”,所以动画名称是“移动”,现在这个动画将会做的是。当你运行动画时,它将从紧靠屏幕左侧的地方开始。
调整为距离屏幕左侧大约50个像素,我们看到的就是动画的过程,它从左边开始。
一直刷新到屏幕大约一半的地方,刷新页面,它就会执行完全相同的操作,结果我们不仅需要指定动画的开始点和结束点,我们还可以指定各种不同的关键。
在动画的不同点设置帧,我们希望捕捉到一些东西。例如,在动画开始时,具有这组CSS属性,可能在动画进行到一半时有一组不同的CSS属性,然后在最后再有一组CSS属性。
所以我可以说,如果我想让标题不仅向左移动到右边,还要再移动回来,我可以说在开始时,在零百分比的时候,当你完成零百分比的动画时,你应该距离左侧零百分比。
在动画进行到一半时,你应该距离左侧50个像素。当你完成动画时,100%到达左侧,让我们回到。嗯,距离左侧为零百分比,我现在有三个关键帧,动画的开始,动画的中间回到。
动画的开始,再次刷新页面时,我们会向右移动,然后再回来,我们能够向一个方向移动,然后再移动回来。
还有其他属性,我可以设置动画的迭代次数。
例如,设置为2,这意味着动画不是只运行一次然后停止,而是运行两次然后停止。因此,当我刷新时,它向右移动,然后向左移动,然后再重复一次。如果你真的想的话。
可以将其设置为无限,这意味着从不停止执行这个动画,它始终在进行。
要将这个标题移动到右侧,然后再移动到左侧,依据我指定的关键帧。因此,如果你看到页面上有东西以某种方式互动地移动,有很多方法可以实现这一点,但仅使用CSS也能很好地创建这类动画。
而现在这个动画只是无限循环。我们可以使用JavaScript来控制这个动画,所以让我们看看效果。我要回到页面的主体,除了一个写着“欢迎”的标题,我还会添加一个按钮,上面写着“做”,接下来就是添加一点JavaScript。
我将添加一些JavaScript,以便按钮现在可以控制动画,决定动画何时开始和停止。因此我们在这个脚本内部的操作是,首先说,document.addEventListener,DOMContentLoaded,这意味着等到DOM加载完成,如同我们之前所做的,现在让我获取那个h1元素。
我最初会将它的样式。animationPlayState设置为paused。所以,动画播放状态是样式的一个属性,让我决定动画是正在播放还是暂停,我可以使用JavaScript来控制它。与其说无限运行,我可以说动画的播放状态应该从。
通过首先获取h1元素,然后修改该元素的动画播放状态属性来实现暂停。但是我希望的情况是,每当有人点击按钮时,我想要改变动画的播放状态。所以我要说,document.querySelector按钮。意味着获取那个按钮,当有人点击按钮时,让我们运行这个函数。
当前的动画播放状态是paused,我们将继续设置动画播放状态为running,否则如果它已经在运行,那么我们就将动画播放状态设置为paused。因此,整体来看,这个函数将做的是,它将获取标题,初始暂停每次。
如果按钮被点击,运行这个函数。
函数会判断如果我们暂停,就开始运行动画;否则通过修改标题的动画播放状态来暂停动画。所以现在如果我刷新这个页面,我们有欢迎信息加上一个按钮,上面写着“点击这里”,最初一切都是暂停的,没有动画发生。
但我点击这里,这开始了动画,它将无限进行,直到我决定停止它,此时我再次点击它,动画暂停。我可以控制何时开始和何时暂停动画,这在你想创建更互动的东西时特别有帮助。
这意味着你可以逐渐地改变 CSS 属性,而不是立即改变。你有能力去动画化某些东西,让它更好地工作。那么,让我们看一个例子,如何将这个想法付诸实践,回到我们的帖子示例,我们有一个无限滚动的帖子列表,但。
现在想象一下,我们希望在完成后能够隐藏帖子,所以我准备了一个名为隐藏的示例,它与之前的非常相似。!
div,现在点击隐藏按钮什么也不做,我们稍后将实现这一点。!
index.html 模板中唯一的变化是当我添加一个新帖子时会发生什么。它从服务器加载帖子,然后在获取到那些帖子时,它会遍历每个单独的帖子,这只是一个字符串文本。并且它将那个字符串文本添加到页面的一个元素中,通过这个 add post 函数。
ad post 函数是创建一个 div 来存储那个帖子。给它一个类名,因为我们将通过这个类名来动画化它。然后将其内部 HTML 设置为帖子的内容,比如“帖子编号一”、“帖子编号二”、“帖子编号三”,再添加一个只写着“隐藏”的按钮。
然后我们将把它添加到 DOM 中。这就是 adpost 现在要做的,我们通过这段 JavaScript 代码生成一些 HTML,然后将这些 HTML 添加到页面上。现在我们添加的是一个 div,包含文本,同时还会给我们访问一个按钮的权限。
我们最终希望能够隐藏那篇帖子。那么我们如何让帖子隐藏功能正常工作呢?我们想要做的是以某种方式检测用户何时点击这些隐藏按钮,有多种方法可以做到这一点,但一种方法是监听任何点击事件。
每当有人点击文档时,我可能会想问他们到底点击了什么。结果是,对于大多数事件监听器,事件监听函数可以将事件信息作为可选参数传入。
比如说点击事件、滚动事件、按下键事件或抬起键事件。你可以访问的一个属性是event.target,它代表事件的目标,在这种情况下就是实际被点击的东西。我会把event.target保存在一个名为element的变量中,现在的想法是无论发生什么。
被点击的目标是事件的目标,我们将其保存在element中。我想知道element是否是隐藏按钮,我也可以为每个隐藏按钮附加事件监听器,这只是另一种实现方式。
为了演示,我们说当我们在任何地方点击时,将其保存在这个变量中,如果它是一个隐藏按钮,那么它将具有类名hide,因为我给每个隐藏按钮都赋予了类名hide。所以我可以说,如果element.className等于hide。
这意味着被点击的对象是具有类名hide的元素,我们可以假设它实际上是一个隐藏按钮。接下来,我想做的是调用element.remove来去掉那个元素。那么如果我刷新页面,这会有什么效果呢?让我们试试帖子一,如果我隐藏它,我想隐藏帖子一。
好吧,这并没有完全成功,虽然接近,它去掉了隐藏按钮。
但我并不想去掉隐藏按钮,我想去掉整个帖子。所以这里发生了什么似乎是,如果元素的类名是hide,意味着我点击了一个隐藏按钮。
element.remove只会去掉那个元素,它去掉了隐藏按钮,但并不去掉包含它的帖子。如果从DOM的角度考虑,帖子是一个div,而它的子元素是这个隐藏按钮,所以去掉按钮并不会同时去掉帖子。如果你也想去掉帖子。
你需要去掉的不是元素,而是元素的父级。结果在JavaScript中也有方法可以做到这一点,element.parentElement.remove。也就是说获取这个元素的父级并去掉它,我想隐藏它,隐藏帖子一,好的,现在我看到帖子二。
如果我想隐藏帖子三,我就隐藏帖子三。现在帖子三消失了,现在我直接从帖子二跳到帖子四,这样是有效的,但也不是,因为所有的帖子都是完全相同的。
三,看起来并不明显,因为帖子二和四几乎看起来一模一样,你真的要注意才能知道隐藏成功了。因此,这可能是动画实际上非常有帮助的时候。我可以说些类似的话,让我们给这个帖子。
每个帖子都有一个关联的动画,我们将给它一个动画名称,叫做隐藏,动画持续时间为两秒,我们将说需要两秒才能隐藏,并且设置动画填充模式为前进。我想向前进行动画,最初我将给帖子设置一个动画播放状态。
初始时,我不想让动画运行,因为我不想立即隐藏所有帖子,暂停这个动画。稍后我们会继续运行动画,以便实际隐藏帖子,然后我需要定义隐藏帖子到底意味着什么,我会说好吧,在0的状态下。
百分比标记,这意味着什么,让我们给自己一个不透明度为1。不透明度是一个CSS属性,它控制HTML元素的透明度,而在动画结束时将不透明度设置为0。因此,最初我们可以完全看到这个元素。
这个元素是完全透明的,现在我需要做的实际上是触发这个事件。某种方式下,这可能会在我的事件监听器内发生。在不立即移除元素的情况下,让我先获取父元素并将其动画播放状态设置为运行,例如,意味着当我点击隐藏按钮时就开始运行动画。
动画将会在几秒钟内将不透明度从1变为0。如果我真的想要的话,可以添加另一个事件监听器,表示获取父元素,添加事件监听器。有一个事件叫做动画结束,当动画结束时会触发。
然后我可以说,好的,当动画结束后,我们将继续移除这个元素。因此,总的来说,而不是在我点击隐藏按钮时立即移除元素。
我想做的是,如果你点击一个按钮,而这个按钮是隐藏的,那么就获取父元素,而不是隐藏按钮,而是帖子本身。将其动画播放状态设置为运行,意味着运行隐藏动画,然后为整个帖子添加一个事件监听器。
当动画结束时,去掉整个帖子,从DOM中完全移除。那么现在这一切的效果是什么,进行这个动画后,如果我刷新页面,就会看到所有这些帖子。如果我试图隐藏第二个帖子,比如说,您会看到透明度变化。
然后它慢慢消失,直到完全透明时,帖子才会完全消失。所以我可以说隐藏第四个帖子,它消失了,第五个帖子跳起来填补它的位置,我可以对这些帖子做任何事,点击隐藏按钮时触发动画。
这就是动画能做的一部分价值,它能让我们的用户界面更友好,而不是立即删除一个帖子,而是通过优雅的淡出效果让它消失。即使这样做也并不完美,动画上可能会注意到的一件事是,它在帖子消失时会跳起来。
如果我隐藏第三个帖子,它就消失了,第五个帖子会突然跳起来以填补它的位置。我希望能更聪明一点,在帖子消失后稍微缩小它的大小,这样帖子不会猛地跳入位置,而是更自然地滑入。
这里我可以玩玩,也许我想说,让我将这个动画变成多部分动画。
所以在这里,我不只是从零到百分之百设置透明度从1到0,也许在动画的前75%中,会处理透明度从1降到0,但在动画的最后25%中仍然以透明度为0结束,但任何会产生垂直空间的东西我。
我希望减少到零,所以高度应该是零像素,行高,也就是文本的高度,应该也是零像素,任何内边距我希望消失,实际上我已经在帖子底部添加了一些外边距,我也想去掉它。所以我希望将所有这些设置为零,从它们的初始值开始。
最初的高度恰好是高度的百分之百,对于行高也是如此,父元素的高度最初有大约20像素的内边距和10像素的底部外边距,我希望在动画的75%处这些仍然成立,但只有在动画的最后25%中。
我希望将所有这些垂直高度属性设置为零,移除所有高度,移除行高,移除所有内边距。这样效果就是,我会有一个动画,在动画的前75%中,唯一变化的是透明度,透明度从1变为0。
完全可见到完全透明,这条帖子已经透明了,你看不见它,但它仍然占据页面上的物理空间。现在我们将减少这条帖子的高度,这样你就完全看不见它了,所以如果我刷新这个页面,这里又出现了所有帖子,但如果我点击隐藏某一特定帖子。
我们会看到它首先渐渐消失。
然后它的高度缩小,以便下一个帖子能够很顺畅地滑入位置,我可以再这样做。隐藏帖子,它是透明的,然后滑入位置。这再次只是应用了css动画的这个理念,使用动画属性使我们的界面更好用,视觉上更清晰。
有一条帖子消失了,其他帖子现在已经向上滚动,以便占据它的位置,因此我们现在能够使用javascript创建许多漂亮的用户界面,我们能够创建单页面应用程序,创造无限滚动,甚至可以创建一些动画。
但你可能意识到的一件事是,我们的应用程序开始变得相当复杂,需要很多javascript代码来操作我们不同部分的内容。