Appearance
ajax:从文档应用到浏览器应用
元信息
- 目标:理解 Web 从"用户代理手工驱动"到"脚本可编程驱动"的转变——请求照发、响应照收,但响应由脚本接住作数据处理而非替换页面;由此前端第一次拥有数据层,从页面制作走向应用开发
- 关键概念:ajax、User Agent、文档应用与浏览器应用、局部更新、异步调用、回调、XMLHttpRequest、fetch
- 关联阶段:cp3
- 常见误区:以为 ajax 是一种新技术/新语言(它是浏览器内生能力 + 一种使用方式);把
var data = $.get(url)当同步写法用(ajax 天生异步,网络不可靠不允许阻塞等待);以为 ajax 改变了 HTTP 一问一答(它只改"谁来发请求");用 script 标签跨域取数却不知那要求返回的是代码且依赖全局变量
元问题:为什么页面可以不刷新
早期 Web 是文档应用:一切交互最终都归结为"整页跳转"——点链接跳页、提交表单跳页,服务器每次返回完整的新页面。这套模式的架构假设是"一次请求 = 一份完整文档",随之而来三个别扭:
- 只改一行数据(如"点赞数 +1"),也要重新下载整个页面
- 页面刷新导致状态丢失:填了一半的表单、滚动位置全部归零
- 交互节奏被网络往返切割,体验是"一页一页翻",而不是"连续操作"
ajax 的思路一句话可以说尽:请求照发、响应照收,但响应不再是用来替换页面的文档,而是被脚本接住的数据。 由此前端第一次拥有了自己的数据层:后端提供数据服务(API),前端负责数据到界面的全部呈现——这是"前端"从页面制作成长为应用开发的分水岭,本周的 JSON/restful/模板,以及后续第 11 周的路由、第 12 周的存储,都是这条转折的连锁反应:
| 连锁反应 | 结果 | 对应篇章 |
|---|---|---|
| 请求由脚本发起 | 请求 API:XHR → fetch | 本篇、网络请求API |
| 响应是数据不是页面 | 需要数据格式与 API 约定 | JSON、restful |
| 数据要变成界面 | 前端自己渲染 | 前端模板 |
| 页面不再跳转 | "页面"概念被重新定义 | 前端路由(第 11 周) |
| 状态不再随跳转清零 | 状态管理成为课题 | 第 12 周 |
问题的提出
早期的浏览器能力有限,它只能由终端用户驱动,即只有经由人类键鼠操作才能执行各种操作,比如发起http请求,填写表单,接收和渲染服务端响应等,因此浏览器的名字被称为:User Agent。
但随着浏览器引入了脚本,页面里包含的js代码可以无需用户的帮助,自行使用DOM API修改页面,填写表单,甚至模拟用户的点击,提交表单。
但是,当表单提交之后,js代码的执行过程即结束,它无法对服务端返回的响应数继续进行处理。换言之,提交表单和处理响应这一过程仍然是由人类用户主导的,js代码只是辅助性的参与部分过程。
那么,能不能由js代码独立地发起一个http请求,并完整地处理http响应的整个过程呢?
这就是:AJAX:Asynchronous JavaScript and XML
Web函数的调用方式
传统的Web函数调用过程完全由浏览器代理,即发起HTTP调用、接收结果和进行本地处理等过程完全由浏览器包办:
- 用户在当前页面点击链接或提交按纽,浏览器作为代理,向服务端发起请求,获得返回的html文档,然后渲染为新页面,并替换掉旧页面
- 从用户角度看,即当前窗口有一个明显地刷新,新页面替代掉了旧页面,地址栏变换为新页面地址
可否允许开发者编写代码来自行发起调用、接收结果和本地处理?
- 用户点击链接或提交按纽,由程序员的脚本作为代理,向服务端发起请求,获得返回的json数据
- 将收到的json数据使用DOM API对当前页面进行操纵
- 从用户角度看,浏览器并不会刷新页面,当前页面只是发生了局部的更新,因此地址栏也不会发生变化。
示例
服务端:http://x.com/data.json 是一个Web函数,它返回一段json文本,可能是静态硬编码或由服务端脚本动态生成
1577808000, 1609430400 都是Unix时间戳,分别表示 2020-01-01和 2021-01-01
客户端:http://x.com/index.html是一个函数调用者,它该如何调用服务端的Web函数,获取数据并处理?
问题:如何将:http://x.com/data.json 返回的json字符串,转换为js代码?
解决方案一:以代码的方式载入
http://x.com/data.js
http://x.com/index.html
问题:需要服务端和浏览器端共同约定变量名,并且是一个全局变量
解决方案二:以数据方式载入
http://x.com/index.html
问题:如何实现上述fetch函数?
如果你自己编写一个HTTP客户端或浏览器,譬如在移动端原生应用的开发中,你就可以使用HTTP客户端库发起任何调用请求、任意处理响应的数据
解决方案三:巧用 script 标记
http://x.com/data.js
http://x.com/index.html
实际上是方案一的变形,其缺陷仍然是:
- Web函数返回值是文本类型(合法的javascript源代码),而非json数据
- 需要借助全局变量,并且变量命名易存在冲突
AJAX:Asynchronous JavaScript and XML
- AJAX 是由浏览器内生支持的获取服务端的新模式,它允许由程序员编写的js脚本发起请求,接收和处理返回的数据
- 因为服务端函数属于网络资源,故总是以异步的方式进行工作
- 即采用回调函数的方式进行工作
- 程序员通常会采用返回JSON格式数据作为调用的数据格式
- ajax早期使用xml,但后续改为更为简单易用的json
AJAX 示例
http://x.com/data.json
http://x.com/index.html
简化起见,使用了jquery提供的ajax库,请自行加上对jquery的引用
异步调用 vs. 同步调用
上述代码是错误的,因为Ajax天生只支持异步调用方式,这是由于网络请求是不可靠的,可能成功,也可能不成功(如网络中断、信号不好等),如果使用同步的方式,则意味着限入无限等待,从而形成白屏或卡死等糟糕的用户体验
异步调用的写法是,传入一个函数引用,该函数将会在数据成功返回后被执行,请参考定时器的用法
优劣与边界
由用户代码接管Web调用的所得:
- 单页效果:比如用户注册流程中,用户名重复导致报错,当用户离开用户输入框时,由js脚本独自发起一个http请求,向服务端查询该用户名是否重复,然后接受服务端响应,就地提示(动态插入错误提示)。无需整页提交和刷新,改善用户体验
- 客户端动态页面:由客户端脚本独立发起http请求,服务端只需提供json数据,由客户端将json数据转换为html页面。尤其适合局部的页面更新
边界提醒:
- ajax 改变的是"谁来发请求",HTTP 的一问一答没有变——想要持续推送,见HTTP Stream API
- 页面不刷新后,URL 不再随视图变化——"页面"如何重新定义,是第 11 周前端路由的课题
参考
- http://www.runoob.com/ajax/ajax-tutorial.html
- http://www.runoob.com/jquery/jquery-ajax-intro.html
- 各种ajax访问库:Jquery ajax, Axios, Fetch https://segmentfault.com/a/1190000012836882
- Public APIs 免费 API 集合列表 https://github.com/public-apis/public-apis