0%

lodash源码解析:pull家族

本篇分析下pull家族的方法,包括pullpullAllpullAllBypullAllWithpullAt以及核心方法basePullAllbasePullAt。顺便分析依赖到的 lodash 方法memoize,最后给出pull方法的原生实现。

具体的依赖路径图如下所示:

另外,在 basePullAt 方法的源码中发现一个 bug,在数组中多次列出同一个要删除的元素索引的情况下,会导致方法执行结果不对,删除元素过多。但是在打包好文件中将 let 变为了 var 并挪动了位置,掩盖了这个 bug

依赖的内置方法

baseAt

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
import get from '../get.js';

/**
* `at`方法的基础实现,不用支持单个的路径
*
* @private
* @param {Object} object 要迭代的对象
* @param {string[]} paths 要获取的对象的元素路径数组
* @returns {Array} 返回要获取的元素组成的数组
*/
function baseAt(object, paths) {
// 初始化
let index = -1;
const length = paths.length;
const result = new Array(length);
const skip = object == null;

// 迭代
while (++index < length) {
// object为null或undefined时,就返回undefined
// 否则就给result对应位置赋值get();
result[index] = skip ? undefined : get(object, paths[index]);
}
return result;
}

export default baseAt;

baseGet

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
import castPath from './castPath.js';
import toKey from './toKey.js';

/**
* `get`方法的基础实现,不用支持默认值
*
* @private
* @param {Object} object 要检索的对象。
* @param {Array|string} path 要获取属性的路径。
* @returns {*} 返回解析的值。
*/
function baseGet(object, path) {
// 拿到路径数组
path = castPath(path, object);

let index = 0;
const length = path.length;

// 迭代,一级级向下拿到最终的值
while (object != null && index < length) {
object = object[toKey(path[index++])];
}
// index == length,也就是执行到底时,返回最终的值
return index && index == length ? object : undefined;
}

export default baseGet;

baseIndexOfWith

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
/**
* 该函数类似`baseIndexOf`,但是接受一个comparator
*
* @private
* @param {Array} array 要检查的数组
* @param {*} value 要搜索的值
* @param {number} fromIndex 开始搜索位置处的索引
* @param {Function} comparator 比较器comparator调用每个元素
* @returns {number} 返回匹配值得索引,否则返回`-1`
*/
function baseIndexOfWith(array, value, fromIndex, comparator) {
// 从设定的索引处开始
let index = fromIndex - 1;
// 这里用了解构方式获取length,赞成
const { length } = array;

// 开始迭代
while (++index < length) {
// 拿array的每一个元素通过comparator的方式与value做对比
if (comparator(array[index], value)) {
// 找到了就返回索引
return index;
}
}
// 找不到就返回-1
return -1;
}

export default baseIndexOfWith;

baseUnset

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

/**
* `unset`方法的基础实现
*
* @private
* @param {Object} object 要修改的对象
* @param {Array|string} path 要移除的属性路径
* @returns {boolean} 如果删除成功,那么返回 `true` ,否则返回 `false`。
*/
function baseUnset(object, path) {
// 获取属性路径数组
path = castPath(path, object);
// 获取父级属性值的引用
object = parent(object, path);
// 在父级属性值中删掉该属性
return object == null || delete object[toKey(last(path))];
}

export default baseUnset;

castPath

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

/**
* 当`value`不是单独值的时候,返回path数组
*
* @private
* @param {*} value 要检查的值
* @param {Object} [object] 要查询key的对象
* @returns {Array} 返回转换换后的属性路径数组
*/
function castPath(value, object) {
// value是数组的时候直接返回value
if (Array.isArray(value)) {
return value;
}
// 如果value是一个key,就用数组包起来,否则调用stringToPath
return isKey(value, object) ? [value] : stringToPath(value);
}

export default castPath;

compareAscending

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import isSymbol from '../isSymbol.js';

