浅析JavaScript解析赋值、浅拷贝和深拷贝的区别
文章首发于sau交流学习社区
一、赋值(Copy)
赋值是将某一数值或对象赋给某个变量的过程,分为:
1、基本数据类型:赋值,赋值之后两个变量互不影响
2、引用数据类型:赋**址**,两个变量具有相同的引用,指向同一个对象,相互之间有影响
对基本类型进行赋值操作,两个变量互不影响。
// saucxs
let a = "saucxs";
let b = a;
console.log(b); // saucxs
a = "change";
console.log(a); // change
console.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();
// 1545620645915
JSON.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、深拷贝和浅拷贝
3、MDN之展开语法
浅析JavaScript解析赋值、浅拷贝和深拷贝的区别的更多相关文章
- Python FAQ2:赋值、浅拷贝、深拷贝的区别?
在Python编程过程中,经常会遇到对象的拷贝,如果不理解浅拷贝和深拷贝的概念,你的代码就可能出现一些问题.所以,在这里按个人的理解谈谈它们之间的区别. 一.赋值(assignment) 在<P ...
- 对Python中列表和数组的赋值,浅拷贝和深拷贝的实例讲解
引用:https://www.jb51.net/article/142775.htm 列表赋值: 1 2 3 4 5 6 7 >>> a = [1, 2, 3] >>&g ...
- C++浅拷贝和深拷贝的区别
C++浅拷贝和深拷贝的区别 2012-04-24 21:22 11454人阅读 评论(6) 收藏 举报 c++deleteclass编译器c c++默认的拷贝构造函数是浅拷贝 浅拷贝就是对象的数据成员 ...
- javascript中的浅拷贝和深拷贝(拷贝引用和拷贝实例)
作者:千锋教育链接:https://www.zhihu.com/question/23031215/answer/326129003来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- Javascript中的浅拷贝和深拷贝
很多开发语言中都有浅拷贝和深拷贝的说法,这里简单区分一下它们在Javascript中的区别,以及jQuery中深拷贝的实现. 在谈浅拷贝和深拷贝之前,先要屡清楚Javascript中的按值访问和按引用 ...
- 关于python中赋值、浅拷贝、深拷贝之间区别的深入分析
当重新学习了计算机基础课程<数据结构和算法分析>后再来看这篇自己以前写的博文,发现错误百出.python内置数据类型之所以会有这些特性,归根结底是它采用的是传递内存地址的方式,而不是传递真 ...
- JavaScript中浅拷贝和深拷贝的区别和实现
深拷贝和浅拷贝的区别 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存: 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共 ...
- Javascript/js 的浅拷贝与深拷贝(复制)学习随笔
js变量的数据类型值分基本类型值和引用类型值. 在ES6(ECMAScript6)以前,基本数据类型包括String.Number.Boolean.Undefined.Null. 基本类型值的复制(拷 ...
- Python中赋值、浅拷贝和深拷贝的区别
前言文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http: ...
随机推荐
- 在 Ubuntu 系统中部署 Git Server
http://blog.csdn.NET/poisonchry/article/details/11849781 虽然有很多开源的Git仓库,不过并非所有都尽人意,譬如Github,Gitlab等,不 ...
- Grunt的配置和使用
Grunt和Grunt插件是通过NodeJs的包管理工具npm安装并进行管理的. Grunt 0.4.x必须配合NodeJs=>0.8.0版本使用(奇数版本的NodeJs不是稳定的开发版本) ...
- JavaScipt浅谈——全局变量和局部变量
全局变量的作用域为所属的整个程序. 全局变量的定义形式有: (1)在函数外定义 (2)在函数内定义,但不加var声明 (3)使用 window.变量名 的形式定义 (4) ...
- Hystrix 熔断机制原理
相关配置 circuitBreaker.enabled 是否开启熔断 circuitBreaker.requestVolumeThreshold 熔断最低触发请求数阈值 circuitBreaker. ...
- leetCode刷题(找到最长的回文字符串)
Given a string, find the length of the longest substring without repeating characters. Examples: Giv ...
- JSON-RPC远程调用协议
1. JSON-RPC简介 2. 请求 3. 响应 4. 错误 4.1. 错误对象 4.2. 错误码 5. 批量调用 6. 示例 6.1. 列表形式参数 6.2. key-value形式参数 6.3. ...
- seek()对中文偏移测试
当前目录下创建"中文测试.txt"文件,写入: 我是大好人aaa我是大坏人bbb f = open('中文测试.txt', 'r+', encoding='utf-8') # f. ...
- Redis未授权访问
最近在做校招题目的时候发现有问到未授权访问,特此搭建了诸多未授权访问的环境并且一一复现并做简单总结.再次记录下来 环境介绍 0x00环境搭建 我这里用到的是Microsoft(R) Windows(R ...
- 关于dropout的有趣的进化论解释
训练神经网络时,使用dropout技术来防止网络的过拟合.我们这里且不谈这个技术的细节,但就这项技术的有趣的生物进化论解释了解下.自然界的高等生物进化出了两性繁殖,其原因可以解释为使得变异的基因能散播 ...
- UUID那些事
UUID那些事 UUID 是一个全局唯一的通用识别码.它使用某种规则,而不是某种中心化的自增方式,来保证这个识别码的全局唯一性.UUID 有非常多的使用场景,比如在分布式系统中,需要生成全局唯一 ID ...