欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽(tīng)并進(jìn)行數(shù)據(jù)埋點(diǎn)的方法

 更新時(shí)間:2022年11月14日 10:12:45   作者:flymyd  
niapp起源?uni-app是一個(gè)使用Vue.js開(kāi)發(fā)所有前端應(yīng)用的框架,下面這篇文章主要給大家介紹了關(guān)于Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽(tīng)并進(jìn)行數(shù)據(jù)埋點(diǎn)的相關(guān)資料,需要的朋友可以參考下

零、前言

最近接到需求,領(lǐng)導(dǎo)希望使用微信開(kāi)放平臺(tái)上免費(fèi)的We分析進(jìn)行數(shù)據(jù)埋點(diǎn),但又不希望在現(xiàn)有uniapp開(kāi)發(fā)的微信小程序代碼上做侵入式修改,筆者奉命進(jìn)行了技術(shù)調(diào)研,考慮通過(guò)劫持事件的方式來(lái)實(shí)現(xiàn)捕獲特定事件并上傳分析平臺(tái)的功能。

需要特別注意的是,微信小程序是不能得到document對(duì)象的,$el上掛載的也是undefined,自然也就不能通過(guò)全局addEventListener的方式來(lái)監(jiān)聽(tīng)特定事件。在調(diào)研中想到可以通過(guò)劫持小程序的自定義組件構(gòu)造器Component()來(lái)實(shí)現(xiàn)事件的監(jiān)聽(tīng)。

為了便于理解,部分?jǐn)?shù)據(jù)結(jié)構(gòu)通過(guò)TypeScript接口形式進(jìn)行描述。

一、軟件環(huán)境

  • HbuilderX 3.4.7.20220422
  • 微信開(kāi)發(fā)者工具 Stable 1.05.2203070
  • 小程序基礎(chǔ)庫(kù)版本 2.24.4 [749]

二、相關(guān)分析及實(shí)現(xiàn)

uniapp編譯微信小程序時(shí)對(duì)于事件的處理分析

部分知識(shí)via掘金:http://www.dbjr.com.cn/article/267434.htm

uniapp使用了uni-app runtime這個(gè)運(yùn)行時(shí)將小程序發(fā)行代碼進(jìn)行打包,實(shí)現(xiàn)了Vue與小程序之間的數(shù)據(jù)及事件同步。

源Vue模板及編譯產(chǎn)物wxml對(duì)照

uniapp的模板編譯器代碼在/Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/uni-template-complier下。

首先以一個(gè)簡(jiǎn)單的Vue模板為例,觀察uniapp是如何將Vue template編譯為wxml的:

<template>
  <div @click="add();subtract(2)" @touchstart="mixin($event)">{{ num }}</div>
</template>

編譯結(jié)果為:

<view 
    data-event-opts="{{
        [
            ['tap', [['add'],['subtract',[2]]] ],
            ['touchstart', [['mixin',['$event']]] ]
        ]
    }}"
    bindtap="__e" bindtouchstart="__e"
    class="_div">
    {{num}}
</view>

可以看到,uniapp將tap和touchstart事件綁定到__e函數(shù)上,然后將事件對(duì)應(yīng)的動(dòng)作放到了名為eventOpts的dataset中。

data-event-opts

data-event-opts非常重要。data-event-opts是一個(gè)二維數(shù)組,每個(gè)子數(shù)組代表一個(gè)事件類型。事件類型有兩個(gè)值,第一個(gè)表示事件類型名稱,第二個(gè)表示觸發(fā)事件函數(shù)的個(gè)數(shù)。事件函數(shù)又是一個(gè)數(shù)組,第一個(gè)值表述事件函數(shù)名稱,第二個(gè)是參數(shù)表。下面用TypeScript的類型聲明方式進(jìn)行簡(jiǎn)單描述:

//data-event-opts是一個(gè)二維數(shù)組,每個(gè)子數(shù)組代表一個(gè)事件類型EventTypes
const dataEventOpts: EventTypes;
interface EventTypes {
  [index:number]: EventType;
}
//事件類型的描述為EventType。EventType只有兩個(gè)元素,也就是說(shuō)EventType.length===2
interface EventType {
  //EventType的第一個(gè)元素是事件類型名稱
  //第二個(gè)元素是事件函數(shù)的數(shù)組EventFuncList,數(shù)組內(nèi)元素為被觸發(fā)的事件函數(shù)
  [index:number]: string | EventFuncList;
}
interface EventFuncList {
  //事件函數(shù)依舊是一個(gè)數(shù)組
  [index:number]: EventFunc;
}
//事件函數(shù)的元素為1或2個(gè),分別是事件函數(shù)名稱和參數(shù)表Array<any>
interface EventFunc {
  [index:number]: string | Array<any>;
}