/**
* 比较value,并用升序排序
*
* @private
* @param {*} value 要比较的值
* @param {*} other 另一个要比较的值
* @returns {number} 返回`value`的是否升序排列的指示符
*/
function compareAscending(value, other) {
// 两个值不完全相等
if (value !== other) {
// value是否不为undefined
const valIsDefined = value !== undefined;
// value是否为null
const valIsNull = value === null;
// value是否是自己本身
const valIsReflexive = value === value;
// value是否为symbol
const valIsSymbol = isSymbol(value);

// 同上
const othIsDefined = other !== undefined;
const othIsNull = other === null;
const othIsReflexive = other === other;
const othIsSymbol = isSymbol(other);

// 如果value为字符串
const val =
typeof value === 'string'
? // 是就执行字符串比较的结果
value.localeCompare(other)
: // 否的话就转为负
-other;

// 下面是进行顺序比较
if (
(!othIsNull && !othIsSymbol && !valIsSymbol && val > 0) ||
(valIsSymbol &&
othIsDefined &&
othIsReflexive &&
!othIsNull &&
!othIsSymbol) ||
(valIsNull && othIsDefined && othIsReflexive) ||
(!valIsDefined && othIsReflexive) ||
!valIsReflexive
) {
return 1;
}
if (
(!valIsNull && !valIsSymbol && !othIsSymbol && val < 0) ||
(othIsSymbol &&
valIsDefined &&
valIsReflexive &&
!valIsNull &&
!valIsSymbol) ||
(othIsNull && valIsDefined && valIsReflexive) ||
(!othIsDefined && valIsReflexive) ||
!othIsReflexive
) {
return -1;
}
}
// 如果两个值完全相等===,则返回0
return 0;
}

export default compareAscending;

copyArrayAt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 复制`source`的值到`array`中
*
* @private
* @param {Array} source 复制值的来源
* @param {Array} [array=[]] 要复制到的数组
* @returns {Array} 返回 `array`.
*/
function copyArray(source, array) {
let index = -1;
const length = source.length;

// array若是空,就把array设为与source等长的空数组
array || (array = new Array(length));
// 开始迭代
while (++index < length) {
// 把每个值复制过去,所以是个浅拷贝
array[index] = source[index];
}
return array;
}

export default copyArray;

isKey

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
import isSymbol from '../isSymbol.js';

/** 用于匹配属性路径中的属性名称。 */
// 带有深度路径属性名的正则
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
// 直接属性名的正则
const reIsPlainProp = /^\w*$/;

/**
* 检查`value`是一个属性名并且不是一个属性路径
*
* @private
* @param {*} value 要检查的值
* @param {Object} [object] 要查询key的对象
* @returns {boolean} 如果时一个属性名则返回`true`,否则返回`false`
*/
function isKey(value, object) {
// 如果是个数组,则返回false
if (Array.isArray(value)) {
return false;
}
const type = typeof value;
// 如果typeof结果是个number、boolean或者value直接为null或者value是个symbol,则返回true
if (
type === 'number' ||
type === 'boolean' ||
value == null ||
isSymbol(value)
) {
return true;
}
// 直接属性名正则验证 || 带深度的属性名验证 || 或value在object上能查询到
return (
reIsPlainProp.test(value) ||
!reIsDeepProp.test(value) ||
(object != null && value in Object(object))
);
}

export default isKey;

memoizeCapped

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
import memoize from '../memoize.js';

/** 用于memoize缓存的最大条数 */
const MAX_MEMOIZE_SIZE = 500;

/**
* `memoize`方法的特殊版本,当缓存数超过`MAX_MEMOIZE_SIZE`时会清理memoized
*
* @private
* @param {Function} func 将该函数的输出缓存
* @returns {Function} 返回一个新的memoized函数
*/
function memoizeCapped(func) {
// 这里借用resolver函数,计算result.cache的size
const result = memoize(func, (key) => {
const { cache } = result;
// 当size达到300时,清理cache
if (cache.size === MAX_MEMOIZE_SIZE) {
cache.clear();
}
return key;
});

return result;
}

export default memoizeCapped;

parent

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

