深浅拷贝和extend实现

前言

深浅拷贝和 extend 是项目中常用的工具类函数,今天就动手实现一下

下面会重复这几个基础函数,为了简洁下面不会出现相关定义

function isObject(obj) {
  return obj && typeof obj === "object";
}
function type(obj) {
  return Object.prototype.toString
    .call(obj)
    .replace(/\[object.(.+?)\]/, "$1")
    .toLowerCase();
}
function array(par) {
  return type(par) === "array";
}

浅拷贝用法

var obj = { name: "test", args: [{ name: 1 }] };
var test = { ...obj };
// 修改下原来属性
obj.name = "foo";
// test.name test
obj.args.push("456");
// args: (2) [{…}, "456"]

从上面例子可以看到,当两个对象出现相同字段的时候,后者会覆盖前者,而不会进行深层次的覆盖。

由此可以得到一个结论:浅拷贝可以简单理解为只拷贝对象的一层属性,如果拷贝的属性还是对象,那么修改它则会影响到拥有相同属性的对象。

浅拷贝实现

结合上面的结论,我们动手实现一个函数

export function copy(obj) {
  if (!isObject(obj)) {
    return;
  }
  const src = array(obj) ? [] : {};
  for (const key in obj) {
    const value = obj[key];
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      src[key] = value;
    }
  }
  return src;
}

测试用例

test("浅拷贝", () => {
  const value = { a: 123, b: 456, c: [1] };
  const test = copy(value);
  expect(test).toEqual({ a: 123, b: 456, c: [1] });
  expect(value === test).toBeFalsy();
  expect(value.c === test.c).toBeTruthy();
  expect(value.a === test.a).toBeTruthy();
});

深拷贝

上面我们已经实现了浅拷贝,浅拷贝只是简单的拷贝了一层属性,如果是深拷贝呢?

其实就是对属性为对象进行重复的调用,在实现这个之前先看一个比较简单的做法。

json

function deepAssign(par) {
  return JSON.parse(JSON.stringify(par));
}
// 测试用例
const value = {a: {name: 456}};
const test = deepAssign({value);
test.a === value.a;
//false
deepAssign({ a() {}, b: /abc/, c: Math.floor, d: null, e: new Set() });
// {b: {}, d: null, e: {}}

需要特别注意用这个方法处理函数、正则之类的对象不会出现预期结果,不过在处理接口返回的数据不失为一种方法。

递归

相比JSON的方式实现,可以让我们自由定制一些类型,例如上面的正则就可以通过hack方法创建,为了简化源码,这里只处理对象和数组,对于其他类型的处理可以参考一下第三方库

function deepCopy(obj, has = new WeakMap()) {
  if (!isObject(obj)) {
    return;
  }
  // 避免循环引用
  if (has.has(obj)) {
    return has.get(obj);
  }
  const src = array(obj) ? [] : {};
  has.set(obj, src);
  for (const key in obj) {
    const value = obj[key];
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      // 注意这一行
      src[key] = isObject(value) ? copy(value, has) : value;
    }
  }
  return src;
}

测试用例

test("深拷贝", () => {
  const value = { a: 123, b: 456, c: [1] };
  const test = deepCopy(value);
  expect(test).toEqual({ a: 123, b: 456, c: [1] });
  expect(value === test).toBeFalsy();
  expect(value.c === test.c).toBeFalsy();
  expect(value.a === test.a).toBeTruthy();
});

注意上面用了一个WeakMap,这个是为了避免循环引入,你可以把上面的WeakMap相关代码去掉,然后在控制台输入下面这个例子,看看会出现什么结果。

var a = {};
a.a = a;
var test = deepCopy(a);

extend

extend 分为两部分:

  1. 浅合并,Object.assign就是浅拷贝;
  2. 深合并,没有相关的的 api,需要手动实现

浅合并使用方法

在实现这个方法之前,我们约定一下格式

assign( target [, object1 ] [, objectN ] )

第一个参数为目标对象必须是对象,之后的参数是目标对象,可以不为对象

var obj1 = {
  a: 1,
  b: { b1: 1, b2: 2 },
};

var obj2 = {
  b: { b1: 3, b3: 4 },
  c: 3,
};

var obj3 = {
  d: 4,
};

console.log(assign(obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b3: 4 },
//    c: 3,
//    d: 4
// }

浅合并实现

根据上面的约束,动手实现一下

function assign() {
  let target = arguments[0];
  let i = 1,
    leg = arguments.length;
  if (!isObject(target)) {
    target = {};
  }
  for (; i < leg; i++) {
    const value = arguments[i];
    if (value == null) {
      continue;
    }
    for (const name in value) {
      if (Object.prototype.hasOwnProperty.call(value, name)) {
        const copy = value[name];
        target[name] = copy;
      }
    }
  }
  return target;
}

测试用例

test("浅拷贝", () => {
  const value1 = { name: "test", age: 17 };
  const value2 = { name: "app", age: [1, 2, 3] };
  const test = assign(value1, value2, null, undefined, "abc");
  expect(assign(test)).toEqual({
    name: "app",
    age: [1, 2, 3],
    0: "a",
    1: "b",
    2: "c",
  });
  expect(test.age === value2.age).toBeTruthy();
  var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 },
  };

  var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3,
  };

  var obj3 = {
    d: 4,
  };
  expect(assign(obj1, obj2, obj3)).toEqual({
    a: 1,
    b: { b1: 3, b3: 4 },
    c: 3,
    d: 4,
  });
});

