在Node.js里运行依赖浏览器能力的代码时,由于缺少window、document等全局对象,往往会抛出未定义错误。为了进行单元测试、服务端渲染或简单爬虫,我们需要在Node.js进程中模拟出浏览器环境。

使用jsdom模拟DOM环境
jsdom是最常用的纯JavaScript实现的DOM库,它能在Node.js中构造出完整的document和window对象。
安装与基础用法
通过npm安装jsdom后,可以用如下代码创建一个包含HTML结构的文档环境:
const { JSDOM } = require('jsdom');
// 创建DOM实例,载入一段HTML
const dom = new JSDOM('<!DOCTYPE html><p id="msg">hello</p>');
const document = dom.window.document;
// 像在浏览器中一样操作DOM
const text = document.getElementById('msg').textContent;
console.log(text); // 输出 hello
将浏览器全局注入Node.js
如果前端代码直接使用了全局的document,可把jsdom的window属性挂到Node.js的global上:
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<div class="box">content</div>');
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
// 此时前端代码中的document.querySelector可正常运行
const box = document.querySelector('.box');
console.log(box.textContent);
使用happy-dom作为轻量替代
happy-dom比jsdom更轻量,启动速度更快,适合对性能敏感的测试场景。
const { Window } = require('happy-dom');
const window = new Window();
const document = window.document;
document.body.innerHTML = '<span id="tip">hi</span>';
console.log(document.getElementById('tip').textContent);
使用Puppeteer启动真实浏览器
当代码依赖真实布局、Canvas或网络请求时,使用无头浏览器更加可靠。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent('<h1 id="title">Test</h1>');
const title = await page.$eval('#title', el => el.textContent);
console.log(title);
await browser.close();
})();
方案对比
| 方案 | 速度 | 真实度 | 适用场景 |
|---|---|---|---|
| jsdom | 中 | 较高 | DOM操作测试 |
| happy-dom | 快 | 一般 | 单元测试 |
| Puppeteer | 慢 | 完整 | 渲染、爬取 |
注意事项
- 模拟环境无法完全还原浏览器差异,如CSS计算、事件冒泡细节。
- 在代码里应避免直接使用未转义的<input>等标签名称,而应用字符串或变量表示。
- 函数调用如querySelector()不会被解析为标签,请按正常JS书写。
模拟浏览器环境的核心,是在Node.js中提供前端代码所预期的全局对象与DOM接口。