Skip to content

北工大web

更新: 12/9/2025 字数: 0 字 时长: 0 分钟

Introduction

What are we going to learn

前端三件套:

  • HTML
  • CSS
  • JavaScript

后端:

  • Flask (Python web 框架)
  • 数据库访问

进阶:

  • JQuery 和 AJAX:这是JavaScript的扩展技术,能让你更容易地操作网页,并实现不刷新整个页面的情况下与服务器进行数据交换

HTML

HTML(超文本标记语言 HyperText Markup Language)是一种标记形语言,可以通过标签(Tags)组织出多样的文本页面,定义文本的结构和内容。

标签由一些特殊的字符(ex: <> )和特殊的单词组成。

这些特殊的单词告诉浏览器这个标记应该做什么。例如,<p> 标签告诉浏览器:这里面的内容是一个段落。

一个 HTML 文档由文本内容和 HTML 元素组成。

  • 元素(Elements)
    • 一个元素可以包含文本、其他元素,也可以是空的。它是由标签来标识的。
    • 结构通常是这样的:<标签名>内容</标签名>
  • 属性 (Attributes)

一个完整HTML页面的基本结构

html
<!DOCTYPE html>
<html>
<head>
    <title>My page title</title>
</head>
<body>
    <h1>This is a big heading</h1>
    <p>This is a paragraph...</p>
</body>
</html>

让我们来分解一下这个结构:

  • <!DOCTYPE html>
    • 它不是一个 HTML 标签,而是一个 文档类型声明。
    • 它的作用是告诉浏览器,你接下来要处理的是一个 HTML 文档。
  • <html>, <head>, 和 <body>
    • <html> 元素是 根元素,它包含了文档中所有其他的 HTML 元素。
    • 一个 HTML 页面被分为两个主要部分:头部 (<head>) 和身体 (<body>) 。头部包含了页面的元数据(metadata),而身体包含了页面的实际内容(ex:文本、图片等)。

Headings

标题是用来展示文档结构的。<h1>是最高级别的标题,<h6>是最低级别的。除了用于展示,Headings 对搜索引擎优化(SEO)也很有帮助。

浏览器会为不同级别的标题提供默认的样式(比如<h1>字号最大、字体最粗),不过这些样式后续都可以通过 CSS 来自由修改。

html
<html>
<body>
    <h1>一级标题</h1>
    <h2>二级标题</h2>
    <h3>三级标题</h3>
    <h4>四级标题</h4>
    <h5>五级标题</h5>
    <h6>六级标题</h6>
</body>
</html>

Paragraphs

段落是 HTML 文档中最基本的文本单位。网页上的绝大部分正文内容,都应该放在<p>标签里。

<p>标签是一个容器,里面除了可以放文字,还可以嵌套包含其他的内联 HTML 元素(比如链接 <a> 和强调 <strong>)。

输入

html
<html>
<body>
    <p>这是一个段落。</p>
    <p>这是另一个段落。</p>
</body>
</html>

输出

这是一个段落。

这是另一个段落。

Division

<div> 是一个块级容器元素,它本身不携带任何具体的语义含义,默认默认独占一行,且宽度默认占满父容器,常用于分组和组织内容。

html
<div>
    <p>这是一个段落。</p>
    <p>这是另一个段落。</p>
</div>

链接可以让用户从一个页面跳转到另一个页面,或者跳转到同一页面的不同位置。

一个链接包含两个主要部分:目标地址 (destination) 和 显示文本 (label) 。

html
<a href="https://www.baidu.com">这是一个链接</a>

<a> 标签功能非常强大,可以创建多种链接 :

  • 链接到外部网站
  • 链接到自己网站内部的其他页面
  • 链接到当前页面的其他位置(页内跳转)
  • 触发邮件客户端
  • 触发执行 JavaScript 函数(一种运行在浏览器中的程序,后续会介绍)

在链接到外部网站时,需要使用绝对路径(absolute URL)。

html
<a href="https://www.baidu.com">百度</a>

而在链接到自己网站内部的其他页面时,可以使用相对路径(relative URL) 。

html
<!-- 会链接到项目根目录下的 about.html -->
<a href="/about.html">关于我们</a>

Empty Elements

大部分 HTML 元素都是一个容器,可以嵌套其他内容。但有些元素不能嵌套任何东西,它们被称为空元素。

常见例如:

  • <br>: 强制换行
  • <hr>: 插入一条水平分割线
  • <img>: 插入一张图片

Images

当图片是网页的实际内容时(ex: 产品图、照片)就应该使用<img>标签。如果图片仅仅是为了装饰(ex: 背景图案)那么更推荐用 CSS 来处理。

重要属性:

  • src: 图片的路径(同样可以是绝对路径或相对路径)
  • alt: 图片加载失败时显示的替代文本
  • title: 鼠标悬停在图片上时显示的提示文字
  • width / height: 设置图片的宽度和高度
html
<img src="path/to/image.jpg" 
alt="描述图片内容" title="图片标题" width="600" height="400">

当你需要为一张图片配上标题说明时,推荐使用<figure>元素把它包裹起来,并用<figcaption>来写标题。这样做语义更清晰。

html
<figure>
    <img src="path/to/image.jpg" alt="描述图片内容">
    <figcaption>图片标题</figcaption>
</figure>

TIP

不是所有图片都需要用 <figure> 包裹,只有那些作为独立单元且需要标题说明的内容才适合使用 。

Lists

当您需要列出一些项目时,使用列表可以让内容看起来更清晰。HTML 提供了三种类型的列表,我们主要学习最常用的两种。

无序列表 (Unordered / Bulleted Lists)

整个列表用 <ul> 标签包裹,每个列表项用 <li> 标签。

输入:

html
<ul>
    <li>列表项 1</li>
    <li>列表项 2</li>
    <!-- 对于简单的列表项,可以省略 </li>标签 -->
    <li>列表项 3
</ul>

输出:

  • 列表项 1
  • 列表项 2
  • 列表项 3

有序列表 (Ordered Lists)

整个列表用 <ol> 标签包裹,每个列表项用 <li> 标签。

输入

html
<ol>
    <li>第一项</li>
    <li>第二项</li>
    <li>第三项
</ol>

输出

  1. 第一项
  2. 第二项
  3. 第三项

Tables

HTML 表格用于显示由行和列组成的网格数据。

基本结构:一个表格由以下几个核心标签构成:

  • <table>: 整个表格的容器
  • <tr>: 代表表格中的一行 (Table Row)
  • <td>: 代表行一个普通的单元格 (Table Data),普通的数据都放在这里
  • <th>: 代表表头单元格 (Table Header),用于定义列或行的标题 ,通常会加粗并居中显示

输入

html
<table>
    <tr>
        <th>干员姓名</th>
        <th>干员种类</th>
        <!-- 对于简单的表格,也可以省略结尾 </th>标签 -->
        <th>分支
    </tr>
    <tr>
        <td>Amiya</td>
        <td>术士</td>
        <!-- 同理省略 -->
        <td>本源术士
    </tr>
    <tr>
        <td>Exusiai
        <td>狙击
        <td>速射手
    </tr>
    <tr>
        <td>Skadi
        <td>近卫
        <td>无畏者
    </tr>
</table>

输出

干员姓名干员种类分支
Amiya术士本源术士
Exusiai狙击速射手
Skadi近卫无畏者

使用 colspanrowspan 属性,可以让单元格跨越多列或多行。

输入

html
<table>
    <tr>
        <th>姓名</th>
        <th colspan=2>联系方式</th>
    </tr>
    <tr>
        <td>歼灭型哥布林</td>
        <td>电话</td>
        <td>123456789</td>
    </tr>
    <tr>
        <td>佩佩</td>
        <td>Email</td>
        <td>lisi@example.com</td>
    </tr>
</table>

输出

姓名联系方式
歼灭型哥布林电话123456789
佩佩Emaillisi@example.com

Blockquote

<blockquote> 标签可以用来表示一段引用的文字,通常会缩进显示,以区别于普通段落。

输入

html
<blockquote>
    比深渊更加深邃的漆黑,绽放吾之真红吧!
</blockquote>

输出

比深渊更加深邃的漆黑,绽放吾之真红吧!

Forms

前面的所有标签都是为了展示信息,而表单则是为了收集信息,是让网页与用户进行交互的桥梁。

一个表单通常包含各种类型的输入控件,比如文本框、单选按钮、复选框、下拉菜单等。

表单让一个网页变得可以交互 。它包含两个部分:

  • 前端:用 HTML 写的、用户能看到的表单界面
  • 后端:是一种运行在服务器上的程序(比如用 Python, Java 等语言编写),负责接收和处理前端发来的数据

表单的核心标签:<form>

  • 所有的表单控件都必须放在 <form> 标签内部。
  • action 属性:这个属性至关重要,它指定了服务器上接收表单数据的程序的URL(地址)。

