前端面试题-JavaScript高级篇

以下为JavaScript高级篇面试考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。高级JavaScript工程师的面试不再局限于API的使用或孤立的知识点,而是聚焦于对语言、引擎、生态以及软件工程思想的综合理解与掌控能力。

一、 V8引擎工作原理与垃圾回收 (GC)

理解JavaScript的执行环境是高级优化的前提。

V8引擎核心流程

  1. 解析 (Parsing) : V8将JavaScript源代码解析成抽象语法树 (AST)

  2. 解释 (Interpretation) : Ignition (V8的解释器) 将AST转换成字节码并执行。同时,Ignition会收集分析信息,用于后续的优化。

  3. 编译 (Compilation) : 对于被频繁执行的代码(热点代码),TurboFan (V8的优化编译器) 会介入,利用分析信息将字节码编译成高度优化的机器码,以提升执行效率。这个过程被称为JIT (Just-In-Time) 编译 。如果优化的假设失败(如函数参数类型改变),会进行去优化 (Deoptimization) ,回退到字节码执行。

垃圾回收 (Garbage Collection)

V8采用分代回收 (Generational Collection)的策略,将堆内存分为 新生代 (New Generation)老生代 (Old Generation)

新生代 (Scavenger算法)

  • 空间小,存活对象少。采用Scavenger算法,将空间一分为二(From-Space 和 To-Space)。
  • 回收时,将From-Space中的存活对象复制到To-Space,然后清空From-Space。最后,From-Space和To-Space角色互换。
  • 对象若经历多轮回收仍存活,则被**晋升 (Promotion)**到老生代。

老生代

  • 空间大,存活对象多。采用标记-清除算法。

  • 标记阶段: 从根对象(如全局对象)开始,遍历所有可达对象并打上标记。

  • 清除阶段: 清除非标记对象所占用的内存。

  • 整理阶段 : 为解决内存碎片化问题,在清除后,会将所有存活对象向一端移动,形成连续的内存空间。

代码示例 (导致内存泄漏的场景):

高级开发者需要能够识别并解释内存泄漏。闭包引用了已分离的DOM节点是典型案例。

js 复制代码
function createLeakingElement() {
  const container = document.getElementById('container');
  const detachedElement = document.createElement('div');
  detachedElement.textContent = 'This is a potentially leaking element.';
  container.appendChild(detachedElement);

  // 关键:一个外部可访问的函数,通过闭包持有了对 detachedElement 的引用
  const leakingClosure = function() {
    // 即使 detachedElement 从DOM树中移除,只要 leakingClosure 存在,
    // detachedElement 就不会被GC回收。
    console.log(detachedElement.textContent);
  };

  // 从DOM中移除元素
  container.removeChild(detachedElement);

  // 返回这个闭包
  return leakingClosure;
}

// globalLeaker 现在持有了对 detachedElement 的间接引用
// 即使它在DOM中已不可见,它依然存在于内存中
window.globalLeaker = createLeakingElement();

// 只要 window.globalLeaker 不被设为 null,这块内存就永远无法被回收

二、 事件循环 (Event Loop)

高级面试会深入到Node.js环境,考察对Event Loop各阶段的理解。

  • 浏览器 vs. Node.js: 两者模型相似,但Node.js的事件循环有更明确的阶段划分。

  • Node.js 事件循环的六个阶段:

    1. timers : 执行 setTimeout()setInterval() 的回调。
    2. pending callbacks: 执行上一轮循环中延迟到本轮执行的I/O回调。
    3. idle, prepare: 仅内部使用。
    4. poll : 核心阶段。检索新的I/O事件;执行与I/O相关的回调。如果队列不为空,会遍历执行;如果为空,会在此阻塞等待,直到有新的I/O事件或到达 timers 设定的阈值。
    5. check : 执行 setImmediate() 的回调。
    6. close callbacks : 执行如 socket.on('close', ...) 的回调。
  • process.nextTick() 与微任务 (Micro-task) :

    • process.nextTick() 有自己独立的队列,其优先级高于所有微任务。
    • 在一个阶段执行完毕后,事件循环会立即 清空 nextTick 队列,然后才清空微任务队列,之后才进入下一个阶段。

代码示例 (Node.js环境下):

js 复制代码
const fs = require('fs');

console.log('1. Script Start');

// Timers 阶段
setTimeout(() => {
  console.log('7. setTimeout');
}, 0);

// Check 阶段
setImmediate(() => {
  console.log('8. setImmediate');
});

