上一篇文章《巧用域名發散,緩解單廣告位併發請求限制》中提到了我已經將廣告的數據請求寫成了單廣告位請求。既然數據請求都已是單廣告位的了,那麼曝光統計也理所應當是單廣告位的。html
pv是什麼?前端
咱們找一下百度百科的解釋: 就是頁面瀏覽量(page view),一般是衡量一個網絡新聞頻道或網站甚至一條網絡新聞的主要指標。網頁瀏覽數是評價網站流量最經常使用的指標之一,簡稱爲PV。jquery
曝光是什麼,區別在哪?web
簡單來講,就是用戶在瀏覽器中看到了咱們關注的東西后,給後臺的一個反饋。之因此把pv和曝光放在同一個專題下講,是由於兩者統計的都是瀏覽量。不一樣的是,pv關注點在頁面,統計的的是頁面的瀏覽量;而曝光關注的是我頁面中內容是否被用戶統計到,換個說法是面向DOM結構的瀏覽量統計。數據庫
原來是什麼樣的,我想要的是什麼樣的?數組
原來的曝光統計就是很普通的加載後發出一個請求。雖然每一個廣告位都有本身的曝光,可是這種曝光僅僅是能統計到今天投放的廣告的量,這樣這要從投放端看看投放了啥,再乘以頁面pv,就相等了。沒有太大實際意義,還白白的佔用的帶寬。我想要的是統計用戶看到的廣告是哪些,因爲用戶不必定會看完整個頁面,這樣每一個廣告位的瀏覽量必定是小於等於pv。這樣咱們就能夠統計到,哪些廣告位被砍的多,哪些廣告位被看到的次數少,甚至哪些廣告位從未被看到過。更進一步來講曝光統計還能夠進一步對頁面設計以及改版有必定的知道意義,咱們常說沒有數據的優化就是空談。對於大多數網站的頁面每每都是在摸索中前進,咱們並不知道什麼樣的設計更可以受到用戶的青睞,一次改版中有受歡迎的部分,也有不受歡迎的部分。瀏覽器
那麼咱們是如何知道大多數用戶的想法呢?就是反饋。調查問卷、隨機訪問能夠嗎?固然這樣作會獲得必定的反饋,同時也消耗了大量的人力物力。從另外一個角度來考慮,這種方式會帶來相似「薛定諤的貓」的效果,咱們主動的問卷活動,或者隨機訪問,自己就會干擾反饋的結果。好比我問別人,「我長得帥嗎?」對方礙於面子,或者時間趕不耐煩的敷衍或是爲了要活動獎品,草草的全都打鉤。這種反饋每每都不真實,好比上學的時候的教師滿意度調查,你們是否是都寫得滿意。網絡
比較好的方式是讓用戶不知道本身正被調查,經過觀察用戶在瀏覽頁面時的行爲來統計判斷用戶的感覺,嘴上每每會說謊,但身體卻很誠實。用戶在看網頁時的交互行爲每每是我的感覺最客觀的表達。數據結構
前端技術實現思路併發
首先是要分析哪些用戶行爲是咱們應該且可收集的,好比頁面pv、曝光、點擊等是比較公認的用戶行爲,他們之間也應該符合漏斗轉化模型。再有一些公司還會針對鼠標滑過,懸停時間等作一些等交互行爲甚至行爲細節(好比頁面渲染完成後的曝光時間、劃過次數、懸停時長)作一些統計。咱們今天就簡單說一下頁面pv、曝光、點擊中的曝光。
頁面pv,每每是經過js發出一個請求,這個請求最簡單的方法就是利用<img>標籤的src屬性發起get請求。
點擊就要分兩種狀況:自己有跳轉和自己無跳轉。若是自己有跳轉,通常的方式都是統計連接+redirect原目標連接的方式。若是是自己無跳轉,就要經過js綁定click事件,在事件回調函數中利用pv的方法發出一個個體請求,因此咱們一本會把發pv的方法封裝到公用模塊。
曝光是我想重點說的。曝光,顧名思義就是讓用戶看到纔算,換個說法就比較直接了,就是相關的DOM出如今瀏覽器的可視區。大多數的用戶端網頁都是縱向滾動條的形式,判斷是否出如今可視區,也變成了dom在文檔中位置和頁面滾動條位置的比較。
DOM位置 <= (滾動條位置 + 可視區高度)
固然我在這個需求的不斷優化過程當中經歷了幾個階段:
階段一:監聽滾動事件和遍歷DOM
這種思路主要來自於懶加載的實現,對頁面中的相關dom添加已定義的屬性,每次出發頁面的滾動事件,就遍歷帶有自定義屬性的DOM,
1 <div id="ad1" ad-pv="曝光請求地址"> 2 .......一大堆 3 </div>
並比對DOM和滾動條的位置,若是DOM位置小於等於滾動條+可視區高度,咱們就認爲「曝光了」,這是咱們就對其利用pv的公共函數發出請求,後臺接受請求,並計入統計數據庫。對於發出過曝光請求的DOM,咱們應該作出標記,以便下一次出發頁面滾動時過濾掉。好比將ad-pv的屬性值賦值爲空(ad-pv="")。下一次不處理爲空的DOM。
階段二:減小頻繁的調用
在上面這種方法下,就是監聽滾動條太頻繁,性能損耗太大,並且在異步的回調中涉及好多的DOM狀態修改。一次鼠標滾輪會出發好屢次的scroll事件回調。其實我只想要最後一次回調,該怎麼作呢? 答案很簡單——「減小函數被調用的次數」。具體「函數節流」或者「防反跳」的實現方法,網上能搜到不少,我在項目中直接使用的underscore中的debounce,至於爲何不用throttle,好多的文章都說「scroll 時更新樣式,如隨動效果用throttle」,可是我不是想在scroll時更新樣式,而是中止滾動時時最終回調一次,debounce更符合個人需求。
我將處理判斷位置併發曝光請求的這種事兒,都用debounce封裝了一層,await 設爲500ms。在scroll裏面調用debouncePv()
1 $(window).on("scroll",function(){ 2 debouncePv(); 3 });
這樣每一次鼠標滾輪,都會在中止500ms後觸發僅一次的判斷邏輯。
階段三:減小DOM遍歷的註冊機制與引用計數控制狀態
每次處理函數中連理DOM的好處是實時獲取還有那些沒曝光的DOM,可是也帶來了一些問題,好比DOM上存儲曝光請求地址本就顯得不合理;遍歷DOM樹的耗時時相比於滾輪的頻率也不小,若是不加debounce,還真說很差下一次的回調和當前的DOM遍歷那個先結束。咱們借鑑目前好多MV*框架中用數據結構來模擬一層DOM的思想。咱們用一個對象數組來存儲全部須要曝光的DOM的文檔位置,而且保證按位置從小到大排序。每次觸發,咱們的滾動條位置只須要和數組中每一項的文檔位置比較並對「符合位置區間」發出曝光請求,直到「不符合位置」的中止,這樣的好處有三個:
一、遍歷數組比遍歷DOM要快、也不用在DOM上加過多的標記;
二、不用每次全部的DOM比較一遍,能夠儘早中止;
三、對於「曝光」過的對象,能夠從數組中刪除,下一次處理函數直接從上一次中止的對象(文檔位置對應的數據)開始比較。
咱們應該如何生成這個數組呢?我不建議一開始的時候只遍歷一遍DOM,若是個人DOM是js異步加載渲染的或者頁面用了相似bigPipe的技術,就不適合了。個人廣告代碼要使用整個站點全部的頁面,就要支持動態添加。因此我在外層暴露的接口是用於添加曝光對象的。這樣隨時向數組中能夠添加。每次添加後對數組按照文檔位置排序一次。保證事件處理的時候用的是一個有序的數組。我管這種動態添加的方式叫註冊機制。
目前我對外暴露錄得接口只有添加接口。可是我畢竟綁定了一個事件,監聽事件會帶來必定的性能損耗。我不能老是在監聽吧。理想的方式是,有曝光對象就監聽,沒有曝光對象就解綁事件。還好個人數組是「註冊」進來的,我能夠在註冊時增長引用計數,發曝光請求的時候減小引用計數。這樣我就能夠在註冊(0->1)或是每次處理結束的時候判斷是綁或不綁事件。這樣我對外的接口仍是隻有註冊用的函數。
這裏介紹一個小技巧。解綁的時候,萬一頁面自己就對scroll綁定了事件,我這一解綁不就把頁面的事件給解除了嗎?好在jquery提供了一個事件命名空間的概念。我也是隻綁定和解除本身空間下的scroll。我綁定的空間名:ads-lazy-pv
1 lazyPvListener: function(){ 2 var self = this; 3 $(window).on("scroll.ads-lazy-pv",function(){ 4 self.debouncePv() 5 }); 6 }, 7 lazyPvOff: function(){ 8 var self = this; 9 $(window).off("scroll.ads-lazy-pv"); 10 }
總結:
這樣一個曝光的邏輯就開發完了,若是僅僅完成「任務」,其實並不難,但優化卻佔用了我很大的精力。這個曝光看起來沒用太多高深的技術,但開發起來卻非常用心。尤爲是想廣告代碼這種跑在全部頁面上的代碼,更要考慮到不少複雜的狀況,稍有紕漏將是公司財產的損失,身爲一個開發人員,每一步都要膽大心細。