常见的表单控件:

  • 单行文本输入框:<input type="text">

    这是最常用的输入框,用于输入单行的文本,比如用户名、搜索关键词等。

    name 属性:每个表单控件都应该有一个 name 属性,它相当于这个数据的标签,后端程序通过该属性来识别是哪个输入框的数据。

    value 属性:这个属性用来设置输入框的默认值。

  • 提交按钮:<input type="submit">

    用于提交整个表单的按钮。当用户点击它时,表单数据就会被发送到 action 属性指定的地址。

    输入

    html
    <form action="/submit_form">
        用户名:
        <input type="text" name="username" value="默认用户名">
        <br>
        email:
        <input type="text" name="email" placeholder="请输入邮箱">
        <br>
        密码:
        <input type="password" name="password">
        <br>
        <input type="submit" value="提交">
    </form>

    输出

    用户名:
    email:
    密码:
  • 多行文本输入框:<textarea>

    当需要用户输入多行文本时(如评论、留言),使用 <textarea>

    可以用 rowscols 属性来控制它的大小。

    html
    <textarea name="comments" rows="4" cols="50">输入评论...</textarea>
  • 单选按钮:<input type="radio">

    用于提供多个选项,但只允许用户选择其中一个 。

    关键点:要让一组单选按钮成为互斥的整体,它们必须拥有 完全相同的 name 属性。

    输入

    html
    <!-- checked  关键词使单选按钮为默认选中选项 -->
    <input type="radio" name="gender" value="male" checked><br>
    <input type="radio" name="gender" value="female">

    输出


  • 复选框:<input type="checkbox">

    用于提供多个选项,允许用户 选择零个、一个或多个。

    输入

    html
    <input type="checkbox" name="hobby" value="reading">阅读
    <input type="checkbox" name="hobby" value="traveling">旅行
    <input type="checkbox" name="hobby" value="sports">运动

    输出

    阅读 旅行 运动
  • 下拉选择列表:<select><option>

    使用 <select> 来创建一个下拉列表。

    列表中的每一个选项都用一个 <option> 标签来定义。

    value 属性:通常我们会给每个 <option> 添加一个 value 属性。提交表单时,被选中选项的 value 值会被发送到服务器。如果没有 value,则发送选项的文本内容。

    html
    <select name="country">
        <option value="us">美国</option>
        <option value="cn">中国</option>
        <option value="uk">英国
    </select>

    可以通过 <optgroup> 标签来对选项进行分组,使得下拉列表更有层次感。

    输入

    html
    <select name="fruits">
        <optgroup label="热带水果">
            <option value="mango">芒果</option>
            <option value="pineapple">菠萝</option>
        </optgroup>
        <optgroup label="温带水果">
            <option value="apple">苹果</option>
            <option value="pear"></option>
        </optgroup>
    </select>

    输出

  • 分组控件:<fieldset><legend>

    <fieldset>可以为表单中逻辑相关的一组控件画上一个边框。

    <legend>则为这个分组提供一个标题。

    html
    <fieldset>
        <legend>个人信息</legend>
        姓名:<input type="text" name="name"><br>
        年龄:<input type="text" name="age"><br>
    </fieldset>

HTML5新增的输入类型:

HTML5增加了很多智能的输入类型,它们能提供更好的用户体验和内置的数据验证。

  • 文件上传: <input type="file">
  • 数字输入: <input type="number">,可以设置最大/最小值
  • 范围滑块: <input type="range">
  • 日期和时间: <input type="date">, <input type="time">
  • 颜色选择器: <input type="color">

此外你可以使用 <label> 标签来为表单控件提供描述,且当用户点击标签内容时,相关联的输入控件会获得焦点(表现为也被点击),这提升了可用性。

html
<!-- 通过 id 显示关联(推荐) -->
 <label for="usernameInput">Username:</label>
<input type="text" id="usernameInput" name="username">

<!-- 隐式关联 -->
 <label>
  Email:
  <input type="email" name="email">
</label>

为元素赋予 id 和 class

通过给元素添加 idclass 属性,可以在将来使用 CSS 和 JavaScript 更方便地选中和操作这些元素。

html
<!-- 使用 id -->
<div id="header">
    <h1>这是一段文字。</h1>
    <p>o.0</p>
</div>

<!-- 使用 class -->
<div class="content">
    <p>这是一些内容。</p>
</div>

id 是元素的唯一标识符,在整个 HTML 文档中只能出现一次。而 class 则可以被多个元素共享,用于标记一类元素。

CSS

CSS 是一种用来描述 HTML 元素外观和表现形式 (presentation) 的标准。

在 HTML 中使用 CSS 主要使用外部样式表 (External Style Sheet) 的方式:

  • 我们将所有的 CSS 规则写在一个单独的 .css 文件里。
  • 然后在 HTML 文件的 <head> 部分,使用 <link> 标签来引入这个 CSS 文件。
html
<head>
    <link rel="stylesheet" href="styles.css">
</head>

此外还有内部样式表 (Internal Stylesheet) 法和行内样式 (Inline Styles) 法,但不推荐使用。

html
<!-- 内部样式表 -->
<head>
    <style>
        body {
            background-color: lightblue;
        }
    </style>
</head>
<!-- 行内样式 -->
<p style="color: red;">这是一个红色的段落。</p>

CSS 语法基础

一个 CSS 文件由一条或多条样式规则 (style rules) 组成 。

每一条规则都包含两个部分:选择器 (Selector) 和 声明块 (Declaration Block) 。

  • 选择器:指定要应用样式的 HTML 元素。
  • 声明块:包含一个或多个声明 (declarations),每个声明由属性 (property) 和 值 (value) 组成。
css
/* h1 是选择器 */
h1 {
    /* "color: blue;" 是一条声明 */
    /* "color" 是属性, "blue" 是值 */
    color: blue;
    background-color: yellow;
    border: 1px solid black;
}

/* p 是选择器 */
p {
    color: red;
}

选择器

首先,为了能让CSS选中元素,我们通常会在HTML里给元素加上两种“标签”:idclass

  • id - 元素的“身份证号”,在整个 HTML 页面中必须是唯一的。
  • class - 元素的“标签”或“分组名”,可以被多个元素重复使用。
  1. 基础选择器 (Basic Selectors)

    css
    /* 元素选择器 (Element Selector) */
    /* 选择所有的 <p> 元素 */
    p {
        color: red;
    }
    
    /* 分组选择器 (Grouped Selectors) */
    /* 选择所有的 <h1> 和 <h2> 元素 */
    h1, h2 {
        font-family: Arial, sans-serif;
    }
    
    /* 类选择器 (Class Selector) */
    /* 选择所有 class="highlight" 的元素 */
    .highlight {
        background-color: yellow;
    }
    
    /* ID 选择器 (ID Selector) */
    /* 选择 id="main-title" 的元素 */
    #main-title {
        font-size: 24px;
    }
  2. 关系选择器 (Contextual selectors)

    这类选择器通过元素之间的层级或兄弟关系来筛选元素,非常强大。

    css
    /* 后代选择器 (Descendant Selector) */
    /* 选择所有 <div> 内的 <p> 元素 */
    div p {
        color: blue;
    }
    
    /* 子元素选择器 (Child Selector) */
    /* 选择所有 <ul> 的直接子元素 <li> */
    ul > li {
        /*ul > li { ... } 只会选中<ul>的直接子元素<li>,
        如果<li>内部还嵌套了另一个列表,那个列表里的<li>则不会被选中。*/
        list-style-type: square;
    }
    
    
    /* 相邻兄弟选择器 (Adjacent Sibling Selector) */
    /* 它会选中紧跟在某个元素之后的那个同级元素 。*/
    /* 选择紧跟在 <h1> 后面的第一个 <p> 元素 */
    h1 + p {
        margin-top: 0;
    }
    
    /* 普通兄弟选择器 (General Sibling Selector) */
    /* 它会选中紧跟在某个元素之后的所有同级元素 。*/
    /* 选择所有紧跟在 <h1> 后面的 <p> 元素 */
    h1 ~ p {
        color: green;
    }
  3. 伪类选择器 (Pseudo-class Selectors)

    伪类用于定义元素的特殊状态,比如鼠标悬停、链接被访问过等。

    最常见的用法是定义链接 <a> 标签在不同状态下的样式:

    css
    a:link {
        color: blue; /* 未访问的链接 */
    }
    a:visited {
        color: purple; /* 已访问的链接 */
    }
    a:hover {
        color: red; /* 鼠标悬停时 */
    }
    a:active {
        color: orange; /* 点击时 */
    }

常用 CSS 属性

  1. 文本颜色

    css
    color: red; /* 设置文本颜色 */
    /* 颜色可以用颜色名称、十六进制值、RGB值、RGBA值等多种方式表示 */
  2. 背景

    我们可以为元素设置背景颜色或背景图片。

    css
    background-color: lightblue; /* 设置背景颜色 */
    background-image: url('path/to/image.jpg'); /* 设置背景图片 */
    background-size: cover; /* 让背景图片覆盖整个元素 */
    background-repeat: no-repeat; /* 不重复背景图片 */
    /* 
    repeat: 默认值,在水平和垂直方向重复。
    repeat-x: 只在水平方向重复 。
    repeat-y: 只在垂直方向重复 。
    no-repeat: 不重复 。
    */
    background-position: center; /* 背景图片居中 */

    背景简写 (Shorthand):为了方便,我们可以把所有背景相关的属性写在一个 background 属性里。

    css
    background: lightblue url('path/to/image.jpg') no-repeat center/cover;
  3. 边框

    边框用于在元素的周围创建线条,以区分或突出内容。

    边框主要有三个属性可以设置:

    css
    /* 边框宽度 */
    /* 顺时针规则 :上 右 下 左 */
    /* 此处为:上下边框宽度为2px,左右边框宽度为4px */
    border-width: 2px  4px;
    /* 边框样式 */
    /* 比如 solid (实线), dotted (点状线), dashed (虚线) */
    border-style: solid;
    /* 边框颜色 */
    border-color: black;
  4. 文本与字体

    css
    /* text-align: 设置文本的水平对齐方式,
    可选值有 left, right, center, justify (两端对齐) 。*/
    text-align: center;
    /* 设置或移除文本的装饰线。
    最常见的用法是 a { text-decoration: none; },用来去掉链接默认的下划线 。*/
    text-decoration: underline;
    /* 设置字体。
    为了确保所有用户都能正常显示,我们通常会提供一个备选字体列表。*/
    font-family: Arial, Helvetica, sans-serif;
    /* 设置字体大小 */
    /* 为了更好的可访问性和响应式设计,
    推荐使用 相对单位,如 em 或 %,而不是 px 这样的固定单位。*/
    font-size: 1em;
  5. 列表与表格样式

    列表:使用 list-style-type 属性可以更改列表项前面的标记。ex:ul { list-style-type: square; } (将无序列表的圆点变为方块) 。

    表格: 默认情况下,表格单元格的边框是分开的。使用 border-collapse: collapse; 可以将它们合并为单一边框,看起来更整洁。