// Micro-task
Promise.resolve().then(() => {
  console.log('5. Promise.then');
});

// process.nextTick 队列 (最高优先级)
process.nextTick(() => {
  console.log('4. process.nextTick');
});

// I/O 操作,其回调将在 Poll 阶段执行
fs.readFile(__filename, () => {
  console.log('6. I/O (readFile) callback');

  // I/O回调内部的调度
  setTimeout(() => console.log('11. I/O -> setTimeout'), 0);
  setImmediate(() => console.log('9. I/O -> setImmediate'));
  process.nextTick(() => console.log('10. I/O -> nextTick'));
});

console.log('2. Script End');
console.log('3. Poll phase may start here...');

// 理论输出顺序:
// 1. Script Start
// 2. Script End
// 3. Poll phase may start here...
// 4. process.nextTick
// 5. Promise.then
// 6. I/O (readFile) callback
// 10. I/O -> nextTick
// 9. I/O -> setImmediate
// 7. setTimeout
// 8. setImmediate
// 11. I/O -> setTimeout
// (注意:9, 7, 8, 11 的确切顺序可能因I/O耗时和系统调度而有细微变化,但基本规律如此)

三、 高级性能优化

Tree Shaking (摇树优化)

原理

  • 依赖ES Modules (import/export) 的静态结构,在编译时分析代码,移除未被实际引用的"死代码"(dead-code)。

实践

  • Webpack, Rollup等现代打包工具在生产模式下默认开启。开发者需保证代码遵循ESM规范,并避免有副作用的模块导入。

Code Splitting (代码分割)

目的

  • 将巨大的单体bundle分割成多个小块(chunks),按需加载,以减小首屏加载体积,提升用户体验。

策略

  1. 按路由分割: 每个页面或路由对应一个chunk。

  2. 按组件分割: 对于非首屏、或需要交互才出现的大型组件(如弹窗、图表)进行懒加载。

  3. 公共库分离 (Vendor Splitting) : 将不常变动的第三方库(如React, Lodash)打包成独立的vendor chunk,利用浏览器缓存。

利用浏览器渲染路径

  • 关键渲染路径 : 优化CSS加载(内联关键CSS)、减少阻塞渲染的脚本、使用 async/defer

  • 硬件加速 : 尽量使用 transformopacity 属性进行动画,它们能被提升到单独的合成层(Compositor Layer),由GPU处理,避免触发重排(Reflow)和重绘(Repaint)。

代码示例 (React中的代码分割)

jsx 复制代码
import React, { Suspense, lazy } from 'react';

// 使用 React.lazy 和动态 import() 来实现组件的懒加载
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));
const AnotherLazyComponent = lazy(() => import('./components/AnotherLazyComponent'));

function App() {
  const [showHeavy, setShowHeavy] = React.useState(false);

  return (
    <div>
      <h1>My App</h1>
      <button onClick={() => setShowHeavy(true)}>Load Heavy Component</button>

      {/* 
        Suspense 组件用于在懒加载组件下载和解析期间,显示一个fallback UI。
        只有当 showHeavy 为 true 时,浏览器才会去请求 HeavyComponent.js。
      */}
      <Suspense fallback={<div>Loading...</div>}>
        {showHeavy && <HeavyComponent />}
        
        {/* 假设这是另一个需要懒加载的组件 */}
        {/* <AnotherLazyComponent /> */}
      </Suspense>
    </div>
  );
}

四、 内存管理与诊断

内存泄漏的常见原因

  1. 意外的全局变量: 未经声明的变量被赋值,成为全局对象的属性。

  2. 遗忘的定时器或回调 : setInterval 未被清除,其回调函数及其闭包环境无法被回收。

  3. 分离的DOM节点引用: 如第一节的代码示例。

  4. 闭包的滥用: 闭包会使其外部函数的作用域持续存在,如果作用域中包含大量数据,则可能造成内存占用过高。

诊断工具 (Chrome DevTools)

  • Performance Monitor: 实时监控CPU使用率、JS堆大小、DOM节点数等。

  • Memory Tab:

    • Heap Snapshot (堆快照) : 拍摄堆内存的快照,用于分析对象分布、查找分离的DOM树、定位内存泄漏。
    • Allocation Instrumentation on Timeline: 记录内存分配的时间线,用于定位是哪个函数或操作导致了频繁的内存分配或内存激增。

代码示例 (遗忘的定时器):

