
var / let / const 的区别: var:函数作用域、存在变量提升、允许重复声明。let / const:块级作用域、存在暂时性死区(TDZ)、禁止重复声明。“为什么
var在 for 循环中会导致闭包问题?而let不会?”
// 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
}let/const 声明前访问变量会抛出 ReferenceError。var a = 1 会挂载到 window.aglobal“如何用闭包实现一个计数器?闭包一定会导致内存泄漏吗?”
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
console.log(counter.getCount()); // 1使用 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;
};
}prototype:构造函数的属性,指向原型对象。__proto__:实例对象的内部属性,指向其构造函数的 prototype。class 本质仍是基于原型的语法糖。“什么是寄生组合式继承?为什么它是 JavaScript 继承的最佳实践?”
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(); // Aliceinstanceof 原理:检查对象的 __proto__ 链是否包含构造函数的 prototype。Object.getPrototypeOf() 或 Reflect.getPrototypeOf() 获取原型。Callback → Promise → Generator + co → async/await
“Promise 相比回调函数有哪些优势?”
// 回调地狱(难以维护、错误处理分散)
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);
}
}Promise.all([p1, p2])Promise.allSettled().then() / .catch() 回调被放入微任务队列,优先于宏任务执行。.then() 返回新 Promise,支持连续处理。“手写
Promise.all,并说明其行为。”
Promise.allPromise.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)
);
});
});
};Promise.race():返回第一个 settled 的 Promise 结果。Promise.any():返回第一个 fulfilled 的结果(ES2021)。window.addEventListener('unhandledrejection', ...)setTimeout、setInterval、I/O、UI 渲染Promise.then、queueMicrotask、MutationObserver“以下代码的输出顺序是什么?”
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出顺序:1 → 4 → 3 → 2process.nextTick()(Node.js)优先级高于 Promise.then。map、filter、reduce、compose)。“如何用
reduce实现map?如何实现函数组合(compose)?”
// 用 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); // 11useMemo / useCallback 本质是缓存纯函数结果,提升性能。interface)、泛型(Generics)、联合/交叉类型等高级特性。“
interface和type有什么区别?”
特性 | interface | type |
|---|---|---|
可扩展(声明合并) | ❌ | |
支持计算属性 | ❌ | |
能表示原始类型 | ❌ | (如 type A = string) |
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');Partial<T>、Pick<T, K>、Omit<T, K>、Record<K, T>typeof、in、自定义函数缩小类型范围。虚拟 DOM 是用 JS 对象描述真实 DOM 的轻量表示。当状态变化时,通过 Diff 算法 计算最小更新 patch,批量应用到真实 DOM,避免频繁重排重绘。
“为什么不能直接操作真实 DOM?key 的作用是什么?”
// 创建虚拟节点
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);
}// 错误:使用 index 作为 key
{list.map((item, index) => <Item key={index} value={item} />)}
// 正确:使用唯一 ID
{list.map(item => <Item key={item.id} value={item} />)}原因:当列表发生插入/删除时,
index会错位,导致 React 复用错误的组件实例,引发状态错乱(如输入框内容错位)。
场景 | React | Vue 3 |
|---|---|---|
父 → 子 | props | props |
子 → 父 | 回调函数 / useState | $emit / v-model |
跨层级 | Context / Zustand | provide/inject / Pinia |
全局状态 | Redux Toolkit / Jotai | Pinia |
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);// 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;
}
}
});“优先使用 props/events;跨多层用 Context/provide;复杂状态用 Redux/Pinia。避免过早引入全局状态。”
// React
const LazyComp = React.lazy(() => import('./HeavyComponent'));
<Suspense fallback="Loading..."><LazyComp /></Suspense>
// Vue 3
const AsyncComp = defineAsyncComponent(() => import('./Heavy.vue'));function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}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>
);
}特性 | Webpack | Vite |
|---|---|---|
启动速度 | 慢(需打包) | 极快(原生 ESM) |
HMR 机制 | 模块替换 | 原生 ESM 精准更新 |
适用场景 | 大型复杂项目 | 快速开发、中小型项目 |
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`);
});
});
}
}export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'axios']
}
}
}
}
});“Tree Shaking 生效的前提是什么?” 答:使用 ES Module(静态导入)、未使用导出项、正确配置
sideEffects
display: none)️ 回流(reflow)成本远高于重绘(repaint)!
// 危险
el.innerHTML = userInput;
// 安全
el.textContent = userInput; // 最佳
// 或手动转义
const escapeHtml = str => str.replace(/[&<>"']/g, m => ({
'&': '&', '<': '<', '>': '>',
'"': '"', "'": '''
})[m]);// 后端(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 攻击层级 | 能力要求 |
|---|---|
语言基础 | JS 核心(作用域、闭包、原型、异步、事件循环) |
框架深度 | React/Vue 响应式原理、组件设计、状态管理 |
工程能力 | 构建工具、性能优化、CI/CD、监控体系 |
计算机基础 | 网络协议、浏览器原理、安全机制、数据结构 |
面试建议:回答时采用 “场景 → 问题 → 方案 → 权衡” 结构,展现系统思维与工程判断力。
模块 | 实践建议 |
|---|---|
虚拟 DOM | 手写 mini React,理解 patch 与 reconciliation |
状态管理 | 对比 Context / Redux / Zustand / Pinia 适用场景 |
性能优化 | 在个人项目中跑 Lighthouse,完成一次完整优化 |
工程化 | 配置 Webpack/Vite,理解 loader/plugin 工作原理 |
浏览器 | 用 Performance 面板分析一个页面的渲染瓶颈 |