對(duì)照模板,就可以得出如下推論:

['tap',[['add'],['subtract',[2]]]]表示事件類型為tap,觸發(fā)函數(shù)有兩個(gè),一個(gè)為add函數(shù)且無(wú)參數(shù),一個(gè)為subtract且參數(shù)為2。 ['touchstart',[['mixin',['$event']]]]表示事件類型為touchstart,觸發(fā)函數(shù)有一個(gè)為mixin,參數(shù)為$event對(duì)象。

不難看出,我們?cè)谶M(jìn)行事件捕捉時(shí),只需要讀取到data-event-opts[i][0]就可以得到每個(gè)事件的類型。

handleEvent事件:__e

所有的事件都會(huì)調(diào)用__e事件,也就是handleEvent。在上文的模板中,handleEvent做了如下操作:

1、拿到點(diǎn)擊元素上的data-event-opts屬性:[['tap',[['add'],['subtract',[2]]]],['touchstart',[['mixin',['$event']]]]]

2、根據(jù)點(diǎn)擊類型獲取相應(yīng)數(shù)組,比如bindTap就取['tap',[['add'],['subtract',[2]]]]bindtouchstart就取['touchstart',[['mixin',['$event']]]]

3、依次調(diào)用相應(yīng)事件類型的函數(shù),并傳入?yún)?shù),比如tap調(diào)用this.add();this.subtract(2)

uniapp對(duì)mp-wx的相關(guān)處理在/Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/uni-mp-weixin下。

// @dcloudio/uni-mp-weixin/dist/index.js:1302
function handleEvent (event) {
  event = wrapper$1(event);

  // [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
  const dataset = (event.currentTarget || event.target).dataset;
  if (!dataset) {
    return console.warn('事件信息不存在')
  }
  const eventOpts = dataset.eventOpts || dataset['event-opts']; // 支付寶 web-view 組件 dataset 非駝峰
  if (!eventOpts) {
    return console.warn('事件信息不存在')
  }

  // [['handle',[1,2,a]],['handle1',[1,2,a]]]
  const eventType = event.type;

  const ret = [];

  eventOpts.forEach(eventOpt => {
    let type = eventOpt[0];
    const eventsArray = eventOpt[1];

    const isCustom = type.charAt(0) === CUSTOM;
    type = isCustom ? type.slice(1) : type;
    const isOnce = type.charAt(0) === ONCE;
    type = isOnce ? type.slice(1) : type;

    if (eventsArray && isMatchEventType(eventType, type)) {
      eventsArray.forEach(eventArray => {
        const methodName = eventArray[0];
        if (methodName) {
          let handlerCtx = this.$vm;
          if (handlerCtx.$options.generic) { // mp-weixin,mp-toutiao 抽象節(jié)點(diǎn)模擬 scoped slots
            handlerCtx = getContextVm(handlerCtx) || handlerCtx;
          }
          if (methodName === '$emit') {
            handlerCtx.$emit.apply(handlerCtx,
              processEventArgs(
                this.$vm,
                event,
                eventArray[1],
                eventArray[2],
                isCustom,
                methodName
              ));
            return
          }
          const handler = handlerCtx[methodName];
          if (!isFn(handler)) {
            throw new Error(` _vm.${methodName} is not a function`)
          }
          if (isOnce) {
            if (handler.once) {
              return
            }
            handler.once = true;
          }
          let params = processEventArgs(
            this.$vm,
            event,
            eventArray[1],
            eventArray[2],
            isCustom,
            methodName
          );
          params = Array.isArray(params) ? params : [];
          // 參數(shù)尾部增加原始事件對(duì)象用于復(fù)雜表達(dá)式內(nèi)獲取額外數(shù)據(jù)
          if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
            // eslint-disable-next-line no-sparse-arrays
            params = params.concat([, , , , , , , , , , event]);
          }
          ret.push(handler.apply(handlerCtx, params));
        }
      });
    }
  });

  if (
    eventType === 'input' &&
    ret.length === 1 &&
    typeof ret[0] !== 'undefined'
  ) {
    return ret[0]
  }
}

