大前端面试题|浅拷贝与深拷贝🔥

302 阅读4分钟

1.jpg

前言

在前端开发中 浅拷贝深拷贝 可以说是面试中经常碰到的经典问题, 并且在实战项目开发过程中, 也常常会因为数据拷贝问题, 导致一些隐藏的 BUG;到底什么是 深浅拷贝 呢,又有哪些开发场景会遇到,带着这些疑惑咱们一起来学习吧, 本文将会挨个进行揭秘

简介

我们来通过内存分配学习一下深浅拷贝吧, 首先复习下赋值

赋值

一个变量的数据,赋值给另一个变量;

const data1 = 1
const data2 = data1

const data3 = {a:1,b:2}
const data4 = data3   // 新栈旧堆

/*
栈		堆
data1:1		 地址1:{a:1,b:2}
data2:1
data3:地址1
data4:地址1
*/

这里要小心,原始类型直接开辟新的栈空间放数据,互不影响;对象类型也是开辟新的内存空间,但是放的是堆中地址,相互影响

浅拷贝

我们接着来看浅拷贝和对象赋值内存中的变化

赋值后生成一个新的对象也就是有新的【堆】地址,并且这个对象里有原对象的所有数据,其中原始类型拷贝值,引用/对象类型拷贝地址

const data1 = { a: 1, b: 2, c: [3, 4, 5] };
const data2 = data1

/*
# 普通赋值:新栈旧堆
栈		        堆
data1:地址1	地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址1

# 浅拷贝赋值:新栈新堆(大家重点看c的变化)
栈		        堆
data1:地址1	地址1: { a: 1, b: 2, c: [3, 4, 5] };
data2:地址2	地址2:  { a: 1, b: 2, c:地址1中的c   } 
*/

深拷贝

明确对象赋值和浅拷贝区别后,咱们再来看看深拷贝的内存是怎么发生变化的,期待吗哈哈(*❦ω❦)

赋值后生成一个新的对象也就是有新的【堆】地址,并且这个对象里有原对象的所有数据,完全独立互不影响

const data1 = { a: 1, b: 2, c: [3, 4, 5] };
const data2 = data1

/*
# 普通赋值:新栈旧堆
栈        	      堆
data1:地址1      地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址1

# 深拷贝赋值:新栈新堆(完全独立互不影响)
栈		   堆
data1:地址1   地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址2   地址2: { a: 1, b: 2, c: [3, 4, 5] }
*/

和浅拷贝的区别:浅拷贝里面有原对象的地址 存在修改相互影响,而深拷贝完全独立互补影响

特性

咱们来汇总下三者之间的特性吧

种类和原数据是否指向同一个对象地址数据原始类型数据引用类型
赋值同一个地址影响影响
浅拷贝新地址不影响影响
深拷贝新地址不影响不影响

浅拷贝

知道深浅拷贝相关概念后,如何实现浅拷贝呢?

通过下述几种方式都可以实现

  • 展开运算符

  • Object.assign(目标对象, 对象1, 对象2,...)

  • 函数库lodash的_.clone方法

  • Array.prototype.concat()

  • Array.prototype.slice()

<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
const obj1 = { a: 1, b: 2, c: [3, 4, 5] };

// 浅拷贝
// const obj2 = { ...obj1 };
// const obj2 = Object.assign({}, obj1);
// const obj2 = _.clone(obj1);

// 数据是原始类型互不影响
// console.log(obj1.a, obj2.a); // 1、1
// obj1.a = 11;
// console.log(obj1.a, obj2.a); // 11、1

// 数据是对象类型相互影响
console.log(obj1.c[0], obj2.c[0]); // 3、3
obj1.c[0] = 33;
console.log(obj1.c[0], obj2.c[0]); // 33、33
</script>

深拷贝

接着咱们来说深拷贝的实现方式

内置对象JSON

这种方式相对实战公司用的多一些,主要方便省事吧 但并不是最优解

const obj1 ={num:1,d: (new Date)}
const obj2 = JSON.parse(JSON.stringify(obj1))
{num: 1, d: '2021-10-15T05:08:09.012Z'}

对于部分谁类型,拷贝之后数据会丢失,例如日期对象拷贝后就会丢失 这个点也是面试官经常会追问的

lodash

通过lodash第三方模块的cloneDeep

