摘要:問題初探索刪掉那一點重寫的代碼后,表現(xiàn)符合預(yù)期了。每一次都重新造一個虛擬的,然后監(jiān)聽其自定義事件,并且立即觸發(fā)這個自定義事件。真的不要隨便重寫原生方法。。。于是,我全面總結(jié)一下了中的事件系統(tǒng),也算是對基礎(chǔ)的鞏固。
寫在前面
前段時間,我寫過一篇文章前端開發(fā)中的Error以及異常捕獲。 在文章中,我提到了這個問題:
經(jīng)過不斷探索(不想再噴自己了),我找到了原因。下面一一道來。本文主要講解自己找問題原因的思路,如果想看結(jié)論和總結(jié),請直接跳到文末。
問題復(fù)現(xiàn)我是在自己以前的項目中測試addEventListener的重寫的。這里直接上精簡后的問題代碼:
import React from "react"; import ReactDOM from "react-dom"; const nativeAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, func, options) { const wrappedFunc = function (...args) { try { return func.apply(this, args); } catch (e) { const errorObj = { error_msg: e.message || "", error_stack: e.stack || (e.error && e.error. error_native: e }; } } return self.nativeAddEventListener.call(this, type, wrappedFunc, options); }; const App = function() { return11111}; ReactDOM.render(, document.body);
運(yùn)行這段代碼,瀏覽器上一片空白,但是卻沒有任何報錯。我一臉懵逼。
問題初探索刪掉那一點重寫addEventListener的代碼后,表現(xiàn)符合預(yù)期了。應(yīng)該是重寫那兒的問題。但是仔細(xì)看了過后,那段代碼并沒有什么問題。并且這段代碼我在其他地方也試過,表現(xiàn)一直是正常的。是不是和React哪里沖突了?我使用的React版本是
我搜索了react-dom源碼中的addEventListener關(guān)鍵字,總共出現(xiàn)了四次。初步看了一下,并沒有什么問題,只是注冊了一些事件而已。沒有具體分析這些代碼的含義,我選擇了先更換React的版本試一試,于是,我換成了15.6.2的版本。令人吃驚的是,表現(xiàn)符合預(yù)期了。難道真的和React的版本有關(guān)系? 在我的認(rèn)知中,兩個版本中最大的不同就是:React v16采用了全新的Fiber架構(gòu),而我對Fiber的理解大概就是:重新設(shè)計了react node的數(shù)據(jù)結(jié)構(gòu),模擬實現(xiàn)了自己的任務(wù)堆棧,結(jié)合時間分片來進(jìn)行任務(wù)的調(diào)度,從而更新整個系統(tǒng)。另外,React有自己的一套事件系統(tǒng),addEventListener和事件也是緊密相關(guān)的,難道影響到了這個?
我決定從ReactDOM.render()這個方法入手,調(diào)試一下ReactDOM的源代碼。之前并沒有研究過React的源碼,壓力有點大。調(diào)試了一翻之后,我并沒有發(fā)現(xiàn)什么問題,并且已經(jīng)有點懵逼了。我準(zhǔn)備同時調(diào)試react v15和react v16的代碼,看看有什么不同。為了方便,我將問題代碼全部抽了出來,全部寫到了一個html文件中,并且直接引用React的cdn地址。這個時候,我發(fā)現(xiàn)了一個神奇的問題:直接引用cdn地址后,不管React是什么版本,就算是v16版本,也不會出現(xiàn)之前問題,表現(xiàn)都是符合預(yù)期的。我更加懵逼了。
發(fā)現(xiàn)問題靜下心來仔細(xì)觀察后,我發(fā)現(xiàn)了,我cdn引用的都是react的production版本,而我在項目中使用的react代碼,卻是development版本的,難道是development和production的diff代碼,導(dǎo)致了上面的問題。于是我重新仔細(xì)看了一下v16的development的代碼,找到了代碼中一段長長的注釋:
大意就是:在開發(fā)版本中,react不會采用try{}catch(){}的方式來捕獲錯誤,而是會把所有開發(fā)者定義的callback用一個叫做invokeGuardedCallback的函數(shù)包裹起來,然后使用一個假的dom,監(jiān)聽、觸發(fā)自定義事件來執(zhí)行invokeGuardedCallback,并且通過一個全局的錯誤捕捉函數(shù)來捕獲錯誤。
在這段注釋的下面,就是注釋中提到的invokeGuardedCallback的代碼。
我仔細(xì)研究了這個invokeGuardedCallback的代碼,其核心就是:
function invokeGuardedCallback(name, func, context, a, b, c, d, e, f){ ... var fakeNode = document.createElement("react"); var evt = document.createEvent("Event"); var evtType = "react-" + (name ? name : "invokeguardedcallback"); var callCallback = function(){ ... fakeNode.removeEventListener(evtType, callCallback, false); // 這里很重要?。?! ... func.apply(context, funcArgs); // 這里是真正執(zhí)行react中的邏輯代碼 } fakeNode.addEventListener(evtType, callCallback, false); evt.initEvent(evtType, false, false); fakeNode.dispatchEvent(evt); ... }
react將所有容易出錯的函數(shù),都用這個invokeGuardedCallback包了起來。每一次都重新造一個虛擬的element,然后監(jiān)聽其自定義事件,并且立即觸發(fā)這個自定義事件。調(diào)試了這個invokeGuardedCallback后,我發(fā)現(xiàn)在react v16中,發(fā)現(xiàn)很多函數(shù)被多次執(zhí)行。
為什么會多次執(zhí)行呢? 終于,我找到了問題的原因:
我重寫了addEventListener, 在函數(shù)外包了一層try{}catch(){},返回的是一個新的函數(shù),所以,最終注冊在事件監(jiān)聽器上的,并不是我傳入的那個函數(shù)。這個時候,調(diào)用removeEventListener時,無法移除我傳入addEventListener的函數(shù)。
在invokeGuardedCallback中,removeEventListener的邏輯相當(dāng)于并沒有生效。于是,在Fiber的調(diào)度中,某個函數(shù)被多次重復(fù)執(zhí)行了,而被重復(fù)執(zhí)行的函數(shù)并不是冪等的,問題便產(chǎn)生了。
問題的總結(jié)與思考問題終于定位了,一句總結(jié),就是:
重寫了addEventListener,卻并沒有考慮到與之對應(yīng)的removeEventListener,導(dǎo)致removeEventListener無法正常工作。
下面是一些思考:
一開始,如果我仔細(xì)看一下react源碼中addEventListener周圍的代碼,或許能更早發(fā)現(xiàn)這個問題,就不用繞這么大一個圈了。
自己對于第三方庫的development版本和production版本,并沒有一個很強(qiáng)烈的認(rèn)知、意識,以前上線的不少項目,線上竟然還是用的第三方庫的development版本,這個毛病,一定得改掉。
分析問題的能力還很欠缺,不夠敏感??紤]問題的全面性需要提高。
真的不要隨便重寫原生方法。。。
寫在后面在探索這個問題的過程中,我看到了react巧妙應(yīng)用自定義事件來捕獲錯誤。于是,我全面總結(jié)一下了Web中的事件系統(tǒng),也算是對基礎(chǔ)的鞏固。由于篇幅已經(jīng)不夠了,這里就直接放文章鏈接吧:
談一談web中的事件
談一談web中的事件
歡迎關(guān)注我的公眾號: 符合預(yù)期的CoyPan,
這里只有干貨,符合你的預(yù)期。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101006.html
摘要:忍者級別的函數(shù)操作對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...
摘要:博主之前已經(jīng)推薦了一款神器下面,就總結(jié)一下移動端遇見的坑。解決原理虛擬鍵盤彈出時將元素設(shè)置為,虛擬鍵盤消失時候設(shè)置回來。解決方案由于虛擬鍵盤出現(xiàn)并未拋出事件,而檢測或者事件,皆會有一定延遲,會出現(xiàn)閃爍現(xiàn)象。 做過很多移動端的項目,在開發(fā)調(diào)試過程中,一款好的調(diào)試工具會讓效率大大提高。博主之前已經(jīng)推薦了一款神器:http://web.jobbole.com/87587/ 下面,就總結(jié)一下移...
摘要:由于初版需求及開發(fā)工作都沒有參與,在接手項目后過了遍前端結(jié)構(gòu)發(fā)現(xiàn)所有交互及組件都是現(xiàn)擼,并未使用市面上已有的優(yōu)秀前端框架從我個人角度理解上出發(fā),后續(xù)需求變更中當(dāng)需要實現(xiàn)某些常用組件樣式或交互時,基本上都需要現(xiàn)擼或者尋找合適的組件。 2016悄無聲息的過去了,再過不久便是農(nóng)歷新年 這幾天相對清閑梳理了一下去年所做的工作,希望在新的一年能發(fā)展的更好 今年一共研發(fā)或升級了五款產(chǎn)品:合伙人、奪...
摘要:前戲補(bǔ)上參會的完整記錄,這個問題從一開始我就是準(zhǔn)備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補(bǔ)上參會的完整記錄,這個問題從一開始我就是準(zhǔn)備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:之前實習(xí)做的一個移動端的頁面需要的功能有圖片上傳點擊客戶端的返回按鈕有提示即與客戶端有交互遇到不少的坑總結(jié)一下問題圖片上傳功能使用工具百度的暫時遇到的坑刪除圖片實際上并沒有完全刪除需要自己在源碼上添加詳情看的提問上傳的圖片旋轉(zhuǎn)角度有問題比 之前實習(xí)做的一個移動端的頁面 需要的功能有圖片上傳 點擊客戶端的返回按鈕 有提示(即與客戶端有交互) 遇到不少的坑 總結(jié)一下問題 1.圖片上傳功能 ...
閱讀 743·2023-04-25 19:28
閱讀 1401·2021-09-10 10:51
閱讀 2400·2019-08-30 15:55
閱讀 3421·2019-08-26 13:55
閱讀 3011·2019-08-26 13:24
閱讀 3337·2019-08-26 11:46
閱讀 2764·2019-08-23 17:10
閱讀 1428·2019-08-23 16:57