這是我參與8月更文挑戰的第5天,活動詳情查看:8月更文挑戰javascript
上一篇講了搭建前端監控系統的接口監控,由於本系列文章主要是講關於前端收集數據的SDK的實現,這一篇來說錯誤監控,收集錯誤的過程也能學習到不少東西。咱們對於通過打包處理的前端應用還會使用sourcemap映射處理錯誤堆棧信息。html
SyntaxError:語法錯誤前端
Uncaught ReferenceError(引用錯誤)java
RangeError(範圍錯誤)react
TypeError(類型錯誤)webpack
URIError(URL 錯誤)ios
資源加載錯誤web
接口錯誤面試
promise未處catch的錯誤ajax
跨域腳本錯誤
咱們來區分一下這些錯誤。
語法錯誤:
<script> const value = 10 @! console.log(value) </script>
複製代碼
js引擎在解析scrpit代碼時,會先進行詞法分析, 將js轉換成[{}] 格式的tokens流, 爲何要這麼作呢? 由於後面要將tokens流轉換成抽象語法樹, 在生成語法樹的過程當中,引擎會有語法分析器對語法進行判斷, 上述代碼語法分析器判斷沒法生成一顆有效的語法樹,拋出語法錯誤。中止對代碼的解析。
拋出錯誤: Uncaught SyntaxError: Invalid or unexpected token
引用錯誤:
<script> const value = 10 console.log(test) console.log(value) </script>
複製代碼
js引擎對當前script中的代碼先進行詞法分析,語法解析完成後,成功構成一顆ast語法樹,此時js引擎會對當前ast樹進行預編譯。即在內存中開闢空間,將變量/函數存放到分配的空間中,聲明變量/函數,此時變量/函數提高就發生在這個階段。
預編譯階段的執行流程
當預編譯完成後,js引擎會進入運行階段, 代碼運行到console.log(test)
時,進行做用域查找,當查找到頂層window時此時尚未這個變量的聲明,那麼js引擎拋出錯誤. 代碼再也不向下執行。
拋出的錯誤test.html:12 Uncaught ReferenceError: test is not defined
範圍錯誤:
<script>
const value = []
value.length = -1
console.log(value)
</script>
複製代碼
當預編譯完成後,js引擎會進入運行階段,代碼運行到value.length = -1
時, js引擎發現了value的length被賦值爲了-1
。此時js引擎拋出 Uncaught RangeError: Invalid array length
。由於-1
值不在數組所容許的範圍或者集合中。拋出錯誤後,js代碼終止。
還有其餘這幾種狀況
// Number 對象的方法參數超出範圍
const num = new Number(12.34);
console.log(num.toFixed(-1));
// 函數堆棧超過最大值
const foo = () => foo();
foo(); // RangeError: Maximum call stack size exceeded
複製代碼
拋出的錯誤: Uncaught RangeError: Invalid array length
類型錯誤:
<script>
const value = []
const test = {};
test.go();
console.log(value)
</script>
複製代碼
值的類型或參數不是預期類型時發生的錯誤
拋出錯誤: Uncaught TypeError: test.go is not a function
URL 錯誤:
<script>
const value = []
decodeURI("%"); // URIError: URI malformed
console.log(value)
</script>
複製代碼
使用全局 URI 處理函數而產生的錯誤。
拋出錯誤: Uncaught URIError: URI malformed at decodeURI (<anonymous>)
資源加載錯誤:
<script src="./notfound.js"></script>
複製代碼
資源加載錯誤,即網站中的資源若是加載失敗則拋出資源加載錯誤
拋出錯誤 GET http://127.0.0.1:5500/notfound.js net::ERR_ABORTED 404 (Not Found)
接口錯誤
axios.get('/notfound')
複製代碼
在咱們上一章中對接口監控監聽到這個錯誤
拋出錯誤: GET http://127.0.0.1:5501/notfound 404 (Not Found)
promise未catch的錯誤
<script>
const value = []
new Promise((resolve, reject) => {
resolve(a.b)
})
console.log(value)
</script>
複製代碼
在promise中出現的錯誤會被統一放到Promise的catch中進行處理。若是沒有catch則向上拋出,由於執行機制的緣由,並不會阻塞線程執行,因此value是能夠正常打印
拋出錯誤: Uncaught (in promise) ReferenceError: a is not defined
跨域腳本錯誤
<script src="https://test.bootcdn.net/ajax/libs/test.js"></script>
複製代碼
前端須要在script標籤中配置 crossorigin
才能夠捕獲到跨站腳本內部報出的錯誤,由於瀏覽器只容許同域下的腳本捕獲具體的錯誤信息。
由於可能有些瀏覽器對crossorigin 不支持,此時咱們用try catch繼續向上拋出。
window.onerror
當發生 JavaScript 運行時錯誤(包括處理程序中引起的語法錯誤和異常)時,會觸發window.onerror的回調。
可是window.onerror捕獲不到資源加載錯誤
使用window.addEventListener('error')捕獲資源錯誤,可是window.addEventListener('error')也能夠捕獲到js運行錯誤,能夠經過target?.src || target?.href區分是資源加載錯誤仍是js運行時錯誤。
既然window.addEventListener('error')也能夠捕獲到錯誤,那麼咱們爲何要用window.onerror呢?
由於window.onerror的事件對象數據更加多,更加清晰。
window.addEventListener('unhandledrejection')
捕獲promise未catch的錯誤
面試官:請用一句話描述 try catch 能捕獲到哪些 JS 異常
async await promise try...catch
js運行加載時的數據結構
{
content: // 堆棧信息
col: // 列
row: // 行
message // 主要錯誤信息
name // 錯誤的主要name
resourceUrl // url
errorMessage // 完整的錯誤信息
scriptURI // 腳本url
lineNumber: // 行號
columnNumber // 列號
}
// 若是是使用webpack打包的框架代碼報錯,在處理一次
添加
{
source // 對應的資源
sourcesContentMap // sourceMap的信息
}
複製代碼
資源錯誤數據結構
{
url
}
複製代碼
Promise catch的錯誤
{
type: // 類型
reason // 緣由
}
複製代碼
設計代碼
let formatError = errObj => {
let col = errObj.column || errObj.columnNumber // Safari Firefox 瀏覽器中才有的信息
let row = errObj.line || errObj.lineNumber // Safari Firefox 瀏覽器中才有的信息
let message = errObj.message
let name = errObj.name
let { stack } = errObj
if (stack) {
let matchUrl = stack.match(/https?:\/\/[^\n]+/) // 匹配從http?s 開始到出現換行符的信息, 這個不只包括報錯的文件信息並且還包括了報錯的行列信息
let urlFirstStack = matchUrl ? matchUrl[0] : ''
// 獲取到報錯的文件
let regUrlCheck = /https?:\/\/(\S)*\.js/
let resourceUrl = ''
if (regUrlCheck.test(urlFirstStack)) {
resourceUrl = urlFirstStack.match(regUrlCheck)[0]
}
let stackCol = null // 獲取statck中的列信息
let stackRow = null // 獲取statck中的行信息
let posStack = urlFirstStack.match(/:(\d+):(\d+)/) // // :行:列
if (posStack && posStack.length >= 3) {
;[, stackCol, stackRow] = posStack
}
// TODO formatStack
return {
content: stack,
col: Number(col || stackCol),
row: Number(row || stackRow),
message,
name,
resourceUrl
}
}
return {
row,
col,
message,
name
}
}
複製代碼
let frameError = async errObj => {
const result = await $.get(`http://localhost:3000/sourcemap?col=${errObj.col}&row=${errObj.row}`)
return result
}
複製代碼
window.onerror
let _originOnerror = window.onerror
window.onerror = async (...arg) => {
let [errorMessage, scriptURI, lineNumber, columnNumber, errorObj] = arg
let errorInfo = formatError(errorObj)
// 若是是使用webpack打包的框架代碼報錯,在處理一次,這裏暫時使用resourceUrl字段進行區分是不是框架代碼報錯
if (errorInfo.resourceUrl === 'http://localhost:3000/react-app/dist/main.bundle.js') {
let frameResult = await frameError(errorInfo)
errorInfo.col = frameResult.column
errorInfo.row = frameResult.line
errorInfo.name = frameResult.name
errorInfo.source = frameResult.source
errorInfo.sourcesContentMap = frameResult.sourcesContentMap
}
errorInfo._errorMessage = errorMessage
errorInfo._scriptURI = scriptURI
errorInfo._lineNumber = lineNumber
errorInfo._columnNumber = columnNumber
errorInfo.type = 'onerror'
cb(errorInfo)
_originOnerror && _originOnerror.apply(window, arg)
}
複製代碼
window.onunhandledrejection
let _originOnunhandledrejection = window.onunhandledrejection
window.onunhandledrejection = (...arg) => {
let e = arg[0]
let reason = e.reason
cb({
type: e.type || 'unhandledrejection',
reason
})
_originOnunhandledrejection && _originOnunhandledrejection.apply(window, arg)
}
複製代碼
window.addEventListener(
'error',
event => {
// 過濾js error
let target = event.target || event.srcElement
let isElementTarget =
target instanceof HTMLScriptElement ||
target instanceof HTMLLinkElement ||
target instanceof HTMLImageElement
if (!isElementTarget) return false
// 上報資源地址
let url = target.src || target.href
cb({
url
})
},
true
)
複製代碼
面試官:請用一句話描述 try catch 能捕獲到哪些 JS 異常
async await promise try...catch