Эта статья была впервые опубликована в публичном аккаунте:КойПан, как и ожидалось
написать впереди
В интерфейсных проектах из-за того, что JavaScript сам по себе является слабо типизированным языком, в сочетании со сложностью среды браузера, проблемами с сетью и т. д., могут возникать ошибки. Очень важно хорошо выполнять работу по отслеживанию ошибок веб-страниц, постоянно оптимизировать код и повышать надежность кода. Эта статья начнется с ошибки и расскажет о том, как перехватывать исключения на странице. Статья длинная и подробная, наберитесь терпения.
Ошибка во фронтенд-разработке
Ошибка в JavaScript
JavaScript,Error
Это конструктор, создайте объект ошибки через него. Когда ошибка времени выполнения выброшена ошибка экземпляра объекта. Создание синтаксиса ошибок выглядит следующим образом:
// message: 错误描述
// fileName: 可选。被创建的Error对象的fileName属性值。默认是调用Error构造器代码所在的文件的名字。
// lineNumber: 可选。被创建的Error对象的lineNumber属性值。默认是调用Error构造器代码所在的文件的行号。
new Error([message[, fileName[, lineNumber]]])
Стандарт ECMAScript:
Ошибка имеет двастандартныйАтрибуты:
-
Error.prototype.name
: неправильное имя -
Error.prototype.message
: неправильное описание
Например, введите следующий код в консоли Chrome:
var a = new Error('错误测试');
console.log(a); // Error: 错误测试
// at <anonymous>:1:9
console.log(a.name); // Error
console.log(a.message); // 错误测试
Ошибка только однастандартныйметод:
-
Error.prototype.toString
: возвращает строку, представляющую ошибку.
Продолжите приведенный выше код:
a.toString(); // "Error: 错误测试"
нестандартные свойства
У каждого поставщика браузера есть собственная реализация Error. Например, следующие свойства:
-
Error.prototype.fileName
: Создает неправильное имя файла. -
Error.prototype.lineNumber
: выдает неправильный номер строки. -
Error.prototype.columnNumber
: выдает неправильный номер столбца. -
Error.prototype.stack
: информация о стеке. Это чаще используется.
Ни одно из этих свойств не является стандартным и используется в производственной среде.Используйте с осторожностью. Но современные браузеры почти все поддерживают его.
Вид ошибки
В дополнение к универсальному конструктору Error в JavaScript есть 7 других типов конструкторов ошибок.
- InternalError: создает экземпляр исключения, представляющего внутреннюю ошибку в механизме Javascript. Например: «Слишком много рекурсии». Стандарт без ECMAScript.
- RangeError: числовая переменная или параметр вне допустимого диапазона. Пример: var a = новый массив (-1);
- EvalError: ошибка, связанная с eval(). Сам eval() не выполняется правильно.
- ReferenceError: ошибка ссылки. Пример: console.log(b);
- SyntaxError: Синтаксическая ошибка. Пример: вар а = ;
- TypeError: переменная или параметр не относятся к допустимой области. Пример: [1,2].split('.')
- URIError: Неверные аргументы переданы в encodeURI или decodeURl(). Пример: decodeURI('%2')
Когда во время выполнения JavaScript возникает ошибка, выдается одна из 8 вышеперечисленных ошибок (вышеупомянутые 7 плюс общий тип ошибки). Тип ошибки может быть переданerror.nameполучать.
Вы также можете создать свой собственный тип ошибки на основе Error, который здесь не будет подробно описан.
другие ошибки
Выше перечислены все ошибки, возникающие при запуске самого JavaScript. На странице также будут другие исключения, такие как неправильное манипулирование DOM.
DOMException
DOMExceptionЯвляется основным объектом W3C DOM, представляющим исключение, возникающее при вызове веб-API. Что такое веб-API? Наиболее распространенным является ряд методов элементов DOM, а другие включают XMLHttpRequest, Fetch и т. д., которые здесь не будут объясняться. Просто посмотрите на следующий пример манипулирования DOM:
var node = document.querySelector('#app');
var refnode = node.nextSibling;
var newnode = document.createElement('div');
node.insertBefore(newnode, refnode);
// 报错:Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
Только с точки зрения логики кода JS проблем нет. Но работа кода не соответствует правилам DOM.
DOMException
Синтаксис конструктора следующий:
// message: 可选,错误描述。
// name: 可选,错误名称。常量,具体值可以在这里找到:https://developer.mozilla.org/zh-CN/docs/Web/API/DOMException
new DOMException([message[, name]]);
DOMException
Есть три свойства:
-
DOMException.code
: Номер ошибки. -
DOMException.message
:неверное описание. -
DOMException.name
: Имя ошибки.
Взяв приведенный выше код ошибки в качестве примера, значение каждого атрибута выброшенного исключения DOMException:
code: 8
message: "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
name: "NotFoundError"
Promise
сгенерированное исключение
существуетPromise
, еслиPromise
одеялоreject
, будет выброшено исключение:PromiseRejectionEvent
. Обратите внимание, что следующие два случая приведут кPromise
одеялоreject
:
- Сам бизнес-код вызывает
Promise.reject
. -
Promise
Ошибка в коде в .
PromiseRejectionEvent
Конструктор в настоящее время в основном несовместим с браузерами, поэтому я не буду говорить о нем здесь.
PromiseRejectionEvent
имеет два свойства:
-
PromiseRejectionEvent.promise
:одеялоreject
изPromise
. -
PromiseRejectionEvent.reason
:Promise
одеялоreject
причина. будет переданоreject
.Promsie
изcatch
параметры в .
Ошибка загрузки ресурса
Из-за сети, безопасности и других причин сбой загрузки ресурсов веб-страницы, ошибки интерфейса запроса и т. д. также являются распространенными ошибками.
Сводка ошибок
При работе веб-страницы могут возникать четыре вида ошибок:
- Исключения, создаваемые самим языком во время выполнения JavaScript.
- Когда JavaScript запущен, при вызове Web Api возникает исключение.
- Отказ в обещаниях.
- Веб-страница загружает ресурсы, и при вызове интерфейса возникает исключение.
Я думаю, что для первых двух видов ошибок нам не нужно различать их в обычном процессе разработки, но их можно объединить в: [ошибка кода].
поймать ошибки
Ошибки веб-страницы, как разработчики могут обнаружить эти ошибки?
try...catch...
try...catch…
Все не чужие. Обычно используется для обнаружения ошибок в определенной логике кода.
try {
throw new Error("oops");
}
catch (ex) {
console.log("error", ex.message); // error oops
}
когдаtry-block
Когда в коде возникает исключениеcatck-block
Генерал-лейтенант ловит исключение и браузер не выдает ошибку. Однако этот способ не отлавливает ошибки в асинхронном коде, такие как:
try {
setTimeout(function(){
throw new Error('lala');
},0);
} catch(e) {
console.log('error', e.message);
}
В это время браузер по-прежнему выдает ошибку:Uncaught Error: lala
.
Представьте себе следующее, если разумно разделить весь код, а затем использоватьtry catch
Заканчивай, ты можешь поймать все ошибки? Эта функция может быть достигнута путем компиляции инструментов. но,try catch
Это более требовательно к производительности.
window.onerror
window.onerror = function(message, source, lineno, colno, error) { ... }
Параметры функции:
-
message
: Сообщение об ошибке (строка) -
source
: URL скрипта, в котором произошла ошибка (строка) -
lineno
: Номер строки (номер), где произошла ошибка -
colno
: номер столбца (номер), в котором произошла ошибка -
error
: Объект ошибки (объект)
Обратите внимание, что если эта функция возвращаетtrue
, то функция обработки ошибок браузера по умолчанию не будет выполняться.
window.addEventListener('error')
window.addEventListener('error', function(event) { ... })
мы называемObject.prototype.toString.call(event)
, который возвращает[object ErrorEvent]
. можно увидетьevent
даErrorEvent
экземпляр объекта.ErrorEvent
объект события, который генерируется при возникновении ошибки в скрипте, изEvent
унаследовал. Поскольку это событие, его можно получить естественным образом.target
Атрибуты.ErrorEvent
Также включает информацию о том, когда произошла ошибка.
- ErrorEvent.prototype.message: строка, содержащая описание возникшей ошибки.
- ErrorEvent.prototype.filename: строка, содержащая имя файла сценария, в котором произошла ошибка.
- ErrorEvent.prototype.lineno: число, содержащее номер строки, в которой произошла ошибка.
- ErrorEvent.prototype.colno: число, содержащее номер столбца, в котором произошла ошибка.
- ErrorEvent.prototype.error: объект Error, выдаваемый при возникновении ошибки.
Обратите внимание, что здесьErrorEvent.prototype.error
Соответствующий объект Error указан выше.Error
, InternalError
,RangeError
,EvalError
,ReferenceError
,SyntaxError
,TypeError
,URIError
,DOMException
один из.
window.addEventListener('unhandledrejection')
window.addEventListener('unhandledrejection', function (event) { ... });
В использованииPromise
, если нет объявленияcatch
кодовый блок,Promise
Будет выброшено исключение.Толькоэтим методом илиwindow.onunhandledrejection
чтобы поймать исключение.
event
что упомянуто вышеPromiseRejectionEvent
. Нам просто нужно обратить внимание на егоreason
Просто сделай это.
Разница между window.onerror и window.addEventListener('error')
- прежде всегопрослушиватель событийа такжеобработчик событияразница. Слушатель может быть объявлен только один раз, и последующие объявления переопределяют предыдущие. Обработчик событий может связывать несколько функций обратного вызова.
- Когда ресурс ( img или script ) не загружается, элемент, который загружает ресурс, запускает
Event
интерфейсerror
событие и выполнитьonerror()
функция обработчика. Но эти события ошибок не пузыруют до окна. Однако этиerror
события могут бытьwindow.addEventListener('error')
захватывать. То есть перед лицом ошибки загрузки ресурса вы можете использовать толькоwindow.addEventListerner('error')
,window.onerror
**неверный.
Сводка по отлову ошибок
Думаю, что в процессе разработки для ошибочных мест можно использоватьtry{}catch(){}
Чтобы отловить ошибки, хорошо поработайте над итоговой строкой и избегайте зависания страницы. А для глобального перехвата ошибок в современных браузерах я просто используюwindow.addEventListener('error')
,window.addEventListener('unhandledrejection')
Вот и все. Если вам нужно учитывать совместимость, вам нужно добавитьwindow.onerror
, три используются одновременно,window.addEventListener('error')
В частности, используется для обнаружения ошибок загрузки ресурсов.
Ошибка междоменного сценария, Ошибка сценария
В процессе перехвата ошибок во многих случаях невозможно получить полную информацию об ошибке, и получается только одно сообщение об ошибке."Script Error"
.
причина
Из-за проблем безопасности, упомянутых в этом посте 12 лет назад:blog.Jeremiah Grossman.com/2006/12/i-may…
Когда синтаксическая ошибка возникает в скрипте, загруженном из другого домена, во избежание утечки информации сведения о синтаксической ошибке не сообщаются, а просто"Script error."
заменять.
Вообще говоря, JS-файлы страницы размещаются в CDN, а URL-адрес самой страницы создает междоменные проблемы, поэтому это вызывает"Script Error"
.
Решение
добавить серверAccess-Control-Allow-Origin
, страница находится вscript
конфигурация на этикеткеcrossorigin="anonymous"
. Таким образом, он решает проблемы, вызванные междоменным"Script Error"
вопрос.
может обойтиScript Error
какие
описано выше"Script Error"
стандартное решение. Однако не все браузеры поддерживаютcrossorigin="anonymous"
, не все серверы можно настроить вовремяAccess-Control-Allow-Origin
, в таком случае есть ли способ отловить все ошибки глобально и получить подробную информацию?
Взлом нативных методов
См. пример:
const nativeAddEventListener = EventTarget.prototype.addEventListener; // 先将原生方法保存起来。
EventTarget.prototype.addEventListener = function (type, func, options) { // 重写原生方法。
const wrappedFunc = function (...args) { // 将回调函数包裹一层try catch
try {
return func.apply(this, args);
} catch (e) {
const errorObj = {
...
error_name: e.name || '',
error_msg: e.message || '',
error_stack: e.stack || (e.error && e.error.stack),
error_native: e,
...
};
// 接下来可以将errorObj统一进行处理。
}
}
return nativeAddEventListener.call(this, type, wrappedFunc, options); // 调用原生的方法,保证addEventListener正确执行
}
Мы угнали роднойaddEventListener
код, даaddEventListener
Функция обратного вызова в коде добавляет слойtry{}catch(){}
, так что ошибки, выдаваемые функцией обратного вызова, будутcatch
жить,браузер неtry-catch
Возникающее исключение перехватывается междоменным, поэтому мы можем получить подробную информацию об ошибке. С помощью вышеуказанных операций мы можем получить все ошибки в функциях обратного вызова, которые прослушивают события. А как насчет других сценариев? Продолжайте захватывать нативные методы.
Во фронтенд-проекте, помимо мониторинга событий, часто встречаются запросы к интерфейсу. Тогда приведенный выше код, давайте захватим егоAjax.
if (!XMLHttpRequest) {
return;
}
const nativeAjaxSend = XMLHttpRequest.prototype.send; // 首先将原生的方法保存。
const nativeAjaxOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (mothod, url, ...args) { // 劫持open方法,是为了拿到请求的url
const xhrInstance = this;
xhrInstance._url = url;
return nativeAjaxOpen.apply(this, [mothod, url].concat(args));
}
XMLHttpRequest.prototype.send = function (...args) { // 对于ajax请求的监控,主要是在send方法里处理。
const oldCb = this.onreadystatechange;
const oldErrorCb = this.onerror;
const xhrInstance = this;
xhrInstance.addEventListener('error', function (e) { // 这里捕获到的error是一个ProgressEvent。e.target 的值为 XMLHttpRequest的实例。当网络错误(ajax并没有发出去)或者发生跨域的时候,会触发XMLHttpRequest的error, 此时,e.target.status 的值为:0,e.target.statusText 的值为:''
const errorObj = {
...
error_msg: 'ajax filed',
error_stack: JSON.stringify({
status: e.target.status,
statusText: e.target.statusText
}),
error_native: e,
...
}
/*接下来可以对errorObj进行统一处理*/
});
xhrInstance.addEventListener('abort', function (e) { // 主动取消ajax的情况需要标注,否则可能会产生误报
if (e.type === 'abort') {
xhrInstance._isAbort = true;
}
});
this.onreadystatechange = function (...innerArgs) {
if (xhrInstance.readyState === 4) {
if (!xhrInstance._isAbort && xhrInstance.status !== 200) { // 请求不成功时,拿到错误信息
const errorObj = {
error_msg: JSON.stringify({
code: xhrInstance.status,
msg: xhrInstance.statusText,
url: xhrInstance._url
}),
error_stack: '',
error_native: xhrInstance
};
/*接下来可以对errorObj进行统一处理*/
}
}
oldCb && oldCb.apply(this, innerArgs);
}
return nativeAjaxSend.apply(this, args);
}
}
Когда мы говорим о фреймворках, некоторые фреймворки используют **console.error
** метод выдает ошибку. мы можем угнатьconsole.error
, чтобы отловить ошибки.
const nativeConsoleError = window.console.error;
window.console.error = function (...args) {
args.forEach(item => {
if (typeDetect.isError(item)) {
...
} else {
...
}
});
nativeConsoleError.apply(this, args);
}
Существует множество нативных методов, таких какfetch
,setTimeout
Ждать. Здесь не указано. Но использовать нативные методы захвата для покрытия всех сценариев очень сложно.
Как интерфейсные фреймворки ловят ошибки
В основном рассмотримReact
а такжеVue
Как решить проблему перехвата ошибок.
Отлов ошибок в React
существуетReact
До v16 вы можете использоватьunstable_handleError
для обработки пойманных ошибок.React
После v16 используйтеcomponentDidCatch
для обработки пойманных ошибок. Чтобы отлавливать ошибки глобально, вы можете поместить слой компонентов в самый внешний слой, вcomponentDidCatch
Захват сообщений об ошибках в формате . Для конкретного использования обратитесь к официальной документации:реагировать JS.org/blog/2017/0…
существуетReact
, ошибка будетthrow
публично заявить. На момент написания этой статьи я столкнулся с проблемой, если загрузкаreact
Перед соответствующим кодом угнать по вышеуказанному методуaddEventListener
,ТакReact
не будет работать должным образом, но об ошибках не сообщается.React
Есть набор собственной системы событий, будет ли она с этим связана? раньше не училсяReact
Исходник, грубо отладил следующее, проблемы не нашел. Подробно она будет изучена позже.
Перехват ошибок в Vue
Vue
В исходном коде, когда выполняется ключевые функции (такие как функции крюка и т. Д.),try{}catch(){}
,существуетcacth
Обработка пойманных ошибок в . См. исходный код ниже.
...
// vue源码片段
function callHook (vm, hook) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget();
var handlers = vm.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm);
} catch (e) {
handleError(e, vm, (hook + " hook"));
}
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
popTarget();
}
...
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
logError(e, null, 'config.errorHandler');
}
}
logError(err, vm, info);
}
function logError (err, vm, info) {
{
warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm);
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console !== 'undefined') {
console.error(err);
} else {
throw err
}
}
Vue中提供了
Vue.config.errorHandler` для обработки пойманных ошибок.
// err: 捕获到的错误对象。
// vm: 出错的VueComponent.
// info: Vue 特定的错误信息,比如错误所在的生命周期钩子
Vue.config.errorHandler = function (err, vm, info) {}
Если разработчик не настроилVue.config.errorHandler
, то пойманная ошибка начнется сconsole.error
способ вывода.
сообщить об ошибке
Как сообщить об ошибке после ее обнаружения? Самый распространенный и простой способ — пройти<img>
. Код прост, и проблем с междоменными связями нет.
function logError(error){
var img = new Image();
img.onload = img.onerror = function(){
img = null;
}
img.src = `${上报地址}?${processErrorParam(error)}`;
}
Когда есть много отчетных данных, вы можете использоватьpost
способ сообщить.
Неверная отчетность на самом деле является сложным проектом, включающим стратегии отчетности, классификацию отчетности и многое другое. Особенно, когда бизнес проекта является более сложным, следует уделять больше внимания качеству отчетности, чтобы не влиять на нормальную работу бизнес-функций. Код, обрабатываемый инструментами упаковки, часто необходимо комбинироватьsourceMap
Позиционирование кода. Эта статья не будет представлять его.
написать на обороте
Создание полной и пригодной для использования системы мониторинга ошибок переднего плана — сложный и масштабный проект. Тем не менее, этот проект часто требуется. В этой статье в основном представлены некоторые детали ошибки, на которые вы, возможно, не обратили внимания, и способы обнаружения ошибок на странице. Код части перехвата нативного метода можно найти вgithub.com/CoyPan/Fecоказаться.
В соответствии с ожиданиями.