JavaScript 高频知识点五十条

934 阅读8分钟

一、JavaScript 基础

1. varletconst 的区别是什么?

答案

  • var:函数作用域,存在变量提升,可重复声明。
  • let:块级作用域,不存在变量提升,不可重复声明。
  • const:块级作用域,声明后不可重新赋值(引用类型内部仍可变)。

纵向延伸

  • let 和 const 不会创建全局对象属性(在浏览器中为 window)。
  • 使用 const 更推荐用于不变的变量,增强代码可读性与安全性。

更深层次问题

  • 在 for 循环中使用 var 和 let 会导致什么不同?
  • 如何手动实现一个类似 let 的块级作用域?

2. JavaScript 中的原始类型有哪些?

答案: 原始类型包括:

  • number
  • string
  • boolean
  • null
  • undefined
  • symbol(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 引擎会执行以下步骤:

  1. 创建一个空对象;
  2. 将构造函数的作用域赋给这个新对象(即 this 指向该对象);
  3. 执行构造函数中的代码(为对象添加属性);
  4. 如果构造函数返回的是一个对象,则返回该对象;否则返回新对象。

纵向延伸

  • 构造函数通常首字母大写,表示应配合 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) 处理异步操作。

异步流程如下:

  1. 调用栈为空时,从回调队列中取出任务执行。
  2. 宏任务(如 setTimeout)和微任务(如 Promise.then)分别排队。

纵向延伸

  • 微任务优先于宏任务执行。
  • 使用 async/await 可以让异步代码更像同步。

更深层次问题

  • 为什么 Promise.resolve().then() 会比 setTimeout(fn, 0) 先执行?
  • 如何手动实现一个简易版的事件循环?

10. 什么是事件循环?请解释宏任务和微任务的区别。

答案: 事件循环是 JavaScript 异步执行的核心机制,负责协调代码执行、收集和分派事件。

  • 宏任务(Macrotask) :如 setTimeoutsetIntervalI/OrequestAnimationFrame
  • 微任务(Microtask) :如 Promise.then/catch/finallyqueueMicrotaskMutationObserver

纵向延伸

  • 微任务总是优先于下一个宏任务执行。
  • 微任务队列清空后才进入下一个宏任务阶段。

更深层次问题

  • 为什么 Promise.resolve().then() 比 setTimeout(fn, 0) 更快?
  • 如何利用微任务优化 UI 渲染?

二、TypeScript 基础

11. TypeScript 是什么?它解决了什么问题?

答案: TypeScript 是 JavaScript 的超集,提供了静态类型检查、面向对象特性、装饰器等功能。它解决了 JS 缺乏类型系统的痛点,提升了大型项目的可维护性和协作效率。

纵向延伸

  • 支持编译到任意版本的 JavaScript。
  • 提供更好的 IDE 支持(如自动补全、错误提示)。

更深层次问题

  • TypeScript 编译后的代码是否会影响运行性能?
  • 如何配置 TypeScript 项目?

12. interface 和 type 的区别是什么?

答案

特性interfacetype
扩展方式使用 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 有三种状态:

  1. pending(等待中)
  2. fulfilled(已成功)
  3. 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

答案

特性MapObject
键类型任意类型(包括对象、Symbol仅字符串或 Symbol
可迭代性默认可迭代需配合 Object.keys() 等方法
内存管理更高效的键值对存储属性过多时性能下降
方法丰富性提供 sizeclear() 等方法需手动实现相关功能

纵向延伸

  • 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)。
  • 支持拦截多种操作,如 deletePropertyhas 等。

更深层次问题

  • Proxy 与 Object.defineProperty 的区别?
  • 如何处理嵌套对象的响应式?

27. TypeScript 中的 PartialRequiredReadonly 类型有什么作用?

答案

  • 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; }

纵向延伸

  • 这些类型常用于表单验证、接口定义等场景。
  • 结合 PickOmit 可灵活控制属性。

更深层次问题

  • 如何自定义类似 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];
};

纵向延伸

  • 映射类型包括 PartialRequiredPickOmit 等。
  • 支持联合类型的映射。

