🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

【使用者行為監控】別只做工具人了!手把手帶你寫一個前端埋點統計 SDK

你是否一直對前端使用者行為監控系統的底層原理充滿好奇?想知道那些“黑科技”是如何攔截點擊、統計 PV(頁面瀏覽量)與 UV(獨立訪客數)、精確計算頁面停留時長的嗎?與其只做工具的使用者,不如深入底層,探尋其背後的實現機制。本文將從原理角度切入,手把手帶你設計並實現一個輕量級、功能完備的使用者行為監控 SDK。

讀完這篇,你將收穫什麼?

通過手寫這個 SDK,你不僅能獲得一個可用的監控工具,更能深入掌握以下核心知識點:

  1. 瀏覽器事件模型:load、beforeunload、visibilitychange、pagehide... 到底該用哪個?怎麼避坑?一次講清楚!
  2. SPA 路由劫持大法:單頁應用路由跳轉不刷新頁面,怎麼監控?帶你手寫 history.pushState 攔截器,配合 popstate 玩轉路由監聽。
  3. 數據上報的“穩”字訣:頁面都要關了,數據還沒發出去咋辦?Navigator.sendBeacon 救命,穩穩地把最後一條數據送達伺服器。
  4. SDK 架構設計:從 0 到 1 設計一個高內聚、低耦合的 SDK(入口、採集、存儲、上報分離),順便把 NPM 發佈流程也走一遍。

預備知識:別被名詞嚇唬住

在動手寫代碼之前,咱們先統一一下“黑話”,搞清楚兩個最基礎的指標:PVUV

PV 和 UV 到底有啥區別?

  • PV (Page View - 頁面瀏覽量) :簡單說就是頁面被打開了多少次。
    舉個例子:小明打開了你的网站首頁(PV+1),手抖刷新了一下(PV+1),又點進去看詳細頁(PV+1)。小明一個人就貢獻了 3 個 PV。
    核心意義:衡量網站被訪問的頻次,看流量大小(看熱鬧)。

  • UV (Unique Visitor - 獨立訪客數) :簡單說就是有多少個不同的人來過。
    舉個例子:還是小明,他今天瘋狂刷新了你的网站 100 次(PV=100),但他還是小明這一個人,所以 UV 只算 1。
    核心意義:衡量網站被多少真實用戶使用了(看人頭)。

系統架構與功能設計

別急著寫代碼,先看圖! 👇下面這張流程圖清晰地展示了數據在 SDK 內部是如何流轉的:

image.png

如圖所示,整個流程主要分為三步走:

  1. 存儲校驗(Storage):初始化時,先檢查 localStorage,判斷是新客還是回頭客(UV 校驗)。
  2. 核心採集(Collection):啟動監聽器,分別捕獲 PV、點擊和停留時長。
  3. 數據上報(Reporting):所有數據最終匯聚到上報模塊,發送給伺服器。

搞懂了數據是怎麼“流”的之後,接下來我們得明確一下源頭到底要“抓”什麼。

1. 咱們到底要抓哪些數據?

簡單來說,這個 SDK 就是你安插在頁面里的偵察兵,主要負責收集這 4 類情報:

  • 點擊 (Click):使用者到底最愛戳哪個按鈕?(比如那個“立即購買”到底有沒有人點?)
  • PV (流量):頁面今天被加載了多少次?(看熱鬧的人多不多?)
  • UV (訪客):到底有多少活人來過?(去重後的真實用戶數)
  • 停留時長 (Dwell Time):使用者進來是秒關,還是津津有味地看了半天

2. 核心戰術(怎麼抓?)