微信小程序自定義組件Component

mp-wx中的Component文檔:https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html

構(gòu)造器Component()

在uniapp-mp-wx中,組件的裝載是通過(guò)實(shí)例化Component進(jìn)行的。uniapp會(huì)默認(rèn)裝載如下8個(gè)參數(shù):

interface optionsList {
  options: Object | Map<any, any>,
  data: Object,
  properties: Object | Map<any, any>,
  behaviors: string | Array<any>,
  lifetimes: Object,
  pageLifetimes: Object,
  methods: Object,
  created: Function
}

并且在methods中注入如下兩個(gè)函數(shù):

methods: {
      __l: handleLink,   //建立組件父子關(guān)系
      __e: handleEvent   //事件處理器
}

劫持自定義組件構(gòu)造器Component

劫持Component的構(gòu)造器,在每個(gè)組件的__e中注入自定義的事件劫持器eventProxy

// 劫持Component
const _componentProto_ = Component;
Component = function(options) {
  //options.methods內(nèi)有uniapp注入的事件處理器__e及mpHook
  Object.keys(options.methods).forEach(methodName => {
    //劫持事件處理器__e
    if (methodName == "__e") {
      eventProxy(options.methods, methodName)
    }
  })
  _componentProto_.apply(this, arguments);
}

通過(guò)劫持事件處理器__e,我們可以實(shí)現(xiàn)觸發(fā)事件時(shí)執(zhí)行我們想要的邏輯了。

分析事件對(duì)象并編寫事件處理器劫持函數(shù)eventProxy

微信小程序事件對(duì)象描述文檔:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html#%E4%BA%8B%E4%BB%B6%E5%AF%B9%E8%B1%A1

在上一步里我們劫持了Component,并且成功獲得了事件處理器__e,那么編寫針對(duì)事件處理器的劫持函數(shù)吧。

function eventProxy(methodList, methodName) {
  const _funcProto_ = methodList[methodName];
  methodList[methodName] = function() {
    _funcProto_.apply(this, arguments);
    let prop = {};
    if (isObject(arguments[0])) {
      if (Object.keys(arguments[0]).length > 0) {
        //arguments[0]即為事件對(duì)象的屬性
      }
    }
  }
}

uniapp-mp-wx中,事件對(duì)象通常具有如下屬性:

["type", "timeStamp", "target", "currentTarget", "mark", "detail", "touches", "changedTouches", "mut", "_userTap", "mp", "stopPropagation", "preventDefault"]

其中,對(duì)于數(shù)據(jù)埋點(diǎn)尤其有用的是如下四個(gè)屬性:

  • type:描述事件類型。常見(jiàn)種類有tap(click)、input、blur、focus等
  • currentTarget:事件綁定的當(dāng)前組件

從Vue模板編譯一節(jié)中可知,我們應(yīng)該關(guān)注currentTarget.dataset.eventOpts這個(gè)屬性,這里記載了事件被觸發(fā)時(shí)的一些信息。

interface currentTarget {
  id: string,				//當(dāng)前元素的id
  dataset: Object		//當(dāng)前元素上由data-開(kāi)頭的自定義屬性組成的集合
}

mark:可以使用 mark 來(lái)識(shí)別具體觸發(fā)事件的 target 節(jié)點(diǎn)。此外, mark 還可以用于承載一些自定義數(shù)據(jù)(類似于 dataset )。

當(dāng)事件觸發(fā)時(shí),事件冒泡路徑上所有的 mark 會(huì)被合并,并返回給事件回調(diào)函數(shù)。(即使事件不是冒泡事件,也會(huì) mark 。)

如果想要得到一些詳細(xì)的錨點(diǎn)數(shù)據(jù),可以在代碼中做一些mark標(biāo)記。

<view mark:myMark="last" bindtap="bindViewTap">
  <button mark:anotherMark="leaf" bindtap="bindButtonTap">按鈕</button>
</view>
<script>
Page({
  bindViewTap: function(e) {
		//Object.keys(e.mark)即為觸發(fā)事件的節(jié)點(diǎn)經(jīng)過(guò)的所有mark
    e.mark.myMark === "last" // true
    e.mark.anotherMark === "leaf" // true
  }
})
</script>

