var namespace = {};void function (window, document, ns, undefined){ ns = window[ns]; if(!window.performance || ns.Performance){ //performance api , (Date : 2011-11), ie9+(包括相容模式), chrome11+, Firefox7+ . (Safari,Opera. 沒有實現) return; } var mixin = function (oTarget ,oSource) { var s; for (s in oSource){ if(oSource.hasOwnProperty(s)){ oTarget[s] = oSource[s]; } } return oTarget; }, performance = window.performance, timing = performance.timing, navigation = performance.navigation, create = function (properties) { var obj; if(Object.create){ return Object.create(null, properties); } obj = {}; for (var s in properties){ /* ie9+ 相容模式. performance 可用,但ES5 相關特性不可用. 但是getter 的設計,本來是為瞭解決.擷取時間不正確的狀況處理的. 比如 responseTime 的設計. 為瞭解決這個問題.引入了update介面. */ if(properties.hasOwnProperty(s)){ if(typeof properties[s].get == 'function'){ //如果屬性是一個函數,那麼就應該是需要延遲擷取的屬性.則調用相關get方法擷取當前值. obj[s] = properties[s].get(); }else{ obj[s] = properties[s].value; } } } return obj; }, properties = { isDirectClientCache : {//是否直接走的用戶端緩衝. value : function () { if(navigation.type === 1){//重新整理的訪問,自然不可能直接走cache. return false; } if(timing.requestStart === 0){//Firefox7,當直接走緩衝時request,connect相關時間節點都為0 return true; } if(timing.connectStart === timing.connectEnd){//Freifox8+,Chrome11+,IE9+ //應注意的是,有時候304時, 如果資源問本地檔案,chrome會因為connect串連建立過快,而導致 此處為true. 但基本線上應不會出現此類問題. //另一個解決思路是對比responseStart和responseEnd.但這個受幹擾影響更多.更不靠譜. return true; } return false; }() }, navigationType : { value : navigation.type /* 0 nomal get or link . 1 reload. 2 back forward. 3 reserved . (oters method.) 但應注意的是,Firefox8+,開始又恢複了Firefox3.5-的老問題,即返回(後退)的方式訪問頁面,onload不會被觸發. 所以如果搜集資訊上報,是在onload回調中,就要警惕,Firefox8+瀏覽器,會導致上報指令碼,沒有被執行的問題. Opera,Safari 也存在同樣的問題,將來這兩款瀏覽器支援performance時也應注意. 另外一個問題就是,domContentLoaded事件存在onload同樣的問題.只有IE和Chrome,沒有這個問題. 最靠譜的做法是註冊到onpageshow上(如果瀏覽器支援的話). 否則.除非我們犧牲精準度.來直接執行指令碼... 輔onpageshow 事件支援情況: onpageshow的支援列表: Firefox 1.5 + Safari5+ Chrome4+ Opera12,IE10 PP2,至今仍未支援 . */ }, redirectCount : {//只能統計到同源的重新導向次數. value : navigation.redirectCount }, redirectTime : {//重新導向消耗的時間. value : timing.redirectEnd - timing.redirectStart }, /* fetchTime,我們 拿到,沒什麼價值,也沒有最佳化的可能性存在. fetchTime : { value : Math.max(timing.domainLookupStart - timing.fetchStart, 0) //Firefox的domainLookupStart,如果因沒有發生dns look up ,則該值不是fetchStart,而是navigationStart的值.並沒有遵守標準. }, */ domainLookupTime : { value : timing.domainLookupEnd - timing.domainLookupStart }, connectTime : {//注意,這個不是串連保持的時間,而是與伺服器端建立串連所花費的時間. value : timing.connectEnd - timing.connectStart }, requestTime : {//因為木有準確的擷取RequestEnd的有效辦法.所以這個值實際上是我們接收到伺服器響應資料的那個時間 減去發出HTTP請求,所花費的時間. //注意,Firefox 7,在走cache時.requestStart,會為0.Firefox8以修複.當無法正確擷取時,我們應該返回 -1. value : timing.responseStart - (timing.requestStart || timing.responseStart + 1) } }, deferrProperites = { //延時的屬性 responseTime : { /* .responseEnd 返回使用者代理程式接收到最後一個字元的時間,和當前串連被關閉的時間中,更早的那個. 同樣,文檔可能來自伺服器、緩衝、或本地資源. 補充: 此值的讀取應該是在我們可以確保真的是Response結束以後. 比如window.onload. 因為考慮到chunked輸出的情況. 那麼我們指令碼執行,並擷取該值時,響應還沒有結束. 這就會導致擷取時間不準確. bugs : 1. IE10 PP2, 以及Chrome17- ,走本機快取時.在文檔中間的指令碼執行時去讀取此值, 將為0. IE9本來沒有問題,結果IE10 PP2,反倒有了問題. 2. Chrome16-,(Chrome17,已修複此問題.)在地址欄輸入相同地址,走本機快取時. responseEnd的時間,居然早於responseStart的時間. (不得不承認,這簡直就是奇葩啊!) 3. Chrome17-,從頁面a,到地址b,再重新導向到地址c, 此時如果地址c是走緩衝.則. ResponseEnd的時間,會早於ResponseStart的時間.(好吧,我們把希望寄予Chrome18好了.) 實現差異:(由於草案中,並未提及,當文檔被分段輸出後.在中間文檔資料,接受過程中,responseEnd應如何處理,導致瀏覽器實現存在差異.) IE9 - IE10 PP2 , Firefox8-Firefox10,在不走存在Response階段(非走cache的情況下.).會根據每次接收到的資料區塊的時間,去更新.responseEnd的時間. Chrome17-,Firefox7,則在分段資料的接受過程中,不會更新.responseEnd的時間,其值,始終為0. 基於不確定性,所以該值被擷取的時如果過早,就返回 -1, 否則返回正確的值. DomContentLoaded., 即語義上的DOM Parse 也早已結束了.而且其他資源也都加在完畢了. 而更早的 domInteractive .IE系列存在bug: IE9 - IE10 PP2(IE10,走cache情況除外). 在分段輸出文檔的情況下,該值並不是全部文檔解析完成後的時間,而是第一個資料區塊被解析完成的時間. 實現差異:(由於草案中,並未提及,文檔解析並未結束時,其預設值的應該是多少.導致瀏覽器實現有差異.) 按我個人理解,並未解析結束,應該為0. 但是IE似乎對這個東西理解不太一樣. 其他瀏覽器會是0. 但是. IE9-IE10 PP2,則會比較有趣.即使是分段輸出,我取到的值.也和onload以後去到的,domInteractive的值是一致的. 導致這一神奇現象的原因是,正式IE系的bug所導. 該時間是錯誤的引用了,DOM解析完成第一個資料區塊的時間.而不是整個文檔的. 但是糾結起來就要挖掘更深層次的原因了. 因為草案只說該值體現的是,使用者代理程式把"current document readiness" 設定為 "interactive"的時間. 如果IE系處理分段輸出的html文檔,向來都是這樣做的。那麼該值與其他瀏覽器的差異。也是可以理解的. 基於,以上綜合原因,所以決定藉助domContentLoadedEventStart 檢測來確定responseTime是否可信. 如不可信,則返回-1. */ get : function () {//此值IE9僅共參考.並不值信任.IE10則可信任. var val = timing.responseEnd - timing.responseStart; if(timing.domContentLoadedEventStart){ if(val < 0 ) {//修正chrome16- 走cache時的bug. val = 0; } }else { val = -1; } return val; } }, domParsingTime : {//IE系不可信任.職能期待IE系將來的實現了. get : function () { /* 注意IE9 - IE10 PP2,bug. IE下,這個計算出來的值,僅共參考.完全不靠譜. iE9 - IE10 PP2 , 當文檔是chunked方式輸出的時候.總是要等最後一個chunked被瀏覽器接收後, domLoading才會有有效值. 也就是說,IE中目前的狀況是.domLoading.無論如何,都要晚於responseEnd. 其他瀏覽器則無此問題. 但是這個問題導致我們計算IE下DOM Parsing等後續的一系列時間不準確. 即 domInteractive - domLoading 甚至會經常得到0. */ return timing.domContentLoadedEventStart ? timing.domInteractive - timing.domLoading : -1; } }, resourcesLoadedTime : {//IE系列不可信任,原因當然是分段輸出問題.domLoading不準確,導致後續計算都不準確的問題.所以僅供參考. /* 此時間指的是.頁面所有onload計算範圍內的資源載入結束後到文檔開始進行dom parse的時間段. */ get : function () { return timing.loadEventStart ? timing.loadEventStart - timing.domLoading : -1; } }, firstPaintTime : {//從導航到頁面首次渲染所消耗的時間.(該屬性並非標準的屬性.而是微軟私人的.目前IE9+支援) get : function () {//如果都不支援該屬性,或,讀取該屬性時,頁面還沒有渲染,則返回 -1; //這段代碼是一個願望,希望將來,firstPaint 將進入草案,並被瀏覽器實現.哪怕他們依賴首碼. var t = timing.firstPaint || timing.msFirstPaint || timing.mozFirstPaint || timing.webkitFirstPaint || timing.oFirstPaint; return t ? t - timing.fetchStart : -1; } }, domContentLoadedTime : {//從導航 到 頁面domReady所消耗的時間. get : function () { return timing.domContentLoadedEventStart ? timing.domContentLoadedEventStart - timing.fetchStart : -1; } }, windowLoadedTime :{//擷取當前文檔fetchStart 到 window.onload,所花費的總時間. get : function () { return timing.loadEventStart ? timing.loadEventStart - timing.fetchStart : -1; } } }, pfm = create(mixin(properties, deferrProperites)); ns.Performance = pfm; if(Object.defineProperty){//單體對象的方法,沒有必要掛在到prototype上. so .. Object.defineProperty(pfm, 'update', {value : function () {return this;}}); }else{ //for IE9+,相容模式. 需要每次依賴update方法,每次都去手動更新那些不靠譜的東西. pfm.update = function () { for (var s in deferrProperites){ if(deferrProperites.hasOwnProperty(s)){ pfm[s] = deferrProperites[s].get(); } } } } }(window, document, 'namespace');window.onload = function () { setTimeout(function () { var pfm = namespace.Performance; pfm.update(); alert([ 'isDirectClientCache : ' + pfm.isDirectClientCache, 'navigationType : ' + pfm.navigationType, 'redirectCount : ' + pfm.redirectCount, 'redirectTime : ' + pfm.redirectTime, 'domainLookupTime : ' + pfm.domainLookupTime, 'connectTime : ' + pfm.connectTime, 'requestTime : ' + pfm.requestTime, 'responseTime : ' + pfm.responseTime, 'domParsingTime : ' + pfm.domParsingTime, 'resourcesLoadedTime : ' + pfm.resourcesLoadedTime, 'firstPaintTime : ' + pfm.firstPaintTime, 'domContentLoadedTime : ' + pfm.domContentLoadedTime, 'windowLoadedTime : ' + pfm.windowLoadedTime ].join('\n')); }, 300);}
一切都在注釋裡了. 有興趣可以看看.
要補充的是,這段代碼的測試環境: Chrome11+,Firefox7+,IE9+, 別的瀏覽器就沒辦法了. 另外,代碼適當使用了ES5的api . 如果不是為了這一點,那麼其實這套api應該設計成另外的樣子. 而不是屬性器. 因為我終於是被IE9的相容模式,給攪和了. 為了相容.不得不設計出update 介面出來...