We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
经常会听到有人说:
好吧......以免被别人鄙视,今天好好总结梳理下这部分知识。。
深拷贝和浅拷贝是只针对 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
相关文章:【积点成势】写一个函数实现深拷贝
The text was updated successfully, but these errors were encountered:
No branches or pull requests
经常会听到有人说:
好吧......以免被别人鄙视,今天好好总结梳理下这部分知识。。
1、什么是深拷贝和浅拷贝?分别有哪些方式?
深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用类型的。如下图所示:
只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
常见的浅拷贝方法有:
会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
常见的深拷贝方法有:
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、参考阅读
相关文章:【积点成势】写一个函数实现深拷贝
The text was updated successfully, but these errors were encountered: