传递函数与传递匿名闭包的排序行为

时间:2016-01-20 02:40:49

标签: javascript sorting scope closures

问题和疑问

我有一个小应用程序,它异步从服务器获取JSON数据,将其格式化为表,并允许用户对表执行一些简单的操作(排序,过滤等)。

其中一个排序函数访问先前生成的表中的DOM元素(下面给出的定义)。

    var SORT = function(){
             var my = {};
             // public methods
             my.byName = function(sign){    
                     (!sign) ? sign=1 : 1;
                     var T = $("#resultArea").find("tbody");
                     var R = T.children("tr");
                     R.sort( function(a,b){
                             var an = $(a).attr("data-name");
                             var bn = $(b).attr("data-name");
                             return sign * an.localeCompare(bn);
                             });
                     R.detach().appendTo(T);
                    }
...
return my; }();

当将其指定为用户可以点击的元素的回调时,我有两种表述。

$("#sort-button").click(SORT.byName);(将函数作为参数传递)

OR

$("sort-button").click(function(){SORT.byName();});(传递调用该​​函数的匿名闭包)

第一个选项失败,但第二个选项有效。以下是对具有536行进行排序的测试用例的失败原因:

  • 前第2行(按字母顺序排在第1行之前)移至第268位。
  • 前一行269移动到位置536。
  • 前一行536移动到位置2.

我已经尝试过并且未能以相同的方式构建失败的MWE(一旦我成功就会更新问题)。问题是:出了什么问题?为什么使用匿名闭包工作?

更新

已对代码段的早期版本进行了清理,以删除参数sign和开头的行(如果将其评估为false或未定义,则将符号设置为1),这个想法是通过传递sign=-1作为参数,可以进行降序排序。当我从实时代码中的定义中删除sign时,问题就消失了。

所以我发现有问题的行是(!sign) ? sign=1 : 1;当被​​更透明的if(sign !== undefined){sign=1;}取代时,问题就会消失。我认为它的作用是在第一次传递时将全局 sign设置为1,然后在第二次传递时定义符号,以便返回 {{ 1}},函数结束(只完成一次传递)。

那为什么这个错误不会导致匿名闭包方法的排序崩溃?

3 个答案:

答案 0 :(得分:1)

如果没有看到更多代码,我无法确切地知道发生了什么。您发布的片段似乎没问题。关于你的问题,当你通过SORT.byName而不是发送包装在匿名函数中时,会有一个细微的差别。具体来说,它是this函数执行时byName的值。

执行click(SORT.byName)时,您正在发送对该函数的直接引用,这意味着当它被调用时,this的值是click的jQuery处理程序集它在它调用你的回调函数之前;通常这是对触发事件的元素的引用。

但是,当您执行click(function() { SORT.byName(); })时,thisbyName的值为SORT对象(但匿名函数中的this仍然是jQuery将其设置为)。这是因为在这里你明确地将函数作为SORT对象的方法调用。

因此,如果您的排序函数依赖于this的值并假设它是SORT对象,那么您可能会遇到问题。

以下是一些演示此行为的代码:

var obj = {
    field: 10,
    method: function() {
        console.log(this);
    }
};

// The first argument to apply sets the value of "this"
function call(f) {
    f.apply("not this", []);
}

call(obj.method); //logs "not this"
call(function() { // logs obj
    obj.method();
}); 

答案 1 :(得分:1)

通过DOM搜索和排序并不好玩。您最好保留一些状态,排序,然后在删除旧的后将新结果附加到DOM。

var myNumbers = [
    [1,'one'],
    [4,'four'],
    [2,'two'],
    [6,'six'],
    [3,'three'],
    [8,'eight'],
    [7,'seven'],
    [5,'five'],
    [10,'ten'],
    [9,'nine']
];

myNumbers.sort(function(a,b){
    if (a[0] > b[0]) {
        return 1;
    }
    if (a[0] > b[0]) {
        return -1;
    }
  return 0;
});

var T = $("tbody");
var R = T.children("tr");

R.detach()

之后你可以像在addElemnt函数中那样在循环中添加结果,而不是循环。

答案 2 :(得分:1)

正如您所发现的那样,问题来自sign参数。当您将一个函数作为事件处理程序传递并且在没有参数的情况下调用SORT.byName()时,您将得到sign=1并且一切都按预期进行。但是当您直接将函数作为处理程序传递时,它将被称为,其中Event对象作为其参数。突然间,您的sign变量中有一个对象,并将其与数字相乘会产生NaN(比较函数的结果无效),这会完全混淆您的排序。

  

当被更透明的if(sign !== undefined){sign=1;}取代时,问题就会消失。我认为它的作用是它在第一次通过时将全局符号设置为1 ...

不。根本没有全局sign变量。我猜你其实想要if (sign === undefined) sign = 1;。传递事件时哪个不起作用,所以你可能想要使用if (!Number.isFinite(sign)) sign = 1;