JS面试题目大全|全面覆盖前端高频考点,助你高效备战大厂面试
本文档系统梳理JavaScript核心知识点,涵盖ES6+语法、闭包、原型链、异步编程、DOM操作、性能优化、手写代码等JS面试题目,提供详细解析、实战代码示例、高频追问策略及学习路径建议,适合初中高级前端开发者系统性复习与查漏补缺。
ES6/ES7核心语法|JS面试题目高频考点TOP 1
1.1 let/const与块级作用域
面试官常问:“var、let、const有什么区别?为什么let没有变量提升?”
⚡ 关键点
• var:函数作用域,存在变量提升(Hoisting),允许重复声明
• let/const:块级作用域(Block Scope),不存在变量提升,存在“暂时性死区”(TDZ)
• const:声明常量,必须初始化,不可重新赋值(但对象属性可变)
经典陷阱题:
// 面试高频陷阱
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出:3 3 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 输出:0 1 2
}
追问策略:为什么let没有变量提升?—— 因为引擎在编译阶段为块级作用域创建了“词法环境”,在TDZ内访问变量会抛出ReferenceError,而非undefined。
1.2 箭头函数与this绑定
面试官常问:“箭头函数与普通函数有哪些本质区别?”
⚙️ 箭头函数四大特性
- 无自己的this、arguments、super、new.target
- this值由外层作用域决定(词法绑定)
- 不能作为构造函数(无prototype)
- 不能用作Generator(无yield)
代码示例:
const obj = {
name: 'Alice',
regularFunc: function() {
setTimeout(function() {
console.log(this.name); // window.name(非严格模式)或 undefined(严格模式)
});
},
arrowFunc: function() {
setTimeout(() => {
console.log(this.name); // 'Alice' —— this绑定到obj
});
}
};
obj.regularFunc(); // undefined
obj.arrowFunc(); // 'Alice'
1.3 模板字符串与字符串方法
常用方法对比:
| 方法 | 功能 | ES5等价 |
|---|---|---|
includes() | 判断是否包含子串 | indexOf(str) !== -1 |
startsWith() | 判断是否以子串开头 | indexOf(str) === 0 |
repeat(n) | 重复字符串n次 | Array(n+1).join(str) |
padStart(n, str) | 头部补全至n长度 | 无原生等价 |
⚡ 实用技巧:模板字符串嵌套
const renderList = (items) => `
${items.map(item => `- ${item}
`).join('')}
`;
console.log(renderList(['JS', 'CSS', 'HTML']));
2.1 数组与对象解构
面试高频场景:从API返回的数据结构中提取字段,或处理函数参数默认值。
⚙️ 解构默认值与嵌套解构
// 嵌套解构 + 默认值
const response = {
data: {
user: { name: 'Bob', age: 25 },
meta: { code: 200 }
}
};
const {
data: {
user: { name, age = 18, email = 'guest@example.com' } = {},
meta: { code } = {}
} = {}
} = response;
console.log(name, age, email, code); // Bob 25 guest@example.com 200
2.2 函数参数解构
用于处理可选配置项,避免冗长的if判断:
function createUser({ name = 'Anonymous', role = 'user', permissions = [] } = {}) {
return { name, role, permissions };
}
createUser(); // { name: 'Anonymous', role: 'user', permissions: [] }
createUser({ name: 'Alice', role: 'admin' }); // { name: 'Alice', role: 'admin', permissions: [] }
3.1 class语法与继承
注意:class只是语法糖,底层仍是基于原型的继承。
⚡ class与原型链对比
// class语法
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用父类构造函数
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks.`);
}
}
const d = new Dog('Rex');
d.speak(); // Rex makes a noise. / Rex barks.
底层等价实现:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
Animal.prototype.speak.call(this);
console.log(this.name + ' barks.');
};
4.1 ES6模块导入导出
面试易错点:导入的是值引用(live bindings),而非值拷贝。
⚡ 动态绑定示例
// counter.js
export let count = 1;
export function increment() {
count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 1
increment();
console.log(count); // 2 —— 重新导入时值已更新
对比CommonJS:CommonJS的require返回的是值拷贝,无法动态更新:
// commonjs-counter.js
let count = 1;
module.exports = { count, increment: () => count++ };
// main.js
const counter = require('./commonjs-counter');
console.log(counter.count); // 1
counter.increment();
console.log(counter.count); // 1 —— 未更新!
5. 其他重要特性
- 扩展运算符(...):用于数组/对象浅拷贝、函数参数展开
- Promise:处理异步操作,支持链式调用
- async/await:基于Promise的语法糖,实现同步风格的异步代码
- Symbol:唯一值,用于对象属性键(避免命名冲突)
- Set/Map:ES6新增集合类型,支持任意类型键/值
⚡ Map vs Object对比
| 特性 | Object | Map |
|---|---|---|
| 键类型 | 仅字符串/Symbol | 任意类型 |
| 大小获取 | Object.keys(obj).length | map.size |
| 迭代性 | 需转换为数组 | 原生支持迭代 |
| 原型污染风险 | 是 | 否 |
闭包与作用域|JS面试题目经典必考
1. 闭包定义与形成条件
定义:函数与其词法环境的组合,使函数能访问其词法作用域外的变量。
形成条件:
- 函数嵌套(内部函数)
- 内部函数引用了外部函数的变量
- 外部函数执行完毕,但内部函数仍被引用
⚡ 典型闭包案例:计数器
function createCounter() {
let count = 0; // 被闭包引用的变量
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter();
console.log(counter2()); // 1 —— 独立的词法环境
2. 闭包的内存泄漏风险
当闭包引用了大型DOM节点或长生命周期对象时,可能导致内存泄漏:
// 风险写法
function setup() {
const element = document.getElementById('btn');
element.onclick = function() {
console.log(element.id); // 引用了外部变量element
};
}
解决方案:手动解除引用或使用WeakMap存储元数据
3. 作用域链与变量查找
变量查找规则:当前作用域 → 上层作用域 → ... → 全局作用域
⚡ 作用域链深度示例
function a() {
let x = 1;
function b() {
let y = 2;
function c() {
let z = 3;
console.log(x, y, z); // 1 2 3 —— 作用域链:c → b → a → 全局
}
c();
}
b();
}
a();
4. 闭包在模块化中的应用
ES6前常用IIFE实现模块封装:
const CounterModule = (function() {
let count = 0; // 私有变量
return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count
};
})();
console.log(CounterModule.getValue()); // 0
CounterModule.increment();
console.log(CounterModule.getValue()); // 1
原型链与继承|JS面试题目难点解析
1. 原型链结构
⚡ 核心关系图
obj.__proto__ === Object.prototype
Object.prototype.__proto__ === null
Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
2. 原型继承的多种方式
2.1 原型链继承
function Parent() {
this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {}
Child.prototype = new Parent(); // 关键:父实例作为子原型
Child.prototype.constructor = Child;
const child1 = new Child();
const child2 = new Child();
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green'] —— 引用类型属性共享!
2.2 构造函数继承(借用构造函数)
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name) {
Parent.call(this, name); // 关键:在子构造函数中调用父构造函数
}
const child1 = new Child('Alice');
const child2 = new Child('Bob');
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue'] —— 每个实例独立副本!
缺点:无法复用方法(每个实例都有独立方法副本)
2.3 组合继承(原型链+构造函数)
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第一次调用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第二次调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child1 = new Child('Alice', 25);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue']
缺点:调用两次父构造函数(浪费性能)
2.4 寄生组合继承(最优方案)
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype); // 创建父原型副本
prototype.constructor = child; // 修复构造函数指向
child.prototype = prototype; // 赋值给子原型
}
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
console.log(this.age);
};
const child1 = new Child('Alice', 25);
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true
3. ES6 class继承本质
ES6的class ... extends底层即使用寄生组合继承:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用父类构造函数
}
}
编译后等价于:
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
function Animal(name) { this.name = name; }
function Dog(name) {
Animal.call(this, name);
}
_inheritsLoose(Dog, Animal);
事件循环与异步编程|JS面试题目深度解析
1. 浏览器事件循环模型
⚡ 执行栈 + 任务队列
- Call Stack:执行同步代码
- Task Queue(Macro-task):setTimeout、setInterval、I/O、UI渲染
- Microtask Queue:Promise.then、MutationObserver、queueMicrotask
执行顺序:同步代码 → 所有微任务 → 渲染 → 下一个宏任务
⚡ 典型输出题
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
queueMicrotask(() => console.log('4'));
new Promise(resolve => {
console.log('5');
resolve();
}).then(() => console.log('6'));
console.log('7');
// 输出顺序:1 → 5 → 7 → 3 → 4 → 6 → 2
2. async/await原理
本质:基于Generator和Promise的语法糖,实现同步风格的异步代码。
⚡ async函数编译过程
// 原始代码
async function fetchData() {
const res1 = await fetch('/api1');
const res2 = await fetch('/api2');
return [res1, res2];
}
// 等价实现(简化版)
function fetchData() {
return __awaiter(this, void 0, void 0, function* () {
const res1 = yield fetch('/api1');
const res2 = yield fetch('/api2');
return [res1, res2];
});
}
3. 并发控制:Promise.all与race
⚡ 并发限制器实现
function limitConcurrent(tasks, limit) {
const results = [];
let index = 0;
function run() {
if (index >= tasks.length) return;
const i = index++;
tasks[i]().then(result => {
results[i] = result;
if (index < tasks.length) run();
});
}
for (let i = 0; i < Math.min(limit, tasks.length); i++) {
run();
}
return new Promise(resolve => {
const checkComplete = () => {
if (results.length === tasks.length) {
resolve(results);
} else {
setTimeout(checkComplete, 10);
}
};
checkComplete();
});
}
// 使用示例
const tasks = [
() => new Promise(r => setTimeout(() => r('A'), 100)),
() => new Promise(r => setTimeout(() => r('B'), 50)),
() => new Promise(r => setTimeout(() => r('C'), 150))
];
limitConcurrent(tasks, 2).then(console.log); // ['A', 'B', 'C'](按原顺序)
4. 实际应用场景
- 图片懒加载:监听滚动事件 + IntersectionObserver
- 防抖节流:搜索框输入、窗口调整
- 请求合并:使用Promise.all批量请求
- 超时控制:Promise.race + setTimeout
DOM操作与事件机制|JS面试题目实战技巧
1. DOM事件流三阶段
- 捕获阶段(Capture):window → document → html → body → ... → 目标元素
- 目标阶段(Target):事件到达目标元素
- 冒泡阶段(Bubble):目标元素 → ... → body → html → document → window
⚡ 事件监听参数
element.addEventListener('click', handler, {
capture: true, // 是否在捕获阶段触发(默认false)
once: true, // 仅触发一次(自动移除)
passive: true // 不调用preventDefault(),提升滚动性能
});
2. 事件委托(Event Delegation)
原理:利用事件冒泡,将子元素事件绑定到父元素
⚡ 典型实现
// HTML: - Item 1
- Item 2
document.getElementById('list').addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('Clicked:', e.target.textContent);
}
});
优势:
- 减少事件监听器数量(性能优化)
- 新添加的子元素自动拥有事件处理能力
3. 自定义事件
// 创建自定义事件
const event = new CustomEvent('dataLoaded', {
detail: { users: ['Alice', 'Bob'] },
bubbles: true,
cancelable: true
});
// 监听
document.addEventListener('dataLoaded', e => {
console.log(e.detail.users);
});
// 触发
document.dispatchEvent(event);
4. 合成事件(SyntheticEvent)
React/Vue等框架封装的跨浏览器事件系统,解决原生事件兼容性问题。
性能优化策略|JS面试题目高分要点
1. 渲染优化
- 关键渲染路径:HTML解析 → CSSOM构建 → DOM + CSSOM合并 → 布局 → 绘制 → 合成
- 减少重排(Reflow):批量修改DOM、使用transform代替top/left
- 使用DocumentFragment:减少DOM操作次数
⚡ 批量DOM操作示例
// 低效:每次append触发一次重排
const ul = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
ul.appendChild(li); // 1000次重排!
}
// 高效:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment); // 1次重排!
2. 内存管理
- 警惕内存泄漏:未清除的定时器、未移除的事件监听器、DOM引用未释放
- 使用WeakMap/WeakSet:存储DOM节点元数据
- 图片懒加载:IntersectionObserver替代scroll监听
⚡ 弱引用示例
const domMap = new WeakMap();
function storeElementData(element, data) {
domMap.set(element, data);
}
function retrieveElementData(element) {
return domMap.get(element);
}
// 当element被移除DOM时,WeakMap自动释放引用
const el = document.getElementById('btn');
storeElementData(el, { count: 0 });
el.addEventListener('click', () => {
const data = retrieveElementData(el);
data.count++;
});
3. 网络优化
- 资源压缩:JS/Gzip/Brotli压缩
- CDN分发:静态资源走CDN
- HTTP/2多路复用:减少连接开销
- Service Worker缓存:实现PWA离线支持
手写代码题库|JS面试题目实战演练
1. 防抖(Debounce)
⚡ 搜索框输入防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 使用
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
console.log('Searching:', e.target.value);
}, 300));
2. 节流(Throttle)
⚡ 滚动事件节流
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用
window.addEventListener('scroll', throttle(function() {
console.log('Scroll position:', window.scrollY);
}, 100));
3. 深拷贝(Deep Clone)
⚡ 支持循环引用的深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
// 测试循环引用
const obj = { a: 1 };
obj.self = obj;
const cloned = deepClone(obj);
console.log(cloned.self === cloned); // true
4. Promise并发控制
⚡ 限制最大并发数
function limitPromise(promises, limit) {
const results = [];
let index = 0;
function run() {
if (index >= promises.length) return;
const i = index++;
promises[i]().then(result => {
results[i] = result;
run();
});
}
for (let i = 0; i < Math.min(limit, promises.length); i++) {
run();
}
return new Promise(resolve => {
const check = () => {
if (results.length === promises.length) resolve(results);
else setTimeout(check, 10);
};
check();
});
}
高频追问策略|JS面试题目高分技巧
1. 面试官追问逻辑
- 技术深度:从“怎么用” → “为什么” → “如何实现”
- 工程思维:从“单点” → “系统” → “演进”
- 实战经验:从“理论” → “踩坑” → “优化”
2. 典型追问链
答:闭包是函数与其词法环境的组合...
答:当闭包引用了大型DOM节点或长生命周期对象时,可能导致内存泄漏。解决方案包括:手动解除引用、使用WeakMap存储元数据、避免不必要的闭包。
答:① 使用Chrome DevTools的Memory面板录制分配栈;② 检查未清除的定时器;③ 查看是否有DOM元素被JS引用;④ 分析堆快照(Heap Snapshot);⑤ 使用Lighthouse进行自动化检测。