<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
  const obj1 = {
    msg: 1,
    likes: ["吃饭", "睡觉", "挤痘痘"],
  };
  const obj2 = _.cloneDeep(obj1);

  console.log(obj1.likes[0]); // 1
  console.log(obj2.likes[0]); // 1

  obj1.likes[0] = 2;

  console.log(obj1.likes[0]); // 2
  console.log(obj2.likes[0]); // 深拷贝1、浅拷贝2
</script>

自己写递归

这种方式一般不会用,常出现在面试说lodash的cloneDeep原理,或者不用第三方的自己写如何实现

<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
  const obj1 = {
    msg: 1,
    likes: ["吃饭", "睡觉", "挤痘痘"],
  };
  const obj2 = deepCopy(obj1);

  console.log(obj1.likes[0]); // 1
  console.log(obj2.likes[0]); // 1

  obj1.likes[0] = 2;

  console.log(obj1.likes[0]); // 2
  console.log(obj2.likes[0]); // 深拷贝1、浅拷贝2

  function deepCopy(obj) {
    var result = Array.isArray(obj) ? [] : {};
    for (var key in obj) {
      if (typeof obj[key] === "object" && obj[key] !== null) {
        result[key] = deepCopy(obj[key]); //递归复制
      } else {
        result[key] = obj[key];
      }
    }
    return result;
  }
</script>

Facebook Immutable 推荐

这种方式可以有效规避传统深拷贝的内存占用,不过容易和es6中Set、Map数据结构搞混淆 本文不做详细解释,下述是大致代码大家可以参考

<script src="https://unpkg.com/immutable@3.8.2/dist/immutable.js"></script>
<script>
  // Immutable

  //   const a = { msg: "hello 1" };
  //   const b = a;
  //   console.log(a.msg, b.msg);
  //   b.msg = "hello 2";
  //   console.log(a.msg, b.msg); // 深  1 2、 浅 2 2

  // Map 是immutable中 用来标识对象的
  // List 是immutable中 用来标识数组的
  // 切记切记切记  千万别和es6的Map、Set数据结构搞混淆
  //   const a = Immutable.fromJS({ msg: "hello 1", msg2: 666 });

  //   console.log(a.getIn(["msg"]));
  //   console.log(a.getIn(["msg2"]));

  //   const b = a.setIn(["msg"], "hello 2");

  //   console.log(a.getIn(["msg"]));
  //   console.log(a.getIn(["msg2"]));
  //   console.log(b.getIn(["msg"]));
  //   console.log(b.getIn(["msg2"]));

  // ==========
  // ==========
  // ==========
  // ==========
  const a = Immutable.fromJS({
    num: 666,
    list: [
      { id: 1, title: "商品1", num: 1, price: 1.11 },
      { id: 2, title: "商品2", num: 2, price: 2.22 },
      { id: 3, title: "商品3", num: 3, price: 3.33 },
    ],
  });

  console.log(a.getIn(["list", 1, "num"])); // 2

  //   a.setIn(['list', 1, 'num'], 5)
  const b = a.updateIn(["list", 1, "num"], (oldData) => oldData + 1);

  console.log(a.getIn(["list", 1, "num"])); // 2
  console.log(b.getIn(["list", 1, "num"])); // 3
</script>

immer.js 推荐

immer 的作者同时也是 mobx 的作者。mobx 又像是把 Vue 的一套东西融合进了 React,已经在社区取得了不错的反响。immer 则是他在 immutable 方面所做的另一个实践。

本文主要讲深浅拷贝的视线方案,在这里就不详细介绍immer.js大家可以进一步查阅相关资料学习

好了 到这里咱们就讲完了 最后来说一下先关使用场景吧为啥面试频率如此之高,实战哪里又会遇到呢? 带着这个好奇怎么最后来学习下💪🏻ヾ(◍°∇°◍)ノ゙

场景

浅拷贝:

  • vue3 ref修改子组件数据 Object.assign(模型数据, 新数据)
  • vue/react移动端分页上拉合并数据 [...旧数据,...新数据] 等等

深拷贝:

  • vue2/react数据回显
  • react-redux 状态管理 等等

你实战工作哪些具体场景用到深浅拷贝了呢?

不防评论区告诉大家吧 ThanksO(∩_∩)O谢谢~