更深层次问题

  • 如何实现 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];
};

纵向延伸

  • 映射类型支持 requiredreadonly? 等修饰符。
  • 可结合条件类型实现复杂逻辑。

更深层次问题

  • 如何实现 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. 如何优化前端代码的加载速度?

答案优化策略

  1. 代码分割:使用 Webpack 的 SplitChunks 或 import() 动态加载。
  2. 懒加载:按需加载非关键资源(如图片、组件)。
  3. 压缩资源:使用 Terser 压缩 JS,Babel 转译。
  4. CDN 加速:将静态资源部署到 CDN。
  5. 预加载关键资源:通过 <link rel="preload"> 提前加载关键资源。

纵向延伸

  • 使用 Tree Shaking 移除未使用代码。
  • 预渲染(SSR)提升首屏加载速度。

更深层次问题

  • 如何通过 Webpack 实现按需加载?
  • Code Splitting 与 Lazy Loading 的区别?

39. 如何减少 JavaScript 的内存占用?

答案优化策略

  1. 避免全局变量:使用模块化开发(如 ES Modules)。
  2. 及时释放引用:将不再使用的对象设为 null
  3. 使用 WeakMap/WeakSet:存储弱引用对象。
  4. 减少闭包使用:避免闭包导致的内存泄漏。
  5. 使用内存分析工具:通过 Chrome DevTools 的 Memory 面板分析内存占用。

纵向延伸

  • 使用 Object.freeze 冻结对象,减少内存开销。
  • 避免频繁创建大对象,使用对象池。

更深层次问题

  • 如何检测内存泄漏?
  • WeakMap 与 Map 的性能对比?

40. 如何优化 JavaScript 的执行性能?

答案优化策略

  1. 减少 DOM 操作:批量操作 DOM,避免频繁重排。
  2. 使用防抖/节流:控制高频事件的触发频率。
  3. 避免不必要的计算:缓存计算结果。
  4. 使用原生方法:如 Array.map 比 for 循环更高效。
  5. 使用 Web Worker:将计算密集型任务移至后台线程。

纵向延伸

  • 使用 requestAnimationFrame 优化动画性能。
  • 使用 IntersectionObserver 替代 scroll 事件。

更深层次问题

  • 如何通过 Performance API 分析性能瓶颈?
  • Web Worker 的通信机制?

六、框架与工程化

41. 什么是模块化?如何实现模块化开发?

答案: 模块化是将代码拆分为独立、可复用的模块,降低耦合度。

实现方式

  1. CommonJS(Node.js):

    // module.js
    exports.add = (a, b) => a + b;
    // app.js
    const { add } = require('./module');
    
  2. ES Modules

    // module.js
    export const add = (a, b) => a + b;
    // app.js
    import { add } from './module';
    

纵向延伸

  • 模块化支持代码复用、依赖管理、作用域隔离。
  • 使用 WebpackVite 等工具打包模块。

更深层次问题

  • 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)加速开发环境启动速度。

核心区别

特性ViteWebpack
开发模式基于 ESM,无需打包基于打包,编译后运行
构建速度快速启动,热更新即时首次打包较慢,适合生产环境
生产构建依赖 Rollup依赖 Webpack
插件生态插件较少,但与 Vue/React 集成插件丰富,生态成熟

纵向延伸

  • Vite 适合现代前端项目(如 Vue 3、React 18)。
  • Webpack 更适合复杂项目,支持代码分割、热更新等。

更深层次问题

  • Vite 的 SSR 实现原理?
  • 如何结合 Vite 与 TypeScript?

44. 什么是前端工程化?它包含哪些核心内容?

答案: 前端工程化是通过标准化、自动化的方式提升开发效率和代码质量,核心内容包括:

  1. 模块化开发:代码拆分、依赖管理。
  2. 构建工具:Webpack、Vite 等。
  3. 版本控制:Git 流程、CI/CD。
  4. 代码规范:ESLint、Prettier。
  5. 性能优化:加载速度、内存管理。
  6. 测试:单元测试、E2E 测试。

