乐闻世界logo
搜索文章和话题

前端面试题手册

axios 实例如何创建和配置?请说明 axios.create() 的使用方法

使用 axios.create() 方法可以创建具有自定义配置的 axios 实例,这在需要多个不同配置的 HTTP 客户端时非常有用。基本用法const instance = axios.create({ baseURL: 'https://api.example.com', timeout: 1000, headers: {'X-Custom-Header': 'foobar'}});// 使用实例发送请求instance.get('/users') .then(response => { console.log(response.data); });配置选项详解1. 基础配置const instance = axios.create({ // 基础 URL,会自动添加到请求 URL 前面 baseURL: 'https://api.example.com/v1', // 请求超时时间(毫秒) timeout: 10000, // 自定义请求头 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, // 请求方法 method: 'get', // URL 参数 params: { key: 'value' }, // 请求体数据(仅适用于 PUT、POST、DELETE 和 PATCH) data: { firstName: 'John' }});2. 高级配置const instance = axios.create({ // 允许携带 cookie withCredentials: true, // 响应类型 responseType: 'json', // 'arraybuffer', 'document', 'json', 'text', 'stream', 'blob' // 响应编码 responseEncoding: 'utf8', // xsrf token 名称 xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', // 上传/下载进度监控 onUploadProgress: function (progressEvent) { // 上传进度 }, onDownloadProgress: function (progressEvent) { // 下载进度 }, // 最大重定向次数 maxRedirects: 5, // 最大请求体长度 maxContentLength: 2000, // 最大响应体长度 maxBodyLength: 2000, // 代理配置 proxy: { protocol: 'https', host: '127.0.0.1', port: 9000, auth: { username: 'mikeymike', password: 'rapunz3l' } }});多实例配置场景场景 1:多个后端服务// 用户服务const userService = axios.create({ baseURL: 'https://api.user-service.com', timeout: 5000});// 订单服务const orderService = axios.create({ baseURL: 'https://api.order-service.com', timeout: 10000});// 支付服务const paymentService = axios.create({ baseURL: 'https://api.payment-service.com', timeout: 15000, headers: { 'X-Payment-Key': 'secret-key' }});// 使用userService.get('/users/1');orderService.get('/orders/123');paymentService.post('/payments', { amount: 100 });场景 2:不同环境配置// 开发环境const devInstance = axios.create({ baseURL: 'http://localhost:3000', timeout: 5000});// 生产环境const prodInstance = axios.create({ baseURL: 'https://api.production.com', timeout: 10000, headers: { 'X-Environment': 'production' }});// 根据环境导出const api = process.env.NODE_ENV === 'production' ? prodInstance : devInstance;export default api;场景 3:带认证的 API 和普通 API// 需要认证的 APIconst authApi = axios.create({ baseURL: 'https://api.example.com', timeout: 10000});// 添加认证拦截器authApi.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config;});// 公开 API(无需认证)const publicApi = axios.create({ baseURL: 'https://api.example.com', timeout: 5000});// 使用publicApi.get('/public/data'); // 无需认证authApi.get('/private/profile'); // 需要认证实例方法创建实例后,可以使用以下方法:const instance = axios.create({ baseURL: 'https://api.example.com' });// 请求方法instance.request(config)instance.get(url, config)instance.delete(url, config)instance.head(url, config)instance.options(url, config)instance.post(url, data, config)instance.put(url, data, config)instance.patch(url, data, config)// 获取 URI(不发送请求)instance.getUri(config)默认配置修改修改实例默认配置const instance = axios.create();// 修改默认配置instance.defaults.baseURL = 'https://api.example.com';instance.defaults.headers.common['Authorization'] = 'AUTH_TOKEN';instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';instance.defaults.timeout = 10000;请求时覆盖配置const instance = axios.create({ baseURL: 'https://api.example.com', timeout: 5000});// 单次请求覆盖配置instance.get('/long-operation', { timeout: 30000 // 覆盖默认的 5000ms});配置优先级配置会按以下优先级合并(后面的覆盖前面的):库默认值axios.create() 中的配置实例的 defaults 属性请求时的配置参数// 库默认值axios.defaults.timeout = 0;// 实例创建配置const instance = axios.create({ timeout: 5000 // 覆盖库默认值});// 实例 defaults 配置instance.defaults.timeout = 10000; // 覆盖创建时的配置// 请求配置instance.get('/data', { timeout: 20000 // 最终使用这个值});完整封装示例// api.jsimport axios from 'axios';// 创建实例const api = axios.create({ baseURL: process.env.VUE_APP_API_URL || 'http://localhost:3000', timeout: 10000, headers: { 'Content-Type': 'application/json' }});// 请求拦截器api.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); });// 响应拦截器api.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); });// 导出实例和方法export default api;// 特定服务 APIexport const userApi = { getProfile: () => api.get('/users/profile'), updateProfile: (data) => api.put('/users/profile', data), changePassword: (data) => api.post('/users/change-password', data)};export const orderApi = { getOrders: (params) => api.get('/orders', { params }), createOrder: (data) => api.post('/orders', data), cancelOrder: (id) => api.delete(`/orders/${id}`)};使用示例import api, { userApi, orderApi } from './api';// 使用默认实例const fetchData = async () => { const data = await api.get('/data'); return data;};// 使用特定服务 APIconst getUserProfile = async () => { const profile = await userApi.getProfile(); return profile;};const createNewOrder = async (orderData) => { const order = await orderApi.createOrder(orderData); return order;};​
阅读 0·3月6日 22:56