盒子模型 (Box Model)

在 CSS 看来,页面上的 每一个 HTML 元素都是一个矩形的盒子 。无论是段落、标题,还是图片,它们都存在于自己的盒子里。

这个盒子由内到外分为四个层次:

  1. 内容区域 (Content Area):盒子的核心部分,显示实际内容(文本、图片等)。受 widthheight 属性控制。
  2. 内边距 (Padding):内容区域与边框之间的空间。受 padding 属性控制。
  3. 边框 (Border):包围内边距和内容的线条。受 border 属性控制。
  4. 外边距 (Margin):盒子与其他元素之间的空间。受 margin 属性控制。

盒子实际占用的空间:

  • 总宽度 = width + 左右padding + 左右border + 左右margin
  • 总高度 = height + 上下padding + 上下border + 上下margin

盒子的类型 display 属性:

display 是控制布局最重要的 CSS 属性之一,它决定了盒子在页面上如何表现。

  • block:块级元素。

    特点:会独占一行,宽度默认会撑满父容器的整个宽度 。<div><p> 都是典型的块级元素。

  • inline:内联元素。

    特点:不会独占一行,会和其他内联元素在同一行内从左到右排列,直到排不下为止。<span><a> 都是典型的内联元素。

css
/* 让所有的 <div> 元素表现得像内联元素 */
div {
    display: inline;
}

TIP

display: none; 会让元素在页面上彻底消失,就好像它在 HTML 中从未存在过一样,并且不占用任何空间。

Flask 简介

Flask 是一个用 Python 编写的微框架 (micro-framework) 。

“微”意味着它非常轻量、简洁,只提供了 Web 开发最核心的功能。它不包含数据库访问层、用户认证系统等复杂功能,这些都需要我们根据需求自己添加。

TIP

在开始写代码前,最好搭建一个干净、独立的环境。

在激活了的虚拟环境中,使用 Python 的包管理工具 pip 来安装 Flask :

bash
(flaskenv) microblog> pip install flask

我们来看一个最简单的 Flask 应用长什么样。

hello.py
python
# 1. 从 flask 库中导入 Flask 这个类
from flask import Flask

# 2. 创建一个 Flask 应用的实例 (instance)
app = Flask(__name__)

# 3. 定义一个路由 (Route)
@app.route("/")
# 4. 定义一个视图函数 (View Function)
def hello_world():
    # 5. 返回要显示在浏览器上的内容
    return "<p>Hello, World!</p>"
  • 路由 @app.route("/"): 这是一个 装饰器。它的作用是建立 URL 和函数之间的关联。这行代码的意思是:当有用户访问网站的根目录 (/) 时,就执行它下面的那个函数。

  • 视图函数 def hello_world(): 这个函数负责处理用户的请求,并生成要返回给浏览器的响应。函数的返回值就是用户将在浏览器上看到的内容。

接下来运行这个 Flask 应用:

  1. 在命令行中,告诉 Flask 你的应用入口是哪个文件:

    bash
    export FLASK_APP=hello.py  # Mac/Linux
    set FLASK_APP=hello.py     # Windows
  2. 运行 Flask 内置的开发服务器:

    bash
    flask run
  3. 打开浏览器,访问http://127.0.0.1:5000/,你就能看到 "Hello, World!" 了。

Flask 路由参数

在 Flask 中,你不仅可以定义固定的 URL 路径(ex: /),还可以定义包含可变部分的 URL 来处理动态内容。

@app.route() 装饰器中,你可以使用尖括号 < > 来标记 URL 中的可变部分。 例如:

python
@app.route("/user/<username>")
def show_user_profile(username):
    return f"User: {username}"

当用户访问 /user/Amiya 时,username 参数会被设置为 "Amiya",函数会返回 User: Amiya

默认情况下,<username> 中的变量被视为字符串。但你可以明确指定期望的类型,Flask 会自动进行类型转换和验证。例如:

python
@app.route("/post/<int:post_id>")
def show_post(post_id):
    return f"Post ID: {post_id}"

常用的转换器还有:

  • string:默认类型,匹配除斜杠 / 之外的任意文本
  • int:匹配整数
  • float:匹配浮点数
  • path:类似于 string,但可以匹配包 /
  • uuid:匹配 UUID 字符串

模板 (Templates)

直接在 Python 函数里返回 HTML 字符串是一种非常糟糕的方式,难以阅读和维护 。为此,Flask 自带了一个强大的模板引擎,叫做 Jinja2。

Jinja2 模板语法

在模板 .html 文件中,我们可以使用特殊的语法来嵌入动态数据和逻辑。

python
# 变量: 使用 {{ ... }} 来显示从 Python 传过来的变量
# 示例: <h1>Hello, {{ user.username }}!</h1>
template_variable = "<h1>Hello, {{ user.username }}!</h1>"

# 控制结构: 使用 {% ... %} 来写逻辑,比如条件判断和循环
# 条件判断: {% if user %} ... {% else %} ... {% endif %}
template_condition = """
{% if user %}
    <p>欢迎, {{ user.username }}!</p>
{% else %}
    <p>请先登录</p>
{% endif %}
"""

# 循环: {% for post in posts %} ... {% endfor %}
template_loop = """
{% for post in posts %}
    <div>
        <h3>{{ post.title }}</h3>
        <p>{{ post.content }}</p>
    </div>
{% endfor %}
"""

render_template() 函数

在视图函数中,我们不再 return 一个字符串,而是使用 render_template() 函数来渲染模板。

python
from flask import render_template

@app.route('/')
def index():
    user = {'username': 'Vivek'}
    # 参数一:文件地址,flask 会使用在 templates 文件夹下对应的 html 文件 
    # 后续参数:想传递给模板的数据
    return render_template('index.html', title='Home', user=user)
Details
templates/index.html
html
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ title }} - 我的博客</title>
</head>
<body>
    <h1>Hello, {{ user.username }}!</h1>
</body>
</html>

模板继承 (Template Inheritance)

这是一个非常强大的功能,可以减少大量重复的 HTML 代码。

实现方式:

  • base.html 中,用 {% block content %}{% endblock %} 来定义一个“坑”,这个“坑”里的内容是每个页面自己独有的。
  • 在其他的子模板中(如 index.html),首先用 {% extends "base.html" %} 来声明它继承自基础模板。
  • 然后,将这个页面独有的内容写在 {% block content %} ... {% endblock %} 之间,Jinja2 会自动把这部分内容填到父模板的坑里。
html
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}我的网站{% endblock %}</title>
</head>
<body>
    <header>
        <h1>欢迎来到我的网站</h1>
    </header>
    <nav>
        <a href="/">首页</a>
        <a href="/about">关于我们</a>
    </nav>
    <main>
        {% block content %}{% endblock %}
    </main>
    <footer>
        <p>版权所有 &copy; 2024</p>
    </footer>
</body>
</html>
html
{% extends "base.html" %}

{% block title %}首页 - 我的博客{% endblock %}

{% block content %}
    <h2>最新文章</h2>
    <p>这里是首页的独有内容。</p>
{% endblock %}

WTForms

当 HTML 提交 <form> 表单时,后端需要做很多事:接收数据、检查数据是否有效、若无效则返回错误信息并重新显示表单等。手动处理这些逻辑繁琐又易错,因此我们可以借助 Flask-WTF 扩展来简化这些工作。

安装 Flask-WTF

bash
pip install flask-wtf

实现 CSRF 保护

Flask-WTF 能自动帮我们抵御一种常见的网络攻击——CSRF (跨站请求伪造)。如果不清楚什么是 CSRF,你暂时只需要知道 Flask-WTF 能让表单更加安全。

为了实现 CSRF 保护,Flask-WTF 需要一个加密密钥 (Cryptographic Key),你可以在应用配置中设置它:

app.py
python
from flask import Flask

app = Flask(__name__)
app.config['SECRET_KEY'] = '你自己的随机密钥'

# 同级目录下的 routes.py
from blogapp import routes

你也可以将配置和应用实例代码写在不同的 .py 中:

python
from flask import Flask
from config import Config # 或者: from .config import Config

app = Flask(__name__)
app.config.from_object(Config)

