资讯专栏INFORMATION COLUMN

对JavaScript对象数组按指定属性和排序方向进行排序

z2xy / 1079人阅读

摘要:对数据进行排序是必不可少的功能。对简单的名值对象按照指定属性和排序方向进行排序根据排序属性及排序方向,对两个项依次进行比较,并返回代表排序位置的值。按照指定属性及升降方向进行排序。

标签:JavaScript 对象数组 排序

引言

在以数据为中心的信息系统中,以表格形式展示数据是在常见不过的方式了。对数据进行排序是必不可少的功能。排序可以分为按单个字段排序和按多个字段不同排序方向排序。单字段排序局限性较大,不能满足用户对数据的关注点变化的需求,而多字段排序就可以较好的弥补这个缺陷。

多字段排序,实现的方式从大的层面上可以分为后端实现和前端实现。

后端排序

后端实现排序可以在数据库层面实现或者在应用程序层面实现。

数据库层面实现多字段排序非常简单,使用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
(item1: T, item2: T, ...props: IPropertyOrder[]) {
    "use strict";
    var cps: Array = []; // 存储排序属性比较结果。
    // 如果未指定排序属性,则按照全属性升序排序。    
    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实现多属性排序,减少了对服务器端的请求,减轻服务器端的计算压力,但是也仅适用于只需要对本地数据进行排序的情形。如果需要对整个数据集进行多属性排序,最终还是要在服务器端的数据库层面上进行。

如果你有更好的实现方式,欢迎留言交流。

本文章版权归作者本人所有,转载请注明出处。: )

参考资料

JavaScript MDN - Array.prototype.sort : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79669.html

相关文章

  • JavaScript 处理数组函数总结

    摘要:从而将传入的数组误判为非数组。返回值把指定的值添加到数组后的新长度。方法用于删除并返回数组的最后一个元素返回值的最后一个元素。如果数组已经为空,则不改变数组,并返回值。 JavaScript的array可以包含任意数据类型,并通过索引来访问每个元素。 1、检测数组:instanceof、slice()、Array.isArray() 检测一个对象是不是数组的三种方法:(1)方法一:i...

    instein 评论0 收藏0
  • 前端实用资源整理

    摘要:事件的响应分区为三个阶段捕获目标冒泡阶段。绑定的多个事件会被覆盖,后者覆盖前者。再用转换成数值表示。如实际数量为,则展示为项目中使用过滤器做的处理可以抽取方法的,调整相关,可以获取指定位数的缩写。 CSS html5中a的download属性 定义和用法download 属性定义下载链接的地址或指定下载文件的名称。文件名称没有限定值,浏览器会自动在文件名称末尾添加该下载文件的后缀 (...

    Gu_Yan 评论0 收藏0
  • 前端实用资源整理

    摘要:事件的响应分区为三个阶段捕获目标冒泡阶段。绑定的多个事件会被覆盖,后者覆盖前者。再用转换成数值表示。如实际数量为,则展示为项目中使用过滤器做的处理可以抽取方法的,调整相关,可以获取指定位数的缩写。 CSS html5中a的download属性 定义和用法download 属性定义下载链接的地址或指定下载文件的名称。文件名称没有限定值,浏览器会自动在文件名称末尾添加该下载文件的后缀 (...

    wslongchen 评论0 收藏0
  • JavaScript 实现数组更多的高阶函数

    摘要:实现数组更多的高阶函数吾辈的博客原文场景虽说人人平等,但有些人更加平等。若是有一篇适合萌新阅读的自己实现数组更多操作的文章,情况或许会发生一些变化。类似于的初始值,但它是一个函数,避免初始值在所有分组中进行累加。 JavaScript 实现数组更多的高阶函数 吾辈的博客原文: https://blog.rxliuli.com/p/fc... 场景 虽说人人平等,但有些人更加平等。 为...

    aervon 评论0 收藏0
  • MongoDB指南---11、使用复合索引、$操作符如何使用索引、索引数组、索引基数

    摘要:操作符如何使用索引有一些查询完全无法使用索引,也有一些查询能够比其他查询更高效地使用索引。有时能够使用索引,但是通常它并不知道要如何使用索引。索引对象和数组允许深入文档内部,对嵌套字段和数组建立索引。 上一篇文章:MongoDB指南---10、索引、复合索引 简介下一篇文章:MongoDB指南---12、使用explain()和hint()、何时不应该使用索引 1、使用复合索引 在多...

    saucxs 评论0 收藏0

发表评论

0条评论

z2xy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<