JavaScript的深浅复制

为什么有深复制、浅复制?

JavaScript中有两种数据类型,基本数据类型如undefinednullbooleannumberstring,另一类是Object。简单数据类型只存储在内存中的栈区,复制的时候是值传递给新的索引。而复杂数据类型由栈区和堆区共同储存,栈区执行同样的操作,只是把堆地址复制了一份,而真实数据在堆区中依然只有一份。

为了不影响原有数据,那么我们就新建一个对象,遍历原有对象的属性赋值到新属性。

let newObj = {}
for (let prop in obj) {
newObj[prop] = obj[prop]
}

上面这个循环也可以用Object.assign({}, obj);来实现。

这样做是否解决问题?未必,因为Object中可以嵌套Object,如果原有对象属性中有复杂数据类型,那么新的对象中也只能得到一个地址。这种情况被称为浅复制。我们希望能将对象中的对象,无论多少层,都能复制一份,能达到这种效果的,称为深复制

深复制的几种方法

首先假设有数据

let obj = {
a: 23,
b: [0, 1, [2, 3], function() {console.log('in array')}, undefined],
c: {k: 'value'},
d: function() {console.log('a')}
}

JSON.parse(JSON.stringify(obj))

let newObj = JSON.parse(JSON.stringify(obj))
newObj.newKey = 'newValue'
console.log(obj)
console.log(newObj)

如果处理对象只是简单的键值对,这个方法效果不错。

这个方法的缺点

  • 无法复制函数
  • 忽略undefined
  • 无法处理Set、Map、Symbol类型(即使用上repalce参数)
  • 原有的原型链会消失
  • 循环引用的对象会报错

递归法

因为要处理属性的值也是Object这种情况,自然可以想到递归这种处理方法。

function deepCopy(oldObj, newObj) {
let obj = newObj || {}
for (let i in oldObj) {
if (oldObj[i] === obj) { // 防止循环引用
continue
} if (typeof oldObj[i] === 'object') {
// obj[i] = (oldObj[i].constructor === Array) ? [] : {}
obj[i] = oldObj[i] instanceof Array ? [] : {}
deepCopy(oldObj[i], obj[i])
} else {
obj[i] = oldObj[i]
}
}
return obj;
}

这样就能处理一个嵌套了Object和Array等复杂变量的对象。但是对象中还可能包含Date和RegExp对象、Set、Map……

Lodash库

const lodash = require('lodash')
let newObj = lodash.cloneDeep(obj)

数组的复制

let arr = [1, 2, 3, [4, 5], {a: 1}]

let copy = arr
copy.push(6) let copy1 = [...arr]
copy1.push(999) let copy2 = Array.from(arr)
copy2.push(888) let copy3 = arr.slice()
copy3.push(777) // 以上方法都是浅拷贝
arr[4].a = 2 console.log(arr) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6 ]
console.log(copy1) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6, 999 ]
console.log(copy2) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6, 888 ]
console.log(copy3) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6, 777 ]

参考连接

  1. 摸索 JS 内深拷贝的最佳实践 - 简单易懂的前端角 - SegmentFault 思否
  2. 理解JavaScript:不可变的原始值与可变的对象引用
  3. [ JS 进阶 ] 基本类型 引用类型 简单赋值 对象引用 - kraaas前端博客 - SegmentFault 思否
  4. 什么是js深拷贝和浅拷贝及其实现方式
  5. js 深拷贝 vs 浅拷贝 - 掘金
  6. 深拷贝的终极探索(99%的人都不知道) - 颜海镜 - SegmentFault 思否

