第1章 - 前端开发中的网络应用
嗨,前端开发的朋友们!
作为前端开发者,你每天都在和网络打交道:发送 HTTP 请求、处理异步数据、优化加载速度、解决跨域问题……但你真的理解这些操作背后的网络原理吗?
这一章,我会用最实战的方式,带你把网络知识应用到实际的前端开发中。学完后,你会发现很多之前遇到的「奇怪问题」,其实都有迹可循!
🌐 HTTP 请求实战
Fetch API 详解
基本 GET 请求
// 最简单的 GET 请求
fetch('https://api.example.com/users')
.then(response => {
console.log('状态码:', response.status); // 200
console.log('状态文本:', response.statusText); // OK
console.log('响应头:', response.headers); // Headers 对象
// 检查响应是否成功
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // 解析 JSON
})
.then(data => {
console.log('数据:', data);
})
.catch(error => {
console.error('请求失败:', error);
});
// 使用 async/await(推荐)
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log('用户列表:', data);
return data;
} catch (error) {
console.error('请求失败:', error);
}
}
POST 请求提交数据
// 提交 JSON 数据
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify(userData)
});
const result = await response.json();
return result;
} catch (error) {
console.error('创建用户失败:', error);
}
}
// 使用
const newUser = {
name: '张三',
email: 'zhangsan@example.com',
age: 25
};
createUser(newUser);
上传文件
// 上传图片文件
async function uploadImage(file) {
const formData = new FormData();
formData.append('image', file);
formData.append('userId', '123');
try {
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData // 不要设置 Content-Type,浏览器会自动设置
});
const result = await response.json();
console.log('上传成功:', result.url);
return result;
} catch (error) {
console.error('上传失败:', error);
}
}
// HTML
// <input type="file" id="fileInput" accept="image/*">
document.getElementById('fileInput').addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
uploadImage(file);
}
});
Axios 库使用
import axios from 'axios';
// 创建实例,配置基础 URL 和超时
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器(添加 token)
api.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
console.log('发送请求:', config.url);
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器(统一处理错误)
api.interceptors.response.use(
response => {
console.log('收到响应:', response.status);
return response.data; // 直接返回数据
},
error => {
if (error.response) {
// 服务器返回了错误状态码
switch (error.response.status) {
case 401:
console.error('未授权,请登录');
// 跳转到登录页
window.location.href = '/login';
break;
case 404:
console.error('资源不存在');
break;
case 500:
console.error('服务器错误');
break;
default:
console.error('请求失败:', error.response.data);
}
} else if (error.request) {
// 请求已发送但没有收到响应
console.error('网络错误,请检查连接');
} else {
// 请求配置出错
console.error('请求配置错误:', error.message);
}
return Promise.reject(error);
}
);
// 使用封装好的 API
async function getUsers() {
try {
const data = await api.get('/users');
return data;
} catch (error) {
console.error('获取用户失败');
}
}
async function createUser(userData) {
try {
const data = await api.post('/users', userData);
return data;
} catch (error) {
console.error('创建用户失败');
}
}
🚀 网络性能优化
1. 资源预加载
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接(DNS + TCP + TLS)-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/main.js" as="script">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/images/next-image.jpg">
2. 图片懒加载
// 方式1:使用 Intersection Observer API
class LazyLoad {
constructor() {
this.images = document.querySelectorAll('img[data-src]');
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{
rootMargin: '50px 0px', // 提前 50px 开始加载
threshold: 0.01
}
);
this.observe();
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;
// 创建新图片对象预加载
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
img.classList.add('loaded');
};
tempImg.src = src;
// 停止观察已加载的图片
this.observer.unobserve(img);
}
});
}
observe() {
this.images.forEach(img => this.observer.observe(img));
}
}
// 使用
new LazyLoad();
// HTML
// <img data-src="image.jpg" alt="懒加载图片" class="lazy">
<!-- 方式2:使用原生 loading 属性(简单但兼容性差)-->
<img src="image.jpg" loading="lazy" alt="图片">
3. 请求合并与缓存
// 请求合并(批量获取用户信息)
class RequestBatcher {
constructor(fetchFn, delay = 50) {
this.fetchFn = fetchFn;
this.delay = delay;
this.queue = [];
this.timer = null;
}
add(id) {
return new Promise((resolve, reject) => {
this.queue.push({ id, resolve, reject });
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this.flush();
}, this.delay);
});
}
async flush() {
const items = this.queue.slice();
this.queue = [];
const ids = items.map(item => item.id);
try {
// 批量请求
const results = await this.fetchFn(ids);
// 将结果分配给各个 Promise
items.forEach(item => {
const result = results.find(r => r.id === item.id);
item.resolve(result);
});
} catch (error) {
items.forEach(item => item.reject(error));
}
}
}
// 批量获取用户信息
async function fetchUsersByIds(ids) {
const response = await fetch(`/api/users?ids=${ids.join(',')}`);
return response.json();
}
const userBatcher = new RequestBatcher(fetchUsersByIds);
// 使用(多次调用会自动合并)
async function displayUser(userId) {
const user = await userBatcher.add(userId);
console.log('用户信息:', user);
}
// 这些请求会被合并成一个
displayUser(1);
displayUser(2);
displayUser(3);
// 实际只发送一个请求: /api/users?ids=1,2,3
4. 缓存策略
// 带缓存的请求封装
class CachedRequest {
constructor(ttl = 60000) { // 默认缓存 60 秒
this.cache = new Map();
this.ttl = ttl;
}
async get(url) {
const cached = this.cache.get(url);
// 检查缓存是否有效
if (cached && Date.now() - cached.timestamp < this.ttl) {
console.log('使用缓存:', url);
return cached.data;
}
// 发送请求
console.log('发送请求:', url);
const response = await fetch(url);
const data = await response.json();
// 存入缓存
this.cache.set(url, {
data,
timestamp: Date.now()
});
return data;
}
clear(url) {
if (url) {
this.cache.delete(url);
} else {
this.cache.clear();
}
}
}
const cachedRequest = new CachedRequest(30000); // 30秒缓存
// 使用
async function getUsers() {
return await cachedRequest.get('/api/users');
}
🔒 跨域问题解决
什么是跨域?
// 同源策略:协议、域名、端口都相同才算同源
// 当前页面: https://www.example.com:443
// 同源 ✅
https://www.example.com/api/users
https://www.example.com:443/data
// 跨域 ❌
http://www.example.com // 协议不同(https vs http)
https://api.example.com // 域名不同(www vs api)
https://www.example.com:8080 // 端口不同(443 vs 8080)
解决方案1:CORS(后端配置)
// 前端代码(简单请求)
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));
// 后端需要配置响应头:
// Access-Control-Allow-Origin: *
// 或
// Access-Control-Allow-Origin: https://www.example.com
// 前端代码(预检请求)
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: '张三' })
});
// 浏览器会先发送 OPTIONS 预检请求
// 后端需要配置:
// Access-Control-Allow-Origin: https://www.example.com
// Access-Control-Allow-Methods: POST, GET, OPTIONS
// Access-Control-Allow-Headers: Content-Type, Authorization
// Access-Control-Max-Age: 86400
解决方案2:代理(开发环境)
// Vite 配置(vite.config.js)
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
// 前端代码
fetch('/api/users') // 会被代理到 https://api.example.com/users
.then(response => response.json());
// Webpack Dev Server 配置(webpack.config.js)
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
}
解决方案3:JSONP(仅支持 GET)
// JSONP 原理:利用 <script> 标签不受跨域限制
function jsonp(url, callback) {
const callbackName = 'jsonp_' + Date.now();
// 创建全局回调函数
window[callbackName] = (data) => {
callback(data);
document.body.removeChild(script);
delete window[callbackName];
};
// 创建 script 标签
const script = document.createElement('script');
script.src = `${url}?callback=${callbackName}`;
document.body.appendChild(script);
}
// 使用
jsonp('https://api.example.com/users', (data) => {
console.log('数据:', data);
});
// 服务器需要返回:
// jsonp_1234567890({"users": [...]});
📡 WebSocket 实时通信
WebSocket 基础
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectInterval = 5000; // 5秒重连
this.heartbeatInterval = 30000; // 30秒心跳
this.heartbeatTimer = null;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket 连接成功');
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
this.handleMessage(data);
};
this.ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};
this.ws.onclose = () => {
console.log('WebSocket 连接关闭');
this.stopHeartbeat();
// 自动重连
setTimeout(() => {
console.log('尝试重新连接...');
this.connect();
}, this.reconnectInterval);
};
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.error('WebSocket 未连接');
}
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send({ type: 'ping' });
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
}
}
handleMessage(data) {
switch (data.type) {
case 'pong':
console.log('心跳响应');
break;
case 'message':
console.log('新消息:', data.content);
break;
default:
console.log('未知消息类型:', data);
}
}
close() {
this.stopHeartbeat();
if (this.ws) {
this.ws.close();
}
}
}
// 使用
const ws = new WebSocketClient('wss://chat.example.com');
ws.connect();
// 发送消息
ws.send({
type: 'message',
content: '你好!'
});
聊天室示例
// HTML
/*
<div id="chat-container">
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="输入消息...">
<button id="sendBtn">发送</button>
</div>
*/
class ChatRoom {
constructor(wsUrl) {
this.ws = new WebSocket(wsUrl);
this.messagesDiv = document.getElementById('messages');
this.messageInput = document.getElementById('messageInput');
this.sendBtn = document.getElementById('sendBtn');
this.init();
}
init() {
this.ws.onopen = () => {
this.addSystemMessage('已连接到聊天室');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.displayMessage(data);
};
this.ws.onerror = (error) => {
this.addSystemMessage('连接错误', 'error');
};
this.ws.onclose = () => {
this.addSystemMessage('连接已断开', 'error');
};
// 发送按钮
this.sendBtn.addEventListener('click', () => {
this.sendMessage();
});
// 回车发送
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.sendMessage();
}
});
}
sendMessage() {
const text = this.messageInput.value.trim();
if (!text) return;
const message = {
type: 'message',
content: text,
timestamp: Date.now()
};
this.ws.send(JSON.stringify(message));
this.messageInput.value = '';
}
displayMessage(data) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message';
messageDiv.innerHTML = `
<span class="user">${data.username}:</span>
<span class="content">${this.escapeHtml(data.content)}</span>
<span class="time">${this.formatTime(data.timestamp)}</span>
`;
this.messagesDiv.appendChild(messageDiv);
this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight;
}
addSystemMessage(text, type = 'info') {
const messageDiv = document.createElement('div');
messageDiv.className = `system-message ${type}`;
messageDiv.textContent = text;
this.messagesDiv.appendChild(messageDiv);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString();
}
}
// 使用
const chat = new ChatRoom('wss://chat.example.com');
📊 网络监控与调试
Performance API
// 监控页面加载性能
window.addEventListener('load', () => {
const perfData = performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
const dnsTime = perfData.domainLookupEnd - perfData.domainLookupStart;
const tcpTime = perfData.connectEnd - perfData.connectStart;
const requestTime = perfData.responseEnd - perfData.requestStart;
const domParseTime = perfData.domComplete - perfData.domLoading;
console.log('页面加载时间:', pageLoadTime + 'ms');
console.log('DNS 解析时间:', dnsTime + 'ms');
console.log('TCP 连接时间:', tcpTime + 'ms');
console.log('请求响应时间:', requestTime + 'ms');
console.log('DOM 解析时间:', domParseTime + 'ms');
});
// 监控资源加载
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
console.log(`${resource.name}: ${resource.duration}ms`);
});
Network Information API
// 检测网络状态
if ('connection' in navigator) {
const connection = navigator.connection;
console.log('网络类型:', connection.effectiveType); // 4g, 3g, 2g, slow-2g
console.log('下行速度:', connection.downlink + ' Mbps');
console.log('往返时间:', connection.rtt + ' ms');
console.log('省流模式:', connection.saveData);
// 根据网络状况调整策略
if (connection.effectiveType === 'slow-2g' || connection.saveData) {
// 加载低质量图片
loadLowQualityImages();
} else {
// 加载高质量图片
loadHighQualityImages();
}
// 监听网络变化
connection.addEventListener('change', () => {
console.log('网络状况改变:', connection.effectiveType);
});
}
在线/离线状态
// 检测在线状态
console.log('当前在线状态:', navigator.onLine);
window.addEventListener('online', () => {
console.log('网络已连接');
showNotification('网络已恢复', 'success');
// 同步离线数据
syncOfflineData();
});
window.addEventListener('offline', () => {
console.log('网络已断开');
showNotification('网络连接已断开', 'error');
// 启用离线模式
enableOfflineMode();
});
💡 实用技巧
1. 请求超时控制
// Fetch 超时控制
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
]);
}
// 使用
try {
const response = await fetchWithTimeout('/api/users', {}, 3000);
const data = await response.json();
} catch (error) {
if (error.message === '请求超时') {
console.error('请求超时,请稍后重试');
}
}
2. 请求重试
// 自动重试
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
console.log(`第 ${i + 1} 次尝试失败`);
if (i === retries - 1) {
throw error; // 最后一次失败则抛出错误
}
// 等待后重试(指数退避)
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
}
3. 并发控制
// 控制并发请求数量
class RequestQueue {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.current = 0;
this.queue = [];
}
async add(fn) {
if (this.current >= this.maxConcurrent) {
await new Promise(resolve => this.queue.push(resolve));
}
this.current++;
try {
return await fn();
} finally {
this.current--;
const resolve = this.queue.shift();
if (resolve) resolve();
}
}
}
const queue = new RequestQueue(3);
// 发送 10 个请求,但最多同时 3 个
const promises = [];
for (let i = 1; i <= 10; i++) {
promises.push(
queue.add(() => fetch(`/api/user/${i}`))
);
}
const results = await Promise.all(promises);
📝 小结
这一章我们学习了:
✅ HTTP 请求:Fetch API、Axios 封装、请求拦截器
✅ 性能优化:资源预加载、图片懒加载、请求合并、缓存策略
✅ 跨域解决:CORS、代理、JSONP
✅ WebSocket:实时通信、聊天室实现、自动重连
✅ 网络监控:Performance API、网络状态检测、在线离线
✅ 实用技巧:超时控制、自动重试、并发控制
🎯 下一步
现在你已经掌握了前端网络开发的核心技能,接下来可以学习后端开发中的网络应用!
