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