# 对象数组的排序
很多时候想要对一个对象数组进行排序,当然使用lodash的sortBy
或者orderBy
是更好的选择。不过想要再其中定制一些自己的东西就比较麻烦, 我们自己尝试实现一个。
最基本的工具还是使用 Array.prototype.sort()
,针对于各种中英文字符串,直接采用大小写对比(会默认使用UTF-16)的码点值来比较。获取码点值:codePointAt
。
# 1、比较两个字符串的先后
# 1.1 String.prototype.localeCompare()
localeCompare()
方法返回一个数字,表示参考字符串在排序顺序中是在给定字符串之前、之后还是与之相同
返回一个数字表示 referenceStr
在排序中是否位于 compareString
的前面、后面或二者相同。
- 当
referenceStr
在compareString
前面时返回负数 - 当
referenceStr
在compareString
后面时返回正数 - 当两者相等时返回
0
警告: 切勿依赖于 `-1` 或 `1` 这样特定的返回值。
不同浏览器之间(以及不同浏览器版本之间)返回的正负数的值各有不同,因为 W3C 规范中只要求返回值是正值和负值,而没有规定具体的值。一些浏览器可能返回 -2
或 2
或一些其他的负、正值。
当然还有一些语言相关的和参数就不展开了,就简单介绍下 详细请查看 (opens new window)
下面是一些例子
'a'.localeCompare('b') // -1
'ac'.localeCompare('abc') // 1
他是基于 new Intl.Collator('zh').compare
,当然语言可以自己选.和码点没有绝对的关系。
const ni = '你';
const hao = '好';
console.log(ni.codePointAt(0), hao.codePointAt(0)); //你 = 20320, 好 = 22909
ni > hao; // false
const _compare = new Intl.Collator('zh').compare;
console.log(_compare(ni, hao)); // 1
# 1.2 利用字符串依次码点值来比较
const compareCodePoint = function (a, b) {
const len = Math.max(a.length, b.length);
if (len === 0) {
return 0;
}
for (let i = 0; i < len; i++) {
const codePointA = a[i] ? a[i].codePointAt(0) || 0 : 0;
const codePointB = b[i] ? b[i].codePointAt(0) || 0 : 0;
if (codePointA < codePointB) {
return -1;
}
if (codePointA > codePointB) {
return 1;
}
}
return 0;
};
console.log(compareCodePoint('a', 'b')); // -1
console.log(compareCodePoint('b', 'a')); // 1
console.log(compareCodePoint('ab', 'ac')); // -1
console.log(compareCodePoint('ab', 'ab1')); // -1
console.log(compareCodePoint('你', '好')); // -1
console.log(compareCodePoint('好', '呀')); // 1
compareCodePoint
引用到Array.prototype.sort()
函数中是升序的效果
const list2 = ['1', 'b1', 'c', '3', 'b', '2', 'a'];
console.log(list2.sort(compareCodePoint)); // ['1', '2', '3', 'a', 'b', 'b1', 'c']
如果想要降序,给个降序标记即可,比如乘以 -1
const list2 = ['1', 'b1', 'c', '3', 'b', '2', 'a'];
console.log(list2.sort((a, b) => compareCodePoint(a, b) * -1)); // ['c', 'b1', 'b', 'a', '3', '2', '1']
不过这个 compareCodePoint
和直接使用大小于是一样的效果, 比如
console.log(list2.sort()); // ['1', '2', '3', 'a', 'b', 'b1', 'c']
// 直接使用大小于也是根据码点值来比较的。
'1' < '2'; // true
# 2、一个简单的sortBy函数
如果是数组对象的话, 数组的引用可以变, 但是内部的对象引用还是不能变
function sortBy(collection, iteratees) {
if (!collection || collection.length <= 1) {
return collection;
}
if (Object.prototype.toString.call(iteratees) !== '[object Array]') {
iteratees = [iteratees];
}
const _getKey = function (item) {
const key = [];
for (let i = 0, len = iteratees.length; i < len; i++) {
const iter = iteratees[i];
if (typeof iter === 'function') {
key.push(iter(item));
}
if (typeof iter === 'string') {
key.push(item[iter]);
}
}
return key;
};
const _compare = function (a, b) {
for (let i = 0, len = a.key.length; i < len; i++) {
if (a.key[i] < b.key[i]) {
return -1;
}
if (a.key[i] > b.key[i]) {
return 1;
}
}
return 0;
};
return collection
.map((value, index) => {
return {
value: value,
index: index,
key: _getKey(value)
};
})
.sort(_compare)
.map(_ => _.value);
}
用法
const users2 = sortBy(users, ['user', 'age']);
const users3 = sortBy(users, o => o.age);
# 3、做的更加复杂一点
简单支持一下升降序和自定义compare函数,同时支持一下比较值的深度读取
const getValueByPath = function (object, prop) {
prop = prop || '';
const paths = prop.split('.');
let current = object;
let result = null;
for (let i = 0, j = paths.length; i < j; i++) {
const path = paths[i];
if (!current) break;
if (i === j - 1) {
result = current[path];
break;
}
current = current[path];
}
return result;
};
const isObject = function (obj) {
return obj !== null && typeof obj === 'object';
};
/**
*
*
* @export
* @param {*} collection
* @param {*} sortKey 单独的一个key
* @param {*} reverse 'ascending' | 'descending'
* @param {*} compareMethod 自定义比较函数
* @param {*} sortBy 可以是 [key1.key2]的形式
* @return {*}
*/
function orderBy(collection, sortKey, reverse, compareMethod, sortBy) {
if (!sortKey && !compareMethod && (!sortBy || (Array.isArray(sortBy) && !sortBy.length))) {
return collection;
}
if (typeof reverse === 'string') {
reverse = reverse === 'descending' ? -1 : 1;
} else {
reverse = reverse && reverse < 0 ? -1 : 1;
}
const _getKey = function (value, index) {
if (sortBy) {
if (!Array.isArray(sortBy)) {
sortBy = [sortBy];
}
return sortBy.map(function (by) {
if (typeof by === 'string') {
return getValueByPath(value, by);
} else {
return by(value, index, collection);
}
});
}
if (sortKey !== '$key') {
if (isObject(value) && '$value' in value) value = value.$value;
}
return [isObject(value) ? getValueByPath(value, sortKey) : value];
};
const getKey = compareMethod ? null : _getKey;
const compare = function (a, b) {
if (compareMethod) {
return compareMethod(a.value, b.value);
}
for (let i = 0, len = a.key.length; i < len; i++) {
if (a.key[i] < b.key[i]) {
return -1;
}
if (a.key[i] > b.key[i]) {
return 1;
}
}
return 0;
};
return collection
.map(function (value, index) {
return {
value: value,
index: index,
key: getKey ? getKey(value, index) : null
};
})
.sort(function (a, b) {
let order = compare(a, b);
if (!order) {
// make stable https://en.wikipedia.org/wiki/Sorting_algorithm#Stability 稳定排序
order = a.index - b.index;
}
return order * reverse;
})
.map(item => item.value);
}
用法
const users = [
{ user: 'fred', age: 48, wallet: { count: 23 } },
{ user: 'barney', age: 36, wallet: { count: 22 } },
{ user: 'fred', age: 40, wallet: { count: 24 } },
{ user: 'geemi', age: 23, wallet: {count: 28} },
{ user: 'barney', age: 34, wallet: {count: 25 } }
];
orderBy(users, 'user');
orderBy(users, '', 1, null, 'user');
orderBy(users, '', 1, null, 'wallet.count');
orderBy(users, '', 1, (a, b) => {
if (a.age < b.age) {
return -1;
}
if (a.age > b.age) {
return 1;
}
return 0;
});