zoukankan      html  css  js  c++  java
  • 关于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早就已经遍历完了。

    希望这篇博文可以帮助需要的人,谢谢大家的阅读,有不足之处请谅解,望大家指出来共同进步。

  • 相关阅读:
    8.用户注销
    7.用户登陆,用户退出,记住用户名和密码
    6.后台验证码-session作用域
    5.验证用户名是否已经被注册:AJAXC请求
    4.前端注册表单验证 && 表单回填
    3.注册后台处理逻辑编写
    HTTP Status 500
    jquery之stop()的用法
    angular.forEach
    jquery如何获取第一个或最后一个子元素?
  • 原文地址:https://www.cnblogs.com/jelly7723/p/5080580.html
Copyright © 2011-2022 走看看