纵向延伸

  • 工程化的目标是提高可维护性、协作效率和稳定性。
  • 使用 Monorepo 管理多项目。

更深层次问题

  • 如何制定团队的工程化规范?
  • 工程化与架构设计的关系?

45. 如何实现前端项目的自动化测试?

答案测试类型

  1. 单元测试:测试单个函数或组件。
  2. 集成测试:测试多个模块的交互。
  3. 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. 浏览器的渲染流程是怎样的?如何优化渲染性能?

答案渲染流程

  1. HTML 解析:生成 DOM 树。
  2. CSS 解析:生成 CSSOM 树。
  3. 构建 Render Tree:合并 DOM 和 CSSOM。
  4. 布局(Layout) :计算元素位置和尺寸。
  5. 绘制(Paint) :将像素绘制到屏幕。
  6. 合成(Composite) :将图层合成为最终图像。

优化策略

  • 减少重排:批量修改样式。
  • 使用 will-change:提前声明元素变化。
  • 避免过度绘制:减少不必要的背景色或透明度。
  • 使用 transform 和 opacity:触发 GPU 加速。

纵向延伸

  • 使用 requestAnimationFrame 控制动画帧率。
  • 使用 Layer Compositing 优化图层合成。

更深层次问题

  • 如何通过 Performance API 分析渲染性能?
  • GPU Acceleration 的原理?

47. 什么是跨域?如何解决跨域问题?

答案: 跨域是浏览器出于安全限制,阻止不同源(协议、域名、端口)的请求。

解决方法

  1. CORS(推荐):

    • 服务器设置 Access-Control-Allow-Origin
  2. JSONP(不推荐):

    • 通过 <script> 标签加载跨域数据。
  3. 代理服务器

    • 在前端服务器设置反向代理(如 Nginx)。
  4. WebSocket

    • 不受同源策略限制。

示例

Access-Control-Allow-Origin: https://example.com

纵向延伸

  • CORS 支持复杂请求(如 POST)。
  • 使用 CSP(内容安全策略)增强安全性。

更深层次问题

  • 如何通过 CORS 实现带凭证的请求?
  • 跨域与 CSRF 的关系?

48. 什么是 XSS?如何防范 XSS 攻击?

答案XSS(跨站脚本攻击) 是攻击者将恶意脚本注入网页,窃取用户数据或执行恶意操作。

防范方法

  1. 输入过滤:对用户输入进行 HTML 转义。

  2. 输出编码:在渲染前对数据进行编码。

  3. CSP(内容安全策略):

    • 限制脚本来源。
  4. 使用安全库:如 DOMPurify 清理 HTML。

示例

// 转义 HTML
const escaped = escapeHTML(userInput);
document.getElementById('output').innerHTML = escaped;

纵向延伸

  • 使用 HttpOnly 防止 Cookie 被窃取。
  • 使用 SameSite 防止 CSRF。

更深层次问题

  • XSS 与 CSRF 的区别?
  • 如何通过 CSP 阻止内联脚本?

49. 什么是 CSRF?如何防范 CSRF 攻击?

答案CSRF(跨站请求伪造) 是攻击者诱导用户执行非预期的操作(如转账、修改密码)。

防范方法

  1. CSRF Token

    • 服务器生成随机 Token,前端请求时携带。
  2. SameSite Cookie

    • 设置 SameSite=Strict 或 Lax
  3. 验证请求头

    • 检查 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,通过加密通信保障数据安全。

核心机制

  1. 身份认证:通过数字证书验证服务器身份。
  2. 数据加密:使用对称加密传输数据。
  3. 完整性校验:使用哈希算法确保数据未被篡改。

优势

  • 防止中间人攻击。
  • 防止数据窃听。
  • 提升搜索引擎排名(SEO)。

纵向延伸

  • 使用 Let's Encrypt 免费获取 SSL 证书。
  • 使用 HSTS 强制 HTTPS。

更深层次问题

  • TLS 1.3 与 TLS 1.2 的区别?
  • HTTPS 如何影响前端性能?