針對上面這些目標,我們逐個擊破:

  1. 抓 PV(頁面瀏覽量):這事兒分兩頭堵。

    • MPA(傳統頁面):死守 window.load,加載完就報。
    • SPA(單頁應用):路由變了頁面不刷新咋辦?劫持 history.pushStatereplaceState,再監聽 popstate,路由一變,PV 立馬 +1。

    注:SPA和MPA區別:

    • MPA (Multi-Page Application):傳統多頁應用(如京東首頁跳轉詳細頁),每次跳轉都會重新加載 HTML,觸發 window.load
    • SPA (Single-Page Application):現代單頁應用(如 Vue/React 專案),頁面跳轉不刷新瀏覽器,只是 JS 換了內容,不會觸發 window.load
  2. 抓 UV(獨立訪客數):在瀏覽器本地 (localStorage) 盖个章。如果發現今天已經蓋過章了,就不重複上報了。

  3. 抓使用者點擊:利用 事件委託 技術,在最外層 (document) 裝個竊聽器。不管你點了裡面的哪個元素,最終都會被我捕獲。

  4. 抓頁面停留時長:這個也得分頭行動

    • 通用招數:監聽 beforeunload(頁面要關了)和 visibilitychange(頁面隱藏了),一旦觸發就計算時間。
    • SPA 特攻:路由切換時(pushState/popstate),除了報 PV,還得把上一頁的停留時間給結清了。

3. 目錄結構

為了保持代碼的模組化和可維護性,我們採用以下目錄結構:

behavior-monitor/
├── dist/                # 打包產物
├── src/                 # 源碼目錄
│   ├── index.ts         # 入口文件
│   ├── tracker.ts       # 行為採集邏輯(PV/Click/Dwell)
│   ├── storage.ts       # 本地存儲與 ID 管理 
│   └── sender.ts        # 上報邏輯
├── test/                # 測試靶場
│   ├── server.js        # 本地測試服務
│   └── index.html       # 行為觸發頁面
├── package.json         # 專案配置
├── rollup.config.js     # Rollup 打包配置
├── tsconfig.json        # TypeScript 配置
└── README.md

行為監控源碼在 src目錄下 ,最終使用rollup對代碼進行打包,dist是打包產物 ; test目錄下是對打包產物的測試。現在就從 0 到 1 開幹,做個mini版的使用者行為監控 SDK。

🚀 瀏覽專案的完整代碼及示例可以點擊這裡 user-behavior-monitor ,如果對您有幫助歡迎Star。

核心代碼實現

  1. 主入口 (index.ts)

入口文件負責對外暴露初始化方法,串聯各個模塊。在這裡我們進行 UV 的初步檢查。

import { trackUserBehavior } from './tracker';
import { getUserID, isUVRecorded, setUVRecorded } from './storage';
import { sendBehaviorData } from './sender';

export interface InitOptions {
  projectName: string;
  reportUrl: string;
}

export const initUserBehaviorMonitor = (options: InitOptions) => {
  const { projectName, reportUrl } = options;
  const userId = getUserID();

  // UV 邏輯:如果本地未記錄,則上報 UV 並標記
  if (!isUVRecorded()) {
    sendBehaviorData({
      behavior: 'uv',
      userId,
      projectName,
      timestamp: new Date().toISOString()
    }, reportUrl);
    setUVRecorded();
  }

  // 啟動行為追蹤
  trackUserBehavior(projectName, reportUrl);
};
  1. 行為採集 (tracker.ts)

這也是 SDK 最核心的部分。為了方便理解,我們將功能拆分為四個具體的任務模塊:

(1) 任務分配 (架構拆解)

trackUserBehavior 是總指揮,它負責啟動所有的監控任務:

export const trackUserBehavior = (projectName: string, reportUrl: string) => {
  // 1. 點擊監控:通過事件委派監聽使用者的點擊操作
  trackClicks(projectName, reportUrl);

  // 2. MPA(傳統頁面) PV 監控:監聽頁面首次加載
  trackMpaPageView(projectName, reportUrl);

  // 3. 停留時長監控:在頁面關閉或隱藏時,計算並上報時長
  trackPageDwellTime(projectName, reportUrl);

  // 4. SPA 路由監控:專門處理單頁應用的路由跳轉
  trackSpaBehavior(projectName, reportUrl);
};

這樣拆分後,職責非常清晰:

  • trackClicks: 負責監控點擊操作。
  • trackMpaPageView: 只管首次打開網頁的那一次 PV。
  • trackSpaBehavior: 負責處理後續的路由跳轉
  • trackPageDwellTime: 兜底處理所有非路由跳轉引起的頁面關閉。

