参考:

  1. 【进阶4-1期】详细解析赋值、浅拷贝和深拷贝的区别
  2. How to differentiate between deep and shallow copies in JavaScript

在编程语言中,数据或者值是存放在变量中的。拷贝的意思就是使用相同的值创建新的变量。

当我们改变拷贝的东西时,我们不希望原来的东西也发生改变。

深拷贝的意思是这个新变量里的值都是从原来的变量中复制而来,并且和原来的变量没有关联。

浅拷贝的意思是,新变量中存在一些仍然与原来的变量有关联的值。

JavaScript 数据类型

原始数据类型 (有的资料叫做基本数据类型):数字、字符串、布尔值、undefined、null

这些值被赋值后就和对应的变量绑定在一起。如果你拷贝这些变量,就是实实在在的拷贝。

b = a 就是一次拷贝,重新给 b 赋值,a 的值不会改变:

const a = 5
let b = a // this is the copy b = 6 console.log(b) //
console.log(a) //

复合数据类型(有的资料叫做引用数据类型)——对象 和 数组

技术上讲,数组也是对象。

这种类型的值,只在初始化的时候存储一次。赋值给变量也仅仅是创建了一个指向这个值的引用。

拷贝 b = a,改变 b 中的属性 pt 的值,a 中包含的 pt 的值也改变了,因为 a 和 b 实际上指向的是同一个对象:

const a = {
en: 'Hello',
de: 'Hallo',
es: 'Hola',
pt: 'Olà'
}
let b = a
b.pt = 'Oi'
console.log(b.pt) // Oi
console.log(a.pt) // Oi

上面这个例子就是一个浅拷贝

新的对象有着原对象属性的一份精确拷贝。如果属性值是原始类型,拷贝的就是原始类型值,如果属性是引用类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址或者改变了内存中的值,另一个对象的属性也会变化。

也就是说浅拷贝只拷贝了第一层的原始类型值,和第一层的引用类型地址。

浅拷贝的场景:

展开操作符 Spread operator

使用这个操作符可以将所有的属性值复制到新对象中。

const a = {
en: 'Bye',
de: 'Tschüss'
}
let b = {...a}
b.de = 'Ciao'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

还可以合并两个对象,比如 const c = { ...a, ...b}.

Object.assign()

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,然后返回目标对象。

第一个参数是被修改和最终返回的值,第二个参数是你要拷贝的对象。通常,只需要给第一个参数传入一个空对象,这样可以避免修改已有的数据。

const a = {
en: 'Bye',
de: 'Tschüss'
}
let b = Object.assign({}, a)
b.de = 'Ciao'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

拷贝数组

const a = [1,2,3]
let b = [...a]
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

数组方法——map, filter, reduce

这些方法都可以返回新的数组:

const a = [1,2,3]
let b = a.map(el => el)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

在拷贝的过程中修改特定的值:

const a = [1,2,3]
const b = a.map((el, index) => index === 1 ? 4 : el)
console.log(b[1]) // 4
console.log(a[1]) // 2

Array.slice

使用array.slice() 或者 array.slice(0) 你可以得到原数组的拷贝。

const a = [1,2,3]
let b = a.slice(0)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

嵌套对象或数组

就算使用了上面的方法,如果对象内部包含对象,那么内部嵌套的对象也不会被拷贝,因为它们只是引用。因此改变嵌套对象,所有的实例中的嵌套对象的属性都会被改变。所以说上面的场景全部都只实现了浅拷贝。

const a = {
foods: {
dinner: 'Pasta'
}
}
let b = {...a}
b.foods.dinner = 'Soup' // changes for both objects
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Soup

深拷贝

拷贝所有属性,并拷贝属性指向的动态分配的内存。深拷贝时对象和所引用的对象一起拷贝,相比浅拷贝速度较慢且花销大。拷贝对象和原对象互不影响。

对嵌套的对象进行深拷贝,一种方法是手动拷贝所有嵌套的对象。

const a = {
foods: {
dinner: 'Pasta'
}
}
let b = {foods: {...a.foods}}
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

如果对象除了 foods 之外还有很多属性,仍然可以利用展开操作符,比如

const b = {...a, foods: {...a.foods}}.

如果你不知道这个嵌套结构的深度,那么手动遍历这个对象然后拷贝每个嵌套的对象就很麻烦了。

一个很简单的方法就是使用 JSON.stringify 和 JSON.parse

