当前位置:首页 > 技术 > 正文内容

前端埋点 SDK 的设计与实现

访客 技术 2026年7月5日 1

SDK 配置初始化

通过实例化追踪器完成基础配置,支持环境识别、事件代理、曝光检测等能力的开关控制。

const trackerConfig = {
  env: import.meta.env.VITE_APP_ENV ?? 'dev',
  runtime: import.meta.env.MODE ?? 'development',
  autoTrackClick: true,
  autoTrackView: true,
  trackHiddenElements: true,
};

window.__TRACKER__ = new AnalyticsTracker(trackerConfig);

核心数据模型

定义统一的上报数据结构,涵盖用户身份、页面位置、交互事件等维度。

this._basePayload = {
  env: options.env,
  deviceId: '',
  openId: '',
  unionId: '',
  userId: '',
  internalId: '',
  userRole: '',
  identityType: '',
  corpId: '',
  staffId: '',
  sessionBranchId: '',
  eventName: '',
  pagePath: '',
  moduleName: '',
  elementName: '',
  extraParams: {},
};

数据注入接口

提供数据合并方法,支持增量更新基础字段,避免直接覆盖参数对象。

public injectData(partialData) {
  this._basePayload = {
    ...this._basePayload,
    ...partialData,
    extraParams: {
      ...this._basePayload.extraParams,
      ...partialData.extraParams,
    },
  };
}

冷启动追踪

在应用启动时自动触发,用于统计用户来源渠道和首次进入行为。

public recordColdLaunch() {
  const source = this._basePayload.entrySource;
  if (!source) return;
  
  this.dispatchEvent({
    eventName: 'app_cold_launch',
  });
}

SPM 层级定位体系

借鉴电商平台的页面定位模型,通过层级编码实现精确的页面元素寻址。

采用 a.b.c.d 四级编码结构:

  • 一级:站点/应用标识
  • 二级:页面标识
  • 三级:区块标识
  • 四级:元素标识

手动事件上报

__TRACKER__.dispatchEvent({
  eventName: 'module_exposure',
  moduleName: 'product_list',
  elementName: 'recommend_card',
  extraParams: { skuId: '10086' },
});

public dispatchEvent(eventData) {
  const payload = {
    ...this._basePayload,
    ...eventData,
    extraParams: {
      ...this._basePayload.extraParams,
      ...eventData.extraParams,
    },
  };
  this._transportLayer.send(payload);
}

声明式埋点与事件代理

利用 HTML 数据属性标记埋点元素,通过事件委托减少内存占用,支持动态元素的自动绑定。

// 组件层标记
<button
  data-track-action={JSON.stringify({
    eventName: 'form_submit',
    moduleName: 'checkout_page',
    elementName: 'confirm_btn',
    extraParams: { orderAmount: 299 },
  })}
>
  提交订单
</button>

// 代理层实现
document.body.addEventListener('click', (event) => {
  let currentNode = event.target;
  const spmChain = {};
  
  while (currentNode?.dataset) {
    const ds = currentNode.dataset;
    if (ds.trackSpmb) spmChain.pagePath = ds.trackSpmb;
    if (ds.trackSpmc) spmChain.moduleName = ds.trackSpmc;
    if (ds.trackSpmd) spmChain.elementName = ds.trackSpmd;
    if (ds.trackAction) spmChain.actionConfig = ds.trackAction;
    currentNode = currentNode.parentElement;
  }
  
  if (spmChain.actionConfig) {
    const config = JSON.parse(spmChain.actionConfig);
    this.dispatchEvent({
      pagePath: spmChain.pagePath,
      moduleName: spmChain.moduleName,
      elementName: spmChain.elementName,
      ...config,
    });
  }
});

区块曝光监测

基于 IntersectionObserver API 实现可视区域检测,支持自定义曝光阈值和重复曝光控制。

// 组件绑定
<section
  data-track-view={JSON.stringify(viewData)}
  ref={__TRACKER__.observeViewableArea}
>
  {children}
</section>

// 观察者初始化
ctx._viewObserver = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.intersectionRatio < 0.5) return;
      const finalData = resolveSpmFromCache(ctx, entry);
      ctx.dispatchEvent(finalData);
    });
  },
  { threshold: 0.5 }
);

// 动态刷新绑定(防抖处理)
export function observeViewableArea() {
  clearTimeout(this._viewDebounceTimer);
  this._viewDebounceTimer = setTimeout(() => {
    this._viewObserver.disconnect();
    const targets = document.querySelectorAll('[data-track-view]');
    targets.forEach((el) => this._viewObserver.observe(el));
  }, 500);
}

缓存辅助函数

// 利用 WeakMap 存储 DOM 的 SPM 解析结果,自动处理内存回收
ctx._spmCache = new WeakMap();

function resolveSpmFromCache(ctx, entry) {
  const target = entry.target;
  let cached = ctx._spmCache.get(target);
  
  if (!cached) {
    cached = {};
    let node = target;
    while (node?.dataset) {
      const ds = node.dataset;
      if (ds.trackSpmb) cached.pagePath = ds.trackSpmb;
      if (ds.trackSpmc) cached.moduleName = ds.trackSpmc;
      if (ds.trackSpmd) cached.elementName = ds.trackSpmd;
      node = node.parentElement;
    }
    ctx._spmCache.set(target, cached);
  }
  
  const userData = JSON.parse(target.dataset.trackView);
  return { ...cached, ...userData };
}

隐藏元素检测

针对弹窗、下拉菜单等由隐藏变为可见的元素,提供独立的观察策略。

ctx._hiddenObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.intersectionRatio <= 0) return;
    ctx.dispatchEvent(resolveSpmFromCache(ctx, entry));
  });
});

// 刷新方法
public refreshHiddenTracking() {
  if (!this._config.trackHiddenElements) return;
  
  clearTimeout(this._hiddenDebounceTimer);
  this._hiddenDebounceTimer = setTimeout(() => {
    this._hiddenObserver.disconnect();
    const nodes = document.querySelectorAll('[data-track-hidden]');
    nodes.forEach((el) => this._hiddenObserver.observe(el));
  }, 200);
}

React 封装组件

提供声明式组件降低接入成本,通过 type 字段区分不同的追踪模式。

// 使用示例
<Trackable
  mode="view"
  payload={{
    eventName: 'banner_show',
    moduleName: 'homepage',
    elementName: 'promo_banner',
    extraParams: { campaignId: 'summer2024' },
  }}
>
  <Banner content={promoData} />
</Trackable>

// 组件实现
export default function Trackable({ mode = 'click', as: Component = 'div', ...props }: TrackableProps) {
  const enrichedProps = {
    ...props,
    mode,
  };
  return strategyMap[mode](enrichedProps);
}

const strategyMap = {
  click: ({ payload, children, className, hidden, ...rest }) => {
    return React.createElement(
      Component,
      {
        'data-track-action': JSON.stringify(payload),
        className: clsx(className, hidden && 'hidden'),
        ...rest,
      },
      children
    );
  },
  
  view: ({ payload, children, className, hidden, ...rest }) => {
    return React.createElement(
      Component,
      {
        'data-track-view': JSON.stringify(payload),
        ref: __TRACKER__.observeViewableArea,
        className: clsx(className, hidden && 'hidden'),
        ...rest,
      },
      children
    );
  },
  
  hidden: ({ payload, children, className, hidden, ...rest }) => {
    return React.createElement(
      Component,
      {
        'data-track-hidden': JSON.stringify(payload),
        ref: __TRACKER__.refreshHiddenTracking,
        className: clsx(className, hidden && 'hidden'),
        ...rest,
      },
      children
    );
  },
};

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。