关于for循环中i=0与i=arr.length容易被忽视的bug
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早就已经遍历完了。
希望这篇博文可以帮助需要的人,谢谢大家的阅读,有不足之处请谅解,望大家指出来共同进步。
关于for循环中i=0与i=arr.length容易被忽视的bug的更多相关文章
- for循环中i--的妙用 及 两变量互换数值的问题
int[] array = new int[4]; for(int i = 0; i < array.length; i++){ array[i] = (int)(Math.random() * ...
- Python 之 for循环中的lambda
第一种 f = [lambda x: x*i for i in range(4)] (如果将x换成i,调用时候就不用传参数,结果都为3) 对于上面的表达式,调用结果: >>> f ...
- JavaScript形而上的For循环中的Break
break相当于循环中的GOTO,需避免使用. 下面是一个break使用例子. 找出第一个months小于7的项目. const cats = [ { name: 'Mojo', months: 84 ...
- vue 如何在循环中 "监听" 的绑定v-model数据
vue 如何在循环中 "监听" 的绑定v-model数据 阅读目录 vue 如何在循环中 "监听" 的绑定v-model数据 1. 普通属性的值进行监听 2. ...
- Java循环中try...finally...遇到continue
一段很简单的代码,先自己在大脑中给出结果: for (int i = 0; i < 5; i++) { System.out.println("enter: i=" + i) ...
- 浅谈循环中setTimeout执行顺序问题
浅谈循环中setTimeout执行顺序问题 (下面有见解一二) 期望:开始输出一个0,然后每隔一秒依次输出1,2,3,4. for (var i = 0; i < 5; i++) { setTi ...
- JS如何获取PHP循环中的ID
JS如何获取PHP循环中的ID kaalrz 二路公交车 结帖率:83.33% 首先抱歉,因为昨天那帖图片几次都不能用,修改到不能再次修改,今天早上回帖又提示没有这个帖,只好重发一次. 如 ...
- for循环中 i++和++i 是否有区别?
正常情况下 i++和++i是有区别的: 前者是:先引用,后增加, 后者是:先增加,后引用, 但是在for循环中: for(var i=0;i<10;i++){ System.out.print ...
- 循环中的let和const声明
一.循环中的let声明 每次循环的时候let声明都会创建一个新变量i,并将其初始化为i的当前值,所以循环内部创建的每个函数都能得到属于他们的i的副本. 最初的: for (var i = 0 ; i ...
随机推荐
- Pahom on Water(最大流)
Pahom on Water Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...
- 高屋建瓴 cocos2d-x-3.0架构设计 Cocos2d (v.3.0) rendering pipeline roadmap(原文)
Cocos2d (v.3.0) rendering pipeline roadmap Why (the vision) The way currently Cocos2d does rendering ...
- RGB,CMYK,HSB各种颜色表示的转换 C#语言
Introduction Why an article on "colors"? It's the same question I asked myself before writ ...
- javascript面向对象——继承
javascript和其他语言相比,它没有真正意义上的继承,也不能从一个父类extends,要实现它的继承可以通过其他方式来实现: 步骤:1.继承父类的属性 2.继承父类的原型 下面就以一个拖拽为例子 ...
- 树莓派变成一个Web服务器: nginx + php + sqlite
将树莓派变成一个Web服务器,通过访问网页,就可以控制树莓派,比如:查看摄像头\开灯等等. 一想到Linux Web服务器,我们首先想到的是,Apache + MySql + Php. 树莓派可以安装 ...
- 如何在.Net中使用Redis
Redis是一个key-value存储系统.和Memcached类似,但是解决了断电后数据完全丢失的情况,而且她支持更多无化的value类型,除了和string外,还支持lists(链表).sets( ...
- C++编译时函数名修饰约定规则(很具体),MFC提供的宏,extern "C"的作用
调用约定: __cdecl __fastcall与 __stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数 ...
- javascript语言精粹:继承
继承提供了2个有用的任务: 1.代码重用 2.引入了一套类型系统的规范,因为程序员无需编写显示类型转换的代码,他们的工作量将大大减轻.这是一件很好的事情,应为类型转换会丧失类型系统在安全上的优势. 在 ...
- 评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>
侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而 ...
- win32多线程程序设计笔记(第五章)
前面章节介绍了线程创建等过程,现在的问题是:如何在某个线程内终止另外一个正在运行的线程? windows核心编程中提到终止运行线程的方法: 1)线程函数自己返回: 2)线程通过调用ExitThread ...