埋點技術在不少流量較大的應用中常用到的熱門技術,旨在經過在用戶進行操做或者請求的時候,記錄下這些操做,而後針對記錄的操做數量、操做頻率才從新設計產品,提升用戶體驗,而埋點技術在前端的實現相比起後端來講也較爲多見,這是由於用戶的操做並不必定會帶來http請求,並且前端SPA盛行後後端很難監測後用戶對頁面瀏覽的狀況,因此重擔固然就壓在前端身上來。javascript
小插曲:其實在大部分的功能性應用中,經過對各功能模塊的埋點監測,Banner輪播圖的用戶訪問率很是低,也就是所Banner的存在大部分狀況下只是一個好看的裝飾品,這一點也引起了大量產品經理的爭論。html
PV、UV、IP在網站運營和管理中是很是常見的3個Metric,也是產品經理心心念念天天茶不思飯不想的三個寶貝,對於應用開發者來講,也必然會接到相關的開發需求,那麼待會兒就會來聊一下這三個小寶貝。前端
PV(Page View)訪問量, 即頁面瀏覽量或點擊量,衡量網站用戶訪問的網頁數量;在必定統計週期內用戶每打開或刷新一個頁面就記錄1次,屢次打開或刷新同一頁面則瀏覽量累計。 說白了就是統計一下某些頁面在一段時間好比一天內被訪問了多少次,哪怕是同一個用戶訪問屢次也不要緊,說不定這個用戶就是特別鍾愛這個頁面呢?因此重複訪問是計算爲有效的。vue
IP(Internet Protocol)獨立IP數,是指1天內多少個獨立的IP瀏覽了頁面,即統計不一樣的IP瀏覽用戶數量。同一IP無論訪問了幾個頁面,獨立IP數均爲1;不一樣的IP瀏覽頁面,計數會加1。 IP是基於用戶廣域網IP地址來區分不一樣的訪問者的,因此,多個用戶(多個局域網IP)在同一個路由器(同一個廣域網IP)內上網,可能被記錄爲一個獨立IP訪問者。若是用戶不斷更換IP,則有可能被屢次統計。 說白了就是根據用戶的IP來標識一個用戶,以統計有多少用戶訪問應用。java
可是PV、UV和IP的統計方式都是會有偏差的。ajax
例如統計PV的時候,來源不明的水軍可能會利用腳本不斷的重複訪問某個網頁,使得PV數不正常激增,此時PV > 真實數據。後端
統計UV的時候,若是有個用戶不斷清除cookie或者換了不少臺設備來訪問的話,那麼這個用戶會被統計屢次,此時 UV > 真實數據。 若是多個使用者共用一個帳號和同一個設備的時候,此時UV < 真實數據。api
統計IP的時候,若是用戶切換了手機的網絡模式(4g -> wifi)此時IP > 真實數據,多個使用者共用同一個設備的時候,此時IP < 真實數據。 ** 也就是偏差老是存在的,在數據量較多,統計週期較長並明顯存在某些規律的狀況下,其實這些偏差也是能夠忽略的。跨域
其實不管是PV、UV的統計(IP的統計通常後端能夠獨立完成,不須要前端的參與),仍是埋點技術,說穿了就是要在合適的時機向後端發送一個合適的請求。瀏覽器
什麼時機纔算是合適的時機呢?這也要具體狀況具體分析,好比,PV量統計通常是在路由跳轉監聽中進行,咱們能夠在一個全局性的路由鉤子中實現。而UV的統計則依賴後端多一些,前端只須要把種好的cookie信息再攜帶到任意請求的請求體中便可,後端來進行過濾篩選。
埋點技術場景則複雜一些,好比在某些按鈕的點擊操做中,滾動條的監聽事件處理程序中等等。
PV、UV以及埋點中請求的時機各有不一樣,發送請求的方式其實也會有選擇的空間。
常見的請求的方式例如使用ajax或者fetch來發送GET/POST請求固然能夠解決須要,可是這樣的方式每每消耗比較大,相應速度也會較慢,好處在於傳輸到後端的數據能夠攜帶的稍微多一些,請求回來的數據通常都是JSON格式的數據,處理起來也很方便。
可是用Ajax或者fetch的話,極可能帶來跨域的問題,由於有的時候記錄埋點數據和PV、UV數據的服務器是與應用服務器分離的。
並且其實不少狀況下前端在發送此類請求的時候,並不會傳遞不少信息,每每只是幾個簡單的query params字段,並且也並不期待服務端的返回信息來使用。
因此真正使用場景較多的實現方式是以img請求的方式來進行的。
上面收到大部分狀況下使用img請求的方式是最可行的,那麼是爲何呢?具體要怎麼實現呢?
首先,利用Image Beacon是不會碰到跨域問題的,瀏覽器的安全級別限制不針對這個。
並且請求體的體積較小,請求速度較快,網絡資源消耗較少。
既然如此,那爲何不選擇其餘類型文件的請求例如JS文件、CSS文件或者TTF字體類的文件呢?
是由於JS等類型的文件必需要插入到文檔樹中瀏覽器纔會發送請求,極可能帶來渲染的成本並且有可能會阻塞文檔樹的渲染,可是圖片請求則否則,構造圖片打點不只不用插入DOM,只要在js中new出Image對象就能發起請求,並且尚未阻塞問題,在沒有js的瀏覽器環境中也能經過img標籤正常打點,這是其餘類型的資源請求所作不到的。
說到這裏,還有最後一個選擇要作,就是應該使用什麼格式的Img呢?
首先,1x1像素是最小的合法圖片。並且,由於是經過圖片打點,因此圖片最好是透明的,圖片透明只要使用一個二進制位標記圖片是透明色便可,不用存儲色彩空間數據,能夠節約體積。
那麼BMP、PNG和GIF格式都支持透明格式,咱們要選擇哪一種呢?答案是GIF格式,據統計,最小的BMP文件須要74個字節,PNG須要67個字節,而合法的GIF,只須要43個字節,體積小請求成本當前就會較低。
最後總結一下爲何此類請求最佳方案是使用GIF格式的1 * 1尺寸透明圖片呢?
最最神奇的是,其實後端都不須要返回前端這個1 * 1的圖片,由於前端根本不須要將其渲染到body中,因此後端返回204 -- Not Content都沒啥關係,由於咱們的目標只有發送請求,請求發送以後,其餘的均可以不計較了。
接下來咱們來看一下在項目中要怎麼去實現PV、UV的統計以及埋點,在這裏我以Vue項目爲例。
/** * @module beacon實現模塊 */
import Qs from 'querystring'
/** * @function 具體的beacon動做 * @param {String} apiUrl 發送的接口請求 * @param {Object} params 具體發送的數據 * @example beaconAction('/api/recored', { url: '...' }).then((res) => {}).catch((error) => {}) */
export const beaconAction = (apiUrl, params) => {
/** 若是參數不是字符串則轉換爲query-string */
let _params = typeof params === 'string' ? params : Qs.stringify(params)
/** 建立Image對象來發送請求 */
let img = new Image(1, 1)
let src = `${apiUrl}?${_params}`
img.src = src
/** 其實並不須要將此圖片append到body中,請求此時已經發送,目的已經達成了 */
/** 利用load和error事件來監聽動做的完成,返回Promise便於操做 */
return new Promise((resolve, reject) => {
img.onload = function () {
resolve({ code: 200, data: 'success!' })
}
img.onerror = function (e) {
reject(new Error(e.error))
}
})
}
複製代碼
/** * PV/UV記錄 */
router.afterEach((to, from) => {
const path = to.path
/** 若是開啓了登錄權限驗證 */
if (process.env.AUTH_ENABLED) {
/** 除了登錄界面,其餘路由界面都請求記錄 */
if (to.path !== '/login') {
pathBeaconAction(path)
}
}
})
複製代碼
在vue中推薦你們使用自定義指令來實現埋點操做,封裝好合適的自定義指令後,就能夠在須要埋點的dom或者組件上經過很是輕鬆的指令設置來實現埋點效果。
在這裏推薦你們使用一個第三方指令** v-track** 來完成需求,你們能夠移步官網來查看詳細的使用說明。
使用方式:
<!-- 頁面行爲埋點(track-view爲v-track全局註冊的組件) -->
<track-view v-track:18015></track-view>
<track-view v-track:18015.watch="{ rest }"></track-view>
<track-view v-track:18015.watch.delay="{ rest }"></track-view>
<track-view v-if="rest" v-track:18015></track-view>
<!-- 事件行爲埋點(DOM) -->
<div v-track:18015.click="handleClick"></div>
<div v-track:18015.click="{ handleClick, item, index }"></div>
<div v-track:18015.click.async="{ handleSearch, rest }"></div>
<div v-track:18015.click.delay="handleClick"></div>
<!-- 事件行爲埋點(組件) -->
<cmp v-track:18015.click="handleClick"></cmp>
<cmp v-track:18015.[自定義事件名]="handleSearch"></cmp>
<cmp v-track:18015.[自定義事件名].delay="handleSearch"></cmp>
<cmp v-track:18015.[自定義事件名].async="{ handleSearch, rest }"></cmp>
<!-- 區域展示埋點(block 能夠是 DOM 或者組件) -->
<block v-track:18015.show></block>
<block v-track:18015.show.once></block>
<block v-track:18015.show.custom="{ ref: 'scroll' }"></block>
<block v-track:18015.show.custom.once="{ ref: 'scroll' }"></block>
複製代碼
修飾符說明:
.click
表示事件行爲的埋點.[custom-event]
表示自定義事件行爲的埋點.native
表示監聽組件原生click事件行爲的埋點.watch
表示頁面異步行爲的埋點.async
配合.click
指令,表示異步事件行爲的埋點.delay
表示埋點是否延遲執行,默認先執行埋點再執行回調.show
表示區域曝光埋點.once
配合.show
指令,只執行一次埋點.custom
配合.show
指令,表示使用自定義scroll事件埋點和PVUV的實現方案實在是太多了,而每一種方案都會引發不少人的討論,在此但願諸君也能有本身的想法,歡迎來討論。