(2) PV 與來源 (Referrer)

對於傳統的 MPA 網站,我們只需要監聽 window.load 事件。不僅要記錄“PV + 1”,還要記錄 document.referrer,告訴伺服器用戶是從哪裡跳過來的(比如從百度搜索進入)。

const trackMpaPageView = (projectName: string, reportUrl: string) => {
  window.addEventListener('load', () => {
        // 獲取用戶id
        const userId = getUserID();
        // 增加 PV 計數 => (曝光+1)
        const pv = incrementPV(); 

        // 發送 PV 數據到服務器
        sendBehaviorData({
          behavior: 'pv',
          userId,
          projectName,
          timestamp: new Date().toISOString(),
          pageUrl: window.location.href,
          referrer: document.referrer || '', // 記錄來源
          pv,
        }, reportUrl);
        ...
  });
};

(3) SPA 路由監聽 (核心難點)

SPA(單頁應用)的特點是頁面跳轉不刷新

  • MPA:每次跳轉都是一次全新的頁面加載,瀏覽器會自動處理一切。
  • SPA:跳轉只是 JS 修改了 URL 和 DOM,瀏覽器不會自動觸發加載事件。

所以,我們需要主動監聽路由變化並手動處理數據的上報與傳遞。

在 SPA(如 Vue/React)中,路由跳轉主要有三種方式,導致我們需要不同的監聽手段:

  1. 代碼跳轉(如 router.push 或者 router.replace

    • 現象:JS 裡調用了 history.pushStatehistory.replaceState
    • 坑點:瀏覽器這時候是裝死的,它悄悄改了 URL,但完全不通知任何人(不觸發事件)。
    • 對策:我們得把 pushStatereplaceState 這兩個原生方法劫持(重寫),在它幹活之前,先插播一段我們的上報邏輯。
  2. 瀏覽器後退/前進

    • 現象:用戶點了瀏覽器左上角的回退或者前進箭頭。
    • 坑點:這回不觸發 pushState 了,而是觸發 popstate 事件了。
    • 對策:老老實實監聽 popstate 事件。
  3. Hash 模式 (#)

    • 現象:URL 裡的 # 變了。
    • 對策:監聽 hashchange 事件。

關鍵技巧:Referrer 接力

在 SPA 內部跳轉時,瀏覽器不會更新 document.referrer。我們需要手動維護一個 lastPageUrl 變量,把“上一個頁面的 URL”傳給“下一個頁面”,這樣才能串聯起完整的使用者訪問路徑。

const trackSpaBehavior = (projectName: string, reportUrl: string) => {
  const handleRouteChange = () => {
    // 1. 防抖校驗:如果 URL 沒變(比如 hashchange 和 popstate 同時觸發),直接退出
    if (window.location.href === lastPageUrl) return;

    // 2. 結算上一頁:上報前一個頁面的停留時間
    reportDwellTime(projectName, reportUrl);

    // 3. 記錄當前 URL 為 referrer (在更新 lastPageUrl 之前!)
    const referrer = lastPageUrl;

    // 4. 更新狀態:保存當前 URL,為下一次跳轉做準備
    pageLoadTime = Date.now();
    lastPageUrl = window.location.href;

    // 5. 記錄新頁面:上報 PV
    const userId = getUserID();
    const pv = incrementPV();
    sendBehaviorData({
      behavior: 'pv',
      userId,
      projectName,
      timestamp: new Date().toISOString(),
      pageUrl: window.location.href,
      referrer: referrer, // 這裡的 referrer 是跳轉前的頁面 URL
      pv,
    }, reportUrl);
  };

  // 1. 監聽 Hash 和瀏覽器後退/前進
  window.addEventListener('hashchange', handleRouteChange);
  window.addEventListener('popstate', handleRouteChange);

  // 2. 劫持 History API (解決 pushState/replaceState 不觸發事件的問題)
  const originalPush = history.pushState;
  const originalReplace = history.replaceState;

  // 路由跳轉,劫持 pushState
  history.pushState = function (...args: Parameters<typeof history.pushState>) {
    originalPush.apply(this, args);
    handleRouteChange();
  };

  // 路由跳轉,劫持 replaceState
  history.replaceState = function (...args: Parameters<typeof history.replaceState>) {
    originalReplace.apply(this, args);
    handleRouteChange();
  };
};

(4) 頁面停留時長 (Dwell Time)

正如前面所說,抓取時長要分頭行動

第一步:通用招數(處理關閉/隱藏)
使用者直接關閉頁面或切換到後台時,觸發結算:

const trackPageDwellTime = (projectName: string, reportUrl: string) => {
  // 1. 頁面關閉/刷新時
  window.addEventListener('beforeunload', () => {
    reportDwellTime(projectName, reportUrl);
  });

  // 2. 兼容移動端(部分移動端不觸發 beforeunload,只觸發 pagehide)
  window.addEventListener('pagehide', () => {
    reportDwellTime(projectName, reportUrl);
  });

  // 3. 頁面隱藏/切後台時
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      reportDwellTime(projectName, reportUrl);
    }
  });
};

第二步:SPA 特攻(處理路由切換)
這部分邏輯其實已經包含在上面的 (3) SPA 路由監聽 里了。在 handleRouteChange 函數中,我們在跳轉前第一件事就是調用 reportDwellTime(),把上一頁的時間結清。

// 回顧一下 trackSpaBehavior 裡的邏輯
const handleRouteChange = () => {
  // 1. 路由要變了?先結帳!上報停留時長
  reportDwellTime(projectName, reportUrl); 
  // ...
};

第三步:停留時長防抖 (避免重複上報)

痛點:使用者關閉頁面時,瀏覽器可能會同時觸發 beforeunloadpagehide 等多個事件。如果不處理,可能會導致同一段停留時間被重複上報

解決辦法:引入一個標記變量 lastDwellReportedForLoadTime。只要當前時間段已經上報過一次,就直接跳過,不再重複處理。

// 記錄上一次上報停留時間的時間戳
let lastDwellReportedForLoadTime: number | null = null;

const reportDwellTime = (projectName: string, reportUrl: string) => {
  // 防抖:如果當前加載時間段已經上報過,直接跳過
  if (lastDwellReportedForLoadTime === pageLoadTime) return;

  // ... 計算並上報 ...

  // 標記已上報
  lastDwellReportedForLoadTime = pageLoadTime;
};

(6) 使用者點擊監控 (事件委託)

如果給頁面上每個按鈕都單獨綁定事件,性能會很差。更高效的做法是利用事件冒泡:只在最外層的 document 上綁定一個監聽器。不管使用者點了哪個按鈕,事件最終都會冒泡到 document,我們在這裡統一攔截處理。

const trackClicks = (projectName: string, reportUrl: string) => {
  document.addEventListener('click', (event) => {
    // 只關心那些帶 data-track-click 屬性的元素
    const target = event.target as HTMLElement;
    if (target && target.dataset.trackClick) {
       // ... 上報 ...
    }
  });
};

使用方式:在 HTML 元素上添加屬性即可自動采集。

<button data-track-click="buy_button">購買</button>

3. 數據存儲 (storage.ts)

這一層主要充當 SDK 的記性。它得清楚地記得:這個用戶是誰?今天來了幾次?今天有沒有報到過?

為了保證刷新頁面也不會“失憶”,我們利用瀏覽器的 localStorage 來實現持久化存儲。

  • 唯一用戶 ID:用戶首次訪問時生成一個 UUID(唯一標識 ID),像發身份證一樣存入 localStorage
  • PV 日統計:按日期作為 Key 來計數,每天重新開始。
  • UV 標記:記錄每日 UV,確保每天只上報一次。

核心邏輯拆解:

  1. 你是誰?(獲取 UserID)

    邏輯:先去 localStorage 翻翻有沒有身份證(USER_ID)。

    • :直接用,說明是老熟人。
    • 沒有:說明是新客,立刻給他印一張新身份證(生成 UUID),並存起來,下次來就認識了。
/**
 * @description: 獲取用戶ID
 * @return {string} 用戶ID
 */
