Open Samgao0312 opened 2 years ago
经常会听到有人说:
好吧......以免被别人鄙视,今天好好总结梳理下这部分知识。。
深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用类型的。如下图所示:
常见的浅拷贝方法有:
Object.assign() Es6的 ... 运算符。
Object.assign()
...
常见的深拷贝方法有:
JSON.parse(JSON.stringfy(目标对象))
我们先看一下下面这段代码:
let obj1 = { name: 'G-Dragon', age: 30, language: [1, [2, 3], [4, 5]] } let obj2 = obj1; obj2.name = 'Louis'; obj2.language[1] = ['二', '三']; console.log(obj1) console.log(obj2)
输出:
{ name: 'Louis', age: 30, language: [ 1, [ '二', '三' ], [ 4, 5 ] ] } { name: 'Louis', age: 30, language: [ 1, [ '二', '三' ], [ 4, 5 ] ] }
结论: 当把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
function shallowCopy(obj) { const result = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] = obj[key] } } return result; } let obj1 = { name: 'G-Dragon', age: 30, language: [1, [2, 3], [4, 5]] } let obj2 = shallowCopy(obj1); obj2.name = 'Louis'; obj2.language[1] = ['二', '三']; console.log(obj1) console.log(obj2)
{ name: 'G-Dragon', age: 30, language: [ 1, [ '二', '三' ], [ 4, 5 ] ] } { name: 'Louis', age: 30, language: [ 1, [ '二', '三' ], [ 4, 5 ] ] }
结论: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
let target = { name: 'G-Dragon', age: 30, friend: [ { name: 'JiangNan', age: 31, }, ], sayName: () => { return 'Hi, I am ' + this.name } }; let shallowCopy = (obj) => { // 检查传入的参数是还是数组 const result = Array.isArray(obj) ? [] : {}; for(let key in obj) { if (obj.hasOwnProperty(key)) { result[key] = obj[key]; } } return result; } let t1 = shallowCopy(target); let t2 = shallowCopy(target) console.log(t1); console.log(t2) console.log('-------------') t1.friend.push({ name: '小刘医生', age: 27 }) console.log(t1); console.log(t2)
上面代码输出为:
{ name: 'G-Dragon', age: 30, friend: [ { name: 'JiangNan', age: 31 } ], sayName: [Function: sayName] } { name: 'G-Dragon', age: 30, friend: [ { name: 'JiangNan', age: 31 } ], sayName: [Function: sayName] } ------------- { name: 'G-Dragon', age: 30, friend: [ { name: 'JiangNan', age: 31 }, { name: '小刘医生', age: 27 } ], sayName: [Function: sayName] } { name: 'G-Dragon', age: 30, friend: [ { name: 'JiangNan', age: 31 }, { name: '小刘医生', age: 27 } ], sayName: [Function: sayName] }
结论: shallowCopy方法只拷贝了对象的一层,这只是一种浅拷贝。其实还有一些原生的方法也是只拷贝一层,比如 Object.assign 和 ... 扩展运算符
shallowCopy
Object.assign
let newObj = Object.assign({}, target); // 这是一层的浅拷贝 let newObj = {...target}; // 这也是一层的浅拷贝
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
let obj = { a: { a: 'justin', b: 50 } } let initObj = Object.assign({}, obj) console.log(initObj) //{ a: { a: 'justin', b: 50 } } initObj.a.a = 'justin_2' console.log(obj.a.a) //justin_2
let arr = [1, 3, { user_name: 'G-Dragon' }]; let arr2 = arr.concat(); console.log(arr2) //[ 1, 3, { user_name: 'G-Dragon' } ] arr2[2].user_name = 'wa~ wo~'; console.log(arr2) //[ 1, 3, { user_name: 'wa~ wo~' } ] console.log(arr) //[ 1, 3, { user_name: 'wa~ wo~' } ], 表明修改新对象会改到原对象
补充:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
原数组的元素会按照下述规则拷贝:
let arr = [1, 3, { user_name: 'G-Dragon' }, function() {}]; let arr2 = JSON.parse(JSON.stringify(arr)); arr2[2].user_name = '孙悟空'; console.log(arr) //[ 1, 3, { user_name: 'G-Dragon' }, [Function] ] console.log(arr2) //[ 1, 3, { user_name: '孙悟空' }, null ]
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse() 把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
缺点: 该方法虽然可以实现 Array 和 Object 的深拷贝,但不能处理函数。这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。
Array
Object
JSON.stringify()
原理:遍历Object、Array,知道里面东都市基本数据类型,然后再去复制,就是深度拷贝。
有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。关于这块如有疑惑,请仔细阅读ConardLi大佬如何写出一个惊艳面试官的深拷贝?这篇文章。
function deepClone(obj, hash = new WeakMap()) { if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作 if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝 if (typeof obj !== 'object') return obj; // 是对象的话就要进行深拷贝 if (hash.get(obj)) return hash.get(obj); let cloneObj = new obj.constructor(); // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身 hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { // 实现一个递归拷贝 cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; } let obj = { name: 1, address: { x: 100 } }; obj.o = obj; // 对象存在循环引用的情况 let d = deepClone(obj); obj.address.x = 200; console.log(d);
另一种不太严谨的实现:
function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ //判断是否为自身属性 if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; }
该函数库有提供 _.cloneDeep用来做Deep Copy;
_.cloneDeep
let _ = require('lodash'); let obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] } let obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f) //false
相关文章:【积点成势】写一个函数实现深拷贝
经常会听到有人说:
好吧......以免被别人鄙视,今天好好总结梳理下这部分知识。。
1、什么是深拷贝和浅拷贝?分别有哪些方式?
深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用类型的。如下图所示:![image](https://user-images.githubusercontent.com/18101244/177551518-5f5e0c37-a53c-4952-92ee-3c1de836ea80.png)
常见的浅拷贝方法有:
常见的深拷贝方法有:
2、赋值和浅拷贝的区别
2.1 赋值
我们先看一下下面这段代码:
输出:
结论: 当把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
2.2 浅拷贝
输出:
结论: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
3、赋值 vs 浅拷贝 vs 深拷贝
4、浅拷贝的实现方式
4.1 for/in遍历
上面代码输出为:
结论:
shallowCopy
方法只拷贝了对象的一层,这只是一种浅拷贝。其实还有一些原生的方法也是只拷贝一层,比如Object.assign
和...
扩展运算符4.2 Object.assign()
Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是Object.assign()
进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。4.3 Array.prototype.concat()
补充:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
原数组的元素会按照下述规则拷贝:
5、深拷贝的实现方式
5.1 JSON.parse(JSON.stringify())
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse() 把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
缺点: 该方法虽然可以实现
Array
和Object
的深拷贝,但不能处理函数。这是因为JSON.stringify()
方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。5.2 手写递归
原理:遍历Object、Array,知道里面东都市基本数据类型,然后再去复制,就是深度拷贝。
有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。关于这块如有疑惑,请仔细阅读ConardLi大佬如何写出一个惊艳面试官的深拷贝?这篇文章。
另一种不太严谨的实现:
5.3 函数库lodash
该函数库有提供
_.cloneDeep
用来做Deep Copy;6、参考阅读
相关文章:【积点成势】写一个函数实现深拷贝