0%

lodash源码解析:head、tail、indexOf、initial、nth、nthArg

本篇按顺序看下接下来的几个零散的小方法,包括headtailindexOfinitialnthnthArg,最后给出几个方法的原生实现。

引用的内置方法

主要是indexOflastIndexOf两个方法用到了内置方法strictIndexOfstrictLastIndexOf。其实使用strict的原因是为了避免NaN的影响,因为在 JS 中NaN === NaN为假,所以当值不为NaN时才使用严格相等判断;如果值为NaN,就会使用特殊的baseFindIndexbaseIsNaN组合起来判断。

strictLastIndexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* `lastIndexOf`的特殊版本,执行的是值的严格相等比较,也就是`===`
*
* @private
* @param {Array} array 要检查的数组
* @param {*} value 要搜索的值
* @param {number} fromIndex 开始搜索位置处的索引
* @returns {number} 返回匹配值的索引,否则返回`-1`
*/
function strictLastIndexOf(array, value, fromIndex) {
// 提前+1,方便做判断
let index = fromIndex + 1;
// index向越来越小迭代
while (index--) {
// 核心,严格相等判断
if (array[index] === value) {
// 返回找到值的索引
return index;
}
}
return index;
}

export default strictLastIndexOf;

lodash 方法

head方法其实用的就是数组下标实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 获得`array`的第一个元素
*
* @since 0.1.0
* @alias first
* @category Array
* @param {Array} array 要查询的数组
* @returns {*} 返回数组的第一个元素
* @see last
* @example
*
* head([1, 2, 3])
* // => 1
*
* head([])
* // => undefined
*/
function head(array) {
// array存在 且 length属性存在不为0
return array != null && array.length
? // 返回第一个元素
array[0]
: // 否则返回undefined
undefined;
}

export default head;

tail

tail方法使用的是扩展运算符进行数组解构实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 获取除了array数组第一个元素以外的全部元素。
*
* @since 4.0.0
* @category Array
* @param {Array} array 要检索的数组。
* @returns {Array} 返回 array 数组的切片(除了array数组第一个元素以外的全部元素)。
* @example
*
* tail([1, 2, 3])
* // => [2, 3]
*/
function tail(array) {
// 获取长度
const length = array == null ? 0 : array.length;
// 空数组或空值默认返回
if (!length) {
return [];
}
// 解构出tail
const [, ...result] = array;
return result;
}

export default tail;

indexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import baseIndexOf from './.internal/baseIndexOf.js';
import toInteger from './toInteger.js';

/**
* 使用 [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 等值比较,
* 返回首次在数组array中找到的value的索引值, 如果 `fromIndex` 为负值,将从`array`尾端索引进行匹配。
*
* @since 0.1.0
* @category Array
* @param {Array} array 要检查的数组
* @param {*} value 要搜索的值
* @param {number} [fromIndex=0] 开始搜索位置的索引
* @returns {number} 返回匹配值的索引,否则返回`-1`
* @example
*
* indexOf([1, 2, 1, 2], 2)
* // => 1
*
* // Search from the `fromIndex`.
* indexOf([1, 2, 1, 2], 2, 2)
* // => 3
*/
function indexOf(array, value, fromIndex) {
// 获取length
const length = array == null ? 0 : array.length;
if (!length) {
return -1;
}
// 设置初始位置处索引index
let index = fromIndex == null ? 0 : toInteger(fromIndex);
// 如果index小于0,则计算出一个从左端数的index
if (index < 0) {
index = Math.max(length + index, 0);
}
// 调用核心方法baseIndexOf
return baseIndexOf(array, value, index);
}

export default indexOf;

lastIndexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import baseFindIndex from './.internal/baseFindIndex.js';
import baseIsNaN from './.internal/baseIsNaN.js';
import strictLastIndexOf from './.internal/strictLastIndexOf.js';
import toInteger from './toInteger.js';

