for循環中的這兩種寫法
for(var i=0,len=arr.length;i<len;i++){ }
上面這種是最為常見也是初學者經常寫的
而下面這種寫法,在性能上則是比上面的更好,然而我們今天要討論的並不是這兩者性能上的區別,而是第一種寫法有時候會帶來一些讓人毫無頭緒的bug。
for(var i=arr.length;i--;){ }
今天寫一個h5的視頻彈幕的時候就遇到了這個bug,先貼代碼
function drawScreen() { setTimeout(function () { //繪制視頻 context.drawImage(videoEle, 0, 0, 960, 720) //繪制彈幕 for (var i = 0, len = messages.lengthg; i < len; i++) { // for (var i = messages.length; i--;) { var message = messages[i] context.fillText(message.value, message.x, message.y) messages[i].x -= 10 if (messages[i].x <= 0) { messages.splice(i, 1) } } setTimeout(arguments.callee, 30) }, 30) } drawScreen()
簡要解釋一下這段代碼的意思,我主要想寫一個視頻彈幕,所以利用canvas替代了原有的視頻video標簽。
context.drawImage(videoEle, 0, 0, 960, 720)
這里主要是用drawImage把原有視頻的畫面一張一張的拿出來之后重新繪制在畫布上,相當於取代了原有的video標簽。
for (var i = 0, len = messages.length; i < len; i++) { var message = messages[i] context.fillText(message.value, message.x, message.y) messages[i].x -= 10 if (messages[i].x <= 0) { messages.splice(i, 1) } }
這段則是我們將要討論的代碼。messages是一個數組,保存着彈幕的信息,包括彈幕的位置,彈幕的內容都在這里保存着。
我們先用一個for循環把messages的東西都取出來,之后就開始對數組里面保存的每一個彈幕進行繪制在canvas上。
在此處,當某條彈幕的內容超出了屏幕的寬度的時候,我們需要把它從數組中刪除掉從而節約資源,這時候問題就出現了。
假設現在我們有三條彈幕的信息,分別簡單的設為1,2,3
var messages=[1,2,3]
當第一條彈幕消息超出了屏幕的寬度,這時的for循環中調用這行代碼把第一條彈幕消息從數組中刪除了
messages[i,1]
所以,此時彈幕數組變成了,注意這里,很重要!!
messages // [2,3]
雖然刪除了第一個彈幕消息,但是原來的for還在繼續執行,這時的for循環執行到了i=1的情況。
由於原數組變成了
messages // [2,3]
所以messages[1]讀取到的就是原來的彈幕3。
messages[1] //3
到了這一步,獲取你已經發現問題了。嗯,我們的for循環還在繼續,所以此時的i=2,那么問題就來了messages[2]到底是誰呢?
messages // [2,3]
很明顯,messages只有messages[0]和messages[1],所以很抱歉,此時的messages[3]獲取到的是undefined,好了,接着看我們原來的代碼
for (var i = 0, len = messages.length; i < len; i++) { var message = messages[i] context.fillText(message.value, message.x, message.y) messages[i].x -= 10 if (messages[i].x <= 0) { messages.splice(i, 1) } }
messages[3]是undefined,所以很明顯此處的
context.fillText(message.value, message.x, message.y)
肯定會出錯。undefined怎么可能會拿得到value,x,y等值呢?所以瀏覽器就給我報了個錯
Uncaught TypeError: Cannot read property 'value' of undefined
嗯,很好,我們bug的根源已經找到了,那么該怎么修改呢?這就扯到開篇說的var i=len了。所以此處我們bug的改法是這樣的
for (var i = messages.length; i--;) { var message = messages[i] context.fillText(message.value, message.x, message.y) messages[i].x -= 10 if (messages[i].x <= 0) { messages.splice(i, 1) } }
請注意,for循環的地方改變了,這樣的寫法bug就沒了,因為我們的i值是從最后一個開始遍歷,所以此處當第一條彈幕被刪除的時候,彈幕2與彈幕3早就已經遍歷完了。
希望這篇博文可以幫助需要的人,謝謝大家的閱讀,有不足之處請諒解,望大家指出來共同進步。