前端與Node.js
1. 前端(瀏覽器環境)
前端通常指的是在瀏覽器中運行的JavaScript代碼。它主要負責用户界面的渲染、交互邏輯和與後端API的通信。
- 核心運行環境:瀏覽器(如Chrome、Firefox、Safari等)
- 核心引擎:V8(Chrome)、SpiderMonkey(Firefox)、JavaScriptCore(Safari)等
- 宿主對象(Host Objects):
window:全局對象,提供瀏覽器窗口相關的APIdocument:DOM操作接口,用於構建和操作網頁結構navigator:獲取瀏覽器信息localStorage、sessionStorage:瀏覽器本地存儲fetch、XMLHttpRequest:網絡請求API
- 特點:
- 運行在客户端,直接面向用户
- 受同源策略(Same-Origin Policy)限制
- 需要考慮瀏覽器兼容性
- 代碼通常通過HTML文件加載
2. Node.js(服務器端環境)
Node.js是一個基於Chrome V8引擎構建的JavaScript運行時,它允許JavaScript在服務器端運行。
- 核心運行環境:Node.js 運行時
- 核心引擎:V8(與Chrome瀏覽器相同)
- 宿主對象:
global:全局對象(相當於瀏覽器中的window)process:提供進程信息和控制Buffer:處理二進制數據require、module.exports:模塊系統(CommonJS)fs、path、http等:Node.js內置模塊,提供文件系統、網絡、路徑處理等能力
- 特點:
- 運行在服務器端,處理業務邏輯、數據存儲、API服務等
- 無瀏覽器安全限制(如同源策略)
- 可直接訪問操作系統資源(文件系統、網絡等)
- 通常通過命令行啓動(
node app.js)
🌟 關鍵區別總結
|
維度
|
前端(瀏覽器)
|
Node.js(服務器)
|
|
運行環境 |
瀏覽器
|
Node.js 運行時
|
|
全局對象 |
|
|
|
模塊系統 |
ES Modules(主流)、CommonJS(打包工具中)
|
CommonJS(原生)、ES Modules(支持)
|
|
網絡請求 |
|
|
|
文件系統 |
無法直接訪問(受限)
|
可通過 |
|
主要用途 |
UI渲染、用户交互
|
後端服務、API、腳本工具
|
為什麼Lodash、Axios能在前端和Node.js中“通用”?
這是本文的核心問題。答案是:它們是“環境無關”的JavaScript庫,通過巧妙的設計實現了跨平台兼容。
我們以 Lodash 和 Axios 為例,深入分析其原理。
🔹 案例1:Lodash —— 純函數工具庫的“環境無關性”
Lodash 是一個提供大量實用函數的JavaScript工具庫,如 _.debounce、_.cloneDeep、_.get 等。
為什麼它能跨平台?
- 不依賴特定宿主對象
- Lodash 的函數大多是純函數(Pure Functions),只依賴輸入參數,不依賴
window、document或fs等環境特定對象。 - 例如
_.cloneDeep(obj)只操作JavaScript原生對象,不涉及DOM或文件系統。
- 模塊化設計
- Lodash 支持多種模塊格式:CommonJS、ES Modules、UMD(Universal Module Definition)。
- UMD 是關鍵!它是一種兼容多種環境的模塊包裝方式:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS (Node.js)
module.exports = factory();
} else {
// 瀏覽器全局變量
root._ = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
// Lodash 核心實現
return _;
}));
- 這段代碼會自動檢測當前環境,選擇合適的模塊導出方式。
- 構建工具支持
- 在前端項目中,Webpack、Vite等工具會將Lodash打包進最終的bundle.js。
- 在Node.js中,直接通過
require('lodash')加載。
✅ 結論:Lodash 之所以通用,是因為它不依賴環境API + 使用UMD兼容多模塊系統。
🔹 案例2:Axios —— HTTP客户端的“適配器模式”跨平台方案
Axios 是一個基於Promise的HTTP客户端,用於發送網絡請求。
它比Lodash更復雜,因為網絡請求在瀏覽器和Node.js中實現方式完全不同:
- 瀏覽器:使用
XMLHttpRequest或fetch - Node.js:使用
http/https模塊
那麼,Axios是如何做到“一套API,兩端運行”的?
核心機制:適配器模式(Adapter Pattern)
Axios 內部採用了適配器模式,根據運行環境自動選擇合適的HTTP實現。
- 默認適配器選擇邏輯:
// Axios 源碼簡化示意
function getDefaultAdapter() {
let adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 瀏覽器環境
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// Node.js 環境
adapter = require('./adapters/http');
}
return adapter;
}
- 瀏覽器適配器:使用
XMLHttpRequest發送請求 - Node.js適配器:使用
http模塊發送請求 - 統一的API層:
- 無論底層是XHR還是http模塊,Axios向上暴露的API完全一致:
axios.get('/api/users')
.then(response => console.log(response.data));
- 配置化與可替換:
- Axios 允許開發者手動指定適配器,甚至使用自定義適配器。
✅ 結論:Axios 通過適配器模式,在不同環境中使用不同的底層實現,但對外提供統一的API,從而實現跨平台。
如何判斷一個庫是否“跨平台通用”?
你可以通過以下幾點快速判斷:
|
判斷標準
|
跨平台庫(如Lodash)
|
非跨平台庫(如jQuery)
|
|
是否依賴DOM/BOM API |
❌ 不依賴
|
✅ 依賴 |
|
是否依賴Node.js內置模塊 |
❌ 不依賴 |
✅ 如 |
|
模塊格式 |
支持 UMD 或 ESM + CJS
|
僅支持 CJS 或僅瀏覽器
|
|
構建方式 |
可通過CDN引入或npm安裝
|
通常只能npm安裝用於Node.js
|
✅ 推薦的跨平台庫:Lodash、Axios、Moment.js(已歸檔)、Day.js、Zod、Yup等
❌ 僅Node.js庫:express、fs-extra、child_process❌ 僅瀏覽器庫:jquery、three.js(雖可Node運行,但無意義)
|
特性
|
CJS (CommonJS) |
ESM (ES Modules) |
UMD (Universal) |
|
語法 |
|
|
兼容多種
|
|
加載方式 |
同步
|
異步(支持動態導入)
|
取決於環境
|
|
原生支持 |
Node.js ✅
|
瀏覽器 ✅ + Node.js ✅
Node.js 從 v12+ 開始支持(需 |
❌(需構建)
|
|
Tree Shaking |
❌ 不支持
|
✅ 支持
|
❌ 通常不支持
|
|
適用環境 |
Node.js
|
前端 + 現代 Node.js
|
所有環境(兼容性最強)
|
|
典型使用 |
|
|
CDN 引入或老項目兼容
|
實際開發中的常見組合“ESM + CJS”
指的是一個 npm 包同時提供兩種格式的構建版本:
dist/index.esm.js→ 供前端構建工具(Vite/Webpack)使用 ESMdist/index.cjs.js→ 供 Node.js 直接require使用
例如 package.json 中可能這樣配置:
{
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js"
}
}
}
前端工程項目打包
前端打包就是在 Node.js 環境下進行的。
- 誰在幹活? → Node.js 進程中的打包工具(Webpack/Vite)
- 輸入是什麼? → 你的源碼 +
node_modules中的庫 - 輸出是什麼? → 適合瀏覽器加載的
.html、.js、.css、.png等靜態文件 - 為什麼必須用 Node.js? → 因為需要文件系統、模塊解析、高性能計算等能力,瀏覽器做不到
1. 代碼是如何處理的?
- ✅ 流程:
入口文件 → 遞歸解析import→ 收集所有模塊(含node_modules)→ 轉換(Babel/TS)→ 優化 → 輸出dist/靜態文件。 - ✅ 關鍵機制:
- Tree Shaking:僅打包實際使用的代碼(需 ESM 模塊)。
- Scope Hoisting:合併模塊,減少閉包開銷。
- Code Splitting:自動分包(如
vendor.js),提升緩存利用率。
2. 變量(如 process.env)是如何處理的?
- ✅ 機制:構建時靜態替換(不是運行時)。
- ✅ 流程:
- 打包工具讀取 Node.js 環境變量(或
.env文件) - 在配置中定義替換規則(如 Vite 的
define,Webpack 的DefinePlugin) - 源碼中的
process.env.NODE_ENV被替換為字符串(如'production') - 無用代碼被 Tree Shaking 刪除
- ⚠️ 注意:
- 瀏覽器中沒有
process,這是“偽變量”。 - 敏感信息(如密鑰)不應暴露在前端變量中。
3. 開發依賴(devDependencies)如何處理?
- ❌ 不會被打包進前端文件。
- ✅ 用途:僅在 Node.js 構建時使用,如:
- 打包工具(Vite、Webpack)
- 編譯器(TypeScript、Babel)
- 代碼檢查(ESLint、Prettier)
- ✅ 類比:廚師的刀具,不端上餐桌。
4. 生產依賴(dependencies)如何處理?
- ✅ 會被打包,但僅限“被引用”的部分。
- ✅ Tree Shaking 生效前提:
- 使用 ESM 語法:
import { func } from 'lib' - 庫支持 ESM 格式(
package.json有module字段) - 生產模式構建(
mode: 'production')
- ❌ 不會打包:
- 未使用的庫或函數(如只用
lodash.debounce,其他函數被搖掉) - 全量導入(
import _ from 'lodash')會打包整個庫(應避免)
✅ 一句話總結
打包是在 Node.js 中運行工具,將代碼按需打包,通過 靜態替換注入變量,開發依賴不進前端,生產依賴按需打包,最終生成輕量、高效的瀏覽器可用文件。