浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

文章首发于sau交流学习社区

一、赋值(Copy)

赋值是将某一数值或对象赋给某个变量的过程,分为:

1、基本数据类型:赋值,赋值之后两个变量互不影响

2、引用数据类型:赋**址**,两个变量具有相同的引用,指向同一个对象,相互之间有影响

对基本类型进行赋值操作,两个变量互不影响。

// saucxs
let a = "saucxs";
let b = a;
console.log(b);  // saucxsa = "change";
console.log(a);   // changeconsole.log(b);    // saucxs

对引用类型进行赋**址**操作,两个变量指向同一个对象,改变变量 a 之后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。

// saucxs
let a = {name: "saucxs",book: {title: "You Don't Know JS",price: "45"}
}
let b = a;
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

通常在开发中并不希望改变变量 a 之后会影响到变量 b,这时就需要用到浅拷贝和深拷贝。

 

二、浅拷贝(Shallow Copy)

1、什么是浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

 

 

上图中,`SourceObject` 是原对象,其中包含基本类型属性 `field1` 和引用类型属性 `refObj`。浅拷贝之后基本类型数据 `field2` 和 `filed1` 是不同属性,互不影响。但引用类型 `refObj` 仍然是同一个,改变之后会对另一个对象产生影响。

简单来说可以理解为浅拷贝只解决了第一层的问题,拷贝第一层的**基本类型值**,以及第一层的**引用类型地址**。

 

2、浅拷贝使用场景

2.1 Object.assign()

`Object.assign()` 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

有些文章说`Object.assign()` 是深拷贝,其实这是不正确的。

// saucxs
let a = {name: "saucxs",book: {title: "You Don't Know JS",price: "45"}
}
let b = Object.assign({}, a);
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

上面代码改变对象 a 之后,对象 b 的基本属性保持不变。但是当改变对象 a 中的对象 `book` 时,对象 b 相应的位置也发生了变化。

 

2.2 展开语法 `Spread`

// saucxs
let a = {name: "saucxs",book: {title: "You Don't Know JS",price: "45"}
}
let b = {...a};
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

 

2.3 Array.prototype.slice方法

slice不会改变原数组,`slice()` 方法返回一个新的数组对象,这一对象是一个由 `begin`和 `end`(不包括`end`)决定的原数组的**浅拷贝**。

// saucxs
let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]console.log(b);
//  ["1", [4, 3]]

可以看出,改变 `a[1]` 之后 `b[0]` 的值并没有发生变化,但改变 `a[2][0]` 之后,相应的 `b[1][0]` 的值也发生变化。

说明 `slice()` 方法是浅拷贝,相应的还有`concat`等,在工作中面对复杂数组结构要额外注意。

 

三、深拷贝(Deep Copy)

3.1 什么是深拷贝?

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

 

3.2 使用深拷贝的场景

3.2.1 JSON.parse(JSON.stringify(object))