在 Vue 项目中如何正确使用 axios?请说明最佳实践和与 Vue 3 的组合式 API 结合方式

在 Vue 项目中使用 axios 时,需要考虑 Vue 的响应式系统、生命周期、组合式 API 等特性。1. 基础封装创建 Axios 实例// utils/request.jsimport axios from 'axios';import { ElMessage, ElLoading } from 'element-plus';// 创建实例const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, headers: { 'Content-Type': 'application/json' }});// 请求拦截器service.interceptors.request.use( config => { // 添加 token const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } // 添加时间戳防止缓存 if (config.method === 'get') { config.params = { ...config.params, _t: Date.now() }; } return config; }, error => { return Promise.reject(error); });// 响应拦截器service.interceptors.response.use( response => { const res = response.data; // 根据业务状态码处理 if (res.code !== 200) { ElMessage.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); } return res.data; }, error => { const { response } = error; if (response) { switch (response.status) { case 401: ElMessage.error('登录已过期,请重新登录'); localStorage.removeItem('token'); window.location.href = '/login'; break; case 403: ElMessage.error('没有权限访问'); break; case 404: ElMessage.error('请求的资源不存在'); break; case 500: ElMessage.error('服务器内部错误'); break; default: ElMessage.error(response.data?.message || '请求失败'); } } else { ElMessage.error('网络错误,请检查网络连接'); } return Promise.reject(error); });export default service;按模块组织 API// api/user.jsimport request from '@/utils/request';export const userApi = { // 获取用户信息 getInfo: () => request.get('/user/info'), // 更新用户信息 updateInfo: (data) => request.put('/user/info', data), // 上传头像 uploadAvatar: (file) => { const formData = new FormData(); formData.append('file', file); return request.post('/user/avatar', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); }, // 修改密码 changePassword: (data) => request.post('/user/password', data)};// api/article.jsexport const articleApi = { getList: (params) => request.get('/articles', { params }), getDetail: (id) => request.get(`/articles/${id}`), create: (data) => request.post('/articles', data), update: (id, data) => request.put(`/articles/${id}`, data), delete: (id) => request.delete(`/articles/${id}`)};2. 在 Vue 2 中使用Options API 方式<template> <div class="user-profile"> <div v-if="loading">加载中...</div> <div v-else-if="error">{{ error }}</div> <div v-else> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> </div> </div></template><script>import { userApi } from '@/api/user';export default { data() { return { user: null, loading: false, error: null }; }, created() { this.fetchUserInfo(); }, beforeDestroy() { // 取消未完成的请求 if (this.cancelToken) { this.cancelToken.cancel('组件销毁'); } }, methods: { async fetchUserInfo() { this.loading = true; this.error = null; // 创建取消令牌 this.cancelToken = axios.CancelToken.source(); try { const data = await userApi.getInfo({ cancelToken: this.cancelToken.token }); this.user = data; } catch (err) { if (!axios.isCancel(err)) { this.error = err.message; } } finally { this.loading = false; } } }};</script>3. 在 Vue 3 中使用Composition API 方式<template> <div class="user-profile"> <div v-if="loading">加载中...</div> <div v-else-if="error">{{ error }}</div> <div v-else> <h1>{{ user?.name }}</h1> <p>{{ user?.email }}</p> <button @click="refresh">刷新</button> </div> </div></template><script setup>import { ref, onMounted, onUnmounted } from 'vue';import { userApi } from '@/api/user';const user = ref(null);const loading = ref(false);const error = ref(null);let controller = null;const fetchUserInfo = async () => { // 取消之前的请求 if (controller) { controller.abort(); } controller = new AbortController(); loading.value = true; error.value = null; try { const data = await userApi.getInfo({ signal: controller.signal }); user.value = data; } catch (err) { if (err.name !== 'AbortError') { error.value = err.message; } } finally { loading.value = false; }};const refresh = () => { fetchUserInfo();};onMounted(() => { fetchUserInfo();});onUnmounted(() => { if (controller) { controller.abort(); }});</script>封装可复用的 Composable// composables/useApi.jsimport { ref, onMounted, onUnmounted } from 'vue';import axios from 'axios';export function useApi(apiFunction, options = {}) { const { immediate = true, defaultValue = null } = options; const data = ref(defaultValue); const loading = ref(false); const error = ref(null); let controller = null; const execute = async (...params) => { if (controller) { controller.abort(); } controller = new AbortController(); loading.value = true; error.value = null; try { const result = await apiFunction(...params, { signal: controller.signal }); data.value = result; return result; } catch (err) { if (!axios.isCancel(err)) { error.value = err; throw err; } } finally { loading.value = false; } }; onUnmounted(() => { if (controller) { controller.abort(); } }); if (immediate) { onMounted(() => execute()); } return { data, loading, error, execute };}// 使用// composables/useUser.jsexport function useUser(userId) { const { data: user, loading, error, execute } = useApi( () => userApi.getInfo(userId), { immediate: true } ); return { user, loading, error, refresh: execute };}使用 Pinia 管理状态// stores/user.jsimport { defineStore } from 'pinia';import { ref, computed } from 'vue';import { userApi } from '@/api/user';export const useUserStore = defineStore('user', () => { // State const userInfo = ref(null); const loading = ref(false); const error = ref(null); // Getters const isLoggedIn = computed(() => !!userInfo.value); const userName = computed(() => userInfo.value?.name || ''); // Actions const fetchUserInfo = async () => { loading.value = true; error.value = null; try { const data = await userApi.getInfo(); userInfo.value = data; return data; } catch (err) { error.value = err.message; throw err; } finally { loading.value = false; } }; const updateUserInfo = async (data) => { const result = await userApi.updateInfo(data); userInfo.value = { ...userInfo.value, ...result }; return result; }; const logout = () => { userInfo.value = null; localStorage.removeItem('token'); }; return { userInfo, loading, error, isLoggedIn, userName, fetchUserInfo, updateUserInfo, logout };});// 在组件中使用<script setup>import { useUserStore } from '@/stores/user';const userStore = useUserStore();// 直接访问状态和 actionsconsole.log(userStore.userName);await userStore.fetchUserInfo();</script>4. 全局加载状态管理// composables/useLoading.jsimport { ref } from 'vue';const requestCount = ref(0);export function useLoading() { const showLoading = () => { requestCount.value++; }; const hideLoading = () => { if (requestCount.value > 0) { requestCount.value--; } }; const isLoading = computed(() => requestCount.value > 0); return { showLoading, hideLoading, isLoading };}// 在请求拦截器中使用service.interceptors.request.use(config => { const { showLoading } = useLoading(); showLoading(); return config;});service.interceptors.response.use( response => { const { hideLoading } = useLoading(); hideLoading(); return response; }, error => { const { hideLoading } = useLoading(); hideLoading(); return Promise.reject(error); });5. 文件上传组件封装<template> <div class="file-upload"> <input ref="fileInput" type="file" :accept="accept" @change="handleFileChange" style="display: none" /> <el-button @click="triggerUpload" :loading="uploading"> {{ uploading ? `上传中 ${progress}%` : '选择文件' }} </el-button> <div v-if="fileName" class="file-name">{{ fileName }}</div> </div></template><script setup>import { ref } from 'vue';import { userApi } from '@/api/user';const props = defineProps({ accept: { type: String, default: 'image/*' }});const emit = defineEmits(['success', 'error']);const fileInput = ref(null);const uploading = ref(false);const progress = ref(0);const fileName = ref('');const triggerUpload = () => { fileInput.value?.click();};const handleFileChange = async (e) => { const file = e.target.files[0]; if (!file) return; fileName.value = file.name; uploading.value = true; progress.value = 0; try { const formData = new FormData(); formData.append('file', file); const result = await userApi.uploadAvatar(formData, { onUploadProgress: (e) => { if (e.total) { progress.value = Math.round((e.loaded * 100) / e.total); } } }); emit('success', result); ElMessage.success('上传成功'); } catch (error) { emit('error', error); ElMessage.error('上传失败'); } finally { uploading.value = false; fileInput.value.value = ''; }};</script>6. 请求防抖和节流// composables/useDebounceApi.jsimport { ref } from 'vue';import { debounce, throttle } from 'lodash-es';export function useDebounceApi(apiFunction, wait = 300) { const loading = ref(false); const error = ref(null); const execute = debounce(async (...params) => { loading.value = true; error.value = null; try { return await apiFunction(...params); } catch (err) { error.value = err; throw err; } finally { loading.value = false; } }, wait); return { execute, loading, error };}// 搜索框使用示例<script setup>import { watch } from 'vue';import { useDebounceApi } from '@/composables/useDebounceApi';const searchQuery = ref('');const { execute: search, loading, error } = useDebounceApi(searchApi.search, 500);watch(searchQuery, (newVal) => { if (newVal) { search({ keyword: newVal }); }});</script>最佳实践总结封装请求层:统一处理配置、拦截器、错误提示使用 AbortController:组件卸载时取消请求创建 Composables:复用请求逻辑,配合 Vue 3 组合式 API状态管理:使用 Pinia 管理全局状态和用户信息加载状态:统一管理全局 loading,避免重复显示文件上传:封装组件,显示上传进度防抖节流:搜索等场景使用防抖减少请求次数错误处理:统一处理错误,根据状态码给出友好提示
阅读 0·3月6日 22:56

