【underscore 源码解读】如何优雅地写一个『在数组中寻找指定元素』的方法

Why underscore

(觉得这部分眼熟的可以直接跳到下一段了...)

最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。

阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多。为什么是 underscore?最主要的原因是 underscore 简短精悍(约 1.5k 行),封装了 100 多个有用的方法,耦合度低,非常适合逐个方法阅读,适合楼主这样的 JavaScript 初学者。从中,你不仅可以学到用 void 0 代替 undefined 避免 undefined 被重写等一些小技巧 ,也可以学到变量类型判断、函数节流&函数去抖等常用的方法,还可以学到很多浏览器兼容的 hack,更可以学到作者的整体设计思路以及 API 设计的原理(向后兼容)。

之后楼主会写一系列的文章跟大家分享在源码阅读中学习到的知识。

  1. underscore-1.8.3 源码解读项目地址 https://github.com/hanzichi/underscore-analysis

  2. underscore-1.8.3 源码全文注释 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js

  3. underscore-1.8.3 源码解读系列文章 https://github.com/hanzichi/underscore-analysis/issues

欢迎围观~ (如果有兴趣,欢迎 star & watch~)您的关注是楼主继续写作的动力

题外话

先说点题外话。

自从 5 月 16 日开始 underscore 系列解读文章,目前已经收获了 160+ star,在这里子迟也感谢大家的支持,并将继续努力分享源码里的干货。有朋友私信我说好几天没看到更新,在此也请大家原谅,毕竟我把它当成了今年的计划之一,而且平时也要上班工作,只能利用闲暇时间,而且楼主本人对文章的质量要求比较高,如果是一律的流水文章,读者学不到什么东西,自己的那关都过不了。其实如果有心,应该能发现 underscore-1.8.3 源码全文注释 一直有在更新(注释行数已经快破 1000 了)。

Main

言归正传,上一章 中我们结束了 Object 扩展方法部分,今天开始来解读 Array 部分的扩展方法。其实 JavaScript 中的数组是我最喜欢的类型,能模拟栈、队列等数据结构,还能随意插入元素(splice),非常的灵活,这点做过 leetcode 的应该都深有体会(这里也顺便安利下我的 leetcode 题解 Repo https://github.com/hanzichi/leetcode)。

今天要讲的是,如何在数组中寻找元素,对应 underscore 中的 .findIndex,.findLastIndex,.indexOf,.lastIndexOf 以及 _.sortIndex 方法。

等等,是不是有点眼熟,没错,JavaScript 中已经部署了 indexOf 方法(ES5)以及 findIndex 方法(ES6),这点不介绍了,大家可以自行学习。

我们先来看 .findIndex 和 .findLastIndex 函数。如果了解过 Array.prototype.findIndex() 方法,会非常容易。.findIndex 的作用就是从一个数组中找到第一个满足某个条件的元素,.findLastIndex 则是找到最后一个(或者说倒序查找)。

举个简单的例子:

var arr = [1, 3, 5, 2, 4, 6];

var isEven = function(num) {
return !(num & 1);
};

var idx = _.findIndex(arr, isEven);
// => 3
直接看源码,注释已经写的非常清楚了。这里要注意这个 predicate 函数,其实就是把数组中的元素传入这个参数,返回一个布尔值。如果返回 true,则表示满足这个条件,如果 false 则相反。

// Generator function to create the findIndex and findLastIndex functions
// dir === 1 => 从前往后找
// dir === -1 => 从后往前找
function createPredicateIndexFinder(dir) {
// 经典闭包
return function(array, predicate, context) {
predicate = cb(predicate, context);

var length = getLength(array);// 根据 dir 变量来确定数组遍历的起始位置var index = dir > 0 ? 0 : length - 1;for (; index >= 0 && index 

接下来看 _.sortIndex 方法,这个方法无论使用还是实现都非常的简单。如果往一个有序数组中插入元素,使得数组继续保持有序,那么这个插入位置是?这就是这个方法的作用,有序,很显然用二分查找即可。不多说,直接上源码。

// .sortedIndex(list, value, [iteratee], [context])
.sortedIndex = function(array, obj, iteratee, context) {
// 注意 cb 方法
// iteratee 为空 || 为 String 类型(key 值)时会返回不同方法
iteratee = cb(iteratee, context, 1);

// 经过迭代函数计算的值
var value = iteratee(obj);

var low = 0, high = getLength(array);

while (low
最后我们说说 .indexOf 和 .lastIndexOf 方法。

ES5 引入了 indexOf 和 lastIndexOf 方法,但是 IE // Generator function to create the indexOf and lastIndexOf functions
// .indexOf = createIndexFinder(1, .findIndex, .sortedIndex);
//
.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
function createIndexFinder(dir, predicateFind, sortedIndex) {

// API 调用形式// _.indexOf(array, value, [isSorted]) // _.indexOf(array, value, [fromIndex]) // _.lastIndexOf(array, value, [fromIndex]) return function(array, item, idx) {  var i = 0, length = getLength(array);  // 如果 idx 为 Number 类型  // 则规定查找位置的起始点  // 那么第三个参数不是 [isSorted]  // 所以不能用二分查找优化了  // 只能遍历查找  if (typeof idx == 'number') {    if (dir > 0) { // 正向查找      // 重置查找的起始位置      i = idx >= 0 ? idx : Math.max(idx + length, i);    } else { // 反向查找      // 如果是反向查找,重置 length 属性值      length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;    }  } else if (sortedIndex && idx && length) {    // 能用二分查找加速的条件    // 有序 & idx !== 0 && length !== 0    // 用 _.sortIndex 找到有序数组中 item 正好插入的位置    idx = sortedIndex(array, item);    // 如果正好插入的位置的值和 item 刚好相等    // 说明该位置就是 item 第一次出现的位置    // 返回下标    // 否则即是没找到,返回 -1    return array[idx] === item ? idx : -1;  }  // 特判,如果要查找的元素是 NaN 类型  // 如果 item !== item  // 那么 item => NaN  if (item !== item) {    idx = predicateFind(slice.call(array, i, length), _.isNaN);    return idx >= 0 ? idx + i : -1;  }  // O(n) 遍历数组  // 寻找和 item 相同的元素  // 特判排除了 item 为 NaN 的情况  // 可以放心地用 `===` 来判断是否相等了  for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx 

这里有一点要注意,.indexOf 方法的第三个参数可以表示 [fromIndex] 或者 [isSorted],而 .lastIndexOf 的第三个参数只能表示 [fromIndex],我们从代码中便可以轻易看出:

.indexOf = createIndexFinder(1, .findIndex, .sortedIndex);
.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
关于这点我也百思不得其解,不知道做这个限制是为了什么考虑,欢迎探讨~

最后给出本文涉及的五个方法的源码位置 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js# L613-L673

关键字:JavaScript, idx, array, item

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部