深合并

function deepAssign(...args) {
  let obj = {};
  for (const value of args) {
    if (array(value)) {
      if (!array(obj)) {
        obj = [];
      }
      // 对数组内容进行深拷贝
      const arr = value.reduce((x, y) => {
        x = x.concat(isObject(y) ? deepAssign([], y) : y);
        return x;
      }, []);
      obj = [...obj, ...arr];
    } else if (isObject(value)) {
      for (let [name, val] of Object.entries(value)) {
        if (isObject(val) && name in obj) {
          val = deepAssign(obj[name], val);
        }
        obj = {
          ...obj,
          [name]: val,
        };
      }
    }
  }
  return obj;
}

if (isObject(val) && name in obj)注意这行的判断,只有当 target 属性上有值的时候才会进行递归调用。

测试用例

test("深合并", () => {
  const value1 = [1, 2, { name: { age: [1, 2, 3, 4, 5] } }];
  const value2 = [1, 2, { age: 18 }];
  const test = deepAssign(value1, value2);
  expect(test[2] === value1[2]).toBe(false);
  expect(test).toEqual([
    1,
    2,
    { name: { age: [1, 2, 3, 4, 5] } },
    1,
    2,
    { age: 18 },
  ]);
  const obj1 = { name: [1, 2, 3] };
  const obj2 = { age: 17, name: [4, 5, 6] };
  const obj = deepAssign(obj1, obj2);
  expect(obj).toEqual({ name: [1, 2, 3, 4, 5, 6], age: 17 });
  const a = {};
  a.a = a;
  expect(deepAssign(a)).toEqual({ a: { a } });
});

最后

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

原文链接:juejin.im

上一篇:微前端-angular+qiankun+single-spa(一)
下一篇:自己把web应用丢到服务器上,告别求助后端(单页应用与服务端渲染)

相关推荐

  • (初级前端)面试如何写出一个满意的深拷贝

    前言 已经有很多关于深拷贝与浅拷贝的文章,为什么自己还要写一遍呢💯 ❝ 学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。

    14 天前
  • 面试篇---1 如何区分深拷贝与浅拷贝

    如何区分深拷贝与浅拷贝? 简单来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

    1 年前
  • 重新梳理下js中的深拷贝和浅拷贝

    参考链接: http://www.cnblogs.com/stles...(http://www.cnblogs.com/stleslie/p/7128765.html) https://blog.c...

    1 年前
  • 赋值、浅拷贝、深拷贝区别

    数据类型存储 基本类型数据保存在在栈内存中引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。 为什么基本数据类型存在栈内存,引用数据类型存在堆内存? 基本数据...

    4 个月前
  • 谈谈深拷贝与浅拷贝

    前言 关于深拷贝和浅拷贝其实是两个比较基础的概念,但是我还是想整理一下,因为里面有很多小细节还是很有意思的。 深拷贝和浅拷贝的区别 深拷贝和浅拷贝是大家经常听到的两个名词,两者到底有什么不同...

    2 年前
  • 谈谈深拷贝、浅拷贝

    前提: 假设您已经知道为什么在JavaScript中需要深拷贝和浅拷贝了。 举两个例子: 在上面数组和对象中分别改变了 和 ,但是最后结果的得到和原来的值保持一致。

    9 个月前
  • 详解 Jquery extend() 和Jquery.fn.extend()

    Jquery extend() API文档上的解释:将一个或者多个对象扩展一个新对象,返回一个新的对象 $.extend(deep,target,obj1,objN) deep:是否深度克隆对象,...

    2 年前
  • 记笔记:一个因数组赋值引起的深拷贝、浅拷贝的学习

    前言 今天写业务的时候发现一个问题:两个嵌套对象的数组分别赋值为新的常量,常量push合并,原数组也会合并。 复现问题 问题原因 直接把原数组定义为常量,如const newArr1 = o...

    2 个月前
  • 记录一下最近在学的深浅拷贝

    前言 最近写代码经常用到深浅拷贝,从一开始的闷头使用渐渐想要深究其理,这篇文章记录一下我的认为,有所不足,恭请指正 我们可以先看看一个常遇到的一个小问题 从上面的例子中我们看到了,如果给一...

    2 年前
  • 解构赋值是深拷贝吗?

    最近在使用Redux想到一个问题,Redux里常用的一种语法是这样的: Redux通过解构赋值...state,保留了state里未修改的部分,并覆盖修改的部分,那么现在问题来了,这里的新对象通...

    1 年前

官方社区

扫码加入 JavaScript 社区