JavaScript | 手写浅拷贝、深拷贝(面试篇)

217 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

一、前言

在开发中偶尔会碰到一些场景是要自己去处理一些数据,为了方便数据的对比,你需要复制一份一样的,这时候就需要用到拷贝了。所谓深浅拷贝,浅拷贝的意思就是,你只是复制了对象数据的引用,并没有把内存里的值另外复制一份,那么深拷贝就是把值完整地复制一份新的值,下面就对深浅拷贝做一个详细了解。

二、浅拷贝

实现浅拷贝的方式

1. Object.assgin()

   let obj = {name:'lucky',age:18,a:{sex:nan}}
   let change = Object.assign({},obj)
   change.name = 'll' //改变当前层
   change.a.sex = 'nv' //改变内层
   console.log('change----',change)
   console.log('obj----',obj)

如下图打印所示,改变当前层级的为浅拷贝,改变嵌套层的为深拷贝

81FAA569-3105-421A-B912-DA16660E0B56.png

2. Array.prototype.concat()

let arr = [1,2,{name:'lucky'},3]
let change = arr.concat()
change[2].name = 'll'
console.log('arr----',arr)
console.log('change----',change)

验证结果

94EAFF45-86A9-4C92-A198-318A38555357.png

3. Array.prototype.slice()

let arr = [1,2,{name:'lucky'},3]
let change = arr.slice()
change[2].name = 'll'
console.log('arr----',arr)
console.log('change----',change)

验证结果

EEE624DD-D158-4E06-AB1C-04D556651732.png

4.简单手写一个浅拷贝

 function fn(target){
     let type = Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
     
     if(type === 'array'){
        var copy = []
      }else{
        var copy = {}
     }
     
     for(let key in target ){
       // 如果是容器自身的才需要处理
        if (target.hasOwnProperty(key)) {
           copy[key] = target[key]
        }
      }
      
       console.log(target)
       console.log(copy)
       console.log(copy === target)
    }
    let obj = {name:'lucky',age:18,eat:function(){},a:{name:'li'}}
    let arr = [1,2,3,4,5,6]
    fn(obj)
    fn(arr)

验证结果

4FD3E5FD-ECD0-4BDF-9BF4-22CC83B63932.png

三、深拷贝

在很多时候浅拷贝满足不了需求,因为深拷贝才是完整数据的拷贝,深拷贝改变数据不会影响到原数据。

深拷贝的实现方式

1. JSON.parse() 和 JSON.stringify()

  let obj = {name:'lucky',age:18}
  let change = JSON.parse(JSON.stringify(obj))
  change.name = 'll'
  console.log('obj----',obj)
  console.log('change----',change)

验证结果

原对象数据没有改变

016C9F37-B913-49C8-8E8E-6192709601EB.png

2. 递归写法

    //检测类型函数
    function checkType(obj) {
        return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
    }
    //深拷贝
    function deepClone(obj){
       //判断类型 如果是基本类型 则直接返回 如果是对象类型,则开始拷贝
        if (checkType(obj) === 'object') {
            var newObj = {};
        } else if (checkType(obj) === 'array') {
            var newObj = [];
        } else {
            return obj;
        }
        //拷贝
        for (var key in obj) {
            //每次拷贝之前 把拷贝的递归一下,如果是基本值,则直接返回,否则再次拷贝
            newObj[key] = deepClone(obj[key]);
        }
        return newObj;
    }
    
    let obj = {name:'lucly',
               eat:function(){},
               a:{sex:'nan'}  
              }
    let result = deepClone(obj)
    result.name = 'll'
    console.log('obj----',obj)
    console.log('result----',result)

验证结果

递归深拷贝完成

3CADE13B-46F0-4762-8E28-83FDE679466B.png

3.最终写法

因为递归写法消耗性能比较大,如果对象里面循环引用,那么就会存在爆栈现象,所以我们再优化一下写法。

 /** 
  解决问题: 循环引用正常
  注意: 在整个递归调用过程中, 只有一个map在反复使用
**/

function deepClone (target, map=new Map()) {

if (target!==null && typeof target==='object') { // 非函数的对象
 // const map = new Map()  // 用来缓存target与其对应的拷贝对象的容器
  // 从缓存中取出对应的拷贝对象,如果有了, 直接返回它
  let clone = map.get(target)
  if (clone) return clone

  // 如果没有, 创建一个新拷贝空容器, 缓存起来
  clone = Array.isArray(target) ? [] : {}
  map.set(target, clone)

  // 遍历target中所有数据, 依次添加到新容器
  for (const key in target) {  // key是对象的属性名或数组的下标
    if (target.hasOwnProperty(key)) { // 如果是容器自身的才需要处理
      clone[key] = deepClone(target[key], map) // 对属性值进行克隆处理后保存
    }
  }
  return clone
} else {
  return target
}
}
 let obj = {name:'lucly',
            eat:function(){},
            a:{sex:'nan'}  
          }
 let result = deepClone(obj)
 result.name = 'll'
 console.log('obj----',obj)
 console.log('result----',result)

好了,以上就是本篇文章的分享,感谢阅读!