Flask 与 JavaScript:使用 fetch 与 JSON 构建动态网页实战指南
2026/9/18 14:10:51 网站建设 项目流程

Flask 与 JavaScript:使用 fetch 与 JSON 构建动态网页实战指南

【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask

导读

本文是 Flask 官方文档中 docs/patterns/javascript.rst 的深度技术解析。你将从中学到如何在不刷新页面的前提下,用现代浏览器内置的fetchAPI 与 Flask 后端完成数据交互:包括如何在模板渲染阶段安全地把数据注入 JavaScript、如何在 JS 中动态生成后端 URL、如何收发 JSON 数据并优雅处理重定向与页面内容替换。文末结合仓库源码与 examples/javascript 示例工程,带你写出可直接运行的前后端联动代码。

为什么还需要"JavaScript + AJAX"模式?

传统 HTML<form>提交会触发整页刷新与重定向,而现代 Web 应用追求的是"局部数据更新"。Flask 官方文档给出的思路是:在页面中加入 JavaScript,用fetch向服务器发起异步请求,再原地替换页面内容

这里需要澄清一个常见误区:fetch并不是什么新框架,它是浏览器内置的标准 API,是 MDN Fetch API 就用同一套视图同时演示了fetch、XHR 与 jQuery 三种写法,方便对照:

@app.route("/", defaults={"js": "fetch"}) @app.route("/<any(xhr, jquery, fetch):js>") def index(js): return render_template(f"{js}.html", js=js)

渲染模板:服务端与浏览器的时间差

理解 JavaScript 在 Flask 项目中的角色,首先要分清两条执行时间线:

  • 模板在服务器端渲染,发生在响应发送给浏览器之前;
  • JavaScript 在用户浏览器中运行,发生在模板渲染完成并交付之后。

因此,JavaScript 不可能反向影响 Jinja 模板的渲染结果,但反过来——你完全可以把数据渲染进将要运行的 JavaScript 代码中。这是整个模式的第一块基石。

tojson过滤器安全注入数据

把 Python 数据传给页面中的<script>块,官方推荐使用 Jinja 的tojson过滤器。它在渲染时把数据序列化为合法的 JavaScript 对象字面量,同时转义不安全字符(如</script>),防止破坏页面结构或引入 XSS 风险。如果你忘了用tojson,浏览器控制台通常会报SyntaxError

data = generate_report() return render_template("report.html", chart_data=data)
<script> const chart_data = {{ chart_data|tojson }} chartLib.makeChart(chart_data) </script>

仓库的测试 tests/test_json.py 专门验证了这个行为——注入包含</script>datetime的数据后,渲染结果中</script>被转义为\u003c/script\u003edatetime被序列化为 RFC 822 格式的 HTTP 日期字符串:

rv = flask.render_template_string( "const data = {{ data|tojson }};", data={"name": "</script>", "time": datetime.datetime(2021, 2, 1, 7, 15)}, ) # 输出: # const data = {"name": "\u003c/script\u003e", "time": "Mon, 01 Feb 2021 07:15:00 GMT"};

这段测试也顺带揭示了 Flask 序列化的底层能力:源码 src/flask/json/provider.py 中的_default函数支持把datetime/date转为 HTTP 日期字符串、UUID转字符串、dataclass自动转 dict,这正是tojson比裸json.dumps更适合注入模板的原因。

少见的替代写法:把数据放进 HTML 标签的data-属性也是一种可行方案,但此时必须使用单引号包裹属性值data-chart='{{ chart_data|tojson }}'),否则会产出非法甚至不安全的 HTML。tojson默认输出双引号字符串,恰好适合这种场景。

生成 URL:两条路径

要让 JavaScript 发起请求,前提是它知道该请求哪个 URL。官方文档给出了两条互补的路径。

路径一:模板渲染时直接用url_for

最简单的做法是继续用 Flask 的url_for,在渲染模板时把结果注入 JavaScript:

const user_url = {{ url_for("user", id=current_user.id)|tojson }} fetch(user_url).then(...)

url_for在 src/flask/helpers.py 中定义,它要求处于活跃的请求或应用上下文中,最终委托给current_app.url_for()完成路由反解,生成的 URL 会正确处理SCRIPT_NAME前缀、静态文件挂载点等细节。

路径二:运行时在 JS 里拼 URL