/**
* 这个方法类似`indexOf`,区别是它是从右到左遍历array的元素。
*
* @since 0.1.0
* @category Array
* @param {Array} array 要搜索的数组。
* @param {*} value 要搜索的值。
* @param {number} [fromIndex=array.length-1] 开始搜索位置的索引。
* @returns {number} 返回匹配值的索引值,否则返回 -1。
* @example
*
* lastIndexOf([1, 2, 1, 2], 2)
* // => 3
*
* // Search from the `fromIndex`.
* lastIndexOf([1, 2, 1, 2], 2, 2)
* // => 1
*/
function lastIndexOf(array, value, fromIndex) {
// 获取长度
const length = array == null ? 0 : array.length;
// 如果为空值或者空数组,返回-1
if (!length) {
return -1;
}
let index = length;
// 如果设置了fromIndex,就把index设为fromIndex
if (fromIndex !== undefined) {
index = toInteger(fromIndex);
// 防止index越界
index =
index < 0 ? Math.max(length + index, 0) : Math.min(index, length - 1);
}
// 最后,当不为NaN时,直接进行严格比较
return value === value
? strictLastIndexOf(array, value, index)
: // 为NaN时,进行特殊比较
baseFindIndex(array, baseIsNaN, index, true);
}

export default lastIndexOf;

initial

这里不使用扩展运算符,是因为扩展运算符只能是最后一个元素,所以就老老实实使用 slice。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import slice from './slice.js';

/**
* 获得`array`中除去最后一个元素的其他内容
*
* @since 0.1.0
* @category Array
* @param {Array} array 要查询的数组
* @returns {Array} 返回`array`的剪切
* @example
*
* initial([1, 2, 3])
* // => [1, 2]
*/
function initial(array) {
// 获取length
const length = array == null ? 0 : array.length;
// 如果数组有内容,就剪切第0个到倒数第一个(不包含)之间的内容
return length ? slice(array, 0, -1) : [];
}

export default initial;

last

lasthead方法差不多,也是用的数组下标实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取array中的最后一个元素。
*
* @since 0.1.0
* @category Array
* @param {Array} array 要查询的数组
* @returns {*} 返回array的最后一项
* @example
*
* last([1, 2, 3])
* // => 3
*/
function last(array) {
// 先取到array的长度
const length = array == null ? 0 : array.length;
// array[length - 1]就是最后一项,否则返回undefined
return length ? array[length - 1] : undefined;
}

export default last;

nth

nth方法和lasthead的实现类似,只不过多了索引的验证和负数索引的处理。这里的负数索引n += n < 0 ? length : 0;转换的很干脆,学习一波。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import isIndex from './.internal/isIndex.js';

/**
* 获取`array`数组的第n个元素。如果n为负数,则返回从数组结尾开始的第n个元素。
*
* @since 4.11.0
* @category Array
* @param {Array} array 要查询的数组
* @param {number} [n=0] 要返回元素的索引
* @returns {*} 返回`array`的第n个元素
* @example
*
* const array = ['a', 'b', 'c', 'd']
*
* nth(array, 1)
* // => 'b'
*
* nth(array, -2)
* // => 'c'
*/
function nth(array, n) {
// 获取length
const length = array == null ? 0 : array.length;
if (!length) {
return;
}
// 处理n为负数的情况
n += n < 0 ? length : 0;
// 看看n是不是有效的索引,是的话就返回该位置处的值
return isIndex(n, length) ? array[n] : undefined;
}

export default nth;

nthArg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import nth from './nth.js';

/**
* 创建一个函数,这个函数返回第 n 个参数。如果 n为负数,则返回从结尾开始的第n个参数。
*
* @since 4.0.0
* @category Util
* @param {number} [n=0] 要返回参数的索引值
* @returns {Function} 返回一个新的直通函数
* @example
*
* const func = nthArg(1)
* func('a', 'b', 'c', 'd')
* // => 'b'
*
* const func = nthArg(-2)
* func('a', 'b', 'c', 'd')
* // => 'c'
*/
function nthArg(n) {
// 返回一个函数,这个函数的功能就是返回第n个参数
return (...args) => nth(args, n);
}

export default nthArg;

原生实现

其实headtailinitiallast的原生实现方式和 lodash 实现过程差不多,只不过自己写的时候没有必要加那么多的参数判断。

head 和 tail

1
2
3
4
5
6
7
// 原生
const array = [1, 2, 3];
const [head, ...tail] = array;
console.log(head);
// 1
console.log(tail);
// [2, 3]

initial

1
2
3
4
// 原生
const array = [5, 4, 3, 2, 1]
console.log(array.slice(0, -1););
// [5, 4, 3, 2]

indexOf 和 lastIndexOf

这两个方法本身ES5就是原生实现了的。

1
2
3
4
5
6
// 原生
var array = [2, 9, 9, 4, 3, 6];
console.log(array.indexOf(9));
// 1
console.log(array.lastIndexOf(9));
// 2
👆 全文结束,棒槌时间到 👇