前言
学习的underscore.js 源码版本为1.13.1。
这一节学习如何在数组中查找指定元素,涉及的方法包括findIndex、findLastIndex、sortedIndex、indexOf、lastIndexOf
ES6 findIndex
ES6中数组新增了findIndex方法,会返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1;
先来看下使用:
const arr = [5, 12, 8, 130, 44];
const isLargeNumber = (element) => element > 13;
console.log(arr.findIndex(isLargeNumber)); // 3
findIndex 会找出第一个大于 13 的元素的下标,所以最后返回 3
实现findIndex
function findIndex (array, predicate, context) {
var length = array.length;
for (var index = 0; index < length; index++) {
if (predicate.call(context, array[index], index, array)) return index;
}
return -1;
}
const arr = [5, 12, 8, 130, 44];
findIndex(arr, function (item, index, array) {
return item > 13;
});
// 3
实现findLastIndex
findIndex是正序查找, 同样我们也可以实现一个倒序查找的findLastIndex
function findLastIndex (array, predicate, context) {
var length = array.length;
for (var index = length - 1; index >= 0; index--) {
if (predicate.call(context, array[index], index, array)) return index;
}
return -1;
}
const arr = [5, 12, 8, 130, 44];
findLastIndex(arr, function (item, index, array) {
return item > 13;
});
// 4
createPredicateIndexFinder
从上面代码可以看出findIndex、findLastIndex中除了for循环内语句不一样外,其余都是一样的处理逻辑,underscore.js中利用传参的不同,返回不同的函数,此时高阶函数上场.
/**
* 利用传参的不同,返回不同的函数
* @param {number} dir 实现正序和倒序遍历的关键因子
*/
function createPredicateIndexFinder (dir) {
return function (array, predicate, context) {
var length = arr.length;
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (predicate.call(context, array[index], index, array)) return index;
}
return -1;
};
}
var findIndex = createPredicateIndexFinder(1);
var findLastIndex = createPredicateIndexFinder(-1);
sortedIndex
使用二分查找确定value在list中的位置序号,value按此序号插入能保持list原有的排序。
使用:
_.sortedIndex([10, 20, 30, 40, 50], 35); // 3
list既然是有序的,那我们可以使用二分法查找插入位置。
第一版实现
function sortedIndex (array, obj) {
var low = 0, high = array.length;
while (low < high) {
var mid = Math.floor((low + high) / 2);
if (array[mid] < obj) low = mid + 1;
else high = mid;
}
return low;
}
sortedIndex([10, 20, 30, 40, 50], 35); // 3
但这样局限性大,我们还希望实现下面效果:
var stooges = [{name: 'moe', age: 40}, {name: 'curly', age: 60}];
_.sortedIndex(stooges, {name: 'larry', age: 50}, 'age'); // 1
所以我们需要加上一个参数iteratee函数对数组的每一个元素进行处理,此外还需要考虑this指向问题;
在上一篇我们学习了内部函数cb,用来处理不同类型的iteratee
function sortedIndex(array, obj, iteratee, context) {
iteratee = cb(iteratee, context, 1);
var value = iteratee(obj);
var low = 0, high = array.length;
while (low < high) {
var mid = Math.floor((low + high) / 2);
if (iteratee(array[mid]) < value) low = mid + 1;
else high = mid;
}
return low;
}
indexOf、lastIndexOf
根据以上findIndex 和 FindLastIndex 的实现方式,我们很容易写出第一版indexOf、lastIndexOf
第一版
function createIndexFinder(dir) {
return function(array, item, idx) {
var i = 0, length = array.length;
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
var indexOf = createIndexFinder(1);
var lastIndexOf = createIndexFinder(-1);
indexOf([1, 2, 3], 2); // 1
lastIndexOf([1, 2, 3, 1, 2, 3], 2); // 4
第二版 支持 fromIndex
indexOf中fromIndex
MDN中介绍fromIndex:
开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。 注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。如果抵消后的索引值仍小于0,则整个数组都将会被查询。其默认值为0.
lastIndexOf中fromIndex
MDN中介绍fromIndex:
从此位置开始逆向查找。默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移。即使该值为负,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
第二版实现:
function createIndexFinder(dir) {
return function(array, item, idx) {
var i = 0, length = array.length;
if (typeof idx == 'number') {
// indexOf就处理开始查找的位置
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else {
// lastIndexOf就处理遍历数组的length
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
var indexOf = createIndexFinder(1);
var lastIndexOf = createIndexFinder(-1);
indexOf([1, 2, 3, 2], 2, 2); // 3
lastIndexOf([1, 2, 3, 1, 2, 3], 2, 2); // 1
第三版优化 考虑NaN
实现的indexOf中是使用 === 来进行判断的,我们知道NaN !== NaN, 那么就可以利用一开始写的 findIndex 从数组中找到符合条件的值的下标
var isNumber = function (num) {
return Object.prototype.toString.call(num) === '[object Number]'
}
function isNaN$1(obj) {
return isNumber(obj) && isNaN(obj);
}
function createIndexFinder(dir, predicateFind) {
return function(array, item, idx) {
var i = 0, length = array.length;
if (typeof idx == 'number') {
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
}
// NaN !== NaN
if (item !== item) {
// 在截取好的数组中查找第一个满足isNaN$1函数的元素的下标
idx = predicateFind(array.slice.call(array, i, length), isNaN$1);
return idx >= 0 ? idx + i : -1;
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
var indexOf = createIndexFinder(1, findIndex);
var lastIndexOf = createIndexFinder(-1, findLastIndex);
indexOf([1, NaN, 3, NaN], NaN, 2); // 3
lastIndexOf([1, NaN, 3, 1, NaN, 3], NaN, 2); // 1
第四版优化 indexOf支持二分查找
支持对有序的数组进行更快的二分查找
如果 indexOf 第三个参数不传开始搜索的下标值,而是一个布尔值 true,就认为数组是一个排好序的数组,这时候,就会采用更快的二分法进行查找,这个时候,可以利用我们写的 sortedIndex 函数
最终版:
function createIndexFinder(dir, predicateFind, sortedIndex) {
return function(array, item, idx) {
var i = 0, length = array.length;
// 处理查找位置
if (typeof idx == 'number') {
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
} else if (sortedIndex && idx && length) { // 采用更快的二分法进行查找
idx = sortedIndex(array, item);
return array[idx] === item ? idx : -1;
}
// 处理NaN
if (item !== item) {
// 在截取好的数组中查找第一个满足isNaN$1函数的元素的下标
idx = predicateFind(array.slice.call(array, i, length), isNaN$1);
return idx >= 0 ? idx + i : -1;
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
// 只有 indexOf 是支持有序数组使用二分查找,lastIndexOf 并不支持
var indexOf = createIndexFinder(1, findIndex, sortedIndex);
var lastIndexOf = createIndexFinder(-1, findLastIndex);
参考资料:
github.com/mqyqingfeng…
developer.mozilla.org/zh-CN/docs/…