Advertisement

Vue 曝光埋点插件的实现原理

阅读量:

VUE 曝光埋点插件的实现原理

vue自定义 插件

说到vue插件,vue就提供了自定义插件的功能。

定义

定义一个文件,这个文件 导出一个对象,这个对象包含install方法。
install方法有两个参数 一个是vue构造器 一个是使用插件时传入的参数。

复制代码
    const log = require("./core");// 具体 log方法
    const logPlugin = {
    install(vue, options) {
        if (vue.$prototype.isServer) return;
        // 一些自己想做的事情
        // 全局添加方法属性,添加自定指令,注入mixin
    }
    
    }
    
    module.exports = logPlugin; // 单独使用的插件一般使用commonjs规范,向外暴露,并且适配性高
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码
使用
复制代码
    import { VueLog } from "log";
    Vue.use(VueLog, { eventType: 'click', actionType: 'page-icon', pageType: 'info-page' })
    // 第一个参数就是你导出的自定义插件 第二个参数就是对应插件中install中第2个参数options 你可以传入一些属性
    
    
      
      
      
    
    AI写代码

vue 自定义指令

因为我们需要做的是曝光dom,所以我们需要一个自定指令安装到dom上,来收集dom信息,并且确定这个dom是需要曝光埋点的

定义

参数
vue-directive有两个参数,第一个是指令名称,无需带v,如log-exp,第2个参数是一个对象,这个对象包含四个钩子函数,均为可选

  • bind
    bind只执行一次 ,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted
    被绑定元素插入到父节点时调用,仅保证父元素存在,不保证绑定元素已经插入到文档中。

  • update
    被绑定元素的vnode发生改变时调用,可能发生在其子vnode更新之前,指令值可能变化了,也可能没变化

  • componentUpdated
    被绑定元素vnode及其子vnode全部更新后调用

钩子函数参数
每个钩子函数都有两个参数,第一个是el即被绑定元素实例,可以用来直接操作dom,第二个元素binding是一个对象,包含以下几个属性:

  • name
    指令名 不包含v-

  • value
    指令的绑定值,如v-log-exp=”1+1“,此值为2

  • oldValue
    指令绑定的前一个值,仅在update和comoponentUpdated中可用

  • expression
    字符串形式的指令表达式,如v-log-exp=”1+1“,此值为1+1

  • arg
    传给指令的参数,如v-log-exp:a=‘1’ ,此值为a

  • modifiers
    指令的修饰符对象,如v-log-exp.foo.bar,此值为{foo:true,bar:true}

  • vnode
    指令绑定的虚拟dom

  • oldVnode
    指令绑定的更新前的虚拟dom节点,仅在update和comoponentUpdated中可用

注意事项

  • 参数中除了el外,其他只是可读的,不要去操作它
  • 自定义指令的第二个也可以是一个方法,不写钩子函数的话,vue默认方法会执行在bind 和update中

具体实践