在 React 项目中如何正确使用 axios?请说明最佳实践和常见坑点

在 React 项目中使用 axios 时,需要考虑组件生命周期、状态管理、性能优化等多个方面。1. 基础封装创建 API 服务层// api/client.jsimport axios from 'axios';const apiClient = axios.create({ baseURL: process.env.REACT_APP_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' }});// 请求拦截器apiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error));// 响应拦截器apiClient.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); });export default apiClient;按模块组织 API// api/userApi.jsimport apiClient from './client';export const userApi = { getProfile: () => apiClient.get('/users/profile'), updateProfile: (data) => apiClient.put('/users/profile', data), uploadAvatar: (formData) => apiClient.post('/users/avatar', formData, { headers: { 'Content-Type': 'multipart/form-data' } })};// api/postApi.jsexport const postApi = { getList: (params) => apiClient.get('/posts', { params }), getDetail: (id) => apiClient.get(`/posts/${id}`), create: (data) => apiClient.post('/posts', data), update: (id, data) => apiClient.put(`/posts/${id}`, data), delete: (id) => apiClient.delete(`/posts/${id}`)};2. 在组件中使用使用 useEffect 和 AbortControllerimport { useEffect, useState } from 'react';import { userApi } from '../api/userApi';function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); const fetchUser = async () => { try { setLoading(true); const data = await userApi.getProfile(userId, { signal: controller.signal }); setUser(data); } catch (err) { if (!axios.isCancel(err)) { setError(err.message); } } finally { setLoading(false); } }; fetchUser(); // 清理函数:组件卸载时取消请求 return () => { controller.abort(); }; }, [userId]); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> );}自定义 Hook 封装// hooks/useApi.jsimport { useState, useEffect, useCallback } from 'react';import axios from 'axios';export const useApi = (apiFunction, dependencies = []) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const execute = useCallback(async (...params) => { const controller = new AbortController(); try { setLoading(true); setError(null); const result = await apiFunction(...params, { signal: controller.signal }); setData(result); return result; } catch (err) { if (!axios.isCancel(err)) { setError(err); throw err; } } finally { setLoading(false); } return () => controller.abort(); }, dependencies); return { data, loading, error, execute };};// 使用function PostList() { const { data: posts, loading, error, execute: fetchPosts } = useApi(postApi.getList); useEffect(() => { fetchPosts(); }, [fetchPosts]); if (loading) return <Spinner />; if (error) return <ErrorMessage error={error} />; return ( <ul> {posts?.map(post => <PostItem key={post.id} post={post} />)} </ul> );}3. 常见坑点及解决方案坑点 1:内存泄漏警告// ❌ 错误示例:组件卸载后设置状态useEffect(() => { fetchUser().then(data => { setUser(data); // 组件卸载后可能报错 });}, []);// ✅ 正确做法:使用 AbortController 或标记位useEffect(() => { let isMounted = true; const controller = new AbortController(); fetchUser({ signal: controller.signal }) .then(data => { if (isMounted) { setUser(data); } }); return () => { isMounted = false; controller.abort(); };}, []);坑点 2:竞态条件// ❌ 错误示例:快速切换时显示旧数据useEffect(() => { fetchUser(userId).then(data => { setUser(data); // 可能显示之前请求的结果 });}, [userId]);// ✅ 正确做法:取消之前的请求useEffect(() => { const controller = new AbortController(); fetchUser(userId, { signal: controller.signal }) .then(data => setUser(data)) .catch(err => { if (!axios.isCancel(err)) { setError(err); } }); return () => controller.abort();}, [userId]);坑点 3:表单重复提交// ❌ 错误示例:重复点击提交const handleSubmit = async (values) => { await createPost(values); // 可以重复点击};// ✅ 正确做法:使用 loading 状态防止重复提交const [submitting, setSubmitting] = useState(false);const handleSubmit = async (values) => { if (submitting) return; setSubmitting(true); try { await createPost(values); message.success('创建成功'); } catch (error) { message.error(error.message); } finally { setSubmitting(false); }};坑点 4:错误边界处理// ErrorBoundary.jsimport React from 'react';class ApiErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('API Error:', error, errorInfo); } render() { if (this.state.hasError) { return <ErrorFallback error={this.state.error} />; } return this.props.children; }}4. 与状态管理结合使用 Context + useReducer// contexts/ApiContext.jsconst ApiContext = createContext();const initialState = { users: [], loading: false, error: null};function apiReducer(state, action) { switch (action.type) { case 'FETCH_START': return { ...state, loading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, loading: false, users: action.payload }; case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload }; default: return state; }}export function ApiProvider({ children }) { const [state, dispatch] = useReducer(apiReducer, initialState); const fetchUsers = useCallback(async () => { dispatch({ type: 'FETCH_START' }); try { const users = await userApi.getList(); dispatch({ type: 'FETCH_SUCCESS', payload: users }); } catch (error) { dispatch({ type: 'FETCH_ERROR', payload: error.message }); } }, []); return ( <ApiContext.Provider value={{ ...state, fetchUsers }}> {children} </ApiContext.Provider> );}使用 React Query(推荐)import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';// 获取数据function useUsers() { return useQuery({ queryKey: ['users'], queryFn: () => userApi.getList(), staleTime: 5 * 60 * 1000, // 5分钟缓存 });}// 修改数据function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: userApi.create, onSuccess: () => { // 成功后刷新用户列表 queryClient.invalidateQueries({ queryKey: ['users'] }); } });}// 组件中使用function UserManager() { const { data: users, isLoading } = useUsers(); const createUser = useCreateUser(); const handleCreate = async (values) => { await createUser.mutateAsync(values); }; if (isLoading) return <Spinner />; return ( <div> <UserList users={users} /> <CreateUserForm onSubmit={handleCreate} /> </div> );}5. 性能优化请求去重// hooks/useDedupedApi.jsconst pendingRequests = new Map();export const useDedupedApi = (apiFunction) => { return useCallback(async (...params) => { const key = JSON.stringify({ func: apiFunction.name, params }); if (pendingRequests.has(key)) { return pendingRequests.get(key); } const promise = apiFunction(...params).finally(() => { pendingRequests.delete(key); }); pendingRequests.set(key, promise); return promise; }, [apiFunction]);};乐观更新const useOptimisticUpdate = () => { const queryClient = useQueryClient(); const updateOptimistically = useCallback(async ({ queryKey, mutationFn, updateFn, rollbackOnError = true }) => { // 取消正在进行的重新获取 await queryClient.cancelQueries({ queryKey }); // 保存之前的状态 const previousData = queryClient.getQueryData(queryKey); // 乐观更新 queryClient.setQueryData(queryKey, updateFn); try { await mutationFn(); } catch (error) { // 出错时回滚 if (rollbackOnError) { queryClient.setQueryData(queryKey, previousData); } throw error; } }, [queryClient]); return { updateOptimistically };};最佳实践总结封装 API 层:统一处理配置、拦截器、错误处理使用 AbortController:组件卸载时取消请求,防止内存泄漏自定义 Hooks:复用请求逻辑,统一管理 loading/error 状态防止重复提交:使用 loading 状态或防抖处理考虑使用 React Query:自动处理缓存、重试、乐观更新等错误边界:使用 ErrorBoundary 捕获渲染错误竞态处理:确保只显示最新请求的结果
阅读 0·3月6日 22:02

