JS复习之深浅拷贝
一、复习导论(数据类型相关)
想掌握JS的深浅拷贝,首先来回顾一下JS的数据类型,JS中数据类型分为基本数据类型和引用数据类型。
基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问。包含Number、String、Boolean、null、undefined 、Symbol、bigInt。
引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针,这个指针指向堆内存中的引用地址。除了上面的 7 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等
由于基本数据类型和引用数据类型存储方式的差异,所以我们在进行复制变量时,基本数据类型复制后会产生两个独立不会互相影响的变量,而引用数据类型复制时,实际上是将这个引用类型在栈内存中的引用地址复制了一份给新的变量,其实就是一个指针。因此当操作结束后,这两个变量实际上指向的是同一个在堆内存中的对象,改变其中任意一个对象,另一个对象也会跟着改变。于是在引用数据类型的复制过程中便出现了深浅拷贝的概念。
二、深浅拷贝的区别
浅拷贝,对于目标对象第一层为基本数据类型的数据,就是直接赋值,即传值;而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即传地址,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。
深拷贝,则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
三、浅拷贝的实现方式
1.对象的浅拷贝
(1)Object.assign()
ES6中新增的方法,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),详细用法传送门。代码示例:
let obj = {
a:1,
b:{
m:'2',
n:'3'
},
c:[1,2,3,4,5,6]
} let copyObj = Object.assign({},obj)
obj.a = 5
obj.b.m = '222' console.log(copyObj) //{ a: 1, b: { m: '222', n: '3' }, c: [ 1, 2, 3, 4, 5, 6 ] }
上面的代码修改了obj内部a的值和b.m的值,但是在复制出来的对象中,a的值并未改变,m的值改变了。所以Object.assign()复制时遇到基本数据类型时直接复制值,但是遇到引用数据类型仍然复制的是地址,严格来讲属于浅拷贝。
(2)循环遍历
let obj = {
a:1,
b:{
m:'2',
n:'3'
},
c:[1,2,3,4,5,6]
} let copyObj = {}
for(var k in obj){
copyObj[k] = obj[k]
} copyObj.a = 5
copyObj.b.m = '333' console.log(obj) //{ a: 1, b: { m: '333', n: '3' }, c: [ 1, 2, 3, 4, 5, 6 ] }
console.log(copyObj) //{ a: 5, b: { m: '333', n: '3' }, c: [ 1, 2, 3, 4, 5, 6 ] }
2.数组的浅拷贝
Array.concat()、Array.slice(0)、 Array.from()、拓展运算符(...)
let arr = [1,2,3,4,[5,6]]
let copyArr = arr.concat() //arr.slice(0) 、Array.from()、[...arr]
copyArr[0] = '555'
copyArr[4][1] = 7 console.log(arr) //[ 1, 2, 3, 4, [ 5, 7 ] ]
console.log(copyArr) //[ '555', 2, 3, 4, [ 5, 7 ] ]
四、深拷贝的实现方式
1.JSON.parse()和JSON.stringify()
const obj1 = {
x: 1,
y: {
m: 1
}
};
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}} 原对象未改变
console.log(obj2) //{x: 2, y: {m: 2}}
这种方法使用较为简单,可以满足基本日常的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是有以下几个缺点:
(1)undefined、任意的函数、正则表达式类型以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);
(2) 它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
(3) 对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)
(4) 如果对象中存在循环引用的情况无法正确处理。
//忽略undefined、symbol 和函数
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 //正则情况下
let obj = {
name: "muyiy",
a: /'123'/
}
console.log(obj);
// {name: "muyiy", a: /'123'/} let b = JSON.parse(JSON.stringify(obj));
console.log(b);
2.jQuery.extend()
附上源码解析:
jQuery.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法
var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
//以上其中的变量:options是一个缓存变量,用来缓存arguments[i],name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的value。
//copy传入对象上每个key对应的value,copyIsArray判定copy是否为一个数组,clone深拷贝中用来临时存对象或数组的src。 // 处理深拷贝的情况
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
//跳过布尔值和目标
i++;
} // 控制当target不是object或者function的情况
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
} // 当参数列表长度等于i的时候,扩展jQuery对象自身。
if (length === i) {
target = this; --i;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
// 扩展基础对象
for (name in options) {
src = target[name];
copy = options[name]; // 防止永无止境的循环,这里举个例子,
// 如 var a = {name : b};
// var b = {name : a}
// var c = $.extend(a, b);
// console.log(c);
// 如果没有这个判断变成可以无限展开的对象
// 加上这句判断结果是 {name: undefined}
if (target === copy) {
continue;
}
if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是数组的话就让clone副本等于src否则等于空数组。
} else {
clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是对象的话就让clone副本等于src否则等于空数组。
}
// 递归拷贝
target[name] = jQuery.extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。
}
}
}
}
// 返回修改的对象
return target;
};
jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用。
3.lodash.cloneDeep()
已经有大佬专门写了lodash的源码解析(传送门)
4.自己实现一个深拷贝
function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
参考文档:https://segmentfault.com/a/1190000015042902
https://github.com/yygmind/blog/issues/29
JS复习之深浅拷贝的更多相关文章
- js中的深浅拷贝
js中的深浅拷贝 js中有深拷贝.浅拷贝一说,所谓的深浅拷贝是针对value类型为引用类型(函数.对象.数组)而言的,大概理解的就是: 浅拷贝: 拷贝出的对象c和原始对象o,c和o在key对应的val ...
- 【 js 基础 】 深浅拷贝
underscore的源码中,有很多地方用到了 Array.prototype.slice() 方法,但是并没有传参,实际上只是为了返回数组的副本,例如 underscore 中 clone 的方法: ...
- js对象的深浅拷贝
JS数据类型可以分为(ES5,暂时不考虑ES6): 简单数据类型:Number.String.undefined.boolean 复杂数据类型:Object.Array 简单的数据类型,往往是赋值操作 ...
- Javascript 中的深浅拷贝
工作中经常会遇到需要复制 JS 数据的时候,遇到 bug 时实在令人头疼:面试中也经常会被问到如何实现一个数据的深浅拷贝,但是你对其中的原理清晰吗?一起来看一下吧! 为什么会有深浅拷贝 想要更加透彻的 ...
- 【 js 基础 】【 源码学习 】 深浅拷贝
underscore的源码中,有很多地方用到了 Array.prototype.slice() 方法,但是并没有传参,实际上只是为了返回数组的副本,例如 underscore 中 clone 的方法: ...
- JS中深浅拷贝 函数封装代码
一.了解 基本数据类型保存在栈内存中,按值访问,引用数据类型保存在堆内存中,按址访问. 二.浅拷贝 浅拷贝只是复制了指向某个对象的指针,而不是复制对象本身,新旧对象其实是同一内存地址的数据,修改其中一 ...
- js 基础数据类型和引用类型 ,深浅拷贝问题,以及内存分配问题
js 深浅拷贝问题 浅拷贝一般指的是基本类型的复制 深拷贝一般指引用类型的拷贝,把引用类型的值也拷贝出来 举例 h5的sessionStorage只能存放字符串,所以要存储json时就要把json使用 ...
- js 深浅拷贝 笔记总结
一.js 数据类型 javaScritp的数据类型有:数值类型.字符串类型.布尔类型.null.undefined.对象(数组.正则表达式.日期.函数),大致分成两种:基本数据类型和引用数据类型, 其 ...
- JS对象和数组深浅拷贝总结②
在实际开发中遇到过太多次深拷贝浅拷贝的问题.总结一下~ JS数据存储和深浅拷贝实际运用① 这是之前写过的一篇文章,解决浅拷贝深拷贝的问题只说了一种方法,今天来补充一下. 介绍深拷贝和浅拷贝都在上一篇文 ...
随机推荐
- Python_网络编程_socket()
什么是 Socket? Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯. 详细资 ...
- NUC972当检测到sd卡时,在sd卡驱动中操作gpio开启sd卡的电源,解决sd卡因低电压有时识别不正常的问题
1.根据硬件原理图,找到对应控制sd卡电源的gpio引脚,并在sd卡驱动文件中定义操作改该引脚的宏 2.在sd卡检测函数中,使用glib增加开sd卡电源的操作,如此当sd卡每次被检测到时,驱动中就会自 ...
- 工作三年终于社招进字节跳动!字节跳动,阿里,腾讯Java岗面试经验汇总
前言 我大概我是从去年12月份开始看书学习,到今年的6月份,一直学到看大家的面经基本上百分之90以上都会,我就在5月份开始投简历,边面试边补充基础知识等.也是有些辛苦.终于是在前不久拿到了字节跳动的o ...
- 如何清理MacBook电脑缓存?
使用Mac电脑的用户都知道,Mac其空间硬盘大多数都是只有128G以及256G的规格,而用惯了win系统基本都是超过512G的硬盘的用户,转手来使用Mac电脑,就不得不接受这一硬伤问题. 用户往往再怎 ...
- Requests 库的使用
Python 的标准库 urllib 提供了大部分 HTTP 功能,但使用起来较繁琐.通常,我们会使用另外一个优秀的第三方库:Requests,它的标语是:Requests: HTTP for Hum ...
- 宝塔Linux面板安装
宝塔linux6.0版本是基于centos7开发的,务必使用centos7.x 系统 提示:Centos官方已宣布在2020年停止对Centos6的维护更新,各大软件开发商也逐渐停止对Centos6的 ...
- 牛客练习赛67 D牛妹爱数列 题解(dp)
题目链接 题目大意 给你一个长为n的01串,要你进行最少的操作使得这01串变成全为0,求最少操作次数 有两种不同类型的操作 1:翻转一个前缀 2:单调翻转一个元素 题目思路 居然是一个dp,标程讲的很 ...
- Java反射说得透彻一些
目录 一.反射机制是什么? 二.反射的具体使用 2.1 获取对象的包名以及类名 2.2 获取Class对象 2.3 getInstance()获取指定类型的实例化对象 2.4 通过构造函数对象实例化对 ...
- 基于混沌Logistic加密算法的图片加密与还原
摘要 一种基于混沌Logistic加密算法的图片加密与还原的方法,并利用Lena图和Baboon图来验证这种加密算法的加密效果.为了能够体现该算法在图片信息加密的效果,本文还采用了普通行列置乱加密算法 ...
- MySQL制作具有千万条测试数据的测试库
有时候需要制造一些测试的数据,以mysql官方给的测试库为基础,插入十万,百万或者千万条数据.利用一些函数和存储过程来完成. 官方给的测试库地址:https://github.com/datachar ...