js库 - 浅拷贝 & 深拷贝

学了堆栈内存空间,应该就理解了什么叫简单数据类型存在栈内存,复杂数据类型存在堆内存了。

然后面试中经常会问、业务中也经常会遇到的问题就是深浅拷贝的问题了。

栈内存中简单数据类型直接拷贝就能得到一个副本,但是复杂数据类型的拷贝如果也想得到一个副本,就需要深拷贝了。

 

浅拷贝:

var  a = 1;

var b = a;

 

这就是浅拷贝了,虽然你视觉上看上去a = b;但是修改b的值,a不会收影响。因为b是a的一个副本,就像你拷贝了一个文件夹副本一样。修改副本,源文件夹不会受影响。

但这种拷贝情况只局限在简单类型的拷贝:

string、number、boolean、null、undefiend

如果你拷贝一个数组/对象(以数组为例):

var c = [1,2,3];

var d = c;

d.push(4);

console.log(c)

 

c也被改变了。

但是如果你改变d的整个数据,让他等于一个新的数组(甚至对象)c这时返倒不受影响了。

很奇怪很费解吧?

这是因为:

数组、对象这类复杂类型数据结构,在栈内存里存放的只是指向堆内存中存放数据的地址,

你直接d = c; 拷贝的也是一个副本,但这个副本区别之处是,他并非数据的副本,而是栈内存地址的副本。

这个副本d的地址改变,对c没有影响,而你给d重新指向一个新的数组,就是改变地址了。此时只有d指向了新数组[5,6,7],但是c不受影响,所以打印出来的不变。

 

但是虽然有两个地址,一个是原、一个是副本。可他们同时指向同一个堆内存的数据。

这样看来,你拷贝出来的d和c用的是同一个数组。

所以d.push执行以后,并不是c也跟着push了,而是c指向的数组和被d.push的是同一个数组。

你可以想象成,同一个房间,d和c都拿着钥匙,这俩钥匙是不同的,但是他们都是同一间屋子的钥匙。d用钥匙打开门往屋子里放了一个东西。那么c打开这个屋子,这个东西也还会在。

 

以上这些,就是基础知识的解读。

 

深拷贝

具体深拷贝就是要理解了复杂类型拷贝的缺点,然后再进行弥补。

既然想要复杂类型也像简单类型那样拷贝一个新数据的话,就不单单是拷贝地址了。而是要重新建立一个新数据存放空间,然后挨个把想要拷贝的东西搬进来才行。

 

所以拷贝前我们得判断一个数据他到底是什么类型的,好“对症下药”:

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
getType: function (target) {
  /*
    * @Author: guojufeng@
    * @Date: 2017-12-20 15:07:06
    * @purpose 获取一个值的类型
    * @param {variateName} target: 要获取类型的变量名或对象
    * @output {string} result || "null": 返回的参数 - result或者null,为字符串形式的
    */
  if (target === null) {
    console.log("getType类型判断为: " + target)
    return "null";
  }
  let result = typeof (target);
  if (result == "object") {
    if (target instanceof Array) {
      result = "array";
    } else if (target instanceof Object) {
      let target = Object.prototype.toString.call(target);
      if (target == "[object Object]") {
        result = "object";
      } else {
        result = target;//构造类
      }
    }
  }
  console.log("getType类型判断为: " + result)
  return result; //返回类型值的字符串形式
}

以上代码,判断一个值的类型,输入值本身,返回字符串形式的类型描述。

如果是简单类型,返回typeof方法返回的对应值即可。

这里特殊处理了null,因为他用typeof返回object。

然后对于复杂类型的数据,再深入判断其实array类型还是object类型。

对object类型中,还有构造类需要区分。直接返回[object String]这样类型的。但其实在深拷贝阶段,直接将其放到object形式处理了。

 

 

然后就是深拷贝的代码:

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
deepClone: function (origin) {
  /*
    * @Author: guojufeng@
    * @Date: 2018-10-30 20:48:44
    * @purpose 深度克隆
    * @param {variateName} origin: 要克隆的对象变量名
    * @output {对应值} 根据origin的类型返回的具体值
    */
  let type = this.getType(origin),
    target;
  if (type == "array") {
    target = [];
    /* 数组 */
    origin.forEach(el => {
      // console.log("ele",el)
      target.push(this.deepClone(el));
    });
  } else if (type == "object") {
    /* 对象 */
    target = {};
    for (const key in origin) {
      if (origin.hasOwnProperty(key)) {
        /* 注意,只拷贝元素身上的,而不拷贝其原型上的值 */
        const el = origin[key];
        target[key] = this.deepClone(el);
      }
    }
  } else if (type == "function") {
    /* 函数 */
    target = function () {};
    target = origin;
  } else {
    /* 原始值 */
    target = origin;
  }
  return target;
}

  

对于简单类型,直接进行拷贝

对于函数,新建一个function,然后拷贝

对于数组,新建一个数组,然后 forEach 遍历拷贝。如果循环过程中,数组中嵌套复杂类型,再次递归调用深拷贝方法。

对于对象,新建一个对象,然后for in遍历拷贝非原型值。如果循环过程中,对象中嵌套复杂类型,再次递归调用深拷贝方法。

以上,就是整个逻辑。

 

 

 

 

 

声明:

  请尊重博客园原创精神,转载或使用图片请注明:

  博主:xing.org1^

  出处:http://www.cnblogs.com/padding1015/

posted @   xing.org1^  阅读(865)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示