2016-11-12 85 views
4

我正在处理一个挑战,我必须显示处于脱机状态和联机状态的Twitch通道。这里是有bug的函数:getJSON函数中覆盖的变量值

function loadStreams(){ 
    for (var i = 0; i < channel_list.length; i++){ 
    offlineName = channel_list[i]; 
    console.log("offline name is: " + offlineName); 
    URL = "https://wind-bow.hyperdev.space/twitch-api/streams/" + channel_list[i] + "?callback=?"; 
    $.getJSON(URL, function(data){ 
     console.log("Now offline name is: " + offlineName); 
     console.log(data); 
     if (data.stream !== null){ 
     currChannel = new Channel(data.stream.channel.display_name, data.stream.channel.status); 
     } 
     else { 
     currChannel = new Channel(offlineName, "Offline"); 
     }  
     outArr.push(currChannel); 
    });  
    } 
    //showAll(); 
} 

channe_list是一个预加载通道名称的字符串数组。它的定义如下:

var channel_list = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]; 

我的代码只需通过channel_list去,获取JSON数据,并返回结果并创建频道对象的新实例,其定义为:

var Channel = function(name, status){ 
    this.name = name; 
    this.status = status; 
} 

由于某些原因,在我的else块中,变量“offlineName”总是被channel_list数组的最后一个值'noobs2ninjas'覆盖。换句话说,当我在else块中创建一个Channel类的实例时,“offlineName”总是“noobs2ninjas”。请让我知道我在这里做错了什么。这是我的CodePen如果你想看看整个事情:

https://codepen.io/tcao2/pen/XNbbbm?editors=1010

回答

5

这是你的问题

你可能知道的多快for循环运行(以及它的速度非常快)但是你的网络相比之下速度并不快,这就是导致你问题的原因。让我解释一下

你正在使用$ .getJSON和URL,它的值取决于你的offlineName,但是你在使用offlineName成功回调too.Now假设为第一req。 offlineName现在是“ESL_SC2”现在ajax请求在URL中使用它,但像往常一样,由于网络延迟,响应不会立即到达,同时循环现在在第二次迭代.BUT等待offlineName现在是“OgamingSC2”!并且会在你的第一个请求成功回调完成时使用,但等待还有更多的条目,所以即使“OgamingSC2”稍后会被击败。此外,循环速度非常快,以至于在第一次或第二次响应进入时,循环已经在最后一次迭代,所以只有最终的offlineName值(noobs2ninjas)能够存活,然后在所有其他的成功回调中使用。

解决方案:解决方案是找到某种方式,每次迭代将保持其值offlineName的值,并在其相应的成功回调中使用相同的值。最简单的方法是使用let申报URLofflineName这限制了每次迭代它本质范围提供类似于封闭的效果

https://codepen.io/vsk/pen/LbNpBQ

与上面的代码唯一的问题是let是最近加法和旧的浏览器不支持它好,所以其他的解决办法是,以真正实现每个请求封闭传递URLofflineName

(function(url,name) { 
    $.getJSON(url, function(data){ 
    if (data.stream !== null){ 
    currChannel = new Channel(data.stream.channel.display_name, data.stream.channel.status); 
    } 
    else { 
    currChannel = new Channel(name, "Offline"); 
    }  
    outArr.push(currChannel); 
    }); 
})(URL,offlineName); 

https://codepen.io/vsk/pen/rWeOGL

编辑:这些被称为自动执行功能并没有什么特别的关于他们的代码下面

function hello(url,name){    //line #39 
    //your code       
}          //ln #53 
hello(URL,offlineName);     //ln #54 

只是一个简写见this你会发现它运行完美但是当你注释掉这个函数的时候(第一行) 39,53,54)再次恢复到旧窃听behavior.You可能会奇怪怎么能简单的功能改变行为,所以drastically.Here是如何 - 这一切都基于范围链

就像Java中的JS解释器(以下简称虚拟机)当它到达hello的定义时,它会逐行读取您的代码,它会读取它(研究参数,返回值和内部代码),然后继续;现在它已经达到了呼叫hello(URL,offlineName);它运行hello中的代码,但后来它意识到getJson有一个回调,此时不能调用它,因此它在“将被称为”列表中记录此列表以及该函数中使用的所有变量的值因此,即使在后面的循环迭代中,URLofflineName被重新初始化/分配了新值,但它们不影响[1]中绑定的值,因为它们与它们没有关系,它们是完全不同的这是因为JS按值传递参数(至少对于原始类型)

但范围链最重要的是即使在循环结束后getJson引用的值仍然存在唯一的问题是你不能直接访问它们,但是VM可以。原因是 - 链中的最后一个函数是一个回调函数(记录在列表中),因此,VM运行时必须能够承受它所需的值在将来,nerds称它为闭包,内部函数总是可以访问外部函数中存在的东西,即使认为外部函数调用已经结束,并且控制已经返回到其他地方。请注意,即使在您之前的窃听代码值中,也只能保存问题他们被覆盖,因为他们都只有一个外部功能,即loadStreams,但是当您创建并调用单独的hello时,每个人创建一个单独的环境(类似于平行宇宙)。

实质上,它创建了范围链,因此每次迭代都可以使其具有“自己的空间”,而不受其他人的干扰。

for loop --> hello() --> getJson's inner function(每次迭代)

您可能会let顺利,但首先在http://caniuse.com/#feat=let

+1

非常感谢您的回复新手看看兼容性图表!有趣的是,问题出在网络的速度和循环的运行速度上(我习惯于Java,刚开始学习js和web dev)。第一种解决方案对我来说很有意义(我将不得不考虑'let'关键字),但对于第二种解决方案,闭包有什么作用? –

+1

@Thinh Cao新增编辑 – Viney

+1

非常感谢! –