js 复制代码
class PulsingDot {
  constructor() {
    this.size = 0;
    this.isGrowing = true;

    // 定时器通过闭包持有了对 this (PulsingDot实例) 的引用
    this.intervalId = setInterval(() => {
      if (this.isGrowing) {
        this.size += 1;
        if (this.size >= 10) this.isGrowing = false;
      } else {
        this.size -= 1;
        if (this.size <= 0) this.isGrowing = true;
      }
    }, 100);
  }

  // 必须提供一个销毁方法来清除定时器
  destroy() {
    clearInterval(this.intervalId);
    console.log('PulsingDot destroyed and interval cleared.');
  }
}

let dot = new PulsingDot();

// 假设在某个时间点,我们不再需要这个 dot 实例
dot = null;

// 问题:虽然 dot 变量被设为 null,但 PulsingDot 实例无法被回收,
// 因为 setInterval 的回调函数仍然持有对它的引用,定时器还在不停地运行。
// 正确做法:在销毁对象前,调用 dot.destroy()。

五、 软件设计模式

高级开发者应能将设计模式思想融入日常编码,以构建可维护、可扩展的系统。

  • 单例模式 (Singleton) : 确保一个类只有一个实例,并提供一个全局访问点。

  • 观察者模式 (Observer / Pub/Sub) : 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。

  • 工厂模式 (Factory) : 定义一个用于创建对象的接口,让子类决定实例化哪一个类。

  • 装饰器模式 (Decorator) : 动态地给一个对象添加一些额外的职责。

  • 代理模式 (Proxy) : 为其他对象提供一种代理以控制对这个对象的访问。

代码示例 (观察者模式/发布-订阅):

js 复制代码
class EventBus {
  constructor() {
    this.listeners = {};
  }

  // 订阅
  on(eventName, callback) {
    if (!this.listeners[eventName]) {
      this.listeners[eventName] = [];
    }
    this.listeners[eventName].push(callback);
  }

  // 取消订阅
  off(eventName, callback) {
    if (!this.listeners[eventName]) return;
    this.listeners[eventName] = this.listeners[eventName].filter(
      listener => listener !== callback
    );
  }

  // 发布
  emit(eventName, ...args) {
    if (!this.listeners[eventName]) return;
    this.listeners[eventName].forEach(listener => {
      try {
        listener(...args);
      } catch (e) {
        console.error(`Error in listener for event "${eventName}":`, e);
      }
    });
  }
}

// --- 使用场景 ---
const bus = new EventBus();

function onUserLogin(userData) {
  console.log('Analytics Service: User logged in', userData.name);
}

function updateNavbar(userData) {
  console.log('UI Service: Updating navbar for', userData.name);
}

bus.on('user:login', onUserLogin);
bus.on('user:login', updateNavbar);

// 某处登录成功后...
bus.emit('user:login', { id: 1, name: 'Mickey' });

// 用户退出时,可以取消订阅
// bus.off('user:login', onUserLogin);

六、 模块化与工程化

模块化方案演进

从IIFE、CommonJS (require/module.exports)、AMD (define/require) 到 ES Modules (import/export) 。高级开发者需理解它们的差异及适用场景。

构建工具

Webpack

一个强大的、高度可配置的模块打包器。核心概念:Entry , Output , Loaders (转换非JS模块), Plugins (执行更广泛的任务,如打包优化、资源管理), Mode

Vite

新一代前端构建工具。利用浏览器原生ESM支持,在开发环境下实现极速的冷启动和热更新 (HMR)。生产环境则使用Rollup进行打包。

Monorepo

在单一代码仓库中管理多个项目/包的策略。工具:Lerna, Nx, Turborepo。

代码示例 (一个基础的 webpack.config.js):

js 复制代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 模式:'development' 或 'production'
  mode: 'development',

  // 入口文件
  entry: './src/index.js',

  // 输出配置
  output: {
    filename: 'bundle.[contenthash].js', // contenthash 用于缓存优化
    path: path.resolve(__dirname, 'dist'),
    clean: true, // 在生成文件之前清空 output 目录
  },

  // 模块处理规则
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          // 使用 babel-loader 来转换 ES6+ 语法
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
          }
        }
      },
      {
        test: /.css$/,
        // loader 的执行顺序是从右到左
        use: ['style-loader', 'css-loader']
      }
    ]
  },

  // 插件配置
  plugins: [
    // 自动生成一个 HTML 文件,并注入打包后的 JS
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],

  // 开发服务器配置
  devServer: {
    static: './dist',
    hot: true,
  },
  
  // Source Map 配置,用于调试
  devtool: 'eval-source-map',
};

