0%

lodash源码解析:zip家族

本篇继续分析下 zip 家族的方法,zip 方法主要是把目标数组群合成为数组或对象。合成数组的方法包括zipunzipzipWithunzipWith,合成对象的方法包括zipObjectzipObjectDeep及核心方法baseZipObjectbaseSet

对应源码分析已推到 github 仓库: https://github.com/MageeLin/lodash-source-code-analysis

zip 家族方法的依赖路径图如下所示:

zip

zip 方法比较有意思,它和 unzip 既是互为逆操作,却又是相同的操作。在 zip 方法内部就是调用的 return unzip(arrays)

unzip

unzipzipzipWithunzipWith 实现的基础。但是 unzip 本质上其实只有两步:

  1. 找到 arrays 中最长的 array 的长度;
  2. arrays 的每个 array 中拿出对应位置的元素组装到一起,形成新的 arrays
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
import filter from './filter.js';
import map from './map.js';
import baseProperty from './.internal/baseProperty.js';
import isArrayLikeObject from './isArrayLikeObject.js';

/**
* 这个方法类似于`zip`,但是它接收分组`array`组成的`arrays`,
* 并且创建一个`arrays`,`arrays`中的`array`是zip前的分组结构
*
* @since 1.2.0
* @category Array
* @param {Array} array 要执行的的arrays
* @returns {Array} 返回重新分组后的arrays
* @see unzipWith, zip, zipObject, zipObjectDeep, zipWith
* @example
*
* const zipped = zip(['a', 'b'], [1, 2], [true, false])
* // => [['a', 1, true], ['b', 2, false]]
*
* unzip(zipped)
* // => [['a', 'b'], [1, 2], [true, false]]
*/
function unzip(array) {
// 当array是空数组时,返回[]
if (!(array != null && array.length)) {
return [];
}
let length = 0;
// 迭代执行了两个功能,第一个是讲数组筛选出来
// 第二个是让length为最长子数组的长度
array = filter(array, (group) => {
if (isArrayLikeObject(group)) {
length = Math.max(group.length, length);
return true;
}
});
let index = -1;
// 根据最长长度length建一个空数组
const result = new Array(length);
// 迭代
while (++index < length) {
// 把每一个array都取对应的位置处的值map成一个新array
// 然后赋值给result[index]
result[index] = map(array, baseProperty(index));
}
return result;
}

export default unzip;

zip

刚才说zip 方法和 unzip 既是互为逆操作,却又是相同的操作。就是因为本质上都是调用 unzip,但是对同一个 arrays 不停的用 unzip 来调用时,其实就是在不停的重复形成两个数组。

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

/**
* 创建一个分组元素的`arrays`,`arrays`的第一个`array`包含所有给定`array`的第一个元素,
* `arrays`的第二个`array`包含所有给定`array`的第二个元素,以此类推。
*
* @since 0.1.0
* @category Array
* @param {...Array} [arrays] 要处理的arrays
* @returns {Array} 返回分组后的array组成的arrays
* @see unzip, unzipWith, zipObject, zipObjectDeep, zipWith
* @example
*
* zip(['a', 'b'], [1, 2], [true, false])
* // => [['a', 1, true], ['b', 2, false]]
*/
function zip(...arrays) {
// 返回了unzip(arrays)的结果,unzip和zip在本质上是一样的
return unzip(arrays);
}

export default zip;

unZipWith

同样的原理 unZipWithzipWith 也是同样的关系。但是他们都依赖了 unZip,只不过是每次循环的时候调用了一次 iteratee

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

