JavaScript变量作用域/面向对象编程和回调函数需要帮助

4

我认为这个问题超出了典型的变量作用域和闭包范畴,或者说我很蠢。无论如何,以下是我的问题...

我正在一个jQuery插件中动态创建一堆对象。这些对象看起来像这样:

function WedgePath(canvas){
    this.targetCanvas = canvas;
    this.label;
    this.logLabel = function(){ console.log(this.label) }
}

jQuery插件大致如下:
(function($) {
  $.fn.myPlugin = function() {

  return $(this).each(function() {

     // Create Wedge Objects
     for(var i = 1; i <= 30; i++){ 
      var newWedge = new WedgePath(canvas);
      newWedge.label = "my_wedge_"+i;
      globalFunction(i, newWedge]);
     } 
    });
  }
})(jQuery);

所以...插件创建了许多wedgeObjects,然后为每个对象调用“globalFunction”,并传入最新的WedgePath实例。全局函数如下。
function globalFunction(indicator_id, pWedge){

    var targetWedge = pWedge; 
    targetWedge.logLabel();

}

接下来发生的是控制台正确记录了每个楔形图的标签。然而,我需要在globalFunction中添加更多复杂性。实际上应该像这样...
function globalFunction(indicator_id, pWedge){

        var targetWedge = pWedge; 

        someSql = "SELECT * FROM myTable WHERE id = ?";
        dbInterface.executeSql(someSql, [indicator_id], function(transaction, result){

            targetWedge.logLabel();

        })

    }

这里涉及到很多内容,我会解释一下。我正在使用客户端数据库存储(我称之为WebSQL)。'dbInterface'是我创建的一个简单Javascript对象的实例,它处理与客户端数据库交互的基础知识[在此问题的末尾显示]。executeSql方法最多可以采用4个参数。

  • SQL字符串
  • 一个可选的参数数组
  • 一个可选的成功处理程序
  • 一个可选的错误处理程序(在此示例中未使用)

我需要发生的是:当WebSQL查询完成后,它需要获取一些数据并操作特定楔子的某些属性。但是,当我在onSuccess处理程序内调用WedgePath的一个实例上的'logLabel'时,我得到的是创建于插件代码中的最后一个WedgePath实例的标签。

现在我怀疑问题出在var newWedge = new WedgePath(canvas)行上。所以我尝试将每个newWedge推入一个数组中,我认为这样可以防止该行在每次迭代时替换或覆盖WedgePath实例...

wedgeArray = [];

// Inside the plugin...
for(var i = 1; i <= 30; i++){ 
    var newWedge = new WedgePath(canvas);
    newWedge.label = "my_wedge_"+i;
    wedgeArray.push(newWedge);
} 

for(var i = 0; i < wedgeArray.length; i++){
    wedgeArray[i].logLabel()
}

但是,我得到的是WedgePath的最后一个实例被创建。

这让我很疯狂。我为问题的长度道歉,但我想尽可能清楚明了。

END

==============================================================

此外,如果相关的话,这是dbInterface对象的代码。

function DatabaseInterface(db){

 var DB = db;

 this.sql = function(sql, arr, pSuccessHandler, pErrorHandler){

  successHandler = (pSuccessHandler) ? pSuccessHandler : this.defaultSuccessHandler;
  errorHandler = (pErrorHandler) ? pErrorHandler : this.defaultErrorHandler;

  DB.transaction(function(tx){

   if(!arr || arr.length == 0){
    tx.executeSql(sql, [], successHandler, errorHandler);
   }else{
    tx.executeSql(sql,arr, successHandler, errorHandler)
   }

  });      
 }

 // ----------------------------------------------------------------
 // A Default Error Handler
 // ----------------------------------------------------------------

 this.defaultErrorHandler = function(transaction, error){
  // error.message is a human-readable string.
     // error.code is a numeric error code
     console.log('WebSQL Error: '+error.message+' (Code '+error.code+')');

     // Handle errors here
     var we_think_this_error_is_fatal = true;
     if (we_think_this_error_is_fatal) return true;
     return false;
 }


 // ----------------------------------------------------------------
 // A Default Success Handler
 // This doesn't do anything except log a success message
 // ----------------------------------------------------------------

 this.defaultSuccessHandler = function(transaction, results)
  {
      console.log("WebSQL Success. Default success handler. No action taken.");
  }    
}
2个回答

3
我猜这是由于客户端数据库存储以异步方式运行,就像 AJAX 调用一样。这意味着它不会停止调用链以等待被调用方法的结果。
因此,JavaScript 引擎在运行 globalFunction 之前完成了 for 循环。
要解决这个问题,您可以在闭包中执行 db 查询。
function getDataForIndicatorAndRegion(indicator_id, region_id, pWedge){ 
    return function (targetWedge) { 
        someSql = "SELECT dataRows.status FROM dataRows WHERE indicator_id = ? AND region_id = ?"; 
        dbInterface.sql(someSql, [indicator_id, region_id], function(transaction, result) {
            targetWedge.changeColor(randomHex());
        });
    }(pWedge);
}

这样可以保留每次执行的pWedge。因为第二种方法是调用自身并将当前的pWedge作为参数发送。

编辑:更新了来自评论的代码,并对其进行了更改。回调函数可能不应该是自我调用的。如果它自我调用,函数的结果会作为参数传递。此外,如果不起作用,请尝试传递其他参数。


这就快成功了,我已经能够感受到它的味道了。我不得不在'executeSql()'匿名onSuccess函数的末尾添加"...})(targetWedge)",它确实记录了targetWedge的正确标签。但现在发生了两件非常奇怪的事情。首先,console.log(result)会生成一个未定义的错误。其次,尽管它记录了楔形标签(这表明正在调用匿名函数),但它也调用了默认的onSuccess处理程序。我是真的很迷惑还是这是一个真正令人困惑的问题? - gargantuan
首先,您的意思是在executeSql内部执行console.log(result)吗?其次,您能否编辑您的问题并提供有关您想要实现什么的信息?小部件的目的是什么? - fredrik
请使用该代码更新问题。看起来有点模糊。但根据我所知,indicator_id和region_id都来自于主函数getDataForIndicatorAndRegion。您应该将它们添加到自调用函数中,以保留它们。}(pWedge,indicator_id,region_id)。 - fredrik
好的,没问题。不知道编辑时间会过期。但是如果保留其他参数,它是否有效? - fredrik
抱歉,回复晚了,今天忙碌。不行,似乎没起作用。我认为你已经回答了原始问题(如果你加入我提到的修改,我会接受你的答案)。我想知道结果问题是否需要完全另一个问题?保持这样的问题进行似乎不好。 - gargantuan
显示剩余2条评论

0

我猜测你的问题是在globalFunction内部发生了修改闭包:

function(transaction, result){

        targetWedge.logLabel();

    })

阅读this


我花了相当多的时间来理解闭包,因为我怀疑这可能是问题所在。我不认为我完全理解了它,但我的推理是每次调用globalFunction时,都会创建一个新的闭包,并且无论目标楔子在什么位置,都会被添加到该闭包中,对于该函数调用实例而言。但也许不是这样。 - gargantuan
1
只是一个猜测,但是:当 $(this).each() 循环迭代时,它会创建一个楔形变量,这个变量会被传递下去,并在 globalFunction 的回调函数中使用。每次 each() 循环的迭代速度比回调函数被调用的速度更快,这意味着在第二个 each 上,楔形引用已经在第一个 globalfunction 回调内部发生了改变。 - Andrew Bullock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接