什么是 axios 以及它与原生 fetch API 相比有哪些优势?

什么是 axiosAxios 是一个基于 Promise 的 HTTP 客户端,可以用在浏览器和 Node.js 环境中。它提供了简洁的 API 来处理 HTTP 请求和响应。Axios 相比 Fetch API 的优势1. 自动转换 JSON 数据// Axios 自动处理 JSONconst response = await axios.get('/api/users');console.log(response.data); // 已经是 JavaScript 对象// Fetch 需要手动转换const response = await fetch('/api/users');const data = await response.json(); // 需要额外调用2. 请求/响应拦截器// 添加请求拦截器axios.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${token}`; return config;});// 添加响应拦截器axios.interceptors.response.use( response => response, error => { if (error.response.status === 401) { // 处理未授权 } return Promise.reject(error); });3. 请求取消功能const controller = new AbortController();axios.get('/api/data', { signal: controller.signal});// 取消请求controller.abort();4. 更好的错误处理Axios 在 HTTP 错误状态码(4xx, 5xx)时会自动 reject Promise提供了更详细的错误信息(response, request, config 等)5. 请求超时设置axios.get('/api/data', { timeout: 5000 });6. 支持请求进度监控axios.post('/api/upload', formData, { onUploadProgress: (progressEvent) => { const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); }});7. 浏览器和 Node.js 兼容浏览器端基于 XMLHttpRequestNode.js 端基于 http 模块8. 支持 CSRF 保护axios.defaults.xsrfCookieName = 'csrftoken';axios.defaults.xsrfHeaderName = 'X-CSRFToken';总结| 特性 | Axios | Fetch API ||------|-------|-----------|| JSON 自动转换 | ✅ | ❌ || 拦截器 | ✅ | ❌ || 请求取消 | ✅ | ✅ (AbortController) || 超时设置 | ✅ | ❌ || 进度监控 | ✅ | ❌ || 浏览器兼容 | IE11+ | 现代浏览器 || 体积 | ~13KB | 原生支持 |Axios 适合需要复杂 HTTP 处理的场景,而 Fetch API 适合简单的请求场景。
阅读 0·3月6日 21:56

