1. JavaScript基础概念高频考点解析
作为前端开发的基石语言,JavaScript的基础概念是面试官最喜欢深挖的点。我见过太多候选人因为对基础概念理解不透彻而在面试中翻车。下面这些核心知识点,建议每个前端开发者都能倒背如流:
1.1 变量提升与暂时性死区
变量提升(Hoisting)是JavaScript中一个容易让人困惑的特性。很多人以为使用let/const就能完全避免提升问题,其实不然。看这个典型例子:
javascript复制console.log(a); // undefined
var a = 1;
console.log(b); // ReferenceError
let b = 2;
这里的关键区别在于:var声明的变量会被提升并初始化为undefined,而let/const虽然也会被提升,但不会初始化,形成所谓的"暂时性死区"(TDZ)。在实际项目中,这种差异可能导致难以追踪的bug。我的经验是:始终在作用域顶部声明所有变量,避免依赖提升特性。
1.2 闭包的实际应用场景
闭包(Closure)可能是面试中被问得最多的概念。简单来说,闭包就是能够访问其他函数内部变量的函数。但面试官更想听到的是你对闭包实际应用的理解:
javascript复制// 模块模式
const counter = (function() {
let privateCounter = 0;
return {
increment: function() {
privateCounter++;
},
getValue: function() {
return privateCounter;
}
};
})();
counter.increment();
console.log(counter.getValue()); // 1
这种模式在现代前端开发中仍然常见,特别是在需要封装私有状态时。React的Hooks本质上也是利用了闭包特性。我曾在一个性能敏感的项目中过度使用闭包导致内存泄漏,后来通过Chrome DevTools的Memory面板才定位到问题。
1.3 this指向的终极判断法则
关于this的指向问题,记住这个判断顺序:
- 是否使用new调用?→ this指向新创建的对象
- 是否使用call/apply/bind?→ this指向指定的对象
- 是否作为对象方法调用?→ this指向该对象
- 默认情况下 → 非严格模式指向window,严格模式为undefined
箭头函数是个例外,它的this在定义时就已经确定。在实际项目中,我经常看到这样的错误:
javascript复制const obj = {
name: 'Alice',
greet: function() {
setTimeout(function() {
console.log(`Hello, ${this.name}`); // Hello, undefined
}, 100);
}
};
解决方法要么使用箭头函数,要么提前保存this引用。
2. JavaScript异步编程深度剖析
现代前端开发离不开异步编程,这也是面试中的重点考察领域。从早期的回调地狱到现在的async/await,JavaScript的异步处理方式经历了巨大演变。
2.1 事件循环与任务队列
理解事件循环(Event Loop)是掌握JavaScript异步的核心。看这段代码:
javascript复制console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
输出顺序是1、4、3、2。这是因为:
- 同步代码最先执行
- Promise微任务(microtask)优先于setTimeout宏任务(macrotask)执行
- 事件循环会先清空微任务队列,再处理一个宏任务
在实际项目中,不注意这种执行顺序可能导致UI更新延迟等问题。我曾在Vue项目中遇到DOM更新不及时的情况,就是因为混用了宏任务和微任务。
2.2 Promise高级用法与陷阱
虽然Promise已经普及,但很多开发者对其高级用法仍不熟悉。比如这个常见的错误:
javascript复制// 错误写法
function fetchData() {
return new Promise((resolve) => {
if (cache) {
resolve(cache); // 同步resolve
} else {
fetch(url).then(resolve);
}
});
}
// 正确写法
function fetchData() {
return Promise.resolve().then(() => {
return cache || fetch(url);
});
}
第一种写法会导致then回调的时序不一致,可能引发难以调试的问题。Promise的设计初衷之一就是要保证异步一致性。
另一个常见需求是控制并发请求数,可以使用这样的实现:
javascript复制async function parallelWithLimit(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = task().finally(() => executing.delete(p));
executing.add(p);
results.push(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
2.3 async/await的最佳实践
async/await让异步代码看起来像同步代码,但也容易误用。以下是一些经验法则:
-
避免不必要的await:
javascript复制// 不好 const data = await fetchData(); return await process(data); // 好 const data = await fetchData(); return process(data); -
总是处理错误:
javascript复制async function main() { try { const data = await fetchData(); } catch (err) { // 至少记录错误 console.error('Failed to fetch data', err); throw err; // 或者返回默认值 } } -
注意并行优化:
javascript复制// 顺序执行(慢) const a = await getA(); const b = await getB(); // 并行执行(快) const [a, b] = await Promise.all([getA(), getB()]);
我曾在一个项目中发现页面加载很慢,就是因为开发者不必要地串行await多个独立请求,改为并行后性能提升了40%。
3. JavaScript面向对象与原型链
虽然现代前端更倾向于函数式编程,但理解JavaScript的面向对象特性仍然是面试必考内容。
3.1 原型继承与class语法糖
JavaScript使用原型继承而非经典继承。ES6的class只是语法糖:
javascript复制class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
console.log(`${this.name} barks.`);
}
}
这等价于ES5的原型链实现。关键点:
- 子类的
__proto__指向父类(继承静态属性) - 子类prototype的
__proto__指向父类prototype(继承实例方法) - super在构造函数中指向父类构造函数,在方法中指向父类原型
3.2 new操作符的内部机制
当使用new调用函数时,发生了这些步骤:
- 创建一个新对象,其
__proto__指向构造函数的prototype - 将this绑定到这个新对象
- 执行构造函数代码
- 如果构造函数没有显式返回对象,则返回这个新对象
手动实现一个new:
javascript复制function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
3.3 实现私有属性的几种方式
虽然ES2022正式加入了私有字段(#前缀),但了解其他实现方式也很重要:
- 闭包方式:
javascript复制function Person(name) {
let _age = 0;
return {
getName: () => name,
getAge: () => _age,
setAge: age => { _age = age; }
};
}
- Symbol方式:
javascript复制const _age = Symbol('age');
class Person {
constructor(age) {
this[_age] = age;
}
getAge() {
return this[_age];
}
}
- WeakMap方式:
javascript复制const _private = new WeakMap();
class Person {
constructor(age) {
_private.set(this, { age });
}
getAge() {
return _private.get(this).age;
}
}
每种方式各有优缺点,需要根据项目需求选择。我曾在一个大型项目中使用WeakMap实现私有属性,发现调试比较困难,后来改用TypeScript的private修饰符。
4. JavaScript性能优化与内存管理
高级前端职位面试中,性能优化是必问话题。JavaScript作为解释型语言,有其独特的性能特征。
4.1 垃圾回收机制
V8引擎的垃圾回收主要采用分代式GC:
- 新生代(Young Generation):使用Scavenge算法,频繁回收
- 老生代(Old Generation):使用Mark-Sweep(标记清除)和Mark-Compact(标记整理)
常见内存泄漏场景:
- 意外的全局变量
- 未清除的定时器或事件监听器
- 闭包持有大对象引用
- DOM引用未释放
我曾遇到一个SPA应用内存持续增长的问题,最后发现是路由切换时没有正确清理前一个页面的DOM事件监听器。
4.2 性能优化实战技巧
- 节流(throttle)与防抖(debounce):
javascript复制// 防抖:连续触发时只执行最后一次
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:固定时间间隔执行
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
- 使用Web Worker处理CPU密集型任务:
javascript复制// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('Result:', e.data);
};
// worker.js
self.onmessage = (e) => {
const result = processData(e.data);
self.postMessage(result);
};
- 虚拟列表优化长列表渲染:
javascript复制function renderVisibleItems(items, scrollTop, containerHeight, itemHeight) {
const startIdx = Math.floor(scrollTop / itemHeight);
const endIdx = Math.min(
items.length - 1,
startIdx + Math.ceil(containerHeight / itemHeight)
);
return items.slice(startIdx, endIdx + 1).map((item, i) => ({
...item,
style: { position: 'absolute', top: `${(startIdx + i) * itemHeight}px` }
}));
}
4.3 现代JavaScript性能特性
- Intersection Observer替代滚动监听:
javascript复制const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
lazyLoad(entry.target);
observer.unobserve(entry.target);
}
});
});
images.forEach(img => observer.observe(img));
- 使用requestIdleCallback处理低优先级任务:
javascript复制function processTask(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
performTask(tasks.pop());
}
if (tasks.length > 0) {
requestIdleCallback(processTask);
}
}
requestIdleCallback(processTask);
- 合理使用WebAssembly处理性能瓶颈:
javascript复制// 加载并实例化Wasm模块
WebAssembly.instantiateStreaming(fetch('module.wasm'))
.then(({ instance }) => {
const result = instance.exports.compute(1000);
console.log(result);
});
在最近的一个数据可视化项目中,我们将核心算法用Rust编写并编译为Wasm,性能比纯JavaScript实现提升了近10倍。