export const getUserID = (): string => {
  let userId = localStorage.getItem(USER_ID_KEY);
  if (!userId) {
    // 給他發個新身份證
    userId = generateUUID();
    // 存起來,下次就認識了
    localStorage.setItem(USER_ID_KEY, userId);
  }
  return userId;
};

/**
 * @description: 生成唯一標識符
 * 簡單來說,這就是用來生成一個獨一無二的字符串 ID。
 * 它通過隨機替換模板中的字符來保證唯一性,就像給每個人發一個不重複的號碼牌。
 * @return {string} 唯一標識符
 */
const generateUUID = (): string => {
  return 'xxxx-xxxx-4xxx-yxxx-xxxx'.replace(/[xy]/g, (char) => {
    const random = (Math.random() * 16) | 0;
    const value = char === 'x' ? random : (random & 0x3) | 0x8;
    return value.toString(16);
  });
};
  1. 今天來了第幾次?(PV 計數)

    邏輯:不能只存一個總數,因為 PV 通常是按統計的。

    技巧:在 Key 裡帶上日期,比如 pv_count_2023-10-01。這樣到了第二天,日期變了,Key 也變了,計數器自動歸零,重新開始。

/* 當天 PV +1 */
export const incrementPV = (): number => {
  // 獲取當天的日期
  const today = new Date().toISOString().split('T')[0];
  const pvData = localStorage.getItem(`${PV_COUNT_KEY}_${today}`);
  const newPV = (pvData ? parseInt(pvData, 10) : 0) + 1;
  localStorage.setItem(`${PV_COUNT_KEY}_${today}`, newPV.toString());
  return newPV;
};
  1. 今天記過人頭了嗎?(UV 標記)

    邏輯:UV 是按天去重的。如果今天已經上報過這個人的 UV 了,就別再發了,省流量。

    實現:在 localStorage 裡存一個標記 uv_record_date = '2023-10-01'。每次初始化時檢查一下,如果存的日期是今天,說明“已閱”,不用再報。

/* 當前版本:存在即認為已記錄 */
export const isUVRecorded = (): boolean => {
  const today = new Date().toISOString().split('T')[0];
  return localStorage.getItem(UV_STORAGE_KEY) === today;
};

4. 數據上報(sender.ts)

收集到數據後,如何發給後端?這看似簡單,實則暗藏玄机。

1. 核心痛點:頁面關了,請求還沒發完怎麼辦?

用戶看完網頁直接關掉(或者刷新跳轉),這時候瀏覽器會無情地殺掉當前頁面進程裡所有正在跑的異步請求(XHR/Fetch)。結果就是:監控數據還沒發出去,就死在半路上了。

2. 解決方案

為了確保數據必達,我們採用一套組合拳:

  1. 首選 Navigator.sendBeacon
    它是專門為“頁面卸載上報”設計的。
    特點:瀏覽器會在後台默默把數據發完,不阻塞頁面關閉,也不會被殺掉。

  2. 次選 fetch + keepalive
    如果瀏覽器不支持 Beacon,或者你需要自定義 Header(Beacon 不支持自定義 Header),就用 fetch 並開啟 keepalive: true
    特點:告訴瀏覽器“這個請求很重要,頁面關了也請幫我發完”。

3. 代碼實現

export const sendBehaviorData = (data: Record<string, any>, url: string) => {
  // 1. 包裝數據:加上一些公共信息(比如 UserAgent,螢幕解析度等)
  const dataToSend = {
    ...data,
    userAgent: navigator.userAgent,
    // screenWidth: window.screen.width, // 可選
  };

  // 2. 優先使用 sendBeacon (最穩,且不阻塞)
  // 注意:sendBeacon 不支持自定義 Content-Type,默認是 text/plain
  // 這裡用 Blob 強制指定為 application/json
  if (navigator.sendBeacon) {
    const blob = new Blob([JSON.stringify(dataToSend)], {
      type: 'application/json',
    });
    // sendBeacon 返回 true 表示進入隊列成功
    navigator.sendBeacon(url, blob);
    return;
  }

  // 3. 降級方案:使用 fetch + keepalive
  // 即使頁面關閉,keepalive 也能保證請求發出
  fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(dataToSend),
    keepalive: true, // <--- 關鍵參數!防止頁面關閉時請求被殺
  }).catch((err) => {
    console.error('上報失敗:', err);
  });
};