复制代码
    const logPlugin = {
    install(vue, options) {
        if (vue.$prototype.isServer) return;
        vue.directive('log-exp', { // 进行一个指令的定义
            bind: (el, bindings) => {
                const args = JSON.stringify(bindings.value);
                el.setAttribute('data-log-exp', args); // 将绑定的值声明为实例上的属性,便于后续曝光的时候获取
            }
        })
    }
    
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码
使用
复制代码
    <div v-log-exp="{a:1}"></div> // 绑定自己声明的对象
    
    
      
    
    AI写代码

曝光对象intersection-observer

定义

intersection-observer提供了一个异步观察目标元素与其祖先元素或顶级文档元素(viewport)交叉状态变化的方法。不随着目标元素的滚动同步触发。这是它相比监听滚动的优势。
祖先元素与视窗(viewport)被称为根(root)。

** 参数**
new IntersetctionObserver(callback,{})有两个参数,第一个参数是回调函数,
回调函数总共有两个值:

  • entries:
    目标观察对象数组

  • observer
    被调用的IntersetctionObserver对象实例

第二个参数是一个对象,主要有三个值:

  • root
    详细设置观察元素的祖先元素,设置后会观察目标元素与此祖先元素交叉状态的变化,默认null,指向顶级根元素

  • rootMargin
    计算交叉时添加到root元素的矩形偏移量,可以有效夸大或缩小根的范围来满足需求。可以用px或% 默认是0px;
    类似于向root元素添加了一个margin,当目标元素与margin相交时就会触发变化;相当于扩大了或缩小margin的范围,可以提前进行下一步操作。

  • thresholds
    一个阈值列表,升序排列。代表监听对象交叉区域和边界区域的比例。当监听对象的任何阈值被越过时,会生成一个通知,触发回调函数
    在我看来,代表监听元素出现在视图中的,一般出现一半就代表已经曝光了
    api

  • observer
    监听一个对象

  • unobsever
    停止监听一个对象

使用

在我们的自定义插件中使用Exposure对象,并在bind时像此对象添加el作为观察对象

复制代码
    const Exposure = require("./core/exposure")
    const logPlugin = {
       install(vue, options) {
       if (vue.$prototype.isServer) return;
       const observe = new Exposure(options);// 初始化对象,绑定回调及其他
       vue.directive('log-exp', { // 进行一个指令的定义
           bind: (el, bindings) => {
               const args = JSON.stringify(bindings.value);
               el.setAttribute('data-log-exp', args); // 将绑定的值声明为实例上的属性,便于后续曝光的时候获取
               observe.add(el);// 观察此dom
           }
       })
       }
    
    }
    
    module.exports = logPlugin; // 单独使用的插件一般使用commonjs规范,向外暴露,并且适配性高
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

Exposure对象对IntersetctionObserver包装了一下,声明了一些使用的变量和函数

复制代码
    if (window) {
    require('intersection-observer'); // 引入observer
    IntersectionObserve.prototype['THROTTLE_TIMEOUT'] = 200;// 原型上的防抖策略时间根据需求更改一下
    }
    
    module.exports = class Exposure {
    constructor(options) {
        // 使用使用的对象
        this.argData = [] // 要上报的数据
        this.maxNum = options.maxNum || 10; // 达到最大阈值,立马调用上报
        this.options = options;
        this._observer = null; // IntersectionObserver对象
        this._timer = null; //定时器 隔一段时间上报一次,提高效率
        this.init()
    }
    
    init() {
        const that = this;
        this._observer = new IntersectionObserver(
            // 回调函数
            function (entries, observer) {
                entries.forEach((entry) => {
                    if (entry.isIntersecting) { // 代表触发了,出现在视图中了
                        clearTimeout(that._timer);// 连续触发就清楚定时器,类似节流
                        const arg = entry.target.attributes['data-log-exp'];// 获得我们bind时绑定的数据
                        that.argData.push(arg); // 添加到上报数据集中
                        that._observer.unobserve(entry.target);// 取消观察 曝光只曝光一次
                        if (that.argData.length >= that.maxNum) {// 达到阈值直接上报
                            that.logData();// 上报
                        }
                        else if (that.argData.length > 0) { // 一直触发就会一直清除定时器,直至到达阈值或不在触发,到达时间会执行定时器中方法
                            that._timer = setTimeout(() => {
                                that.logData();// 上报
                            }, 200)
                        }
                    }
                }
                )
            }, {
            root: null, // 根元素
            rootMargin: 0, // 距离
            threshold: 0.5 // 超过比例
        }
        )
    }
    
    add(entry) {
        this._observer.observe(entry);// 添加观察对象
    }
    
    logData() {
        const logArgs = this.argData;
        this.argData = [];// 置空
        logArgs.forEach((arg) => {
            log(arg, this.options) // 自己的上报方法
            console.log(arg);
        })
    }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

全部评论 (0)

还没有任何评论哟~