/**
* 此方法类似于`unzip`,但是它接受一个`iteratee`迭代方法参数
* 来指定重组值应该如何被组合。
* iteratee 调用时会传入每个分组的值: (...group)。
*
* @since 3.8.0
* @category Array
* @param {...Array} [arrays] 要处理的arrays
* @param {Function} iteratee 迭代方法迭代每个array,决定如何组合分组值
* @returns {Array} 返回重新分组后的arrays
* @example
*
* const zipped = zip([1, 2], [10, 20], [100, 200])
* // => [[1, 10, 100], [2, 20, 200]]
*
* unzipWith(zipped, add)
* // => [3, 30, 300]
*/
function unzipWith(array, iteratee) {
// 当array是空数组时,返回[]
if (!(array != null && array.length)) {
return [];
}
// 直接调用unzip返回结果
const result = unzip(array);
// 返回iteratee.apply(undefined, group)来转化group中的结果
return map(result, (group) => iteratee.apply(undefined, group));
}

export default unzipWith;

zipWith

zipWith 中需要注意的地方是对 iteratee 参数的判断,如果最后一个参数是函数,就把他从 arrayspop 出去。

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

/**
* 这个方法类似于`zip`,但是它接受一个`iteratee`(迭代方法),
* 来指定分组的值应该如何被组合。
* iteratee函数调用每个组的元素: (...group).
*
* @since 3.8.0
* @category Array
* @param {...Array} [arrays] 要处理的arrays
* @param {Function} iteratee 迭代方法迭代每个array,决定如何组合分组值
* @returns {Array} 返回分组后的array组成的arrays
* @see unzip, unzipWith, zip, zipObject, zipObjectDeep, zipWith
* @example
*
* zipWith([1, 2], [10, 20], [100, 200], (a, b, c) => a + b + c)
* // => [111, 222]
*/
function zipWith(...arrays) {
// 拿到最后一个参数iteratee,并把它从arrays中pop出去
const length = arrays.length;
let iteratee = length > 1 ? arrays[length - 1] : undefined;
iteratee =
typeof iteratee === 'function' ? (arrays.pop(), iteratee) : undefined;
// 和zip方法类似,本质是调用unzipWith(arrays, iteratee)
return unzipWith(arrays, iteratee);
}

export default zipWith;

zipObject

zipObject 方法和 zip 系列的功能不太一样,zipObject 接受的两个参数,第一个是键的数组,第二个是值的数组,最后拼成一个对象。

下面是前置依赖的方法 assignValuebaseSet,前者用于普通赋值,后者用于深度赋值。

baseAssignValue

这个地方不太懂为什么要给 enumerable: true__proto__enumerable 不应该是 false 吗?需要再思考思考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* `assignValue` and `assignMergeValue`的基础实现,没有进行value检查
*
* @private
* @param {Object} object 要修改的对象
* @param {string} key 要分配的属性键
* @param {*} 要分配的值
*/
function baseAssignValue(object, key, value) {
// 当给对象的原型分配值时,使用Object.defineProperty()
if (key == '__proto__') {
Object.defineProperty(object, key, {
configurable: true,
enumerable: true,
value: value,
writable: true,
});
} else {
// 普通情况就直接分配
object[key] = value;
}
}

export default baseAssignValue;

assignValue

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

/** 用于检查对象的自身属性 */
const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
* 当`object`上`key`对应的值与`value`不相等时,就把`value`分配给它
*
* @private
* @param {Object} object 要修改的对象
* @param {string} key 要分配的属性键
* @param {*} value 要分配的属性值
*/
function assignValue(object, key, value) {
// 拿到object上对应的属性值
const objValue = object[key];

// 当key不是object的自身属性,或者 要分配的值与objValue对应的值不相等时
if (!(hasOwnProperty.call(object, key) && eq(objValue, value))) {
// 当value为有效数字时
if (value !== 0 || 1 / value === 1 / objValue) {
// 执行baseAssignValue()
baseAssignValue(object, key, value);
}
// 或者当 value为undefined 且 object上没有key时
} else if (value === undefined && !(key in object)) {
// 执行baseAssignValue()
baseAssignValue(object, key, value);
}
}

export default assignValue;

baseSet

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