3. 工程化構建配置

既然是 SDK,最好的分發方式當然是發佈到 NPM。這樣其他專案只需要一行命令就能接入你的前端錯誤監控系統。這裡我們選擇 Rollup對代碼進行打包,因為它比 Webpack 更適合打包庫(Library),生成的代碼更簡潔。

3.1 package 配置 (package.json)

package.json 不僅僅是依賴管理,它還定義了你的包如何被外部使用。配置不當會導致用戶引入報錯或無法獲得代碼提示。

{
  "name": "behavior-monitor-sdk",
  "version": "1.0.0",
  "description": "A lightweight front-end behavior monitoring SDK",
  "main": "dist/index.cjs.js", // CommonJS 入口
  "module": "dist/index.esm.js", // ESM 入口
  "browser": "dist/index.umd.js", // UMD 入口
  "type": "module",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w"
  },
  "keywords": ["behavior-monitor", "frontend", "sdk"],
  "license": "MIT",
  "files": ["dist"], // 發佈時僅包含 dist 目錄
  "devDependencies": {
    "rollup": "^4.9.0",
    "@rollup/plugin-typescript": "^11.1.0",
    "@rollup/plugin-terser": "^0.4.0", // 用於壓縮代碼
    "typescript": "^5.3.0",
    "tslib": "^2.6.0"
  }
}

💡 關鍵字段解讀:

  • name: 包的“身份證號”。在 NPM 全球範圍內必須唯一,發佈前記得先去搜一下有沒有重名。
  • 入口文件“三劍客”(決定了別人怎麼引用你的包):
    • main: CommonJS 入口。給 Node.js 環境或舊舊構建工具(如 Webpack 4)使用的。
    • module: ESM 入口。給現代構建工具(Vite, Webpack 5)使用的。支持 Tree Shaking(搖樹優化),能減小體積。
    • browser: UMD 入口。給瀏覽器直接通過 <script> 標籤引入使用的(如 CDN)。
  • files: 發佈白名單。指定 npm publish 時只上傳哪些文件(這裡我們只傳編譯後的 dist 目錄)。源碼、測試代碼等不需要發上去,以減小包體積。

3.2 TypeScript 配置 (tsconfig.json)

我們需要配置 TypeScript 如何編譯代碼,並生成類型聲明文件(.d.ts),這對使用 TS 的用戶非常友好。

{
  "compilerOptions": {
    "target": "es5", // 編譯成 ES5,兼容舊瀏覽器
    "module": "esnext", // 保留 ES 模塊語法,交給 Rollup 處理
    "declaration": true, // 生成 .d.ts 類型文件 (關鍵!)
    "declarationDir": "./dist", // 類型文件輸出目錄
    "strict": true, // 開啟嚴格模式,代碼更健壯
    "moduleResolution": "node" // 按 Node 方式解析模塊
  },
  "include": ["src/**/*"] // 編譯 src 下的所有文件
}

3.3 Rollup 打包配置 (rollup.config.js)

為了兼容各種使用場景,我們配置 Rollup 輸出三種格式:

  1. ESM (.esm.js): 給現代構建工具(Vite, Webpack)使用,支持 Tree Shaking。
  2. CJS (.cjs.js): 給 Node.js 或舊版工具使用。
  3. UMD (.umd.js): 可以直接在瀏覽器通過 <script> 標籤引入會掛載全局變量。
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.ts', // 入口文件
  output: [
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',
      sourcemap: true,
    },
    {
      file: 'dist/index.esm.js',
      format: 'es',
      sourcemap: true,
    },
    {
      file: 'dist/index.umd.js',
      format: 'umd',
      name: 'frontendBehaviorMonitor', // <script> 引入時的全局變量名
      sourcemap: true,
      plugins: [terser()], // UMD 格式進行壓縮體積
    },
  ],
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      declaration: true,
      declarationDir: 'dist',
    }),
  ],
};

4. 發佈到 NPM (保姆級教程)

