第 18 章 · 浏览器、Node 与 JavaScript 运行时差异
本章目标:从运行时环境理解前端工程化——浏览器与 Node.js 各提供什么 API;CJS 与 ESM 如何共存;package.json 与包管理器如何影响构建;Polyfill、Babel、browserslist 如何衔接现代语法与目标浏览器。本章是 frontend-web 收官,衔接 frontend-framework。
前置:ch01~ch17;Node.js 18+。学时:3~4 小时。
18.1 两个 JavaScript 世界
同一份 .js 在浏览器与 Node 中表现不同,因宿主环境注入的全局对象与 API 不同;ECMAScript(let、Promise、class)才是共用语言层。
浏览器:window, document, DOM, fetch, localStorage
Node.js:process, fs, path, http, npm CLI
└──── 共用 ECMAScript 语法 ────┘
行业案例 · 贤紫优选商城:订单导出脚本在 Node 读 MySQL;运营后台 Vue 在浏览器调 REST。在 Node 里写 document.querySelector 会立刻报错。
18.2 浏览器 vs Node.js 对比表
| 维度 | 浏览器 | Node.js |
|---|---|---|
| 全局对象 | window | global / globalThis |
| DOM | document、HTMLElement | 无 |
| 文件系统 | 仅用户选取文件 | fs、path |
| 网络 | fetch、WebSocket | http/https、fetch(18+) |
| 模块 | ES Module(<script type="module">) | CJS 历史默认 + ESM |
| 包管理 | 消费构建产物 | npm install、运行 Vite |
| 安全 | 同源策略、CSP | 可访问本机与内网 |
| 用途 | 页面交互 | 构建、BFF、脚本、SSR |
快速验证:
// 浏览器控制台
typeof window; // "object"
typeof require; // undefined(无 bundler)
// Node REPL
typeof window; // "undefined"
typeof process; // "object"
18.3 CommonJS vs ESM
| 特性 | CommonJS | ES Module |
|---|---|---|
| 语法 | require / module.exports | import / export |
| 加载 | 运行时同步 | 编译时静态分析 |
| Tree Shaking | 困难 | 支持 |
| 浏览器 | 不支持 | 原生支持 |
| Node | 默认(无 type) | "type":"module" 或 .mjs |
academy-demo(浏览器 + Vite):
import { fetchCourses } from '../api/client.js';
export function mountHome(root) { /* ... */ }
CJS / ESM 互操作(Node 18+):
// CJS 中动态 import ESM
const { readFile } = await import('node:fs/promises');
// ESM 中引用 JSON(legacy)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const cfg = require('./legacy.json');
| 场景 | 建议 |
|---|---|
| Vite 项目 | 统一 ESM,"type": "module" |
| 发布 npm 库 | exports 声明 import/require 双入口 |
常见错误:浏览器未打包直接 require() → ReferenceError: require is not defined。
18.4 包管理器对比
| 工具 | 锁文件 | 特点 |
|---|---|---|
| npm | package-lock.json | Node 自带,学院默认 |
| pnpm | pnpm-lock.yaml | 省磁盘、依赖隔离严 |
| yarn | yarn.lock | Berry 版与 npm 差异大 |
npm install # 开发装依赖
npm ci # CI 严格按 lock
npx vite # 执行本地 CLI
勿混用多种 lock 文件,避免版本漂移。
18.5 package.json 字段详解
{
"name": "academy-demo",
"private": true,
"type": "module",
"engines": { "node": ">=18.0.0" },
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"exports": { ".": "./src/main.js" },
"browserslist": ["> 0.5%", "last 2 versions", "not dead"],
"devDependencies": { "vite": "^6.0.0" }
}
| 字段 | 含义 |
|---|---|
type: "module" | .js 按 ESM 解析 |
engines | 声明 Node 版本,CI 对齐 |
exports | 限制包对外入口,支持 import/require 映射 |
scripts | npm run <key> 命令 |
browserslist | Babel、Autoprefixer、plugin-legacy 读取 |
dependencies | 运行时依赖 |
devDependencies | 构建期依赖(Vite) |
库作者 exports 示例:
"exports": {
".": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" },
"./style.css": "./dist/style.css"
}
18.6 浏览器 API 与 Node API 对照
| 需求 | 浏览器 | Node.js |
|---|---|---|
| HTTP | fetch(url) | fetch 或 http.request |
| 读文件 | <input type="file"> | fs.readFile |