2017 年 5 月 31 日星期三

数据表格中的迭代器

DataTables 从根本上运行重复数据。作为开发者,我们当然想要访问和处理这些数据,一次处理一个条目或以某种方式操作它们。我们称之为对这些条目进行迭代循环。DataTables 有许多内置的迭代方法,但如果你刚接触 DataTables API,不一定明白应该使用哪种方法。

在本文中,我想花一些时间深入探究 API 并解释 DataTables 中内置的迭代器,以及何时最好使用它们。我还将讨论使用每个迭代器是否有任何惩罚(提示 - 是的,易用性和性能通常需要权衡取舍)。

DataTables 中有三个主要的迭代器

还有一些帮助器方法,它们可以让你更轻松地从重复的数组中获取数据,例如 reduce()map()。这些内容将在文章最后探讨,但深度较浅。

基本原理

DataTables API 在本质上类似数组,也就是说它看起来非常像 Javascript 数组。它有一个 length 属性、 slice()push() 方法,这些方法你通常都会将它们与数组关联在一起 - 实际上,它使用了 JavaScript 中内置的大量数组函数;你甚至可以使用 for 循环来迭代它,但它实际上并不是一个数组。这是一个类 (DataTables.Api) 的实例,它的外观和行为都非常像一个数组。

在讨论迭代方法时,重要的是要牢记上面介绍的内容,因为这是 DataTables 及其 API 如何工作的关键(以及 链式调用 的概念)。

核心迭代方法

你应该使用的迭代方法取决于你要实际执行的表操作或数据检索。简单总结一下

  • rows().every()columns().every() 以及 cells().every() - 在你想要对每个选定条目执行 API 方法时使用它们,但会有一定性能损失。
  • each() - 不是行、列或单元格的任何数据类型的迭代器(也就是说,用于 DOM 元素和数据)
  • iterator() - 较低级别的 API,但会带来一个巨大的开发开销。

*.every 方法

您可能早已注意到,*.every() 方法总是显示为链接到表项访问 API 方法(rows().every()columns().every()cells().every())之一,而不是仅描述为 every(),就像 each() 和其他实用程序方法。

每个 *.every() 方法的实际操作取决于使用的项访问方法,具体取决于它们如何执行以及向它们传入的参数。它们提供了一种简单的方法,可在该对象的范围内针对所选的每个项访问 DataTables API 对象。

我们用一个示例来说明:您想在表中的每一行上使用 row().child.show(),以使所有子行可见。该方法没有复数形式,因此您需要依次访问每一行并调用该方法。您可能很想做类似的事情

var nodes = table.rows().nodes();
for ( var i=0, ien=nodes.length ; i<ien ; i++ ) {
    table.row( nodes[i] ).child.show();
}

这将奏效,但非常麻烦,因为您需要选择所有行,遍历它们,选择每一行,然后对它们执行所需的运算。

*.every() 方法会自动将内部函数的范围更改为该项的 DataTable API 实例。这意味着您可以在上面中使用 this 代替 table.row(...) - 换句话说,它是为您完成的选择。因此,我们可以将上面内容重写为

table.rows().every( {
    this.child.show();
} );

更容易阅读,简洁得多!

这对 columns()cells() 方法同样有效,但不能用于 API 使用的任何其他数据类型。例如,您无法使用 rows().data().every() - *.every() 函数的全部目的是为了方便访问该项的 API 实例,而这对其他数据类型来说不是有效的事情。

现在转向缺点 - 由于上下文切换,*.every() 方法成为 DataTables 中最慢的迭代器。必须为每个项创建一个新的 API 实例,并将函数上下文切换到该实例。尽管这在现代浏览器上速度很快,但在大数据集上仍然会很明显。因此,当需要最大性能时,不应使用这些方法(例如,拖放事件)。然而,它们的使用方便性不可低估,并且应该在大多数情况下(例如,单击事件)使用它们。

each() 方法

对于*.every()方法无法处理的地方,each() 方法可以处理!*.every() 的一个限制是,它只能与上述提到的三个复数项选择器方法连接。对于 DataTables API 可以承载的所有其他数据类型,我们使用 each() 来访问实例中的数据。

许多人会熟悉 jQuery.each 方法,该方法可以用于迭代对象和数组 - DataTables 自己的 each() 方法基本上是相同的:对于实例中的每一项调用一次回调函数,允许对其进行操作

table
    .rows()
    .nodes()
    .each( function( value, index, api ) {
        $( value ).addClass( 'loopy' );
    } );

在这种情况下,我们使用 rows().nodes() 获取表中的所有行节点(tr 元素),然后遍历它们,使用 jQuery 为每个元素添加一个类。

