作为一名前端开发者,我们每天都在与各种API打交道。从最初的XMLHttpRequest到现在的Fetch API,前端异步请求技术经历了怎样的演变?今天就让我们通过实际代码来探索这段技术演进的历程。
前后端分离时代的到来
还记得早期的Web开发吗?那时候前后端是紧密耦合的,页面刷新是家常便饭。而现在,我们已经进入了前后端分离的时代:
前后端分离 js 主动请求接口 (异步任务),拿到数据
这种架构让前端可以"自己做主",通过JavaScript主动拉取资源,实现了真正的Web 2.0动态页面体验。
XMLHttpRequest:异步请求的开山鼻祖
理解XMLHttpRequest的工作原理
XMLHttpRequest可以说是前端异步请求的开山鼻祖。虽然它"早期接口请求的对象",但理解它的工作原理对我们掌握现代异步编程至关重要。
让我们来看看XMLHttpRequest的经典用法:
javascript
const getJSON = async(url) => {
return new Promise((resolve,reject) => {
// executor
// pending 状态
const xhr = new XMLHttpRequest(); // 实例化
// http 请求 GET 打开一个数据传输的通道
// 底层,好理解 浏览器网络请求的通道被打开
console.log(xhr.readyState);
xhr.open('GET','https://api.github.com/users/usersname/repos')
console.log(xhr.readyState);
xhr.send() // 发送请求
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
// 响应内容到到达了
resolve(JSON.parse(xhr.responseText))
}
}
})
}
XMLHttpRequest的readyState详解
这里有个关键概念需要理解:readyState
。它表示XMLHttpRequest对象的状态:
- 0: 未初始化(还没有调用open()方法)
- 1: 启动(已经调用open()方法,但尚未调用send()方法)
- 2: 发送(已经调用send()方法,但尚未接收到响应)
- 3: 接收(已经接收到部分响应数据)
- 4: 完成(已经接收到全部响应数据)
当readyState === 4
时,说明响应内容已经到达,我们就可以处理数据了。
从XML到JSON的数据格式演变
有趣的是,XMLHttpRequest中的"XML"反映了早期的数据交换格式:
xml
<song>
<author>周深</author>
<title>大鱼</title>
</song>
而现在我们更多使用的是JSON格式:
json
{
"author": "林俊杰",
"title": "江南"
}
JSON格式更轻量、更易于JavaScript处理,这也体现了前端技术的不断进步。
Fetch API:现代异步请求的新宠
告别回调地狱,拥抱Promise
XMLHttpRequest有个明显的问题:它是"es6之前的对象,连promise都没有"。这意味着我们只能通过事件监听和回调函数来处理异步操作,容易陷入回调地狱。
Fetch API的出现彻底改变了这种情况:
javascript
document.addEventListener('DOMContentLoaded', async () => {
console.log(fetch('https://api.github.com/users/usersname/repos'))
// resolve() fullfilled 完成了
// reject() rejected 失败
const result = await fetch('https://api.github.com/users/usersname/repos')
const data = await result.json()
document.getElementById('repos').innerHTML = data.map(item => `<li>${item.name}</li>`).join('')
})
Promise的三种状态
使用Fetch API时,我们需要理解Promise的三种状态:
- pending状态:等待中,异步操作还未完成
- fulfilled状态:已完成,通过resolve()触发
- rejected状态:已失败,通过reject()触发
async/await:让异步代码像同步代码一样
ES8引入的async/await语法糖让异步代码变得更加优雅:
javascript
// 传统的then链式调用
fetch('https://api.github.com/users/usersname/repos')
.then(res => res.json())
.then(json => {
// 处理数据
})
// async/await写法
const result = await fetch('https://api.github.com/users/usersname/repos')
const data = await result.json()
// 像同步代码一样处理数据
"then的链式调用有的繁琐",而async/await让代码"像同步代码一样",大大提高了代码的可读性和可维护性。
性能优化:DOM加载时机的选择
DOMContentLoaded vs window.onload
在实际开发中,我们经常需要在DOM加载完成后执行JavaScript代码。这里有两个重要的事件:
javascript
document.addEventListener('DOMContentLoaded', async () => {
// DOM结构加载完成就执行,不等待图片、样式表等资源
})
window.onload = function() {
// 所有资源都加载完成后执行,"有点晚"
}
正如注释中提到的,window.onload
"有点晚",因为它要等待所有资源(包括图片、样式表)都加载完成。而DOMContentLoaded
只需要DOM结构加载完成就可以执行,性能更好。
API接口 vs 网站地址:理解URL的本质
在前端开发中,我们需要区分两种不同的URL:
ruby
// API接口地址 - 返回JSON格式数据
// https://api.github.com/users/usersname/repos
// 网站地址 - 用户访问的页面
// www.bilibili.com
API接口地址是"资源"的概念,前端通过JavaScript主动拉取这些资源,而网站地址是用户直接访问的页面。这种分离让前端可以更灵活地处理数据和用户交互。
实战应用:GitHub仓库列表展示
让我们看看如何将获取到的数据渲染到页面上:
javascript
// 获取数据并渲染
const data = await getJSON('https://api.github.com/users/usersname/repos')
document.getElementById('repos').innerHTML = data.map(item => `<li>${item.name}</li>`).join('')
这里使用了函数式编程的思想:
- 使用
map()
方法遍历数组 - 将每个仓库对象转换为
<li>
标签 - 使用
join('')
将数组元素连接成字符串 - 通过
innerHTML
更新DOM
技术演进的思考
从XMLHttpRequest到Fetch API,从回调函数到Promise,从then链式调用到async/await,前端异步请求技术的演进体现了几个重要趋势:
- 语法简化:代码变得更加直观和易读
- 错误处理:更好的错误处理机制
- 性能优化:更高效的资源管理
- 开发体验:更好的开发者体验
写在最后
无论是XMLHttpRequest还是Fetch API,它们都是前端异步编程的重要工具。理解它们的工作原理和使用场景,不仅能帮助我们写出更好的代码,也能让我们在面对复杂的异步场景时游刃有余。
技术在不断演进,但核心思想是不变的:让前端能够主动获取数据,提供更好的用户体验。这正是Web 2.0时代动态页面的魅力所在。