在以数据为中心的信息系统中,以表格形式展现数据是在常见不过的方式了。对数据进行排序是必不可少的功能。排序能够分为按单个字段排序和按多个字段不一样排序方向排序。单字段排序局限性较大,不能知足用户对数据的关注点变化的需求,而多字段排序就能够较好的弥补这个缺陷。javascript
多字段排序,实现的方式从大的层面上能够分为后端实现和前端实现。php
后端实现排序能够在数据库层面实现或者在应用程序层面实现。html
数据库层面实现多字段排序很是简单,使用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的如出一辙)。若是其它语言没有内置相似的支持,则按照排序算法来实现,这是通用的,与编程语言无关。java
在JavaScript中,数组有一个排序方法“sort”,当数组是一个简单数组(数组元素是简单类型——字符串、数值和布尔)时,使用该方法能够很方便的到达排序目的。可是当数组元素是非简单类型,好比名/值对的Object,而且想要按照指定的某几个属性按不一样的排序方向进行排序时,简单的调用“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" }); }
/** ** 排序方向。 */ 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实现多属性排序,减小了对服务器端的请求,减轻服务器端的计算压力,可是也仅适用于只须要对本地数据进行排序的情形。若是须要对整个数据集进行多属性排序,最终仍是要在服务器端的数据库层面上进行。