说一下 splice 和 slice 的功能用法

splice() 和 slice() 都是 JavaScript 中用来处理数组的方法,但它们的功能和用法有所不同。splice()splice() 方法通过删除或替换现有元素或在数组中添加新元素来改变数组的内容。其基本语法如下:array.splice(start[, deleteCount[, item1[, item2[, ...]]]])start: 指定修改的开始位置(数组索引)。deleteCount: (可选)整数,表示要从数组中删除的元素数量。item1, item2, …: (可选)要添加进数组的新元素。示例:let myArray = ['a', 'b', 'c', 'd'];myArray.splice(1, 2, 'x', 'y'); // 从索引1开始删除2个元素,并添加'x'和'y'console.log(myArray); // 输出: ['a', 'x', 'y', 'd']slice()slice() 方法则返回一个新的数组,包含从开始到结束(不包括结束)选择的数组的一部分。原始数组不会被修改。其基本语法如下:array.slice(begin[, end])begin: 提取起始处的索引(从该索引开始提取元素)。end: (可选)提取结束处的索引(到该索引之前的元素会被提取)。示例:let myArray = ['a', 'b', 'c', 'd'];let newArray = myArray.slice(1, 3); // 提取从索引1到索引2的元素console.log(newArray); // 输出: ['b', 'c']console.log(myArray); // 原数组不变,输出: ['a', 'b', 'c', 'd']总结来说,splice() 是一个可以在任何位置添加或删除元素的方法,这会改变原数组,而 slice() 用于创建一个新的数组,包含原数组的一部分,原数组不会改变。
阅读 0·2月7日 16:44

