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早就已经遍历完了。
希望这篇博文可以帮助需要的人,谢谢大家的阅读,有不足之处请谅解,望大家指出来共同进步。