一、window.onerror
indow.onerror = function(message, source, lineno, colno, error) { ... }
onerror
有五個入參:
參數 | 解釋 |
---|---|
msg | 錯誤信息 |
url | 錯誤所在文件 |
line | 錯誤所在代碼行,整型 |
colno | 錯誤所在代碼列,整型 |
erro | 錯誤Error對象 |
只需要把這些信息回傳到server端即可,再配合sourcemap的話我們就可以知道是源碼中的哪一行出錯了,從而實現完美的錯誤實時監控系統了。然而要完美還是需要做很多工作的。
1. 基本特性
以下三種方式可以引發onerror
:
- 運行時錯誤,例如無效的對象引用或安全限制
- 下載錯誤,如圖片
- 在IE9中,獲取多媒體數據失敗也會引發
可以通過設置
returnValue=true
,或直接return true
來阻止瀏覽器顯示錯誤信息。但不會阻止script debuggers
彈出的調試框。只有運行錯誤才會觸發
onerror
,語法錯誤不會觸發。<script> 標簽不支持
onerror
。定義在 <body> 標簽上的
onerror
屬性相當于window.onerror
(經測試,Firefox、Opera支持,IE9、chrome無反應)。
2. 瀏覽器兼容性
QuirksM3ode列出的各瀏覽器對onError的支持情況
?Chrome 13+
?Firefox 6.1+
?Internet Explorer 5.5+
?Safari 5.1+
?Opera 11.61+ (QuirksMode 測試到11.51尚不支持,我手頭上的11.61已支持)
除window
對象外,支持onerror
的元素:
元素 | 支持情況 |
---|---|
<img> | 全支持 |
<script> | IE9/IE10/safari 5.1+/chrome 13+ 支持 |
<css> 和 <iframe> | 不支持onerror |
3. 跨域
我們的js文件一般都是和網站不同域的,這是為了提高頁面的渲染速度以及架構的可維護性(單獨CDN域名,充分利用瀏覽器http并發數)。這樣的js文件中發生錯誤我們直接監控你會發現你啥信息都收集不到。
實驗一:
我們的站點是a.com,頁面中引用了兩個js文件,一個是a.com域名下的a.js,一個是b.com域名下的b.js,我們在a.js文件中添加window.onerro
r監控,在b.js文件中主動拋出錯誤
<!-- index.html -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js" ></script>
// a.js
window.onerror = function (message, url, line, column, error) {
console.log('log---onerror::::',message, url, line, column, error);
}
// b.js
throw new Error('this is the error happened in b.js');
我們可以看到下圖的結果,onerror
函數拿到的信息是Script error, a 0 null,啥卵用都沒有,你完全不知道發生了什么錯誤,哪個文件發生的錯誤。
這是瀏覽器所做的安全限制措施,當加載自不同域(協議、域名、端口三者任一不同)的腳本中發生語法(?)錯誤時,為避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的"Script error."。
但是我們確實是需要知道發生錯誤的具體信息啊,不然監控就沒有意義了。既然又是類同源限制的問題,那肯定是可以通過CORS來解決了。
實驗二
我們給b.js加上Access-Control-Allow-Origin:*
的response header
,后面我們會發現還是沒啥變化。
實驗三
我們繼續給b.js加上crossorigin
屬性,發現可以了,想要的信息都收集到了
<!-- index.html -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js" crossorigin></script>
結論:如果想通過
onerror
函數收集不同域的js錯誤,我們需要做兩件事:
- 相關的js文件上加上
Access-Control-Allow-Origin:*
的response header
- 引用相關的js文件時加上
crossorigin
屬性
注意: 以上兩步缺一不可。實驗二告訴我們,如果只是加上Access-Control-Allow-Origin:*
的話,錯誤還是無法捕獲。如果只加上crossorigin
屬性,瀏覽器會報無法加載的錯誤,如下圖:
可是。。。
如果你使用
sentry
的raven.js
的話,你會發現你什么都不用做,他依然可以幫你捕獲到一些錯誤的非常具體信息,確實是有點神奇啊,具體怎么做的?關鍵就是raven
源碼中的install方法中調用的_instrumentTryCatch
函數起了作用,他通過tryCatch
的方式wrap
了一些關鍵函數,使得這些函數里的報錯能夠捕獲,_instrumentTryCatch
的具體實現原理我們后面再說
install: function() {
var self = this;
if (self.isSetup() && !self._isRavenInstalled) {
TraceKit.report.subscribe(function () {
self._handleOnErrorStackInfo.apply(self, arguments);
});
if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
self._instrumentTryCatch();// 通過tryCatch來wrap關鍵函數,從而獲得error的具體信息
}
if (self._globalOptions.autoBreadcrumbs)
self._instrumentBreadcrumbs();
// Install all of the plugins
self._drainPlugins();
self._isRavenInstalled = true;
}
Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
return this;
},
其實如果你真的什么都不做,raven
也只是能捕獲一些異步錯誤,同步錯誤還是無法捕獲,所以你即使使用了sentry
等第三方的錯誤收集庫,你還是需要加上Access-Control-Allow-Origin:*
和crossorigin
屬性
二、Performance
The Performance interface provides access to performance-related information for the current page. It's part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API.
Performace
接口允許訪問當前頁面性能相關的信息。它是High Resolution Time API
的一部分。但是它被Performance Timeline API
, the Navigation Timing API
,the User Timing API
, 和the Resource Timing API
擴展增強了。實際上Performance
的主要功能都是由這幾個API提供的。
單單看上面的內容,大家一定還是會感到疑惑,這performace
究竟是個什么東西?ok,我們直接打開百度的網頁,然后在控制臺里輸出Window.performance
(window.performace
返回的就是performance
對象)
Performance
對象里出現了4個屬性。
timing
timing
對象提供了各種與瀏覽器處理相關的時間數據。具體如下表
名稱 | 作用(這里所有時間戳都代表UNIX毫秒時間戳) |
---|---|
connectEnd |
瀏覽器與服務器之間的連接建立時的時間戳,連接建立指的是所有握手和認證過程全部結束 |
connectStart |
HTTP請求開始向服務器發送時的時間戳,如果是持久連接,則等同于fetchStart 。 |
domComplete |
當前網頁DOM結構生成時,也就是Document.readyState 屬性變為“complete ”,并且相應的readystatechange 事件觸發時的時間戳。 |
domContentLoadedEventEnd |
當前網頁DOMContentLoaded 事件發生時,也就是DOM結構解析完畢、所有腳本運行完成時的時間戳。 |
domContentLoadedEventStart |
當前網頁DOMContentLoaded 事件發生時,也就是DOM結構解析完畢、所有腳本開始運行時的時間戳。 |
domInteractive |
當前網頁DOM結構結束解析、開始加載內嵌資源時,也就是Document.readyState 屬性變為“interactive ”、并且相應的readystatechange 事件觸發時的時間戳。 |
domLoading |
當前網頁DOM結構開始解析時,也就是Document.readyState 屬性變為“loading ”、并且相應的readystatechange 事件觸發時的時間戳。 |
domainLookupEnd |
域名查詢結束時的時間戳。如果使用持久連接,或者從本地緩存獲取信息的,等同于fetchStart
|
domainLookupStart |
域名查詢開始時的時間戳。如果使用持久連接,或者從本地緩存獲取信息的,等同于fetchStart
|
fetchStart |
瀏覽器準備通過HTTP請求去獲取頁面的時間戳。在檢查應用緩存之前發生。 |
loadEventEnd |
當前網頁load 事件的回調函數結束時的時間戳。如果該事件還沒有發生,返回0。 |
loadEventStart |
當前網頁load 事件的回調函數開始時的時間戳。如果該事件還沒有發生,返回0。 |
navigationStart |
當前瀏覽器窗口的前一個網頁關閉,發生unload 事件時的時間戳。如果沒有前一個網頁,就等于fetchStart
|
redirectEnd |
最后一次重定向完成,也就是Http響應的最后一個字節返回時的時間戳。如果沒有重定向,或者上次重定向不是同源的。則為0 |
redirectStart |
第一次重定向開始時的時間戳,如果沒有重定向,或者上次重定向不是同源的。則為0 |
requestStart |
瀏覽器向服務器發出HTTP請求時(或開始讀取本地緩存時)的時間戳。 |
responseEnd |
瀏覽器從服務器收到(或從本地緩存讀取)最后一個字節時(如果在此之前HTTP連接已經關閉,則返回關閉時)的時間戳 |
responseStart |
瀏覽器從服務器收到(或從本地緩存讀取)第一個字節時的時間戳。 |
secureConnectionStart |
瀏覽器與服務器開始安全鏈接的握手時的時間戳。如果當前網頁不要求安全連接,則返回0。 |
unloadEventEnd |
如果前一個網頁與當前網頁屬于同一個域下,則表示前一個網頁的unload 回調結束時的時間戳。如果沒有前一個網頁,或者之前的網頁跳轉不是屬于同一個域內,則返回值為0。 |
unloadEventStart |
如果前一個網頁與當前網頁屬于同一個域下,則表示前一個網頁的unload 事件發生時的時間戳。如果沒有前一個網頁,或者之前的網頁跳轉不是屬于同一個域內,則返回值為0。 |
了解上面timing
提供的各種屬性之后,我們可以計算出網頁在加載時候某一部分消耗的具體時間,可以精確到千分之一毫秒。例如要計算出發送請求到接受完數據所消耗的時間。
const timing = window.performance.timing
const contactDuration = timing.responseEnd - timing.requestStart
navagation
PerformanceNavigation
接口呈現了如何導航到當前文檔的信息。PerformanceNavigation
有兩個屬性,一個是type,表示如何導航到當前頁面的,主要有4個值。
- type=0:表示當前頁面是通過點擊鏈接,書簽和表單提交,或者腳本操作,或者在url中直接輸入地址訪問的。
- type=1: 表示當前頁面是點擊刷新或者調用
Location.reload()
方法訪問的。 - type=2: 表示當前頁面是通過歷史記錄或者前進后退按鈕訪問的。
- type=255: 其他方式訪問的
另外一個屬性是redirectCount
,表示到達當前頁面之前經過幾次重定向。
其他屬性
performance.timeOrigin
表示performance
性能測試開始的時間。是一個高精度時間戳(千分之一毫秒)
performance.onresourcetimingbufferfull
表示當瀏覽器資源時間性能緩沖區已滿時會觸發的回調函數。下面是mdn上關于這個屬性的一個demo。這個demo的主要內容是當緩沖區內容滿時,調用buffer_full
函數。
function buffer_full(event) {
console.log("WARNING: Resource Timing Buffer is FULL!");
performance.setResourceTimingBufferSize(200);
}
function init() {
// Set a callback if the resource buffer becomes filled
performance.onresourcetimingbufferfull = buffer_full;
}
<body onload="init()">
performance.memory
一個非標準屬性,由chrome瀏覽器提供。這個屬性提供了一個可以獲取到基本內存使用情況的對象。
Performance.mark
The mark() method creates a timestamp in the browser's performance entry buffer with the given name.
這段話可以分解出三個關鍵詞。首先timestamp
,這里的timestamp
指的是高精度時間戳(千分之一毫秒),其次是performance entry buffer
。performance entry buffer
指的是存儲performance
實例對象的區域,初始值為空。最后就是given name
,表示生成的每一個timestamp
都有相應的名稱。
所以這句話就可以理解成,在瀏覽器的performance entry buffer
中,根據名稱生成高精度時間戳。也就是很多人說過的“打點”。
Performance.measure
The measure() method creates a named timestamp in the browser's performance entry buffer between two specified marks (known as the start mark and end mark, respectively). The named timestamp is referred to as a measure.
這段定義和上面mark
的定義有些類似,其最核心的不同點在于這句話。between two specified marks
。所以measur
是指定兩個mark
點之間的時間戳。如果說mark
可以理解為"打點"的話,measure
就可以理解為"連線"。
一個小例子
我們來看一個使用mark
和measure
的小demo,這個例子也是引用MDN,這里做一下簡單講解。
// 標記一個開始點
performance.mark("mySetTimeout-start");
// 等待1000ms
setTimeout(function() {
// 標記一個結束點
performance.mark("mySetTimeout-end");
// 標記開始點和結束點之間的時間戳
performance.measure(
"mySetTimeout",
"mySetTimeout-start",
"mySetTimeout-end"
);
// 獲取所有名稱為mySetTimeout的measures
var measures = performance.getEntriesByName("mySetTimeout");
var measure = measures[0];
console.log("setTimeout milliseconds:", measure.duration)
// 清除標記
performance.clearMarks();
performance.clearMeasures();
}, 1000);
結果:
可以看到,高精度的時間戳是非常精準的。(我們知道由于執行隊列的原因,
setTimeout
不會在給定的1000ms之后就立即執行)Performance API
提供了很多方便測試我們程序性能的接口。比如mark
和measure
。很多優秀的框架也用到了這個API進行測試,比如我最近在看的Vue框架。它里面就頻繁用到了mark
和measure
來測試程序性能。所以想要開發高性能的web程序,了解Performace API
還是非常重要的。