/**
* 获取`object`上`path`的父级属性值
*
* @private
* @param {Object} object 要查询的对象
* @param {Array} path 要获取父级属性值的路径
* @returns {*} 返回父级属性值
*/
function parent(object, path) {
// 当length小于2,也就是length == 1时,直接就返回object
// 否则,就用baseGet获取(去掉最后一个路径的)路径数组对应的值
return path.length < 2 ? object : baseGet(object, slice(path, 0, -1));
}

export default parent;

stringToPath

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
47
48
49
50
51
52
53
54
55
56
57
58
import memoizeCapped from './memoizeCapped.js';

// 点的charCode
const charCodeOfDot = '.'.charCodeAt(0);
// 转义字符正则表达式
const reEscapeChar = /\\(\\)?/g;
// 属性名正则表达式
const rePropName = RegExp(
// 匹配不是.或括号的任意值
'[^.[\\]]+' +
'|' +
// 或者匹配括号内的属性名
'\\[(?:' +
// 匹配一个非字符串表达式
'([^"\'][^[]*)' +
'|' +
// 或者是匹配字符串(支持转义字符)
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
')\\]' +
'|' +
// 或者匹配 "" 作为连续 点 或 空括号之间的空白
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
'g'
);

/**
* 将一个`string`转化为属性路径数组
*
* @private
* @param {string} string 要转化的字符串
* @returns {Array} 返回属性路径数组
*/
// stringToPath是一个缓存函数
const stringToPath = memoizeCapped((string) => {
// 定义一个空数组result
const result = [];
// 当第一个字符为 点 时,push一个''
if (string.charCodeAt(0) === charCodeOfDot) {
result.push('');
}

// 借用String.prototype的replace方法
// 第一个参数是正则表达式,第二个参数是创建新的子字符串的函数
// match是匹配的子串,,substring是被匹配的字符串
string.replace(rePropName, (match, expression, quote, subString) => {
let key = match;
if (quote) {
key = subString.replace(reEscapeChar, '$1');
} else if (expression) {
key = expression.trim();
}
result.push(key);
});
// 将result返回
return result;
});

export default stringToPath;

toKey

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

/** 作为多种`Number`类型常量的引用 */
const INFINITY = 1 / 0;

/**
* 当`value`不是string或symbol,将`value`转化为字符串key
*
* @private
* @param {*} value 要检查的值
* @returns {string|symbol} 返回key
*/
function toKey(value) {
// 当为string或symbol时,直接返回value
if (typeof value === 'string' || isSymbol(value)) {
return value;
}
// 当不是string时,先强制转化为字符串
const result = `${value}`;
// 当为-0时返回-0,其余直接返回
return result == '0' && 1 / value == -INFINITY ? '-0' : result;
}

export default toKey;

依赖的 lodash 方法

