《手写深拷贝》

97 阅读3分钟

深/浅拷贝?

const a = [1,2,3]
let b = a
b[1] = 5
console.log(b)  //[1, 5, 3]
console.log(a)  //[1, 5, 3]

通俗的说:

a赋给了b,改b a也变 改a b也变,这就是浅拷贝(上边的例子)。

a赋给了b,改b a不变 改a b不变,这就是深拷贝。

稍微专业点的说:

  • 浅拷贝:只是拷贝了源对象的地址,所以源对象的任何值发生改变时,拷贝对象的值也会随之而发生变化。
  • 深拷贝:则是拷贝了源对象的所有而不是地址,所以即使源对象的值发生任何变化时,拷贝对象的值也不会改变。

深拷贝的两种方式

1. JSON

首先JSON.stringify()  方法将一个 JavaScript 对象或值转换为 JSON 字符串, 然后JSON.parse()  方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。

const b = JSON.parse(JSON.stringify(a))

没错,这样就实现了深拷贝,但是有缺陷。 JSON不支持日期正则和函数等等...

还不支持引用。如果a包含了对其他对象的引用,可能会报错。

2. 递归

程序员何苦为难程序员,也许少写两行代码能少掉两根头发。

要点:递归、判断类型、检查环、不拷贝原型上的属性

首先让我们先不带任何顾虑的写几行代码...

const deepClone = (a)=>{
    //判断传进来的a是不是对象
    if(a instanceof Object){
        //再定义一个东西用来当容器...
        let result = undefined
        //接下来判断对象是否为函数(箭头还是普通函数)/日期/正则/普通对象
        if(a instanceof Function){
            if(a.prototype){ //普通函数
                result = function(){
                return a.apply(this,arguments)
                }
            }else{
            //箭头函数
            result = (...args)=>{
                return a.call(undefined,...args)
            }else if(a instanceof Array){
            //数组
                result = [ ] 
            }else if(a instanceof Date ){
            //a-0会变成一串数字 当时间戳用
                result = new Date(a-0) 
            }else if(a instanceof RegExp ){ 
            //接受两个参数 一个文本 一个flags
            result = new RegExp(a.source,a.flags) 
            }else{ //其他的都是普通对象
                result = { }
            }
            for(let key in a){ 
                result[key] = deepClone(a[key]) //递归
            }
            return result
        }
    }else{  //基本数据类型
        return a
    }
}

真不错,但是还有问题。 如果加一句 a.self = a 去看a的话会看到a.self 看到 a.self又看到a 自己引用自己 那递归就没有出口了。

怎么解决?

把之前拷过的东西都记下来 再遇见就直接return 用Map记录(因为map的key可以是对象)

const cache = new Map()
const deepClone = (a)=>{
    if(cache.get(a)) { //新加代码
    //如果a已经存在cache里了 就说明已经完事了
        return cache.get(a)
    }
    if(a instanceof Object){
        let result = undefined
        if(a instanceof Function){
            if(a.prototype){ //普通函数
                result = function(){
                return a.apply(this,arguments)
                }
            }else{
            //箭头函数
            result = (...args)=>{
                return a.call(undefined,...args)
            }else if(a instanceof Array){
            //数组
                result = [ ] 
            }else if(a instanceof Date ){
                result = new Date(a-0) 
            }else if(a instanceof RegExp ){ 
            result = new RegExp(a.source,a.flags) 
            }else{ //其他的都是普通对象
                result = { }
            }
            cache.set(a,result)  //新加代码
            for(let key in a){ 
                result[key] = deepClone(a[key]) //递归
            }
            return result
        }
    }else{  //基本数据类型
        return a
    }
}

还有一个问题。我们在遍历a的时候没必要什么都遍历,a有些属性是继承得到的,继承得到的没必要拷贝它。 只需要在遍历的时候加个判断if(a.hasOwnProperty(key) )

const cache = new Map()
const deepClone = (a)=>{
    if(cache.get(a)) { 
    //如果a已经存在cache里了 就说明已经完事了
        return cache.get(a)
    }
    if(a instanceof Object){
        let result = undefined
        if(a instanceof Function){
            if(a.prototype){ //普通函数
                result = function(){
                return a.apply(this,arguments)
                }
            }else{
            //箭头函数
            result = (...args)=>{
                return a.call(undefined,...args)
            }else if(a instanceof Array){
            //数组
                result = [ ] 
            }else if(a instanceof Date ){
                result = new Date(a-0) 
            }else if(a instanceof RegExp ){ 
            result = new RegExp(a.source,a.flags) 
            }else{ //其他的都是普通对象
                result = { }
            }
            cache.set(a,result)
            
            for(let key in a){ 
               if(a.hasOwnProperty(key) ){  //新加代码
                  result[key] = deepClone(a[key]) 
                }
            }
            return result
        }
    }else{  //基本数据类型
        return a
    }
}

打完收工。