diff --git a/source/_posts/front-end/nodejs-mock-interview.md b/source/_posts/front-end/nodejs-mock-interview.md index 8fd2b96a..c023801c 100644 --- a/source/_posts/front-end/nodejs-mock-interview.md +++ b/source/_posts/front-end/nodejs-mock-interview.md @@ -1,5 +1,5 @@ --- -title: nodejsd的模拟面试 +title: nodejs的模拟面试 date: 2024-10-29 20:11:28 tags: [nodejs, 面试] categories: [前端] @@ -12,17 +12,16 @@ CommonJS(简称CJS)是一种JavaScript模块化规范,最初为在服务 1. **模块导出和导入**:CommonJS使用`module.exports`导出内容,其他文件使用`require`导入。例如: ```javascript - // 导出模块 - // math.js - module.exports = { - add: (a, b) => a + b, - subtract: (a, b) => a - b, - }; - - // 导入模块 - const math = require('./math'); - console.log(math.add(2, 3)); // 输出5 +// 导出模块 +// math.js +module.exports = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, +}; +// 导入模块 +const math = require("./math"); +console.log(math.add(2, 3)); // 输出5 ``` 2. **同步加载**:CommonJS模块是同步加载的,这在服务端环境中是合理的,因为文件系统通常是本地的,读取速度快。但在浏览器端不适用,因为网络加载是异步的,CommonJS因此不适用于前端模块化。 @@ -36,52 +35,59 @@ CommonJS(简称CJS)是一种JavaScript模块化规范,最初为在服务 在CommonJS中,`require`的实现是通过Node.js的`Module`系统来管理和加载模块的。`require`本质上是一个函数,用来加载模块、解析依赖、缓存已加载的模块,从而确保模块的高效加载。下面是`require`的实现原理分解: ### 1. **路径解析** - - `require`首先会解析模块的路径,以确定该路径对应的文件位置。 - - 如果是核心模块(如`fs`、`path`等),Node.js会直接加载这些模块,因为它们内置在Node.js中。 - - 如果是自定义模块或第三方库,Node.js会在`node_modules`目录中查找指定模块。 - - Node.js的路径解析规则是先查找本地文件,然后查找`node_modules`,并遵循目录层级查找(从当前目录逐层往上)。 + +- `require`首先会解析模块的路径,以确定该路径对应的文件位置。 +- 如果是核心模块(如`fs`、`path`等),Node.js会直接加载这些模块,因为它们内置在Node.js中。 +- 如果是自定义模块或第三方库,Node.js会在`node_modules`目录中查找指定模块。 +- Node.js的路径解析规则是先查找本地文件,然后查找`node_modules`,并遵循目录层级查找(从当前目录逐层往上)。 ### 2. **模块缓存** - - Node.js会缓存已经加载的模块,保存在`require.cache`对象中。缓存的模块是一个`Module`对象,其`exports`属性包含模块的导出内容。 - - 如果模块已经加载并存在于缓存中,`require`会直接从缓存返回导出的内容,避免重复加载。 + +- Node.js会缓存已经加载的模块,保存在`require.cache`对象中。缓存的模块是一个`Module`对象,其`exports`属性包含模块的导出内容。 +- 如果模块已经加载并存在于缓存中,`require`会直接从缓存返回导出的内容,避免重复加载。 ### 3. **创建Module对象** - - 如果模块没有被缓存,Node.js会为该模块创建一个新的`Module`对象: - ```javascript - const module = new Module(filename); - ``` - - 这个`Module`对象包含`id`、`filename`、`loaded`和`exports`等属性,用来表示模块的唯一标识、文件路径、加载状态、导出对象等。 + +- 如果模块没有被缓存,Node.js会为该模块创建一个新的`Module`对象: + ```javascript + const module = new Module(filename); + ``` +- 这个`Module`对象包含`id`、`filename`、`loaded`和`exports`等属性,用来表示模块的唯一标识、文件路径、加载状态、导出对象等。 ### 4. **加载模块并执行** - - `require`会读取模块文件内容,然后将代码包裹在一个自执行函数中,这个函数接收`exports`、`require`、`module`、`__filename`和`__dirname`五个参数,从而确保每个模块都有自己的作用域。 - - 例如,假设模块代码是: - ```javascript - module.exports = { - add: (a, b) => a + b - }; - ``` - 加载时,Node.js会将其转换为如下结构: - ```javascript - (function(exports, require, module, __filename, __dirname) { - module.exports = { - add: (a, b) => a + b - }; - }); - ``` - - `require`执行该函数,将执行结果赋值给`module.exports`。执行完后,`module.exports`就包含了模块的导出内容。 + +- `require`会读取模块文件内容,然后将代码包裹在一个自执行函数中,这个函数接收`exports`、`require`、`module`、`__filename`和`__dirname`五个参数,从而确保每个模块都有自己的作用域。 +- 例如,假设模块代码是: + ```javascript + module.exports = { + add: (a, b) => a + b, + }; + ``` + 加载时,Node.js会将其转换为如下结构: + ```javascript + (function (exports, require, module, __filename, __dirname) { + module.exports = { + add: (a, b) => a + b, + }; + }); + ``` +- `require`执行该函数,将执行结果赋值给`module.exports`。执行完后,`module.exports`就包含了模块的导出内容。 ### 5. **返回module.exports** - - 执行完成后,`require`函数返回`module.exports`的内容。其他模块可以通过调用`require`访问该模块的导出对象。 + +- 执行完成后,`require`函数返回`module.exports`的内容。其他模块可以通过调用`require`访问该模块的导出对象。 ### 6. **循环依赖处理** - - 如果模块A和模块B存在循环依赖,Node.js会在加载时创建一个未完成的`module.exports`对象放入缓存,使得相互依赖的模块可以访问未完成的内容,从而避免死循环。 + +- 如果模块A和模块B存在循环依赖,Node.js会在加载时创建一个未完成的`module.exports`对象放入缓存,使得相互依赖的模块可以访问未完成的内容,从而避免死循环。 ### 简化版代码示例 + 以下代码是Node.js中`require`实现的简化示例: ```javascript // 简化的 CommonJS require 实现 -const Module = function(filename) { +const Module = function (filename) { this.filename = filename; this.exports = {}; // 导出对象 this.loaded = false; // 加载状态 @@ -89,7 +95,7 @@ const Module = function(filename) { Module._cache = {}; // 模块缓存 -Module._load = function(filename) { +Module._load = function (filename) { // 检查缓存 if (Module._cache[filename]) { return Module._cache[filename].exports; @@ -102,7 +108,7 @@ Module._load = function(filename) { // 读取文件内容并包装为函数 const wrappedSrc = `(function(exports, require, module, __filename, __dirname) { ${readFileSync(filename)} \n})`; const compiledWrapper = eval(wrappedSrc); - + // 执行模块函数 compiledWrapper(module.exports, require, module, filename, dirname(filename)); @@ -118,7 +124,9 @@ function require(filename) { ``` ### 总结 + `require`的核心在于以下几步: + 1. **解析路径**以找到模块文件。 2. **检查缓存**以避免重复加载。 3. **创建并缓存模块对象**,防止循环依赖。 @@ -128,49 +136,60 @@ function require(filename) { 这就是CommonJS中`require`的基本实现逻辑,通过缓存、作用域隔离等机制,确保模块的高效、独立运行。 ## CommonJS和ESM的区别: + - **导入/导出语法**:CommonJS使用`require`和`module.exports`,而ESM使用`import`和`export`。 - **加载方式**:CommonJS是同步加载,ESM支持异步加载。 - **执行时机**:CommonJS模块在加载时立即执行,ESM模块则在编译时静态分析依赖关系。 总体来说,CommonJS是Node.js中常见的模块化规范,对服务端开发尤其重要,而在前端开发中更推荐使用ES模块化(ESM)标准。 - ## ESM是什么 ESM(ECMAScript Module)是JavaScript在ECMAScript 6(ES6)标准中引入的模块系统,也称为ES模块。它为JavaScript提供了一种官方、标准化的模块化方式,在浏览器和Node.js环境中都支持。ESM解决了JavaScript早期模块化规范(如CommonJS、AMD)存在的诸多问题,并引入了一些重要的新特性。 ### ESM的特点和语法 + 1. **静态导入和导出** + - ESM支持静态分析,这意味着在编译阶段,JavaScript引擎就能确定模块依赖关系,而不是像CommonJS那样动态加载。这种静态结构有助于性能优化和代码检查。 - **导出**使用`export`关键字,支持命名导出和默认导出: + ```javascript // named export export const add = (a, b) => a + b; - export function subtract(a, b) { return a - b; } + export function subtract(a, b) { + return a - b; + } // default export - export default function multiply(a, b) { return a * b; } + export default function multiply(a, b) { + return a * b; + } ``` + - **导入**使用`import`关键字,可以选择导入模块的部分内容,或者直接导入默认导出: ```javascript - import { add, subtract } from './math.js'; - import multiply from './math.js'; + import { add, subtract } from "./math.js"; + import multiply from "./math.js"; ``` 2. **支持异步加载** + - 在浏览器环境中,ES模块可以异步加载,且在模块中可以使用`