Twitch观众项目循环问题:仅获最后一个频道数据如何解决?
嘿,我完全懂你遇到的这个问题!这是JavaScript新手很容易踩的闭包陷阱坑,我来给你一步步拆解原因和解决办法:
首先先把你当前的代码贴出来方便对照:
var url1="https://wind-bow.glitch.me/twitch-api/streams/"; var channelList= ["channel1","channel2","channel3"]; //I am trying to create for loop to define url with different channel names. for(var i=0;i<channelList.length;i++){ var url="https://wind-bow.glitch.me/twitch-api/channels/" + channelList[i]; $.getJSON(url1+channelList[i],function(data){ //When the user click off button,I want to show offline channels. $("#off").on("click", () => { if(data.stream === null) { $.getJSON(url,function(seconddata){ $("#main").append("<div class='row' style='margin-top:5px;'><div class='col-md-4'><p><img src='"+seconddata.logo+"'width='42' height='42'>"+" "+"<a href='"+seconddata.url+"' target=_blank>"+seconddata.display_name+"</a></p></div></div>") }); } }); }); }
问题根源
你遇到的核心问题有两个:
var声明的变量没有块级作用域:var i和var url是在全局/函数作用域下的,循环执行时只会创建一次变量,每次循环都是修改同一个变量的值。- 异步操作的特性:
$.getJSON是异步请求——循环会先一口气跑完,所有的请求才会陆续返回并执行回调函数。这时候i已经变成了channelList.length,url也变成了最后一个频道的地址,所以所有回调都只会拿最后一个频道的数据。
解决办法
下面给你三种常用的解决方案,按推荐程度排序:
方案1:用ES6的let替代var(最简洁)
let有块级作用域,每次循环都会创建一个全新的i和url变量,每个异步回调都会捕获到当前循环对应的那个变量:
var url1="https://wind-bow.glitch.me/twitch-api/streams/"; var channelList= ["channel1","channel2","channel3"]; // 把循环里的var改成let for(let i=0;i<channelList.length;i++){ let url="https://wind-bow.glitch.me/twitch-api/channels/" + channelList[i]; $.getJSON(url1+channelList[i],function(data){ $("#off").on("click", () => { if(data.stream === null) { $.getJSON(url,function(seconddata){ $("#main").append("<div class='row' style='margin-top:5px;'><div class='col-md-4'><p><img src='"+seconddata.logo+"'width='42' height='42'>"+" "+"<a href='"+seconddata.url+"' target=_blank>"+seconddata.display_name+"</a></p></div></div>") }); } }); }); }
方案2:用立即执行函数(IIFE)创建闭包(兼容旧环境)
如果你的运行环境不支持ES6,就用IIFE把每次循环的变量封装起来,给每个回调单独绑定当前的频道和地址:
var url1="https://wind-bow.glitch.me/twitch-api/streams/"; var channelList= ["channel1","channel2","channel3"]; for(var i=0;i<channelList.length;i++){ // 用IIFE把当前循环的频道和url传进去,创建独立作用域 (function(currentChannel, currentUrl) { $.getJSON(url1+currentChannel,function(data){ $("#off").on("click", () => { if(data.stream === null) { $.getJSON(currentUrl,function(seconddata){ $("#main").append("<div class='row' style='margin-top:5px;'><div class='col-md-4'><p><img src='"+seconddata.logo+"'width='42' height='42'>"+" "+"<a href='"+seconddata.url+"' target=_blank>"+seconddata.display_name+"</a></p></div></div>") }); } }); }); })(channelList[i], "https://wind-bow.glitch.me/twitch-api/channels/" + channelList[i]); }
方案3:用forEach遍历数组(更易读)
forEach的回调函数本身就自带独立作用域,每个迭代的参数都是当前频道的独立副本,完美避开闭包陷阱:
var url1="https://wind-bow.glitch.me/twitch-api/streams/"; var channelList= ["channel1","channel2","channel3"]; // 用forEach遍历每个频道 channelList.forEach(function(channel) { var url="https://wind-bow.glitch.me/twitch-api/channels/" + channel; $.getJSON(url1+channel,function(data){ $("#off").on("click", () => { if(data.stream === null) { $.getJSON(url,function(seconddata){ $("#main").append("<div class='row' style='margin-top:5px;'><div class='col-md-4'><p><img src='"+seconddata.logo+"'width='42' height='42'>"+" "+"<a href='"+seconddata.url+"' target=_blank>"+seconddata.display_name+"</a></p></div></div>") }); } }); }); });
额外优化建议
你现在把$("#off").on("click")放在循环里了,这样会给按钮绑定多次点击事件(频道有多少个就绑定多少次),其实完全没必要。可以先把所有频道的数据存起来,只绑定一次点击事件,点击时再渲染离线频道:
var url1="https://wind-bow.glitch.me/twitch-api/streams/"; var channelList= ["channel1","channel2","channel3"]; // 用数组存储所有频道的完整数据 var allChannelData = []; // 先批量获取所有频道的流数据和频道信息 channelList.forEach(function(channel) { $.getJSON(url1 + channel, function(streamData) { $.getJSON(`https://wind-bow.glitch.me/twitch-api/channels/${channel}`, function(channelInfo) { allChannelData.push({ isOnline: streamData.stream !== null, info: channelInfo }); }); }); }); // 只给按钮绑定一次点击事件 $("#off").on("click", () => { $("#main").empty(); // 先清空之前的内容 // 遍历所有数据,渲染离线频道 allChannelData.forEach(channel => { if(!channel.isOnline) { $("#main").append(` <div class='row' style='margin-top:5px;'> <div class='col-md-4'> <p> <img src='${channel.info.logo}' width='42' height='42'> <a href='${channel.info.url}' target=_blank>${channel.info.display_name}</a> </p> </div> </div> `); } }); });
这样代码逻辑更清晰,性能也更好~
内容的提问来源于stack exchange,提问作者Çağatay Sert
相关产品推荐
相关产品推荐

