對JavaScript對象數組按指定屬性和排序方向進行排序

標籤:JavaScript 對象數組 排序javascript

引言

在以數據爲中心的信息系統中,以表格形式展現數據是在常見不過的方式了。對數據進行排序是必不可少的功能。排序能夠分爲按單個字段排序和按多個字段不一樣排序方向排序。單字段排序侷限性較大,不能知足用戶對數據的關注點變化的需求,而多字段排序就能夠較好的彌補這個缺陷。前端

多字段排序,實現的方式從大的層面上能夠分爲後端實現和前端實現。java

後端排序

後端實現排序能夠在數據庫層面實現或者在應用程序層面實現。算法

數據庫層面實現多字段排序很是簡單,使用SQL的排序指令「Order By」便可——Order By field1 asc, field2 desc, field3 asc -- ...數據庫

應用程序層面是指Web應用層(這裏不討論C/S架構),好比PHP、Java Web、ASP.NET等。應用程序層面實現就是使用PHP、Java、.NET(C#/VB)這些後端服務語言來實現對數據的排序。以ASP.NET C# 爲例,由於C#中的LINQ內置了對集合類型的諸多操做,而且支持多屬性排序,因此使用LINQ可以很方便的實現此目的——from f in foos orderby f.Name descending, f.Num ascending select f(能夠發現LINQ的排序語法幾乎與SQL的如出一轍)。若是其它語言沒有內置相似的支持,則按照排序算法來實現,這是通用的,與編程語言無關。編程

前端排序

在JavaScript中,數組有一個排序方法「sort」,當數組是一個簡單數組(數組元素是簡單類型——字符串、數值和布爾)時,使用該方法能夠很方便的到達排序目的。可是當數組元素是非簡單類型,好比名/值對的Object,而且想要按照指定的某幾個屬性按不一樣的排序方向進行排序時,簡單的調用「sort」方法就不能實現此目的了。後端

不過好在「sort」方法預留了自定義排序的接口,能夠實現想要的排序方式。數組

來看看數組的「sort」方法是怎樣的。瀏覽器

sort函數原型

// 對數組的元素作原地的排序,並返回這個數組。
// 默認按照字符串的Unicode碼位點(code point)排序。
Array.prototype.sort([compareFunction]:number); // number:-1 | 0 | 1。

// 典型的比較函數(升序排序)。
function compareFunction(item1, item2) {
    if(item1 > item2) {
        return 1; // 若是是降序排序,返回-1。
    }else if(item1 === item2) {
        return 0;
    }else {
        return -1; // 若是是降序排序,返回1。
    }
}

說明:若是沒有指明compareFunction,那麼元素會被轉換爲字符串的諸個字符並按照Unicode位點順序排序。例如,"Cherry"會被排列到"banana"以前。當對數字進行排序的時候, 9 會出如今 80 以前,由於他們會先被轉換爲字符串,而 "80" 比 "9" 要靠前。服務器

  • 若是 compareFunction(a, b) 小於 0 ,那麼 a 會被排列到 b 以前;

  • 若是 compareFunction(a, b) 等於 0 ,a 和 b
    的相對位置不變。備註:ECMAScript標準並不保證這一行爲,並且也不是全部瀏覽器都會遵照(例如 Mozilla 在 2003

年以前的版本);

  • 若是 compareFunction(a, b) 大於 0 , b 會被排列到 a 以前。

  • compareFunction(a, b) 必須老是對相同的輸入返回相同的比較結果,不然排序的結果將是不肯定的。

注:以上規則得出的排序結果是升序的,若是想要獲得降序的結果,則在比較結果大於 0 時返回小於 0 的結果,比較結果小於 0 時 返回大於 0 的結果便可。

要實現多屬性排序,關鍵就在於比較函數的實現。根據以上規則, 實現多屬性不一樣方向排序,依然要返回兩個比較項的大小關係。那麼多屬性對象的大小關係如何肯定呢?這個能夠分兩步走。

第一步,記錄下兩個排序項按照各個排序屬性及方向進行比較獲得的結果。

var propOrders = { "prop1":"asc", "prop2":"desc", "prop3":"asc"};

function cmp(item1, item2, propOrders) {
    var cps = []; // 用於記錄各個排序屬性的比較結果,-1 | 0 | 1 。
    var isAsc = true; // 排序方向。     
    for(var p in propOrders) {
        isAsc = propOrders[p] === "asc";
        if(item1[p] > item2[p]) {
            cps.push(isAsc ? 1 : -1);
            break; // 能夠跳出循環了,由於這裏就已經知道 item1 「大於」 item2 了。
        } else if(item1[p] === item2[p]) {
            cps.push(0);
        } else {
            cps.push(isAsc ? -1 : 1);
            break; // 能夠跳出循環,item1 「小於」 item2。
        }        
    }     
    
    /*
     .
     .
     .
    */
}

第二步,根據各排序屬性比較結果綜合判斷得出兩個比較項的最終大小關係。

/* 
     .
     .
     . 
    */
    
    for(var j = 0; j < cps.length; j++) {
        if(cps[j] === 1 || cps[j] === -1) {
            return cps[j];
        }
    }
    return 0;

有了上述思路後,實現整個比較函數就容易了,下面是比較函數的完整JavaScript代碼:

比較函數

function SortByProps(item1, item2) {
    "use strict";
    var props = [];
    for (var _i = 2; _i < arguments.length; _i++) {
        props[_i - 2] = arguments[_i];
    }
        
    var cps = []; // 存儲排序屬性比較結果。
    // 若是未指定排序屬性,則按照全屬性升序排序。    
    var asc = true;
    if (props.length < 1) {
        for (var p in item1) {
            if (item1[p] > item2[p]) {
                cps.push(1);
                break; // 大於時跳出循環。
            } else if (item1[p] === item2[p]) {
                cps.push(0);
            } else {
                cps.push(-1);
                break; // 小於時跳出循環。
            }
        }
    } else {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            for (var o in prop) {
                asc = prop[o] === "asc";
                if (item1[o] > item2[o]) {
                    cps.push(asc ? 1 : -1);
                    break; // 大於時跳出循環。
                } else if (item1[o] === item2[o]) {
                    cps.push(0);
                } else {
                    cps.push(asc ? -1 : 1);
                    break; // 小於時跳出循環。
                }
            }
        }
    }        
         
    for (var j = 0; j < cps.length; j++) {
        if (cps[j] === 1 || cps[j] === -1) {
            return cps[j];
        }
    }
    return 0;          
}