/**
* `set`的基础实现
*
* @private
* @param {Object} object 要修改的对象
* @param {Array|string} path 要设置的属性路径
* @param {*} value 要设置的属性值
* @param {Function} [customizer] 自定义路径创建的方法
* @returns {Object} 返回对象
*/
function baseSet(object, path, value, customizer) {
// 不是对象就直接返回
if (!isObject(object)) {
return object;
}

// 返回路径对应的路径数组
path = castPath(path, object);

const length = path.length;
const lastIndex = length - 1;

let index = -1;
let nested = object;

// 这里的迭代就是在用路径数组不停的给object添加后代属性,
// object,nested(变),objValue(变)都是原对象上的
// value,newValue(变)都是要设置的属性上的
while (nested != null && ++index < length) {
const key = toKey(path[index]);
let newValue = value;

// 这下面都是怎么整理出对应的每一层的newValue
if (index != lastIndex) {
// nested是每一层的object
const objValue = nested[key];
// 如果有自定义路径创建的方法,就用,没有就undefined,方便下一步
newValue = customizer ? customizer(objValue, key, nested) : undefined;
// 当对象上没有下一级路径时,就开始创建
if (newValue === undefined) {
newValue = isObject(objValue)
? // 原对象上有这个key就直接赋给newValue
objValue
: // 是index则代表空数组,否则就是空对象
isIndex(path[index + 1])
? []
: {};
}
}
assignValue(nested, key, newValue);
nested = nested[key];
}
return object;
}

export default baseSet;

baseZipObject

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
/**
* `zipObject` 的基础实现,使用 `assignFunc`来分配属性值。
*
* @private
* @param {Array} props 属性标识符
* @param {Array} values 属性值
* @param {Function} assignFunc 分配属性的函数
* @returns {Object} 返回一个新的对象
*/
function baseZipObject(props, values, assignFunc) {
// 初始化
let index = -1;
const length = props.length;
const valsLength = values.length;
// 新建一个结果对象
const result = {};

// 迭代
while (++index < length) {
// 规范values中的value值
const value = index < valsLength ? values[index] : undefined;
// 使用assignFunc来分配value
assignFunc(result, props[index], value);
}
return result;
}

export default baseZipObject;

zipObject

zipObject 方法在调用 baseZipObject 时,传的参数为 assignValue,所以是根据 props 的每个元素直接当 key,实现赋值。

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

/**
* 这个方法类似 `fromPairs`,但是它接受2个数组,
* 第一个数组中的值作为属性标识符(属性名),第二个数组中的值作为相应的属性值。
* @since 0.4.0
* @category Array
* @param {Array} [props=[]] 属性标识符数组
* @param {Array} [values=[]] 属性值数组
* @returns {Object} 返回一个新的对象
* @see unzip, unzipWith, zip, zipObjectDeep, zipWith
* @example
*
* zipObject(['a', 'b'], [1, 2])
* // => { 'a': 1, 'b': 2 }
*/
function zipObject(props, values) {
return baseZipObject(props || [], values || [], assignValue);
}

export default zipObject;

zipObjectDeep

zipObjectDeep 方法在调用 baseZipObject 时,传的参数为 baseSet,所以是根据 props 的每个元素的不同形式来进行深度赋值。

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

/**
* 这个方法类似 `zipObject`,但是它支持属性路径。
*
* @since 4.1.0
* @category Array
* @param {Array} [props=[]] 属性标识符
* @param {Array} [values=[]] 属性值
* @returns {Object} 返回新对象
* @see unzip, unzipWith, zip, zipObject, zipWith
* @example
*
* zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2])
* // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
*/
function zipObjectDeep(props, values) {
// 同样是调用的baseZipObject(),但是分配方法用的是baseSet
return baseZipObject(props || [], values || [], baseSet);
}

export default zipObjectDeep;

前端记事本,不定期更新,欢迎关注!


👆 全文结束,棒槌时间到 👇