JS 数组有哪些方法? 讲讲它们的区别跟使用场景

JavaScript数组作为核心数据结构,掌握其方法能显著提升代码效率和可维护性。本文将系统分析常用数组方法,深入探讨它们的区别、适用场景及最佳实践,帮助开发者写出更简洁、高性能的代码。所有方法基于ECMAScript标准,重点聚焦于函数式方法,避免常见陷阱。对于更详细的信息,可参考MDN文档。常见数组方法分类数组方法可大致分为以下几类,每类服务于特定需求:迭代方法:用于遍历和转换数组,如 map、filter、reduce、forEach,适合声明式编程。变更方法:直接修改原数组,如 push、pop、shift、unshift、splice、sort,适用于栈操作或原地修改。生成方法:创建新数组或字符串,如 slice、concat、join,常用于数据处理。其他方法:如 fill、from、includes、indexOf,提供额外功能。 关键提示:函数式方法(如 map、filter)返回新数组,不修改原数组,而变更方法(如 push)直接操作原数组。选择时需权衡性能和可读性。迭代方法详解迭代方法是数组处理的核心,强调纯函数特性,避免副作用。map作用:创建新数组,其中每个元素是调用回调函数的结果。不修改原数组。参数:回调函数(item, index, array)使用场景:数据转换,如数字列表转字符串、计算倍数。避免在回调中修改原数组,保持函数式纯度。代码示例:const numbers = [1, 2, 3];const doubled = numbers.map(num => num * 2);console.log(doubled); // [2, 4, 6]// 原数组未被修改console.log(numbers); // [1, 2, 3]filter作用:创建新数组,包含通过测试的元素。不修改原数组。参数:回调函数(item, index, array)使用场景:数据过滤,如筛选偶数、有效对象。与map对比:map转换所有元素,filter仅保留满足条件的元素。代码示例:const numbers = [1, 2, 3, 4];const evens = numbers.filter(num => num % 2 === 0);console.log(evens); // [2, 4]// 原数组未被修改console.log(numbers); // [1, 2, 3, 4]reduce作用:将数组元素归约成单个值(如总和、最大值)。不修改原数组。参数:回调函数(accumulator, currentValue, index, array),初始值可指定(如 0)。使用场景:聚合计算、链式操作。性能提示:对大型数组,避免嵌套循环,reduce 更高效。代码示例:const numbers = [1, 2, 3, 4];const sum = numbers.reduce((acc, num) => acc + num, 0);console.log(sum); // 10const max = numbers.reduce((acc, num) => Math.max(acc, num), -Infinity);console.log(max); // 4forEach作用:对数组每个元素执行回调,但不返回新数组。不修改原数组。参数:回调函数(item, index, array)使用场景:遍历操作,如DOM修改。避免使用:因无返回值,不适合链式操作,仅用于副作用。代码示例:const items = ['a', 'b', 'c'];items.forEach(item => { console.log(`Item: ${item}`);});// 输出: Item: a// Item: b// Item: c// 原数组未被修改console.log(items); // ['a', 'b', 'c']变更方法详解变更方法直接修改原数组,适用于原地操作,但可能破坏函数式纯度。push/pop作用:push添加元素到末尾,pop移除末尾元素(栈操作)。参数:push接收多个值;pop无参数。使用场景:栈实现、队列操作。性能提示:对于频繁操作,避免在循环中使用,考虑slice等替代方案。代码示例:const stack = [];stack.push('item1', 'item2');console.log(stack); // ['item1', 'item2']const last = stack.pop();console.log(last); // 'item2'console.log(stack); // ['item1']splice作用:插入、删除或替换数组元素,返回被移除的元素。参数:start索引,deleteCount,items(可选)。使用场景:动态数组修改。注意事项:修改原数组,可能导致意外副作用。代码示例:const arr = [1, 2, 3, 4];const removed = arr.splice(1, 2, 'a', 'b');console.log(removed); // [2, 3]console.log(arr); // [1, 'a', 'b', 4]sort作用:对数组元素排序,默认按字符串规则(需显式指定比较函数)。参数:可选比较函数(a, b)。使用场景:数据排序。性能提示:对大型数组,使用Array.prototype.sort可能慢,优先使用Array.from和稳定排序。代码示例:const nums = [3, 1, 4, 2];nums.sort((a, b) => a - b);console.log(nums); // [1, 2, 3, 4]// 对字符串排序const names = ['Alice', 'Bob', 'Charlie'];console.log(names.sort()); // ['Alice', 'Bob', 'Charlie']生成方法详解生成方法返回新数组或字符串,不修改原数组,适合数据处理。slice作用:返回新数组,包含从start到end(不含)的元素。参数:start(索引,负值表示倒数),end(可选,索引)。使用场景:复制数组片段、避免原地修改。关键区别:slice vs splice——slice不修改原数组,splice会修改。代码示例:const arr = [1, 2, 3, 4];const sub = arr.slice(1, 3);console.log(sub); // [2, 3]console.log(arr); // [1, 2, 3, 4] // 原数组未被修改concat作用:连接多个数组或值,返回新数组。参数:一个或多个数组/值。使用场景:合并数组、拼接数据。性能提示:对大型数组,避免嵌套concat,使用[...arr1, ...arr2]更高效。代码示例:const arr1 = [1, 2];const arr2 = [3, 4];const merged = arr1.concat(arr2);console.log(merged); // [1, 2, 3, 4]join作用:将数组元素连接成字符串,用指定分隔符。参数:分隔符(默认',')。使用场景:生成字符串、日志输出。注意事项:对大型数组,可能产生内存问题,避免过度使用。代码示例:const fruits = ['apple', 'banana', 'cherry'];const str = fruits.join(', ');console.log(str); // 'apple, banana, cherry'方法选择指南掌握方法区别后,需根据场景选择最优方案:map vs filter:map用于转换所有元素(如[1,2,3] → [2,4,6]),filter用于过滤(如[1,2,3,4] → [2,4])。选择建议:数据转换用map,数据筛选用filter。避免副作用:forEach适合遍历副作用(如DOM操作),但不适合链式操作;map和filter返回新数组,适合纯函数式代码。性能优化:对大型数组,优先使用slice(不修改原数组)而非splice(修改原数组)。计算聚合时,reduce 比 for 循环更高效且可读。避免在循环中使用push,改用array.map().push() 或 array.concat()。安全实践:始终优先使用函数式方法(map、filter),避免for循环,提升代码可测试性。对原地操作(如splice),确保数据副本,防止意外副作用。 实践建议:在开发中,使用console.log验证数组行为,例如:结论JavaScript数组方法是前端开发的核心工具。本文系统分析了关键方法的区别与使用场景,强调函数式方法的优势(如map、filter)和变更方法的适用性。最佳实践:优先使用声明式方法,避免副作用;性能敏感场景,选择高效操作;持续学习新特性(如Array.from和Array.of)。掌握这些方法,能显著提升代码质量,使开发更高效、可维护。记住:数组方法的正确选择是性能优化和代码健壮性的关键。 延伸阅读:在现代JavaScript中,数组方法与迭代器结合,可实现更高级的流式处理。例如,使用Array.from转换可迭代对象:​
阅读 0·2月7日 13:47

