请添加图片描述

项目简介

这是一个演示如何使用不同的JavaScript技术发送Ajax请求并处理JSON响应的Flask示例项目。项目展示了三种常用的Ajax实现方式:

  • 现代的fetch API
  • 传统的XMLHttpRequest对象
  • 流行的jQuery库的ajax方法

通过这个项目,您可以学习到如何在不离开当前页面的情况下与服务器进行数据交互。

项目结构

javascript/
├── .gitignore           # Git忽略文件
├── LICENSE.txt          # 许可证文件
├── README.rst           # 项目说明(reStructuredText格式)
├── README.md            # 项目详解(Markdown格式)
├── js_example/          # 主应用目录
│   ├── __init__.py      # 应用初始化文件
│   ├── templates/       # HTML模板目录
│   │   ├── base.html    # 基础模板
│   │   ├── fetch.html   # 使用fetch API的页面
│   │   ├── jquery.html  # 使用jQuery的页面
│   │   └── xhr.html     # 使用XMLHttpRequest的页面
│   └── views.py         # 视图函数和路由定义
├── pyproject.toml       # 项目配置和依赖
└── tests/               # 测试目录
    ├── conftest.py      # 测试配置
    └── test_js_example.py # 测试用例

技术栈

  • 后端:Python + Flask
  • 前端:HTML, CSS, JavaScript
  • 测试:pytest, coverage
  • 前端库:jQuery (可选)
  • 样式:normalize.css, sakura.css

代码解析

1. 应用初始化 - __init__.py

# 导入Flask类,用于创建Flask应用实例
from flask import Flask

# 创建Flask应用实例,__name__变量用于确定应用的根目录
app = Flask(__name__)

# 导入views模块,确保视图函数被注册到应用实例
from js_example import views  # noqa: E402, F401

这个文件负责初始化Flask应用实例。app = Flask(__name__) 创建了一个Flask应用对象,__name__参数帮助Flask确定应用的根目录,以便能够正确加载静态文件和模板。最后一行导入views模块,确保所有的路由和视图函数被正确注册到应用实例上。

2. 视图函数 - views.py

# 导入Flask的jsonify函数,用于返回JSON格式的响应
from flask import jsonify
# 导入Flask的render_template函数,用于渲染HTML模板
from flask import render_template
# 导入Flask的request对象,用于获取请求数据
from flask import request

# 从当前包导入app实例
from . import app


@app.route("/", defaults={"js": "fetch"})
@app.route("/<any(xhr, jquery, fetch):js>")
def index(js):
    """
    主页面路由函数
    
    参数:
        js: 字符串,指定使用的JavaScript请求方法,可以是'fetch'、'xhr'或'jquery'
    
    返回:
        渲染对应的HTML模板,默认为fetch.html
    """
    # 渲染对应的HTML模板,传递js参数给模板
    return render_template(f"{js}.html", js=js)


@app.route("/add", methods=["POST"])
def add():
    """
    加法计算API端点
    
    接收POST请求,获取表单中的a和b参数,计算它们的和并返回JSON格式的结果
    
    返回:
        JSON响应,包含计算结果,格式为{"result": 计算结果}
    """
    # 从表单中获取参数a,默认为0,转换为float类型
    a = request.form.get("a", 0, type=float)
    # 从表单中获取参数b,默认为0,转换为float类型
    b = request.form.get("b", 0, type=float)
    # 返回JSON格式的结果
    return jsonify(result=a + b)

这个文件定义了两个主要的路由函数:

  • index(js):处理根路径和带有js参数的路径,根据参数渲染不同的模板页面。默认使用fetch方法。
  • add():处理POST请求的加法计算API,从表单中获取两个数字,计算它们的和,并以JSON格式返回结果。

3. 基础模板 - templates/base.html

这是所有页面的基础模板,定义了页面的基本结构和样式:

<!doctype html>
<!-- 设置页面标题 -->
<title>JavaScript Example</title>
<!-- 引入normalize.css,用于规范化不同浏览器的默认样式 -->
<link rel="stylesheet" href="https://unpkg.com/normalize.css@8.0.1/normalize.css">
<!-- 引入sakura.css,提供基础的样式美化 -->
<link rel="stylesheet" href="https://unpkg.com/sakura.css@1.3.1/css/sakura.css">
<!-- 自定义样式 -->
<style>
  /* 导航菜单样式 */
  ul { margin: 0; padding: 0; display: flex; list-style-type: none; }
  li > * { padding: 1em; }
  li.active > a { color: #5e5e5e; border-bottom: 2px solid #4a4a4a; }
  /* 表单样式 */
  form { display: flex; }
  label > input { width: 3em; }
  form > * { padding-right: 1em; }
  /* 结果显示样式 */
  #result { font-weight: bold; }
</style>

<!-- 导航菜单,允许用户切换不同的JavaScript请求方式 -->
<ul>
  <li><span>Type:</span>
  <li class="{% if js == 'fetch' %}active{% endif %}">
    <a href="{{ url_for('index', js='fetch') }}">Fetch</a>
  <li class="{% if js == 'xhr' %}active{% endif %}">
    <a href="{{ url_for('index', js='xhr') }}">XHR</a>
  <li class="{% if js == 'jquery' %}active{% endif %}">
    <a href="{{ url_for('index', js='jquery') }}">jQuery</a>
</ul>

<!-- 介绍部分,由子模板填充 -->
<p>{% block intro %}{% endblock %}</p>

<!-- 计算器表单 -->
<form id="calc">
  <label>a <input name="a"></label>
  <span>+</span>
  <label>b <input name="b"></label>
  <input type="submit" value="Calculate">
</form>
<span>= <span id="result"></span></span>

<!-- JavaScript脚本部分,由子模板填充 -->
{% block script %}{% endblock %}

这个模板定义了一个简单的计算器表单,包含两个输入框和一个计算按钮。它使用了Flask的模板语法,定义了两个可被子模板重写的块:introscript

4. 使用fetch API - templates/fetch.html

{% extends 'base.html' %}

{% block intro %}
  <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch"><code>fetch</code></a>
  is the <em>modern</em> plain JavaScript way to make requests. It's
  supported in all modern browsers.
{% endblock %}

{% block script %}
  <script>
    /**
     * 表单提交处理函数
     * @param {Event} ev - 表单提交事件对象
     */
    function addSubmit(ev) {
      // 阻止表单默认提交行为
      ev.preventDefault();
      // 使用fetch API发送POST请求
      fetch({{ url_for('add')|tojson }}, {
        method: 'POST',
        body: new FormData(this) // 使用表单数据作为请求体
      })
        .then(parseJSON)  // 解析JSON响应
        .then(addShow);   // 显示计算结果
    }

    /**
     * 解析JSON响应
     * @param {Response} response - fetch返回的响应对象
     * @returns {Promise} 解析后的JSON数据
     */
    function parseJSON(response) {
      return response.json();
    }

    /**
     * 显示计算结果
     * @param {Object} data - 包含计算结果的JSON数据
     */
    function addShow(data) {
      var span = document.getElementById('result');
      span.innerText = data.result;
    }

    // 获取表单元素并添加提交事件监听器
    var form = document.getElementById('calc');
    form.addEventListener('submit', addSubmit);
  </script>
{% endblock %}

这个模板展示了如何使用现代的fetch API发送Ajax请求。fetch API是ES6引入的新一代网络请求API,使用Promise处理异步操作,代码更加简洁易读。

5. 使用XMLHttpRequest - templates/xhr.html

{% extends 'base.html' %}

{% block intro %}
  <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code></a>
  is the original JavaScript way to make requests. It's natively supported
  by all browsers, but has been superseded by
  <a href="{{ url_for("index", js="fetch") }}"><code>fetch</code></a>.
{% endblock %}

{% block script %}
  <script>
    /**
     * 表单提交处理函数
     * @param {Event} ev - 表单提交事件对象
     */
    function addSubmit(ev) {
      // 阻止表单默认提交行为
      ev.preventDefault();
      // 创建XMLHttpRequest对象
      var request = new XMLHttpRequest();
      // 添加load事件监听器,请求完成时调用addShow函数
      request.addEventListener('load', addShow);
      // 初始化请求,设置请求方法为POST和请求URL
      request.open('POST', {{ url_for('add')|tojson }});
      // 发送请求,使用表单数据作为请求体
      request.send(new FormData(this));
    }

    /**
     * 请求完成后处理函数,显示计算结果
     * 注意:这里的this指向XMLHttpRequest对象
     */
    function addShow() {
      // 解析响应文本为JSON对象
      var data = JSON.parse(this.responseText);
      // 获取结果显示元素并设置文本内容
      var span = document.getElementById('result');
      span.innerText = data.result;
    }

    // 获取表单元素并添加提交事件监听器
    var form = document.getElementById('calc');
    form.addEventListener('submit', addSubmit);
  </script>
{% endblock %}

这个模板展示了如何使用传统的XMLHttpRequest对象发送Ajax请求。这是最早的Ajax实现方式,虽然代码相对复杂,但具有最广泛的浏览器兼容性。

6. 使用jQuery - templates/jquery.html

{% extends 'base.html' %}

{% block intro %}
  <a href="https://jquery.com/">jQuery</a> is a popular library that
  adds cross browser APIs for common tasks. However, it requires loading
  an extra library.
{% endblock %}

{% block script %}
  <!-- 引入jQuery库 -->
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script>
    /**
     * 表单提交处理函数
     * @param {Event} ev - 表单提交事件对象
     */
    function addSubmit(ev) {
      // 阻止表单默认提交行为
      ev.preventDefault();
      // 使用jQuery的ajax方法发送POST请求
      $.ajax({
        method: 'POST',
        url: {{ url_for('add')|tojson }},
        data: $(this).serialize() // 序列化表单数据
      }).done(addShow); // 请求成功时调用addShow函数
    }

    /**
     * 显示计算结果
     * @param {Object} data - 包含计算结果的JSON数据
     */
    function addShow(data) {
      // 使用jQuery选择器选择结果元素并设置文本内容
      $('#result').text(data.result);
    }

    // 使用jQuery选择器选择表单元素并添加提交事件监听器
    $('#calc').on('submit', addSubmit);
  </script>
{% endblock %}

这个模板展示了如何使用jQuery库的ajax方法发送Ajax请求。jQuery提供了简洁的API和良好的跨浏览器兼容性,但需要额外加载jQuery库。

7. 测试文件

测试配置 - tests/conftest.py
# 导入pytest库,用于测试框架
import pytest

# 从js_example包导入Flask应用实例
from js_example import app


@pytest.fixture(name="app")
def fixture_app():
    """
    创建测试用的Flask应用实例的fixture
    
    设置app.testing为True,这会改变Flask的行为以方便测试
    在测试结束后恢复原始状态
    
    返回:
        Flask应用实例
    """
    # 启用测试模式,这会禁用错误捕获,以便在测试中显示完整的错误信息
    app.testing = True
    # yield关键字使这个函数成为生成器,返回app实例给测试使用
    yield app
    # 测试完成后,恢复app.testing的原始状态
    app.testing = False


@pytest.fixture
def client(app):
    """
    创建测试客户端的fixture
    
    参数:
        app: Flask应用实例(由fixture_app提供)
    
    返回:
        Flask测试客户端,用于模拟HTTP请求
    """
    # 返回Flask的测试客户端,用于发送HTTP请求到应用
    return app.test_client()

这个文件定义了测试中使用的fixture,为测试提供Flask应用实例和测试客户端。

测试用例 - tests/test_js_example.py
# 导入pytest库,用于测试框架
import pytest
# 从flask导入template_rendered信号,用于测试模板渲染
from flask import template_rendered


@pytest.mark.parametrize(
    ("path", "template_name"),
    (
        ("/", "fetch.html"),
        ("/plain", "xhr.html"),
        ("/fetch", "fetch.html"),
        ("/jquery", "jquery.html"),
    ),
)def test_index(app, client, path, template_name):
    """
    测试主页路由是否正确渲染了对应的模板
    
    使用parametrize装饰器参数化测试,测试多个路径和对应的模板
    
    参数:
        app: Flask应用实例
        client: Flask测试客户端
        path: 要测试的URL路径
        template_name: 期望渲染的模板名称
    """
    def check(sender, template, context):
        """检查渲染的模板名称是否符合预期"""
        assert template.name == template_name

    # 连接template_rendered信号到check函数,捕获模板渲染事件
    with template_rendered.connected_to(check, app):
        # 发送GET请求到指定路径
        client.get(path)


@pytest.mark.parametrize(
    ("a", "b", "result"), ((2, 3, 5), (2.5, 3, 5.5), (2, None, 2), (2, "b", 2))
)def test_add(client, a, b, result):
    """
    测试加法API端点的功能
    
    使用parametrize装饰器参数化测试,测试多种输入场景
    
    参数:
        client: Flask测试客户端
        a: 第一个加数
        b: 第二个加数
        result: 期望的计算结果
    """
    # 发送POST请求到/add端点,传递参数a和b
    response = client.post("/add", data={"a": a, "b": b})
    # 断言响应中的result值等于期望的结果
    assert response.get_json()["result"] == result

这个文件包含了两个测试用例:

  • test_index:测试主页路由是否正确渲染了对应的模板。
  • test_add:测试加法API端点的功能,包括各种输入场景。

安装和运行

安装依赖

python3 -m venv .venv
. .venv/bin/activate  # Windows系统使用 .venv\Scripts\activate
pip install -e .

运行应用

flask --app js_example run

然后在浏览器中打开 http://127.0.0.1:5000 查看应用。

运行测试

pip install -e '.[test]'
coverage run -m pytest
coverage report

三种Ajax实现方式的比较

实现方式 优点 缺点
fetch API 现代API,使用Promise,代码简洁 旧浏览器不支持,需要polyfill
XMLHttpRequest 兼容性最好,所有浏览器都支持 代码较为繁琐,基于事件的异步处理
jQuery.ajax 代码简洁,跨浏览器兼容性好 需要额外加载jQuery库,增加页面体积

总结

这个示例项目展示了三种不同的JavaScript Ajax实现方式,它们各有优缺点,可以根据项目需求选择合适的方式。fetch API是现代Web开发的推荐方式,但在需要支持旧浏览器的场景下,XMLHttpRequest或jQuery.ajax也是不错的选择。

通过这个项目,您可以学习到如何在Flask应用中集成JavaScript,实现客户端与服务器的数据交互,从而创建更加动态和响应式的Web应用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