前端高频知识点汇总:从手写实现到工程化实践(面试&开发双视角)
前言
本文汇总了前端开发/面试中高频的6个核心知识点,基于实际开发场景和面试问题展开,涵盖 Promise.all手写实现、懒加载(原生+Vue3)、Axios二次封装、HTTP状态码解析、数组扁平化去重排序、Vue3父子组件通信。每个知识点均包含「原理拆解、代码实现、易错点、优化方案」,既适合新手夯实基础,也适合面试者梳理核心考点,助力快速掌握前端核心技能。
一、手写Promise.all:从错误分析到正确实现
1.1 常见错误实现及原因
很多初学者手写Promise.all时易犯以下错误,导致代码无法运行或偏离原生规则:
javascript
// 错误示例
function myPromiseAll(){
const new Promise((resolve,reject)=>{ // 语法错:const不能修饰new实例
const results=[]
const res=Array.from(arguments) // 设计错:依赖arguments,违背原生可迭代对象参数规则
res.forEach((res.index)=>{ // 语法错:forEach回调参数格式错误
Promise.resolve(res).then(value=>{ // 逻辑错:处理整个数组而非单个元素
results.push(value)
const len=0; len++ // 语法错:const变量不可修改
if(len===res.length) resolve(results)
}.catch(err=>reject(err)) // 语法错:then回调未闭合
})
})
}
核心错误总结:
- 语法错误:
const new Promise、const重赋值、then/catch括号未闭合、forEach参数格式错; - 逻辑错误:依赖
arguments(原生接收可迭代对象)、处理整个数组而非单个元素; - 设计偏差:未返回Promise、缺少可迭代对象校验、空数组处理。
1.2 正确实现(对齐原生规则)
javascript
function myPromiseAll(iterable) {
return new Promise((resolve, reject) => {
try {
// 1. 校验可迭代对象(对齐原生)
if (typeof iterable[Symbol.iterator] !== 'function') {
throw new TypeError(`${typeof iterable} ${iterable} is not iterable`);
}
const items = Array.from(iterable);
const total = items.length;
const results = [];
let completed = 0;
// 2. 空数组处理
if (total === 0) {
resolve(results);
return;
}
// 3. 遍历处理单个元素
items.forEach((item, index) => {
Promise.resolve(item)
.then(value => {
results[index] = value; // 按索引存值,保证顺序
completed++;
if (completed === total) resolve(results);
})
.catch(err => reject(err)); // 一错即错
});
} catch (err) {
reject(err);
}
});
}
核心要点:
- 接收「可迭代对象」(如数组),对齐原生
Promise.all调用规则; - 按索引存储结果,保证输出顺序与输入一致;
- 支持同步/异步元素,
Promise.resolve统一处理; - 一错即错:任意元素reject,立即触发整体reject。
二、懒加载实现:从原生到Vue3工程化
懒加载核心是「延迟加载非可视区域资源」,减少首屏请求,提升性能,分「原生实现」和「Vue3插件实现」两种场景。
2.1 原生懒加载(兼容&现代两种方案)
方案1:传统实现(scroll + getBoundingClientRect)
兼容IE,需手动节流优化:
html
<!-- HTML -->
<img class="lazy-img" data-src="real-img-1.jpg" alt="懒加载图片">
<script>
// 节流函数
function throttle(fn, wait = 100) {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
}
};
}
// 判断元素是否进入视口
function isInViewport(el) {
const rect = el.getBoundingClientRect();
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
return rect.top <= viewHeight + 100; // 提前100px加载
}
// 核心逻辑
function handleLazyLoad() {
const imgs = document.querySelectorAll('.lazy-img:not([data-loaded])');
imgs.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
img.dataset.loaded = 'true';
}
});
}
// 初始化+监听
handleLazyLoad();
window.addEventListener('scroll', throttle(handleLazyLoad));
window.addEventListener('resize', throttle(handleLazyLoad));
</script>
方案2:现代实现(IntersectionObserver)
ES6+新增API,异步监听,性能更优:
html
<img class="lazy-img" data-src="real-img-1.jpg" alt="懒加载图片">
<script>
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // 停止监听,释放内存
}
});
}, { rootMargin: '100px 0' }); // 提前100px加载
document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img));
} else {
// 降级到传统方案
handleLazyLoad();
window.addEventListener('scroll', throttle(handleLazyLoad));
}
</script>
2.2 Vue3+VueUse实现懒加载插件
基于useIntersectionObserver封装全局指令,适配Vue3工程化项目:
javascript
// src/plugins/lazyPlugin.js
import { useIntersectionObserver } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import 'element-plus/es/components/message/style/css';
export const lazyPlugin = {
install(app) {
// 注册全局自定义指令 v-img-lazy
app.directive('img-lazy', {
mounted(el, binding) {
// 监听元素视口状态
const { stop } = useIntersectionObserver(
el, // 要监听的DOM元素
([{ isIntersecting }]) => { // 视口状态变化回调
if (isIntersecting) {
el.src = binding.value; // 赋值真实图片地址
// 加载失败兜底
el.onerror = () => {
el.src = 'error-img.jpg';
ElMessage.warning('图片加载失败');
};
stop(); // 停止监听
}
},
{ rootMargin: '100px 0' } // 提前加载
);
}
});
}
};
// 入口文件 main.js
import { createApp } from 'vue';
import App from './App.vue';
import { lazyPlugin } from './plugins/lazyPlugin';
const app = createApp(App);
app.use(lazyPlugin); // 安装插件
app.mount('#app');
使用方式:
vue
<template>
<img v-img-lazy="item.imgUrl" alt="商品图片" class="lazy-img">
</template>
三、Axios二次封装:前端请求层工程化
Axios封装核心是「统一配置、身份认证、全局错误处理」,适配Vue3+Pinia+Element Plus技术栈:
javascript
// src/utils/http.js
import axios from 'axios';
import { ElMessage } from 'element-plus';
import 'element-plus/es/components/message/style/css';
import { useUserStore } from '@/stores/userStore';
import router from '@/router';
// 1. 创建独立Axios实例(隔离全局配置)
const httpInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量区分环境
timeout: 30000
});
// 2. 请求拦截器:自动携带Token
httpInstance.interceptors.request.use(
config => {
const userStore = useUserStore();
if (userStore.userInfo.token) {
// Bearer Token格式(OAuth2.0标准)
config.headers.Authorization = `Bearer ${userStore.userInfo.token}`;
}
return config;
},
e => Promise.reject(e)
);
// 3. 响应拦截器:统一处理结果+错误
httpInstance.interceptors.response.use(
res => res.data, // 简化返回数据(只给业务层data)
e => {
// 区分网络错误和服务器响应错误
const msg = e.response ? e.response.data.message : '网络异常,请检查网络';
ElMessage.warning(msg);
// 401未授权:Token失效/未登录
if (e.response?.status === 401) {
const userStore = useUserStore();
userStore.clearUserInfo(); // 清空用户状态
router.push('/login'); // 跳登录页
}
return Promise.reject(e); // 保留错误链,供业务层catch
}
);
export default httpInstance;
核心优势:
- 实例隔离:避免污染全局Axios配置,支持多域名请求;
- 认证自动化:登录后自动携带Token,401自动清状态跳登录;
- 错误全局化:统一错误提示,减少业务层重复代码;
- 环境适配:通过环境变量区分开发/测试/生产环境的baseURL。
四、HTTP状态码300/400/500:核心含义与业务处理
HTTP状态码按首位数字分类,核心区别是「责任方不同」,结合Axios封装讲解业务处理逻辑:
4.1 3xx系列:重定向(资源位置变更)
| 状态码 | 含义 | 常见场景 | 处理逻辑 |
|---|---|---|---|
| 301 | 永久重定向 | 域名更换、接口路径永久迁移 | Axios自动跟随,无需手动处理 |
| 302 | 临时重定向 | 登录成功跳首页、临时维护跳转 | 自动跟随,特殊场景可拦截处理 |
| 304 | 资源未修改(缓存命中) | 静态资源(图片/CSS/JS)缓存 | Axios自动复用缓存,减少请求 |
4.2 4xx系列:客户端错误(请求有问题)
| 状态码 | 含义 | 常见场景 | 处理逻辑 |
|---|---|---|---|
| 400 | 请求语法/参数错误 | 必传参数缺失、JSON格式错误 | 全局提示后端错误信息 |
| 401 | 未授权(Token无效) | Token过期、未登录访问权限接口 | 清用户状态+跳登录页 |
| 403 | 禁止访问(权限不足) | 普通用户访问管理员接口 | 提示"无权限操作" |
| 404 | 资源/接口不存在 | 接口路径写错、资源ID无效 | 提示"接口不存在" |
| 408 | 请求超时 | 网络卡顿、请求体过大 | 提示"请求超时,请重试" |
4.3 5xx系列:服务端错误(服务器异常)
| 状态码 | 含义 | 常见场景 | 处理逻辑 |
|---|---|---|---|
| 500 | 服务器内部错误 | 后端代码Bug、数据库连接失败 | 提示"服务器错误,请稍后重试" |
| 502 | 网关错误 | Nginx配置错误、后端服务宕机 | 提示"网关错误,联系管理员" |
| 503 | 服务不可用 | 服务器过载、维护中 | 提示"服务维护中,请稍后重试" |
| 504 | 网关超时 | 后端接口响应过慢、数据库超时 | 提示"网关超时,请重试" |
核心原则:
- 4xx:客户端问题(改请求参数/登录/权限);
- 5xx:服务端问题(客户端只能提示重试,后端排查日志);
- 3xx:自动处理为主,特殊场景手动拦截。
五、数组扁平化去重排序:从API到手动实现
5.1 简洁实现(ES6+ API)
利用flat(Infinity)+Set+sort实现,适合现代环境:
javascript
function flattenAndSort(arr) {
// 1. 深度扁平化(Infinity表示无限层级)
const flattened = arr.flat(Infinity);
// 2. 去重(Set不允许重复基础类型)
const unique = [...new Set(flattened)];
// 3. 数字升序排序(避免默认字符串排序)
return unique.sort((a, b) => a - b);
}
// 示例:输入[3, [2, [1, 4], 2], [5, 3]] → 输出[1,2,3,4,5]
5.2 手动实现(兼容低版本环境)
手动实现深度扁平化(递归+reduce),替代flat(Infinity):
javascript
// 手动深度扁平化
function flatDeep(arr) {
return arr.reduce((acc, val) => {
// 递归处理子数组,非数组直接拼接
return Array.isArray(val) ? acc.concat(flatDeep(val)) : acc.concat(val);
}, []);
}
// 去重(兼容无Set环境)
function uniqueArr(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// 完整函数
function flattenAndSortOpt(arr) {
const flattened = flatDeep(arr);
const unique = uniqueArr(flattened);
return unique.sort((a, b) => a - b);
}
5.3 易错点说明
flat(Infinity)兼容性:IE不支持,需手动实现;Set去重限制:仅支持基础类型(引用类型去重无效);sort()默认坑:默认按字符串Unicode排序,数字排序必须传比较函数(a,b)=>a-b。
六、Vue3父子组件通信:核心规则与实践
Vue3父子组件通信遵循「单向数据流」,核心方式是「Props下传,事件上抛」,附完整示例:
6.1 子组件(Child.vue):接收Props+触发事件
vue
<template>
<div class="child">
<p>父组件传的商品名:{{ productName }}</p>
<p>当前数量:{{ count }}</p>
<button @click="handleAdd">数量+1</button>
<!-- 插槽:父组件传内容 -->
<slot></slot>
</div>
</template>
<script setup lang="ts">
// 1. 定义Props(类型校验+默认值)
const props = defineProps({
productName: { type: String, required: true },
initCount: { type: Number, default: 0 }
});
// 2. 子组件内部状态
const count = ref(props.initCount);
// 3. 声明自定义事件
const emit = defineEmits(['count-change']);
// 4. 触发事件:给父组件传值
const handleAdd = () => {
count.value++;
emit('count-change', count.value); // 事件名+参数
};
</script>
6.2 父组件(Parent.vue):传Props+监听事件
vue
<template>
<div class="parent">
<p>子组件传回的数量:{{ totalCount }}</p>
<!-- 使用子组件:传Props+监听事件 -->
<Child
:product-name="productName"
:init-count="0"
@count-change="handleCountChange"
>
<!-- 插槽内容 -->
<p>父组件传给子组件的内容</p>
</Child>
</div>
</template>
<script setup lang="ts">
import Child from '@/components/Child.vue';
import { ref } from 'vue';
const productName = ref('Vue实战教程');
const totalCount = ref(0);
// 处理子组件事件
const handleCountChange = (newCount: number) => {
totalCount.value = newCount;
};
</script>
6.3 核心规则与扩展
- 单向数据流 :子组件不能直接修改Props,需通过
emit通知父组件修改; - 命名规范 :Props/事件名用
camelCase定义,模板中用kebab-case(如product-name); - 多层通信 :爷孙组件用
provide/inject,避免Props透传; - 访问子组件 :用
ref获取子组件实例(慎用,破坏单向数据流)。
七、面试技巧:高频问题回答思路
- 手写类问题(Promise.all/扁平化) :
- 先讲核心原理→写代码→分析易错点→优化方案;
- 工程化问题(Axios/懒加载插件) :
- 讲封装思路→核心功能(拦截器/指令)→业务处理→优化点;
- Vue组件通信 :
- 总述核心方式(Props+emit)→代码示例→单向数据流→扩展场景(插槽/provide);
- 状态码问题 :
- 分类总述(3xx/4xx/5xx责任方)→结合业务处理→工程化优化。
八、总结
本文覆盖的6个知识点是前端「基础+工程化」的核心,也是面试高频考点:
- 基础能力:Promise.all手写、数组扁平化(考验逻辑思维);
- 性能优化:懒加载(原生+Vue3,提升页面加载速度);
- 工程化:Axios封装、Vue组件通信(企业级项目必备);
- 问题排查:HTTP状态码(前后端联调核心)。
建议结合代码实操,重点掌握「原理+易错点+优化方案」,既能应对面试,也能提升实际开发效率。
参考资料
- Vue官方文档:https://vuejs.org/
- Axios官方文档:https://axios-http.com/
- VueUse文档:https://vueuse.org/
- MDN HTTP状态码:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status