js深拷贝和浅拷贝的深入讲解
作者:Jimmy_fx
深拷贝和浅拷贝是面试中经常出现的,主要考察对基本类型和引用类型的理解深度,这篇文章主要给大家介绍了关于js深拷贝和浅拷贝的相关资料,需要的朋友可以参考下
浅拷贝
创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,会影响到另一个对象。
实现方法
方法一:Object.assign
es6 中 object 的一个方法,用于 JS 对象的合并等,返回目标对象。它不会拷贝对象的继承属性和不可枚举的属性
let target = {}; let source = {a:{b:1}}; Object.assign(target,source) console.log(taget) // {a:{b:1}} target.a.b = 2; console.log(taget) // {a:{b:2}} console.log(source) // {a:{b:2}}
方法二:扩展运算符方式
/* 对象的拷贝 */ let obj = {a:1,b:{c:1}} let obj2 = {...obj} obj.a = 2 console.log(obj) //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}} obj.b.c = 2 console.log(obj) //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}} /* 数组的拷贝 */ let arr = [1, 2, 3]; let newArr = [...arr];
方法三:concat和slice 浅拷贝数组
仅仅针对数组类型,都会返回一个新的数组对象。
concat 浅拷贝数组 let arr = [1, 2, 3]; let newArr = arr.concat(); newArr[1] = 100; console.log(arr); // [ 1, 2, 3 ] console.log(newArr); // [ 1, 100, 3 ] slice 浅拷贝数组 let arr = [1, 2, {val: 4}]; let newArr = arr.slice(); newArr[2].val = 1000; console.log(arr); //[ 1, 2, { val: 1000 } ]
深拷贝
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
实现方法
方法一:乞丐版(JSON.stringify和JSON.parse)
let obj1 = { a:1, b:[1,2,3] } let str = JSON.stringify(obj1); let obj2 = JSON.parse(str); console.log(obj2); //{a:1,b:[1,2,3]} obj1.a = 2; obj1.b.push(4); console.log(obj1); //{a:2,b:[1,2,3,4]} console.log(obj2); //{a:1,b:[1,2,3]}
缺陷:
- 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失
- 拷贝 Date 引用类型会变成字符串
- 无法拷贝不可枚举的属性
- 无法拷贝对象的原型链
- 拷贝 RegExp 引用类型会变成空对象
- 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null
手写递归实现
基础版
let obj = {a:{b:1}}; function deepClone(obj){ let cloneObj = Object.prototype.toString.call(obj) === "[object Array]" ? [] : {}; for(let key in obj){ if(typeof obj[key]=== "object" && obj !== null){ cloneObj[key] = deepClone(obj[key]) }else{ cloneObj[key] = obj[key] } } return cloneObj; } let obj2 = deepClone(obj); obj2.a.b = 33 console.log("obj",obj) // {a:{b:1}} console.log("obj2",obj2) // {a:{b:33}}
缺陷:
- 不能复制不可枚举的属性以及 Symbol 类型
- 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝
- 对象的属性里面成环,即循环引用没有解决
改进版
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null) const deepClone = function (obj, hash = new WeakMap()) { if (obj.constructor === Date) return new Date(obj) // 日期对象直接返回一个新的日期对象 if (obj.constructor === RegExp) return new RegExp(obj) //正则对象直接返回一个新的正则对象 //如果循环引用了就用 weakMap 来解决 if (hash.has(obj)) return hash.get(obj) let allDesc = Object.getOwnPropertyDescriptors(obj) //遍历传入参数所有键的特性 let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) //继承原型链 hash.set(obj, cloneObj) for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key] } return cloneObj }
手写深拷贝参考:
https://www.jb51.net/article/247540.htm
https://www.jb51.net/article/247544.htm
其他实现方法
lodash库的_.cloneDeep方法,jQuery.extend()方法
FAQ:赋值和深浅拷贝的区别
注意:前提都是针对引用类型
赋值
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
let a = {name:'jimmy',b:{age:12}} let b = a; b.name = 'chimmy'; b.b.age = 21; console.log(a) // {name:'chimmy',b:{age:21}} console.log(b) // {name:'chimmy',b:{age:21}}
浅拷贝
重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
let a = {name:'jimmy',b:{age:12}} let b = {...a}; b.name = 'chimmy'; b.b.age = 21; console.log(a) // {name:'jimmy',b:{age:21}} console.log(b) // {name:'chimmy',b:{age:21}}
深拷贝
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
let a = {name:'jimmy',b:{age:12}} let b = {...a}; b.name = 'chimmy'; b.b.age = 21; console.log(a) // {name:'jimmy',b:{age:12}} console.log(b) // {name:'chimmy',b:{age:21}}
总结
到此这篇关于js深拷贝和浅拷贝的文章就介绍到这了,更多相关js深拷贝和浅拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!