【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法
Why underscore
(觉得这部分眼熟的可以直接跳到下一段了...)
最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。
阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多。为什么是 underscore?最主要的原因是 underscore 简短精悍(约 1.5k 行),封装了 100 多个有用的方法,耦合度低,非常适合逐个方法阅读,适合楼主这样的 JavaScript 初学者。从中,你不仅可以学到用 void 0 代替 undefined 避免 undefined 被重写等一些小技巧 ,也可以学到变量类型判断、函数节流&函数去抖等常用的方法,还可以学到很多浏览器兼容的 hack,更可以学到作者的整体设计思路以及 API 设计的原理(向后兼容)。
之后楼主会写一系列的文章跟大家分享在源码阅读中学习到的知识。
- underscore-1.8.3 源码解读项目地址 https://github.com/hanzichi/underscore-analysis
- underscore-1.8.3 源码全文注释 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js
- 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 < length; index += dir) {
// 找到第一个符合条件的元素
// 并返回下标值
if (predicate(array[index], index, array)) return index;
}
return -1;
};
}
// Returns the first index on an array-like that passes a predicate test
// 从前往后找到数组中 `第一个满足条件` 的元素,并返回下标值
// 没找到返回 -1
// _.findIndex(array, predicate, [context])
_.findIndex = createPredicateIndexFinder(1);
// 从后往前找到数组中 `第一个满足条件` 的元素,并返回下标值
// 没找到返回 -1
// _.findLastIndex(array, predicate, [context])
_.findLastIndex = createPredicateIndexFinder(-1);
接下来看 _.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 < high) {
var mid = Math.floor((low + high) / 2);
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
};
最后我们说说 _.indexOf 和 _.lastIndexOf 方法。
ES5 引入了 indexOf 和 lastIndexOf 方法,但是 IE < 9 不支持,面试时让你写个 Polyfill,你会怎么做(可以把 underscore 的实现看做 Polyfill)?如何能让面试官满意?首先如果分开来写,即两个方法相对独立地写,很显然代码量会比较多,因为两个方法功能相似,所以可以想办法调用一个方法,将不同的部分当做参数传入,减少代码量。其次,如果数组已经有序,是否可以用更快速的二分查找算法?这点会是加分项。
源码实现:
// 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 < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
// Return the position of the first occurrence of an item in an array,
// or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
// _.indexOf(array, value, [isSorted])
// 找到数组 array 中 value 第一次出现的位置
// 并返回其下标值
// 如果数组有序,则第三个参数可以传入 true
// 这样算法效率会更高(二分查找)
// [isSorted] 参数表示数组是否有序
// 同时第三个参数也可以表示 [fromIndex] (见下面的 _.lastIndexOf)
_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
// 和 _indexOf 相似
// 反序查找
// _.lastIndexOf(array, value, [fromIndex])
// [fromIndex] 参数表示从倒数第几个开始往前找
_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
这里有一点要注意,_.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
【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法的更多相关文章
- 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten
Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起
Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...
- 学underscore在数组中查找指定元素
前言 在开发中,我们经常会遇到在数组中查找指定元素的需求,可能大家觉得这个需求过于简单,然而如何优雅的去实现一个 findIndex 和 findLastIndex.indexOf 和 lastInd ...
- 如何优雅的写一个Vue 的弹框
写Vue或者是react 都会遇见弹框的问题.也尝试了多种办法来写弹框,一直都不太满意,今天特地看了一下 Element UI 的源码,模仿着写了一个简易版. 大概有一下几个问题: 1.弹框的层级问题 ...
随机推荐
- 如何使用github搭建个人博客
1.去github官网注册个人帐号:没有的:猛戳这里去注册,比如我的账户名:wjf444128852,我的已经汉化(可在github里搜索github如何汉化有插件) 2.点击仓库-新建,仓库名字必须 ...
- iframe高度自适应
前两天在网上看到了一道面试题,问iframe高度自适应的问题.发现自己之前几乎没有关注过iframe的问题,所以在这里记录一下. 原题目是: 页面A的域名是:http://www.taobao.com ...
- 原生JS实战:经典贪吃蛇(开局10倍速度,来看看你最高能得多少分!)
本文是苏福的原创文章,转载请注明出处:苏福CNblog:http://www.cnblogs.com/susufufu/p/5875523.html 该程序是本人的个人作品,写的不好,未经本人允许,请 ...
- How To Restart timer service on all servers in farm
[array]$servers= Get-SPServer | ? {$_.Role -eq "Application"} $farm = Get-SPFarm foreach ( ...
- Android项目实战(二十七):数据交互(信息编辑)填写总结
前言: 项目中必定用到的数据填写需求.比如修改用户名的文字编辑对话框,修改生日的日期选择对话框等等.现总结一下,方便以后使用. 注: 先写实现过程,想要学习的同学可以看看,不需要的同学可以直接拉到最下 ...
- iOS程序破解——获取.ipa程序包
原文在此 首先肯定不是获取自己的ipa包. 为什么要获取ipa包呢?比如,在仿写一些程序时,避免不了获取它的图片素材等等,那么最快也是最有效的方式就是获取原程序的ipa包.更或者,你想要逆向分析某一款 ...
- SqlServer-- NULL空值处理
数据库中,一个列如果没有指定值,那么值就为null,数据库中的null表示"不知道",而不是表示没有.因此select null+1结果是null,因为"不知道" ...
- SqlServer数据冗余的问题和解决
1问题: 1>造成了存储空间的浪费. 2>更新异常.删除异常. 所以一般情况不允许在表中出现数据冗余. 2怎么解决? 把原来表中的数据拆分成多个表来存储. 当把表中的信息拆分成多个表来存储 ...
- ORA-02266: unique/primary keys in table referenced by enabled foreign keys
在数据库里面使用TRUNCATE命令截断一个表的数据时,遇到如下错误 SQL >TRUNCATE TABLE ESCMOWNER.SUBX_ITEM ORA-02266: unique/prim ...
- Linux Bond 技术学习资料
Bond 技术原理 Bond 就是将多块网卡虚拟成为一块网卡的技术,通过 bond 技术让多块网卡看起来是一个单独的以太网接口设备并具有相同的 IP 地址. Bond 的原理是网卡在混杂 (promi ...