持續更新中react相關庫源碼淺析react
try...catch
錯誤捕獲功能同時增長Pause on exceptions
功能react\packages\shared\ReactErrorUtils.js
// Used by Fiber to simulate a try-catch.
let hasError: boolean = false;
let caughtError: mixed = null;
複製代碼
// Used by event system to capture/rethrow the first error.
let hasRethrowError: boolean = false;
let rethrowError: mixed = null;
複製代碼
reporter
對象上的onError
方法提供一個藉口改變錯誤的狀態以及存儲捕獲到的錯誤對象const reporter = {
onError(error: mixed) {
hasError = true;
caughtError = error;
},
};
複製代碼
對invokeGuardedCallback
的封裝,當執行完invokeGuardedCallback
以後,經過hasError
判斷是否發生了錯誤,並調用clearCaughtError
返回錯誤對象,並清空hasError、caughtError
。而後將返回的錯誤存儲到rethrowError
,並改變hasRethrowError
的狀態。git
function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f) {
invokeGuardedCallback.apply(this, arguments);
if (hasError) {
const error = clearCaughtError();
if (!hasRethrowError) {
hasRethrowError = true;
rethrowError = error;
}
}
}
export function clearCaughtError() {
if (hasError) {
const error = caughtError;
hasError = false;
caughtError = null;
return error;
} else {
invariant(
false,
'clearCaughtError was called but no error was captured. This error ' +
'is likely caused by a bug in React. Please file an issue.',
);
}
}
複製代碼
func
的準備對invokeGuardedCallbackImpl
的封裝,利用apply
將this
指向reporter
,用於當invokeGuardedCallbackImpl
函數發生錯誤的時候,調用reporter
上的接口onError
,將錯誤對象存儲到最外層ReactErrorUtils.js
文件模塊的hasError、caughtError
變量上。github
export function invokeGuardedCallback(name,func,context,a,b,c,d,e,f) {
hasError = false;
caughtError = null;
invokeGuardedCallbackImpl.apply(reporter, arguments);
}
複製代碼
func
的invokeGuardedCallbackImpl
invokeGuardedCallbackImpl
在不一樣的環境下有不一樣的實現:chrome
try ... catch
捕獲錯誤,並存儲錯誤到hasError、caughtError
上。try ... catch
功能,同時具有在開發工具中在全部異常發生的地方自動斷點的功能。若是用try ... catch
,那麼try
塊語句中出現的錯誤不會斷點暫停,由於這個異常被catch
捕獲了,除非在chrome下將Pause on exceptions
打開。Because React wraps all user-provided functions in invokeGuardedCallback, and the production version of invokeGuardedCallback uses a try-catch, all user exceptions are treated like caught exceptions, and the DevTools won't pause unless the developer takes the extra step of enabling pause on caught exceptions.數組
下面主要分析invokeGuardedCallbackImpl
bash
react\packages\shared\invokeGuardedCallbackImpl.js
let invokeGuardedCallbackImpl = function(name,func,context,a,b,c,d,e,f){
const funcArgs = Array.prototype.slice.call(arguments, 3);
try {
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
};
複製代碼
將arguments
轉成數組,而後執行傳入的函數func
,執行上下文指定爲傳入的context
。若是func
執行過程當中發生錯誤,拋出的錯誤被捕獲並經過this.onError
傳入最外層的變量上。這裏的this
指向的是reporter
。app
const fakeNode = document.createElement('react');
const invokeGuardedCallbackDev = function(name,func,context,a,b,c,d,e,f){
let windowEvent = window.event;
const windowEventDescriptor = Object.getOwnPropertyDescriptor(
window,
'event',
);
const funcArgs = Array.prototype.slice.call(arguments, 3);
/**
* 給window添加error事件監聽函數,未捕獲的錯誤都會通過這裏
*/
let error;
let didSetError = false;
let isCrossOriginError = false;
function handleWindowError(event) {
error = event.error;
didSetError = true;
if (error === null && event.colno === 0 && event.lineno === 0) {
// 當加載自不一樣域的腳本中發生語法錯誤時,爲避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的"Script error."
// event:
// message:錯誤信息(字符串)。可用於HTML onerror=""處理程序中的event。
// source:發生錯誤的腳本URL(字符串)
// lineno:發生錯誤的行號(數字)
// colno:發生錯誤的列號(數字)
// error:Error對象(對象)
isCrossOriginError = true;
}
if (event.defaultPrevented) {
if (error != null && typeof error === 'object') {
try {
error._suppressLogging = true;
} catch (inner) {
// Ignore.
}
}
}
}
window.addEventListener('error', handleWindowError);
/**
* 利用createEvent建立自定義事件event,並利用initEvent指定名稱,而後給DOM fakeNode添加evtType事件監聽函數
*/
let didError = true;
const evtType = `react-${name ? name : 'invokeguardedcallback'}`;
const evt = document.createEvent('Event');
evt.initEvent(evtType, false/*false表示阻止該事件向上冒泡*/, false/*false表示該事件默認動做不可取消*/);
function callCallback() {
fakeNode.removeEventListener(evtType, callCallback, false);
if (
typeof window.event !== 'undefined' &&
window.hasOwnProperty('event')
) {
window.event = windowEvent;
}
func.apply(context, funcArgs);
didError = false;
}
fakeNode.addEventListener(evtType, callCallback, false);
/**
* 手動觸發fakeNode上的evt事件,此時會先執行傳入的func函數,若是發生錯誤會觸發window上的error事件
*/
fakeNode.dispatchEvent(evt);
/**
* 還原window.event
*/
if (windowEventDescriptor) {
Object.defineProperty(window, 'event', windowEventDescriptor);
}
/**
* 調用onError記錄error
*/
if (didError) {
if (!didSetError) {
error = new Error(
'An error was thrown inside one of your components'
);
} else if (isCrossOriginError) {
error = new Error(
"A cross-origin error was thrown. React doesn't have access to ",
);
}
this.onError(error);
}
// Remove our event listeners
window.removeEventListener('error', handleWindowError);
};
invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
複製代碼
chrome
下Pause on exceptions
功能操做: f12 -> Source Tab -> 點擊 Pause on exceptions 暫停圖標 -> 圖標變成藍色,代表啓用了在未捕獲到的異常出現的時候斷點的功能。less
勾選Pause On Caught Exceptions
, 可以在捕獲到異常的狀況下也斷點。ide
try{
throw'a exception';
}catch(e){
console.log(e);
}
複製代碼
上面 try
裏面的代碼會遇到異常,可是後面的 catch
代碼可以捕獲該異常。若是是全部異常都中斷(勾選了Pause On Caught Exceptions
),那麼代碼執行到會產生異常的 throw 語句時就會自動中斷;而若是是僅遇到未捕獲異常才中斷,那麼這裏就不會中斷。通常咱們會更關心遇到未捕獲異常的狀況。函數