小米前端面试
这是一份针对您图片中列出的前端面试题目的详细解答。我将按照顺序,对每个问题提供清晰、深入的答案,并附上代码示例和对比总结,以帮助您全面准备。

1. 自我介绍
回答思路(模板):
面试官您好,我叫[您的姓名],是[您的学校] [您的专业]的应届生。我主要的技术方向是前端开发,熟练掌握 JavaScript(ES6+)、HTML5、CSS3,并有丰富的 Vue.js / React 实战经验。
在项目方面,我曾在[项目名称]中负责[具体职责,如“基于 Vue CLI 搭建前端架构,实现组件化开发”],并解决了[具体技术难点,如“首屏加载性能优化”]。我对前端工程化、性能优化和用户体验有浓厚的兴趣。
我非常期待能加入贵公司,与团队一起应对挑战。谢谢!
核心要点: 结构清晰、突出技术栈、结合项目经验、展现热情。
2. #06 有效的括号 (20)
这是一个经典的栈应用问题。给定一个只包括 '(', ')', '{', '}', '[', ']' 的字符串,判断字符串是否有效。
解题思路:
- 使用栈数据结构。
- 遍历字符串,遇到左括号就将其压入栈中。
- 遇到右括号时:
- 检查栈是否为空,若为空则直接返回
false。 - 将栈顶元素弹出,检查是否与当前右括号匹配。如果不匹配,返回
false。
- 检查栈是否为空,若为空则直接返回
- 遍历结束后,检查栈是否为空。如果栈为空,说明所有括号都正确匹配,返回
true;否则返回false。
JavaScript 代码实现:
function isValid(s) {const stack = [];const map = {'(': ')','[': ']','{': '}'};for (let char of s) {if (map[char]) {// 如果是左括号,压入栈stack.push(char);} else {// 如果是右括号if (stack.length === 0) {return false; // 栈为空,不匹配}const top = stack.pop(); // 弹出栈顶的左括号if (map[top] !== char) {return false; // 弹出的左括号不匹配当前的右括号}}}// 最后栈必须为空才有效return stack.length === 0;
}// 测试
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true
3. 两栏布局/三栏布局
两栏布局(左侧固定,右侧自适应)
方法1:Flexbox(最推荐)
<style>
.container {display: flex;height: 100vh;
}
.left {width: 200px; /* 固定宽度 */background: #ccc;
}
.right {flex: 1; /* 占据剩余所有空间 */background: #f0f0f0;
}
</style>
<div class="container"><div class="left">左侧固定</div><div class="right">右侧自适应</div>
</div>
方法2:Grid布局
.container {display: grid;grid-template-columns: 200px 1fr; /* 第一列200px,第二列自适应 */height: 100vh;
}
三栏布局(左右固定,中间自适应)
圣杯布局/双飞翼布局(经典,但现代开发中已较少使用)
方法:Flexbox(最简洁)
<style>
.container {display: flex;
}
.left, .right {width: 200px;background: #ccc;
}
.middle {flex: 1;background: #f0f0f0;
}
</style>
<div class="container"><div class="left">左侧固定</div><div class="middle">中间自适应</div><div class="right">右侧固定</div>
</div>
4. Flex 布局
Flexible Box 模型,是一种一维的布局模型,用于在一条直线上排列元素。
核心概念:
- 容器:设置
display: flex;的元素。 - 项目:容器的直接子元素。
- 主轴和交叉轴。
常用容器属性:
.container {display: flex;flex-direction: row; /* 主轴方向:row, row-reverse, column, column-reverse */justify-content: flex-start; /* 主轴对齐:center, space-between, space-around */align-items: stretch; /* 交叉轴单行对齐:flex-start, center, flex-end */flex-wrap: nowrap; /* 是否换行:wrap */
}
常用项目属性:
.item {flex-grow: 0; /* 放大比例 */flex-shrink: 1; /* 缩小比例 */flex-basis: auto; /* 项目初始大小 */flex: 1; /* 是 grow, shrink, basis 的简写 */align-self: auto; /* 单个项目在交叉轴上的对齐方式 */
}
5. Grid 布局
CSS Grid Layout 是一个二维的网格布局系统,将网页划分成网格,可以任意组合不同的网格。
核心概念:
- 容器:设置
display: grid;的元素。 - 项目:容器的直接子元素。
- 行和列。
常用容器属性:
.container {display: grid;grid-template-columns: 100px 1fr 2fr; /* 定义三列:第一列100px,第二列1份,第三列2份 */grid-template-rows: 50px 1fr; /* 定义两行 */gap: 10px; /* 网格间隙 */justify-items: stretch; /* 单元格内水平对齐 */align-items: stretch; /* 单元格内垂直对齐 */
}
常用项目属性:
.item {grid-column-start: 1;grid-column-end: 3; /* 项目占据从第1条网格线到第3条网格线 */grid-row: 1 / 3; /* 是 grid-row-start 和 grid-row-end 的简写 */
}
6. React Hooks
Hooks 是 React 16.8 引入的特性,让你在函数组件中使用 state 和其他 React 特性。
常用 Hooks:
- useState:在函数组件中添加状态。
import { useState } from 'react'; function Example() {const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>); } - useEffect:处理副作用(数据获取、订阅、手动修改DOM等)。
useEffect(() => {// 相当于 componentDidMount 和 componentDidUpdatedocument.title = `You clicked ${count} times`;return () => {// 清理函数,相当于 componentWillUnmount}; }, [count]); // 依赖数组,只有 count 变化时才执行 - useContext:读取 Context。
- useReducer:作为 useState 的替代方案,管理复杂 state 逻辑。
7. 实现的高阶组件 (HOC)
高阶组件是一个函数,它接受一个组件并返回一个新的组件。用于复用组件逻辑。
示例:一个用于跟踪用户点击的高阶组件
function withClickTracker(WrappedComponent) {return function TrackedComponent(props) {const handleClick = (...args) => {console.log('Component was clicked!', props, args);// 这里可以发送数据到数据分析平台if (props.onClick) {props.onClick(...args);}};return <WrappedComponent {...props} onClick={handleClick} />;};
}// 使用
const MyButton = ({ onClick, children }) => (<button onClick={onClick}>{children}</button>
);
const TrackedButton = withClickTracker(MyButton);
8. 受控/非受控组件
受控组件
表单项的值由 React 的 state 控制,通过 onChange 事件更新 state。
function ControlledForm() {const [value, setValue] = useState('');const handleSubmit = (e) => {e.preventDefault();console.log(value);};return (<form onSubmit={handleSubmit}><input type="text" value={value} onChange={(e) => setValue(e.target.value)} /><button type="submit">Submit</button></form>);
}
优点:数据流清晰,易于验证和控制。
非受控组件
表单项的值由 DOM 自身管理,使用 ref 来获取值。
function UncontrolledForm() {const inputRef = useRef(null);const handleSubmit = (e) => {e.preventDefault();console.log(inputRef.current.value);};return (<form onSubmit={handleSubmit}><input type="text" ref={inputRef} /><button type="submit">Submit</button></form>);
}
优点:代码简单,更接近传统HTML。
9. Vue vs React
| 特性 | Vue | React |
|---|---|---|
| 设计哲学 | 渐进式框架,官方提供全套解决方案 | 库,专注于视图层,依赖社区生态 |
| 模板语法 | 基于 HTML 的模板,指令(如 v-if, v-for) | JSX(JavaScript 的语法扩展) |
| 状态管理 | data 属性,响应式系统 | useState/useReducer Hooks,状态不可变 |
| 组件通信 | props 下行,$emit 上行 | props 下行,回调函数上行 |
| 学习曲线 | 相对平缓,API 设计更直观 | 相对陡峭,需要理解函数式编程概念 |
| 生态系统 | Vuex/Pinia(状态管理),Vue Router(路由) | Redux/MobX(状态管理),React Router(路由) |
选择建议:Vue 更适合追求开发效率和规范化的团队,React 更适合追求灵活性和函数式编程的团队。
10. Computed vs Watch (Vue)
Computed(计算属性)
- 用途:基于依赖的响应式数据进行计算,并返回一个新的值。适用于需要根据现有数据衍生出新数据的场景。
- 特点:缓存结果。只有当其依赖的响应式数据发生变化时,才会重新计算。
- 示例:
computed: {fullName() {return this.firstName + ' ' + this.lastName;} }
Watch(侦听器)
- 用途:观察一个特定的响应式数据源,并在其变化时执行副作用(如异步操作、复杂逻辑)。适用于数据变化时需要执行异步请求或开销较大的操作。
- 特点:无缓存,更通用,可以执行异步操作。
- 示例:
watch: {searchQuery(newVal, oldVal) {if (newVal !== oldVal) {this.debouncedSearch();}} }
简单总结:想要一个衍生数据用 computed;想要在数据变化时做点什么用 watch。
11. Vue Keep-alive 组件
<keep-alive> 是 Vue 的内置组件,用于缓存不活动的组件实例,而不是销毁它们。
作用:在组件切换时(如 Tab 切换、路由切换),保留组件的状态(如数据、滚动位置),避免重复渲染,提升性能。
基本用法:
<template><keep-alive><component :is="currentComponent"></component></keep-alive>
</template>
常用属性:
include:只有名称匹配的组件会被缓存。exclude:任何名称匹配的组件都不会被缓存。max:最多可以缓存多少组件实例。
生命周期钩子:被 <keep-alive> 包裹的组件会多出两个生命周期钩子:
activated():组件被激活时调用。deactivated():组件被停用时调用。
12. Composition API vs Options API / Vue2 vs Vue3
Options API (Vue2 的主要写法)
- 通过不同的选项(
data,methods,computed,watch,生命周期)来组织代码。 - 优点:结构清晰,易于初学者理解,逻辑按类型分组。
- 缺点:逻辑关注点分散。一个功能的代码可能分散在
data,methods,mounted等多个地方。
Composition API (Vue3 的主要特性,Vue2.7 也支持)
- 使用
setup()函数和一系列函数(如ref,reactive,onMounted)来组织代码。 - 优点:逻辑关注点集中。可以将一个功能的所有相关代码(状态、方法、生命周期)放在一起,更好的逻辑复用和代码组织(通过自定义函数)。
- 示例:
import { ref, onMounted } from 'vue'; export default {setup() {const count = ref(0);const increment = () => { count.value++; };onMounted(() => { console.log('component mounted'); });return { count, increment };} };
Vue2 vs Vue3 主要区别:
- 性能:Vue3 使用 Proxy 重写响应式系统,性能更好,支持更大的组件树。
- Tree-shaking:Vue3 更好的支持,更小的打包体积。
- Composition API:Vue3 的默认推荐写法。
- Fragment、Teleport、Suspense:Vue3 新增的内置组件。
13. Vue 如何做懒加载?懒加载与滚动加载
懒加载 (Lazy Loading)
通常指路由懒加载或组件懒加载,目的是减少初始包体积,加快首屏加载速度。
路由懒加载(使用 Vue Router):
// 静态导入
// import Home from '@/views/Home.vue'// 懒加载(动态导入)
const Home = () => import('@/views/Home.vue');
const About = () => import('@/views/About.vue');const routes = [{ path: '/', component: Home },{ path: '/about', component: About }
];
组件懒加载:
<template><div><Suspense><template #default><LazyComponent /></template><template #fallback><div>Loading...</div></template></Suspense></div>
</template>
<script>
const LazyComponent = () => import('./LazyComponent.vue');
export default {components: { LazyComponent }
};
</script>
滚动加载 (Infinite Scroll)
指当用户滚动到页面底部或特定位置时,自动加载更多数据。常用于新闻Feed、商品列表等。
实现思路:
- 监听滚动事件或使用 Intersection Observer API。
- 判断是否滚动到了“加载更多”的触发点(如距离底部100px)。
- 触发加载数据的函数,将新数据追加到现有列表。
懒加载 vs 滚动加载:
- 懒加载:关注的是代码资源的按需加载。
- 滚动加载:关注的是数据内容的按需加载。
14. Vue 生命周期中错误处理的钩子函数
Vue 有专门的 错误捕获钩子 来捕获和处理组件树中的错误。
errorCaptured(生命周期钩子)
- 在捕获一个来自后代组件的错误时被调用。
- 返回
false可以阻止错误继续向上传播。 - 示例:
errorCaptured(err, vm, info) {console.error('Error captured:', err, info);// 可以在此处上报错误到监控平台// return false; // 阻止错误继续向上传播 }
Vue 3 的 onErrorCaptured (Composition API)
import { onErrorCaptured } from 'vue';
setup() {onErrorCaptured((err, vm, info) => {console.error('Error captured:', err, info);return false;});
}
全局错误处理:app.config.errorHandler (Vue 3)
const app = createApp(App);
app.config.errorHandler = (err, vm, info) => {// 全局捕获任何未被处理的错误console.error('Global error handler:', err, info);// 发送错误日志到服务器
};
15. Webpack vs Vite
| 特性 | Webpack | Vite |
|---|---|---|
| 构建理念 | 打包器 (Bundler)。先打包所有模块,再启动开发服务器。 | 基于 ESM 的 No-Bundle。启动时按需编译和提供源码。 |
| 启动速度 | 慢。项目越大,启动越慢。 | 极快。无论项目大小,都是秒级启动。 |
| HMR(热更新) | 速度较慢。需要重新构建部分 bundle。 | 极快。仅精确更新修改的模块。 |
| 生产构建 | 成熟稳定,优化手段丰富。 | 使用 Rollup 进行构建,同样高效。 |
| 配置复杂度 | 配置相对复杂,概念多(Loaders, Plugins)。 | 配置更简单,开箱即用。 |
| 适用场景 | 大型、复杂项目,需要高度自定义。 | 现代浏览器项目,追求极速开发体验。 |
核心区别:Vite 利用浏览器原生 ES 模块的支持,在开发阶段避免了不必要的打包,从而获得了极致的性能。
16. 反问
这是你展示对公司和职位兴趣的好机会。可以问一些有深度的问题,例如:
- 关于团队与技术:
- “我们团队目前主要的技术栈和未来的技术规划是怎样的?”
- “目前团队遇到的最大技术挑战是什么?”
- 关于发展与成长:
- “公司对新入职的应届生有哪些培养机制或 mentorship 计划?”
- “这个岗位的晋升路径是怎样的?”
- 关于工作内容:
- “如果我入职,前期会主要负责哪些业务或项目?”
- 关于文化:
- “团队的工作氛围和沟通协作方式是怎样的?”
避免问:薪资、加班等过于直接或负面的问题(这些通常由 HR 后续沟通)。
希望这份详细的解答能帮助您顺利通过面试!
好的,这是针对“小米前端秋招一面”面试题目的逐一详细解答。我将结合核心概念、代码示例和面试回答要点,为您提供一份完整的参考答案。
1. 自我介绍
回答思路:结构清晰,突出技术栈、项目经验和与岗位的匹配度。
示例模板:
面试官您好,我叫[你的名字],是[你的学校] [你的专业] 的应届生。我主要的技术方向是前端开发,熟练掌握 JavaScript、ES6+、Vue/React 等核心技术栈。
我尤其对 Web 图形学和数据可视化有浓厚的兴趣,并深入学习和实践了 Three.js 和相关 WebGL 知识。例如,在[某个项目]中,我使用 Three.js 实现了[某个具体功能,如“3D 楼宇的自动化生成和交互”],并通过[某项优化,如“几何体合并和 LOD”]来提升性能。
我非常期待能加入小米,在富有挑战的业务中贡献我的技术能力。谢谢!
2. JS 中箭头函数和普通函数的区别
这是 ES6 的基础核心考点。
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
this 指向 | 动态绑定,取决于调用方式(谁调用它) | 词法绑定,继承自定义它时所在的作用域的 this |
arguments 对象 | 有,包含所有实参 | 无,需用剩余参数 ...args |
| 能否作为构造函数 | 能,用 new 关键字 | 不能,会抛出错误 |
原型 prototype | 有 | 无 |
| 简写语法 | function() {} | () => {} |
代码示例:
// this 指向的区别
const obj = {name: 'obj',regularFunc: function() { console.log(this.name); },arrowFunc: () => { console.log(this.name); }
};
obj.regularFunc(); // 'obj' (this 指向 obj)
obj.arrowFunc(); // undefined (this 指向全局对象或 undefined(严格模式))// 构造函数
function Person(name) { this.name = name; }
const p = new Person('Alice'); // 正确const Animal = (name) => { this.name = name; };
// const a = new Animal('Cat'); // TypeError: Animal is not a constructor// 参数对象
function regular() { console.log(arguments); }
regular(1, 2); // Arguments(2) [1, 2]const arrow = (...args) => { console.log(args); }; // 使用剩余参数
arrow(1, 2); // [1, 2]
面试回答要点:重点强调 this 绑定的不同,并说明箭头函数因其简洁性和固定的 this,在回调函数(如事件处理、定时器)中特别有用。
3. ES6 有哪些新特性,说你认为比较重要的
选择几个最常用、影响最大的特性深入说明。
let和const:块级作用域,解决var的变量提升和污染全局问题。- 箭头函数:如上所述,简化写法,固定
this。 - 模板字符串:
`Hello, ${name}!`,支持多行字符串和变量嵌入。 - 解构赋值:
const { a, b } = obj;或const [first, second] = arr;,简化数据提取。 - 模块化(
import/export):取代require,成为前端模块标准,支持静态分析,利于 Tree Shaking。 - Promise:解决回调地狱,为异步编程提供更优雅的解决方案。
- Class:语法糖,让基于原型的继承写法更清晰、更像传统面向对象语言。
面试回答要点:不要只罗列。挑 3-4 个你最熟悉的,说清楚它解决了什么问题,并给出简单例子。例如:“我认为 Promise 和 模块化 非常重要。Promise 让我们告别了回调地狱,让异步代码的流程控制变得清晰可控。而 import/export 模块化是现代前端工程的基石,它支持静态优化,使得构建工具可以剔除无用代码(Tree Shaking),极大优化了打包体积。”
4. Promise 和 async/await 有什么关系
关系:async/await 是建立在 Promise 之上的语法糖,其目的是为了让异步代码的写法更像同步代码,进一步提高可读性和可维护性。
async函数总是返回一个 Promise 对象。await关键字后面通常是一个 Promise 对象,它会“等待”该 Promise 完成(resolve 或 reject),并返回其结果。
代码对比:
// 使用 Promise
function fetchData() {return fetch('/api/data').then(response => response.json()).then(data => console.log(data)).catch(error => console.error(error));
}// 使用 async/await (功能完全相同,但更简洁)
async function fetchData() {try {const response = await fetch('/api/data');const data = await response.json();console.log(data);} catch (error) {console.error(error);}
}
面试回答要点:async/await 并没有取代 Promise,它只是让使用 Promise 的代码更易写、易读。底层依然是 Promise 在工作。
5. 异步编程介绍一下
异步编程是指任务的执行不是连续的,不必等待上一个任务结束再执行下一个。这是为了避免阻塞主线程,保证 UI 的流畅响应。
演进历程:
- 回调函数(Callback):最早的方式,容易导致“回调地狱”(Callback Hell),代码难以维护。
- Promise:ES6 引入,通过链式调用(
.then().catch())扁平化了异步流程。 - Generator:ES6 引入,可以暂停执行的函数,配合执行器可用于异步控制,但较复杂。
- async/await:ES2017 引入,终极解决方案,以同步的方式写异步代码,可读性最强。
面试回答要点:讲清楚为什么需要异步(避免阻塞),以及从回调到 async/await 的演进过程,突出后者在可读性和可维护性上的优势。
6. 宏任务和微任务的区别
这是理解 Event Loop(事件循环)的核心。
| 类型 | 代表任务 | 触发时机 |
|---|---|---|
| 宏任务 | setTimeout, setInterval, setImmediate (Node), I/O, UI 渲染 | 每次 Event Loop 循环中执行一个 |
| 微任务 | Promise.then/catch/finally, process.nextTick (Node), MutationObserver | 在每个宏任务执行完毕后,立即清空整个微任务队列 |
执行顺序规则:一个宏任务 → 所有微任务 → UI渲染 → 下一个宏任务。
代码示例:
console.log('script start'); // 同步任务setTimeout(() => {console.log('setTimeout'); // 宏任务
}, 0);Promise.resolve().then(() => {console.log('promise1'); // 微任务}).then(() => {console.log('promise2'); // 微任务});console.log('script end'); // 同步任务// 输出顺序:
// 'script start'
// 'script end'
// 'promise1'
// 'promise2'
// 'setTimeout'
面试回答要点:一定要说出 “微任务的优先级高于宏任务” 以及 “在执行下一个宏任务前,必须清空当前的微任务队列”。
7. ES6 代码如何保证在低版本浏览器运行?(Babel)
核心工具:Babel。
原理:Babel 是一个 JavaScript 编译器(或转译器)。
- 解析:将 ES6+ 代码解析成抽象语法树(AST)。
- 转换:遍历 AST,将其中的新语法(如箭头函数、
const)和新的 API(如Promise,Array.from)转换成等价的 ES5 代码。 - 生成:将转换后的 AST 再生成回 ES5 代码。
补充:Polyfill:Babel 主要转译语法,对于新的全局对象(如 Promise)或实例方法(如 Array.prototype.includes),需要引入 core-js 等 Polyfill 库来模拟实现。
面试回答要点:Babel 负责语法转换,Polyfill 负责 API 模拟,二者结合才能完全兼容低版本浏览器。通常会与 Webpack 等构建工具集成。
8. 如何防止快速重复点击?(防抖与节流)
这是处理高频事件的经典优化手段。
- 防抖:在事件被触发 n 秒后执行回调,如果在这 n 秒内又被触发,则重新计时。(最终只执行一次)
- 场景:搜索框输入联想、窗口 resize 结束后的调整。
- 节流:规定在一个单位时间内,只能触发一次函数回调。如果这个单位时间内触发多次,只有一次生效。(按固定频率执行)
- 场景:滚动加载、按钮点击(防止重复提交)。
代码实现:
// 防抖
function debounce(func, wait) {let timeout;return function (...args) {clearTimeout(timeout); // 清除之前的定时器timeout = setTimeout(() => func.apply(this, args), wait);};
}// 节流 (时间戳版)
function throttle(func, wait) {let lastTime = 0;return function (...args) {const now = Date.now();if (now - lastTime >= wait) {func.apply(this, args);lastTime = now;}};
}// 使用
const debouncedClick = debounce(() => console.log('Clicked!'), 500);
const throttledScroll = throttle(() => console.log('Scrolling!'), 200);
面试回答要点:清晰区分两者概念和适用场景。防抖是“回城”,节流是“技能冷却”。
9. JS 代码从编写到浏览器运行发生了什么?(V8 引擎)
这是一个宏观流程题,考察对 JS 运行机制的理解。
- 下载与解析:浏览器下载
.js文件。 - 解析:V8 引擎的解析器将 JS 代码转换成抽象语法树(AST)。
- 解释执行:
- 解释器(Ignition) 将 AST 转换成字节码并快速执行。
- 优化编译:
- 编译器(TurboFan) 在代码运行时监控热点函数(被多次执行),将其字节码编译成高度优化的机器码,极大提升后续执行速度。这就是 JIT(即时编译)。
- 垃圾回收:Orinoco 垃圾回收器自动管理内存,回收不再使用的对象。
面试回答要点:不要只提 V8,要说出 Ignition 解释器和 TurboFan 编译器的协同工作(JIT),这是 V8 性能的关键。
10. 一个函数从编写到调用执行会发生什么?(执行上下文栈)
过程:
- 定义:JS 引擎在编译阶段识别函数声明,并将其存入内存。
- 调用:当函数被调用时,JS 引擎会创建一个该函数的执行上下文。
- 执行上下文内容:包含三个核心部分:
- 变量环境:存放
var声明的变量和函数声明。 - 词法环境:存放
let/const声明的变量。 this绑定。
- 变量环境:存放
- 压栈:新创建的执行上下文会被压入调用栈顶部。
- 执行:在上下文中逐行执行函数体内的代码。
- 出栈:函数执行完毕,其执行上下文从调用栈弹出,控制权交还给栈中的上一个上下文。
为什么是栈结构?
因为函数的调用关系是后进先出的。例如,A 函数调用 B 函数,B 函数又调用 C 函数。C 执行完后必须先回到 B,B 执行完再回到 A。栈结构的特性完美匹配了这种调用顺序。
面试回答要点:重点说清执行上下文的创建和压栈、出栈过程,并解释栈结构的后进先出特性与函数调用顺序的自然匹配。
11. 打包工具打包之后一般会有哪些产物?
以 Webpack 为例:
- JS Bundle:一个或多个被压缩、混淆的
.js文件,是应用的核心代码。 - CSS Bundle:通常被提取为单独的
.css文件(通过mini-css-extract-plugin)。 - HTML 文件:通常是由
html-webpack-plugin自动生成的index.html,并已自动注入 JS 和 CSS 资源链接。 - 静态资源:如图片、字体,会被处理(压缩、重命名)后输出到特定目录(如
assets/)。 - Source Map:用于生产环境调试的
.map文件,将压缩后的代码映射回源代码。 - 模块联邦等运行时文件。
面试回答要点:说出主要的 JS、CSS、HTML 文件,并可以提一下静态资源和 Source Map。
12. 一次性发送多个请求,统一处理结果有哪些方式?(除了 Promise.all)
这是在考察你对 Promise 其他静态方法的了解。
Promise.allSettled:等待所有 Promise 完成(无论成功或失败),返回一个数组,描述每个 Promise 的结果。适用于不关心单个请求成败,但需要知道所有结果的场景(如批量数据上报)。const promises = [fetch(url1), fetch(url2)]; Promise.allSettled(promises).then(results => {results.forEach(result => {if (result.status === 'fulfilled') {console.log('成功:', result.value);} else {console.log('失败:', result.reason);}}); });Promise.race:返回第一个完成(成功或失败)的 Promise 的结果。适用于竞速或超时控制。const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject('超时'), 5000)); Promise.race([fetch(url), timeoutPromise]).then(data => console.log(data)).catch(err => console.error(err)); // 如果 fetch 超过5秒,这里会捕获‘超时’Promise.any:返回第一个成功的 Promise 的结果。如果全部失败,则返回一个聚合错误。适用于多源备份,只要一个成功即可。
面试回答要点:明确说出 allSettled、race、any 这三者与 all 的区别和各自的应用场景。
13. 项目中如何捕获错误,生产环境怎么做?
开发环境:
- 代码层面:使用
try...catch捕获同步错误,使用.catch()捕获 Promise 错误。 - 工具层面:利用浏览器的开发者工具(Console, Debugger)直接查看和定位错误。
生产环境:
- 全局错误监控:
// 全局错误 window.addEventListener('error', (event) => {// 将 event.error 信息上报到服务器reportError(event.error); }); // Promise 错误 window.addEventListener('unhandledrejection', (event) => {reportError(event.reason); }); - 错误上报:构建一个上报接口,将错误的堆栈信息、用户环境、发生时间等发送到日志服务器。
- 使用监控平台:接入 Sentry、Fundebug 等第三方前端监控服务,它们提供了完整的错误收集、聚合、分析和告警功能。
面试回答要点:区分开发和生产环境的不同做法。生产环境的核心是 “全局监听 + 上报机制”,最好能提到 Sentry 这样的专业工具。
14. import 和 require 的区别
| 特性 | require (CommonJS) | import (ES Module) |
|---|---|---|
| 本质 | 运行时加载,是一个函数调用 | 编译时加载(静态化),是一个关键字 |
| 加载方式 | 同步加载 | 异步加载 |
| 位置 | 可放在代码任何位置 | 必须放在模块顶层(目前),不能嵌套在条件语句中 |
| 赋值 | 动态的,可以解构 const { a } = require(‘mod’) | 静态的,编译时就能确定依赖关系 |
| 性能/优化 | 无法在编译时做优化 | 支持 Tree Shaking(摇树优化),剔除未引用代码 |
| 值类型 | 导出的是值的拷贝 | 导出的是值的引用(只读) |
面试回答要点:最核心的区别是 “动态” vs “静态”。import 的静态特性使得构建工具可以进行 Tree Shaking,这是现代前端工程优化的关键。
15. 手撕:猴子找大王(约瑟夫环问题)
这是一个经典的算法问题。最优解法是使用数学公式,但面试中更常考察模拟过程的实现。
题目:m 只猴子坐一圈报数,报到 n 的出列,求最终剩下的索引。
思路:使用一个数组模拟猴子圈,循环报数,每次移除报到 n 的猴子,直到只剩一只。
代码实现:
function find(m, n) {// 1. 初始化猴子数组 [0, 1, 2, ..., m-1]const monkeys = Array.from({ length: m }, (_, i) => i);// 2. 初始化报数索引let countIndex = 0;// 3. 循环,直到只剩一只猴子while (monkeys.length > 1) {// 4. 计算当前轮次要出列的猴子索引: (当前开始位置 + 数 n-1 步) % 当前剩余猴子数// 因为从1开始数,数到n,实际是移动 n-1 步countIndex = (countIndex + n - 1) % monkeys.length;// 5. 移除该猴子monkeys.splice(countIndex, 1);// 注意:splice 后,countIndex 自然指向了下一位猴子的位置,无需额外+1}// 6. 返回最后剩下的猴子索引return monkeys[0];
}// 测试
console.log(find(5, 3)); // 输出 3
// 过程模拟: 初始 [0,1,2,3,4]
// 第1轮: 数到3,移除2 -> [0,1,3,4],从3开始数
// 第2轮: 数到3,移除0 -> [1,3,4],从1开始数
// 第3轮: 数到3,移除4 -> [1,3],从1开始数
// 第4轮: 数到3,移除1 -> [3]
更优的数学递归解法(时间复杂度 O(m)):
function find(m, n) {if (m === 1) return 0;return (find(m - 1, n) + n) % m;
}
// 思路:已知 m-1 只猴子时的大王索引,反推 m 只猴子时的大王索引。
面试回答要点:可以先给出模拟解法,如果面试官追问效率,再引出数学解法,体现你的思维深度。
希望这份详细的解答能帮助你顺利通过面试!