const a = {
foods: {
dinner: 'Pasta'
}
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

但是这里要注意的是,你只能使用这种方法拷贝 JavaScript 原生的数据类型(非自定义数据类型)。

而且存在问题

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
// 木易杨
let obj = {
name: 'muyiy',
a: undefined,
b: Symbol('muyiy'),
c: function() {}
}
console.log(obj);
// {
// name: "muyiy",
// a: undefined,
// b: Symbol(muyiy),
// c: ƒ ()
// } let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy"}
// 木易杨
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a; let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

拷贝自定义类型的实例

你不能使用 JSON.stringify 和 JSON.parse 来拷贝自定义类型的数据,下面的例子使用一个自定义的 copy() 方法:

class Counter {
constructor() {
this.count = 5
}
copy() {
const copy = new Counter()
copy.count = this.count
return copy
}
}
const originalCounter = new Counter()
const copiedCounter = originalCounter.copy()
console.log(originalCounter.count) //
console.log(copiedCounter.count) //
copiedCounter.count = 7
console.log(originalCounter.count) //
console.log(copiedCounter.count) //

如果实例中有其它对象的引用,就要在copy方法中使用  JSON.stringify 和 JSON.parse 。

除此之外,深拷贝方法还有 jQuery.extend() 和 lodash.cloneDeep()

总结:

JavaScript 深拷贝(deep copy)和浅拷贝(shallow copy)的更多相关文章

  1. 深拷贝(deep clone)与浅拷贝(shallow clone)

    深拷贝(deep clone)与浅拷贝(shallow clone) 浅复制(浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复 ...

  2. copy&mutableCopy 浅拷贝(shallow copy)深拷贝 (deep copy)

    写在前面 其实看了这么多,总结一个结论: 拷贝的初衷的目的就是为了:修改原来的对象不能影响到拷贝出来得对象 && 修改拷贝出来的对象也不能影响到原来的对象 所以,如果原来对象就是imm ...

  3. 由Python的浅拷贝(shallow copy)和深拷贝(deep copy)引发的思考

    首先查看拷贝模块(copy)发现: >>> help(copy)Help on module copy:NAME    copy - Generic (shallow and dee ...

  4. 浅拷贝(Shallow Copy) VS 深拷贝(Deep Copy)

    首先,深拷贝和浅拷贝针对的是对象类型(对象,数组,函数) 浅拷贝指的是只是拷贝了对象的引用地址,彼此之间高耦合,一个改变,另一个可能也随之改变: 深拷贝是指只是完整的将变量的值拷贝过来,是一个新的对象 ...

  5. Shallow copy and Deep copy

    Shallow copy and Deep copy 第一部分: 一.来自wikipidia的解释: Shallow copy One method of copying an object is t ...

  6. Summary: Deep Copy vs. Shallow Copy vs. Lazy Copy

    Object copy An object copy is an action in computing where a data object has its attributes copied t ...

  7. python中的shallow copy 与 deep copy

    今天在写代码的时候遇到一个奇葩的问题,问题描述如下: 代码中声明了一个list,将list作为参数传入了function1()中,在function1()中对list进行了del()即删除了一个元素. ...

  8. javaScript 深拷贝、浅拷贝

    在 JS 中有一些基本类型像是Number.String.Boolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他 ...

  9. [CareerCup] 13.4 Depp Copy and Shallow Copy 深拷贝和浅拷贝

    13.4 What is the difference between deep copy and shallow copy? Explain how you would use each. 这道题问 ...

  10. python deep copy and shallow copy

    Python中对于对象的赋值都是引用,而不是拷贝对象(Assignment statements in Python do not copy objects, they create bindings ...

随机推荐

  1. 管理外部表(External Tables)

    Oracle数据库允许对外部表中的数据进行只读访问.外部表定义为不驻留在数据库中的表,并且可以是为其提供访问驱动程序的任何格式.通过为数据库提供描述外部表的元数据,数据库能够公开外部表中的数据,就好像 ...

  2. HDU - 1050

    wa了5遍?!! (1)前4遍,思路没简化,企图模拟整个过程,但是调用sort函数时由于没有把奇数的屋子和偶数的屋子统一,排序出了问题. 思路:遍历n段,每次只扫未被标记过的一段,ans++并且从该段 ...

  3. 正则表达式匹配日期,支持闰年,格式为YYYYMMDD

    年份:[\d]{4} 带31的月份:(0[13578]|1[02])((0[1-9])|([12][0-9])|(3[01])) 非2月不带31日的月份:((0[469])|11)((0[1-9])| ...

  4. Python爬取指定重量的快递价格

    目录 一.获取查询接口 二.获取相关数据 三.编写爬虫脚本 四.查看查询效果 背景:现在这个时代,快递横飞.我们想寄一个快递,给出的选择也是多种多样的(根据快递的大小.送达的时间.寄送的距离及价格.公 ...

  5. 项目中的Git七步精髓

    项目中Git常用的七步操作: 1.git branch -a  查看所有分支 2.git checkout dev_yxq 如果冲突了,操作回退上一个版本, git status git checko ...

  6. spring-cloud-eureka服务注册与发现

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...

  7. 【算法】Bert预训练源码阅读

    Bert预训练源码 主要代码 地址:https://github.com/google-research/bert create_pretraning_data.py:原始文件转换为训练数据格式 to ...

  8. urlparse解析URL参数

    python2 #! /usr/bin/env python # -*- coding:utf8 -*- # Author:zhangning import urlparse def url2Dict ...

  9. PHP微信商户支付 - 企业付款到零钱功能(即提现)技术资料汇总

    PHP实现微信开发中提现功能(企业付款到用户零钱) 一.实现该功能目的 这几天在小程序里要实现用户从系统中提现到零钱的功能,查了一下文档可以使用 企业付款到用户零钱 来实现: 官方文档:https:/ ...

  10. openssl私钥转换为PKCS8

    先装openssl 原来 -----BEGIN DSA PRIVATE KEY----- 转换后 -----BEGIN PRIVATE KEY----- openssl pkcs8 -topk8 -i ...