2017 年 5 月 31 日星期三

DataTables 中的迭代器

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()),而不是像 each() 和其他公用程式方法一樣簡單地描述為 every()

每個 *.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() 文件,您會注意到強烈建議您不要使用它!設定物件中的資訊被認為是非公開的 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 迭代器,您應該堅持使用。相反地,您會發現您需要利用各種可用的方法,根據您正在處理的資料和您想要執行的操作來選擇要使用的方法。希望本文能幫助您了解何時使用每種方法。

這個月這篇文章相當技術性且以理論為基礎,我將在六月回歸實際的內容!