JavaScript 原型中 prototype 和proto区别是什么?

在JavaScript中,prototype属性和__proto__属性(通常读作"proto")是有关于对象原型链的概念,但它们在使用和目的上有所不同。prototype属性prototype是函数对象(Function objects)的一个属性。当你使用构造函数创建一个新对象时,这个新对象的内部[[Prototype]](也就是它的__proto__属性)会被赋值为构造函数的prototype属性。这意味着,使用同一个构造函数创建的所有对象都会共享同一个prototype对象。举个例子,如果我们有一个构造函数:function Person(name) { this.name = name;}Person.prototype.sayHello = function() { console.log(`Hello, my name is ${this.name}`);};当我们创建一个Person实例时:var person1 = new Person("Alice");person1对象的[[Prototype]](即__proto__)会指向Person.prototype,这使得person1能够访问到sayHello方法。__proto__属性__proto__是每个JavaScript对象都拥有的一个内部属性,它指向该对象的原型。这是一个从对象指向其构造函数的prototype属性的链接。根据ECMAScript标准,__proto__是[[Prototype]]的实现,而[[Prototype]]是对象的内部属性。在现代JavaScript开发中,通常推荐使用Object.getPrototypeOf(obj)来获取对象的原型,而不是直接使用__proto__,因为__proto__并不是所有JavaScript环境中都得到支持。再次拿刚才的例子,person1.__proto__会指向Person.prototype,因为person1是由Person构造函数创建的。小结prototype是函数特有的属性,用于当作构造函数时为实例对象指定原型。__proto__是每个对象都有的属性,指向该对象的原型。在实践中,prototype用来实现基于原型的继承和共享属性/方法,而__proto__提供了一种访问和操作对象原型链的方式。然而,直接操作__proto__被视为不太安全的做法,尤其是在现代JavaScript编程中,应该利用Object.getPrototypeOf()和Object.setPrototypeOf()等方法来替代__proto__的直接使用。
阅读 300·2024年8月9日 17:42

