JS手撕(二) 数组扁平化、浅拷贝、深拷贝
数组扁平化
数组扁平化就是将多层数组拍平成一层,如[1, [2, [3, 4]]]变成[1, 2, 3, 4]
可以使用递归来实现,就直接遍历最外层数组,如果遍历的元素是数组,那就继续递归,直到不是数组为止。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | function myFlatten(arr) {let result = [];
 
 for (let i = 0; i < arr.length; i++) {
 if (Array.isArray(arr[i])) {
 result = result.concat(myFlatten(arr[i]));
 } else {
 result.push(arr[i]);
 }
 }
 
 return result;
 }
 
 
 console.log(myFlatten([1, [2, [3, 4]]]));
 
 | 
简单画了一下递归的过程,包括return后回到上一级的部分。
也可以使用some()方法来更简单地实现,因为some()方法返回数组是否有元素满足条件的布尔值,因为可以将条件设置为数组中是否有元素是数组。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | function myFlatten(arr) {while (arr.some(item => Array.isArray(item))) {
 
 
 console.log(arr);
 
 arr = [].concat(...arr);
 }
 
 return arr;
 }
 
 
 console.log(myFlatten([1, [2, [3, 4]]]));
 
 | 
也可以使用reduce来实现。
| 12
 3
 4
 5
 6
 7
 8
 
 | function myFlatten(arr) {return arr.reduce((pre, cur) => {
 return pre.concat(Array.isArray(cur) ? myFlatten(cur) : cur);
 }, [])
 }
 
 
 console.log(myFlatten([1, [2, [3, 4]]]));
 
 | 
更多方法可查看:面试官连环追问:数组拍平(扁平化) flat 方法实现 - 掘金
大佬讲的非常细,循序渐进介绍了很多种方法。
拷贝
如果我们给把一个对象直接赋值给另一个对象,那么我们修改其中的一个对象都会影响到另一个对象(非重新赋值),因为它们是同一个引用。
而拷贝的话,两个对象就不再是同一个引用了,所以修改对象不会影响到另一个对象。但是拷贝还分为浅拷贝和深拷贝两种。
浅拷贝
浅拷贝就是只能拷贝第一层,如果有嵌套对象,那么嵌套对象是没法拷贝的,所以修改嵌套对象还是会影响到另一个对象。而在后面讲的深拷贝则是即使有嵌套对象,也能够正常拷贝全部的方法。
下面的拷贝只是简单版本的,只考虑普通对象。主要学习思想(其实是懒)。
遍历法
因为浅拷贝只需要拷贝第一层,所以只需要通过遍历,然后给新对象赋值旧对象的属性值即可,因为如果是只有一层的话,那么就不会是对象。如果是对象,即嵌套对象,那就不是浅拷贝能解决的了,而应该给后面的深拷贝来处理。
| 12
 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
 29
 30
 31
 
 | function myShadowCopy(obj) {let newObj = {};
 
 for (const key in obj) {
 if (obj.hasOwnProperty(key)) {
 
 newObj[key] = obj[key];
 }
 }
 
 return newObj;
 }
 
 
 const obj = {
 name: 'clz',
 job: {
 type: 'Coder'
 }
 };
 
 const shadowCopyObj = myShadowCopy(obj);
 
 shadowCopyObj.name = 'czh';
 console.log(obj);
 console.log(shadowCopyObj);
 
 
 shadowCopyObj.job.type = 'tester';
 console.log(obj);
 console.log(shadowCopyObj);
 
 | 
Object.assign()
可以使用Object.assign()来实现浅拷贝,因为Object.assign()的目的就是将一个或多个源对象复制给目标对象,并且返回修改后的对象。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | function myShadowCopy(target) {return Object.assign({}, target);
 }
 
 
 const obj = {
 name: 'clz',
 job: {
 type: 'Coder'
 }
 };
 
 const shadowCopyObj = myShadowCopy(obj);
 
 shadowCopyObj.name = 'czh';
 console.log(obj);
 console.log(shadowCopyObj);
 
 
 shadowCopyObj.job.type = 'tester';
 console.log(obj);
 console.log(shadowCopyObj);
 
 | 
扩展运算符...
| 12
 3
 
 | function myShadowCopy(target) {return { ...target };
 }
 
 | 
效果和上面一样。
顺带一提:通过concat和slice可以浅拷贝数组。
深拷贝
浅拷贝只能拷贝对象的第一层,如果遇到嵌套对象,又会变成对象的引用。这时候就可以使用深拷贝,深拷贝就是拷贝整个对象,而不仅仅是第一层。
深拷贝主要是通过递归来实现,如果属性是对象,则递归调用深拷贝函数。
| 12
 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
 29
 30
 31
 
 | const isObject = (target) => typeof target === 'object' && target !== 'null';
 function myDeepCopy(obj) {
 let newObj = {};
 
 for (const key in obj) {
 if (obj.hasOwnProperty(key)) {
 newObj[key] = isObject(obj[key]) ? myDeepCopy(obj[key]) : obj[key];
 }
 }
 
 return newObj;
 }
 
 
 const obj = {
 name: 'clz',
 job: {
 type: 'Coder'
 }
 };
 
 const deepCopyObj = myDeepCopy(obj);
 
 deepCopyObj.name = 'czh';
 console.log(obj);
 console.log(deepCopyObj);
 
 deepCopyObj.job.type = 'tester';
 console.log(obj);
 console.log(deepCopyObj);
 
 | 
上面的代码还有一个很大的问题,如果存在循环引用会报错。
循环引用就是上面的**y中有z,z中有y*,这种情况下会一直递归,直到超出最大调用堆栈大小。
那么,如何解决这种情况呢?只需要使用map来缓存拷贝过的数据即可,键为拷贝的目标,值为拷贝的结果。先判断有没有拷贝过,如果有,直接返回之前拷贝过的数据。
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | const isObject = (target) => typeof target === 'object' && target !== 'null';
 function myDeepCopy(target, map = new Map()) {
 
 const cache = map.get(target)
 if (cache) {
 
 return cache;
 }
 
 if (isObject) {
 let newObj = {};
 map.set(target, newObj);
 
 for (const key in target) {
 if (target.hasOwnProperty(key)) {
 newObj[key] = isObject(target[key]) ? myDeepCopy(target[key], map) : target[key];
 }
 }
 
 return newObj;
 
 } else {
 
 return target;
 }
 }
 
 
 const obj = {
 x: 'x',
 y: {
 name: 'clz'
 },
 z: {
 age: '21'
 }
 };
 
 obj.y.z = obj.z;
 obj.z.y = obj.y;
 
 console.log(obj);
 
 const deepCopyObj = myDeepCopy(obj);
 
 console.log(deepCopyObj);
 
 | 
structuredClone()
顺带提一下这个方法,之前看到的。(node环境没有这个方法)
全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | const obj = {name: 'clz',
 job: {
 type: 'Coder'
 }
 };
 
 const deepCopyObj = structuredClone(obj);
 
 deepCopyObj.name = 'czh';
 console.log(obj);
 console.log(deepCopyObj);
 
 deepCopyObj.job.type = 'tester';
 console.log(obj);
 console.log(deepCopyObj);
 
 | 
参考
死磕 36 个 JS 手写题(搞懂后,提升真的大) - 掘金
GitHub - qianlongo/fe-handwriting: 手写各种js Promise、apply、call、bind、new、deepClone….
面试官连环追问:数组拍平(扁平化) flat 方法实现 - 掘金
(建议精读)原生JS灵魂之问(中),检验自己是否真的熟悉JavaScript? - 掘金