JavaScript的深浅复制的更多相关文章

  1. 详谈OC(object-c)深浅复制/拷贝-什么情况下用retain和copy

    读前小提示:对于深浅复制有一个清楚的了解,对于学习oc的朋友来说,至关重要.那么首先,我们要明白深浅复制是如何定义的呢.这里为了便于朋友们理解,定义如下. 浅 复 制:在复制操作时,对于被复制的对象的 ...

  2. OC-深浅复制

    [OC学习-26]对象的浅拷贝和深拷贝——关键在于属性是否可被拷贝 对象的拷贝分为浅拷贝和深拷贝, 浅拷贝就是只拷贝对象,但是属性不拷贝,拷贝出来的对象和原来的对象共用属性,即指向同一个属性地址. 深 ...

  3. Objective-c 深浅复制

    深浅复制的定义: 浅复制:在复制时,对于被复制对象的每一层都是指针复制. 深复制:在复制时,对于被复制的对象至少有一层是对象复制. 完全复制:在复制时,对于被复制对象的每一层都是完全复制. retai ...

  4. Python基础之列表深浅复制和列表推导式

    一.列表深浅复制: 浅拷贝内存图如下: 深拷贝内存图如下: 二.列表推导式: 实例: """ 列表推导式 练习:exercise01 """ ...

  5. C# 深浅复制 MemberwiseClone(转载)

    最近拜读了大话设计模式:原型模式,该模式主要应用C# 深浅复制来实现的!关于深浅复制大家可参考MSDN:https://docs.microsoft.com/zh-cn/dotnet/api/syst ...

  6. C# 深浅复制 MemberwiseClone

    学无止境,精益求精 十年河东,十年河西,莫欺少年穷 学历代表你的过去,能力代表你的现在,学习代表你的将来 最近拜读了大话设计模式:原型模式,该模式主要应用C# 深浅复制来实现的!关于深浅复制大家可参考 ...

  7. 潭州课堂25班:Ph201805201 第五课:格式化输出和深浅复制 (课堂笔记)

    格式化输出和字符串转义 占位符 使用示意 作用 %s '%s %s' % ('hello', 'world') 表示占位的是str %d '%d %d' % (1, 2) 表示占位的是int %d ' ...

  8. C#设计模式(6)——原型模式(Prototype Pattern) C# 深浅复制 MemberwiseClone

    C#设计模式(6)——原型模式(Prototype Pattern)   一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创 ...

  9. 【Python初级】由判定回文数想到的,关于深浅复制,以及字符串反转的问题

    尝试用Python实现可以说是一个很经典的问题,判断回文数. 让我们再来看看回文数是怎么定义的: 回数是指从左向右读和从右向左读都是一样的数,例如1,121,909,666等 解决这个问题的思路,可以 ...

随机推荐

  1. 第10组 Alpha冲刺(4/6)

    链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 学习调用中国天气网API,接近实现天气推送功能 对天气推送的形式进行讨论及重确 ...

  2. sip user Authentication and 401

    https://www.vocal.com/sip-2/sip-user-authentication/ https://tools.ietf.org/html/rfc3261 SIP User Au ...

  3. Android系统如何移植wpa_supplicant及wifi驱动

    一.WPA_SUPPLICANT简介 1. 什么是wpa_supplicant wpa_supplicant is a WPA Supplicant for Linux, BSD, Mac OS X, ...

  4. class文件格式版本号

    major version 52:jdk 8, major version 51:jdk 7, major version 50:jdk 6, major version 49:jdk 5, majo ...

  5. 为什么printf()用%f输出double型,而scanf却用%lf呢?

    转:https://blog.csdn.net/bat67/article/details/52056057 示例:double x:scanf(“%f”,&x):输入“123.4”,输出x的 ...

  6. Springboot单元测试Junit深度实践

    Springboot单元测试Junit深度实践 前言 单元测试的好处估计大家也都知道了,但是大家可以发现在国内IT公司中真正推行单测的很少很少,一些大厂大部分也只是在核心产品推广单测来保障质量,今天这 ...

  7. (2)PyCharm开发Flash项目之蓝图构建

    下面通过在PyCharm开发工具中创建一个简单的Flask项目来体会一下Flask的蓝图构建(Blueprint). 何谓蓝图:在Flask中蓝图就在大型应用中,将不同功能的模块(module)分开管 ...

  8. python问题集

    1.selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in P ...

  9. Swift编码总结6

    1.UILabel的minimumScaleFactor: 需要UIlabel根据字数多少来减小字体大小,使得UIlabel能够显示全所有的文字.你需要做的就是设置minimumScaleFactor ...

  10. 寄存器vs缓存vs硬盘

    对于多核cpu来说(一个处理器cpu上有多个核),L1/L2是各个核独自的,L3是多个核共享的 如下配置:一个处理器cpu,六个核.处理器速度为2.2GHz即电流每秒钟可以振荡22亿次.二级缓存256 ...