前端技术面试题库
本文精选了前端面试中的高频题目,涵盖 JavaScript 基础、框架原理、性能优化等核心知识点。
JavaScript 基础
1. 闭包
问题:什么是闭包?闭包的应用场景有哪些?
答案:
闭包是指函数能够访问其词法作用域外的变量。
// 闭包示例
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2应用场景:
- 数据私有化
- 函数柯里化
- 模块化
- 防抖节流
注意事项:
- 闭包会导致内存占用
- 注意避免内存泄漏
2. this 指向
问题:JavaScript 中 this 的指向规则是什么?
答案:
this 的指向取决于函数的调用方式:
// 1. 默认绑定:指向全局对象(严格模式下是 undefined)
function foo() {
console.log(this);
}
foo(); // window (浏览器) 或 global (Node.js)
// 2. 隐式绑定:指向调用对象
const obj = {
name: 'Alice',
sayName() {
console.log(this.name);
}
};
obj.sayName(); // 'Alice'
// 3. 显式绑定:call、apply、bind
function greet() {
console.log(`Hello, ${this.name}`);
}
const person = { name: 'Bob' };
greet.call(person); // 'Hello, Bob'
// 4. new 绑定:指向新创建的对象
function Person(name) {
this.name = name;
}
const p = new Person('Charlie');
console.log(p.name); // 'Charlie'
// 5. 箭头函数:继承外层作用域的 this
const obj2 = {
name: 'David',
sayName: () => {
console.log(this.name);
}
};
obj2.sayName(); // undefined(this 指向外层作用域)优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
3. 原型链
问题:解释 JavaScript 的原型链机制
答案:
// 原型链示例
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
const person = new Person('Alice');
// 原型链查找过程
person.sayName(); // 'Alice'
// 1. 在 person 对象上查找 sayName -> 没找到
// 2. 在 person.__proto__ (Person.prototype) 上查找 -> 找到
// 3. 执行 Person.prototype.sayName
// 原型链关系
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 完整的原型链
person -> Person.prototype -> Object.prototype -> null继承实现:
// ES5 继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Buddy', 'Golden Retriever');
dog.eat(); // 'Buddy is eating'
dog.bark(); // 'Woof!'
// ES6 继承
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}4. Promise
问题:实现一个简单的 Promise
答案:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
});
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
}, reject);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
Promise.resolve(promise).then(resolve, reject);
});
});
}
}
// 使用示例
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('Success!'), 1000);
});
promise.then(value => {
console.log(value); // 'Success!'
});5. 事件循环
问题:解释 JavaScript 的事件循环机制
答案:
// 事件循环示例
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 输出顺序:1, 4, 3, 2
// 解释:
// 1. 同步代码:console.log('1') 和 console.log('4')
// 2. 微任务:Promise.then -> console.log('3')
// 3. 宏任务:setTimeout -> console.log('2')执行顺序:
- 执行同步代码
- 执行微任务队列(Promise、MutationObserver)
- 执行宏任务队列(setTimeout、setInterval、I/O)
- 重复 2-3
复杂示例:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise(resolve => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeoutReact 相关
1. Virtual DOM
问题:React 的 Virtual DOM 是如何工作的?
答案:
Virtual DOM 是对真实 DOM 的 JavaScript 对象表示。
工作流程:
// 1. 创建 Virtual DOM
const vdom = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: {
children: 'Hello'
}
},
{
type: 'p',
props: {
children: 'World'
}
}
]
}
};
// 2. Diff 算法比较新旧 Virtual DOM
function diff(oldVNode, newVNode) {
// 比较节点类型
if (oldVNode.type !== newVNode.type) {
return { type: 'REPLACE', newVNode };
}
// 比较属性
const propsPatches = diffProps(oldVNode.props, newVNode.props);
// 比较子节点
const childrenPatches = diffChildren(
oldVNode.props.children,
newVNode.props.children
);
return {
type: 'UPDATE',
propsPatches,
childrenPatches
};
}
// 3. 根据 Diff 结果更新真实 DOM
function patch(dom, patches) {
switch (patches.type) {
case 'REPLACE':
const newDom = createElement(patches.newVNode);
dom.parentNode.replaceChild(newDom, dom);
break;
case 'UPDATE':
updateProps(dom, patches.propsPatches);
patches.childrenPatches.forEach((patch, index) => {
patch(dom.childNodes[index], patch);
});
break;
}
}优势:
- 减少 DOM 操作
- 批量更新
- 跨平台(React Native)
2. Hooks 原理
问题:React Hooks 是如何实现的?
答案:
// 简化的 Hooks 实现
let currentComponent = null;
let currentHookIndex = 0;
function useState(initialValue) {
const component = currentComponent;
const hookIndex = currentHookIndex;
// 初始化 hooks 数组
if (!component.hooks) {
component.hooks = [];
}
// 获取或初始化 hook
if (component.hooks[hookIndex] === undefined) {
component.hooks[hookIndex] = initialValue;
}
const setState = (newValue) => {
component.hooks[hookIndex] = newValue;
// 触发重新渲染
render(component);
};
currentHookIndex++;
return [component.hooks[hookIndex], setState];
}
function useEffect(callback, deps) {
const component = currentComponent;
const hookIndex = currentHookIndex;
if (!component.hooks) {
component.hooks = [];
}
const oldDeps = component.hooks[hookIndex];
// 检查依赖是否变化
const hasChanged = !oldDeps || deps.some((dep, i) => dep !== oldDeps[i]);
if (hasChanged) {
// 执行副作用
callback();
// 保存依赖
component.hooks[hookIndex] = deps;
}
currentHookIndex++;
}
// 渲染组件
function render(component) {
currentComponent = component;
currentHookIndex = 0;
component.render();
}Hooks 规则:
- 只在顶层调用 Hooks
- 只在 React 函数中调用 Hooks
3. Fiber 架构
问题:React Fiber 是什么?解决了什么问题?
答案:
Fiber 是 React 16 引入的新的协调引擎,实现了可中断的渲染。
核心概念:
// Fiber 节点结构
const fiber = {
// 节点类型
type: 'div',
// 属性
props: {},
// 父节点
return: parentFiber,
// 第一个子节点
child: childFiber,
// 下一个兄弟节点
sibling: siblingFiber,
// 副作用标记
effectTag: 'UPDATE',
// 旧的 props
alternate: oldFiber
};
// 工作循环
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
// 执行工作单元
function performUnitOfWork(fiber) {
// 1. 处理当前 fiber
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 2. 创建子 fiber
const elements = fiber.props.children;
reconcileChildren(fiber, elements);
// 3. 返回下一个工作单元
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
}解决的问题:
- 长时间渲染导致页面卡顿
- 无法中断渲染过程
- 无法设置任务优先级
优势:
- 可中断渲染
- 优先级调度
- 增量渲染
性能优化
1. 首屏优化
问题:如何优化首屏加载速度?
答案:
// 1. 代码分割
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// 2. 路由懒加载
const routes = [
{
path: '/',
component: lazy(() => import('./pages/Home'))
},
{
path: '/about',
component: lazy(() => import('./pages/About'))
}
];
// 3. 图片懒加载
function LazyImage({ src, alt }) {
const [imageSrc, setImageSrc] = useState(placeholder);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setImageSrc(src);
observer.unobserve(entry.target);
}
});
});
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src]);
return <img ref={imgRef} src={imageSrc} alt={alt} />;
}
// 4. 预加载关键资源
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/critical.js" as="script" />
// 5. SSR/SSG
// Next.js
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
// 6. CDN 加速
const imageUrl = 'https://cdn.example.com/image.jpg';
// 7. 压缩资源
// webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()]
}
};2. 运行时优化
问题:如何优化 React 应用的运行时性能?
答案:
// 1. React.memo
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
// 只有当 data 改变时才重新渲染
return <div>{/* 复杂的渲染逻辑 */}</div>;
});
// 2. useMemo
function Component({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>{expensiveValue}</div>;
}
// 3. useCallback
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
// 4. 虚拟滚动
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>{items[index]}</div>
)}
</FixedSizeList>
);
}
// 5. 防抖节流
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 6. Web Worker
// worker.js
self.addEventListener('message', (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
});
// main.js
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => {
console.log(e.data);
};算法题
1. 两数之和
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}2. 防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}3. 节流
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}4. 深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
const cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}5. 数组扁平化
// 方法一:递归
function flatten(arr) {
return arr.reduce((acc, val) => {
return Array.isArray(val)
? acc.concat(flatten(val))
: acc.concat(val);
}, []);
}
// 方法二:迭代
function flatten(arr) {
const result = [];
const stack = [...arr];
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
// 方法三:flat
const arr = [1, [2, [3, [4]]]];
arr.flat(Infinity); // [1, 2, 3, 4]总结
面试准备要点:
- ✅ 理解核心概念和原理
- ✅ 能够手写常见算法
- ✅ 掌握性能优化技巧
- ✅ 了解框架底层实现
- ✅ 多做题多练习
记住:理解原理比死记硬背更重要!
参考资源
持续学习,不断进步!