但有些信息只有浏览器运行时才知道(比如用户在页面上点击产生的 id)。正如前面所说,JS 运行时模板早已渲染完毕,此时url_for已不可用。这时你需要知道应用被挂载的"根 URL"(SCRIPT_ROOT)——简单部署下它是/,但也可能是https://example.com/myapp/这样的前缀路径。

官方推荐在渲染模板时把根 URL 存为全局变量,之后在 JS 里用模板字符串拼接:

const SCRIPT_ROOT = {{ request.script_root|tojson }} let user_id = ... // do something to get a user id from the page let user_url = `${SCRIPT_ROOT}/user/${user_id}` fetch(user_url).then(...)

这里request.script_root取的是当前请求的脚本根路径,tojson会把它安全地转成合法的 JS 字符串字面量。

fetch发起请求:从 GET 到 POST

fetch(url, options)接收一个 URL 和一个配置对象,返回一个Promise。官方文档刻意保持克制:只介绍then()回调链,不深入await与其它回调,更多细节以 MDN 文档为准。

GET:读取 JSON

默认使用 GET 方法。若响应体是 JSON,可以在then()链中逐级解析:

const room_url = {{ url_for("room_detail", id=room.id)|tojson }} fetch(room_url) .then(response => response.json()) .then(data => { // data is a parsed JSON object })

POST 表单数据(推荐)

发送数据时改用 POST,并通过body选项携带负载。最常用的数据格式是表单数据(FormData)与 JSON 两种。

表单数据与 HTML 表单提交的格式完全一致,Flask 视图里用request.form读取:

let data = new FormData() data.append("name", "Flask Room") data.append("description", "Talk about Flask here.") fetch(room_url, { "method": "POST", "body": data, }).then(...)

官方示例 examples/javascript/js_example/views.py 演示了服务端接收表单数据的完整形态,还展示了request.form.get的类型转换能力:

@app.route("/add", methods=["POST"]) def add(): a = request.form.get("a", 0, type=float) b = request.form.get("b", 0, type=float) return jsonify(result=a + b)

对应的前端(fetch.html)用new FormData(this)直接把整个<form>序列化后 POST 出去:

fetch({{ url_for('add')|tojson }}, { method: 'POST', body: new FormData(this) }) .then(parseJSON) .then(addShow);

示例仓库的测试 examples/javascript/tests/test_js_example.py 验证了request.form.get("a", 0, type=float)的容错性:传"b"这类非法浮点时会回退到默认值0,保证接口稳健。

POST JSON 数据(注意 Content-Type)

优先选择表单数据,因为它与 HTML 表单语义一致、更简单。只有当数据结构确实复杂(嵌套对象、数组)时才需要 JSON。发送 JSON 时必须同时携带Content-Type: application/json请求头——否则 Flask 会返回415 Unsupported Media Type错误:

let data = { "name": "Flask Room", "description": "Talk about Flask here.", } fetch(room_url, { "method": "POST", "headers": {"Content-Type": "application/json"}, "body": JSON.stringify(data), }).then(...)

跟随重定向:别让登录响应悄悄丢掉

fetch自动跟随HTTP 重定向(比如 302/303),但浏览器不会因此改变页面——你的视图若在 JS 登录后返回了一个 redirect 响应,页面会停在原地。此时需要手动检查响应的redirected标志,并把response.url赋给window.location完成跳转:

fetch("/login", {"body": ...}).then( response => { if (response.redirected) { window.location = response.url } else { showLoginError() } } )

替换页面内容:局部更新的终点

响应也可能是新 HTML——可以是一段新区域,也可以是整张新页面。若返回的是整页,官方建议用上面的重定向方案处理,而不是用 JS 把整页塞进 DOM。下面的示例展示如何用请求返回的 HTML 替换页面里的<div>

<div id="geology-fact"> {{ include "geology_fact.html" }} </div> <script> const geology_url = {{ url_for("geology_fact")|tojson }} const geology_div = getElementById("geology-fact") fetch(geology_url) .then(response => response.text) .then(text => geology_div.innerHTML = text) </script>

示例工程 fetch.html 的addShow函数是同一思路的简化版:拿到 JSON 后只更新#result这个<span>的文本,实现"计算器不刷新页面出结果"的体验:

function addShow(data) { var span = document.getElementById('result'); span.innerText = data.result; }

在视图里返回 JSON

直接返回 dict(自动序列化)

Flask 视图可以直接返回 Python 字典,框架会自动把它序列化为 JSON 响应(mimetype 为application/json)。结合url_for还能在 JSON 里附带资源链接:

@app.route("/user/<int:id>") def user_detail(id): user = User.query.get_or_404(id) return { "username": User.username, "email": User.email, "picture": url_for("static", filename=f"users/{id}/profile.png"), }

从源码看,自动序列化与jsonify最终都汇聚到 src/flask/json/provider.py 的JSONProvider.response,并由 DefaultJSONProvider.response 落地:在debug 模式下输出会带 2 空格缩进便于阅读,非 debug 模式则使用紧凑分隔符("," , ":")压缩体积,末尾追加一个换行符。

jsonify返回任意 JSON 类型

如果返回的不是字典(比如列表),就需要jsonify,它会创建带application/jsonmimetype 的响应对象:

from flask import jsonify @app.route("/users") def user_list(): users = User.query.order_by(User.name).all() return jsonify([u.to_json() for u in users])

jsonify的实现位于 src/flask/json/init.py:要么传位置参数(单值或列表),要么传关键字参数(作为 dict 序列化),两者不可混用。dumps/loads/jsonify这一族函数都优先走current_app.json的 provider,这也意味着你可以通过自定义JSONProvider子类替换整套 JSON 序列化行为(比如换成 orjson、更改ensure_asciisort_keyscompact等配置,见 src/flask/json/provider.py)。

为什么不要把文件数据塞进 JSON

不要在 JSON 响应里直接返回文件内容:JSON 无法原生表达二进制数据,必须 base64 编码,这既拖慢速度、增加带宽,也不利于缓存。正确做法是:用一个视图专门提供文件下载,把文件的 URL 放进 JSON,让客户端拿到 JSON 后再发起独立请求获取资源(与上文"JSON 中返回图片 URL"的模式一脉相承)。

在视图里接收 JSON

接收请求体中的 JSON,直接使用flask.request.json属性:

from flask import request @app.post("/user/<int:id>") def user_update(id): user = User.query.get_or_404(id) user.update_from_json(request.json) db.session.commit() return user.to_json()

这里有两个值得记住的错误语义,它们是前后端联调时最常见的报错来源:

  • 请求体不是合法 JSON → 抛出400 Bad Request
  • Content-Type头不是application/json→ 抛出415 Unsupported Media Type(与"发送 JSON 时必须带 Content-Type 头"的约定正好呼应)。

结合示例工程跑通完整闭环

仓库 examples/javascript 提供了一个可直接运行的完整示例(flask --app js_example run后访问 http://127.0.0.1:5000 即可体验三种前端实现),其前后端链路正好覆盖本文全部知识点:

  1. 后端 views.py 提供GET /(渲染页面)与POST /add(接收表单、返回 JSON)两个端点;
  2. 前端 base.html 定义#calc表单与#result结果区;
  3. fetch.html、xhr.html、jquery.html 分别用三种方式实现"提交 → 局部更新",其中 XHR 版本展示了被fetch取代的旧式写法:
    var request = new XMLHttpRequest(); request.addEventListener('load', addShow); request.open('POST', {{ url_for('add')|tojson }}); request.send(new FormData(this));
  4. 测试 test_js_example.py 用 Flask 测试客户端验证了页面渲染模板与/add的数值计算逻辑。

核心要点速查

场景关键做法注意事项
模板 → JS 注入数据{{ data|tojson }}不转义会触发浏览器SyntaxError,还可能引入 XSS
data-属性传数据data-x='{{ data|tojson }}'必须用单引号包裹
模板期生成 URL{{ url_for(...)|tojson }}需要活跃的请求上下文
JS 运行时拼 URLSCRIPT_ROOT + "/path/" + id根路径来自request.script_root
发送表单数据body: new FormData(...)视图用request.form读取,最简单推荐
发送 JSONbody: JSON.stringify(obj)+Content-Type: application/json缺请求头会收到 415
返回 JSON视图直接返回 dict,或用jsonifydebug 模式自动缩进、非 debug 紧凑输出
接收 JSONrequest.json坏 JSON → 400;头不对 → 415
处理重定向检查response.redirected,手动window.location = response.urlfetch自动跟随但不改页面
文件下载JSON 里放 URL,客户端二次请求不要 base64 塞进 JSON

掌握这套"服务端渲染注入 + 前端fetch异步交互"的组合拳,你就能在 Flask 应用中实现登录、搜索、评论、实时刷新等各类无刷新交互——所有能力都来自现代浏览器内置 API,无需引入任何第三方 JavaScript 库。

【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询