js 浅拷贝与深拷贝

263 阅读4分钟

说到深拷贝与浅拷贝数据类型是无法忽略的门槛

数据类型分为基本数据类型和引用(对象)数据类型

基本数据类型:(String, Number, boolean, Null, Undefined,Symbol)

引用(对象)数据类型: Object,特殊的有Array, Function, Date, Math, RegExp, Error等


JS的内存跟其他的内存差不多,分为堆(heap)和栈(stack)。

栈:由系统自动分配,自动回收,效率高,但容量小。
堆:由程序员手动分配内存,并且手动销毁(高级语言如JS中有垃圾自动回收机制),效率不如栈,但容量大。


浅拷贝

        
        var obj1 = {
            name: "你我贷"
        }
        var obj2 = {}
        obj2 = obj1;
        console.log(obj1 === obj2) // true
        console.log(obj2.name) // "你我贷"
        obj2.name = "飞旋"
        console.log(obj1 === obj2) // true
        console.log(obj1.name) //飞旋

问题?

        
        var obj1 = {
            name: ["嘉银","你我贷"]
        }
        var obj2 = {}
        obj2 = obj1;
        console.log(obj1 === obj2) //true
        console.log(obj2.name) //["嘉银", "你我贷"]
        obj2.name.push("飞旋");
        console.log(obj1.name) //["嘉银", "你我贷", "飞旋"]
        console.log(obj1 === obj2) //true

注意:浅拷贝有个很大的问题就是 如果父对象的属性是一个 数组或者是另一个对象的话 那么子对象获得的只是一个内存地址,而不是真正的拷贝,因此父对象容易存在被篡改的可能

(JS的基本类型分配在栈中,而因为引用类型大小的不固定,系统将存储该引用类型的地址存在栈中,并赋值给变量本身,而具体的内容存在堆中。所以当访问一个对象的时候,先访问栈中它的地址,然后按照这个地址去堆中找到它的实际内容。

所以当复制的时候,对于基本类型的变量,系统会为新的变量在栈中开辟一个新的空间,赋予相同的值,然后这两个变量就各自独立,毫无牵连。而对于引用类型的变量,新的变量复制的是那个对象在堆中的地址,这两个变量指向的是同一个对象。obj2复制了obj之后,两个其实指向的是同一个对象。)


1)封装方法实现浅拷贝

    var obj1 = {
        name: "嘉银",
        address: "上海"
    }
    function shallowCopy (o) {
    	var obj = obj || {}
    	for(var prop in o) {
    		obj[prop] = o[prop]
    	}
    	return obj
    }
    var obj2 = shallowCopy(obj1);
    console.log(obj1,obj2)


2)通过ES6 Object.assign()方法实现浅拷贝

    var obj1 = {
        name: "嘉银",
        address: "上海"
    }
    var obj2 = Object.assign(obj1)
    obj2.name = "飞旋"
    console.log(obj1,obj2)


3)通过 Array.prototype.concat()方法实现浅拷贝

        var arr1 = ["你我贷","飞旋",{
 		name: "百味"
 	}]
 	var arr2 = arr1.concat()
 	arr2[2].name = "金融"
 	console.log(arr1,arr2)


4)通过Array.prototype.slice()方法实现浅拷贝

        var arr1 = ["你我贷","飞旋",{
 		name: "百味"
 	}]
 	var arr2 = arr1.slice()
 	arr2[2].name = "金融"
 	console.log(arr1,arr2)


特别注意:Array.prototype.slice() 和 Array.prototype.concat() 如果修改新对象里面的属性的数据类型不是对象的话 是不会影响原数组的

        var arr1 = ["你我贷","飞旋",{
 		name: "百味"
 	}]
 	var arr2 = arr1.slice()
 	arr2[1] = "金融"
 	console.log(arr1,arr2)


5)ES6 展开运算符实现浅拷贝

        var obj1 = {
 		name: "你我贷"
 	}
 	var obj2 = { ...obj1}

 	console.log(obj2)


深拷贝

1)通过递归方法实现深拷贝

    var obj1 = {
        name: ["嘉银","你我贷"],
        address: "上海",
        job: {
        	type: "开发",
        	section: "投资"

        }
    }

    function checkType(tar) {
    	return Object.prototype.toString.call(tar)
    }
    function deepCopy(target,obj) {
	if(checkType(target) === "[object Object]") {
	    obj = {};
	}else if(checkType(target) === "[object Array]") {
	    obj = [];
	}else {
	    return target
	}

	for(var prop in target) {
			
            if(checkType(target[prop]) === "[object Object]" 
                || checkType(target[prop]) === "[object Array]") {
		    obj[prop] = deepCopy(target[prop])
	    }else {
		obj[prop] = target[prop]
	    }
	}
	return obj
    }

    var obj2 = deepCopy(obj1);
    console.log(obj2)



2)JSON.parse(JSON.stringify())方法实现深拷贝

    var obj1 = {
        name: ["嘉银","你我贷"],
        address: "上海",
        job: {
        	type: "开发",
        	section: "投资"

        }
    }
    var obj2 = JSON.parse(JSON.stringify(obj1))
    console.log(obj2)


原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

注意:json.stringfy()将对象、数组转换成字符串;json.parse()将字符串转成json对象

弊端:这种方法虽然可以实现数组或对象深拷贝,但不能处理函数

    var obj1 = {
        name: ["嘉银","你我贷"],
        address: "上海",
        job: {
        	type: "开发",
        	section: "投资"

        },
        say: function() {

        }
    }
    var obj2 = JSON.parse(JSON.stringify(obj1))
    console.log(obj1,obj2)



总结:浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。


扩展知识点

Object.assign():

解释:方法将所有可枚举属性的值从一个或多个源对象复制到目标对象,然后返回目标对象 

语法:Object.assign(target,..sources);

参数 :target:目标对象   sources:源对象

返回值:target:目标对象

        var arr1 = ["你我贷","飞旋"]
 	var arr2 = []
 	var arr3 = Object.assign(arr1,arr2)
 	console.log(arr3)