七、 Web安全

XSS (Cross-Site Scripting)

攻击者将恶意脚本注入到网页中,其他用户浏览时执行。

类型

  • 储存型(Stored XSS
  • 反射型(Reflected XSS
  • DOM型(DOM-based XSS)。

防御

  1. 绝不信任任何用户输入

  2. 输出编码/转义: 对用户输入的数据在渲染到页面前进行HTML实体转义。现代框架(如React, Vue)默认进行此操作。

  3. 使用 textContent 代替 innerHTML

  4. Content Security Policy (CSP) : 通过HTTP头,严格限制页面可以加载的资源来源。

CSRF (Cross-Site Request Forgery)

攻击者诱导已登录用户在不知情的情况下,向其已认证的Web应用发送一个伪造的请求(如转账、修改密码)。

防御

  1. Anti-CSRF Token: 服务器为每个用户会话生成一个随机Token,要求所有状态变更的请求(POST, PUT, DELETE)都必须携带此Token。

  2. SameSite Cookie 属性 : 将Cookie设置为 StrictLax,可以阻止浏览器在跨站请求中发送Cookie。SameSite=Strict 是最强的防御。

  3. 检查 Referer 头: 验证请求的来源,但此方法可被伪造。

代码示例 (XSS防御):

js 复制代码
const userInput = '<img src="invalid" onerror="alert('XSS Attack!')">';

// 错误的方式:直接使用 innerHTML
const vulnerableDiv = document.getElementById('vulnerable');
// vulnerableDiv.innerHTML = userInput; // 这将执行 onerror 中的恶意脚本

// 正确的方式:使用 textContent,浏览器会将其作为纯文本处理
const secureDiv = document.getElementById('secure');
secureDiv.textContent = userInput; // 页面将显示字符串 "<img..." 而非图片

八、 框架原理 (以React为例)

Virtual DOM (VDOM)

  • 一个以JavaScript对象形式存在的、对真实DOM的抽象表示。

  • 工作流:

graph TD A[状态变更] --> B(重新渲染生成新的VDOM) B --> C(新旧VDOM进行
Diffing差异比较) C --> D(计算出最小化的变更集); D --> E(将变更批量更新
到真实DOM);

Reconciliation (协调) 与 Diffing 算法

Diffing策略

  1. Tree Diff: 只对同层级的节点进行比较,跨层级的移动会视为节点的销毁和重建。

  2. Component Diff: 如果组件类型不同,直接销毁旧组件,创建新组件;如果类型相同,则更新其属性。

  3. Element Diff : 对于同层级的一组子节点,通过 key 属性进行优化。key 帮助React识别哪些元素是稳定的、哪些是新增或删除的,从而实现高效的移动和复用,而不是原地销毁重建。

代码示例 (key的重要性)

js 复制代码
// 场景:在一个列表的开头插入一个新元素

// --- 不推荐:使用 index 作为 key ---
// 当在开头插入 'grape' 时,列表变为 ['grape', 'apple', 'banana']
// React 看到:
// - key=0 的元素从 'apple' 变为 'grape' (更新)
// - key=1 的元素从 'banana' 变为 'apple' (更新)
// - 新增一个 key=2 的元素 'banana' (新增)
// 这导致了大量不必要的DOM更新。
const BadList = ({ items }) => (
  <ul>{items.map((item, index) => <li key={index}>{item}</li>)}</ul>
);

// --- 推荐:使用稳定且唯一的ID作为 key ---
// 当在开头插入 {id: 3, text: 'grape'} 时
// React 看到:
// - key=1 ('apple') 和 key=2 ('banana') 的元素仍然存在,只需移动位置
// - 新增一个 key=3 的元素 'grape'
// 这只会导致一次新增操作和两次移动操作,效率极高。
const GoodList = ({ items }) => (
  <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>
);

九、 TypeScript

核心价值

  • 为JavaScript带来静态类型系统
  • 在编译阶段发现潜在错误
  • 提升代码的可维护性、可读性和大型项目的健壮性。

高级类型

  • 泛型 (Generics) : 创建可重用的、类型安全的组件或函数。

  • 条件类型 (Conditional Types) : T extends U ? X : Y,使类型可以根据条件变化。

  • 映射类型 (Mapped Types) : [K in keyof T]: ...,基于一个现有类型创建新类型。

  • 工具类型 (Utility Types) : Partial<T>, Required<T>, Readonly<T>, Pick<T, K>, Omit<T, K> 等,对类型进行转换和操作。

代码示例 (泛型与条件类型)

ts 复制代码
// 泛型函数:确保输入和输出类型一致
function identity<T>(arg: T): T {
  return arg;
}
let output = identity<string>("myString"); // output 类型为 string

// 泛型接口
interface GenericRepository<T> {
  findById(id: number): Promise<T | null>;
  findAll(): Promise<T[]>;
  save(entity: T): Promise<T>;
}
// class UserRepository implements GenericRepository<User> { ... }

// 条件类型:从一个类型中提取特定类型的属性名
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

interface Part {
  id: number;
  name: string;
  updatePart(newName: string): void;
  getPartName(): string;
}

// Type 'FunctionNames' will be "updatePart" | "getPartName"
type FunctionNames = FunctionPropertyNames<Part>;

十、 Node.js与服务器端

Node.js不仅是"能在服务器上运行的JavaScript",更是一个基于事件驱动、非阻塞I/O模型的强大运行时环境。对其核心原理、生态系统及最佳实践的掌握,是构建高性能、高并发网络应用的基础。

1. Node.js 核心模型

事件驱动 & 非阻塞I/O

  • 这是Node.js性能的基石。与传统的每个请求独占一个线程的模型不同,Node.js在单个主线程上运行事件循环。

  • 当遇到I/O操作(如数据库查询、文件读写、网络请求)时,Node.js不会等待其完成,而是将操作和回调函数交给底层系统(如libuv库),然后继续处理事件队列中的其他事件。

  • 当I/O操作完成后,其回调函数会被放回事件队列,等待事件循环的下一次轮询来执行。

适用场景
  • 这种模型使得Node.js极其适合处理大量并发连接的I/O密集型应用,如实时聊天服务、API网关、微服务等。

但它天然不适合CPU密集型任务,因为长时间的计算会阻塞主线程,导致整个应用无响应。

2. Web框架:Express & Koa

虽然Node.js内置了http模块,但直接使用它来构建复杂的Web应用是繁琐且低效的。Web框架提供了路由、中间件、模板引擎集成等高级抽象。

Express

事实上的行业标准,以其稳定、灵活和庞大的社区生态而著称。其核心是中间件(Middleware) 概念------一系列按顺序处理请求的函数。

Koa

由Express原班人马打造,被视为下一代Node.js Web框架。Koa的核心是利用async/await语法,通过洋葱模型 (Onion Model) 来组织中间件,使得异步流程控制更为优雅和直观。

代码示例 (Express中间件与路由)

js 复制代码
const express = require('express');
const app = express();

// 1. 应用级中间件:日志记录器
// 这是一个简单的中间件,会记录每个请求的方法、URL和时间戳。
app.use((req, res, next) => {
  const now = new Date().toISOString();
  console.log(`[${now}] ${req.method} ${req.originalUrl}`);
  next(); // 关键:调用 next() 将控制权传递给下一个中间件或路由处理器
});

// 2. 内置中间件:用于解析JSON格式的请求体
app.use(express.json());

// 3. 路由处理器
app.get('/', (req, res) => {
  res.send('Welcome to the homepage!');
});

app.post('/api/users', (req, res) => {
  // req.body 是由 express.json() 中间件处理后得到的
  const newUser = req.body;
  console.log('Creating new user:', newUser);
  // ... 在这里执行数据库插入等操作 ...
  res.status(201).json({ id: Date.now(), ...newUser });
});

// 4. 错误处理中间件 (特殊的4个参数)
// 应该放在所有 app.use() 和路由调用的最后
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

3. Streams (流)

Stream是Node.js中处理流式数据的抽象接口。在处理大文件或大量网络数据时,一次性将所有数据读入内存是低效且危险的(可能导致内存溢出)。Stream允许我们以小块(chunks)的方式、边读取边处理数据。

四种基本类型

  1. Readable : 可供读取数据的流 (如 fs.createReadStream)。

  2. Writable : 可供写入数据的流 (如 fs.createWriteStream, http.ServerResponse)。

  3. Duplex : 既可读又可写的流 (如 net.Socket)。

  4. Transform : 在读写过程中可以修改或转换数据的Duplex流 (如 zlib.createGzip)。

pipe()方法

  • 是连接流的最简单方式,它会自动处理数据从Readable流到Writable流的传输,并能妥善处理背压 (Back-pressure) 问题(即写入速度跟不上读取速度时,自动暂停读取)。

代码示例 (高效的文件服务器):

以下代码使用流高效地提供一个大文件的下载,而无需将整个文件加载到服务器内存中。

js 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  // 假设我们要提供一个名为 'large-video.mp4' 的文件下载
  const filePath = path.join(__dirname, 'large-video.mp4');

  // 检查文件是否存在
  fs.stat(filePath, (err, stats) => {
    if (err) {
      res.writeHead(404, { 'Content-Type': 'text/plain' });
      return res.end('File not found.');
    }

    // 设置响应头
    res.writeHead(200, {
      'Content-Type': 'video/mp4',
      'Content-Length': stats.size
    });

    // 创建一个可读流
    const readStream = fs.createReadStream(filePath);

    // 关键:使用 pipe() 将文件流直接导入到HTTP响应流(res)
    // Node.js 会自动处理数据分块、发送以及背压控制。
    // 这是一种极其高效且内存友好的方式。
    readStream.pipe(res);

    // 监听错误事件
    readStream.on('error', (streamErr) => {
      console.error('Stream Error:', streamErr);
      res.end(); // 发生错误时关闭连接
    });
  });
});