from blogapp import routes
python
import os

class Config:
    # 从环境变量中获取 SECRET_KEY
    SECRET_KEY = os.environ.get('SECRET_KEY') or '你自己的随机密钥'

在 Python 中定义表单

我们通过一个 Python类 来定义表单的结构:

forms.py
python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired

class LoginForm(FlaskForm):
    # 每个变量代表一个表单字段
    # validators 接受一个验证器列表
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Sign In')
  • StringField, PasswordField 等定义了输入框的类型。
  • 第一个参数(ex: 'Username')是这个字段的标签 (label)。
  • validators=[DataRequired()] 是一个 验证器,它告诉 WTForms,这个字段必须被填写,不能为空。

接着我们在在 HTML 模板中渲染表单:

python
# 在视图函数(routes.py)中,我们实例化一个 LoginForm 类
from .forms import LoginForm

@app.route('/login')
def login():
    form = LoginForm()
    return render_template('login.html', form=form)
html
{% extends "base.html" %}

{% block title %}登录{% endblock %}

{% block content %}
    <h2>登录</h2>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}  {# CSRF 保护 #}
        <p>
            {{ form.username.label }}
            {{ form.username(size=32) }}  {# size 控制输入框宽度 #}
        </p>
        <p>
            {{ form.password.label }}
            {{ form.password(size=32) }}
        </p>
        <p>
            {{ form.remember_me() }} 
            {{ form.remember_me.label }}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

目前表单已经可以显示了,但程序还不能处理用户提交的数据,所以接下来需要修改 login 视图函数:

routes.py
python
from flask import flash, redirect

#  1. 允许POST请求
@app.route('/login', methods=['GET', 'POST']) 
def login():
    form = LoginForm()

     # 2. 验证并处理数据  
    if form.validate_on_submit():
        flash(f'Login requested for user {form.username.data}')
        return redirect('/index') # 3. 重定向到首页

    # 4. 如果是GET请求或验证失败,则正常显示表单
    return render_template('login.html', title='Sign In', form=form)
  • methods=['GET', 'POST']:默认路由只接受GET请求(即用户访问页面)。加上 POST 使其能接收用户提交的表单数据。
  • form.validate_on_submit()
    • 当用户通过 GET 请求第一次访问 /login 页面时,这个函数会直接返回 False
    • 当用户填写表单并点击提交(发送 POST 请求)时,这个函数会自动运行所有验证器。如果所有数据都有效,返回 True
  • flash()redirect():在成功处理数据后,我们使用 flash() 来显示一条成功消息,然后使用 redirect() 将用户重定向到另一个页面。

如果 form.validate_on_submit() 因为数据无效而返回 False,WTForms 会自动在 form 对象中填充错误信息。我们只需在模板中把它们显示出来:

login.html
html
{% extends "base.html" %}

{% block title %}登录{% endblock %}

{% block content %}
    <h2>登录</h2>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}  {# CSRF 保护 #}
        <p>
            {{ form.username.label }}
            {{ form.username(size=32) }}  {# size 控制输入框宽度 #}

            <!-- <span> 标签是 HTML 中最常用的行内容器元素,
                用于对文档中的小部分内容进行分组和样式化 -->
            {% for error in form.username.errors %}  
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %} 
        </p>
        <p>
            {{ form.password.label }}
            {{ form.password(size=32) }}

            {% for error in form.password.errors %} 
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %} 
        </p>
        <p>
            {{ form.remember_me() }} 
            {{ form.remember_me.label }}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

注意到类似于 return redirect('/login')<a href="/index">Home</a> 会产生硬编码的问题。

ex:

如果我们想要修改 /login 页面的 url 改为 /auth/signin,就不得不去项目里的每一个 文件中查找并修改这个链接。

我们可以使用函数 url_for() 来解决这个问题:

python
@app.route('/auth/signin', methods=['GET', 'POST'])
def login():
    # ...
    # url_for() 会根据视图函数自动生成 url 地址
    return redirect(url_for('index'))
html
<a href="{{ url_for('index') }}">Home</a>

在 Flask 中管理静态文件

在 Web 开发中,静态文件指的是那些内容固定、不需要由后端 Python 代码动态生成的文件。它们主要包括:

  • CSS 样式表 (.css 文件)
  • JavaScript 脚本 (.js 文件)
  • 图片 (.jpg, .png, .gif 等)
  • 字体文件
  • 有时也包括一些无需修改的、纯粹展示用的 HTML 文件

为了更好地组织项目,Flask 框架有一个约定:所有的静态文件,都应该放在一个名为 static 的文件夹里。

接下来,我们就可以通过 url_for() 函数链接到静态文件。

基本语法:url_for('static', filename='文件在static文件夹内的路径')

  1. 在导航栏中添加一个指向静态页面的链接

    假设在 blogapp/static/ 文件夹下,直接放了一个名为 plain.html 的文件。现在想在导航栏里加一个链接指向它。

    html
    <!-- url_for('static', ...): 
    第一个参数' static' 是一个特殊的指令,它告诉 Flask:
    我要找一个静态文件,到 static 文件夹里去定位。 -->
    
    <!-- filename='plain.htm': 
    第二个参数 filename 告诉 Flask 具体要找哪个文件。 -->
    <a href="{{ url_for('static', filename='plain.html') }}">Plain Page</a>
  2. <head> 中引入CSS样式表

    通常我们会把 CSS 文件放在 static 文件夹内部的一个子文件夹里,比如 style,这样结构更清晰。假设 CSS 文件路径是 blogapp/static/style/mystyle.css

    base.html
    html
    <head>
        <link rel="stylesheet" href="{{ url_for('static', filename='style/mystyle.css') }}">
    </head>

相比硬编码的方式, url_for() 函数会自动生成正确的链接,确保您的网站在任何环境下都能正常工作。

Flask 操作数据库

我们将使用一个流行的 Flask 扩展来操作数据库,叫做 Flask-SQLAlchemy。

其次我们将使用 SQLite 数据库。它的优点是极其简单,因为它不需要运行一个独立的服务器,整个数据库就是一个普通的文件。对于 Python 3 来说,它甚至是内置的,无需额外安装。

安装 Flask-SQLAlchemy

bash
pip install flask-sqlalchemy

接下来在 config.py 中添加数据库配置:

config.py
python
import os
from pathlib import Path

"""
Path(__file__) 创建一个指向当前文件 (config.py) 的路径对象。
resolve() 将其转换为一个绝对路径,解决了所有符号链接等问题。
parent 直接获取该文件所在的父目录。
"""
BASE_DIR = Path(__file__).resolve().parent

class Config:
    """
    基础配置类,存放所有环境共用的配置。
    """
    SECRET_KEY: str = os.environ.get('SECRET_KEY') or '你自己的随机密钥'
    
    # 连接到 SQLite 数据库,先从环境变量中获取 DATABASE_URL
    # 如果没有设置该环境变量,则使用项目目录下的 blogdb.db 文件
    SQLALCHEMY_DATABASE_URI: str = os.environ.get('DATABASE_URL') or \
        f"sqlite:///{BASE_DIR / 'blogdb.db'}"

    # 关闭事件追踪功能节约性能,在新的 Flask-SQLAlchemy 中已被弃用
    SQLALCHEMY_TRACK_MODIFICATIONS: bool = False

__init__.py 中进行初始化:

__init__.py
python
from flask import Flask
from blogapp.config import Config
from flask_sqlalchemy import SQLAlchemy # 1. 导入

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app) # 2. 创建数据库实例

from blogapp import routes, models # 3. 导入自己编写的 models

ORM:用 Python 类定义数据库结构

通过 ORM,我们不再需要写 SQL 的 CREATE TABLE 语句,而是通过 Python 类来定义数据表的结构。

models.py
python
from blogapp import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    # ... 稍后会添加关系 ...

    def __repr__(self):
        return f'<User {self.username}>'
  • User(db.Model):所有的模型类都必须继承自 db.Model
  • db.Column(...):类中的每一个 db.Column 实例都代表表中的一个字段(一列)。
  • primary_key=True:将 id 字段设置为主键,它是每一条记录的唯一标识。
  • unique=True:保证了 usernameemail 字段在表中不能有重复值。
  • index=True:为该字段创建一个索引,加快查询速度。

TIP

def __repr__(self): 是 Python 中的一个特殊方法(魔术方法),用于定义对象的官方字符串表示。

相比 __str__ 方法,__repr__ 更侧重于为开发者提供调试信息,通常包含更多细节,便于识别对象的状态和属性。

使用 db.ForeignKey() 方法可以建立表之间的关系:

models.py
python
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    # 1. 定义“一对多”关系
    posts = db.relationship('Post', backref='author', lazy='dynamic') # 懒加载

    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True) 
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    # 2. 定义外键
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return f'<Post {self.body}>'

db.relationship(...) 不是一个数据库中的真实字段。 它是SQLAlchemy提供的一个高层级关系视图:

  • posts = db.relationship(...):这行代码让我们可以通过一个 User 对象,用 user.posts 的方式轻松获取该用户发表的所有帖子。
  • backref='author':这是一个反向引用。它会自动为 Post 模型添加一个 .author 属性,让我们可以通过 post.author 的方式轻松获取发表该帖子的 User 对象。

初始化数据库

现在 models.py 里已经定义好了两个模型,但物理的数据库文件(blogdb.db)还不存在。我们需要一个命令来根据模型创建这个文件和里面的数据表。

