【重学JS之路】深拷贝和浅拷贝

460 阅读2分钟

这属于面试的高频问题了,深浅拷贝的问题只针对于引用数据类型,对于基本数据类型并没有这样的问题。
为什么引用数据类型会出现这样的问题?
第一篇 js 数据类型中有提到,引用数据类型存放在堆中,栈中存放的是该引用类型的地址,以便能通过地址快速找到该引用数据。
所以在拷贝时就会出现只拷贝引用类型的地址和再在堆中开辟一个新的内存空间的两种拷贝方式。

1、浅拷贝

let arr = [1, 2, 3];
let new_arr = arr;
arr.push(4);
console.log(arr); //[1, 2, 3, 4]
console.log(new_arr); //[1, 2, 3, 4]

开发时我们经常能遇到上面这种情况,当对原数组添加新值时,新数组也会发生改变,这种情况我们就称之为浅拷贝。
原因是浅拷贝只拷贝引用地址,而地址都是指向了同一个对象,所以彼此之间的改变会相互影响。
大部分情况下的拷贝都是浅拷贝,除了上面=(赋值)外实现浅拷贝的方法也有很多:

concat();
slice();
ES6新增:展开运算符(...)
Array.from()

上述方法中 concat 会有些特殊:

let arr = [1, 2, 3];
let new_arr = [].concat(arr);
arr.push(4);
console.log(arr); //[1, 2, 3, 4]
console.log(new_arr); //[1, 2, 3]

上述结果显示,当改变原数组时,新数组并没有发生改变,会以为 concat 属于深拷贝的方法。但当进行下述运算时:

var arr = [{ old: "old" }, ["old"]];
var new_arr = arr.concat();
arr[0].old = "new";
arr[1][0] = "new";
console.log(arr); //[{old: 'new'}, ['new']]
console.log(new_arr); //[{old: 'new'}, ['new']]

所以可以得出结论,当数组元素是基本数据类型时,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。

我们来写个函数简单实现一个浅拷贝:

let shallowCopy = (obj) => {  
    if (typeof obj !== "object") return;  
    let new_obj = obj instanceof Array ? [] : {};  
    for (let key in obj) {    
        if (obj.hasOwnProperty(key)) {      
            new_obj[key] = obj[key];    
        }  
    }  
    return new_obj;
};

2、深拷贝

深拷贝是完全拷贝一个新的对象,相当于开辟了一个新的内存空间,新旧两个对象不相互影响。
实现深拷贝的方法主要有两个,一个是 JSON.parse(JSON.stringify())和还有一个是递归

let arr = [{ old: "old" }, ["old"]];
let new_arr = JSON.parse(JSON.stringify(arr));
arr[0].old = "new";
arr[1][0] = "new";
console.log(arr); //[{old: 'new'}, ['new']]
console.log(new_arr); //[{old: 'old'}, ['old']]

但是 JSON.parse(JSON.stringify())有个最大的问题就是,不能拷贝函数

let arr = [{ old: "old" }, function () {}];
let new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr); //[{old: 'old'}, null]

我们通过递归的形式简单实现一个深拷贝:

let deepCopy = (obj) => {  
    if (typeof obj !== Object) return;  
    let new_obj = obj instanceof Array ? [] : {};  
    for (let key in obj) {    
        if (obj.hasOwnProperty(key)) {      
            new_obj[key] = typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key];    
        }  
    }  
    return new_obj;
};