本篇继续分析下 zip
家族的方法,zip
方法主要是把目标数组群合成为数组或对象。合成数组的方法包括zip
、unzip
、zipWith
、unzipWith
,合成对象的方法包括zipObject
、zipObjectDeep
及核心方法baseZipObject
和baseSet
。
对应源码分析已推到 github
仓库: github.com/MageeLin/lo…
zip
家族方法的依赖路径图如下所示:
zip
zip
方法比较有意思,它和 unzip
既是互为逆操作,却又是相同的操作。在 zip
方法内部就是调用的 return unzip(arrays)
。
unzip
unzip
是 zip
、zipWith
和 unzipWith
实现的基础。但是 unzip
本质上其实只有两步:
- 找到
arrays
中最长的array
的长度; - 从
arrays
的每个array
中拿出对应位置的元素组装到一起,形成新的arrays
;
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
来调用时,其实就是在不停的重复形成两个数组。
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
同样的原理 unZipWith
和 zipWith
也是同样的关系。但是他们都依赖了 unZip
,只不过是每次循环的时候调用了一次 iteratee
。
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
参数的判断,如果最后一个参数是函数,就把他从 arrays
中 pop
出去。
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
接受的两个参数,第一个是键的数组,第二个是值的数组,最后拼成一个对象。
下面是前置依赖的方法 assignValue
和 baseSet
,前者用于普通赋值,后者用于深度赋值。
baseAssignValue
这个地方不太懂为什么要给 enumerable: true
,__proto__
的 enumerable
不应该是 false
吗?需要再思考思考。
/**
* `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
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
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
/**
* `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
,实现赋值。
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
的每个元素的不同形式来进行深度赋值。
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;
前端记事本,不定期更新,欢迎关注!