測試用例

// -------------測試用例------------------------------
    
    var items = [   { name: 'Edward', value: 21 },
                    { name: 'Sharpe', value: 37 },
                    { name: 'And', value: 45 },
                    { name: 'Edward', value: -12 },
                    { name: 'Magnetic', value: 21 },
                    { name: 'Zeros', value: 37 }
                ];
                
    function test(propOrders) {
        items.sort(function (a, b) {
            return SortByProps(a, b, propOrders);
        });
        console.log(items);
    }
    
    function testAsc() {
        test({ "name": "asc", "value": "asc" });
    }
    
    function testDesc() {
        test({ "name": "desc", "value": "desc" });
    }
    
    function testAscDesc() {
        test({ "name": "asc", "value": "desc" });
    }
    
    function testDescAsc() {
        test({ "name": "desc", "value": "asc" });
    }

實測效果

http://jsfiddle.net/Stronger/nktL5cwa/10

TypeScript代碼

/**
** 排序方向。
*/
type Direct = "asc" | "desc";

/**
** 排序屬性。
** 
** @interface IPropertyOrder
*/
interface IPropertyOrder {            
    [name: string] : Direct;
}

/**
** 簡單名/值對象。
** 
** @interface ISimpleObject
*/
interface ISimpleObject {
    [name: string] : string | number | boolean;
}

/**
** 對簡單的名/值對象按照指定屬性和排序方向進行排序(根據排序屬性及排序方向,
** 對兩個項依次進行比較,並返回表明排序位置的值)。
** 
** @template T 簡單的名/值對象。
** @param {T} item1 排序比較項1。
** @param {T} item2 排序比較項2。
** @param {...IPropertyOrder[]} props 排序屬性。
** @returns 若項1大於項2返回1,若項1等於項2返回0,不然返回-1。
*/
function SortByProps<T extends ISimpleObject>
(item1: T, item2: T, ...props: IPropertyOrder[]) {
    "use strict";
    var cps: Array<number> = []; // 存儲排序屬性比較結果。
    // 若是未指定排序屬性,則按照全屬性升序排序。    
    var asc = true;
    if (props.length < 1) {
        for (var p in item1) {
            if (item1[p] > item2[p]) {
                cps.push(1);
                break; // 大於時跳出循環。
            } else if (item1[p] === item2[p]) {
                cps.push(0);
            } else {
                cps.push(-1);
                break; // 小於時跳出循環。
            }
        }
    } else { // 按照指定屬性及升降方向進行排序。
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            for (var o in prop) {
                asc = prop[o] === "asc";
                if (item1[o] > item2[o]) {
                    cps.push(asc ? 1 : -1);
                    break; // 大於時跳出循環。
                } else if (item1[o] === item2[o]) {
                    cps.push(0);
                } else {
                    cps.push(asc ? -1 : 1);
                    break; // 小於時跳出循環。
                }
            }
        }
    }

    for (var j = 0; j < cps.length; j++) {
        if (cps[j] === 1 || cps[j] === -1) {
            return cps[j];
        }
    }
    return 0;    
}

使用場景及侷限性

在前端使用JavaScript實現多屬性排序,減小了對服務器端的請求,減輕服務器端的計算壓力,可是也僅適用於只須要對本地數據進行排序的情形。若是須要對整個數據集進行多屬性排序,最終仍是要在服務器端的數據庫層面上進行。

若是你有更好的實現方式,歡迎留言交流。

本文章版權歸做者本人全部,轉載請註明出處。: )

參考資料

相關文章
相關標籤/搜索