前端與Node.js

1. 前端(瀏覽器環境)

前端通常指的是在瀏覽器中運行的JavaScript代碼。它主要負責用户界面的渲染、交互邏輯和與後端API的通信。

  • 核心運行環境:瀏覽器(如Chrome、Firefox、Safari等)
  • 核心引擎:V8(Chrome)、SpiderMonkey(Firefox)、JavaScriptCore(Safari)等
  • 宿主對象(Host Objects)
  • window:全局對象,提供瀏覽器窗口相關的API
  • document:DOM操作接口,用於構建和操作網頁結構
  • navigator:獲取瀏覽器信息
  • localStoragesessionStorage:瀏覽器本地存儲
  • fetchXMLHttpRequest:網絡請求API
  • 特點
  • 運行在客户端,直接面向用户
  • 受同源策略(Same-Origin Policy)限制
  • 需要考慮瀏覽器兼容性
  • 代碼通常通過HTML文件加載

2. Node.js(服務器端環境)

Node.js是一個基於Chrome V8引擎構建的JavaScript運行時,它允許JavaScript在服務器端運行。

  • 核心運行環境:Node.js 運行時
  • 核心引擎:V8(與Chrome瀏覽器相同)
  • 宿主對象
  • global:全局對象(相當於瀏覽器中的window
  • process:提供進程信息和控制
  • Buffer:處理二進制數據
  • requiremodule.exports:模塊系統(CommonJS)
  • fspathhttp等:Node.js內置模塊,提供文件系統、網絡、路徑處理等能力
  • 特點
  • 運行在服務器端,處理業務邏輯、數據存儲、API服務等
  • 無瀏覽器安全限制(如同源策略)
  • 可直接訪問操作系統資源(文件系統、網絡等)
  • 通常通過命令行啓動(node app.js

🌟 關鍵區別總結

維度

前端(瀏覽器)

Node.js(服務器)

運行環境

瀏覽器

Node.js 運行時

全局對象

window

global

模塊系統

ES Modules(主流)、CommonJS(打包工具中)

CommonJS(原生)、ES Modules(支持)

網絡請求

fetchXMLHttpRequest

httphttps 模塊,或第三方庫

文件系統

無法直接訪問(受限)

可通過 fs 模塊直接讀寫

主要用途

UI渲染、用户交互

後端服務、API、腳本工具


為什麼Lodash、Axios能在前端和Node.js中“通用”?

這是本文的核心問題。答案是:它們是“環境無關”的JavaScript庫,通過巧妙的設計實現了跨平台兼容

我們以 LodashAxios 為例,深入分析其原理。


🔹 案例1:Lodash —— 純函數工具庫的“環境無關性”

Lodash 是一個提供大量實用函數的JavaScript工具庫,如 _.debounce_.cloneDeep_.get 等。

為什麼它能跨平台?
  1. 不依賴特定宿主對象
  • Lodash 的函數大多是純函數(Pure Functions),只依賴輸入參數,不依賴 windowdocument 或 fs 等環境特定對象。
  • 例如 _.cloneDeep(obj) 只操作JavaScript原生對象,不涉及DOM或文件系統。
  1. 模塊化設計
  • 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 _;
}));
  • 這段代碼會自動檢測當前環境,選擇合適的模塊導出方式。
  1. 構建工具支持
  • 在前端項目中,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實現。

  1. 默認適配器選擇邏輯
// 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;
}
  1. 瀏覽器適配器:使用 XMLHttpRequest 發送請求
  2. Node.js適配器:使用 http 模塊發送請求
  3. 統一的API層
  • 無論底層是XHR還是http模塊,Axios向上暴露的API完全一致:
axios.get('/api/users')
  .then(response => console.log(response.data));
  1. 配置化與可替換
  • Axios 允許開發者手動指定適配器,甚至使用自定義適配器。

結論:Axios 通過適配器模式,在不同環境中使用不同的底層實現,但對外提供統一的API,從而實現跨平台。


如何判斷一個庫是否“跨平台通用”?

你可以通過以下幾點快速判斷:

判斷標準

跨平台庫(如Lodash)

非跨平台庫(如jQuery)

是否依賴DOM/BOM API

❌ 不依賴

✅ 依賴 documentwindow

是否依賴Node.js內置模塊

❌ 不依賴 fspath

✅ 如 fs-extra 只能在Node.js用

模塊格式

支持 UMD 或 ESM + CJS

僅支持 CJS 或僅瀏覽器

構建方式

可通過CDN引入或npm安裝

通常只能npm安裝用於Node.js

✅ 推薦的跨平台庫:Lodash、Axios、Moment.js(已歸檔)、Day.js、Zod、Yup等
❌ 僅Node.js庫:expressfs-extrachild_process ❌ 僅瀏覽器庫:jquerythree.js(雖可Node運行,但無意義)

特性

CJS (CommonJS)

ESM (ES Modules)

UMD (Universal)

語法

require() / module.exports

import / export

兼容多種

加載方式

同步

異步(支持動態導入)

取決於環境

原生支持

Node.js ✅

瀏覽器 ✅ + Node.js ✅ 


Node.js 從 v12+ 開始支持(需 .mjs 擴展名或 package.json 中設置 "type": "module"

❌(需構建)

Tree Shaking

❌ 不支持

✅ 支持

❌ 通常不支持

適用環境

Node.js

前端 + 現代 Node.js

所有環境(兼容性最強)

典型使用

const _ = require('lodash')

import _ from 'lodash'

CDN 引入或老項目兼容

實際開發中的常見組合“ESM + CJS” 

指的是一個 npm 包同時提供兩種格式的構建版本:

  • dist/index.esm.js → 供前端構建工具(Vite/Webpack)使用 ESM
  • dist/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)是如何處理的?

  • 機制構建時靜態替換(不是運行時)。
  • 流程
  1. 打包工具讀取 Node.js 環境變量(或 .env 文件)
  2. 在配置中定義替換規則(如 Vite 的 define,Webpack 的 DefinePlugin
  3. 源碼中的 process.env.NODE_ENV 被替換為字符串(如 'production'
  4. 無用代碼被 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 中運行工具,將代碼按需打包,通過 靜態替換注入變量開發依賴不進前端生產依賴按需打包,最終生成輕量、高效的瀏覽器可用文件。