先介绍下本人情况,base 西安,20 年民本金融学毕业,在教培晃荡两年被优化,遂在某培训班转型前端开发,包了两年经验,后面入职了一家初创自研,成功混了两年后项目停住被优化,从去年 11 月到现在躺了一年多,期间入职一家小公司天天加班到 11 点,一周后老板跑路,目前正在仲裁,最近在重拾面试题,前几天面试了软通推的一个项目,回答的非常糟糕,期间自己都没绷住,把回忆起的面试题分享给大家,希望能帮助到和我一样在求职的小伙伴;下面是我回忆的面试题,后面我会给出答案。
/…
flex 布局怎么实现左边固定,右边自适应?
react 中常见的 hook
webpack 中 loader 和 plugin 的区别
写一个深拷贝
写一个数组去重
后台项目中怎么做权限
im 通讯中怎么做心跳检测
defer 和 async 的区别
什么是 treeshake
js 事件循环机制
ts 中的 interface 和 type 的区别和使用场景
什么是宏任务?什么是微任务?
讲讲 promise?
怎么让元素页面居中
…/
Flex 布局实现该效果核心是利用
flex 属性的分配规则,有两种常用方案:
/* 容器 */
.container {
display: flex;
width: 100%;
height: 500px;
}
/* 左侧固定 */
.left {
width: 200px; /* 固定宽度 */
flex: none; /* 禁止伸缩 */
background: #eee;
}
/* 右侧自适应 */
.right {
flex: 1; /* 占据剩余所有空间 */
background: #ddd;
}
.container {
display: flex;
width: 100%;
}
.left {
flex-basis: 200px; /* 固定基准宽度 */
flex-shrink: 0; /* 禁止缩小 */
}
.right {
flex-grow: 1; /* 允许放大占满剩余空间 */
flex-shrink: 1; /* 允许缩小(适配容器不足场景) */
}
核心原理:
flex:1 等价于
flex-grow:1; flex-shrink:1; flex-basis:0%,表示右侧元素可伸缩并占据剩余空间;左侧通过固定宽度 + 禁止伸缩保证尺寸不变。
React Hook 是 React 16.8 新增特性,用于在函数组件中使用状态和生命周期等特性,核心常用 Hook 如下:
| Hook 名称 | 核心作用 | 使用场景 |
| ----------------------- | -------------------------------------------------------- | ------------------------------------------------ |
|
useState | 声明状态变量,返回[状态, 修改状态的方法] | 管理组件内部简单状态(如输入框值、开关状态) |
|
useEffect | 处理副作用(DOM 操作、请求、订阅),替代类组件生命周期 | 数据请求、监听窗口大小、清理定时器 |
|
useRef | 创建可变的引用对象,值更新不触发组件重渲染 | 获取 DOM 元素、保存定时器 ID、跨渲染周期保存值 |
|
useContext | 接收上下文对象,跨组件共享数据(无需 props 逐层传递) | 全局状态(如主题、用户信息)共享 |
|
useReducer | 替代 useState,处理复杂状态逻辑(类似 Redux) | 状态逻辑复杂、多个子值的状态管理 |
|
useMemo | 缓存计算结果,避免重复计算 | 复杂数据处理、避免子组件不必要重渲染 |
|
useCallback | 缓存函数引用,避免函数重复创建 | 传递给子组件的回调函数(配合 memo 使用) |
|
useLayoutEffect | 同步执行副作用,在 DOM 更新后、浏览器绘制前触发 | 需要同步修改 DOM 的场景(如获取 DOM 尺寸) |
Loader 和 Plugin 是 Webpack 核心扩展机制,但定位和作用完全不同:
| 维度 | Loader | Plugin |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| 核心作用 | 转换模块代码(处理非 JS 文件) | 扩展 Webpack 生命周期功能(打包优化、资源管理) |
| 处理对象 | 单个文件(模块),专注“文件转换” | 整个打包流程,专注“流程控制” |
| 执行时机 | 模块解析阶段(module 阶段) | 打包的各个生命周期钩子(compiler/hook) |
|
使用方式 | 配置在
module.rules 数组中 | 配置在
plugins 数组中(需实例化) |
| 示例 | css-loader(解析 CSS)、babel-loader(转译 ES6+)、file-loader(处理静态资源) | HtmlWebpackPlugin(生成 HTML)、MiniCssExtractPlugin(提取 CSS)、CleanWebpackPlugin(清理输出目录) |
| 开发方式 | 导出函数,接收源码返回处理后代码 | 基于 Tapable 钩子,通过 compiler 对象注册事件 |
| 核心总结:Loader 解决“文件怎么转”,Plugin 解决“打包流程怎么扩展”。 | | |
function deepClone(target, map = new WeakMap()) {
// 1. 处理基本类型和null
if (target === null || typeof target !== 'object') {
return target;
}
// 2. 处理循环引用
if (map.has(target)) {
return map.get(target);
}
// 3. 处理日期/正则
if (target instanceof Date) {
const newDate = new Date(target);
map.set(target, newDate);
return newDate;
}
if (target instanceof RegExp) {
const newReg = new RegExp(target.source, target.flags);
map.set(target, newReg);
return newReg;
}
// 4. 处理数组/对象
const cloneTarget = Array.isArray(target) ? [] : {};
map.set(target, cloneTarget); // 缓存当前对象,解决循环引用
// 5. 递归拷贝属性
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], map);
});
return cloneTarget;
}
// 测试示例
const obj = { a: 1, b: [1, 2], c: { d: 3 } };
obj.self = obj; // 循环引用
const newObj = deepClone(obj);
console.log(newObj); // 完整拷贝,无循环引用问题
核心特性:支持基本类型、数组、对象、日期、正则,解决循环引用问题,兼容 Symbol 键名。
function uniqueArr(arr) {
// 利用Set的唯一性,支持基本类型去重
return Array.isArray(arr) ? [...new Set(arr)] : [];
}
function uniqueArr(arr) {
if (!Array.isArray(arr)) return [];
const result = [];
const map = new Map();
arr.forEach(item => {
// 处理引用类型:JSON.stringify转换(或自定义唯一标识)
const key = typeof item === 'object' ? JSON.stringify(item) : item;
if (!map.has(key)) {
map.set(key, true);
result.push(item);
}
});
return result;
}
// 测试
console.log(uniqueArr([1, 2, 2, 3, '3', { a: 1 }, { a: 1 }]));
// 输出:[1, 2, 3, '3', { a: 1 }]
适用场景:方案 1 适合基本类型数组;方案 2 适合包含引用类型的数组。
后台系统权限控制需覆盖 路由、菜单、按钮、接口四层,核心方案如下:
采用“RBAC(基于角色的访问控制)”模型:用户 → 角色 → 权限(菜单/按钮/接口)。
permissions: ['user:list', 'user:add']),存储在
localStorage/
Vuex/
Redux 中。router.beforeEach)校验登录状态,未登录跳转登录页。router.addRoute 动态添加可访问路由。// Vue示例:动态注册路由
const asyncRoutes = [/* 所有需要权限的路由 */];
// 过滤有权限的路由
const accessibleRoutes = asyncRoutes.filter(route => {
return store.getters.permissions.includes(route.meta.permission);
});
// 动态添加
accessibleRoutes.forEach(route => router.addRoute(route));
<!-- Vue示例 -->
<template>
<el-menu>
<el-menu-item
v-for="menu in menuList"
:key="menu.id"
v-if="hasPermission(menu.permission)"
>
{{ menu.name }}
</el-menu-item>
</el-menu>
</template>
<script>
export default {
methods: {
hasPermission(permission) {
return this.$store.getters.permissions.includes(permission);
}
}
}
</script>
// Vue自定义指令示例
Vue.directive('permission', {
inserted(el, binding) {
const { value } = binding;
const permissions = store.getters.permissions;
if (value && !permissions.includes(value)) {
el.parentNode.removeChild(el); // 移除按钮
}
}
});
// 使用:<button v-permission="'user:delete'">删除</button>
心跳检测用于维持 IM 长连接、检测连接状态,核心是“定时双向通信”,实现方案如下:
客户端定时发送“心跳包” → 服务端接收后立即回复“心跳响应” → 客户端超时未收到响应则判定连接断开,触发重连。
class IMHeartbeat {
constructor(wsUrl) {
this.ws = null;
this.wsUrl = wsUrl;
this.heartbeatInterval = 30000; // 30秒发一次心跳
this.heartbeatTimer = null; // 心跳定时器
this.reconnectTimer = null; // 重连定时器
this.reconnectMaxTimes = 5; // 最大重连次数
this.reconnectCount = 0; // 已重连次数
}
// 初始化连接
init() {
this.ws = new WebSocket(this.wsUrl);
this.bindEvent();
}
// 绑定事件
bindEvent() {
// 连接成功
this.ws.onopen = () => {
console.log('连接成功');
this.startHeartbeat(); // 启动心跳
this.reconnectCount = 0; // 重置重连次数
};
// 接收消息
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data);
// 收到心跳响应,重置定时器
if (data.type === 'heartbeat') {
this.resetHeartbeat();
}
};
// 连接关闭
this.ws.onclose = () => {
console.log('连接关闭,开始重连');
this.stopHeartbeat();
this.reconnect();
};
// 连接错误
this.ws.onerror = (err) => {
console.error('连接错误', err);
this.ws.close();
};
}
// 启动心跳
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
// 发送心跳包
this.ws.send(JSON.stringify({ type: 'heartbeat', time: Date.now() }));
}
}, this.heartbeatInterval);
}
// 重置心跳定时器(收到响应后)
resetHeartbeat() {
clearInterval(this.heartbeatTimer);
this.startHeartbeat();
}
// 停止心跳
stopHeartbeat() {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
// 重连逻辑
reconnect() {
if (this.reconnectCount >= this.reconnectMaxTimes) {
console.log('重连次数耗尽,停止重连');
return;
}
this.reconnectTimer = setTimeout(() => {
this.reconnectCount++;
console.log(`第${this.reconnectCount}次重连`);
this.init();
}, 5000); // 5秒重连一次
}
}
// 使用
const im = new IMHeartbeat('ws://localhost:8080/im');
im.init();
{ type: 'heartbeat', code: 200 }。
defer 和
async 是
<script> 标签的属性,用于异步加载 JS 文件,核心区别如下:
| 维度 | defer | async |
| -------------------- | ------------------------------------------------------ | ---------------------------------------- |
|
执行时机 | 文档解析完成后、
DOMContentLoaded 事件触发前执行 | 脚本下载完成后立即执行(暂停文档解析) |
| 执行顺序 | 按脚本在页面中的顺序执行 | 谁先下载完谁先执行(无序) |
| 适用场景 | 依赖 DOM、需按顺序执行的脚本(如 jQuery 插件) | 独立无依赖的脚本(如统计、广告脚本) |
| 加载特性 | 异步加载,不阻塞文档解析 | 异步加载,不阻塞文档解析 |
// 正常脚本(阻塞) 解析HTML → 下载脚本 → 执行脚本 → 继续解析HTML // defer 解析HTML → 异步下载脚本 → 解析完成 → 按顺序执行脚本 → DOMContentLoaded // async 解析HTML → 异步下载脚本 → 下载完成 → 暂停解析 → 执行脚本 → 继续解析HTML
<!-- defer:按顺序执行,DOM解析完执行 --> <script src="a.js" defer></script> <script src="b.js" defer></script> <!-- 一定在a.js之后执行 --> <!-- async:无序执行,下载完立即执行 --> <script src="a.js" async></script> <script src="b.js" async></script> <!-- 可能先于a.js执行 -->
Tree Shaking(摇树优化)是 Webpack/Rollup 等打包工具的优化技术,核心是 剔除代码中未被使用的死代码(dead code),减少打包体积。
import/export),CommonJS(
require)不支持(动态加载,无法静态分析)。mode: 'production'(默认开启 Tree Shaking)。package.json 中声明
sideEffects(标记无副作用的文件,可安全删除)。// package.json
{
"sideEffects": [
"*.css", // CSS文件有副作用(样式生效),不删除
"*.less"
]
}
// utils.js(源码)
export const add = (a, b) => a + b;
export const minus = (a, b) => a - b;
// index.js(业务代码)
import { add } from './utils.js'; // 仅引用add
console.log(add(1, 2));
// 打包后(Tree Shaking后)
// 仅保留add函数,minus被剔除
const add = (a, b) => a + b;
console.log(add(1, 2));
sideEffects,否则会被误删。JS 事件循环(Event Loop)是 JS 处理异步任务的核心机制,用于解决单线程下的异步执行问题,核心规则如下:
| 任务类型 | 包含内容 | 执行优先级 |
| --------------------- | --------------------------------------------------------------------------- | ---------------------------- |
| 宏任务(Macrotask) | script 整体代码、setTimeout、setInterval、I/O、UI 渲染、WebSocket | 低(微任务执行完才执行) |
| 微任务(Microtask) | Promise.then/catch/finally、async/await、queueMicrotask、MutationObserver | 高(宏任务执行完立即执行) |
1. 执行同步代码(属于宏任务)→ 执行栈清空 2. 执行所有微任务 → 微任务队列清空 3. 执行一次UI渲染(可选) 4. 从宏任务队列取第一个任务执行 → 回到步骤1
console.log('同步1'); // 同步代码,立即执行
setTimeout(() => { // 宏任务
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => { // 微任务
console.log('Promise.then');
});
console.log('同步2'); // 同步代码,立即执行
// 执行结果:同步1 → 同步2 → Promise.then → setTimeout
interface(接口)和
type(类型别名)是 TS 中定义类型的核心方式,核心区别及场景如下:
| 特性 | interface | type |
| -------------------- | --------------------------------- | ---------------------------------------------------- |
| 定义方式 | 只能定义对象/类的形状 | 可定义任意类型(基本类型、联合类型、交叉类型等) |
|
扩展方式 | 用
extends 扩展,支持多继承 | 用交叉类型
& 扩展 |
| 合并特性 | 支持重复声明自动合并 | 不支持重复声明(重复定义会报错) |
|
实现方式 | 类可通过
implements 实现 | 类只能实现对象类型的 type(基本类型/联合类型不行) |
| 索引签名 | 支持更灵活的索引签名 | 支持但语法稍繁琐 |
// 定义对象类型
interface User {
name: string;
age: number;
}
// 扩展(继承)
interface Admin extends User {
role: string;
}
// 重复声明自动合并
interface User {
gender: string;
}
// 最终User:{ name: string; age: number; gender: string }
// 类实现
class Person implements User {
name = '张三';
age = 20;
gender = '男';
}
// 定义基本类型
type Str = string;
// 定义联合类型
type Status = 'success' | 'error' | 'loading';
// 定义对象类型
type User = {
name: string;
age: number;
};
// 扩展(交叉类型)
type Admin = User & { role: string };
// 重复声明报错
// type User = { gender: string }; // 错误:标识符“User”重复
宏任务和微任务是 JS 异步任务的两大分类,是事件循环的核心,具体定义如下:
Promise 是 JS 解决异步回调地狱的核心方案,用于表示异步操作的最终完成(或失败)及其结果值。
then()/
catch()/
finally() 实现异步操作串行执行,避免回调嵌套。catch() 统一捕获。// 创建Promise
const promise = new Promise((resolve, reject) => {
// 异步操作(如请求)
setTimeout(() => {
const res = { code: 200, data: '成功' };
if (res.code === 200) {
resolve(res.data); // 成功,触发then
} else {
reject('失败'); // 失败,触发catch
}
}, 1000);
});
// 调用
promise
.then(res => {
console.log('成功:', res);
return res + '处理后'; // 传递给下一个then
})
.then(res => console.log('链式调用:', res))
.catch(err => console.error('错误:', err))
.finally(() => console.log('无论成功失败都执行'));
| 方法 | 作用 | 场景 |
| ---------------------------- | --------------------------------------------------------------- | -------------------------------------------- |
|
Promise.resolve() | 返回一个已成功的 Promise | 快速创建成功的 Promise |
|
Promise.reject() | 返回一个已失败的 Promise | 快速创建失败的 Promise |
|
Promise.all() | 接收 Promise 数组,全部成功才返回,一个失败则立即失败 | 并行执行多个异步任务(如同时请求多个接口) |
|
Promise.race() | 接收 Promise 数组,第一个完成的 Promise 决定结果 | 超时控制(如请求 5 秒未响应则超时) |
|
Promise.allSettled() | 接收 Promise 数组,等待所有任务完成(无论成败),返回所有结果 | 需知道所有异步任务的执行结果(如批量上传) |
|
Promise.any() | 接收 Promise 数组,第一个成功的 Promise 决定结果 | 多个备选接口,取第一个成功的 |
元素居中是前端高频需求,分 水平居中、 垂直居中、 水平垂直居中,以下是常用方案:
| 元素类型 | 实现方案 | 代码示例 |
| ----------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
| 行内元素(文字/span) | 父元素设置
text-align: center |
.parent { text-align: center; } |
| 块级元素(固定宽度) | 元素设置
margin: 0 auto |
.child { width: 200px; margin: 0 auto; } |
| 块级元素(不定宽度) | Flex 布局:父元素
display: flex; justify-content: center |
.parent { display: flex; justify-content: center; } |
| 实现方案 | 代码示例 | 适用场景 |
| ------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------- |
| Flex 布局(推荐) |
.parent { display: flex; align-items: center; } | 任意元素,兼容性好 |
| 定位 + transform |
.parent { position: relative; } .child { position: absolute; top: 50%; transform: translateY(-50%); } | 不定高度元素 |
| 行高(单行文字) |
.parent { line-height: 50px; height: 50px; } | 单行文字垂直居中 |
.parent {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
width: 100vw;
height: 100vh;
}
.child {
width: 200px;
height: 200px;
background: #eee;
}
.parent {
position: relative;
width: 100vw;
height: 100vh;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 回移自身50% */
width: 200px;
height: 200px;
}
.parent {
display: grid;
place-items: center; /* 水平+垂直居中 */
width: 100vw;
height: 100vh;
}
flex: none,右侧
flex: 1 占满剩余空间。useState(状态)、
useEffect(副作用)、
useRef(引用)、
useContext(上下文)是高频考点。来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049190/viewspace-3106893/ ,如需转载,请注明出处,否则将追究法律责任。