JavaScript 运行环境:浏览器与 Node.js 的世界
回想一下,同样的英语在英国和美国使用时会有些许差异——虽然基本语法相同,但某些词汇、表达方式和使用场景会有所不同。JavaScript 也是如此,虽然语言本身是统一的,但在不同的运行环境中,可用的功能和特性会有明显区别。
运行环境的概念
JavaScript 运行环境(Runtime Environment)是指 JavaScript 代码执行时所依赖的整个系统环境,它包括:
- JavaScript 引擎:解释和执行 JavaScript 代码的核心
- 运行时 API:环境提供的功能接口
- 事件循环机制:处理异步操作的机制
- 全局对象:提供基础功能的对象
// 这段代码在不同环境中会有不同表现
console.log(typeof window); // 浏览器: object, Node.js: undefined
console.log(typeof document); // 浏览器: object, Node.js: undefined
console.log(typeof global); // 浏览器: undefined, Node.js: object
console.log(typeof process); // 浏览器: undefined, Node.js: object浏览器环境
浏览器的核心组成
浏览器环境是 JavaScript 最初的家园。当你在浏览器中运行 JavaScript 代码时,实际上是在一个复杂的生态系统中工作。
// 浏览器环境特有的全局对象
console.log(window); // 顶层全局对象
console.log(document); // DOM 文档对象
console.log(navigator); // 浏览器信息对象
console.log(location); // URL 位置对象
console.log(history); // 浏览历史对象Window 对象——浏览器的指挥中心
window 对象在浏览器中扮演着中央枢纽的角色。它既是全局对象,也是浏览器窗口的代表。就像一座大楼的总控室,控制着整栋楼的各种功能。
// Window 对象的多重身份
// 1. 全局对象:所有全局变量都是它的属性
var userName = "Sarah";
console.log(window.userName); // "Sarah"
// 2. 浏览器窗口的代表
console.log(window.innerWidth); // 窗口内部宽度
console.log(window.innerHeight); // 窗口内部高度
console.log(window.outerWidth); // 窗口外部宽度
console.log(window.outerHeight); // 窗口外部高度
// 3. 提供各种实用方法
window.alert("This is an alert!");
window.confirm("Are you sure?");
window.prompt("What's your name?");
// 4. 控制窗口行为
window.open("https://example.com"); // 打开新窗口
window.close(); // 关闭当前窗口
window.scrollTo(0, 100); // 滚动到指定位置DOM——操作网页内容
Document Object Model (DOM) 是浏览器最重要的特色之一。它把 HTML 文档转换成一个树形结构,让 JavaScript 可以轻松操作页面内容。
// DOM 操作示例
// 查找元素
const header = document.querySelector("h1");
const buttons = document.querySelectorAll("button");
const container = document.getElementById("container");
// 修改内容
header.textContent = "Welcome to JavaScript!";
header.innerHTML = "<strong>Welcome</strong> to JavaScript!";
// 修改样式
header.style.color = "blue";
header.style.fontSize = "24px";
// 添加/删除类
header.classList.add("active");
header.classList.remove("inactive");
header.classList.toggle("highlighted");
// 创建新元素
const newParagraph = document.createElement("p");
newParagraph.textContent = "This is a new paragraph";
container.appendChild(newParagraph);BOM——浏览器对象模型
Browser Object Model (BOM) 提供了与浏览器窗口交互的方法。它就像是控制浏览器本身的工具箱。
// Navigator 对象——浏览器信息
console.log(navigator.userAgent); // 浏览器标识字符串
console.log(navigator.language); // 浏览器语言
console.log(navigator.onLine); // 是否在线
console.log(navigator.geolocation); // 地理位置API
// Location 对象——URL 信息与控制
console.log(location.href); // 完整URL
console.log(location.hostname); // 主机名
console.log(location.pathname); // 路径
console.log(location.search); // 查询字符串
location.reload(); // 重新加载页面
location.href = "https://example.com"; // 跳转到新页面
// History 对象——浏览历史
history.back(); // 后退
history.forward(); // 前进
history.go(-2); // 后退2页浏览器 API
现代浏览器提供了大量强大的 API,让 JavaScript 能够实现各种复杂功能。
// 本地存储 API
localStorage.setItem("username", "John");
const name = localStorage.getItem("username");
localStorage.removeItem("username");
// Fetch API——网络请求
fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));
// 定时器 API
setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);
const intervalId = setInterval(() => {
console.log("This runs every second");
}, 1000);
clearInterval(intervalId); // 停止定时器
// Canvas API——图形绘制
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50); // 绘制红色矩形
// Geolocation API——地理位置
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("Latitude:", position.coords.latitude);
console.log("Longitude:", position.coords.longitude);
},
(error) => console.error("Error:", error)
);Node.js 环境
Node.js 的诞生背景
2009 年,Ryan Dahl 创建了 Node.js,将 JavaScript 从浏览器中解放出来。他想解决一个问题:为什么不能用 JavaScript 来编写服务器端程序?就这样,Node.js 诞生了,它让 JavaScript 可以访问文件系统、创建服务器、处理数据库等。
Node.js 基于 Chrome 的 V8 引擎,但去掉了浏览器相关的 API,增加了许多服务器端需要的功能。
Global 对象——Node.js 的全局对象
在 Node.js 中,global 对象扮演着类似浏览器中 window 的角色,但它的职责更专注于服务器端需求。
// Global 对象
console.log(global);
// 全局变量会附加到 global 对象上
global.appName = "My Application";
console.log(appName); // 可以直接访问
// 但在模块中定义的变量不会成为全局变量
var userName = "Michael";
console.log(global.userName); // undefinedProcess 对象——进程信息与控制
process 对象是 Node.js 的核心特性之一,提供了关于当前 Node.js 进程的信息和控制能力。
// Process 对象示例
// 进程信息
console.log(process.version); // Node.js 版本
console.log(process.platform); // 操作系统平台
console.log(process.arch); // CPU 架构
console.log(process.pid); // 进程ID
console.log(process.cwd()); // 当前工作目录
// 环境变量
console.log(process.env.NODE_ENV); // 环境模式
console.log(process.env.PATH); // 系统PATH
// 命令行参数
// 运行: node app.js arg1 arg2
console.log(process.argv); // ['node', 'app.js', 'arg1', 'arg2']
// 进程控制
process.exit(0); // 退出进程(0表示成功)
process.exit(1); // 退出进程(1表示失败)
// 监听进程事件
process.on("exit", (code) => {
console.log(`Process exiting with code: ${code}`);
});
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
process.exit(1);
});模块系统
Node.js 拥有自己的模块系统,这是它与浏览器环境最大的区别之一。在 Node.js 中,每个文件都是一个独立的模块。
// math.js - 导出模块
exports.add = function (a, b) {
return a + b;
};
exports.multiply = function (a, b) {
return a * b;
};
// 或使用 module.exports
module.exports = {
add: function (a, b) {
return a + b;
},
multiply: function (a, b) {
return a * b;
},
};
// app.js - 导入模块
const math = require("./math");
console.log(math.add(5, 3)); // 8
console.log(math.multiply(4, 6)); // 24
// 导入内置模块
const fs = require("fs");
const http = require("http");
const path = require("path");Node.js 核心模块
Node.js 提供了丰富的内置模块,用于处理各种服务器端任务。
// 文件系统模块(fs)
const fs = require("fs");
// 读取文件
fs.readFile("data.txt", "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
// 写入文件
fs.writeFile("output.txt", "Hello, Node.js!", (err) => {
if (err) throw err;
console.log("File has been saved!");
});
// HTTP 模块——创建服务器
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<h1>Hello from Node.js Server!</h1>");
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000/");
});
// Path 模块——路径处理
const path = require("path");
console.log(path.join("/users", "john", "documents")); // /users/john/documents
console.log(path.basename("/users/john/file.txt")); // file.txt
console.log(path.extname("file.txt")); // .txt
console.log(path.dirname("/users/john/file.txt")); // /users/john
// OS 模块——操作系统信息
const os = require("os");
console.log(os.platform()); // 操作系统平台
console.log(os.cpus()); // CPU 信息
console.log(os.totalmem()); // 总内存
console.log(os.freemem()); // 可用内存
console.log(os.homedir()); // 用户主目录环境差异对比
全局对象对比
// 浏览器环境
console.log(typeof window); // "object"
console.log(typeof document); // "object"
console.log(typeof global); // "undefined"
console.log(typeof process); // "undefined"
// Node.js 环境
console.log(typeof window); // "undefined"
console.log(typeof document); // "undefined"
console.log(typeof global); // "object"
console.log(typeof process); // "object"
// 通用方式获取全局对象
const globalObject = (function () {
if (typeof window !== "undefined") return window;
if (typeof global !== "undefined") return global;
if (typeof self !== "undefined") return self;
return this;
})();
// 或使用 globalThis(ES2020)
console.log(globalThis); // 在任何环境都指向全局对象定时器实现
虽然两个环境都有定时器,但实现方式不同。
// 两个环境都支持
setTimeout(() => console.log("After 1 second"), 1000);
setInterval(() => console.log("Every second"), 1000);
// Node.js 特有的 setImmediate
setImmediate(() => {
console.log("Runs immediately after I/O events");
});
// Node.js 特有的 process.nextTick
process.nextTick(() => {
console.log("Runs before any I/O events");
});错误处理
// 浏览器:错误显示在控制台
window.addEventListener("error", (event) => {
console.error("Global error:", event.error);
});
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
});
// Node.js:错误可能导致进程崩溃
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
// 建议记录日志后优雅退出
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
});跨环境编写代码
检测运行环境
有时我们需要编写能在多个环境运行的代码,这时需要检测当前环境。
// 检测是否在浏览器环境
function isBrowser() {
return (
typeof window !== "undefined" && typeof window.document !== "undefined"
);
}
// 检测是否在 Node.js 环境
function isNode() {
return (
typeof process !== "undefined" &&
process.versions != null &&
process.versions.node != null
);
}
// 根据环境执行不同代码
if (isBrowser()) {
console.log("Running in browser");
// 使用 DOM API
document.querySelector("body").style.background = "lightblue";
} else if (isNode()) {
console.log("Running in Node.js");
// 使用 Node.js API
const fs = require("fs");
}通用模块模式
// UMD(Universal Module Definition)模式
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define([], factory);
} else if (typeof module === "object" && module.exports) {
// Node.js
module.exports = factory();
} else {
// 浏览器全局变量
root.MyLibrary = factory();
}
})(typeof self !== "undefined" ? self : this, function () {
// 实际的模块代码
return {
greet: function (name) {
return "Hello, " + name + "!";
},
};
});现代 JavaScript 运行时
Deno——Node.js 的继任者
Deno 是由 Node.js 创始人 Ryan Dahl 创建的新运行时,旨在解决 Node.js 的一些设计缺陷。
// Deno 特性示例
// 原生支持 TypeScript
const greeting: string = "Hello, Deno!";
// 默认安全,需要明确授权
// 运行: deno run --allow-read --allow-net app.ts
// 原生支持 URL 导入
import { serve } from "https://deno.land/std/http/server.ts";
// 顶层 await
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);Bun——性能优先的运行时
Bun 是一个专注于速度的 JavaScript 运行时,声称比 Node.js 快得多。
// Bun 的快速文件读取
const file = Bun.file("data.txt");
const text = await file.text();
// Bun 的内置服务器
Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello from Bun!");
},
});常见问题与解决方案
问题 1:环境变量访问
问题:如何在不同环境安全地访问环境变量?
// 浏览器中没有 process.env
// 通常需要构建工具支持
// Vite 中
const apiUrl = import.meta.env.VITE_API_URL;
// Webpack 中
const apiUrl = process.env.REACT_APP_API_URL;
// Node.js 中
const apiUrl = process.env.API_URL;
// 跨环境安全访问
const getEnvVar = (key) => {
if (typeof process !== "undefined" && process.env) {
return process.env[key];
}
// 浏览器中可能需要从构建时注入的全局变量获取
if (typeof window !== "undefined" && window.ENV) {
return window.ENV[key];
}
return undefined;
};问题 2:模块导入差异
问题:浏览器和 Node.js 的模块系统不同,如何统一?
// 现代方案:使用 ES Modules
// package.json 中指定
{
"type": "module"
}
// 然后在 Node.js 中也可以使用 import/export
import { add } from "./math.js";
// 浏览器中直接使用(需要 type="module")
<script type="module">
import { add } from "./math.js";
console.log(add(5, 3));
</script>问题 3:console 输出位置
浏览器:console.log 输出到浏览器开发者工具的控制台 Node.js:console.log 输出到终端/命令行
// 在 Node.js 中,可以将输出重定向到文件
const fs = require("fs");
const logStream = fs.createWriteStream("app.log", { flags: "a" });
console.log = function (message) {
logStream.write(message + "\n");
};
// 使用日志库(如 winston)更专业
const winston = require("winston");
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: "error.log", level: "error" }),
new winston.transports.File({ filename: "combined.log" }),
],
});总结
JavaScript 运行环境决定了代码可以使用哪些功能和 API。浏览器环境专注于网页交互和用户体验,而 Node.js 环境则专注于服务器端功能和系统操作。
本节要点回顾:
- 浏览器环境提供 DOM、BOM 和 Web API,用于网页交互
- Node.js 环境提供文件系统、网络、进程等服务器端能力
- 两个环境的全局对象不同:
windowvsglobal - 可以通过环境检测编写跨平台代码
- 现代运行时(Deno、Bun)提供了新的可能性
- 理解环境差异是编写高质量 JavaScript 代码的关键