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

JavaScript面试题手册

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

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

javascript 中垃圾回收的方法有哪些?

JavaScript中的垃圾回收(garbage collection)是一种自动内存管理机制,它帮助开发者不需要手动释放分配的内存。在JavaScript中,垃圾回收主要采用了以下几种方法:1. 标记清除(Mark and Sweep)这是最常见的垃圾回收算法。当变量进入环境时,就“标记”这个变量为“进入环境”。当变量离开环境时,则“标记”这个变量为“离开环境”。垃圾收集器会定期运行,它会检查所有的变量,以及它们引用的其他变量是否还在环境中。如果一个变量已经不再环境中,且没有任何其他变量引用它,那么这个变量占用的内存就会被回收。例子:function processData() { var data = { /* 大量数据 */ }; // 使用data进行处理}processData();// processData执行完毕后,data变量离开环境,变成无法访问的状态,会被标记为可回收。2. 引用计数(Reference Counting)引用计数是另一种垃圾回收机制。在这个系统中,每一个值都有一个“引用数”,表示有多少变量或资源引用这个值。如果引用数变为0,则表示该值不再需要,其占用的内存可以被回收。这种方法的一个问题是循环引用:如果两个对象互相引用,即便它们已经不再需要,它们的引用数也不会降到0,导致内存无法被回收。例子:function referenceCycle() { var objectA = {}; var objectB = {}; objectA.other = objectB; objectB.other = objectA;}referenceCycle();// 即使referenceCycle函数执行结束,objectA和objectB因为相互引用,它们的引用数都不为0,造成内存泄漏。3. 分代收集(Generational Collection)分代收集是基于对象存活时间的假设,将对象分为两组:“新生代”和“老生代”。新创建的对象属于新生代,对象如果存活足够长的时间,就会被移动到老生代。通常新生代使用标记-复制(mark-copy)算法,老生代使用标记-清除(mark-sweep)或标记-整理(mark-compact)算法。4. 标记-整理(Mark-Compact)这种方法是对标记-清除的改进。在标记阶段,标记所有活动对象,然后在整理阶段,将所有活动的对象移动到内存的一端,然后清理掉边界之外的内存。5. 增量收集(Incremental Collection)增量收集是将垃圾回收分成小片段执行,每次只处理一部分对象,然后暂停,让程序执行。这种方式可以减少垃圾收集过程中的停顿时间。6. 闲时收集(Idle-time Collection)某些JavaScript引擎会利用CPU空闲时间来执行垃圾回收的工作,以避免影响到程序的执行效率。
阅读 162·2024年8月5日 12:52

JavaScript 中对象 instanceOf 属性的原理是什么?

instanceof 是 JavaScript 中的一个二元操作符,用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。当我们使用语法 object instanceof Constructor 时,instanceof 会沿着 object 的原型链向上查找,检查是否有原型对象等于 Constructor.prototype。如果找到了这样的原型,instanceof 就会返回 true;如果一直查找到原型链的顶端(也就是 null),依然没有找到,就会返回 false。原型链是 JavaScript 中实现继承的一种机制。每个对象都有一个内部链接指向另一个对象,即它的原型。那个原型对象也有自己的原型,以此类推,直到一个对象的原型为 null。常见的 Object.prototype 就是很多对象原型链的终点。这里举一个例子来说明 instanceof 的工作原理:function Car(make, model) { this.make = make; this.model = model;}const myCar = new Car('Toyota', 'Corolla');console.log(myCar instanceof Car); // 输出:trueconsole.log(myCar instanceof Object); // 输出:true在这个例子中,myCar instanceof Car 返回 true,因为 Car.prototype 在 myCar 的原型链上。myCar instanceof Object 也返回 true,尽管我们没有直接设置 myCar 的原型为 Object.prototype。但由于 JavaScript 中几乎所有对象的原型链的最终都会指向 Object.prototype,因此任何对象基本上都是 Object 的一个实例。总的来说,instanceof 操作符提供了一种检查对象类型的方法,并且能够识别对象继承关系中的原型。
阅读 112·2024年8月5日 12:52

JavaScript 中的原型&原型链是怎么工作的?

JavaScript中的原型(prototype)和原型链(prototype chain)是其面向对象编程的基础。这两个概念是理解JavaScript中对象之间的关系和继承机制非常关键的部分。原型(Prototype)在JavaScript中,每一个函数创建时都会有一个名为 prototype的属性,这个属性是一个对象,它包含了可以由特定类型的所有实例共享的属性和方法。这意味着你可以使用原型来添加或者分享功能,而不需要在每个实例中重新定义这些功能。例如,假设我们定义了一个构造函数:function Person(name) { this.name = name;}Person.prototype.sayName = function() { console.log(this.name);};以下是通过 Person构造函数创建的两个实例:var person1 = new Person("Alice");var person2 = new Person("Bob");这里的 person1和 person2都没有自己的 sayName方法。当你调用 person1.sayName()时,JavaScript会查找 person1是否有这个方法。由于 person1没有,它会通过原型链查找 Person.prototype是否有 sayName方法。这样,person1和 person2实际上共享了 Person.prototype上的 sayName方法。原型链(Prototype Chain)原型链是JavaScript实现继承的机制。每个对象都有一个内部链接到另一个对象,即它的原型。这个原型对象本身也有一个原型,以此类推,直到某个对象的原型为 null。按照规范,null的原型没有原型,这通常被视为原型链的末端。当你尝试访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找,直到找到这个属性或者到达原型链的末端。继续上面的例子,我们可以看到原型链是如何工作的:person1.sayName(); // 输出: Alice首先,JavaScript引擎检查 person1自身是否有 sayName属性。发现 person1没有,引擎接着检查 person1的原型(即 Person.prototype)。Person.prototype有 sayName方法,所以这个方法被调用。如果我们有一个对象的原型是另一个对象,那么第二个对象的属性和方法也可以被第一个对象访问。这个过程可以一直持续下去,形成了一个“链”。通过原型链实现继承我们可以使用原型链来实现继承。例如,如果我们有一个 Employee构造函数,它应该继承 Person:function Employee(name, title) { Person.call(this, name); // 继承属性 this.title = title;}Employee.prototype = Object.create(Person.prototype);Employee.prototype.constructor = Employee;Employee.prototype.sayTitle = function() { console.log(this.title);};var employee1 = new Employee("Charlie", "Developer");employee1.sayName(); // 输出: Charlieemployee1.sayTitle(); // 输出: Developer在这里,Employee.prototype被设置为一个新对象,这个新对象的原型是 Person.prototype。这样,Employee的所有实例都继承了 Person的方法。我们还修正了 Employee.prototype.constructor属性,确保它指向正确的构造函数。通过上面的代码,我们创建了一个 Employee的实例 employee1,它能够调用继承自 Person的 sayName方法,同时还有自己特有的 sayTitle方法。
阅读 142·2024年8月5日 12:52