【问题标题】:Sorting behavior on passing function vs. passing anonymous closure传递函数与传递匿名闭包的排序行为
【发布时间】:2016-04-25 17:49:55
【问题描述】:

问题和疑问

我有一个小型应用程序,它从服务器异步获取 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);(将函数作为参数传递)

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

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

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

我尝试过构建一个以相同方式失败的 MWE,但失败了(一旦我成功就会更新问题)。问题是:出了什么问题?为什么使用匿名闭包有效?

更新

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

所以我发现有问题的行是(!sign) ? sign=1 : 1;,当替换为更透明的if(sign !== undefined){sign=1;} 时,问题就消失了。我认为它的作用是在第一次通过时将 global sign 设置为一个,然后在第二次通过时,将符号定义为 返回 1,函数结束(只完成了一次)。

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

【问题讨论】:

  • 鉴于byName 没有在任何地方使用this 也没有接受任何参数,所以应该没有任何可观察到的差异。
  • SORT 对象是在您附加点击处理程序之前创建的,之后就再也没有改变过,对吧?
  • @Bergi 正确。 SORT 定义一次,并带有一些在某些用户事件上调用的方法。仍然无法重现该错误。数字排序和字母排序都会出现相同的故障模式。
  • @Bergi 实际上,您的第一条评论让我意识到我在清理要发布的代码时遗漏了一个细节……我删除了一个我认为不相关的参数。现在将更新问题。

标签: javascript sorting scope closures


【解决方案1】:

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

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

不。根本没有全局 sign 变量。我猜你实际上想要if (sign === undefined) sign = 1;。这在传递事件时效果不佳,因此您可能需要使用if (!Number.isFinite(sign)) sign = 1;

【讨论】:

  • 感谢您的解释。这也解释了为什么我可以访问事件对象(通常在直接将回调函数作为参数传递时我无法传递变量)。
【解决方案2】:

没有看到更多你的代码,我真的无法知道发生了什么。您发布的 sn-p 似乎没问题。关于您的问题,当您传递 SORT.byName 与将其包裹在匿名函数中发送时,存在细微差别。具体就是byName函数执行时this的值。

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

但是,当您执行click(function() { SORT.byName(); }) 时,byNamethis 的值是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();
}); 

【讨论】:

    【解决方案3】:

    在 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 函数一样将结果添加到循环中,而是使用循环。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-25
      • 2019-11-23
      • 2011-05-11
      • 1970-01-01
      • 2012-09-10
      相关资源
      最近更新 更多