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