我们使用 flask shell 命令进入一个特殊的交互式 Python 环境。这个环境会自动加载我们的 Flask 应用,让我们可以在其中执行命令。

bash
(flaskenv) microblog> flask shell
>>> from blogapp import db
>>> db.create_all()  # 创建所有数据表
>>> exit()

发生了什么?

执行 db.create_all() 后,Flask-SQLAlchemy 会找到所有继承自 db.Model 的类(也就是 UserPost),并在 config.py 指定的位置创建一个名为 blogdb.db 的 SQLite 文件,同时在文件内根据我们的模型定义创建好 userpost 两张表。

增加

操作数据库也需要回到 flask shell 环境中进行。

写入操作需要依赖于 Session(会话)执行。

  • 会话 (Session) :您可以把 db.session 想象成一个暂存区,做的所有与写入有关的操作(增加、修改、删除)都先被放进这个暂存区。

  • 只有当您执行 db.session.commit() 时,这些更改才会被一次性地、永久地写入到数据库文件中。

bash
(flaskenv) microblog> flask shell
>>> from blogapp.models import User, Post
# 创建一个 User 对象
>>> u1 = User(username='vivek', email='vivek.nallur@ucd.ie') 
>>> db.session.add(u1)  # 把这个对象添加到会话中
>>> db.session.commit()  # 提交会话,写入数据库

读取

SQLAlchemy 为我们所有的模型类都提供了一个 query 属性,它是所有查询的入口点。

查询方法说明
users = User.query.all()返回一个包含所有 User 对象的列表
u = User.query.get(1)根据主键获取单个 User 对象
peter = User.query.filter_by(username='peter').first()简单的过滤查询,返回第一个匹配的 User 对象
User.query.order_by(User.username).all()按照用户名排序,返回包含所有 User 对象的列表

使用关系

bash
(flaskenv) microblog> flask shell
>>> from blogapp.models import User, Post
>>> u = User.query.get(1) # 获取 id=1 的用户
>>> p = Post(body='Flask!', author=u) # 创建一个 Post 对象,并指定作者
# 添加并提交
>>> db.session.add(p)
>>> db.session.commit()

反过来,我们也可以轻松地通过用户找到他的所有帖子:

python
# u.posts 就是我们之前定义的 relationship
for p in u.posts.all():
    print(p.body)

删除

bash
>>> u = User.query.get(1)
>>> db.session.delete(u)  # 删除该用户
>>> db.session.commit()

结合 Flask 表单和数据库

通过一个用户注册的例子,来展示如何将 Flask-WTF 表单和数据库操作结合起来。

首先定义一个表单,包含用户名、邮箱、密码、重复密码和同意规则的字段,并为它们都添加了 DataRequired() 验证器,确保用户必须填写所有项目:

blogapp/forms.py
python
# ...

# 在 forms.py 中新增
class SignupForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    password2 = PasswordField('Repeat Password', validators=[DataRequired()])
    accept_rules = BooleanField('I accept the site rules', validators=[DataRequired()])

    submit = SubmitField('Register')

接下来,我们需要一个 HTML 页面来向用户展示这个表单:

