文章目录
一、变量与作用域
1. 核心概念
var/let/const的区别:var:函数作用域、存在变量提升、允许重复声明。let/const:块级作用域、存在暂时性死区(TDZ)、禁止重复声明。
- 作用域类型:全局作用域、函数作用域、块级作用域。
- 作用域链:JavaScript 引擎在查找变量时,会沿着当前作用域向上逐层搜索,直到全局作用域。
2. 高频面试题
“为什么
var在 for 循环中会导致闭包问题?而let不会?”
3. 代码示例
// var:函数作用域 + 提升 → 所有回调共享同一个 i(值为 3)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出:3, 3, 3
}
// let:每次循环创建新的块级绑定 → 每个闭包捕获独立的 i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出:0, 1, 2
}
4. 延伸知识点
- 暂时性死区(TDZ):在
let/const声明前访问变量会抛出ReferenceError。 - 全局对象差异:
- 浏览器中:
var a = 1会挂载到window.a - Node.js 中:模块作用域隔离,不会污染
global
- 浏览器中:
二、闭包与内存管理
1. 核心概念
- 闭包:函数与其词法环境的组合。即使外部函数已执行完毕,内部函数仍可访问其变量。
- 典型用途:数据封装、模块模式、防抖节流、私有变量模拟。
- 内存泄漏风险:闭包若长期持有对 DOM 节点或大对象的引用,且未及时释放,可能导致内存无法回收。
2. 高频面试题
“如何用闭包实现一个计数器?闭包一定会导致内存泄漏吗?”
3. 代码示例
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
console.log(counter.getCount()); // 1
4. 延伸建议
- 使用 Chrome DevTools 的 Memory 面板 分析闭包引用链。
- 对于缓存场景,可使用
WeakMap避免强引用导致的内存泄漏:const cache = new WeakMap(); function memoize(fn) { return obj => { if (cache.has(obj)) return cache.get(obj); const result = fn(obj); cache.set(obj, result); return result; }; }
三、原型与继承
1.核心概念
prototype:构造函数的属性,指向原型对象。__proto__:实例对象的内部属性,指向其构造函数的prototype。- ES6
class本质仍是基于原型的语法糖。
2. 高频面试题
“什么是寄生组合式继承?为什么它是 JavaScript 继承的最佳实践?”
3. 代码示例(寄生组合继承)
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 借用构造函数,继承实例属性
this.age = age;
}
// 继承原型方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 修复 constructor 指向
const c = new Child('Alice', 25);
c.say(); // Alice
4.延伸知识点
instanceof原理:检查对象的__proto__链是否包含构造函数的prototype。- 推荐使用
Object.getPrototypeOf()或Reflect.getPrototypeOf()获取原型。
四、异步编程演进
1. 演进路径
Callback → Promise → Generator + co → async/await
2. 高频面试题
“Promise 相比回调函数有哪些优势?”
3.代码对比
// 回调地狱(难以维护、错误处理分散)
getData(a => {
getMoreData(a, b => {
getEvenMore(b, c => {
console.log(c);
});
});
});
// Promise 链式调用(扁平化、统一错误处理)
getData()
.then(getMoreData)
.then(getEvenMore)
.then(console.log)
.catch(err => console.error(err));
// async/await(最接近同步写法,语义清晰)
async function fetchAll() {
try {
const a = await getData();
const b = await getMoreData(a);
const c = await getEvenMore(b);
console.log(c);
} catch (err) {
console.error(err);
}
}
4. 延伸建议
- 并行请求:
Promise.all([p1, p2]) - 容错并行:
Promise.allSettled() - 避免“async 函数滥用”:简单同步逻辑无需包装为 async
五、Promise 深度解析
1. 核心机制
- 状态不可逆:pending → fulfilled / rejected
- 微任务队列:
.then()/.catch()回调被放入微任务队列,优先于宏任务执行。 - 链式调用:每个
.then()返回新 Promise,支持连续处理。
2. 高频面试题
“手写
Promise.all,并说明其行为。”
手写 Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const results = new Array(promises.length);
let completed = 0;
if (promises.length === 0) return resolve(results);
promises.forEach((p, i) => {
Promise.resolve(p).then(
val => {
results[i] = val;
if (++completed === promises.length) resolve(results);
},
err => reject(err)
);
});
});
};
3. 延伸知识点
Promise.race():返回第一个 settled 的 Promise 结果。Promise.any():返回第一个 fulfilled 的结果(ES2021)。- 监听未处理 rejection:
window.addEventListener('unhandledrejection', ...)
六、事件循环机制(Event Loop)
1. 核心模型
- 调用栈(Call Stack):执行同步代码。
- 任务队列:
- 宏任务(Macrotask):
setTimeout、setInterval、I/O、UI 渲染 - 微任务(Microtask):
Promise.then、queueMicrotask、MutationObserver
- 宏任务(Macrotask):
- 执行顺序:同步代码 → 清空微任务队列 → 执行一个宏任务 → 循环
2. 高频面试题
“以下代码的输出顺序是什么?”
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出顺序:1 → 4 → 3 → 2
3. 注意事项
- Node.js 在 v11 之前微任务执行时机与浏览器不同,现已对齐。
process.nextTick()(Node.js)优先级高于Promise.then。
七、函数式编程概念
1. 核心思想
- 纯函数:无副作用、相同输入必得相同输出。
- 不可变性:避免直接修改原数据,返回新对象。
- 高阶函数:接收函数作为参数或返回函数(如
map、filter、reduce、compose)。
2. 高频面试题
“如何用
reduce实现map?如何实现函数组合(compose)?”
3. 代码示例
// 用 reduce 实现 map
Array.prototype.myMap = function(fn) {
return this.reduce((acc, cur, i, arr) => {
acc.push(fn(cur, i, arr));
return acc;
}, []);
};
// 函数组合:compose(f, g)(x) = f(g(x))
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const add1 = x => x + 1;
const double = x => x * 2;
const result = compose(add1, double)(5); // 11
4. 延伸建议
- 推荐库:Ramda、Lodash/fp
- React 中
useMemo/useCallback本质是缓存纯函数结果,提升性能。
八、类型系统与 TypeScript
1. 核心优势
- 编译期类型检查,提前暴露错误。
- 更强的 IDE 智能提示、自动补全、重构支持。
- 支持接口(
interface)、泛型(Generics)、联合/交叉类型等高级特性。
2. 高频面试题
“
interface和type有什么区别?”
| 特性 | interface | type |
|---|---|---|
| 可扩展(声明合并) | ❌ | |
| 支持计算属性 | ❌ | |
| 能表示原始类型 | ❌ | (如 type A = string) |
3. 泛型实战示例
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
async function request<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return res.json();
}
// 使用
interface User { id: number; name: string; }
const user = await request<User>('/api/user');
4. 延伸知识点
- Utility Types:
Partial<T>、Pick<T, K>、Omit<T, K>、Record<K, T> - 类型守卫:通过
typeof、in、自定义函数缩小类型范围。
九、虚拟 DOM 与 Diff 算法
1. 核心概念
虚拟 DOM 是用 JS 对象描述真实 DOM 的轻量表示。当状态变化时,通过 Diff 算法 计算最小更新 patch,批量应用到真实 DOM,避免频繁重排重绘。
2. 高频面试题
“为什么不能直接操作真实 DOM?key 的作用是什么?”
3. 简易虚拟 DOM 实现
// 创建虚拟节点
function h(tag, props, children) {
return { tag, props, children };
}
// 渲染 vnode 到真实 DOM
function render(vnode, container) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const el = document.createElement(vnode.tag);
for (let key in vnode.props) {
el.setAttribute(key, vnode.props[key]);
}
vnode.children.forEach(child => {
el.appendChild(render(child));
});
if (container) container.appendChild(el);
return el;
}
// 简易 diff(仅同层级替换)
function patch(parent, oldVNode, newVNode) {
if (oldVNode.tag !== newVNode.tag) {
parent.replaceChild(render(newVNode), parent.firstChild);
return;
}
// 简化:直接替换子树
const newEl = render(newVNode);
parent.replaceChild(newEl, parent.firstChild);
}
4. key 的正确使用
// 错误:使用 index 作为 key
{list.map((item, index) => <Item key={index} value={item} />)}
// 正确:使用唯一 ID
{list.map(item => <Item key={item.id} value={item} />)}
原因:当列表发生插入/删除时,
index会错位,导致 React 复用错误的组件实例,引发状态错乱(如输入框内容错位)。
十、组件通信与状态管理
1. 通信方案对比
| 场景 | React | Vue 3 |
|---|---|---|
| 父 → 子 | props | props |
| 子 → 父 | 回调函数 / useState | $emit / v-model |
| 跨层级 | Context / Zustand | provide/inject / Pinia |
| 全局状态 | Redux Toolkit / Jotai | Pinia |
2. React:Context + useReducer
const AppContext = createContext();
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER': return { ...state, user: action.payload };
default: return state;
}
}
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, { user: null });
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// 使用
const { state } = useContext(AppContext);
3. Vue 3 + Pinia
// stores/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ name: '', token: '' }),
actions: {
login(data) {
this.name = data.name;
this.token = data.token;
}
}
});
4. 面试回答技巧
“优先使用 props/events;跨多层用 Context/provide;复杂状态用 Redux/Pinia。避免过早引入全局状态。”
十一、性能优化实战
1. 三大方向
- 加载性能:代码分割、懒加载、预加载、CDN
- 运行时性能:防抖节流、虚拟滚动、React.memo
- 内存管理:及时解绑事件、避免闭包泄漏
2. 关键代码示例
1. 懒加载组件
// React
const LazyComp = React.lazy(() => import('./HeavyComponent'));
<Suspense fallback="Loading..."><LazyComp /></Suspense>
// Vue 3
const AsyncComp = defineAsyncComponent(() => import('./Heavy.vue'));
2. 防抖函数
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
3. 虚拟滚动(长列表)
function VirtualList({ items, itemHeight = 50 }) {
const [scrollTop, setScrollTop] = useState(0);
const visibleCount = Math.ceil(window.innerHeight / itemHeight);
const start = Math.floor(scrollTop / itemHeight);
const end = start + visibleCount;
const visibleItems = items.slice(start, end);
return (
<div onScroll={e => setScrollTop(e.target.scrollTop)} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: items.length * itemHeight }} />
<div style={{ transform: `translateY(${start * itemHeight}px)` }}>
{visibleItems.map(item => (
<div key={item.id} style={{ height: itemHeight }}>{item.text}</div>
))}
</div>
</div>
);
}
十二、工程化与构建工具
1. Webpack vs Vite 对比
| 特性 | Webpack | Vite |
|---|---|---|
| 启动速度 | 慢(需打包) | 极快(原生 ESM) |
| HMR 机制 | 模块替换 | 原生 ESM 精准更新 |
| 适用场景 | 大型复杂项目 | 快速开发、中小型项目 |
2. 自定义 Webpack Plugin
class BundleSizePlugin {
apply(compiler) {
compiler.hooks.done.tap('BundleSize', stats => {
stats.toJson().assets?.forEach(asset => {
console.log(`${asset.name}: ${(asset.size / 1024).toFixed(2)} KB`);
});
});
}
}
3. Vite 优化配置
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'axios']
}
}
}
}
});
4. 面试题
“Tree Shaking 生效的前提是什么?”
答:使用 ES Module(静态导入)、未使用导出项、正确配置sideEffects
十三、浏览器原理与安全
1. 渲染流程(关键六步)
- HTML 解析 → DOM 树
- CSS 解析 → CSSOM 树
- DOM + CSSOM → Render Tree(过滤
display: none) - Layout(回流):计算几何位置
- Paint(重绘):绘制像素
- Composite(合成):分层后 GPU 合成
️ 回流(reflow)成本远高于重绘(repaint)!
2. XSS 防御
// 危险
el.innerHTML = userInput;
// 安全
el.textContent = userInput; // 最佳
// 或手动转义
const escapeHtml = str => str.replace(/[&<>"']/g, m => ({
'&': '&', '<': '<', '>': '>',
'"': '"', "'": '''
})[m]);
3. CORS 与 Cookie 安全
// 后端(Express)
app.use(cors({ origin: 'https://your-app.com', credentials: true }));
// 前端
fetch('/api/data', { credentials: 'include' });
Cookie 安全属性:
HttpOnly:禁止 JS 访问(防 XSS)Secure:仅 HTTPS 传输SameSite=Strict/Lax:防御 CSRF 攻击
总结
1. 前端工程师能力图谱
| 层级 | 能力要求 |
|---|---|
| 语言基础 | JS 核心(作用域、闭包、原型、异步、事件循环) |
| 框架深度 | React/Vue 响应式原理、组件设计、状态管理 |
| 工程能力 | 构建工具、性能优化、CI/CD、监控体系 |
| 计算机基础 | 网络协议、浏览器原理、安全机制、数据结构 |
面试建议:回答时采用 “场景 → 问题 → 方案 → 权衡” 结构,展现系统思维与工程判断力。
2. 学习与准备建议
| 模块 | 实践建议 |
|---|---|
| 虚拟 DOM | 手写 mini React,理解 patch 与 reconciliation |
| 状态管理 | 对比 Context / Redux / Zustand / Pinia 适用场景 |
| 性能优化 | 在个人项目中跑 Lighthouse,完成一次完整优化 |
| 工程化 | 配置 Webpack/Vite,理解 loader/plugin 工作原理 |
| 浏览器 | 用 Performance 面板分析一个页面的渲染瓶颈 |
364