memoize

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
*
* 创建一个会缓存 `func` 结果的函数。
* 如果提供了 `resolver` ,就用 `resolver` 的返回值作为 `key` 缓存函数的结果。
* 默认情况下用第一个参数作为缓存的 `key`。 `func` 在调用时 `this` 会绑定在缓存函数上。
*
* **注意:** 缓存会暴露在缓存函数的 `cache`属性上。
* 它是可以定制的,只要替换了 `memoize.Cache` 构造函数,
* 或实现了 [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
* 的 `clear`, `delete`, `get`, `has`, 和 `set`方法。
*
* @since 0.1.0
* @category Function
* @param {Function} func 需要将输出缓存的函数。
* @param {Function} [resolver] 这个函数的返回值作为缓存的 key。
* @returns {Function} 返回新的缓存化后的函数。
* @example
*
* const object = { 'a': 1, 'b': 2 }
* const other = { 'c': 3, 'd': 4 }
*
* const values = memoize(values)
* values(object)
* // => [1, 2]
*
* values(other)
* // => [3, 4]
*
* object.a = 2
* values(object)
* // => [1, 2]
*
* // 修改结果缓存
* values.cache.set(object, ['a', 'b'])
* values(object)
* // => ['a', 'b']
*
* // 替换 `memoize.Cache`.
* memoize.Cache = WeakMap
*/
function memoize(func, resolver) {
// 当func不是函数 || resolver不是函数的时候,报错
if (
typeof func !== 'function' ||
(resolver != null && typeof resolver !== 'function')
) {
throw new TypeError('Expected a function');
}
// 定义一个函数memoized
const memoized = function (...args) {
// 如果resolver存在,就用resolver转化一下参数作为key,否则直接把第一个参数作为key
const key = resolver ? resolver.apply(this, args) : args[0];
const cache = memoized.cache;

// 当cache中有key的时候,直接返回该key的值。完成了读取缓存的操作
if (cache.has(key)) {
return cache.get(key);
}
// cache没有这个key的时候,就用func执行这些参数
const result = func.apply(this, args);
// 然后在cache设置这个key的值为result,完成了设置缓存操作
memoized.cache = cache.set(key, result) || cache;
return result;
};
// 设置memoized这个函数对象的cache属性,默认为一个map
memoized.cache = new (memoize.Cache || Map)();
// 把memoized函数返回
return memoized;
}

memoize.Cache = Map;

export default memoize;

get

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
import baseGet from './.internal/baseGet.js';

/**
* 根据 `object`对象的`path`路径获取值。 如果解析 `value` 是 `undefined` 会以 `defaultValue` 取代。
*
*
* @since 3.7.0
* @category Object
* @param {Object} object 要检索的对象。
* @param {Array|string} path 要获取属性的路径。
* @param {*} [defaultValue] 如果解析值是 undefined ,defaultValue会被返回。
* @returns {*} 返回解析的值。
* @see has, hasIn, set, unset
* @example
*
* const object = { 'a': [{ 'b': { 'c': 3 } }] }
*
* get(object, 'a[0].b.c')
* // => 3
*
* get(object, ['a', '0', 'b', 'c'])
* // => 3
*
* get(object, 'a.b.c', 'default')
* // => 'default'
*/
function get(object, path, defaultValue) {
// object为null或undefined时,返回undefined,否则返回baseGet
const result = object == null ? undefined : baseGet(object, path);
// result为undefined时,则返回默认值,否则返回result
return result === undefined ? defaultValue : result;
}

export default get;

核心内置方法

basePullAll

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
47
48
49
50
51
52
53
54
55
56
57
58
import map from '../map.js';
import baseIndexOf from './baseIndexOf.js';
import baseIndexOfWith from './baseIndexOfWith.js';
import copyArray from './copyArray.js';

/**
* `pullAll`系列方法的基础实现
*
* @private
* @param {Array} array 要修改的数组。
* @param {Array} values 要移除值的数组。
* @param {Function} [iteratee] iteratee(迭代器)调用每个元素。
* @param {Function} [comparator] comparator(比较器)调用每个元素。
* @returns {Array} 返回 `array`.
*/
function basePullAll(array, values, iteratee, comparator) {
// 使用comparator时用baseIndexOfWith,否则用baseIndexOf
const indexOf = comparator ? baseIndexOfWith : baseIndexOf;
// 拿到要移除数组的长度
const length = values.length;

let index = -1;
// 把array的指针赋给seen
let seen = array;

// 如果array和values引用的同一个对象
if (array === values) {
// 就把values换个地址
values = copyArray(values);
}
// 如果iteratee参数存在,就把seen的所有元素执行下iteratee后的新数组再返回给seen
if (iteratee) {
seen = map(array, (value) => iteratee(value));
}
// 开始迭代
while (++index < length) {
let fromIndex = 0;
const value = values[index];
// 当iteratee存在时,则转换下(因为之前把array的转换了,现在需要转换values的),否则返回value
const computed = iteratee ? iteratee(value) : value;

// 如果在seen(array)中能找到匹配值的索引
while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
// 也就是iteratee参数存在
if (seen !== array) {
// 则从seen中删去该值
seen.splice(fromIndex, 1);
}
// array中也删去该值
array.splice(fromIndex, 1);
}
// 内层while执行完后,就决定当前的元素要不要删
}
// 外层while执行完后,就决定了array中每一个元素要不要删
return array;
}