这个迭代方法非常快,因为它不需要在每次执行回调函数时创建一个新的 API 实例。话虽这么说,它仍然不如传统的 for 循环快。如果在代码的关键性能区域中,上面可以写成以下内容

var nodes = table.rows().nodes();

for ( var i=0, ien=nodes.length ; i<ien ; i++ ) {
    $( nodes[i] ).addClass( 'loopy' );
}

需要指出的是,上面只是为了演示代码 - jQuery 已经内置了非常好的数组处理(毕竟它也是类数组),我们可以直接将节点数组传递给 jQuery。我们还可以使用 to$() 方法将 DataTables API 实例转换为 jQuery 实例

table.rows().nodes().to$().addClass( 'loopy' );

iterator() 方法

iterator() 方法与 *.every() 方法类似,因为它可用于访问表中的项信息。但在这种情况下,它不会创建一个新的 API 实例并且不会更改回调函数的作用域。相反,它只提供一个索引到 DataTables 的内部数据存储中的项 - settings()

这就是事情变得有点棘手的地方。如果您查看 settings() 文档,您会注意到强烈建议您不要使用它!settings 对象中的信息被认为是非公开 API,并且参数名称可以在版本之间更改,而无需警告。但是,如果不提 iterator() 方法,关于 DataTables 中迭代器的任何讨论都将严重不足。

iterator() 方法的主要优点在于它的速度很快。它是一个简单的循环,带有用于查找数据的回调函数和索引。它被 DataTables 提供的 API 方法使用,如果您编写的代码必须在微观层面表现得非常好,这就是您的做法。

我强烈建议任何使用 iterator() 的方法都应该写在 自定义 API 方法 中,因此如果您确实访问了需要在版本之间更改的任何内部信息,您只需更改一个点。通常,在“用户空间”中访问表项时,最好坚持使用 *.every() 方法!

实用方法

虽然上述讨论涵盖了 DataTables 中最常用的迭代方法,但还有一些其他值得强调的方法

map()

each() 方法(以及它的 jQuery 表亲)非常相似,map() 方法用于从另一个数组创建一个数组。例如,考虑我们有一个对象数组,我们想要从这些对象中提取一个数据点,例如一个 totalCost 参数

var totalCosts = table
    .rows()
    .data()
    .map( function ( data ) {
        return data.totalCost;
    } );

pluck()

对上述操作的需求并不罕见——事实上,它被称为“提取数据”,而 DataTables 提供了一个实用方法来轻松地从对象中提取单个数据点:pluck()。有了此方法,我们可将上述内容写成

var totalCosts = table.rows().data().pluck( 'totalCost' );

值得注意的是,尽管 pluck() 可用于访问单个数据点,但不能用于构建更复杂的数据对象。它也不能用于访问嵌套数据。对于这种情况,map() 方法仍然派得上用场!

reduce()

reduce() 方法用于将数组转换成一个单一的标量值。它通常用于在 DataTable 中进行求和计算,尽管你也可以将它用于需要将数据集简化为单个值的任何其他计算。继续我们 totalCost 的示例,如果我们想要对该值求和,我们可以使用

var totalCost = table
    .rows()
    .data()
    .pluck( 'totalCost' )
    .reduce( function ( a, b ) {
        return a + b;
    }, 0 );

toArray() 和 to$()

尽管 DataTables API 实例非常有用并且提供了许多实用方法,但有时你可能只需要一个简单的数组数据(例如:在 JSON.stringify() 它时),或者你想要将 DataTables API 实例转换成为一个 jQuery 对象以便可以使用 jQuery API(通常在使用 rows().nodes()column().nodes()cells().nodes() 时)。

var data = table.rows().data();
JSON.stringify( data );

unique()

最后,在这次讨论中还有 unique() 方法。它可以非常简单地获取一个结果集,并移除其中的任何重复值

table
    .rows()
    .data()
    .pluck( 'lastName' )
    .unique();

值得强调的是,DataTables API 并没有尝试复制帮助程序库的全部功能,比如 UnderscoreLodash,用于数组操作。这里提供的实用方法是在使用数据表时最有用的一些方法。如果你确实想要使用这些库的更复杂方法,toArray() 可用于将数据转换成一个简单数组。

结论

没有通用的 DataTables 迭代器,您必须始终使用它。相反,您会发现需要使用各种可用的方法,根据您正在处理的数据以及您想要执行的操作来选择使用哪种方法。希望本文会帮助阐明何时使用每种方法。

这是一个非常技术性和基于理论的帖子 - 我将在六月回到实践中!