【问题标题】:ajax and variable scopeajax 和变量范围
【发布时间】:2011-08-02 19:24:52
【问题描述】:

我正在编写一个脚本来读取多个 twitter 提要并将它们写入一个 div。当我只是向特定提要发出 1 个 AJAX 请求时,该代码有效,但是当我尝试遍历提要列表时,我什么也得不到。我很确定这里应该归咎于变量范围,但我不知道如何在 Javascript 中解决它。

这是一个有效的 1-feed 版本,略微简化(顺便使用 jQuery):

function getTweets()
{
$.getJSON("http://api.twitter.com/1/statuses/user_timeline.json?screen_name=[my username]&count=10&include_rts=true&callback=?",
function(tweets){

    var tweetArray = new Array();

    $.each(tweets, function(i,item){
        var tweet = item.text;
        var htmlString = ...;
        tweetArray.push(htmlString);
    });

    for(var i=0;i<tweetArray.length;i++)
    {
        $("#tweet-div").append(tweetArray[i]);
    }

},"json");
}

因此,获取 JSON 数据,循环遍历其内容,将一些数据转换为 HTML 并将该 HTML 填充到一个数组中,然后循环该数组以将其内容填充到我的 DOM 中。

这是我用来尝试读取多个提要(但失败)的代码:

function getTweets()
{
var tweetArray = new Array();
var users = new Array();
var user1 = [twitter name];
var user2 = [twitter name]; //etc for each user
users.push(user1,user2,...);

for(var n=0;n<users.length;n++)
{
    $.getJSON("http://api.twitter.com/1/statuses/user_timeline.json?screen_name="+users[n]+"&count=10&include_rts=true&callback=?",
    function(tweets){
        $.each(tweets, function(i,item){
            var tweet = item.text;
            var htmlString = ...;
            tweetArray.push(htmlString);
        });



},"json");

}//end main FOR loop

 for(var i=0;i<tweetArray.length;i++)
 {
    $("#tweet-div").append(tweetArray[i]);
 }
}

代码 #2 的最大区别在于,我将 tweetArray 从 .getJSON 成功函数一侧移到了它之外。似乎正在发生的事情是,当我在最后循环通过 .append() 部分中的 tweetArray 时,tweetArray 中没有任何内容。

我的猜测是否正确,因为 .getJSON 成功函数是一个单独的函数,它无法从我的 getTweets() 函数访问局部变量 tweetArray?

如果是这样,我该如何解决这个问题,同时仍然能够像我在 #2 中尝试做的那样循环遍历我的用户列表?

如果我在这里卡住的代码中有任何拼写错误,我深表歉意,但轻微的语法错误不是这里的问题,因为在我的网站上 #1 运行良好。


更新:我意识到如果我在尝试将 tweetArray 数据附加到我的 DOM 之前将 alert(tweetArray) 插入到我的代码中,它甚至不需要将 tweetArray 设为全局变量即可工作。现在我更不知道发生了什么。


更新:这是我正在使用的实际代码的复制/粘贴:

function getTweets()
{
//retrieves a JSON file from twitter.com with the information specified in the URL
//parameters. A description of parameters can be found at
//https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
var tweetArray = new Array();//holds HTML-formatted tweets for everyone

var users = new Array();//holds all the user account info
var user1 = "[twitter name]";
var user2 = "[twitter name]";
var user3 = "[twitter name]";
var user4 = "[twitter name]";
var user5 = "[twitter name]";
var user6 = "[twitter name]";
var user7 = "[twitter name]";
var user8 = "[twitter name]";

users.push(user1,user2,user3,user4,user5,user6,user7,user8);

for(var n=0;n<users.length;n++)
{

    $.getJSON("http://api.twitter.com/1/statuses/user_timeline.json?screen_name="+users[n]+"&count=10&include_rts=true&callback=?",
    function(tweets){
        $.each(tweets, function(i,item){

            var tweetString;

            //the names of the various data can be found
            //in the twitter API reference pages, such as
            //https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
            var tweetText = '<div class="tweet-text" id="tweet-text'+i+'" >'+item.text+'</div>';
            var userID = item.user.id_str;
            var userName = '<div class="tweet-user-name" id="tweet-user-name'+i+'" >'+item.user.name+'</div>';
            var userPic = '<img class="tweet-img" id="tweet-img'+i+'" src="'+item.user.profile_image_url +'" />';

            //formats created_at data from twitter into a nice date/time
            var tweetDate = item.created_at;
            var hourminute = tweetDate.substr(tweetDate.indexOf(":")-2,5);
            var hour = parseInt(hourminute.substr(0,2))+7;//the +7 is a time zone correction
            if (hour > 12)
            {
                hour = hour-12;
            }
            else if(hour == 0)
            {
                hour = 12;
            }
            var ampm = "AM";
            if(hour>=12)
            {
                ampm = "PM";
            }
            var minute = hourminute.substr(3,2);
            var day = tweetDate.substr(0,3)
            var dateNum = tweetDate.substr(8,2);
            var month = tweetDate.substr(4,3);
            switch(month)
            {
                case "Jan":
                month="01"
                break;
                case "Feb":
                month="02"
                break;
                case "Mar":
                month="03"
                break;
                case "Apr":
                month="04"
                break;
                case "May":
                month="05"
                break;
                case "Jun":
                month="06"
                break;
                case "Jul":
                month="07"
                break;
                case "Aug":
                month="08"
                break;
                case "Sep":
                month="09"
                break;
                case "Oct":
                month="10"
                break;
                case "Nov":
                month="11"
                break;
                case "Dec":
                month="12"
                break;
            }
            var year = tweetDate.substr(-4,4);
            var dateFormatted = '<div class="tweet-date" id="tweet-date'+i+'" >'+day+' '+month+'.'+dateNum+'.'+year+' • ' + hour +':'+ minute +' '+ ampm+'</div>';

            //reformats the date yet again so that tweets can be sorted chronologically
            var sortableDate = month+dateNum+year;

            //combines all tweet information into one string of HTML
            tweetString = '<div class="tweet" id="tweet'+i+'">'+ dateFormatted+userName+tweetText + '</div>';

            var tempArray = new Array();
            tempArray=[sortableDate,tweetString];

            //pushes formatted tweet HTML code into an array
            tweetArray.push(tempArray);
        });
    },"json");
}

//at this point in the code, the user's browser is still waiting to receive
//the JSON files from twitter, and tweetArray has no data in it. The next line
//causes the code to break for a few seconds while the data is retrieved from
//twitter before trying to insert anything into the DOM

var waitForJSON = setTimeout(function(){insertTweets(tweetArray)},2000);

}


function insertTweets(content)
{

//sort tweets in tweetArray by date instead of by author
content = content.sort();
content = content.reverse();

//change or remove the "Loading Tweets" message
if(content == "")
{
    $("#load-status").html("There was an error retreiving tweets.");
}
else
{
    $("#load-status").empty();
}

//loops through tweetArray and inserts HTML code into page
for(var i=0;i<content.length;i++)
{
    $("#tweet-box").append(content[i][1]);
}

//create patterned background effect for tweets
$(".tweet:odd").css("background-color","#f0f0f0");
$(".tweet:even").css("background-color","#dddddd");
}

【问题讨论】:

  • 好的,另一个奇怪的问题。我以为我已经通过将 tweetArray 设为全局变量来解决问题。为了测试,我在 .append() 代码之前有 alert(tweetArray) 。当我删除警报时,整个功能停止工作!我不知道发生了什么。
  • 请阅读OP顶部的UPDATE。
  • alert() 修复问题的原因是 alert() 导致代码暂停足够长的时间以使 ajax 成功函数运行。它与我最初认为的变量范围无关。

标签: javascript jquery ajax twitter scope


【解决方案1】:

我的方向与上一个答案相反。你不希望 tweetArray 是全局的。

首先,如果您什么都没有,那么您的代码中可能存在语法错误,您需要找到它,因为多用户代码应该给您太多(重复的推文)而不是什么。由于这是伪代码,而不是您的真实代码,因此我们无法真正帮助解决语法错误的位置。因为语法错误很可能出现在 getJSON 成功函数处理程序中,所以您可能不会直接在调试器中看到语法错误。要查看它在哪里,您需要在回调的开头放置一个断点并逐步执行它以查看它在哪里发疯或在您的回调中放置一个 try/catch 并在抛出异常时输出一些调试信息。

除了潜在的语法错误之外,我在您的第二个代码块中看到的问题是您在所有成功处理程序之间共享 tweetArray,但是在每个成功处理程序中,您正在遍历整个 tweetArray 并附加结果。这意味着第一个 JSON 调用将起作用,因为它将初始推文收集到数组中,然后将它们全部附加。第二个 JSON 调用将添加到同一个数组,然后遍历整个数组并将它们全部追加。这将再次插入第一次通话中的推文——不是你想要的。第三次调用将再次复制之前的所有推文。解决这部分问题的方法是将 tweetArray 放在成功函数中,这样您只需收集然后插入来自特定 JSON 调用的推文。