blogapp/templates/signup.html
html
{% extends "base.html" %}
{% block content %}
    <h1>Register a New User</h1>
    <form action="" method="post">
        <!-- CSRF 保护 -->
        {{ form.hidden_tag() }}

        <!-- 用户名 -->
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}

            <!-- 错误信息 -->
            {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>

        <!-- 省略重复部分 -->

        <!-- 重复密码 -->
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}

            {{ form.password2.label }}<br>
            {{ form.password2(size=35) }}

            <!-- 错误信息 -->
            {% for error in form.password2.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>

        <!-- 同意规则和提交 -->
        <p>{{ form.accept_rules() }} {{ form.accept_rules.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

我们需要创建一个新的视图函数来处理注册逻辑:

blogapp/routes.py
python
# ...

# 在 routes.py 中新增 (需要先导入 SignupForm, User, db, flash, redirect, url_for)
@app.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()

    # 验证表单数据
    if not form.validate_on_submit():
        return render_template('signup.html', title='Register a new user', form=form)

    # 检查两次输入的密码是否一致
    if form.password.data != form.password2.data:
        flash('Passwords do not match!')
        return redirect(url_for('signup'))

    # 创建 User 对象并存入数据库
    user = User(username=form.username.data, email=form.email.data)
    # (注意:实际应用中需要对密码进行哈希处理,这里为简化省略了)
    db.session.add(user)
    db.session.commit()

    # 显示成功消息并重定向到登录页面
    flash(f'User registered with username: {form.username.data}')
    return redirect(url_for('login'))

最后一步,我们需要在网站的导航栏中添加一个链接,让用户能找到这个新的注册页面。

打开 base.html,在导航区添加一个指向 signup 页面的链接:

blogapp/templates/base.html
html
<nav>
    <a href="{{ url_for('index') }}">首页</a>
    <a href="{{ url_for('about') }}">关于我们</a>
    <a href="{{ url_for('signup') }}">注册</a> <!-- 新增 -->
</nav>

结束!

Flask 记录用户状态

session(会话)是 Flask 提供的一个像字典一样的对象,可以在一次请求中往里面存入数据,然后在后续的该用户的其他请求中,再把这些数据取出来。

Flask 会在后台通过加密的 cookie 来安全地处理这些信息。

blogapp/routes.py
python
from flask import session

# ...

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()

    if form.validate_on_submit():
        # 假设验证通过,记录用户登录状态
        session['username'] = form.username.data  # 存入用户名
        flash(f'Login requested for user {form.username.data}')
        return redirect(url_for('index'))

    return render_template('login.html', title='Sign In', form=form)

你可以创建不同的基础面板,给不同用户使用:

  • base.html:给未登录的访客使用
  • base_logged_in.html:给已登录的用户使用

JavaScript 基础

JavaScript 是一种轻量级的脚本语言 (scripting language),其语法类似 Python。JS 直接在用户的浏览器中运行,主要作用是让网页变得可交互 (interactive),例如:

  • 向 HTML 中插入动态文本
  • 对用户的事件(如点击、鼠标移动)做出反应
  • 在用户的电脑上执行计算

和 CSS 一样,将 JavaScript 代码应用到 HTML 页面主要有三种方式,推荐使用外部 JavaScript 文件:

html
<head>
    <script src="filename.js" type="text/javascript"></script>
</head>

与从上到下顺序执行的后端脚本不同,前端 JavaScript 大部分时间都在等待等待用户或浏览器触发某个事件。

嵌入式和内联式

嵌入式:

html
<script type="text/javascript">
  alert("Hello World!"); 
</script>

内联式:

html
<button onclick="alert('You clicked me!')">Click me!</button>

当用户使用对于不支持 JavaScript 的浏览器时,可以使用 <noscript> 标签进行提示,此处省略。

变量

在现代 JavaScript中,我们使用 let 关键字来声明变量(旧的 JavaScript 代码会使用 var):

javascript
let name;
let age = 10;
let message = "Hello, World!";

JavaScript 的类型是动态的,像 Python 一样,变量可以随时被赋予不同类型的值:

javascript
let myVar = 35;       // 此时 myVar 是一个数字
myVar = "Bob";        // 现在 myVar 变成了一个字符串
myVar = true;         // 现在 myVar 又变成了一个布尔值

数据类型

JavaScript 的数据类型分为两大类:

  • 原始数据类型 (Primitive Data Types)

    • Number:表示数字,不区分整数和浮点数
    • String:字符串
    • Boolean:布尔值,truefalse
  • 复合数据类型 (Composite Data Types)

    • Array:一个有序的数据集合
    • Object:一个无序的键值对集合

JavaScript 在处理不同类型数据混合运算时非常灵活,会自动进行类型转换。

运算符

JavaScript 的运算符和大部分编程语言类似,这里只介绍两个特殊的运算符:

  • 严格相等运算符 ===:不仅比较值是否相等,还比较类型是否相同。
    javascript
    5 =='5'      // true
    5 === '5'    // false
    5 === 5      // true
  • 严格不等运算符 !==:不仅比较值是否不等,还比较类型是否不同。
    javascript
    5 !='5'      // false
    5 !== '5'    // true
    5 !== 5      // false

虽然 5 == '5' 返回 true,但在对象的判断上,== 其实也是根据引用来返回结果而非内容的相等性:

javascript
let a = [1, 2, 3];
let b = [1, 2, 3];
a == b      // false

Array(数组)

JavaScript 的数组类似 Python 的列表,长度可变,可以存放不同类型的数据:

javascript
let fruits = ['apple', 'banana', 'orange'];
let numbers = [1, 2, 3, 4, 5];
let mixed = ['hello', 42, true];

// 使用构造函数
let colors = new Array('red', 'green', 'blue');

访问元素也通过:

javascript
// 获取长度
let len = fruits.length;

// 通过索引访问元素
let firstFruit = fruits[0];

// 添加元素
fruits.push('kiwi');

// 删除最后一个元素
fruits.pop();

// 修改元素
fruits[1] = 'grape';

// 删除指定位置的元素
// 参数一:起始索引;参数二:删除的数量
fruits.splice(2, 1);

if 语句 & 循环

if 语句

javascript
if (condition) {
    // 条件为真时执行的代码
} else if (anotherCondition) {
    // 另一个条件为真时执行的代码
} else {
    // 所有条件都不为真时执行的代码
}

循环

javascript
// for 循环
for (let i = 0; i < 5; i++) {
    console.log(i);
}

// 遍历一个集合
let colors = ['red', 'green', 'blue'];
for (let color of colors) {
    console.log(color);
}

// while 循环
let i = 0;
while (i < 10) { 
    // ...
    i++; 
}

函数

类似 Python,类型是动态的,函数不需要指定参数和返回类型:

javascript
function sum(a, b) {
    return a + b;
}

// 或者将函数赋值给一个变量:
let sum = function(a, b) {
    return a + b;
};

// 或者使用箭头函数(ES6 语法):
let sum = (a, b) => a + b;

// 调用函数
let result = sum(5, 10);

对象

JavaScript 中的对象是一个复合数据类型,由一系列的 “名称:值” (name: value) 对组成 。这些“名称:值”对也被称为对象的属性。

javascript
let person = {
    // 属性
    name: "Alice",
    age: 30,
    // 方法
    greet: function() {
        console.log("Hello, " + this.name);
    },
    // 或者:
    sayGoodbye() {
        console.log("Goodbye, " + this.name);
    }
};

// 访问属性 -> 输出 "Alice"
alert(person.name); 

// 调用方法 -> 输出 "Hello, Alice"
person.greet();

如果属性名包含空格或特殊字符,必须使用方括号语法访问:

javascript
let obj = {
    "first name": "John",
    "last-name": "Doe"
};

// 访问属性
alert(obj["first name"]); // 输出 "John"

JavaScript 自带了许多非常有用的内置对象,例如:

  • Date:处理日期和时间
  • Math:提供数学常量和函数
  • String:处理字符串
  • Window:表示浏览器窗口(其实 alert() 就是 window.alert 的简写)

DOM

DOM(Document Object Model)是连接 JavaScript 和 HTML 的桥梁:

  • 在 JavaScript 看来,这个文档(HTML)里的每一个部分——从整个 <html> 标签,到里面的 <head><body>,再到每一个 <h1><p><div>,甚至标签里的文字——都是一个对象。一个标签包裹一个标签,形成了一个树状结构。

  • DOM是浏览器根据HTML文档创建的一个“实时、可交互的对象树”,它为 JavaScript 提供了一套 API 接口,让 JS 能够读取和修改这个树上的任何部分。

  • 当 JavaScript 通过 DOM 修改了这个“对象树”时,浏览器会立刻将这些变化反映到用户看到的页面上。

浏览器为我们提供了三个重要对象:WindowNavigatordocument。JS 通过 document 全局对象来访问和操作 DOM 树。

增删改查

查找元素

现代方法:

javascript
// (推荐)使用 CSS 选择器语法来查找第一个匹配的元素
let firstParagraph = document.querySelector('p');
let specialItem = document.querySelector('.special');
let mainDiv = document.querySelector('#main');

传统方法:

javascript
// 通过 ID 查找单个元素
let header = document.getElementById('header');

// 通过标签名查找所有匹配的元素,返回一个元素节点列表
// 不包含文本节点 (ex: h1, p) 或注释节点 (ex: <!-- comment -->)
let allParagraphs = document.getElementsByTagName('p');

// 使用 CSS 选择器语法来查找所有匹配的元素,返回一个节点列表
let allItems = document.querySelectorAll('.item');

INFO

  • getElementsByTagName() 返回的是一个实时集合 (live collection) 。这意味着,在获取集合之后,再向页面中添加一个新的同类型标签时,这个集合的长度会自动更新。
  • querySelectorAll() 返回的是一个静态集合 (static collection) 。在获取集合之后,即使再向页面添加的新元素,这个静态集合的长度也不会改变 。

修改元素内容

一旦获取到了一个元素对象,就可以对它为所欲为。

javascript
// 修改标签内的内容
// 可以解析 HTML 标签
element.innerHTML = '<h1>新的HTML内容</h1>';  
// 更安全,直接成为目标元素的文本内容(忽略 HTML 标签)
element.textContent = '新的纯文本内容';  

// 修改属性 
// element.setAttribute('属性名', '属性值');
imageElement.setAttribute('src', 'new_image.jpg')

不推荐直接在 JS 中修改样式(ex: element.style.color = 'red';),更专业的做法是通过添加/切换 CSS 类名来控制样式。

  1. 预先定义好 CSS 类:
css
.highlight {
    background-color: yellow;
    font-weight: bold;
}
  1. 在 JS 中添加/切换类名:
javascript
// 获取元素
let myElement = document.getElementById('myElement');
// 为它设置class属性为 'highlight'
myElement.setAttribute('class', 'highlight');

这种做法保持了行为(JS)和表现(CSS)的分离。当你想修改高亮样式时,只需去修改 CSS 文件,而不用动任何 JavaScript 代码,维护起来非常方便。

创建与添加元素

javascript
// document.createElement('tagName'): 创建一个新的、空的元素对象
let newParagraph = document.createElement('p');

// 将新元素添加到父元素的子元素列表末尾
// 如果 newElement 是一个已经存在于 DOM 树中的元素,则会进行移动
parentElement.appendChild(newElement);

// 克隆一个元素(包括它的所有子元素)
let clonedElement = originalElement.cloneNode(true); // true 表示深度克隆

删除元素

javascript
// 从父元素中删除子元素
parentElement.removeChild(childElement);

事件处理

JS 可以监听用户在页面上的各种操作(事件),并对这些操作做出反应。例如:

html
<h1 id="greeting">Hello!</h1>
<button id="changeBtn">Change Text</button>

在 JS 中监听:

javascript
// 1. 先通过DOM找到我们需要操作的元素
const greetingElement = document.getElementById('greeting');
const changeButton = document.getElementById('changeBtn');

// 2. 为按钮添加一个 'click' 事件的监听器
changeButton.addEventListener('click', function() {
    // 3. 当按钮被点击时,通过 DOM 修改 h1 元素的内容
    greetingElement.textContent = 'Goodbye!';
    greetingElement.style.color = 'blue';
});

JavaScript 中级

函数

首先,函数可以在被声明之前调用,这是因为 JavaScript引擎在执行代码前,会先把所有的函数声明提升到作用域的顶部。例如:

javascript
sayHello(); 

function sayHello() {
    console.log("Hello, World!");
}

但在使用函数表达式时,函数不会被提升:

javascript
SayHello();   // 会报错

let SayHello = function() {
    console.log("Hello, World!");
};

函数可以作为变量的特性使用变得非常灵活:

javascript
// yes 和 no 是回调函数
function ask(question, yes, no) {
    if (confirm(question)) {   // confirm 会弹出一个确认框
        yes();   // 如果用户点“确定”,就回调 yes 函数
    } else {
        no();   // 如果用户点“取消”,就回调 no 函数
    }
}

function showOk() {
    alert("You agreed.");
}

function showCancel() {
    alert("You canceled the execution.");
}

// 调用 ask,并把 showOk 和 showCancel 作为回调函数传进去
ask("Do you agree?", showOk, showCancel);

此外,如果你尝试打印一个函数对象,你会看到它的源代码:

javascript
function greet() {
    alert("Hello!");
}

// 输出函数的源代码
alert(greet);

属性删除

JS 对象的属性是可以被删除的,使用 delete 关键字,例如:

javascript
let user = {
    name: "John",
    age: 30
};

delete user.age; // 删除 age 属性
alert(user.age); // undefined

delete 方法还具有返回值,如果删除成功则返回 true

字符串

JS 里也有模板字符串,使用反引号 (`) 包裹,并且可以通过 ${变量名} 的方式插入变量:

javascript
let name = "Alice";
let greeting = `Hello, ${name}!`;

JS 里的字符串是不可变的,如果你想要修改字符串,必须创建一个新的字符串:

javascript
let str = "Hello";
let newStr = str.replace("H", "J"); // newStr 现在是 "Jello"

你可以通过索引访问字符串中的单个字符:

javascript
let str = "Hello";
let firstChar = str[0];

此外还有多行字符串:

javascript
let multiLineStr = `Hello,
Amiya!
o.O
`;

可以使用 str.match()str.test() 方法进行正则表达式匹配:

javascript
let str = "The rain in SPAIN stays mainly in the plain";
let result = str.match(/ain/gi); // ['ain', 'AIN', 'ain', 'ain']

let pattern = /ain/gi;
let testResult = pattern.test(str); // true

jQuery

jQuery 是一个 JavaScript 库/框架,它提供了一系列工具和简便方法,使得用JavaScript操作HTML DOM、处理事件、创建动画以及执行 AJAX 请求变得更加容易。

通常,jQuery 的语法比等效的原生 JavaScript 代码要短得多。

INFO

AJAX(Asynchronous JavaScript and XML,异步 JavaScript 和 XML)是一种在不重新加载整个网页的情况下,与服务器交换数据并更新部分网页内容的技术。之后会介绍。

引入 jQuery

除了从官网下载 jQuery 库文件并放在项目的 static 文件夹中外,还可以通过 CDN 引入:

使用Google CDN

html
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> 
</head>

使用Microsoft CDN

html
<head>
    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script> 
</head>

对于大多数项目,尤其是线上项目,使用CDN通常是更推荐的方式。

等待 DOM 就绪

JavaScript 代码(包括 jQuery 代码)通常需要操作页面上的 HTML 元素。但是,如果您的<script> 标签放在 HTML 的 <head> 里,或者即使放在 <body> 里但试图操作它后面的元素,当 JS 代码执行时,那些 HTML 元素可能还没有被浏览器完全加载和解析。这时去操作它们就会出错。

jQuery 提供了一种简便的方法来确保代码在 DOM 完全加载后再执行:

javascript
$(document).ready(function() {
    // 你的所有的 jQuery 代码都应该写在这里面
    // 这里的代码会安全地等待 HTML 加载完毕后才运行
});
  • $(document):这里我们用 jQuery 选中了整个 HTML 文档。
  • function() { ... }:我们传递了一个匿名函数作为参数给 ready 方法。这个函数包含了我们想要在 DOM 就绪后执行的所有代码。

$()

在 jQuery 中,$ 符号实际上是 jQuery() 函数的一个简化。因为这个函数用得实在太频繁了,所以 jQuery 提供了一个更短的名字 $ 来方便使用。

jQuery 选择器

当你向 $() 函数传递一个字符串参数时,这个字符串通常就是一个 CSS 选择器。jQuery 会使用这个选择器去 HTML 文档中查找所有匹配的元素。

TIP

直觉上遍历整个 DOM 树听起来确实效率低下。不过,实际情况比这要复杂一些,也高效得多。

$() 函数返回的是一个特殊的 jQuery 对象。这个 jQuery 对象包含了所有匹配到的 DOM 元素,并提供了一系方法(如 .css().click().hide() 等)来对这些元素进行批量操作。

javascript
// 元素选择器示例:
let paragraphs = $('p');
// 类选择器示例:
let specialItems = $('.special');
// ID选择器示例:
let mainDiv = $('#main');

// 选择页面全部元素,使用较少
let allElements = $('*');

jQuery 的强大之处在于,它几乎支持所有您在 CSS 中学过的选择器。

选择器语法说明
属性选择器使用方括号 [] 来根据元素的属性进行选择选择具有特定属性的元素。$("img[src^='/artist/']") 会选中所有 src 属性值以 /artist/ 开头的 <img> 元素。
伪类选择器使用冒号 : 来选择特定状态的元素选择特定状态的元素。$("li:first") 会选中第一个 <li> 元素,$("tr:odd") 会选中所有奇数行的 <tr> 元素。
关系选择器 (contextual selectors)使用空格、>+ 等符号来表示元素之间的关系选择特定关系的元素。$("div > p") 会选中所有直接位于 <div> 内的 <p> 元素,$("h1 + p") 会选中紧跟在 <h1> 后面的第一个 <p> 元素。

示例

html
<a href="index.html"> </a>
<a id="second-link"></a>
<a class="example"></a>
<a class="example" href="about.html">   
</a> <span class="example"></span>
  • $("a") -> 选中 1, 2, 3, 4
  • $(".example") -> 选中 3, 4, 5
  • $("#second-link") -> 选中 2
  • $("[href='index.html']") -> 选中 1

操作 HTML 属性

获取属性值

通过 attr('attributeName') 获取属性值,会返回第一个匹配项:

javascript
// 获取页面上第一个 <a> 链接的 href 属性值
var link = $("a").attr("href");

设置属性值

attr() 方法传入两个参数(属性名称和新的值)时,它会为所有匹配到的元素设置该属性的值。

javascript
// 将页面上所有 <a> 链接的 href 属性都改为指向 UCD 网站
$("a").attr("href", "http://www.ucd.ie/");

// 将页面上所有 <img> 图片的 class 属性都设置为 "fancy"
$("img").attr("class", "fancy");

移除属性

removeAttr('attributeName') 会从所有匹配到的元素中移除指定的属性:

javascript
// 移除所有 <a> 链接的 href 属性
$("a").removeAttr("href");

操作固有属性 (Properties)

有些HTML元素的状态并不是通过属性(attribute)来表示的,而是通过元素的固有属性(property)来体现。最典型的例子就是复选框(checkbox)或单选按钮(radio button)的选中状态 (checked)。

TIP

Attribute 是初始值,Property 是当前值

jQuery 推荐使用 prop() 方法来获取和设置这些固有属性的值。

javascript
var theBox = $(".definition"); 

// 获取 checked 属性(通常返回初始值 "checked" 或 null) - 不推荐
theBox.attr("checked"); // 结果是 "checked"

// 获取 checked 固有属性(返回 true 或 false) - 推荐
theBox.prop("checked"); // 结果是 true
HTML
html
<input class="definition" type="checkbox" checked="checked">

操作 CSS 样式

获取 CSS 值

通过 css('propertyName')

javascript
// 获取 ID 为 "colourBox" 的元素的背景颜色
var color = $("#colourBox").css("background-color");

设置 CSS 值

css('propertyName', 'value')css({ property1: 'value1', property2: 'value2' })。传入 {} 可以同时设置多个属性。

javascript
// 将 ID 为 "colourBox" 的元素的背景颜色设置为红色
$("#colourBox").css("background-color", "#FF0000"); 

// 同时设置多个样式
$("#myElement").css({
    "color": "blue",
    "font-weight": "bold",
    "padding": "10px" 
});

事件处理

便捷方法 (Convenience Methods): jQuery为许多常用事件提供了简写方法,如 .click(), .mouseover(), .keydown() 等。

javascript
// 为 ID 为 "helloBtn" 的按钮绑定一个点击事件
$("#helloBtn").click(function(event) {
    // ..
});

更现代、更灵活的方法是使用 .on() 方法:

javascript
// 当类型为 "file" 的 input 元素的值发生改变时,调用 alertFileName 函数
// 参数一:事件类型;参数二:处理函数
$(":file").on("change", alertFileName);

function alertFileName() {
    console.log("The file selected is: " + this.value);
}

TIP

this 关键字在 JavaScript 总是指向当前的上下文。

在此处 DOM 事件处理函数中,this 指向触发事件的元素(ex: <input type="file">)。

移除事件监听器需要使用 .off() 方法,刚好是和 .on() 方法对应:

javascript
// 移除事件监听器
$(":file").off("change", alertFileName);
// 全部移除:$("#myButton").off("click");

TIP

必须将事件绑定代码放在 $(document).ready() 函数内部

DOM 遍历与过滤

之前的 $() 选择器是从整个文档中查找元素,这里的遍历和过滤方法,通常是在一个已经选中的 jQuery 对象(可能包含一个或多个元素)上调用的,以缩小范围或查找相关元素。

常用的遍历方法

向上查找:

  • .parent(): 获取当前元素的直接父元素。
  • .parents(): 获取当前元素的所有祖先元素,可以选择性地传入一个选择器进行过滤。
  • .closest('selector'): 从当前元素开始向上查找,返回第一个匹配指定选择器的祖先元素。

向下查找:

  • .children(): 获取当前元素的直接子元素,可以选择性地传入一个选择器进行过滤。
  • .find('selector'): 获取当前元素内部所有匹配指定选择器的后代元素(无论嵌套多深)。

同级查找:

  • .next(): 获取当前元素的下一个紧邻的兄弟元素。
  • .prev(): 获取当前元素的上一个紧邻的兄弟元素。

遍历每一个元素:

  • .each(function(start_index) { ... }): 对当前集合中的每一个元素执行指定的函数。索引为可选项,this 指向对应元素。

常用的过滤方法

  • .filter('selector'): 筛选出当前集合中匹配指定选择器的元素。

  • .not('selector'): 筛选出当前集合中不匹配指定选择器的元素。

  • .eq(index): 获取当前集合中指定索引位置的那个元素(索引从0开始)。

  • .first(): 获取当前集合中的第一个元素。

  • .last(): 获取当前集合中的最后一个元素。

  • .has('selector'): 筛选出当前集合中,那些包含匹配指定选择器的后代元素的元素。

AJAX

AJAX 代表 Asynchronous JavaScript and XML (异步的 JavaScript 和 XML),不过现在 XML 用得少了,更多的是用 JSON 格式。

AJAX 允许网页中的 JavaScript 在不打断用户操作、不重新加载整个页面的情况下,在后台向服务器发送请求,并接收服务器返回的数据。

AJAX 的工作流程

null

TIP

AJAX 并非框架/库,而是技术的组合 - HTML、CSS、JS、DOM、XMLHttpRequest

jQuery 提供了多种方法来简化 XMLHttpRequest 的使用,以便我们实现 AJAX 功能。

向服务器请求数据 - $.get()

函数定义

javascript
$.get(url [, data ] [, successCallback ] [, dataType ])

参数说明

  • url:请求的服务器资源的地址。

  • data(可选):要发送给服务器的数据。可以是一个普通对象(如 { name: "John", location: "Boston" })或一个查询字符串。

  • successCallback(可选):请求成功时的回调函数。它通常接收三个参数:

    • data:服务器返回的响应主体,通常是字符串或 JSON 对象。
    • textStatus:请求的状态(ex: "success")。
    • jqXHR:jQuery XMLHttpRequest对象。
  • dataType(可选):预期从服务器接收的数据类型,如 "json", "xml", "html"。

jqXHR 对象

$.get() 这样的 jQuery AJAX 方法会返回一个特殊的对象,称为 jqXHR (jQuery XMLHttpRequest) 对象,可以把它想象成代表了这次异步请求本身。

这个 jqXHR 对象提供了一些方法来代替回调函数 successCallback,可以组织出更清晰,功能更强的代码。例如:

javascript
 // 发起请求并获取 jqXHR 对象
var jqxhr = $.get("example.php");

// 请求成功
jqxhr.done(function(data) {
    alert("Success! Data received: " + data);
});

// 请求失败
jqxhr.fail(function(jqXHR, textStatus) {
    alert("Request Failed: " + textStatus);
});

// 请求完成,无论成败
jqxhr.always(function() {
    alert("AJAX request finished (either success or failure).");
});

发起 POST 请求 - $.post()

相比 GET 请求,POST 请求发送的数据量没有限制且支持更复杂的数据类型,还更加安全(发送的数据不会显示在 URL 中)。

函数定义

基本与 $.get() 一致:

javascript
$.post(url [, data ] [, successCallback ] [, dataType ])

通用方法 - $.ajax()

这是 jQuery AJAX 功能中最底层、最灵活的方法。如果您需要对请求进行非常精细的控制(比如设置请求头、指定请求超时时间、处理各种HTTP状态码等),就会用到 $.ajax()

该方法接收一个包含各种配置选项的对象作为参数,例如:

javascript
$.ajax({
    type: "POST", // 请求类型 (GET, POST, PUT, DELETE etc.)
    url: "server.url", // 请求地址
    data: { someData: true }, // 要发送的数据
    statusCode: { // 可以为特定的HTTP状态码指定处理函数
        404: function() { /* 处理 404 Not Found */ },
        503: function() { /* 处理 503 Service Unavailable */ }
    }
})
.done(function(data) { /* 请求成功处理 */ })
.fail(function(jqXHR, textStatus) { /* 请求失败处理 */ })
.always(function() { /* 请求完成处理 */ });

加载 HTML 片段 - $.load()

可以从服务器加载一段 HTML 内容,并直接将其注入到您选定的 DOM 元素内部,替换该元素原有的内容。

函数定义

javascript
$(selector).load(URL [, data ] [, callback ]);

参数说明

  • URL: 要加载的 HTML 内容的地址。
  • data (可选): 发送给服务器的数据。
  • callback (可选): 当加载完成(成功或失败)后执行的回调函数。
示例

目标:当用户点击按钮时,从服务器加载 result.html 文件的内容,并将其显示在 id="stage"<div> 元素中。

result.html
html
<div id="stage" style="background-color:cc0;">STAGE</div>
<button id="driver">Load Data</button>

注册点击事件:

javascript
$(document).ready(function() {
    // 当按钮被点击时,加载 result.html 到 #stage div 中
    $("#driver").click(function(event){
        $('#stage').load('/jquery/result.html'); 
    });
});

获取JSON数据 - $.getJSON()

函数定义

javascript
$.getJSON( URL [, data ] [, callback ] );

参数说明

参数于 $.load 类似,但此处的 callback 请求成功时的回调函数是必须的。这个函数通常接收一个参数,即已经被 jQuery 解析好的 JavaScript 对象。

示例

目标:点击按钮后,从服务器加载 result.json 文件,解析其内容,并将数据显示在 #stage div 中。

result.json
json
{
    "name": "Amiya",
    "age": 16,
    "class": "Caster"
}

获取 JSON 数据并显示:

javascript
$(document).ready(function() {
    $("#driver").click(function(event){
        // 发起 GET 请求获取 JSON 数据
        $.getJSON('/jquery/result.json', function(jd) {
            // 请求成功后,jd 参数就是解析好的 JavaScript 对象
            // 使用 jQuery 的 .html() 和 .append() 方法更新 #stage div 的内容
            $('#stage').html('<p> Name: ' + jd.name + '</p>');
            $('#stage').append('<p>Age : ' + jd.age + '</p>');
            $('#stage').append('<p> Class: ' + jd.class + '</p>');
        });
    });
});

序列化表单数据 - $.serialize()

这个方法在课程 pdf 只有短短的不到两行的介绍,而且我个人感觉使用 WTForms 处理表单以足够方便好用,所以也不打算细说了,感兴趣就自行去了解吧=v=。

它可以将一个 HTML 表单中的所有输入元素的值,自动收集并转换成一个适合在 URL 中传输的查询字符串 (query string) 格式。

语法:$(formSelector).serialize();

顺便一提,Flask 会自动解序列化。

AJAX 结合 Flask

通过实现一个动态的、无需刷新的实时检查用户名是否可用的功能,我们来了解一下如何将 AJAX 和 Flask 结合使用。

null

后端 Flask (routes.py) - 提供检查接口

routes.py
python
from flask import request, jsonify
import time # 用于模拟延迟
# ...

@app.route('/checkuser', methods=['POST'])
def check_username():
    # 模拟服务器处理延迟
    time.sleep(5) 

    # 1. 从 AJAX 请求中获取用户名
    chosen_name = request.form['username'] 

    # 2. 查询数据库
    user_in_db = User.query.filter(User.username == chosen_name).first()

    # 3. 根据查询结果,返回 JSON 响应
    if not user_in_db:
        # 用户名不存在 (可用)
        return jsonify({'text': 'Username is available', 'returnvalue': 0})
    else:
        # 用户名已存在 (不可用)
        return jsonify({'text': 'Sorry! Username is already taken', 'returnvalue': 1})

jQuery - 发送 AJAX 请求

myjs.js
javascript
$(document).ready(function() {
    console.log("Adding event handlers");
    // 监听 username 输入框的 change 事件
    $("#username").on("change", check_username); 
    console.log("functions registered");
});

function check_username() {
    console.log("check_username called");
    // 获取用户输入的用户名
    var chosen_user = $(this); // 'this' 指向触发事件的 input 元素
    console.log("User chose: " + chosen_user.val());

    // 发起 AJAX POST 请求
    $.post('/checkuser', {
        // 将用户名作为数据发送
        'username': chosen_user.val() 
    })
    .done(function(response) { 
        // 请求成功的回调
        var server_response = response['text'];
        var server_code = response['returnvalue'];

        // 假设有一个 span/div 用于显示反馈
        var feedbackElement = $("#checkuser"); 
        feedbackElement.removeClass();
        feedbackElement.html("");

    
        if (server_code == 0) { 
            // 用户名可用
            feedbackElement.html('<span>' + server_response + '</span>');
             // 添加成功样式 (ex: 绿色)
            feedbackElement.addClass("success");
            // (可选) 将焦点移到下一个输入框 (密码)
            $("#password").focus(); 
        } else { 
            // 用户名已被占用
            feedbackElement.html('<span>' + server_response + '</span>');
            feedbackElement.addClass("failure"); // 添加失败样式 (ex: 红色)
            // (可选) 清空输入框并重新聚焦
            chosen_user.val(""); 
            chosen_user.focus(); 
        }
    })
    .fail(function() { 
        // 请求失败的回调 (网络错误等)
        feedbackElement.html('<span>Error contacting server</span>');
        feedbackElement.addClass("failure");
    });
}

HTML 模板 引入JS并提供反馈区域

  1. base.html<head> 中,确保引入了 jQuery 库文件。
  2. signup.html 中(通常在 {% block content %} 内部,或者在 </body> 之前),引入我们刚编写的 myjs.js 文件。
  3. signup.html 的用户名输入框旁边,添加一个空的 <span><div> 元素,并给它一个 ID(比如 id="checkuser"),用于显示服务器返回的反馈信息。
signup.html
html
<p>
     {{ form.username.label }}<br>
     {# 确保 input 有 id="username" #}
     {{ form.username(size=32, id="username") }} 
     <span id="checkuser"></span> {# 用于显示反馈信息 #}

     {% for error in form.username.errors %}
        <span style="color: red;">[{{ error }}]</span>
     {% endfor %}
 </p>

补充:jQuery 修改 HTML 内容

之所以叫补充,是因为课程 pdf (week 7a) 里并没有提到这个内容,但是之后的 AJAX 结合 Flask 章节里有用到这个功能,为了完整性,我在此补充。

修改元素内部 HTML 内容的常用方法

方法说明
.html()获取或设置元素的内部 HTML 内容
.text()获取或设置元素的内部"纯文本"内容
.append()在元素的内部末尾追加新的 HTML 内容或元素
.prepend()在元素的内部开头添加新的 HTML 内容或元素
.before()在元素的外部之前插入新的 HTML 内容或元素
.after()在元素的外部之后插入新的 HTML 内容或元素

补充:Flask 接受 jQuery 请求

同上啊同上 o.0

当浏览器(或者 JavaScript 代码)向 Flask 应用发送一个 HTTP 请求时(无论是 GET、POST 还是其他类型),Flask 会创建一个特殊的 request 对象。

这个 request 对象携带着这次请求的所有信息,包括:

  • 请求的 URL 路径 (request.path)
  • 请求的方法 (request.method,比如 'GET' 或 'POST')
  • 请求头 (request.headers)
  • 以及最重要的——用户发送过来的数据。

request.form:

用于访问在请求体 (Request Body) 中,以标准 HTML 表单提交的默认方式编码的数据。jQuery的 $.post() 方法 在发送普通 JavaScript 对象时也会使用这种编码方式。

如果前端通过一个标准表单或 $.post({'username': 'test'}) 发送数据,那么在 Flask 后端,request.form['username']request.form.get('username') 就会得到 'test'。这就是之前 AJAX 结合 Flask 章节中使用的方式。