该文章基于lodash4.0.0
方法介绍
_.includes(collection, value, [fromIndex=0])
我们知道es6中也有includes
方法,那lodash
中includes
的跟es6中的有什么区别呢?
其实lodash
中的includes
方法跟原生includes
方法类似,也是检测value
值是否在collection
集合中,collection
集合可以是字符串、数组和对象,如果collection
是一个字符串,那么检测value
值(子字符串)是否在字符串中,否则使用SaveValueZero
做等值比较,fromIndex
表示从何处开始检测,如果是负数,则从集合末尾开始检测
参数
collection
:要检索的集合,类型为(Array|Object|string
)value
:要检索的值,可以为任何类型[fromIndex=0]
:要检索的索引起始位置,number
类型,为负数时从末尾开始检索(注意:这里的从末尾开始检索跟原生indexOf
的从末尾开始检索有点不一样,比如formIndex
为-4,表示从末尾检索,检索到倒数第四个下标处,也就是检索长度为4,其实也不是从末尾开始检索的,后面会详细说明原因)
返回值
该方法会返回一个boolean
值,如果在集合中找到value
,返回true
,否则返回false
用法用例
_.includes([1, 2, 3], 1);
// => true
_.includes([1, 2, 3], 1, 2);
// => false
_.includes([1, 2, 3], 1, -1);
// => false
_.includes([1, 2, 3], 1, -3);
// => true
_.includes({ 'user': 'fred', 'age': 40 }, 'fred');
// => true
_.includes('pebbles', 'eb');
// => true
源码解读
lodash的includes长这样的:
includes
function includes(collection, value, fromIndex, guard) {
// 这里对collection做一下判断,如果是数组,就直接返回collection,否则使用values方法取其值
collection = isArrayLike(collection) ? collection : values(collection);
// 如果传入了formIndex且为真,!guard为真,使用toInteger对formIndex进行处理,否则为0
// toInteger就是将目标元素处理成数组的方法
fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
// 取collection的长度,这里的collection一定具有legnth属性的
var length = collection.length;
if (fromIndex < 0) {
// nativeMax就是Math.max,这里主要时为了避免formIndex的绝对值超过了集合的长度
fromIndex = nativeMax(length + fromIndex, 0);
}
return isString(collection)
// collectiopn如果是字符串,就直接使用es的indexOf判断
? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
// 否则,如果length存在且不为0,就返回baseIndexOf的执行结果
: (!!length && baseIndexOf(collection, value, fromIndex) > -1);
}
从源码中我们可以看到,其实includes
方法是接收四个参数的,那第四个参数是干什么的呢?往下看
看函数的第一行,有个values
方法,这个方法也就是lodash
提供给我们调用的values
方法,其目的是取一个集合的value
值,返回是一个数组,values
方法内部还设计到keys
方法,都是lodash
暴露出来的方法,这两个方法我会在后面的文章详细解读,这里就大概知道它是干什么的就好
所以这里就能确定,在函数内部的collection
一定是具有length
属性的(数组或类数组),然后,在函数内部又重新对formIndex
进行赋值了,条件分三种情况:
formIndex
没有传,formIndex
就默认是0formIdnex
有值且为真,同时也传入了guard
,并且也为真,formIndex
还是为0formIndex
有值且为真,!guard
也为真,formIndex
就为toInteger(fromIndex)
的返回值toInteger
这个方法是将元素转为数字,跟上面的values
、keys
一样,都是lodash
暴露出来的可以调用的方法(_.toInteger(value)
),那内部到底是如果做的呢?直接看其源码
toInteger
function toInteger(value) {
if (!value) {
// 这个三木运算其实是没必要的,直接返回0即可
return value === 0 ? value : 0;
}
// 经过toNumber之后,value就是为数字类型,如果vlaue为object等类型,则返回NaN,如果为
// 只包含数字的字符串,则就是将字符串转为了数字,否则返回NaN
value = toNumber(value);
if (value === INFINITY || value === -INFINITY) {
var sign = (value < 0 ? -1 : 1);
// MAX_INTEGER为 1.7976931348623157e+308,sign为 -1 或 1
return sign * MAX_INTEGER;
}
// 对 value 取模,value为整数,remainder就为0,否则为value的小数部分,注意,会有精度丢失的问题
var remainder = value % 1;
// 当value是字符串等类型,value === value就不成立(NaN!==NaN,'1' !== 1),就返回 0
// value为整数,remainder就为0,返回值就为value,否则为value - remainder
return value === value ? (remainder ? value - remainder : value) : 0;
}
INFINITY
就是无限大,当一个数除以 0 时,其结果就是无限大,如果value
不为无限大,remainder
为value
的小数部分(value
为整数,其小数就为0,因为使用了运算符,所以会造成精度丢失的问题),其返回结果三木运算的意思就是:
value === value
不成立(传入的value
不为数字,也不是纯数字的字符串),就返回0value === value
成立,value
为整数就直接返回value
,不为整数,就返回(value - value % 1
),其值也是整数,跟Math.floor()
的结果一致
回到includes
函数,此时formIndex
就是整数了,length
为collection.lengt
h,其值也是正整数,当formIndex
小于0时:
fromIndex = nativeMax(length + fromIndex, 0);
这样可以防止传入的formIndex
的绝对值大于集合本身的长度,如果是这种情况,就直接从0开始检索;
但是这样有一个问题就是formIndex
为正数了,在collection
为字符串时,是直接调用es
的indexOf
取检索的,那这样就无法实现传入的formIndex
为负数时,从集合末尾开始检索了,正是因为这里的代码,所以collection
是字符串时,formIndex
为负数,才导致其并非是跟indexOf
中的从末尾开始检索一样的方式
lodash
中的includes
方法,当collection
为字符串,formIndex
为负数,其检索位置是从字符串长度减去formIndex
的绝对值处开始,一直到末尾
如果collection
不是字符串,且不是空数组、空对象、undefined
等对象时,就使用baseIndexOf
方法检索,看下baseIndexOf
方法具体是怎么处理的:
baseIndexOf
function baseIndexOf(array, value, fromIndex) {
// 如果value值是NaN,调用 indexOfNaN 判断
if (value !== value) {
return indexOfNaN(array, fromIndex);
}
// 这里index 取 fromIndfex - 1,因为下面while循环中是使用++index判断的
var index = fromIndex - 1,
length = array.length;
// 循环遍历array的每一项,当其值等于value时,跳出循环,返回index的值,也就是数组下标
while (++index < length) {
if (array[index] === value) {
return index;
}
}
// 循环结束,array中没有与value相等的值,返回 -1
return -1;
}
可以看到,如果value
不是NaN
,就会走while
循环遍历array
(也就是collection
的values
),找出array
中与value
值相等的项,并返回其下标,如果没有找到,则返回 -1;
注意:在while
循环中判断值是否相等是使用的 ===
判断,也就是不存在隐式类型转换,而且,如果是引用数据类型的话,只能判断其在堆内存中的地址是否一样
最后,我们看下如果value
是NaN
的情况,lodash
是如何处理的:
indexOfNaN
function indexOfNaN(array, fromIndex, fromRight) {
var length = array.length,
// 如果fromRight没有传,则是正向检索,因为下面是++index,所以先将formIndex - 1
index = fromIndex + (fromRight ? 0 : -1);
// formRight代表是否是从formIndex处往起始位置开始检索
while ((fromRight ? index-- : ++index < length)) {
var other = array[index];
// 判断一个元素是否是NaN最好的办法就是判断其自身是否等于自身,因为 NaN !== NaN
if (other !== other) {
return index;
}
}
return -1;
}
可以看到,indexOfNaN
其实是接收三个参数的,第三个参数fromRight
代表是否是从右往左开始检索,也就是是否从fromIndex
处往array
的起始处开始检索,在while
循环中,直接判断array
的每一项是否等于自身,不等于就直接返回其下标,因为value
是NaN
的话,我们只需要找出array
中的NaN
元素就行,而判断一个元素是否是NaN
最好的办法就是判断其自身是否等于自身(NaN !== NaN
);如果array
中没有NaN
元素,则返回 -1
至此,lodash
的includes
解析完毕,最后对includes
总结一下
总结
_.includes(collection, value, [fromIndex=0])
includes
方法接收三个参数(其实是四个参数,但官方文档只写出了三个参数,因为第四个参数无关紧要,我们可以不用管,之所以会存在这个参数,是因为lodash
内部自身调用includes
函数时需要用到这个参数),collection
表示要检索的集合,value
表示要检索的值,formIndex
表示要检索的索引位置
- 其中,
formIndex
可以为负数,官方文档解释为负数是从集合结尾开始检索,其实内部并不是从结尾开始检索的,也是正向检索,只不过是从formIndex
的绝对值的下标处开始检索到结尾,跟从结尾处开始检索的返回的结果是一致的 formIndex
只能是数字或则纯数字的字符串,否则,会使用默认值 0collection
可以是字符串、数组和对象,字符串和数组很常规的检索,但是是对象的时候,是检索的对象的values
,而且判断时使用的===
,不存在隐式类型转换的同时,也无法比较引用数据类型- 如果
value
是NaN
,则是直接检索values
的数组中是否存在NaN
,并返回其下标,没有则返回 -1