export default basePullAll;

basePullAt

该方法有 bug,本系列全部完成后给 lodash 统一提个 pull request 吧

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
import baseUnset from './baseUnset.js';
import isIndex from './isIndex.js';

/**
* `pullAt`方法的基础实现,不支持单独的索引或捕获被移除的元素
*
* @private
* @param {Array} array 要修改的数组
* @param {number[]} indexes 要删除元素的索引数组
* @returns {Array} 返回数组
*/
// 注意: 传到indexes中的数组必须已经排序过,不然会出bug
function basePullAt(array, indexes) {
// 初始化
let length = array ? indexes.length : 0;
const lastIndex = length - 1;

// 开始从右向左循环
while (length--) {
// 此处有bug,应该移到while循环外,否则每次的previous都是重新定义的
let previous;
// index是当前迭代的索引
const index = indexes[length];
// 当length === lastIndex,是让第一次循环能执行
// 或 index !== previous时,是让后来能运行,因为indexes其实已经排序过了,
// 所以只用跟前一个元素比较看看有没有重复就可以了,重复就跳过
if (length === lastIndex || index !== previous) {
// 让previous等于index
previous = index;
// 删除该位置的索引
if (isIndex(index)) {
array.splice(index, 1);
} else {
baseUnset(array, index);
}
}
}
return array;
}

export default basePullAt;

对比打包好的 lodash 库中的 basePullAt,其实是掩盖了bug存在的。如果直接执行basePullAt源码,错误就会暴露出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function basePullAt(array, indexes) {
var length = array ? indexes.length : 0,
lastIndex = length - 1;

while (length--) {
var index = indexes[length];
if (length == lastIndex || index !== previous) {
// 对比可以发现,此处的previous是使用var定义,并且定义的位置
// 放在了if判断的后面,导致if判断依然能生效,掩盖了bug
var previous = index;
if (isIndex(index)) {
splice.call(array, index, 1);
} else {
baseUnset(array, index);
}
}
}
return array;
}

pull 家族

pull

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
import pullAll from './pullAll.js';

/**
* 移除数组`array`中所有和给定值相等的元素,
* 使用 [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 进行全等比较。
*
* **注意:** 和 `without` 方法不同,这个方法会改变数组。通过断言使用 `remove` 从一个数组中移除元素。
*
* @since 2.0.0
* @category Array
* @param {Array} array 要修改的数组。
* @param {...*} [values] 要删除的值。
* @returns {Array} 返回 `array`.
* @see pullAll, pullAllBy, pullAllWith, pullAt, remove, reject
* @example
*
* const array = ['a', 'b', 'c', 'a', 'b', 'c']
*
* pull(array, 'a', 'c')
* console.log(array)
* // => ['b', 'b']
*/
function pull(array, ...values) {
// 核心调用的pullAll
return pullAll(array, values);
}

export default pull;

pullAll

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
import basePullAll from './.internal/basePullAll.js';

/**
* 这个方法类似_.pull,区别是这个方法接收一个要移除值的数组。
*
* **注意:** 和 `difference` 方法不同,这个方法会改变数组。
*
* @since 4.0.0
* @category Array
* @param {Array} array 要修改的数组。
* @param {Array} values 要移除值的数组。
* @returns {Array} 返回 `array`.
* @see pull, pullAllBy, pullAllWith, pullAt, remove, reject
* @example
*
* const array = ['a', 'b', 'c', 'a', 'b', 'c']
*
* pullAll(array, ['a', 'c'])
* console.log(array)
* // => ['b', 'b']
*/
function pullAll(array, values) {
// array和value有值 && array和value有长度
return array != null && array.length && values != null && values.length
? // 满足则调用basePullAll
basePullAll(array, values)
: array;
}

export default pullAll;

pullAllBy

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 basePullAll from './.internal/basePullAll.js';

/**
* 这个方法类似于 `pullAll` ,区别是这个方法接受一个 `iteratee`(迭代函数)
* 调用 `array` 和 `values`的每个值以产生一个值,通过产生的值进行了比较。
* iteratee 会传入一个参数: (value)。
*
* **注意:** 不同于 `differenceBy`, 这个方法会改变数组 array。
*
* @since 4.0.0
* @category Array
* @param {Array} array The array to modify.要修改的数组。
* @param {Array} values The values to remove.要移除值的数组。
* @param {Function} iteratee iteratee(迭代器)调用每个元素。
* @returns {Array} 返回 `array`.
* @see pull, pullAll, pullAllWith, pullAt, remove, reject
* @example
*
* const array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]
*
* pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x')
* console.log(array)
* // => [{ 'x': 2 }]
*/
function pullAllBy(array, values, iteratee) {
// array和value有值 && array和value有长度
return array != null && array.length && values != null && values.length
? // 满足则调用basePullAll,但是多加参数iteratee
basePullAll(array, values, iteratee)
: array;
}

export default pullAllBy;

pullAllWith

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
import basePullAll from './.internal/basePullAll.js';

/**
*
* 这个方法类似于 `pullAll` ,区别是这个方法接受 comparator
* 调用array中的元素和values比较。
* comparator 会传入两个参数:(arrVal, othVal)。
*
* **注意:** 不同于 `differenceWith`, 这个方法会改变数组 array。
*
* @since 4.6.0
* @category Array
* @param {Array} array 要修改的数组。
* @param {Array} values 要移除值的数组。
* @param {Function} [comparator] comparator(比较器)调用每个元素。
* @returns {Array} 返回 `array`.
* @see pull, pullAll, pullAllBy, pullAt, remove, reject
* @example
*
* const array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]
*
* pullAllWith(array, [{ 'x': 3, 'y': 4 }], isEqual)
* console.log(array)
* // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
*/
function pullAllWith(array, values, comparator) {
// array和value有值 && array和value有长度
return array != null && array.length && values != null && values.length
? // 满足则调用basePullAll,但是多加参数comparator
basePullAll(array, values, undefined, comparator)
: array;
}

export default pullAllWith;

pullAt

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
import map from './map.js';
import baseAt from './.internal/baseAt.js';
import basePullAt from './.internal/basePullAt.js';
import compareAscending from './.internal/compareAscending.js';
import isIndex from './.internal/isIndex.js';

/**
* 根据索引 `indexes`,移除`array`中对应的元素,并返回被移除元素的数组。
*
* **注意:** 和 `at`不同, 这个方法会改变数组 `array`。
*
*
* @since 3.0.0
* @category Array
* @param {Array} array 要修改的数组。
* @param {...(number|number[])} [indexes] 要移除元素的索引。
* @returns {Array} 返回移除元素组成的新数组。
* @see pull, pullAll, pullAllBy, pullAllWith, remove, reject
* @example
*
* const array = ['a', 'b', 'c', 'd']
* const pulled = pullAt(array, [1, 3])
*
* console.log(array)
* // => ['a', 'c']
*
* console.log(pulled)
* // => ['b', 'd']
*/
function pullAt(array, ...indexes) {
// 取到length
const length = array == null ? 0 : array.length;

const result = baseAt(array, indexes);

basePullAt(
array,
map(indexes, (index) => (isIndex(index, length) ? +index : index)).sort(
compareAscending
)
);
return result;
}

export default pullAt;

原生实现

pull方法的原生实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 原生实现
function pull(arr, ...removeList) {
// 使用set首先能对removeList去重
// 第二set自带增删查改等各种方法
let removeSet = new Set(removeList);
// 直接对arr过滤,如果set中有该元素,就过滤掉。
return arr.filter((el) => {
return !removeSet.has(el);
});
}
let array = [1, 2, 3, 1, 2, 3];
console.log(pull(array, 2, 3));
// [1, 1]
👆 全文结束,棒槌时间到 👇