标签

NodeJS

Node 是一个 Javascript 运行环境(runtime)。实际上它是对 Google V8 引擎(应用于 Google Chrome 浏览器)进行了封装。V8 引擎执行 Javascript 的速度非常快,性能非常好。Node 对一些特殊用例进行了优化,提供了替代的 API,使得 V8 在非浏览器环境下运行得更好。例如,在服务器环境中,处理二进制数据通常是必不可少的,但 Javascript 对此支持不足,因此,V8.Node 增加了 Buffer 类,方便并且高效地 处理二进制数据。因此,Node 不仅仅简单的使用了 V8,还对其进行了优化,使其在各环境下更加给力。

NodeJS
服务端5月29日 00:52
Cheerio 性能怎么优化?大文件和高并发场景怎么处理?Cheerio 性能优化抓住三个方向:选择器、内存、并发。选择器方面:用 `.find()` 配合具体 class 替代深层后代选择器,缓存 `$container` 后链式调用避免重复查询。内存方面:大文件用 stream 分块解析代替一次 load,批量 DOM 操作先拼字符串再一次性 `.html()` 插入,用完的 `$` 引用及时置空触发 GC。并发方面:多 URL 用 `Promise.all` 并行请求 + 逐个解析,超大数据集用 Worker 线程分片处理。load 选项中 `decodeEntities: false` 和 `withDomLvl1: false` 也能减少不必要的解析开销。 ## 追问 **为什么 .find() 比层级选择器快?** `$('.container .item .title')` 每次都从根节点全量匹配三层;`$('.container').find('.item').find('.title')` 先锁定容器再在子集中查找,搜索范围逐层缩小。差距在元素数量大时(万级以上)才明显。 **大文件怎么避免内存溢出?** 不要 `cheerio.load(wholeFile)`,改用 stream 按 `</item>` 等边界标签分割,每块单独 load 解析后立即释放。内存占用从 O(n) 降到 O(chunk),10MB 文件也不会爆。 **批量插入 DOM 为什么不能逐个 append?** 每次 `.append()` 都触发内部 DOM 树重建,1000 次就是 1000 次重建。正确做法是先用数组拼 HTML 字符串,最后 `.html(str)` 一次性写入,从 O(n) 次操作降到 1 次。 **load 选项哪些影响性能?** `decodeEntities: false` 跳过 HTML 实体解码(不需要中文转义时关闭);`withDomLvl1: false` 跳过 DOM Level 1 兼容处理;`normalizeWhitespace: false` 跳过空白合并。三个都关掉可提速 15-20%。 **多 URL 并发爬取怎么做?** 用 p-limit 或手动分批 `Promise.all`:`for (let i = 0; i < urls.length; i += 5)` 每批 5 个并发,避免同时发起数百请求被限流或打挂目标服务器。 ## 写段代码 ```javascript // 流式解析大文件,内存恒定 const results = []; let buf = ''; fs.createReadStream('big.html') .on('data', chunk => { buf += chunk; const matches = buf.match(/<item[\s\S]*?<\/item>/g) || []; matches.forEach(m => { const $ = cheerio.load(m); results.push($('name').text()); }); buf = buf.slice(buf.lastIndexOf('</item>') + 7); }); ```
服务端5月29日 00:51
如何开发 Cheerio 插件?有哪些实用插件模式?Cheerio 插件的核心模式是扩展 `cheerio.prototype`(即 `$.fn`),给选择器结果集添加自定义方法。基本写法:`module.exports = function(cheerio) { cheerio.prototype.myMethod = function() { return this; } }`,然后 `cheerio.use(pluginFn)` 加载。插件方法内部通过 `this` 访问当前选中的元素集合,用 `cheerio(el)` 包装后即可调用所有原生方法。返回 `this` 支持链式调用,返回 `.get()` 则输出普通数组。 ## 追问 **插件方法怎么访问当前选中的元素?** 方法内 `this` 就是 cheerio 对象,`this.each((i, el) => ...)` 遍历,`cheerio(el)` 包装单个元素。`this.length` 获取匹配数量,`this[i]` 直接取原生节点。 **怎么让插件支持配置参数?** 导出一个工厂函数而非直接函数:`module.exports = (options) => (cheerio) => { ... }`,调用时 `cheerio.use(myPlugin({ trim: true }))`。内部用默认参数合并:`const opts = { trim: true, ...options }`。 **有哪些实用的插件场景?** 最常见三类:①文本清洗——批量 removeTags/cleanText,去掉 script/style/注释;②结构化提取——tableToArray 把表格转二维数组,tableToObjects 按表头生成对象数组;③URL 标准化——resolveUrls 把相对路径转绝对路径,处理懒加载 data-src。 **插件方法和独立工具函数怎么选?** 需要操作 DOM 节点、支持链式调用、依赖当前选择器上下文时用插件;纯数据转换、不涉及 DOM 操作时用普通函数。插件的优势是和 cheerio API 风格统一,缺点是污染原型。 **开发插件要注意什么?** 方法名加业务前缀避免冲突(如 `seo_extractLinks` 而非 `extractLinks`);返回 `this` 保持链式;用 try-catch 包裹核心逻辑防止异常中断;在 peerDependencies 声明 cheerio 版本。 ## 写段代码 ```javascript // 表格转对象数组插件 module.exports = function(cheerio) { cheerio.prototype.tableToObjects = function() { const headers = this.find('th').map((i, el) => cheerio(el).text().trim()).get(); return this.find('tbody tr').map((i, tr) => { const obj = {}; cheerio(tr).find('td').each((j, td) => { obj[headers[j]] = cheerio(td).text().trim(); }); return obj; }).get(); }; }; ```
服务端5月29日 00:51
Cheerio 爬取动态页面为什么拿不到数据?怎么解决?Cheerio 只是 HTML 解析器,不执行 JavaScript,所以通过 JS 动态渲染的内容(SPA、AJAX 加载、无限滚动)用 Cheerio 直接解析会拿到空壳 HTML。解决方案有三条路径:1) 用 Puppeteer/Playwright 先渲染页面拿到完整 HTML 再交给 Cheerio 解析;2) 拦截网络请求直接找到数据 API 端点,用 axios 请求 JSON;3) 分析页面源码中的内联数据(如 window.__INITIAL_STATE__)直接提取。 ## 追问 **方案 2(直接请求 API)相比方案 1 有什么优势?** 速度快、资源消耗低、无需启动浏览器。缺点是 API 可能有签名/鉴权,需要逆向分析请求参数。 **Puppeteer 和 Playwright 选哪个?** Playwright API 更现代,原生支持多浏览器,自动等待机制更智能;Puppeteer 生态更成熟、Chrome 专项优化更好。新项目推荐 Playwright。 **如何优化 Puppeteer + Cheerio 方案的性能?** 拦截非必要资源(图片/字体/CSS)不加载,复用浏览器实例而非每次新建,设置合理的 waitForSelector 超时而非固定等待。 **怎么判断页面是动态渲染还是静态 HTML?** curl 获取 HTML 后检查目标元素是否存在,如果 curl 拿不到但浏览器能看到,就是动态渲染。 ## 写段代码 ```javascript const puppeteer = require('puppeteer'); const cheerio = require('cheerio'); async function scrape(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2' }); const $ = cheerio.load(await page.content()); await browser.close(); return $('.item').map((i, el) => $(el).text()).get(); } ```
服务端5月29日 00:51
Cheerio 有哪些 DOM 操作方法?修改和读取分别怎么用?Cheerio 的 DOM 操作分两大类:读取和修改。读取用 `.text()` `.html()` `.attr()` `.val()` 获取内容;修改用 `.append()` `.prepend()` `.after()` `.before()` 插入节点,`.remove()` `.empty()` 删除,`.replaceWith()` 替换。关键是区分内部插入(append/prepend,插入子节点)和外部插入(after/before,插入兄弟节点)。遍历用 `.find()` `.children()` `.parent()` `.closest()` `.siblings()` 在节点树中移动。 ## 追问 **append 和 after 有什么区别?** `.append()` 是内部插入,把新节点加为目标元素的最后一个子节点;`.after()` 是外部插入,把新节点放在目标元素后面作为兄弟节点。记忆方式:append 是往肚子里塞,after 是站在旁边。 **remove 和 empty 有什么区别?** `.remove()` 连元素本身一起删除;`.empty()` 只清空内容,元素标签保留。爬虫场景中 `.empty()` 常用于清空某个容器再重新填入清洗后的数据。 **怎么批量提取数据并转成数组?** 用 `.map()` + `.get()` 组合:`$('.item').map((i, el) => $(el).text()).get()`。`.map()` 返回 cheerio 对象,`.get()` 转为普通数组,否则无法用 Array 方法。 **.find() 和 .children() 有什么区别?** `.find()` 搜索所有后代元素(含孙子节点),`.children()` 只找直接子元素。性能上 `.children()` 更快,层级确定时优先使用。 **怎么克隆一个节点并插入到其他位置?** `.clone()` 深拷贝节点,返回独立的 cheerio 对象,可链式调用 `.appendTo()` 或 `.insertAfter()` 插入。注意 clone 不会复制 data 和事件绑定。 ## 写段代码 ```javascript const $ = cheerio.load(html); // 读取:提取所有文章标题和链接 const articles = $('.post').map((i, el) => ({ title: $(el).find('h2').text(), link: $(el).find('a').attr('href') })).get(); // 修改:给外链加 target 并移除广告 $('a[href^="http"]').attr('target', '_blank'); $('.ad').remove(); ```
服务端5月29日 00:50
Cheerio 使用中有哪些常见坑?怎么解决?最常见的五大坑:①中文乱码——需要用 iconv-lite 按实际编码解码,别依赖 axios 自动处理;②选择器找不到元素——先 `$.html()` 检查 HTML 是否完整加载,再逐步缩小选择器范围调试;③拿不到动态内容——Cheerio 不执行 JS,要么换 Puppeteer 要么直接调后端 API;④大文件内存溢出——分批处理或流式解析,别一次全 load 进来;⑤XML 解析报错——必须加 `{ xmlMode: true }` 选项,否则自闭合标签和命名空间都会出问题。 ## 追问 **中文乱码具体怎么处理?** axios 设置 `responseType: 'arraybuffer'` 拿到原始字节,先从 Content-Type 或 meta 标签检测编码,再用 `iconv.decode(buf, charset)` 转码。GBK 页面不转码必乱。 **选择器没匹配到元素,排查步骤是什么?** 第一步 `console.log($.html())` 看 HTML 是否完整;第二步从最外层选择器开始逐步缩小;第三步用 `:contains()` 按文本内容定位;第四步确认元素不在 iframe 或 shadow DOM 中。 **处理大文件怎么避免内存泄漏?** 分批 `.slice(i, i+batchSize)` 处理,每批处理完置空引用;更优方案是用 stream 按 `</item>` 分割,逐块 `cheerio.load` 解析,内存恒定。 **XML 模式和 HTML 模式有什么区别?** `xmlMode: true` 会保留大小写(HTML 模式全部转小写)、保留自闭合标签、不补全缺失的闭合标签。解析 RSS/SVG/配置文件必须开启,否则数据丢失。 **提取的文本满是空白字符怎么办?** `.text()` 结果链式调用 `.replace(/\s+/g, ' ').trim()`,或在 load 时加 `{ normalizeWhitespace: true }`,但后者会把换行也合并成空格,按需选择。 ## 写段代码 ```javascript // 正确处理 GBK 编码页面 const resp = await axios.get(url, { responseType: 'arraybuffer' }); const charset = resp.headers['content-type']?.match(/charset=([^;]+)/i)?.[1] || 'utf-8'; const html = charset.toLowerCase() === 'utf-8' ? resp.data.toString() : iconv.decode(Buffer.from(resp.data), charset); const $ = cheerio.load(html); ```
服务端5月29日 00:50
Cheerio 加载 HTML 有哪几种方式?核心方法是 cheerio.load(html, options),支持三种输入源:HTML 字符串、fs.readFileSync 读取的文件内容、Buffer。第二个参数控制解析行为:xmlMode 用于 XML 文档,decodeEntities 控制是否解码 HTML 实体(如 &amp; → &),withDomLvl1 决定是否遵循 DOM Level1 规范。对大文件可用流式读取拼接后再 load。 ## 追问 **xmlMode 什么时候必须开启?** 解析 XML/RSS/Atom feed 时必须开启,否则标签大小写会被统一转小写,XML 的自闭合标签也会被错误处理。 **decodeEntities 设为 false 有什么用?** 保留原始 HTML 实体不解码,适合需要原样输出 HTML 的场景,如内容编辑器。默认 true 会把 &amp; 转成 &。 **能直接加载 URL 吗?** 不能。Cheerio 是纯解析库,没有网络能力。需先用 axios/fetch 获取 HTML 字符串,再传入 load()。 **cheerio.load() 返回的 $ 是什么?** 返回一个函数,既可当选择器 $(selector) 使用,也挂载了 .html()、.xml() 等静态方法,本质是包装后的 DOM 操作入口。 **Buffer 输入和字符串输入有区别吗?** 功能一致,Buffer 会被内部自动转字符串。用 Buffer 可避免编码问题,尤其处理非 UTF-8 内容时更安全。 ## 写段代码 ```javascript const cheerio = require('cheerio'); const $ = cheerio.load('<root><item>值</item></root>', { xmlMode: true, decodeEntities: false }); console.log($('item').text()); // 值 ```
服务端5月29日 00:50
Cheerio 和 Puppeteer 有什么区别?爬虫场景怎么选?Cheerio 是纯 HTML 解析器,不执行 JavaScript,解析速度比 Puppeteer 快 100 倍以上,内存占用极低;Puppeteer 启动真实 Chromium,能执行 JS、处理动态渲染、模拟用户交互,但资源消耗大、速度慢。选择依据很简单:目标页面内容是否由 JS 动态生成——查看网页源码(Ctrl+U)能看到数据就用 Cheerio,看不到就用 Puppeteer。 ## 追问 **Puppeteer 能不能用 Cheerio 解析页面?** 可以,这是常见的混合模式:Puppeteer 负责加载动态页面拿到渲染后的 HTML,再传给 Cheerio 做数据提取,兼顾动态能力与解析效率。 **怎么判断一个网站是否需要 Puppeteer?** 右键查看网页源码,搜索目标数据。如果源码中没有,说明是 JS 动态加载,需要 Puppeteer;源码中已有,直接 Cheerio 即可。也可以用 `curl` 请求看返回的原始 HTML。 **Cheerio 能否处理需要登录的页面?** Cheerio 本身只做解析,登录请求通过 axios/fetch 带 cookie 发送即可,不需要 Puppeteer。只有登录过程涉及 JS 渲染或验证码交互时才需要浏览器。 **两者的资源消耗差异有多大?** Cheerio 解析一个页面通常 <10ms、内存 <50MB;Puppeteer 启动浏览器就要 500ms+、内存 100-300MB。批量爬取时差异是数量级的,100 个页面 Cheerio 几秒搞定,Puppeteer 可能要几分钟。 ## 写段代码 ```javascript // Puppeteer 拿动态页面 + Cheerio 解析 const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2' }); const html = await page.content(); await browser.close(); const $ = cheerio.load(html); $('.item').each((i, el) => { console.log($(el).text()); }); ```
前端5月28日 03:16
如何判断 JS 文件是 Node.js 环境还是浏览器环境?看三个层面:模块语法、全局对象、环境 API,基本够用。 **模块语法**是最直观的线索——用了 `require`/`module.exports` 的基本是 Node.js(CommonJS),但这不是充分条件,因为浏览器端打包工具也能处理 CJS。反过来,纯 `import`/`export`(ESM)两边都能跑,不能用来判断。 **全局对象**更可靠:访问 `process`、`__dirname`、`__filename`、`global` 的是 Node.js;访问 `window`、`document`、`navigator`、`localStorage` 的是浏览器。但要注意,SSR 框架(Next.js)里两者可能同时存在。 **环境 API** 是最终判据:调了 `fs`、`child_process`、`net`、`crypto`(非 Web Crypto 子集)等 Node 核心模块的只能在 Node 跑;用了 DOM API(`document.querySelector`、`addEventListener`)、`WebSocket`、`WebRTC` 的只能在浏览器跑。 一个实用的判断函数: ```js function detectEnv() { if (typeof process !== 'undefined' && process.versions?.node) return 'node'; if (typeof window !== 'undefined' && typeof document !== 'undefined') return 'browser'; return 'unknown'; } ``` 这个函数够用但不完美——Web Worker 里有 `self` 没有 `window`,Electron 里两个都有。 ## 追问 ### Webpack 的 target 配置和这个问题有什么关系? `target: 'node'` 时 Webpack 不会 polyfill `fs`/`path` 等 Node 模块,`target: 'web'` 时会。如果源码用了 Node API 但打包目标是浏览器,构建会报错或打出空模块。所以看 `webpack.config.js` 的 `target` 也能反推这个文件的预期运行环境。 ### TypeScript 怎么区分这两种环境的类型? `tsconfig.json` 里 `"lib": ["DOM"]` 会注入浏览器类型(`document`、`window`),不加就没有。`"types": ["node"]` 会注入 Node 类型(`process`、`__dirname`)。编译时 TS 就能帮你揪出混用的情况——比如在 `lib` 不含 `DOM` 的配置下写了 `document.getElementById`,会直接报类型错误。 ### 实际项目中踩过什么坑? Next.js 里最常见——组件里直接用 `window` 做判断,SSR 阶段 `window` 不存在就炸了。正确做法是把浏览器 API 调用放进 `useEffect` 或 `typeof window !== 'undefined'` 守卫里。另一个坑:库的 `package.json` 没配 `exports` 字段,Node 和浏览器拿到同一个入口文件,结果浏览器端 import 了 Node 模块直接白屏。
服务端5月27日 23:41
Cheerio 和 jsdom 有什么区别?如何选择使用?Cheerio 和 jsdom 是 Node.js 中处理 HTML 的两种常见方案,核心区别在于:Cheerio 是轻量解析器,只做 DOM 遍历和数据提取;jsdom 是完整浏览器环境模拟器,能执行 JavaScript 并提供 window、localStorage 等浏览器 API。选错工具会导致性能浪费或功能缺失,以下是关键对比。## 核心架构差异Cheerio 基于 htmlparser2 构建,实现了一套精简的 DOM 模型,仅保留节点遍历、属性读写和 CSS 选择器能力。它不解析 CSS、不执行脚本、不渲染页面,因此体积小、速度快。jsdom 基于 WHATWG DOM 标准实现,构造了完整的 `window` 对象,包括 `document`、`location`、`localStorage`、`fetch` 等 API。它内置 JavaScript 引擎,可通过 `runScripts: 'dangerously'` 执行页面脚本,还能加载外部资源。简单说:Cheerio 把 HTML 当字符串解析,jsdom 把 HTML 当浏览器渲染。## 功能与性能对比| 维度 | Cheerio | jsdom ||------|---------|-------|| 解析速度 | 快(约 5-10ms/万节点) | 慢(约 100-500ms/万节点) || 内存占用 | 低 | 高(约 8-10 倍) || CSS 选择器 | jQuery 风格,支持链式调用 | 标准 querySelector/querySelectorAll || JavaScript 执行 | 不支持 | 支持 || 浏览器 API | 无 | window/document/localStorage/fetch/Canvas || 事件系统 | 无 | 完整 DOM 事件冒泡机制 || HTML 容错性 | 高(htmlparser2 宽容解析) | 低(严格按标准解析) |Cheerio 的性能优势在批量处理时尤为明显。解析同一份万级节点的 HTML,Cheerio 通常比 jsdom 快 8 倍以上,内存占用低一个数量级。## 选择决策**选 Cheerio 的场景:**- 爬虫抓取静态页面数据(标题、链接、正文)- 批量处理 HTML 文档(清洗标签、提取字段)- 服务端模板渲染后的 HTML 后处理- Serverless 等资源受限环境**选 jsdom 的场景:**- 前端组件单元测试(模拟 DOM 环境)- 服务端渲染(SSR)需要执行客户端脚本- 处理依赖 JavaScript 动态渲染的页面- 需要浏览器 API 的 Node.js 代码(如 window.matchMedia)**一个实用判断标准:** 如果你只需要 `querySelector` + `textContent`,用 Cheerio;如果你需要 `window` 对象,用 jsdom。## 常见坑点**Cheerio 陷阱:** 静态抓取 SPA 页面会拿到空壳 HTML。此时需要搭配 Puppeteer 等无头浏览器先渲染,再用 Cheerio 解析结果,而非换用 jsdom——jsdom 执行 JS 的能力有限,对复杂 SPA 支持不完善。**jsdom 陷阱:** 默认不执行脚本(需手动开启 `runScripts`),且开启后存在安全风险,不要用 jsdom 执行不可信来源的 HTML。另外 jsdom 不支持 `requestAnimationFrame`、`IntersectionObserver` 等部分现代 API,Jest 等测试框架通常会补充 polyfill。## 代码示例Cheerio 快速提取数据:```javascriptconst cheerio = require('cheerio');const $ = cheerio.load(html);// jQuery 风格 APIconst title = $('h1').text();const links = $('a').map((i, el) => $(el).attr('href')).get();const cleaned = (() => { $('script, style').remove(); return $.html();})();```jsdom 模拟浏览器环境:```javascriptconst { JSDOM } = require('jsdom');const dom = new JSDOM(html, { runScripts: 'dangerously' });const document = dom.window.document;const title = document.querySelector('h1').textContent;// 访问浏览器 APIconst storage = dom.window.localStorage;const location = dom.window.location.href;```两者也可以组合使用:先用 Puppeteer 或 jsdom 获取 JS 执行后的 HTML,再用 Cheerio 高效提取数据。## 追问方向- **Cheerio 如何处理编码问题?** `cheerio.load(html, { decodeEntities: false })` 可避免中文乱码。- **jsdom 如何模拟用户交互?** 通过 `dom.window.dispatchEvent` 或 `fireEvent` 库触发事件。- **还有其他选择吗?** node-html-parser 更轻量,parse5 更标准,linkedom 性能介于 Cheerio 和 jsdom 之间。
服务端5月27日 20:26
什么是 Deno?它和 Node.js 有什么区别?## Deno 的核心设计理念 Deno 由 Node.js 创始人 Ryan Dahl 在 2018 年发起,动机是他公开承认 Node.js 有几个设计决策无法在不破坏兼容性的前提下修复:默认不安全的执行环境、混乱的 node_modules 机制、以及 CommonJS 与 ES Modules 并存的模块系统。Deno 从零开始,用 Rust 重写了底层,试图给出更干净的答案。 ## Deno 和 Node.js 的关键区别 **安全模型**:Deno 默认沙箱执行,脚本无法读写文件、访问网络或读取环境变量,必须通过 `--allow-read`、`--allow-net` 等标志显式授权。Node.js 默认完全信任脚本,没有权限墙。 **模块与包管理**:Deno 用 URL 直接导入模块,不依赖 package.json 和 node_modules。Deno 2.0 起 `npm:` 前缀已稳定支持,可以 `import express from "npm:express"` 直接使用 npm 包。Node.js 仍以 npm + package.json 为核心。 **TypeScript**:Deno 原生执行 .ts 文件,零配置。Node.js 22 虽已实验性支持,但生产环境仍需配置转译。 **API 风格**:Deno 全部采用 Promise/async-await,Node.js 保留了大量回调风格的旧 API。 **底层实现**:Deno 基于 Rust + V8,Node.js 基于 C++ + V8 + libuv。 ```bash # Deno 权限示例 deno run --allow-net --allow-read server.ts ``` ## 2026 年选型建议 - **选 Node.js**:已有大型项目、依赖 npm 生态深度、团队招聘池大 - **选 Deno**:新项目优先安全(如跑不可信代码)、边缘部署(Deno Deploy)、TypeScript-first - **考虑 Bun**:CI/CD 追求极致速度、高吞吐 HTTP 场景 ## 追问方向 - Deno 的权限模型能防范供应链攻击吗?-- 能限制恶意包的文件和网络访问,但 `--allow-all` 等于没有限制 - Deno 2.0 的 npm 兼容性有没有坑?-- 大部分主流包可用,但依赖原生 C++ 模块的包可能失败 - 为什么 Ryan Dahl 认为node_modules是个错误?-- 它导致幽灵依赖、磁盘浪费、安装慢,Deno 用全局缓存 + URL 导入替代
服务端5月27日 01:12
Node.js 如何开启多进程?进程之间如何通讯?Node.js 用 `child_process` 模块创建子进程,用 `cluster` 模块做多核利用: **child_process**: - `spawn(command, args)`:启动一个新进程,返回流(适合长时间运行、大量输出的进程) - `exec(command, callback)`:启动 shell 执行命令,缓存输出后回调(适合短命令) - `fork(modulePath)`:特殊 spawn,创建 Node.js 子进程,自带 IPC 通道 **cluster**:基于 `fork` 封装,能创建多个共享同一端口的 worker 进程(常见于 HTTP 服务利用多核)。 **进程通讯**:`fork` 创建的父子进程间有 IPC 通道,用 `process.send(msg)` 和 `process.on('message')` 通信。底层实现:libuv 管道(pipe)。 ## 追问 ### cluster 怎么实现多进程共享端口? 主进程监听端口,将接收到的连接通过 Round-Robin 分发给 worker 进程。worker 不直接监听端口,而是接收主进程分配的连接句柄。Linux 上也可用 `SO_REUSEPORT` 内核级别的分发。 ### fork 和 spawn 的区别? fork 是 spawn 的特殊版——专门 fork Node.js 进程,自动建立 IPC 通道。spawn 启动任何命令,流式处理输出,适合与外部程序交互。 ### PM2 的 cluster 模式和 fork 模式有什么区别? - cluster:PM2 用 Node.js cluster 模块,多实例共享端口,自动负载均衡 - fork:PM2 只是用 child_process.fork 启动多个实例,需要不同端口或用 Nginx 做反向代理
服务端2024年6月24日 16:43
nodejs 的优点和缺点?### Node.js 的优点 #### 1. 高性能 Node.js 使用 V8 引擎,这是 Google Chrome 的 JavaScript 运行时,它将 JavaScript 代码编译成机器代码。这意味着 Node.js 能够提供高性能的网络应用。由于其非阻塞 I/O 和事件驱动架构,Node.js 特别适合处理大量并发连接,这对于实时应用程序(如游戏、聊天服务)和高流量服务是非常有利的。 #### 2. 单一语言开发 使用 Node.js,开发人员可以使用 JavaScript 编写前端和后端代码。这简化了开发流程,因为只需掌握一种语言和一套代码库即可。这也有助于前后端的高效协作。 #### 3. 强大的生态系统 Node.js 有一个庞大的生态系统,npm(Node.js 包管理器)是世界上最大的软件注册表。开发人员可以轻松地找到和共享各种库和工具,这有助于加快开发速度并减少重复造轮子的需要。 #### 4. 易于学习 由于 JavaScript 是最受欢迎的编程语言之一,许多开发人员已经熟悉它。这使得 Node.js 相对容易学习,尤其是对于那些已经有 JavaScript 经验的前端开发人员。 #### 5. 跨平台 Node.js 可以在多种平台上运行,包括 Windows、macOS、Linux,甚至在 Docker 容器中也能良好运作,这使得它非常灵活。 ### Node.js 的缺点 #### 1. 单线程 虽然 Node.js 的单线程模型有助于处理高并发和简化开发,但它也意味着所有 I/O 密集型操作可能会阻塞事件循环,影响应用程序的整体性能。对于计算密集型任务,Node.js 可能不是最佳选择。 #### 2. 不稳定的API Node.js 核心 API 的频繁变动曾经是一个问题,尽管现在已经相对稳定了。但开发者仍然需要留意 API 变动对项目的影响。 #### 3. 异步编程模型 Node.js 大量依赖异步代码,虽然这有助于提高性能,但也可能导致回调地狱(callback hell),使得代码难以理解和维护。尽管现在有 Promise 和 async/await 这样的解决方案,但对于新手来说,异步编程仍然可能是一个挑战。 #### 4. 性能瓶颈 Node.js 的性能虽然在处理 I/O 密集型任务时很出色,但在 CPU 密集型任务上可能就不那么理想。虽然可以通过创建子进程等方式来缓解这个问题,但这增加了复杂性。 #### 5. 年轻的工具 尽管 npm 生态系统非常庞大,但一些库和工具相对于其他语言的生态系统而言可能还不够成熟。这可能意味着更多的漏洞和不稳定性。 #### 实例 以性能为例,LinkedIn 将其后端服务从 Ruby on Rails 迁移到 Node.js,据报道提升了应用程序的性能,并显著减少了服务器需求。这展示了 Node.js 处理大规模网络服务时的性能优势。
前端2024年6月24日 16:43
[Event Loop] 浏览器和nodejs事件循环有什么区别?在浏览器和Node.js中,事件循环是实现非阻塞I/O操作的核心机制,尽管它们在高层面上非常相似,但具体实现上有几个主要区别。以下是我将回顾的几点关键差异及其例子: ### 1. 任务源和处理方式 **浏览器:** 浏览器的事件循环主要处理来自Web API的任务,这些可以是DOM事件、Ajax回调、setTimeout等。它使用了宏任务(macro tasks)和微任务(micro tasks)的概念。宏任务包括script(整体代码)、setTimeout、setInterval和I/O,而微任务主要包括Promise.then、MutationObserver。在一个事件循环中,每次只会从宏任务队列中取出一个任务执行,然后执行所有可用的微任务。 **Node.js:** Node.js的事件循环由libuv库实现,包括了多个阶段,如timers、I/O callbacks、poll、check、close callbacks等。Node.js中处理任务更为复杂,各个阶段几乎都有自己的队列。timers阶段处理setTimeout和setInterval回调,poll阶段负责I/O事件回调,而setImmediate的回调会在check阶段执行。 **例子:** 在浏览器中,`Promise.resolve().then()`会在当前宏任务完成后立即执行,因为微任务总是在宏任务之后清空。 在Node.js中,由于事件循环的阶段性,可能会在执行微任务时插入其他类型的任务,例如,如果在I/O操作完成后添加了一个setImmediate,邑可能在当前阶段的微任务和下一阶段的微任务之间执行。 ### 2. 定时器的精度 **浏览器:** 浏览器的定时器(如setTimeout和setInterval)的精度相对较低,早期定时器至少有4ms的延迟(根据HTML5标准规定),而现代浏览器偶尔会有更高的延迟,以帮助减少后台标签页的能耗。 **Node.js:** Node.js定时器的精度通常更高,因为服务器端的环境对实时性和性能有更高的要求。Node.js的事件循环可以精确到毫秒。 **例子:** 在浏览器中设置 `setTimeout(fn, 1)`可能实际上在4ms后才执行回调,而在Node.js中,相同的设置会尽量接近1ms执行回调。 ### 3. 默认行为和扩展性 **浏览器:** 浏览器的事件循环通常是不可见和不可控制的,由浏览器内核管理。 **Node.js:** Node.js的事件循环可以通过C++插件和核心模块进行扩展,给开发者提供了更多控制。例如,使用libuv库,开发者能够接触到底层的事件循环机制。 **例子:** Node.js的开发者可以编写本地插件,通过直接与libuv交互来修改或增强事件循环的行为,而这在浏览器端是做不到的。 ### 4. 性能和优化 **浏览器:** 浏览器的事件循环是为了优化用户界面和用户互动设计的,因此,许多优化都是围绕用户体验和界面响应性进行的。 **Node.js:** Node.js的事件循环是针对I/O密集型操作进行优化的,特别是网络和文件系统操作。
服务端2024年6月24日 16:43
nodejs 如何使用 DllPlugin 动态链接库?Node.js 中的 `DllPlugin` 及相关的 `DllReferencePlugin` 主要是用于改善构建时间和实现代码分离,在 Webpack 构建过程中使用。`DllPlugin` 用来打包出一个个独立的动态链接库文件,而 `DllReferencePlugin` 则用于在主应用程序中引用这些动态链接库。 以下是使用 `DllPlugin` 的具体步骤: ### 步骤 1: 创建 DLL 文件 首先,你需要在项目中创建一个 webpack 配置文件专门用于构建 DLL。 ```javascript // webpack.dll.config.js const path = require('path'); const webpack = require('webpack'); module.exports = { entry: { vendor: ['lodash', 'react'] // 假设我们希望将 lodash 和 react 打包成一个 DLL }, output: { path: path.join(__dirname, 'dist'), filename: '[name].dll.js', // 输出的文件名 library: '[name]_library' // 全局变量名,其他模块会从此变量上获取到里面的模块 }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dist', '[name]-manifest.json'), name: '[name]_library' }) ] }; ``` 这个配置会将 `lodash` 和 `react` 打包成一个名为 `vendor.dll.js` 的文件,并生成一个 `vendor-manifest.json` 文件。 ### 步骤 2: 在主配置中引用 DLL 然后,在你的主 `webpack` 配置文件中,你需要使用 `DllReferencePlugin` 来引用上一步中生成的 `vendor-manifest.json` 文件。 ```javascript // webpack.config.js const path = require('path'); const webpack = require('webpack'); module.exports = { // ...你的其他配置 plugins: [ // ...你的其他插件 new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./dist/vendor-manifest.json') // 引入 DLL 的 manifest 文件 }) ], // ...其余配置 }; ``` ### 步骤 3: 在 HTML 中引入 DLL 文件 最后,你需要确保在应用程序加载前,先在 HTML 文件中引入这些 DLL 文件。例如: ```html <!DOCTYPE html> <html> <head> <title>My App</title> </head> <body> <script src="./dist/vendor.dll.js"></script> <!-- 接下来是你应用程序的其它脚本 --> </body> </html> ``` ### 使用例子 假设你有一个大型项目,每次构建都需要很长时间,因为第三方库例如 `React`、`Vue` 或 `Lodash` 并不经常更改,但它们每次都会被重新编译。通过使用 DLL,你可以将这些库预编译成静态资源,以便在开发过程中重复使用,从而减少了构建时需要处理的工作量,并加快了构建速度。 使用 DLL 时需要注意的是,当你更新了 DLL 中的依赖项时,你需要重新构建 DLL 文件。同时,应当确保在生产环境构建中不包含 DLL 的引用,或者确保 DLL 是最新的,以避免因为版本不一致带来的问题。 总的来说,`DllPlugin` 提高了开发效率,尤其是在大型项目和频繁构建的环境中,它可以显著减少构建时间并提升开发体验。