【问题标题】:How do I refactor my code to reduce the amount of nested loops?如何重构代码以减少嵌套循环的数量?
【发布时间】:2018-10-09 15:31:28
【问题描述】:

这里是一个辅助函数,它将一个类似对象的数组转换为一个实际的数组,然后循环遍历可迭代对象,将列表中的每个值提供给回调函数:

var each = function(iterable, callback) {
  iterable = Array.prototype.concat.apply([], iterable);
  for(var i = 0; i < iterable.length; i++) {
    callback.apply(iterable[i], [iterable[i], i]);
  }
  return iterable;
};

这里我使用前面提到的辅助函数来循环数组:

var found = [];
each(arguments, function(argument) {
  each(argument.split(","), function(selector) {
    each(handle(selector), function(element) {
      if(found.indexOf(element) < 0) {
        found.push(element);
      }
    });
  });
});

第一个循环遍历参数。第二个循环拆分选择器,第三个循环遍历请求的元素并将它们添加到 found 数组中(如果它们尚未添加)。

注意handle 函数采用选择器(一个字符串)并使用document.querySelectorAll 返回一个元素列表。

此脚本有效,但问题在于可读性和性能。

当有许多参数包含多个 (~5-10) 逗号分隔的选择器,然后由 handle 函数单独处理时,就会出现性能问题。

我通过使用类而不是 id 解决了这个问题。

然后是可读性问题,我试图通过将第二个循环移到父循环之外来解决这个问题,但这需要创建更多变量,唯一的区别是改变 where each 循环,这使得可读性更差,因为有更多的代码要阅读。

问题:如何重构代码以减少嵌套循环的数量?

另外,有必要有第一个循环吗?如果我不使用它,我将如何遍历参数以拆分它们以获取每个单独的选择器?我知道split 方法是针对String 类型的,不能在数组上调用。

注意:我使用的是原生 JavaScript,不包括任何库、框架或外部脚本。

【问题讨论】:

  • 为什么handle() 只能用“一个”选择器调用(document.querySelectorAll())?为什么each() 函数?使用.querySelectorAll(),您已经拥有.forEach(),或者您可以简单地将结果转换为具有.forEach().filter().reduce()、...的实际数组。
  • 恕我直言,这个问题属于codereview.stackexchange.com - 但请检查Help section 在发布之前

标签: javascript performance refactoring readability


【解决方案1】:

您似乎正在寻找一些出现在某些顺序数据源中的值,例如

["A,B", "C,A,D", "A", "C,E,B"]

进入一个集合(没有重复),例如

{"A", "B", "C", "D", "E"}

您可以在没有任何第三方库的情况下使用三个嵌套循环来做到这一点(无需担心性能可读性):

const s = new Set();
for (let x of arguments) {
    for (let g of x.split(",")) {
        for (let i of g) {
            s.add(i);
        }
    }
}

按功能编写,您可以将整件事简化为:

new Set(arguments.join().split(','))

这当然是假设您的任何选择器中都没有逗号。

这里的列表并不是真正嵌套的,因为您在三遍中完全处理了原始列表。细分:

  • 原文:[ 'A,B', 'C,A,D', 'A', 'C,E,B' ]
  • 加入后:'A,B,C,A,D,A,C,E,B'
  • 拆分后:[ 'A', 'B', 'C', 'A', 'D', 'A', 'C', 'E', 'B' ]
  • 设置后:Set { 'A', 'B', 'C', 'D', 'E' }

我认为即使在循环情况下,您也具有线性复杂性,因为您实际上是在原始数组上进行三遍。并非每次传递都会触及每个元素,因此尽管在原始情况下看起来像是三次复杂度,但您应该没问题,但如果情况看起来不好,请考虑进行分析。

【讨论】:

  • 不过,这可能会更慢。
  • 是的,for 循环可能是最快的,但如果它很重要,总是可以分析代码。我不会把它放在像 V8 这样的顶级 JS 引擎来优化像 new Set(arguments.join().split(',')) 这样的行......事实上我希望 V8 不会从 arguments.join() 实现数组,而是以类似于 for 循环的方式组合连接和拆分。
  • 我敢打赌 20:1 它不会做那种优化。
【解决方案2】:

您可以加入然后重新拆分参数并使用三元运算符来减少行数:)

var found = [];
each(arguments.join(",").split(","), function(selector) {
  each(handle(selector), function(element) {
    return (found.indexOf(element) < 0) ? found.push(element) : null;
  });
});

【讨论】:

  • 非常感谢您的帮助,但(谢天谢地)这不再是问题 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-21
  • 1970-01-01
  • 2019-03-27
  • 2022-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多