一、浅拷贝(拷贝一层)
浅拷贝的实现与原理
自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象
1.obj.assign
1 2 3 4
| let target = {}; let source = { a: { b: 1 } }; Object.assign(target, source); console.log(target);
|
1 2 3 4 5 6 7 8 9 10
| let obj1 = { a:{ b:1 }, sym:Symbol(1)}; Object.defineProperty(obj1, 'innumerable' ,{ value:'不可枚举属性', enumerable:false }); let obj2 = {}; Object.assign(obj2,obj1) obj1.a.b = 2; console.log('obj1',obj1); console.log('obj2',obj2);
|
- 它不会拷贝对象的继承属性;
- 它不会拷贝对象的不可枚举的属性;
- 可以拷贝 Symbol 类型的属性。
2.扩展运算符
1
| let cloneObj = { ...obj };
|
3.concat与slice(邪门)
1 2 3 4
| let arr = [1, 2, 3]; let newArr = arr.concat(); let arr2 = [1, 2]; let newArr2 = arr.slice();
|
手写一个浅拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
const shallowClone = (target) => { if (typeof target === 'object' && target !== null) { const cloneTarget = Array.isArray(target) ? []: {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = target[prop]; } } return cloneTarget; } else { return target; } }
|
二、深拷贝
深拷贝要视具体而打造
1.乞丐版(JSON.stringify)
先JSON.stringify转字符串,再JSON.parse转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first)
|
2.loadash的deepclone(自己常用)
3.structuredClone(新Chromium 98以上)
1 2 3 4 5 6 7
| const data = { cat: '薛定谔', date: new Date(123), name: ['bilibili'] }
const copy = structuredClone(data)
|
手写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| let obj1 = { a:{ b:1 } } function deepClone(obj) { let cloneObj = {} for(let key in obj) { if(typeof obj[key] ==='object') { cloneObj[key] = deepClone(obj[key]) } else { cloneObj[key] = obj[key] } } return cloneObj } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2);
|
虽然利用递归能实现一个深拷贝,但是同上面的 JSON.stringify 一样,还是有一些问题没有完全解决,例如:
这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
对象的属性里面成环,即循环引用没有解决。
这种基础版本的写法也比较简单,可以应对大部分的应用情况。但是你在面试的过程中,如果只能写出这样的一个有缺陷的深拷贝方法,有可能不会通过。
所以为了“拯救”这些缺陷,下面我带你一起看看改进的版本,以便于你可以在面试种呈现出更好的深拷贝方法,赢得面试官的青睐。
方法三:改进版(改进后递归实现)
针对上面几个待解决问题,我先通过四点相关的理论告诉你分别应该怎么做。
针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;
当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;
利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object.create 方法创建一个新对象,并继承传入原对象的原型链;
利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值
如果你在考虑到循环引用的问题之后,还能用 WeakMap 来很好地解决,并且向面试官解释这样做的目的,那么你所展示的代码,以及你对问题思考的全面性,在面试官眼中应该算是合格的了
增强版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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) } 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 }
|