要让每条推文只附加一次,我认为您需要像这样进行更改:

function getTweets()
{
    var users = new Array();
    var user1 = [twitter name];
    var user2 = [twitter name]; //etc for each user
    users.push(user1,user2,...);

    for(var n=0;n<users.length;n++)
    {
        $.getJSON("http://api.twitter.com/1/statuses/user_timeline.json?screen_name="+users[n]+"&count=10&include_rts=true&callback=?",
        function(tweets){
            var thisUsersTweets = [];   // initialize this locally and separately for each success handler
            $.each(tweets, function(i,item){
                var tweet = item.text;
                var htmlString = ...;
                thisUsersTweets.push(htmlString);   // collect the tweets for this user only
            });

            for(var i=0;i<thisUsersTweets.length;i++)
            {
                $("#tweet-div").append(thisUsersTweets[i]);   // append just this user's tweets
            }

        },"json");  // end of getJSON call
    }               // end main "for" loop
}                   // end of getTweets()

如果您需要将整个推文列表收集到一个数组中以供以后在函数中使用,那么您也可以这样做,但由于您没有要求,所以我的示例没有这样做。

现在您已经发布了您的实际代码,并且我看到您希望在收集所有推文后对它们进行排序,这是在您的实际代码中执行此操作的一种方法。在此版本中,我们会记录您发起了多少推文请求,并在收到所有回复后,我们调用 insertTweets。

我已将您的实际代码修改为此处的代码。只有四个变化:

  1. 在函数顶层添加responsesRemaining变量
  2. 移除超时调用
  3. 在每次 JSON 调用之前增加 responsesRemaining 变量
  4. 在成功处理程序结束时,递减 responsesRemaining 变量,如果它降至零(收到所有响应),则调用 insertTweets()

此版本的代码将比您的 setTimeout 版本具有以下优势:

  1. 无论推文响应的时间如何,它都会起作用。
  2. 它会在推文全部可用时立即插入推文,而无需等待一定的时间,从而更快地显示结果。
  3. 如果任何获取推文的请求需要更长的时间才能成功,此方法仍会显示他们的推文(您的不会)。

这是修改后的代码:

function getTweets()
{
//retrieves a JSON file from twitter.com with the information specified in the URL
//parameters. A description of parameters can be found at
//https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
var tweetArray = new Array();//holds HTML-formatted tweets for everyone
var responsesRemaining = 0;

var users = new Array();//holds all the user account info
var user1 = "[twitter name]";
var user2 = "[twitter name]";
var user3 = "[twitter name]";
var user4 = "[twitter name]";
var user5 = "[twitter name]";
var user6 = "[twitter name]";
var user7 = "[twitter name]";
var user8 = "[twitter name]";

users.push(user1,user2,user3,user4,user5,user6,user7,user8);

for(var n=0;n<users.length;n++)
{
    ++responsesRemaining;
    $.getJSON("http://api.twitter.com/1/statuses/user_timeline.json?screen_name="+users[n]+"&count=10&include_rts=true&callback=?",
    function(tweets){
        $.each(tweets, function(i,item){

            var tweetString;

            //the names of the various data can be found
            //in the twitter API reference pages, such as
            //https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
            var tweetText = '<div class="tweet-text" id="tweet-text'+i+'" >'+item.text+'</div>';
            var userID = item.user.id_str;
            var userName = '<div class="tweet-user-name" id="tweet-user-name'+i+'" >'+item.user.name+'</div>';
            var userPic = '<img class="tweet-img" id="tweet-img'+i+'" src="'+item.user.profile_image_url +'" />';

            //formats created_at data from twitter into a nice date/time
            var tweetDate = item.created_at;
            var hourminute = tweetDate.substr(tweetDate.indexOf(":")-2,5);
            var hour = parseInt(hourminute.substr(0,2))+7;//the +7 is a time zone correction
            if (hour > 12)
            {
                hour = hour-12;
            }
            else if(hour == 0)
            {
                hour = 12;
            }
            var ampm = "AM";
            if(hour>=12)
            {
                ampm = "PM";
            }
            var minute = hourminute.substr(3,2);
            var day = tweetDate.substr(0,3)
            var dateNum = tweetDate.substr(8,2);
            var month = tweetDate.substr(4,3);
            switch(month)
            {
                case "Jan":
                month="01"
                break;
                case "Feb":
                month="02"
                break;
                case "Mar":
                month="03"
                break;
                case "Apr":
                month="04"
                break;
                case "May":
                month="05"
                break;
                case "Jun":
                month="06"
                break;
                case "Jul":
                month="07"
                break;
                case "Aug":
                month="08"
                break;
                case "Sep":
                month="09"
                break;
                case "Oct":
                month="10"
                break;
                case "Nov":
                month="11"
                break;
                case "Dec":
                month="12"
                break;
            }
            var year = tweetDate.substr(-4,4);
            var dateFormatted = '<div class="tweet-date" id="tweet-date'+i+'" >'+day+' '+month+'.'+dateNum+'.'+year+' • ' + hour +':'+ minute +' '+ ampm+'</div>';

            //reformats the date yet again so that tweets can be sorted chronologically
            var sortableDate = month+dateNum+year;

            //combines all tweet information into one string of HTML
            tweetString = '<div class="tweet" id="tweet'+i+'">'+ dateFormatted+userName+tweetText + '</div>';

            var tempArray = new Array();
            tempArray=[sortableDate,tweetString];

            //pushes formatted tweet HTML code into an array
            tweetArray.push(tempArray);
        });
        --responsesRemaining;
        if (responsesRemaining <= 0) {
            insertTweets(tweetArray);
        }
    },"json");
}
}