detail:自定義事件所攜帶的數(shù)據(jù),如表單組件的提交事件會(huì)攜帶用戶的輸入,媒體的錯(cuò)誤事件會(huì)攜帶錯(cuò)誤信息,詳見(jiàn)組件定義中各個(gè)事件的定義。

點(diǎn)擊事件的detail 帶有的 x, y 同 pageX, pageY 代表距離文檔左上角的距離。

這里給出tap及input事件返回的detail結(jié)構(gòu):

interface tapDetail {
  x: number,  //距離文檔X軸零點(diǎn)的距離,零點(diǎn)為文檔左上角
  y: number		//距離文檔Y軸零點(diǎn)的距離
}

interface inputDetail {
  value: string,		//用戶輸入的值
  cursor: number,		//觸發(fā)事件時(shí)光標(biāo)所在的位置
  keyCode: number		//觸發(fā)事件時(shí)用戶輸入的keyCode
}

結(jié)合如上屬性,簡(jiǎn)單地完善一下事件劫持器吧:

function eventProxy(methodList, methodName) {
  const _funcProto_ = methodList[methodName];
  methodList[methodName] = function() {
    _funcProto_.apply(this, arguments);
    let prop = {};
    if (isObject(arguments[0])) {
      if (Object.keys(arguments[0]).length > 0) {
        //記錄觸發(fā)頁(yè)面信息
        const pages = getCurrentPages();
        const currentPage = pages[pages.length - 1];
        prop["$page_path"] = currentPage.route; //頁(yè)面路徑
        prop["$page_query"] = currentPage.options || {}; //頁(yè)面攜帶的query參數(shù)
        const type = arguments[0]["type"];
        const current_target = arguments[0].currentTarget || {};
        const dataset = current_target.dataset || {};
        prop["$event_type"] = type;
        prop["$event_timestamp"] = Date.now();
        prop["$element_id"] = current_target.id;
        const eventDetail = arguments[0].detail;
        prop["$event_detail"] = eventDetail;
        if (!!dataset.eventOpts && type) {
          if (type == "tap") { //只記錄點(diǎn)擊事件
            const event_opts = dataset.eventOpts;
            if (Array.isArray(event_opts) && event_opts[0].length === 2) {
              let eventFunc = [];
              event_opts[0][1].forEach(event => {
                eventFunc.push({
                  name: event[0],
                  params: event[1] || ''
                })
              })
              prop["$event_function"] = eventFunc;
            }
          }
          postWeData(prop); //在此處上傳記錄的事件數(shù)據(jù)
        }
      }
    }
  };
}

三、完整代碼結(jié)構(gòu)

(function() {
  const isObject = function(obj) {
    if (obj === undefined || obj === null) {
      return false;
    } else {
      return toString.call(obj) == "[object Object]";
    }
  };
  // 劫持Component
  const _componentProto_ = Component;
  Component = function(options) {
    //options.methods內(nèi)有uniapp注入的事件處理器__e及mpHook
    Object.keys(options.methods).forEach(methodName => {
      if (methodName == "__e") {
        //劫持事件處理器
        eventProxy(options.methods, methodName)
      }
    })
    _componentProto_.apply(this, arguments);
  }

  function eventProxy(methodList, methodName) {
    //事件處理器的劫持
  }

  const postWeData = function(data) {
    //埋點(diǎn)上傳器
    console.log(data)
  }
})()

使用:在項(xiàng)目的main.js里引入即可

//main.js
import './common/WeData/index.js'

四、后記

上述事件劫持器只是一個(gè)例子,實(shí)現(xiàn)了基本的tap事件記錄。實(shí)際上筆者通過(guò)擴(kuò)展配置讀取的方式來(lái)完成更加便捷的埋點(diǎn)操作,后續(xù)只需產(chǎn)品給出希望收集的事件名,開(kāi)發(fā)在固定的配置文件中寫好代碼中事件觸發(fā)的函數(shù)名即可實(shí)現(xiàn)tap白名單記錄功能。更加詳細(xì)的埋點(diǎn)功能可以通過(guò)閱讀分析事件對(duì)象小節(jié)來(lái)擴(kuò)展,在此僅做拋磚引玉。

到此這篇關(guān)于Uniapp微信小程序?qū)崿F(xiàn)全局事件監(jiān)聽(tīng)并進(jìn)行數(shù)據(jù)埋點(diǎn)的文章就介紹到這了,更多相關(guān)Uniapp微信小程序全局事件監(jiān)聽(tīng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論