script 标签的 defer 和 async 有什么区别?

当您在 HTML 文档中使用 <script> 标签引入 JavaScript 时,defer 和 async 属性可以控制脚本的加载和执行方式,它们之间的区别主要在于脚本加载的时间以及执行的时机。defer 属性使用 defer 属性的 <script> 标签会让脚本在文档解析期间异步下载,但是会延迟到整个文档解析完毕之后、DOMContentLoaded 事件触发之前执行。这意味着带有 defer 的脚本总是在文档解析完成之后执行,保证了执行时 DOM 已经完全构建好。例子:<script src="example.js" defer></script>如果您有多个带有 defer 属性的脚本,它们将按照在文档中出现的顺序执行,即便有些脚本可能会比其他脚本更早下载完成。async 属性而 async 属性也允许脚本在文档解析时异步下载,但是它一旦下载完成就会立即执行,这可能会在文档的其余部分尚未解析完毕时发生。因此,使用 async 的脚本不能保证按照在页面中出现的顺序执行,也无法保证 DOM 完全构建完成。例子:<script src="example.js" async></script>async 适用于那些不依赖于其他脚本且不依赖于 DOM 的脚本,例如,广告加载或者埋点脚本。总结defer 确保脚本在文档完全解析和 DOM 构建完成后,但在 DOMContentLoaded 事件之前执行。async 确保脚本在下载完成后尽快执行,但可能会打断文档的解析过程。没有这两个属性的 <script> 标签会立即下载并阻塞文档解析直到脚本执行完成。在实际应用中,选择 defer 或 async 取决于脚本对文档解析的依赖性,以及脚本之间的依赖关系。如果您需要确保脚本按照顺序执行,并且在 DOM 完全构建后执行,那么 defer 是更好的选择。如果脚本的执行顺序不重要,并且想尽快获取并执行脚本,可以使用 async。
阅读 166·2024年8月5日 12:52