JavaScript 运行时:浏览器 vs Node.js
简介
JavaScript 运行时(Runtime)是 JavaScript 代码执行的环境。它提供了 JavaScript 引擎(用于解析和执行 JS 代码)以及一组内置的 API(用于与外部环境交互)。
最常见的两个 JavaScript 运行时是:
- 浏览器运行时:运行在用户的浏览器中,用于网页交互
- Node.js 运行时:运行在服务器或本地命令行中,用于后端开发和工具脚本
JavaScript 引擎
JavaScript 引擎是运行时的核心,负责将 JavaScript 代码转换为机器码并执行。
| 引擎 | 使用环境 |
|---|---|
| V8 | Chrome、Node.js、Edge(新版) |
| SpiderMonkey | Firefox |
| JavaScriptCore | Safari |
| Hermes | React Native |
V8 是最流行的 JavaScript 引擎,由 Google 开发,同时被 Chrome 和 Node.js 使用。
浏览器运行时
概述
浏览器运行时提供了运行 JavaScript 代码的环境,主要特点是:
- 可以操作网页(DOM)
- 可以与浏览器交互(BOM)
- 运行在客户端(用户的电脑/手机)
- 全局对象是
window
全局对象:window
在浏览器中,全局对象是 window。所有全局变量和函数都是 window 的属性。
// 在浏览器中
console.log(window); // Window 对象
console.log(this === window); // true(非严格模式下)
var globalVar = 'hello';
console.log(window.globalVar); // "hello"
// 浏览器特有的全局函数
window.alert('Hello!');
window.setTimeout(() => console.log('1秒后'), 1000);
浏览器提供的 API
DOM(文档对象模型):
// 操作网页元素
let title = document.getElementById('title');
title.textContent = '新标题';
let items = document.querySelectorAll('.item');
items.forEach(item => item.style.color = 'red');
// 创建新元素
let div = document.createElement('div');
div.innerHTML = '<p>Hello World</p>';
document.body.appendChild(div);
BOM(浏览器对象模型):
// 浏览器信息
console.log(navigator.userAgent); // 浏览器 UA 字符串
// URL 信息
console.log(location.href); // 当前页面 URL
// 历史记录
history.back(); // 后退一页
// 弹窗
alert('提示信息');
confirm('确定吗?');
prompt('请输入:');
定时器:
// setTimeout:延迟执行一次
let timer1 = setTimeout(() => {
console.log('1秒后执行');
}, 1000);
// setInterval:重复执行
let timer2 = setInterval(() => {
console.log('每秒执行一次');
}, 1000);
// 清除定时器
clearTimeout(timer1);
clearInterval(timer2);
网络请求(fetch):
// 发送 HTTP 请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
// 使用 async/await
async function getData() {
try {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
console.log(data);
} catch (error) {
console.error('请求失败:', error);
}
}
事件监听:
// 监听点击事件
document.getElementById('btn').addEventListener('click', () => {
console.log('按钮被点击了!');
});
// 监听页面加载
window.addEventListener('load', () => {
console.log('页面加载完成');
});
// 监听键盘事件
document.addEventListener('keydown', (event) => {
console.log('按下了:', event.key);
});
浏览器的事件循环
浏览器使用事件循环(Event Loop)处理异步操作:
执行栈(Call Stack)
↓
任务队列(Task Queue)
↓
微任务队列(Microtask Queue)— Promise 回调、MutationObserver
↓
宏任务队列(Macrotask Queue)— setTimeout、setInterval、I/O
// 事件循环示例
console.log('1'); // 同步代码,立即执行
setTimeout(() => console.log('2'), 0); // 宏任务,放入任务队列
Promise.resolve().then(() => console.log('3')); // 微任务,优先于宏任务
console.log('4'); // 同步代码,立即执行
// 输出顺序:1 4 3 2
// 解释:同步代码 → 微任务 → 宏任务
浏览器中无法使用的 API
// 以下 API 在浏览器中不可用:
// require() // CommonJS 模块系统(Node.js 特有)
// fs.readFile() // 文件系统(浏览器出于安全原因不提供)
// process.cwd() // 进程信息(浏览器没有进程概念)
Node.js 运行时
概述
Node.js 是一个基于 V8 引擎的 JavaScript 运行时,让 JavaScript 可以运行在服务器端。
特点:
- 可以操作文件系统
- 可以创建服务器和网络应用
- 运行在服务器端(或本地命令行)
- 全局对象是
global(不是window)
安装和运行
# 检查 Node.js 版本
node --version
# 运行 JavaScript 文件
node app.js
# 进入 REPL(交互式环境)
node
> 1 + 2
3
> .exit # 退出
全局对象:global
在 Node.js 中,全局对象是 global,而不是 window。
// 在 Node.js 中
console.log(global); // Global 对象
console.log(this === global); // false(顶层 this 是空对象或 undefined)
// Node.js 特有的全局变量
console.log(__filename); // 当前文件的完整路径
console.log(__dirname); // 当前文件所在目录
console.log(process.version); // Node.js 版本
注意:在 Node.js 模块中,顶层的
this不是global,而是空对象(严格模式下是undefined)。
CommonJS 模块系统
Node.js 传统使用 CommonJS 模块系统。
导出模块:
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// 导出
module.exports = { add, subtract };
// 或者逐个导出
exports.add = add;
exports.subtract = subtract;
导入模块:
// app.js
const math = require('./math.js');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(5, 3)); // 2
// 导入内置模块
const fs = require('fs');
const path = require('path');
Node.js 提供的 API
文件系统(fs):
const fs = require('fs');
// 读取文件(异步)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
// 读取文件(同步)
try {
let data = fs.readFileSync('file.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
// 写入文件
fs.writeFile('output.txt', 'Hello Node.js!', (err) => {
if (err) console.error('写入失败:', err);
else console.log('写入成功');
});
// 使用 promise 版本(推荐)
const fs = require('fs').promises;
async function readFile() {
try {
let data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
路径处理(path):
const path = require('path');
let filePath = '/users/alice/documents/file.txt';
console.log(path.basename(filePath)); // "file.txt"
console.log(path.dirname(filePath)); // "/users/alice/documents"
console.log(path.extname(filePath)); // ".txt"
console.log(path.join(__dirname, 'data', 'file.json')); // 拼接路径
创建 HTTP 服务器:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Node.js!');
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
进程信息(process):
// 命令行参数
console.log(process.argv); // ['node', 'script.js', ...args]
// 环境变量
console.log(process.env.NODE_ENV);
// 退出进程
process.exit(0); // 0 表示正常退出
// 当前工作目录
console.log(process.cwd());
Node.js 的事件循环
Node.js 的事件循环比浏览器更复杂,有多个阶段:
┌───────────────────────────┐
│ timers │ ← setTimeout、setInterval 回调
├───────────────────────────┤
│ pending callbacks │ ← 某些系统操作的回调
├───────────────────────────┤
│ idle, prepare │ ← 内部使用
├───────────────────────────┤
│ poll │ ← 检索新的 I/O 事件
├───────────────────────────┤
│ check │ ← setImmediate 回调
├───────────────────────────┤
│ close callbacks │ ← 关闭事件的回调
└───────────────────────────┘
// Node.js 事件循环示例
console.log('1'); // 同步代码
setTimeout(() => console.log('2'), 0); // timers 阶段
setImmediate(() => console.log('3')); // check 阶段
Promise.resolve().then(() => console.log('4')); // 微任务
process.nextTick(() => console.log('5')); // 微任务(优先级最高)
console.log('6'); // 同步代码
// 输出顺序:1 6 5 4 2 3(或 1 6 5 4 3 2,2 和 3 顺序可能因情况而异)
浏览器 vs Node.js:详细对比
全局对象对比
| 特性 | 浏览器 | Node.js |
|---|---|---|
| 全局对象 | window |
global |
顶层 this |
window(非严格) |
undefined 或 空对象 |
| 模块化全局 | 无(传统) | module、exports、require |
| 文件路径 | 无 | __filename、__dirname |
API 对比
| 功能 | 浏览器 | Node.js |
|---|---|---|
| DOM 操作 | ✅ document |
❌ 不可用 |
| 网络请求 | ✅ fetch、XMLHttpRequest |
✅ http、https 模块 |
| 文件系统 | ❌ 不可用(安全限制) | ✅ fs 模块 |
| 创建服务器 | ❌ 不可用 | ✅ http.createServer() |
| 环境变量 | ❌ window 下无 |
✅ process.env |
| 定时器 | ✅ setTimeout、setInterval |
✅ setTimeout、setInterval |
| 控制台输出 | ✅ console.log() |
✅ console.log() |
模块系统对比
| 特性 | 浏览器(传统) | 浏览器(现代) | Node.js(传统) | Node.js(现代) |
|---|---|---|---|---|
| 模块格式 | <script> 标签 |
ES Modules | CommonJS | ES Modules |
| 导入 | 全局变量 | import |
require() |
import |
| 导出 | 全局变量 | export |
module.exports |
export |
| 文件扩展名 | .js |
.js |
.js |
.mjs 或 .js(需配置) |
代码示例对比
相同的 JavaScript 语法:
// 以下代码在浏览器和 Node.js 中都能运行
let name = 'Alice';
let greet = (name) => `Hello, ${name}!`;
console.log(greet(name)); // Hello, Alice!
// 数组和对象操作
let arr = [1, 2, 3];
let doubled = arr.map(x => x * 2);
console.log(doubled); // [2, 4, 6]
// Promise 和 async/await
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve('data'), 1000);
});
}
不同的 API 调用:
// ===== 浏览器中 =====
// 操作 DOM
document.getElementById('app').innerHTML = '<h1>Hello</h1>';
// fetch 请求
fetch('/api/data').then(res => res.json()).then(console.log);
// ===== Node.js 中 =====
// 读取文件
const fs = require('fs');
fs.readFile('data.txt', 'utf8', (err, data) => console.log(data));
// http 服务器
const http = require('http');
http.createServer((req, res) => {
res.end('Hello from server!');
}).listen(3000);
ES Modules(现代模块系统)
ES Modules(ESM)是 JavaScript 官方的模块系统,现在浏览器和 Node.js 都支持。
在浏览器中使用 ESM
<!-- 使用 type="module" 启用 ES Modules -->
<script type="module">
import { add } from './math.js';
console.log(add(5, 3)); // 8
</script>
// math.js — 浏览器和 Node.js 都支持这种写法
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 默认导出
export default function multiply(a, b) {
return a * b;
}
<!-- 导入示例 -->
<script type="module">
import { add, subtract } from './math.js';
import multiply from './math.js'; // 默认导入
console.log(add(5, 3)); // 8
console.log(multiply(5, 3)); // 15
</script>
在 Node.js 中使用 ESM
方法 1:使用 .mjs 扩展名
// math.mjs
export function add(a, b) {
return a + b;
}
// app.mjs
import { add } from './math.mjs';
console.log(add(5, 3)); // 8
方法 2:在 package.json 中设置 "type": "module"
// package.json
{
"name": "my-app",
"type": "module",
"main": "app.js"
}
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './math.js';
console.log(add(5, 3)); // 8
CommonJS vs ES Modules
| 特性 | CommonJS | ES Modules |
|---|---|---|
| 语法 | require() / module.exports |
import / export |
| 加载方式 | 同步加载 | 异步加载 |
| 静态分析 | 困难(动态) | 容易(静态) |
| 循环依赖 | 支持(部分) | 支持(更好) |
| 浏览器支持 | ❌ 不支持 | ✅ 支持(需 type=“module”) |
| Node.js 支持 | ✅ 默认 | ✅(需配置) |
// CommonJS(Node.js 传统)
const { add } = require('./math');
module.exports = { add };
// ES Modules(现代,通用)
import { add } from './math.js';
export { add };
共享的 JavaScript 特性
无论在浏览器还是 Node.js 中,以下 JavaScript 特性都是相同的:
// 数据类型
let str = 'hello';
let num = 123;
let arr = [1, 2, 3];
let obj = { a: 1 };
// 函数和作用域
function greet(name) {
return `Hello, ${name}!`;
}
const arrowFunc = (x) => x * 2;
// 内置对象
console.log(Math.max(1, 2, 3)); // 3
console.log(JSON.stringify({ a: 1 })); // '{"a":1}'
console.log(Promise.resolve(42)); // Promise { 42 }
// 数组方法
let nums = [1, 2, 3];
let doubled = nums.map(x => x * 2);
let sum = nums.reduce((a, b) => a + b, 0);
// 对象方法
let keys = Object.keys({ a: 1, b: 2 });
let entries = Object.entries({ a: 1 });
如何选择:浏览器 vs Node.js
使用浏览器的场景
- 开发网页和 Web 应用
- 需要与用户交互(点击、输入等)
- 需要操作 DOM(显示/隐藏元素、修改内容)
- 需要访问浏览器 API(地理位置、摄像头、本地存储等)
// 浏览器专属任务
document.querySelector('.btn').addEventListener('click', () => {
alert('按钮被点击!');
});
localStorage.setItem('theme', 'dark');
let theme = localStorage.getItem('theme');
使用 Node.js 的场景
- 开发后端服务器和 API
- 开发命令行工具
- 操作文件系统(读写文件)
- 构建工具和自动化脚本
- 数据库操作
// Node.js 专属任务
const fs = require('fs');
const files = fs.readdirSync('./');
console.log('目录中的文件:', files);
// 创建简单的 API 服务器
const express = require('express');
const app = express();
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.listen(3000);
可以共用代码的场景
有些代码既可以在浏览器运行,也可以在 Node.js 运行:
// utils.js — 可以在两端共用的工具函数
export function formatDate(date) {
return new Date(date).toLocaleDateString('zh-CN');
}
export function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 数据验证、格式化等纯函数通常可以共用
综合示例
示例 1:读取文件(Node.js)
// Node.js:读取并分析文件
const fs = require('fs');
function countWords(filePath) {
try {
let content = fs.readFileSync(filePath, 'utf8');
let words = content.trim().split(/\s+/).filter(w => w.length > 0);
return {
totalWords: words.length,
uniqueWords: new Set(words).size
};
} catch (err) {
console.error('读取文件失败:', err.message);
return null;
}
}
// console.log(countWords('article.txt'));
示例 2:DOM 操作(浏览器)
<!-- 浏览器:动态更新页面 -->
<!DOCTYPE html>
<html>
<body>
<ul id="todo-list"></ul>
<input type="text" id="todo-input">
<button onclick="addTodo()">添加</button>
<script>
let todos = [];
function addTodo() {
let input = document.getElementById('todo-input');
if (!input.value) return;
todos.push(input.value);
renderTodos();
input.value = '';
}
function renderTodos() {
let list = document.getElementById('todo-list');
list.innerHTML = '';
todos.forEach(todo => {
let li = document.createElement('li');
li.textContent = todo;
list.appendChild(li);
});
}
</script>
</body>
</html>
示例 3:HTTP 请求对比
// ===== 浏览器 =====
fetch('https://api.example.com/users')
.then(res => res.json())
.then(users => {
users.forEach(user => {
console.log(user.name);
});
});
// ===== Node.js =====
const https = require('https');
https.get('https://api.example.com/users', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
let users = JSON.parse(data);
users.forEach(user => console.log(user.name));
});
});
// ===== 共用(使用 fetch 的 Node.js)=====
// Node.js 18+ 也内置了 fetch
// fetch('https://api.example.com/users')
// .then(res => res.json())
// .then(console.log);
示例 4:模块导出/导入对比
// ===== CommonJS(Node.js 传统)=====
// math.js
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require('./math');
console.log(add(2, 3));
// ===== ES Modules(现代,通用)=====
// math.js
export function add(a, b) { return a + b; }
// app.js(或 browser <script type="module">)
import { add } from './math.js';
console.log(add(2, 3));
总结
| 对比项 | 浏览器 | Node.js |
|---|---|---|
| 运行位置 | 客户端(用户设备) | 服务器端 / 本地 |
| 全局对象 | window |
global |
| DOM 访问 | ✅ | ❌ |
| 文件系统 | ❌ | ✅ |
| 网络请求 | fetch |
http / fetch(18+) |
| 模块系统 | ESM(需 type=“module”) | CommonJS(传统)/ ESM |
| 创建服务器 | ❌ | ✅ |
| 事件循环 | 较简单 | 更复杂(多阶段) |
| 适用场景 | 网页、Web 应用 | 后端、CLI 工具、构建工具 |
记住:浏览器和 Node.js 都使用 JavaScript 语法,但提供的 API 不同。浏览器专注于与网页交互,Node.js 专注于服务器端能力。