我正在开展一项挑战,我必须显示离线和在线的Twitch频道。这是一个有bug的函数:
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数据,然后返回结果并创建Channel对象的新实例,定义如下:
var Channel = function(name, status){
this.name = name;
this.status = status;
}
由于某些原因,在我的else块中,变量“offlineName”总是被channel_list数组的最后一个值'noobs2ninjas'覆盖。换句话说,当我在else块中创建Channel类的实例时,“offlineName”始终是“noobs2ninjas”。请告诉我这里我做错了什么。这是我的CodePen,如果你想看看整个事情:
答案 0 :(得分:5)
这是你的问题
您可能已经意识到for
循环的运行速度(它非常快),但是与之相比,您的网络并不快,这就是导致您出现问题的原因。让我解释一下
您正在使用带有URL的$ .getJSON,其值取决于offlineName
,但您在成功回调中也使用offlineName
。现在假设第一次请求。 offlineName
是“ESL_SC2”现在ajax请求在URL中使用它,但通常由于网络延迟,响应不会立即到达,同时循环现在是第二次迭代。但是现在等待offlineName
IS“OgamingSC2” !并且将在第一次请求的成功回调完成时使用,但等待有更多的条目,所以即使“OgamingSC2”也会在稍后被击败。此外,循环非常快,以至于在第一或第二响应进入时,你的循环已经处于最后一次迭代,因此只有最终offlineName
值(noobs2ninjas)存活,然后在所有其他循环中成功回调。
解决方案:解决方案是找到一些方法,每次迭代都会保留其offlineName
值,并在相应的成功回调中使用相同的方法。最简单的方法是使用{{ 1}}声明let
和URL
限制每次迭代的范围它本质上提供了类似于闭包的效果
https://codepen.io/vsk/pen/LbNpBQ
上述代码的唯一问题是offlineName
是最近添加的,而旧版浏览器不支持它,所以另一种解决方案是实际实现每个请求传递let
和URL
offlineName
https://codepen.io/vsk/pen/rWeOGL
编辑:这些被称为自动执行功能,它们没有什么特别的,只是下面代码的简写版本
(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);
请参阅this你会发现它运行得很完美但是当你注释掉这个功能的那一刻(第39,53,54行)它又会恢复到旧的错误行为。你可能想知道怎么可能简单的功能如此彻底地改变了行为。这就是 - 它全部基于范围链
就像Java一样,JS解释器(以下称为VM)现在逐行读取代码,当它达到function hello(url,name){ //line #39
//your code
} //ln #53
hello(URL,offlineName); //ln #54
的定义时,它只读取它(研究参数,返回和内部代码)然后继续;现在它已到达调用hello(URL,offlineName);它在hello中运行代码,但后来它意识到hello
有一个回调,此时无法调用它,所以它将它记录在它的“以便稍后调用”列表中当时该函数中使用的所有变量的值 [1]。因此,即使在以后的循环迭代getJson
和URL
中重新初始化/分配了新值,它们也不会不会影响[1]中绑定的值,因为它们与它们没有关系,它们是完全不同的实体。这是因为JS按值传递参数(至少对于原始类型)
但是关于范围链的最重要的事情是,即使在循环结束后,offlineName
回调中引用的值仍然存在但是你不能直接访问它们但VM可以。原因是 - 链中的最后一个函数是一个回调(记录在列表中),因此为了使VM在未来运行时必须能够存活它所需的值,nerds称之为内部函数始终可以访问事物的闭包出现在外部函数中甚至认为外部函数调用已经结束并且控制已经返回到其他地方。请注意,即使在您之前的错误代码值中得到保存,但只有问题是它们被覆盖,因为它们都只有一个外部函数,即{{ 1}}但是当你创建并调用单独的getJson
时,每个人都会创建一个单独的环境(类似于并行宇宙)。
本质上,它创建了范围链,因此每次迭代都可以拥有它的“自有空间”,而不受其他人的干扰。
loadStreams
(每次迭代)
您可能会使用hello
,但请先查看http://caniuse.com/#feat=let