4.1 準備工作

  1. 註冊帳號:去 npmjs.com 註冊一個帳號(記得驗證郵箱,否則無法發佈)。
  2. 檢查包名:在 NPM 搜一下你的 package.json 裡的 name,確保沒有被佔用。如果不幸重名,改個獨特的名字,比如 behavior-monitor-sdk-vip

4.2 終端操作三步走

打開終端(Terminal),在專案根目錄下操作:

第一步:登錄 NPM

npm login
  • 輸入命令後按回車,瀏覽器會彈出登錄頁面。
  • 或者在終端根據提示輸入用戶名、密碼和郵箱驗證碼。
  • 登錄成功後會顯示 Logged in as <your-username>.
  • 注意:如果你之前切換過淘寶源,發佈時必須切回官方源:npm config set registry https://registry.npmjs.org/

第二步:打包代碼

確保 dist 目錄是最新的,不要發佈空代碼。

npm run build

第三步:正式發佈

npm publish --access public
  • --access public 參數用於確保發佈的包是公開的(特別是當包名帶 @ 前綴時)。
  • 看到 + [email protected] 字樣,恭喜你,發佈成功!

現在,全世界的開發者都可以通過 npm install behavior-monitor-sdk 來使用你的作品了!

5. 如何使用

SDK 發佈後,支持多種引入方式,適配各種開發場景。

方式 1:NPM + ES Modules (推薦)

適用於現代前端專案(Vue, React, Vite, Webpack 等)。

# 請將 behavior-monitor-sdk 替換為你實際發佈的包名
npm install behavior-monitor-sdk

在你的業務代碼入口(如 main.tsapp.js)引入並初始化:

// 請將 initUserBehaviorMonitor 替換為你實際發佈的包名
import { initUserBehaviorMonitor } from 'behavior-monitor-sdk';

initUserBehaviorMonitor({
  projectName: 'MyMallProject', // 專案名稱
  reportUrl: 'https://api.yourserver.com/v1/report' // 上報介面地址
});

方式 2:CDN 直接引入

適用於不使用構建工具的傳統專案或簡單的 HTML 頁面。

<!-- 請將 behavior-monitor-sdk 替換為你實際發佈的包名,x.x.x 替換為具體版本號 -->
<script src="https://unpkg.com/[email protected]/dist/index.umd.js"></script>
<script>
  // UMD 版本會將 SDK 掛載到 window.frontendBehaviorMonitor
  window.frontendBehaviorMonitor.initUserBehaviorMonitor({
    projectName: 'MyMallProject',
    reportUrl: 'https://api.yourserver.com/v1/report',
  });
</script>

方式 3:埋點使用說明 (關鍵)

本 SDK 支持自動采集 PV、UV 和停留時長,但點擊事件需要手動標記。

在需要監控點擊的元素上添加 data-track-click 屬性,值為該按鈕的業務標識:

<!-- 比如:監控購買按鈕的點擊 -->
<button data-track-click="buy_now_btn">立即購買</button>

<!-- 比如:監控輪播圖點擊 -->
<div data-track-click="banner_ad_01">...</div>

6. 總結與展望

至此,我們已經親手打造了一個麻雀雖小、五臟俱全的前端行為監控 SDK。回顧這段旅程,我們不僅實現了代碼,更重要的是深入理解了瀏覽器的底層機制:

  • 知其然:學會了如何監聽 PV、UV、點擊和停留時長。
  • 知其所以然:理解了 history API 的劫持原理、sendBeacon 的可靠性優勢以及事件委託的性能價值。

當然,這只是個起點。在企業級的生產環境中,你還可以繼續擴展:

  1. 數據可視化:搭建一個後端服務和看板,將上報的數據繪製成精美的圖表。
  2. 性能監控:結合 Performance API,監控首屏加載時間 (FCP)、最大內容繪製 (LCP) 等性能指標。
  3. 錯誤監控:監聽 errorunhandledrejection 事件,捕獲 JS 報錯和介面異常。

🚀 閱讀:關於錯誤監控的


原文出處:https://juejin.cn/post/7583612559443279923


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬10   ❤️4
406
🥈
我愛JS
📝1   💬7   ❤️3
61
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付