server.listen(3000, () => {
  console.log('File server listening on port 3000');
});

4. Child Processes (子进程)

为了解决CPU密集型任务阻塞主线程的问题,Node.js提供了child_process模块,允许创建子进程来执行这些任务。

  • spawn() : 启动一个新进程,以流的方式进行I/O,适合处理大量数据。

  • exec() : 启动一个shell来执行命令,将stdout/stderr缓存起来,在进程结束时通过回调一次性返回。有大小限制,适合执行简单的shell命令。

  • fork() : spawn()的一个特殊变体,专门用于创建新的Node.js进程。父子进程之间会建立一个IPC (Inter-Process Communication) 通道,允许通过.send().on('message', ...)来收发消息。

代码示例 (使用fork处理CPU密集型计算):

假设我们需要进行一个耗时的斐波那契数列计算。

parent.js (主进程)

js 复制代码
const { fork } = require('child_process');

console.log('Main process started.');

const child = fork('./child.js'); // 启动子进程

const numberToCompute = 45; // 一个会导致显著计算耗时的数字

// 监听子进程发回的消息
child.on('message', (message) => {
  console.log(`Main process received result from child: Fibonacci(${numberToCompute}) = ${message.result}`);
});

// 向子进程发送任务
console.log(`Main process sending task to child: compute Fibonacci(${numberToCompute}).`);
child.send({ number: numberToCompute });

