一、JavaScript 基础
1. var、let、const 的区别是什么?
答案:
var:函数作用域,存在变量提升,可重复声明。let:块级作用域,不存在变量提升,不可重复声明。const:块级作用域,声明后不可重新赋值(引用类型内部仍可变)。
纵向延伸:
let和const不会创建全局对象属性(在浏览器中为window)。- 使用
const更推荐用于不变的变量,增强代码可读性与安全性。
更深层次问题:
- 在
for循环中使用var和let会导致什么不同? - 如何手动实现一个类似
let的块级作用域?
2. JavaScript 中的原始类型有哪些?
答案: 原始类型包括:
numberstringbooleannullundefinedsymbol(ES6)bigint(ES2020)
纵向延伸:
- 所有原始类型都是不可变的(immutable)。
typeof null返回"object"是历史遗留 bug。
更深层次问题:
Symbol类型在对象中的用途是什么?BigInt与Number的精度差异?
3. 什么是闭包?请举例说明其应用场景。
答案: 闭包是指有权访问另一个函数作用域中变量的函数。例如:
javascript
function outer() {
let count = 0;
return function inner() {
return ++count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
纵向延伸:
- 应用场景:数据封装、计数器、柯里化、缓存函数。
- 优点:避免污染全局变量;缺点:可能导致内存泄漏。
更深层次问题:
- 如何判断一个函数是否是闭包?
- 如何优化闭包导致的内存占用?
4. == 和 === 的区别是什么?
答案:
==:比较时进行类型转换(宽松相等)。===:不进行类型转换,要求值和类型都相同(严格相等)。
纵向延伸:
NaN == NaN为false,但Object.is(NaN, NaN)为true。- 推荐使用
===来避免意外类型转换带来的 bug。
更深层次问题:
0 == ''、0 == false、'' == false各返回什么?Object.is()与===的区别?
5. this 的指向有哪些情况?
答案: this 的指向取决于函数调用方式:
- 普通函数调用:
this指向window(非严格模式)或undefined(严格模式)。 - 方法调用:
this指向调用对象。 - 构造函数调用:
this指向新创建的对象。 - 箭头函数:继承外层作用域的
this。 - 显式绑定:通过
.call()、.apply()、.bind()设置。
纵向延伸:
- 箭头函数没有自己的
arguments对象。 - 使用
.bind()创建的函数不能再次更改this。
更深层次问题:
- 如何在事件处理函数中正确绑定
this? - 在
setTimeout中this的指向为何改变?
6. 什么是原型链?如何理解原型继承?
答案: 每个对象都有一个 [[Prototype]] 属性(可通过 __proto__ 或 Object.getPrototypeOf() 获取),它指向该对象的构造函数的 prototype。这种链式结构称为原型链。
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function () {
console.log(`Hi, ${this.name}`);
};
const p = new Person('Tom');
p.sayHi(); // Hi, Tom
纵向延伸:
- 原型链用于实现继承和共享方法。
- 查找属性时,若自身没有,则沿着原型链向上查找。
更深层次问题:
- 如何手动模拟类的继承机制?
- 原型链中的性能影响及优化策略?
7. new 关键字做了什么?
答案: 执行 new 运算符时,JavaScript 引擎会执行以下步骤:
- 创建一个空对象;
- 将构造函数的作用域赋给这个新对象(即
this指向该对象); - 执行构造函数中的代码(为对象添加属性);
- 如果构造函数返回的是一个对象,则返回该对象;否则返回新对象。
纵向延伸:
- 构造函数通常首字母大写,表示应配合
new使用。 - 若构造函数返回基本类型则无效,若返回对象则替换默认返回值。
更深层次问题:
- 如何手动实现一个简易版的
new函数? - 构造函数中
return this是否必要?
8. JavaScript 中如何实现继承?
答案: 常见方式包括:
- 原型链继承(
SubType.prototype = new SuperType()) - 借用构造函数(在子构造函数中调用父构造函数)
- 组合继承(原型链 + 构造函数)
- 寄生组合式继承(推荐方式)
- ES6 的
class继承(语法糖)
class Animal {}
class Dog extends Animal {}
纵向延伸:
extends实际上是基于原型链的语法糖。super()必须在子类构造函数中调用。
更深层次问题:
- 如何理解寄生组合式继承的实现原理?
class和传统构造函数的区别?
9. JavaScript 是单线程还是多线程?它是如何处理异步操作的?
答案: JavaScript 是单线程语言,同一时间只能执行一个任务。它通过 事件循环(Event Loop) 处理异步操作。
异步流程如下:
- 调用栈为空时,从回调队列中取出任务执行。
- 宏任务(如
setTimeout)和微任务(如Promise.then)分别排队。
纵向延伸:
- 微任务优先于宏任务执行。
- 使用
async/await可以让异步代码更像同步。
更深层次问题:
- 为什么
Promise.resolve().then()会比setTimeout(fn, 0)先执行? - 如何手动实现一个简易版的事件循环?
10. 什么是事件循环?请解释宏任务和微任务的区别。
答案: 事件循环是 JavaScript 异步执行的核心机制,负责协调代码执行、收集和分派事件。
- 宏任务(Macrotask) :如
setTimeout、setInterval、I/O、requestAnimationFrame。 - 微任务(Microtask) :如
Promise.then/catch/finally、queueMicrotask、MutationObserver。
纵向延伸:
- 微任务总是优先于下一个宏任务执行。
- 微任务队列清空后才进入下一个宏任务阶段。
更深层次问题:
- 为什么
Promise.resolve().then()比setTimeout(fn, 0)更快? - 如何利用微任务优化 UI 渲染?
二、TypeScript 基础
11. TypeScript 是什么?它解决了什么问题?
答案: TypeScript 是 JavaScript 的超集,提供了静态类型检查、面向对象特性、装饰器等功能。它解决了 JS 缺乏类型系统的痛点,提升了大型项目的可维护性和协作效率。
纵向延伸:
- 支持编译到任意版本的 JavaScript。
- 提供更好的 IDE 支持(如自动补全、错误提示)。
更深层次问题:
- TypeScript 编译后的代码是否会影响运行性能?
- 如何配置 TypeScript 项目?
12. interface 和 type 的区别是什么?
答案:
| 特性 | interface | type |
|---|---|---|
| 扩展方式 | 使用 extends | 使用交叉类型 & |
| 合并声明 | 支持接口合并(declaration merging) | 不支持 |
| 类型别名 | 不适用 | 支持联合类型、元组等复杂类型 |
纵向延伸:
- 推荐优先使用
interface,需要复杂类型时使用type。 interface更适合定义对象结构,type更灵活。
更深层次问题:
- 如何使用
type定义函数重载? - 如何使用
interface实现混入(mixins)?
13. any 和 unknown 的区别是什么?
答案:
any:关闭类型检查,允许对变量做任何操作。unknown:必须先进行类型检查后才能操作,类型更安全。
纵向延伸:
unknown是替代any的推荐类型。- 使用
if (typeof value === 'string')进行类型收窄。
更深层次问题:
- 如何结合
unknown使用类型守卫? never类型与unknown的关系?
14. TypeScript 中的泛型是什么?请举例说明。
答案: 泛型允许我们编写可以适配多种类型的函数或类,保持类型信息。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("hello"); // "hello"
纵向延伸:
- 泛型提高了代码复用性和类型安全性。
- 可用于接口、类、函数参数等多种场景。
更深层次问题:
- 如何约束泛型的类型范围(如
T extends object)? - 如何使用泛型实现工厂函数?
15. 什么是类型推断?它是如何工作的?
答案: 类型推断是 TypeScript 自动根据变量初始化的值推导出其类型的能力。
let num = 123; // 推断为 number 类型
num = "abc"; // 报错
纵向延伸:
- 初始化时未指定类型但赋值后,TS 会自动推断。
- 推断结果一旦确定,就不能再赋予其他类型。
更深层次问题:
- 如何查看某个变量的推断类型?
- 类型推断是否会影响性能?
16. 什么是类型守卫?请列举几种常见的类型守卫方式。
答案: 类型守卫用于在运行时判断变量的具体类型,以便在类型收窄后进行安全操作。
常见类型守卫:
typeof(适用于原始类型)instanceof(适用于类实例)- 自定义类型谓词函数(如
value is string)
function isString(value: unknown): value is string {
return typeof value === 'string';
}
纵向延伸:
- 类型守卫是实现
unknown类型安全操作的关键。 - 可结合联合类型实现条件分支逻辑。
更深层次问题:
- 如何使用类型守卫处理嵌套对象?
- 类型守卫与类型谓词的区别?
17. TypeScript 中的装饰器是什么?有什么作用?
答案: 装饰器是一种特殊声明,可用于类、方法、属性等,提供一种在不修改源码的情况下增强功能的方式。
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key} with`, args);
return originalMethod.apply(this, args);
};
return descriptor;
}
class MyClass {
@log
greet(name: string) {
return `Hello, ${name}`;
}
}
纵向延伸:
- 常用于日志、权限控制、缓存等横切关注点。
- Angular 中大量使用装饰器。
更深层次问题:
- 如何编写类装饰器?
- 装饰器在 React 或 Vue 中的应用?
18. TypeScript 中的 enum 和 const enum 有何区别?
答案:
enum:编译为对象,可在运行时访问。const enum:编译时内联,运行时无实际对象,减少体积。
enum Direction { Up, Down }
const enum Status { Active, Inactive }
// 编译后:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));
纵向延伸:
const enum性能更好,但无法反射枚举值。- 适用于常量集合,且不需要运行时查询。
更深层次问题:
- 如何将字符串映射为
enum? enum与联合类型的对比?
19. TypeScript 中的 namespace 和 module 有何区别?
答案:
namespace:用于组织内部代码结构,防止命名冲突。module:对应文件级别的模块划分,支持导入导出。
// namespace 示例
namespace MyNamespace {
export function foo() {}
}
MyNamespace.foo();
// module 示例(已不推荐)
module MyModule {
export function bar() {}
}
纵向延伸:
- 现代 TS 推荐使用
import/export模块系统。 namespace更适合旧项目迁移。
更深层次问题:
namespace是否会被 Tree Shaking?- 如何混合使用
namespace和export?
20. TypeScript 中如何实现类型兼容性?
答案: TypeScript 的类型系统基于“结构类型”而非“名义类型”,只要两个类型的结构匹配,就可以相互赋值。
interface Point {
x: number;
y: number;
}
class Coordinate {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
let point: Point = new Coordinate(1, 2); // 结构一致,合法
纵向延伸:
- 类型兼容性提高了灵活性,但也可能带来潜在风险。
- 可通过
branding技术实现名义类型模拟。
更深层次问题:
- 如何实现类型品牌(Nominal Typing)?
- 类型兼容性对泛型的影响?
三、综合与进阶
21. 如何手动实现一个 new 函数?
答案:
function myNew(Constructor, ...args) {
const obj = {};
obj.__proto__ = Constructor.prototype;
const res = Constructor.apply(obj, args);
return typeof res === 'object' ? res : obj;
}
纵向延伸:
myNew的核心逻辑包括:创建空对象、绑定原型链、执行构造函数、处理返回值。- 使用
apply绑定this,模拟new的行为。
更深层次问题:
- 如何通过
Object.setPrototypeOf替代__proto__? - 为什么手动实现的
new无法完全替代原生new?
22. Promise 的三种状态是什么?如何实现一个简易 Promise?
答案: Promise 有三种状态:
- pending(等待中)
- fulfilled(已成功)
- rejected(已失败)
简易实现:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
} else if (this.state === 'rejected') {
onRejected(this.reason);
}
}
}
纵向延伸:
Promise支持链式调用,通过then返回新的Promise。- 实际实现需处理异步、错误传播、队列等复杂逻辑。
更深层次问题:
- 如何实现
Promise.all和Promise.race? async/await是如何基于Promise实现的?
23. 什么是防抖(Debounce)和节流(Throttle)?请举例说明其应用场景。
答案:
- 防抖:在事件被触发后,等待一段时间再执行,若再次触发则重新计时。
- 节流:在一段时间内只允许触发一次,无论触发频率多高。
// 防抖
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// 节流
function throttle(fn, delay) {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn(...args);
flag = true;
}, delay);
};
}
纵向延伸:
- 防抖适用于输入搜索、窗口调整;节流适用于滚动监听、按钮防重复点击。
- 可结合
IntersectionObserver优化性能。
更深层次问题:
- 如何实现带取消功能的防抖/节流?
- 在 Vue/React 中如何结合
useEffect或useCallback使用?
24. JavaScript 中的 Symbol 类型有什么特点?如何使用?
答案:
Symbol是唯一且不可变的值,常用于对象属性的“私有化”。- 每个
Symbol值都是唯一的,即使参数相同也不相等。
const key1 = Symbol('key');
const key2 = Symbol('key');
console.log(key1 === key2); // false
const obj = {
[key1]: 'value1',
[key2]: 'value2',
};
console.log(obj); // { [Symbol(key)]: 'value1', [Symbol(key)]: 'value2' }
纵向延伸:
Symbol可用于避免属性名冲突。Symbol.for()全局注册,Symbol.keyFor()获取键名。
更深层次问题:
Symbol与Object.defineProperty的结合使用?- 如何通过
Symbol实现类的私有方法?
25. Map 和 Object 有什么区别?何时选择使用 Map?
答案:
| 特性 | Map | Object |
|---|---|---|
| 键类型 | 任意类型(包括对象、Symbol) | 仅字符串或 Symbol |
| 可迭代性 | 默认可迭代 | 需配合 Object.keys() 等方法 |
| 内存管理 | 更高效的键值对存储 | 属性过多时性能下降 |
| 方法丰富性 | 提供 size、clear() 等方法 | 需手动实现相关功能 |
纵向延伸:
Map更适合动态键值对存储,如缓存、配置项。Object更适合静态属性集合。
更深层次问题:
- 如何实现
Map的深拷贝? WeakMap与Map的区别及适用场景?
26. JavaScript 中的 Proxy 是什么?如何使用它实现数据劫持?
答案: Proxy 是 ES6 引入的元编程工具,用于拦截并修改对象的基本操作(如属性读取、赋值等)。
const target = {
name: 'Alice',
};
const handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(target, prop, value, receiver);
},
};
const proxy = new Proxy(target, handler);
proxy.name; // Getting name
proxy.name = 'Bob'; // Setting name to Bob
纵向延伸:
Proxy可用于实现响应式系统(如 Vue 3 的reactive)。- 支持拦截多种操作,如
deleteProperty、has等。
更深层次问题:
Proxy与Object.defineProperty的区别?- 如何处理嵌套对象的响应式?
27. TypeScript 中的 Partial、Required、Readonly 类型有什么作用?
答案:
Partial<T>:将T的所有属性变为可选。Required<T>:将T的所有属性变为必填。Readonly<T>:将T的所有属性变为只读。
interface User {
id: number;
name: string;
}
type PartialUser = Partial<User>; // { id?: number; name?: string; }
type RequiredUser = Required<User>; // { id: number; name: string; }
type ReadonlyUser = Readonly<User>; // { readonly id: number; readonly name: string; }
纵向延伸:
- 这些类型常用于表单验证、接口定义等场景。
- 结合
Pick、Omit可灵活控制属性。
更深层次问题:
- 如何自定义类似
Partial的泛型? DeepReadonly的实现原理?
28. TypeScript 中的 Record 和 Partial 有什么区别?
答案:
Record<K, T>:创建一个对象类型,键为K,值为T。Partial<T>:将T的所有属性变为可选。
type RecordExample = Record<'a' | 'b', string>; // { a: string; b: string; }
type PartialExample = Partial<{ a: string; b: number }>; // { a?: string; b?: number; }
纵向延伸:
Record用于定义固定结构的对象;Partial用于放宽属性约束。Record可结合keyof动态生成键。
更深层次问题:
- 如何通过
Record实现映射类型? Partial与Pick的组合使用?
29. 什么是 TypeScript 的装饰器?如何实现一个类装饰器?
答案: 装饰器是一种特殊声明,用于修改类、方法、属性等的行为。
function log(target: Function) {
console.log(`Class ${target.name} was decorated`);
}
@log
class MyClass {}
纵向延伸:
- 装饰器可以用于日志记录、权限控制、依赖注入等。
- 支持多个装饰器组合使用(从上到下执行)。
更深层次问题:
- 如何实现带参数的装饰器?
- 装饰器在 Angular 或 NestJS 中的应用?
30. TypeScript 中的 unknown 类型如何与类型守卫结合使用?
答案:
function processValue(value: unknown) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else if (Array.isArray(value)) {
console.log(value.length);
} else {
console.error('Unknown type');
}
}
纵向延伸:
unknown类型必须通过类型守卫逐步收窄后才能操作。- 与
any不同,unknown更安全。
更深层次问题:
- 如何通过类型谓词实现自定义类型守卫?
unknown与any的性能差异?
四、高级类型与泛型
31. 什么是 TypeScript 的条件类型?请举例说明。
答案: 条件类型基于条件表达式推导类型。
type IsString<T> = T extends string ? 'Yes' : 'No';
type A = IsString<'hello'>; // 'Yes'
type B = IsString<number>; // 'No'
纵向延伸:
- 条件类型常用于类型判断、类型映射。
- 支持嵌套条件类型。
更深层次问题:
- 如何实现
Exclude<T, U>? - 条件类型的分布式特性?
32. 什么是 TypeScript 的映射类型?如何实现一个 Readonly 映射类型?
答案: 映射类型通过遍历对象属性生成新类型。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
纵向延伸:
- 映射类型包括
Partial、Required、Pick、Omit等。 - 支持联合类型的映射。
更深层次问题:
- 如何实现
DeepReadonly? - 映射类型的高级用法(如
Mapped Types with Conditional Types)?
33. 什么是 TypeScript 的 infer 关键字?请举例说明其用途。
答案: infer 用于在条件类型中推导子类型。
type ExtractParam<T> = T extends (arg: infer P) => any ? P : never;
type A = ExtractParam<(name: string) => void>; // string
type B = ExtractParam<number>; // never
纵向延伸:
infer常用于提取函数参数、返回值类型。- 支持嵌套推导。
更深层次问题:
- 如何通过
infer提取数组元素类型? infer与递归类型的结合?
34. 什么是 TypeScript 的 utility types?请列举几个常用类型。
答案: utility types 是 TypeScript 提供的内置类型工具,简化类型操作:
Partial<T>:所有属性变为可选。Required<T>:所有属性变为必填。Readonly<T>:所有属性变为只读。Pick<T, K>:选取特定属性。Omit<T, K>:排除特定属性。Exclude<T, U>:从T中排除U的类型。
纵向延伸:
utility types是开发中常用的类型工具。- 可结合
mapped types自定义类型。
更深层次问题:
- 如何通过
utility types实现类型转换? - 自定义
utility types的实践案例?
35. 什么是 TypeScript 的 mapped types?如何实现一个 Optional 类型?
答案: 映射类型通过遍历对象属性生成新类型。
type Optional<T> = {
[P in keyof T]?: T[P];
};
纵向延伸:
- 映射类型支持
required、readonly、?等修饰符。 - 可结合条件类型实现复杂逻辑。
更深层次问题:
- 如何实现
DeepOptional? - 映射类型的性能优化?
五、异步编程与性能优化
36. async/await 是如何工作的?请举例说明。
答案: async/await 是基于 Promise 的语法糖,使异步代码更像同步。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
纵向延伸:
async函数内部自动返回Promise。await会暂停函数执行,直到Promise完成。
更深层次问题:
async/await与Promise.then的性能对比?- 如何处理多个异步操作的并行执行?
37. 什么是事件循环?如何优化 JavaScript 的异步性能?
答案: 事件循环是 JavaScript 处理异步操作的核心机制,通过任务队列协调代码执行。
优化策略:
- 减少宏任务数量:使用
requestIdleCallback或setTimeout(fn, 0)分片执行。 - 优先使用微任务:微任务优先于宏任务执行。
- 避免阻塞操作:将耗时计算移至 Web Worker。
纵向延伸:
- 微任务(
Promise.then)比宏任务(setTimeout)优先执行。 - 使用
performance.now()监控执行时间。
更深层次问题:
- 如何手动实现一个简易事件循环?
- 事件循环在 Node.js 中的差异?
38. 如何优化前端代码的加载速度?
答案: 优化策略:
- 代码分割:使用
Webpack的SplitChunks或import()动态加载。 - 懒加载:按需加载非关键资源(如图片、组件)。
- 压缩资源:使用
Terser压缩 JS,Babel转译。 - CDN 加速:将静态资源部署到 CDN。
- 预加载关键资源:通过
<link rel="preload">提前加载关键资源。
纵向延伸:
- 使用
Tree Shaking移除未使用代码。 - 预渲染(SSR)提升首屏加载速度。
更深层次问题:
- 如何通过 Webpack 实现按需加载?
Code Splitting与Lazy Loading的区别?
39. 如何减少 JavaScript 的内存占用?
答案: 优化策略:
- 避免全局变量:使用模块化开发(如
ES Modules)。 - 及时释放引用:将不再使用的对象设为
null。 - 使用 WeakMap/WeakSet:存储弱引用对象。
- 减少闭包使用:避免闭包导致的内存泄漏。
- 使用内存分析工具:通过
Chrome DevTools的Memory面板分析内存占用。
纵向延伸:
- 使用
Object.freeze冻结对象,减少内存开销。 - 避免频繁创建大对象,使用对象池。
更深层次问题:
- 如何检测内存泄漏?
WeakMap与Map的性能对比?
40. 如何优化 JavaScript 的执行性能?
答案: 优化策略:
- 减少 DOM 操作:批量操作 DOM,避免频繁重排。
- 使用防抖/节流:控制高频事件的触发频率。
- 避免不必要的计算:缓存计算结果。
- 使用原生方法:如
Array.map比for循环更高效。 - 使用 Web Worker:将计算密集型任务移至后台线程。
纵向延伸:
- 使用
requestAnimationFrame优化动画性能。 - 使用
IntersectionObserver替代scroll事件。
更深层次问题:
- 如何通过
Performance API分析性能瓶颈? - Web Worker 的通信机制?
六、框架与工程化
41. 什么是模块化?如何实现模块化开发?
答案: 模块化是将代码拆分为独立、可复用的模块,降低耦合度。
实现方式:
-
CommonJS(Node.js):
// module.js exports.add = (a, b) => a + b; // app.js const { add } = require('./module'); -
ES Modules:
// module.js export const add = (a, b) => a + b; // app.js import { add } from './module';
纵向延伸:
- 模块化支持代码复用、依赖管理、作用域隔离。
- 使用
Webpack、Vite等工具打包模块。
更深层次问题:
- CommonJS 与 ES Modules 的区别?
- 如何实现模块热更新(HMR)?
42. 什么是 Webpack?如何配置一个简单的 Webpack 项目?
答案: Webpack 是 JavaScript 模块打包工具,将代码、资源等打包为浏览器可执行的文件。
简单配置:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
};
纵向延伸:
- Webpack 支持代码分割、热更新、插件系统。
- 使用
webpack-dev-server实现开发服务器。
更深层次问题:
- Webpack 的
loader与plugin的区别? - 如何优化 Webpack 打包体积?
43. 什么是 Vite?它与 Webpack 的区别是什么?
答案: Vite 是新一代前端构建工具,利用原生 ES Modules(ESM)加速开发环境启动速度。
核心区别:
| 特性 | Vite | Webpack |
|---|---|---|
| 开发模式 | 基于 ESM,无需打包 | 基于打包,编译后运行 |
| 构建速度 | 快速启动,热更新即时 | 首次打包较慢,适合生产环境 |
| 生产构建 | 依赖 Rollup | 依赖 Webpack |
| 插件生态 | 插件较少,但与 Vue/React 集成 | 插件丰富,生态成熟 |
纵向延伸:
- Vite 适合现代前端项目(如 Vue 3、React 18)。
- Webpack 更适合复杂项目,支持代码分割、热更新等。
更深层次问题:
- Vite 的 SSR 实现原理?
- 如何结合 Vite 与 TypeScript?
44. 什么是前端工程化?它包含哪些核心内容?
答案: 前端工程化是通过标准化、自动化的方式提升开发效率和代码质量,核心内容包括:
- 模块化开发:代码拆分、依赖管理。
- 构建工具:Webpack、Vite 等。
- 版本控制:Git 流程、CI/CD。
- 代码规范:ESLint、Prettier。
- 性能优化:加载速度、内存管理。
- 测试:单元测试、E2E 测试。
纵向延伸:
- 工程化的目标是提高可维护性、协作效率和稳定性。
- 使用
Monorepo管理多项目。
更深层次问题:
- 如何制定团队的工程化规范?
- 工程化与架构设计的关系?
45. 如何实现前端项目的自动化测试?
答案: 测试类型:
- 单元测试:测试单个函数或组件。
- 集成测试:测试多个模块的交互。
- E2E 测试:模拟用户操作,测试完整流程。
工具:
- Jest:单元测试框架。
- Cypress:E2E 测试工具。
- Vitest:轻量级测试框架,适合 Vite 项目。
示例:
// sum.test.js (Jest)
test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
纵向延伸:
- 使用
Mock模拟 API 请求。 - 集成 CI/CD 自动运行测试。
更深层次问题:
- 如何编写高质量的测试用例?
- 测试覆盖率与实际质量的关系?
七、浏览器与安全
46. 浏览器的渲染流程是怎样的?如何优化渲染性能?
答案: 渲染流程:
- HTML 解析:生成 DOM 树。
- CSS 解析:生成 CSSOM 树。
- 构建 Render Tree:合并 DOM 和 CSSOM。
- 布局(Layout) :计算元素位置和尺寸。
- 绘制(Paint) :将像素绘制到屏幕。
- 合成(Composite) :将图层合成为最终图像。
优化策略:
- 减少重排:批量修改样式。
- 使用
will-change:提前声明元素变化。 - 避免过度绘制:减少不必要的背景色或透明度。
- 使用
transform和opacity:触发 GPU 加速。
纵向延伸:
- 使用
requestAnimationFrame控制动画帧率。 - 使用
Layer Compositing优化图层合成。
更深层次问题:
- 如何通过
Performance API分析渲染性能? GPU Acceleration的原理?
47. 什么是跨域?如何解决跨域问题?
答案: 跨域是浏览器出于安全限制,阻止不同源(协议、域名、端口)的请求。
解决方法:
-
CORS(推荐):
- 服务器设置
Access-Control-Allow-Origin。
- 服务器设置
-
JSONP(不推荐):
- 通过
<script>标签加载跨域数据。
- 通过
-
代理服务器:
- 在前端服务器设置反向代理(如 Nginx)。
-
WebSocket:
- 不受同源策略限制。
示例:
Access-Control-Allow-Origin: https://example.com
纵向延伸:
- CORS 支持复杂请求(如
POST)。 - 使用
CSP(内容安全策略)增强安全性。
更深层次问题:
- 如何通过
CORS实现带凭证的请求? - 跨域与 CSRF 的关系?
48. 什么是 XSS?如何防范 XSS 攻击?
答案: XSS(跨站脚本攻击) 是攻击者将恶意脚本注入网页,窃取用户数据或执行恶意操作。
防范方法:
-
输入过滤:对用户输入进行 HTML 转义。
-
输出编码:在渲染前对数据进行编码。
-
CSP(内容安全策略):
- 限制脚本来源。
-
使用安全库:如
DOMPurify清理 HTML。
示例:
// 转义 HTML
const escaped = escapeHTML(userInput);
document.getElementById('output').innerHTML = escaped;
纵向延伸:
- 使用
HttpOnly防止 Cookie 被窃取。 - 使用
SameSite防止 CSRF。
更深层次问题:
- XSS 与 CSRF 的区别?
- 如何通过 CSP 阻止内联脚本?
49. 什么是 CSRF?如何防范 CSRF 攻击?
答案: CSRF(跨站请求伪造) 是攻击者诱导用户执行非预期的操作(如转账、修改密码)。
防范方法:
-
CSRF Token:
- 服务器生成随机 Token,前端请求时携带。
-
SameSite Cookie:
- 设置
SameSite=Strict或Lax。
- 设置
-
验证请求头:
- 检查
Origin或Referer。
- 检查
示例:
Set-Cookie: csrfToken=abc123; SameSite=Strict
纵向延伸:
- 使用
Double Submit Cookie验证 Token。 - 使用
OAuth或JWT替代 Cookie。
更深层次问题:
- CSRF 与 XSS 的关联?
- 如何结合 JWT 防范 CSRF?
50. 什么是 HTTPS?它如何保障网络安全?
答案: HTTPS 是 HTTP over TLS/SSL,通过加密通信保障数据安全。
核心机制:
- 身份认证:通过数字证书验证服务器身份。
- 数据加密:使用对称加密传输数据。
- 完整性校验:使用哈希算法确保数据未被篡改。
优势:
- 防止中间人攻击。
- 防止数据窃听。
- 提升搜索引擎排名(SEO)。
纵向延伸:
- 使用
Let's Encrypt免费获取 SSL 证书。 - 使用
HSTS强制 HTTPS。
更深层次问题:
- TLS 1.3 与 TLS 1.2 的区别?
- HTTPS 如何影响前端性能?