JS 深拷贝、浅拷贝

297 阅读3分钟

js基本类型

基本数据类型:Null、 undefined、 number、 string、 boolean、symbol

引用类型:Object对象类型(object、 array、 function、 Data等)

区别点:

基本数据类型,是按照值进行访问的,可以操作保存在变量中的实际的值。

引用类型,js是不允许直接访问值的,不能直接操作对象的内存空间。

基本数据类型是存在栈中的

引用类型的值是同时保存在栈内存和堆内存中的对象

深拷贝、浅拷贝抽象讲解

假设B复制了A,当修改A时,看B是否发生变化,

  • 如果B变化,说明就是浅拷贝

  • 如果B没变,就是深拷贝

深拷贝、浅拷贝使用场景

浅拷贝:只是简单的指针赋值。

深拷贝:指针赋值,且内容拷贝。深拷贝即拷贝实例,其作用是为了不影响拷贝后的数组对起原数组造成影响。

浅拷贝

定义:

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

第一种方法:for...in只循环第一层
function shadowClone(initalObj){
    var obj = {}
    for(var i in initalObj){
        obj[i] = initalObj[i]
    }
    return obj
}
var obj = {
    a:"hello",
    b:{
        a:"world",
        b:20
    },
    c:["tom","jack","lay"],
    d:function(){
        console.log("ni hao")
    }
};
var cloneObj = shadowClone(obj)
console.log(cloneObj.a)  // hello
console.log(cloneObj.b)  // {a:"world",b:20}
console.log(cloneObj.c)  // ["tom","jack","lay"]
console.log(cloneObj.d)  // f(){console.log("ni hao")}

// 更改原对象中的a,b,c,d,看拷贝过来的对象是否变化
cloneObj.a = "changed"
cloneObj.b.a = "changed"
cloneObj.b.b = "30"
cloneObj.c = [1,12,13]
cloneObj.d = function(){ console.log("你好")}

console.log(cloneObj.a)  // hello
console.log(cloneObj.b)  // {a:"changed",b:30}
console.log(cloneObj.c)  // ["tom","jack","lay"]
console.log(cloneObj.d)  // f(){console.log("ni hao"

总结:

浅拷贝只是拷贝了一层,除了对象是拷贝的引用类型,其他的都是直接将值传递,有自己的内存空间。

第二种方法:Object.assign方法

ES6中的Object.assign方法,Object.assign(target,...sources)

参数: target:目标对象 sources:任意多个源对象 返回值:目标对象会被返回

var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1.b); // 2
console.log(obj2.b); // 5

缺点:对象中有多种嵌套,则无法使用

 var obj1 = {
    a: 1,
    b: 2,
    c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]

Object.assign 会跳过那些值为 [null] null 是一个 JavaScript 字面量,表示空值(null or an "empty" value),即没有对象被呈现(no object value is present)。它是 JavaScript 原始值 之一。") 或 undefined 的源对象。

深拷贝

定义:深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。

对数组做深拷贝
for循环
let arr1 = [1,2,3];
  let arr2 = copyArr(arr1);
  function copyArr(arr){
  	let res=[];
  	for(let i=0,length=arr.length;i<length;i++){
  		res.push(arr[i]);
  	}
  	return res;
  }
slice

利用数组自身的方法,slice、concat方法在运行后会返回新的数组

let arr1 = [1,2,3];
let arr2 = arr1.slice(0);

let arr1 = [1,2,3];
let arr2 = arr1.concat();
扩展运算符
let arr1 = [1,2,3];
 let [...arr2] = arr1;
对对象的深拷贝
手动复制

把一个对象的属性复制给另一个对象的属性

var obj1 = {a:"test",b:02,c:30}
var obj2 = {a:obj1.a,b:obj1.b,c:obj1.c}
通过JSON对象来实现深拷贝

JSON.stringify把对象转成字符串,在用JSON.parse把字符串转成新的对象

function deepClone(obj){
    var obj = JSON.stringify(obj)
    objClone = JSON.parse(obj)
    return objClone
}
var a = [1,[2,3],4,5]
var b = deepClone(a)
b[1][0] = 7;
console.log(a) // [1,[2,3],4,5]
console.log(b) // [1,[7,3],4,5]

缺点:

会抛弃对象中的constructor,深拷贝后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

1:无法拷贝对象中的方法属性

2:无法拷贝对象中值为undefined的属性

  • jquery提供一个$.extend可以用来做深拷贝
import $ from 'jquery'
var array = [1,2,3,4]
var newArray = $.extend(true,[],array)  // true为深拷贝,false为浅拷贝

缺点:1,需要引用jQuery库 2,无法拷贝对象中值为undefined的属性

  • 递归拷贝
// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
   // 定义一个变量
   let result;
   // 如果当前需要深拷贝的是一个对象的话
   if (typeof target === 'object') {
   // 如果是一个数组的话
       if (Array.isArray(target)) {
           result = []; // 将result赋值为一个数组,并且执行遍历
           for (let i in target) {
               // 递归克隆数组中的每一项
               result.push(deepClone(target[i]))
           }
        // 判断如果当前的值是null的话;直接赋值为null
       } else if(target===null) {
           result = null;
        // 判断如果当前的值是一个RegExp对象的话,直接赋值    
       } else if(target.constructor===RegExp){
           result = target;
       }else {
        // 否则是普通对象,直接for in循环,递归赋值对象的所有值
           result = {};
           for (let i in target) {
               result[i] = deepClone(target[i]);
           }
       }
    // 如果不是对象的话,就是基本数据类型,那么直接赋值
   } else {
       result = target;
   }
    // 返回最终结果
   return result;
}

手写深拷贝

function deepCopy(obj){
	let result = Array.isArray(obj)?[]:{};  
	if(obj && typeof obj === 'object'){ 
		for(let key in obj){
			if(obj.hasOwnProperty(key)){
				if(obj[key]&&typeof obj[key]==='object'){
					result[key]=deepCopy(obj[key]);
				}else{
					result[key]=obj[key];
				}
			}
		}
	}
	return result;
}

对于拷贝中对象中值为undefined的属性的解决方法(object.keys)

const org = { a: undefined };
const copy = {};
Object.keys(org).forEach(key => {
    copy[key] = origin[key];
});
console.log(copy); // copy: { a: undefined }

通用版的深拷贝、浅拷贝

/* 
* 写一个通用的拷贝(包括深拷贝和浅拷贝都适用)
* 简单数据类型实现深拷贝,直接复制就可
* 引用数据类型
Array.isArray(obj):方法返回一个布尔值,
obj.hasOwnProperty(key):方法返回一个布尔值,判断对象是否包含特定的自身属性
*/
function copyObj(obj){
    var cloneObj;
    // 当输入数据为简单数据类型时直接复制
    if(obj && typeof obj !== 'object'){
        cloneObj = obj
    }else if(obj && typeof obj === 'object'){ // 检测输入数据是数组还是对象
        // 判断obj是否是数组还是对象
        cloneObj = Array.isArray(obj)?[]:{}
        for(let key in obj ){
            if(obj.hasOwnProperty(key)){ //判断对象是否包含特定的自身属性
                if(obj[key] && typeof obj[key] === 'object'){
                    // 若当前元素类型为对象时,递归调用
                    cloneObj[key]  = copyObj(obj[key])
                }
                //若当前元素类型为基本数据类型
                else {
                    cloneObj[key] = obj[key]
                }
            }
        }
    }
   return cloneObj    
}