問題描述
今天同事遇到一個神一樣的BUG:
在原生瀏覽器下,為dom元素綁定一個click事件,其中有個a標簽外鏈,點擊a后進入其他頁面,點擊瀏覽器后退后,頁面點擊事件全體失效!
我於是用ios測了下沒事,用andriod其他瀏覽器試了下也沒事,就是原生的有問題,懷疑是特定的手機有問題,又陸續換了幾台,發現原生的都有問題
於是便開始找解決方案,下面就來聊下今天的漫長之路,這里先給會出問題的代碼:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 7 </head> 8 <body> 9 <div id="ttt"> 10 ttt</div> 11 <br /> 12 <a href="http://www.baidu.com" name="n">百度一下</a> 13 <script type="text/javascript"> 14 var i = 0; 15 var appendDiv = function (msg) { 16 var div = document.createElement('div'); 17 if (msg) { 18 div.innerHTML = msg; 19 } else { 20 div.innerHTML = i++; 21 } 22 document.body.appendChild(div); 23 }; 24 document.addEventListener('click', function (e) { 25 appendDiv('click') 26 }); 27 </script> 28 </body> 29 </html>
DOM事件丟失
第一步想到的當然是事件丟失了,或者就是不執行了,於是乎寫了一段代碼:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 7 </head> 8 <body> 9 <div id="ttt"> 10 ttt</div> 11 <br /> 12 <a href="http://www.baidu.com" name="n">百度一下</a> dsfsdffd<br> 13 <script type="text/javascript"> 14 var i = 0; 15 16 setInterval(function () { 17 var div = document.createElement('div'); 18 div.innerHTML = i++; 19 document.body.appendChild(div); 20 21 var type = 'click'; //要觸發的事件類型 22 var event = document.createEvent('MouseEvents'); 23 event.initMouseEvent(type); 24 document.dispatchEvent(event); 25 26 }, 1000); 27 var appendDiv = function (msg) { 28 var div = document.createElement('div'); 29 if (msg) { 30 div.innerHTML = msg; 31 } else { 32 div.innerHTML = i++; 33 } 34 document.body.appendChild(div); 35 }; 36 document.addEventListener('click', function (e) { 37 appendDiv('click') 38 }); 39 </script> 40 </body> 41 </html>
我定時器不停地向瀏覽器打印數字,而且觸發document的click事件,他工作的蠻好的,但是當我點擊百度一下再回來時候,便不執行了
因為我們沒法在代碼層面上獲取dom的事件信息,所以暫時只能這樣做,而我的判斷是,沒錯!dom事件丟失了
Window事件未丟失
然后我又在這上面糾纏了好久,想試試windows的事件是否丟失,於是寫下了以下代碼:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 7 </head> 8 <body> 9 <div id="ttt"> 10 ttt</div> 11 <br /> 12 <a href="http://www.baidu.com" name="n">百度一下</a> dsfsdffd<br> 13 <script type="text/javascript"> 14 var i = 0; 15 16 setInterval(function () { 17 var div = document.createElement('div'); 18 div.innerHTML = i++; 19 document.body.appendChild(div); 20 21 var type = 'click'; //要觸發的事件類型 22 var event = document.createEvent('MouseEvents'); 23 event.initMouseEvent(type); 24 document.dispatchEvent(event); 25 26 }, 1000); 27 var appendDiv = function (msg) { 28 var div = document.createElement('div'); 29 if (msg) { 30 div.innerHTML = msg; 31 } else { 32 div.innerHTML = i++; 33 } 34 document.body.appendChild(div); 35 }; 36 document.addEventListener('click', function (e) { 37 appendDiv('click') 38 }); 39 40 window.onresize = function () { 41 appendDiv('onresize') 42 } 43 </script> 44 </body> 45 </html>
我點擊回來后,發現事件還在,於是陷入深深的沉思.沉思.沉思.思.思.思.............
問題解決
最后我無意間將這個問題解決了,而且解決的方案匪夷所思:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 7 </head> 8 <body> 9 <div id="ttt"> 10 ttt</div> 11 <br /> 12 <a href="http://www.baidu.com" name="n">百度一下</a> dsfsdffd<br> 13 <script type="text/javascript"> 14 var t = document.getElementById('ttt'); 15 var i = 0; 16 setInterval(function () { 17 var div = document.createElement('div'); 18 div.innerHTML = i++; 19 document.body.appendChild(div); 20 }, 1000); 21 var appendDiv = function (msg) { 22 var div = document.createElement('div'); 23 if (msg) { 24 div.innerHTML = msg; 25 } else { 26 div.innerHTML = i++; 27 } 28 document.body.appendChild(div); 29 }; 30 document.addEventListener('click', function (e) { 31 appendDiv('click') 32 }); 33 </script> 34 </body> 35 </html>
整個解決方案耗費我兩個多小時,而最終卻是這么一段不起眼的代碼:
var t = document.getElementById('ttt'); // var btnfree = document.getElementsByTagName('a'); 無效 // var n = document.getElementsByName('name');無效
現在問題是解決了,我卻更疑惑了,一個大大的問號在我腦里回旋不去,尼瑪在玩我啊......這是為什么??????
問題原理猜想
注意,此處完全是扯淡時間
andriod硬件加速
andriod原生瀏覽器本身使用了硬件加速功能,或者說andriod對自身的瀏覽器做了很好的處理
我們在頁面上看到的頁面具有一個dom樹,而我們的事件js保存在另一個地方,而此時硬件加速為我們提供了一個類似png的中間件
他負責了通訊,但是在我們該網頁轉入后台時,這之間的映射關系卻被破壞了
而我們js代碼中若是多了這么一段代碼:
var t = document.getElementById('ttt');
他的映射關系又建立起來了,如果是這樣的話,是說的過去的!!!
PS:以上的理論顯然無法說服我
垃圾回收/后台掛起
不得已的情況下,我想到了js的垃圾回收,dom結構活生生的在頁面上,頁面不會回收,里面的i一直在用也不會回收
但是我們的DOM樹好像並沒有神引用在頁面中保存,因為沒有瀏覽器就正好將所有的事件全部銷毀了。
而后面我們在js中保存了一個dom樹,他就沒有銷毀????
PS:這里我可以將t給delete了試試,但是我在家沒有環境,只得明天再試了,今天暫時到這里
問題追蹤
var t = document.getElementById('ttt'); t = null;
最后這樣加一句,問題又會復現,所以垃圾回收的概率較高。
結語
當然,這是andriod瀏覽器本身一個BUG,但是如果我們可以從這種小BUG中發現大問題,或者原理性的東西,那真的該好好的研究一番了!!!
若是您有任何想法,請不吝賜教!!!