Skip to content

深拷贝 不会影响原始数据

js
let newObj = JSON.parse(JSON.stringify(oldObj))
let newObj = JSON.parse(JSON.stringify(oldObj))

数组深拷贝

数组实现深拷贝可以使用以下方法

  1. 使用slice()
  2. 使用concat()
  3. ES6扩展运算符
  4. Array.form()
js
let newArr = oldArr.slice(); // 方案一
let newArr = [].concat(oldArr); // 方案二
let newArr = [...oldArr]; // 方案三
let newArr = Array.from(oldArr); // 方案四
let newArr = oldArr.slice(); // 方案一
let newArr = [].concat(oldArr); // 方案二
let newArr = [...oldArr]; // 方案三
let newArr = Array.from(oldArr); // 方案四

对象深拷贝

js
let B = JSON.parse(JSON.stringify(A))
let B = JSON.parse(JSON.stringify(A))

注意

可以转成 JSON 格式的对象才能使用这种方法,如果对象中包含 functionRegExp 则不能用这种方法。

  • 使用递归

注意

  1. 用new obj.constructor ()构造函数新建一个空的对象,而不是使用{}或者[],这样可以保持原形链的继承;
  2. 用obj.hasOwnProperty(key)来判断属性是否来自原型链上,因为for..in..也会遍历其原型链上的可枚举属性。
  3. 上面的函数用到递归算法,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,需要使用 arguments.callee。
js
var clone = function (obj) { 
    if(obj === null) return null 
    if(typeof obj !== 'object') return obj;
    if(obj.constructor === Date) return new Date(obj); 
    if(obj.constructor === RegExp) return new RegExp(obj);
    var newObj = new obj.constructor ();  //保持继承链
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
            var val = obj[key];
            newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; 
            // 使用arguments.callee解除与函数名的耦合
        }
    }  
    return newObj;  
};
var clone = function (obj) { 
    if(obj === null) return null 
    if(typeof obj !== 'object') return obj;
    if(obj.constructor === Date) return new Date(obj); 
    if(obj.constructor === RegExp) return new RegExp(obj);
    var newObj = new obj.constructor ();  //保持继承链
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
            var val = obj[key];
            newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; 
            // 使用arguments.callee解除与函数名的耦合
        }
    }  
    return newObj;  
};

assign

如果对象的属性值为简单类型(如Number,String),通过Object.assign({},Obj)得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。

使用解构赋值和扩展运算符(...)时,你会发现情况和上面是一样的。

对象数组深拷贝

js
var oldArr = [
   { number: 1 },
   { number: 2 },
   { number: 3 }
];
function objArrDeepCopy (obj) {
    var newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    }
    for(var i in obj){
       newobj[i] = typeof obj[i] === 'object' ? objArrDeepCopy(obj[i]) : obj[i];
    }
    return newobj
}

var newArr = objArrDeepCopy(oldArr)
var oldArr = [
   { number: 1 },
   { number: 2 },
   { number: 3 }
];
function objArrDeepCopy (obj) {
    var newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    }
    for(var i in obj){
       newobj[i] = typeof obj[i] === 'object' ? objArrDeepCopy(obj[i]) : obj[i];
    }
    return newobj
}

var newArr = objArrDeepCopy(oldArr)

多层深拷贝

函数拷贝

浅拷贝 同时改变被原始数据

js
let A = {
    name:'1'
}
let B = {}
B = A // Bad 😢
B.name = '???'
console.log(A.name) // ???
let A = {
    name:'1'
}
let B = {}
B = A // Bad 😢
B.name = '???'
console.log(A.name) // ???
js
let array1 = [1,2,3]
let array2 = []
array2 = array1 // Bad 😢
array2[0] = '???'
console.log(array1[0]) // ???
let array1 = [1,2,3]
let array2 = []
array2 = array1 // Bad 😢
array2[0] = '???'
console.log(array1[0]) // ???

浅拷贝复制的是对象的引用地址,没有开辟新的栈,复制的结果是两个对象指向同一个地址,所以修改其中一个对象的属性,另一个对象的属性也跟着改变了。