lodash是否是数组_lodash源码分析——merge

moyui/BlogPosts​github.com
4dd29324f9a3fd7403966d85a3931a44.png

今天讲讲如何 lodash 里面的 merge 方法

如何实现一个 merge 方法

1.官方实现

_.merge(object, [sources])

object (Object): 目标对象。 sources: 来源对象。 (Object): 返回 object.

var object = {
  a: [{ b: 2 }, { d: 4 }]
};

var other = {
  a: [{ c: 3 }, { e: 5 }]
};

_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

2. 步骤实现

目录结构

相对于 get,merge 的目录相对复杂,入口文件以及相关构成如下

 |——merge.js  #merge的入口文件
 |
 |——.internal——baseAssignValue.js 复制主函数
             ——createAssigner.js 对于多个source的遍历器
             ——baseFor.js 构造器
             ——baseMerge.js merge内部主函数
             ——baseMergeDeep.js 对于 对象/数组/typearray的递归复制函数

具体实现

脑海中复现一个大致思路 约定如下:我们将来源对象称之为 source,目标元素称之为 object 对于一次单个复制我们定义为一个 source 元素 merge 到 object 的过程 对于一次完成的 merge 我们可以认为是在多个 source 情况下所进行的多次单个复制操作

  1. 一次单个复制
  2. 判断 source 元素当前遍历到的 key 是否是 undefiend
  3. 目标元素是否有对应 key
  4. 是否覆盖目标元素的对应 key
  5. 对于值复制与引用复制如何分别进行处理
  6. 对于引用复制,如果引用对象存在环结构(对象 a a.b = 对象 b 对象 b b.a = 对象 a)如何处理
  7. 完整复制
  8. 对于多个 source 的处理

源码阅读

  • 入口函数
// createAssigner 对于多个source的遍历处理
// baseMerge 合并一个source的主入口函数
const merge = createAssigner((object, source, srcIndex) => {
  baseMerge(object, source, srcIndex);
});
  • createAssigner
function createAssigner(assigner) {
  return (object, ...sources) => {
    let index = -1;
    // rest语法,判断参数多少 => 0
    let length = sources.length;
    // 只会执行一次

    // 省略中间部分.....
    while (++index < length) {
      // 执行复制函数
      assigner();
    }
    return object;
  };
}
  • baseMerge
function baseMerge(object, source, srcIndex, customizer, stack) {
  // 如果输入与源相同就return
  if (object === source) {
    return;
  }
  // 对于传入的object遍历每一个key
  baseFor(
    source,
    (srcValue, key) => {
      // 如果是引用类型,就构造一个栈防止成环
      if (isObject(srcValue)) {
        // 形成统一的stack
        stack || (stack = new Stack());
        // clone
        baseMergeDeep(
          object,
          source,
          key,
          srcIndex,
          baseMerge,
          customizer,
          stack
        );
      } else {
        // 值复制拷贝
        assignMergeValue(object, key, newValue);
      }
    },
    keysIn
  );
}
  • assignMergeValue
function assignMergeValue(object, key, value) {
  // 这里存在赋值规则,如果相同的值不为undefined并且相同不做复制 || 值为undefined但是对应的key已经在对象里了不做复制
  if (
    (value !== undefined && !eq(object[key], value)) ||
    (value === undefined && !(key in object))
  ) {
    // 复制
    baseAssignValue(object, key, value);
  }
}
  • baseMergeDeep
function baseMergeDeep(
  object,
  source,
  key,
  srcIndex,
  mergeFunc,
  customizer,
  stack
) {
  const objValue = object[key];
  const srcValue = source[key];
  const stacked = stack.get(srcValue);
  // 如果在栈保存的就直接assign
  if (stacked) {
    // 复制对应引用
    assignMergeValue(object, key, stacked);
    return;
  }
  const isArr = Array.isArray(srcValue);
  if (isArr) {
    if (Array.isArray(objValue)) {
      newValue = objValue;
    } else if (isArrayLikeObject(objValue)) {
      // 遍历拷贝
      newValue = copyArray(objValue);
    } else {
      newValue = [];
    }
  }
  // 如果是object或者argument的话
  else if (isPlainObject(srcValue) || isArguments(srcValue)) {
    newValue = objValue;
    if (isArguments(objValue)) {
      newValue = toPlainObject(objValue);
    } else if (typeof objValue === "function" || !isObject(objValue)) {
      newValue = initCloneObject(srcValue);
    }
  }
  // Recursively merge objects and arrays (susceptible to call stack  limits).
  stack.set(srcValue, newValue);
  stack["delete"](srcValue);
  assignMergeValue(object, key, newValue);
}

最佳实践与思考

  • 对于需要深度 merge 的时候采用,浅 merge 请考虑 Object.assign/_.assign
  • 对于 undefiend 的处理,undefiend 对应的 key 在复制的时候会被跳过
  • 利用函数式编程形成的封装函数 遍历 source 与复制的拆分/遍历 key 与 value 复制的拆分
  • 回环处理,利用栈保存已经遍历过的值,最后在处理到回环部分的时候交由普通复制函数处理引用

有趣的问题

  • JSON.parse(JSON.stringify(data)) 与 merge 有多慢

JSON.parse(JSON.stringify(data)) - 0.12499996228143573 merge - 2.9150000191293657

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值