function insertTweets(content)
{

//sort tweets in tweetArray by date instead of by author
content = content.sort();
content = content.reverse();

//change or remove the "Loading Tweets" message
if(content == "")
{
    $("#load-status").html("There was an error retreiving tweets.");
}
else
{
    $("#load-status").empty();
}

//loops through tweetArray and inserts HTML code into page
for(var i=0;i<content.length;i++)
{
    $("#tweet-box").append(content[i][1]);
}

//create patterned background effect for tweets
$(".tweet:odd").css("background-color","#f0f0f0");
$(".tweet:even").css("background-color","#dddddd");
}

【讨论】:

  • 在我的实际代码中,将推文附加到 DOM 的 FOR 循环位于读取推文的 FOR 循环之外。我在我的原始帖子中修复了它。我很欣赏你的代码;但是,我确实需要存储整个数组以供以后使用。在我将所有推文收集到我的 tweetArray 后,我按日期对它们进行排序,以便在我的网站上,推文是按日期而不是按用户排序的。如果您知道如何在不使用 tweetArray 的全局变量的情况下做到这一点,那么我会全力以赴。另外,对于OP评论中的问题的第二部分,我仍然一无所知。
  • 这就解释了问题所在。 getJSON 调用尚未完成。您对 getJSON 的调用是异步的,并且调用只会启动调用。在调用成功处理程序之前它们还没有完成,因此您试图在数组被填充之前将它们添加到页面中。我建议按照我在这里所做的方式进行操作。然后,当成功处理程序有数据时,添加每个调用的推文。
  • 成功!实际上,我只是在执行 .getJSON 内容的循环之后卡住了 setTimeout,并且在 2 秒后它将 tweetArray 传递到另一个执行插入的函数中。谢谢你的帮助。我确信问题与范围或闭包或其他问题有关,所以我离题了。
  • 如果您向我们展示您的真实代码(不是您迄今为止发布的缩写代码),我们可以向您展示您应该如何真正做到这一点。您提到的 setTimeout 是一种技巧,而不是正确的做事方式。如果事情的时机发生变化,它很容易崩溃,而且这不是学习如何解决这些问题的“正确”方式。
  • 我在原始帖子的底部包含了我的实际代码。
【解决方案2】:

我的猜测是否正确,因为 .getJSON 成功函数是一个单独的函数,它无法从我的 getTweets() 函数访问局部变量 tweetArray?

没错。您可以通过将 tweetArray 放置在函数外部将其设为全局变量来解决此问题:

var tweetArray = new Array();
function getTweets()
{
    ...
}

【讨论】:

  • 不过,还有一个问题:我基本上将一个巨大的 .js 文件用于多页网站,其中只有一个页面需要与这个 twitter 提要有任何关系。我的所有页面都存在这个全局变量,是否会有任何与性能相关的问题或类似的问题?我只是习惯于面向对象的语言,其中的范围选项比“无处不在”或“绝对只有一个地方”多一点。
  • 没有。与网站往往存在的所有其他延迟瓶颈相比,拥有一个全局变量不会以任何有意义的方式影响性能。
  • 请参阅我在原始问题的评论部分附加的第二个问题。
  • 虽然全局变量的性能可能没有太大变化,但对全局命名空间的污染更大。如果您在某种主 js 文件中执行此操作,您可能希望将全局污染降至最低。
猜你喜欢
  • 2014-01-31
  • 2021-06-13
  • 1970-01-01
  • 2023-03-28
  • 2017-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多