// 主进程可以继续执行其他任务,不会被计算阻塞
console.log('Main process continues to do other work...');

child.js (子进程)

js 复制代码
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 监听父进程发来的消息
process.on('message', (message) => {
  console.log('Child process received task from parent:', message);
  const result = fibonacci(message.number);

  // 计算完成后,通过IPC通道将结果发送回父进程
  process.send({ result });

  // 子进程完成任务后可以自行退出
  process.exit();
});

通过这种方式,Node.js应用能够充分利用多核CPU资源,将计算密集型任务 offload 到子进程,同时保持主线程的响应性,以处理高并发的I/O请求。

结语

以上是JS高级篇面试考察点的内容,如有错误欢迎评论区指正。

相关推荐
爱吃无爪鱼44 分钟前
07-常用的前端开发组合(技术栈):配方大全
前端·vue.js·前端框架·npm·node.js·sass
cliffordl1 小时前
Web 自动化测试(Playwright)
前端·python
慧慧吖@1 小时前
前端无限列表
前端
好奇的候选人面向对象1 小时前
实现一个左右树形结构一对一关联的组件。这个方案使用两个el-tree组件,并实现它们之间的互相关联选择。
javascript·vue.js·elementui
Lovely Ruby1 小时前
前端er Go-Frame 的学习笔记:实现 to-do 功能(二)
前端·学习·golang
苏打水com1 小时前
第三篇:Day7-9 响应式布局+JS DOM进阶——实现“多端兼容+动态数据渲染”(对标职场“移动端适配”核心需求)
前端·css·html·js
一 乐1 小时前
旅游出行|基于Springboot+Vue的旅游出行管理系统设计与实现(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·旅游
玩具猴_wjh1 小时前
快手(安全方向)面试准备
安全·面试·职场和发展
想睡好1 小时前
元素的显示和隐藏 html5和css3的一些新特性
前端·css3·html5