// saucxs
let a = {name: "saucxs",book: {title: "You Don't Know JS",price: "45"}
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// } a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } console.log(b);
// {
// 	name: "saucxs",
// 	book: {title: "You Don't Know JS", price: "45"}
// }

完全改变变量 a 之后对 b 没有任何影响,这就是深拷贝的魔力。

我们看下对数组深拷贝效果如何。

// saucxs
let a = [0, "1", [2, 3]];
let b = JSON.parse(JSON.stringify( a.slice(1) ));
console.log(b);
// ["1", [2, 3]]a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]console.log(b);
//  ["1", [2, 3]]

对数组深拷贝之后,改变原数组不会影响到拷贝之后的数组。

但是该方法有以下几个问题:

(1)会忽略 `undefined`

(2)会忽略 `symbol`

(3)不能序列化函数

(4)不能解决循环引用的对象

(5)不能正确处理`new Date()`

(6)不能处理正则

 

其中(1)(2)(3) `undefined`、`symbol` 和函数这三种情况,会直接忽略。

// saucxs
let obj = {name: 'saucxs',a: undefined,b: Symbol('saucxs'),c: function() {}
}
console.log(obj);
// {
// 	name: "saucxs", 
// 	a: undefined, 
//  b: Symbol(saucxs), 
//  c: ƒ ()
// }let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "saucxs"}

 

其中(4)循环引用会报错

// saucxs
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

 

其中(5)* `new Date` 情况下,转换结果不正确。

// saucxs
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"

解决方法转成字符串或者时间戳就好了。

// saucxs
let date = (new Date()).valueOf();
// 1545620645915JSON.stringify(date);
// "1545620673267"JSON.parse(JSON.stringify(date));
// 1545620658688

 

其中(6)正则情况下

// saucxs
let obj = {name: "saucxs",a: /'123'/
}
console.log(obj);
// {name: "saucxs", a: /'123'/}let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "saucxs", a: {}}

 

PS:为什么会存在这些问题可以学习一下 JSON。

除了上面介绍的深拷贝方法,

常用的还有`jQuery.extend()` 和 `lodash.cloneDeep()`,后面文章会详细介绍源码实现。

 

四、总结

  和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
 赋值是  改变会使原数据一起改变 改变会使原数据一起改变
 浅拷贝否  改变不会使原数据一起改变 改变会使原数据一起改变
 深拷贝否  改变不会使原数据一起改变 改变不会使原数据一起改变

 

五、参考

1、深拷贝和浅拷贝

2、MDN之Object.assign()

3、MDN之展开语法

4、MDN之Array.stypetype.slice()

转载于:https://www.cnblogs.com/chengxs/p/10788442.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386924.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RUNOOB python练习题27 递归逆向输出字符串

用来练手的python练习题,原题链接: python练习实例27 题干: 利用递归函数调用方式,将所输入的5个字符,以相反顺序打印出来。 实际要实现这个功能很简单,我们只需要反向遍历字符串即可。源代码如下: def p_inverse_boucle(my_st…

ELK 6.2.4搭建

开源实时日志分析ELK平台能够完美的解决我们上述的问题,ELK由ElasticSearch、Logstash和Kiabana三个开源工具组成。 官方网站:https://www.elastic.co/products Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式&#xff…

RUNNOOB python练习题 28 递归 数列

用来练手的python练习题其28,原题链接:python练习实例28 题干: 有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比…

RUNOOB python练习题29

用来练手的python练习题其29,原题链接:python练习实例29 题干 : 给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字。 实际这个正整数无论位数,在python3中都很容易实现。源代码如下: def ent…

定时任务(Spring Cloud Task)

引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.sprin…

服务被人当肉鸡了,叫一路赚钱 xig

网上看了一下&#xff0c;说有专门人研究服务 个人怀疑是阿里云内部人干的&#xff0c;因为买了服务器后&#xff0c;没有安装对外使用的地址性质的网站&#xff0c;IP开通了之后只有阿里的人知道&#xff0c;上面还有阿里云盾。 看了下进程地址&#xff0c;上面的启动命令 x…

RUNOOB python练习题30 回文数

用来练手的python练习题 30。原题链接:python练习实例30 题干 : 一个5位数&#xff0c;判断它是不是回文数。即12321是回文数&#xff0c;个位与万位相同&#xff0c;十位与千位相同。 与上一个例题类似&#xff0c;判断一个数是不是回文数&#xff0c;我们使用字符串类型更加…

高并发与负载均衡-keepalived-概念介绍

keepalived是用户空间的程序&#xff0c;这个程序会同时在主的lvs和备用的lvs启动 转载于:https://www.cnblogs.com/LXL616/p/10793790.html

RUNOOB python练习题31 根据已输入的字符判断星期几

用来练手的python练习题31&#xff0c; 原题链接 : python练习实例31 题干 : 请输入星期几的第一个字母来判断一下是星期几&#xff0c;如果第一个字母一样&#xff0c;则继续判断第二个字母。 一个条件语句练习题&#xff0c;非常简单了可以说&#xff0c;就是把所有的条件都…

解决FTPClient上传文件为空,显示0字节

JAVA使用FTPClient上传文件时总是为空&#xff0c;而使用FileZilla客户端时却不会。 后来查了下资料&#xff0c;FTP服务器有被动模式和主动模式。&#xff08;具体查另外资料&#xff09; 在JAVA中将FTPClient设置为被动模式即可解决问题。 import org.apache.commons.net.f…

软件工程——结对编程第二次作业

目录 1. 题目及要求2. 功能的设计3. GUI&#xff08;图形用户界面&#xff09;的设计4. 容错机制的设计4.1 选择运算符的容错处理4.2 最大值和题目数输入的容错处理4.3 打开文件容错处理4.4 打印的容错处理5. 程序的运行效果6. 对领航员的评价7. 总结本次作业所开发的程序已上传…

RUNOOB python练习题 32 列表的中括号符号小tips

用来练手的python练习题&#xff0c;原题链接: python练习实例32 题干: 按相反的顺序输出列表的值 拿到题目首先写下如下代码: a [1,2,3,4] for i in range(len(a)):print(a[len(a)-i-1])输出结果如下: 使用一个简单的循环就可以完成这个操作。但其实python有利用中括号操…

Anaconda安装第三方包(whl文件)

先说下环境 Anaconda 对应Python3.5的版本 win7,64位系统。 step1&#xff1a;下载whl文件 step2&#xff1a;打开‘Anaconda Command Prompt‘&#xff0c; 如下图&#xff1a; step3&#xff1a;命令行窗口pip安装&#xff0c;代码如下&#xff1a; pip install 路径whl…

RUNOOB python练习题33 使用join方法实现用逗号分隔列表

用来练手的python练习题&#xff0c;原题链接:python练习实例33 题干: 按逗号分隔列表 用逗号分隔列表&#xff0c;我们就想到了join方法。 str.join(sequence)可以用自定的str字符串分隔一个序列&#xff0c;这个序列可以是字符串&#xff0c;列表&#xff0c;元组&#xff…

Use Vim as a Python IDE

Use Vim as a Python IDE I love vim and often use it to write Python code. Here are some useful plugins and tools for building a delightful vim python environment, escpecially for Vim8: 我喜欢vim&#xff0c;经常用它来编写Python代码。以下是一些有用的插件和工…

RUNOOB python练习题 35 python print各色字体及背景

用来练手的python练习题&#xff0c;原题链接: python练习实例35 题干: 文本颜色设置 python中通过指令可以控制输出的背景颜色&#xff0c;前景颜色&#xff0c;以及显示方式。指令的语法如下: ’\033[显示方式&#xff1b;前景色&#xff1b;背景色m 输出字符 \033[0m’ 其…

for else语句小tips : RUNOOB python练习题36

用来练手的python练习题&#xff0c;原题链接: python练习实例36 题干: 求100之内的素数 求某个范围内的素数&#xff0c;和之前的一个例题其实是一样的&#xff0c;上次的同类例题链接如下: python练习实例12 在实现题目要求时&#xff0c;这次用了for else语句&#xff0c…

RUNOOB python练习题37 对一个序列的数进行排序

用来练手的Python练习题&#xff0c;原题链接: python练习实例37 题干: 对10个数进行排序 在我们使用Numpy模块时&#xff0c;这个问题是非常简单的&#xff0c;下面放出降序排列和升序排列的代码: 升序排列 import numpy as npresult np.zeros(10) for i in range(result…

逆向学习-IDApython(一)

背景 IDAPython 由三个分离的模块组成,他们分别是 idc,idautils 和 idaapi。 idc(注意大小写,不是 IDA 中的 IDC)是一个封装了 IDA 的 IDC 的兼容性模块,idautils 是 IDA 的高级实用功能模块,idaapi 允许了我们访问更加底层的数据。 基本操作 idc.Screen或者here()返回此时的地…

RUNOOB python练习题 39 数组排序

用来练手的python练习题&#xff0c;原题链接: python练习实例39 题干: 有一个已经排好序的数组。现输入一个数&#xff0c;要求按原来的规律将它插入数组中。 这个题目用列表List的sort&#xff0c;append方法非常简单&#xff0c;如下: 想要升序排列的话&#xff0c;就直接…