深拷贝和浅拷贝

Posted dylalex

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深拷贝和浅拷贝相关的知识,希望对你有一定的参考价值。

1.基本类型的值和引用类型的值

基本类型值指的是存储在栈中的一些简单的数据段,在javascript中基本数据类型有String,Number,Undefined,Null,Boolean,在ES6中,又定义了一种新的基本数据类型Symbol,所以一共有6种

基本类型是按值访问的,从一个变量复制基本类型的值到另一个变量后这2个变量的值是完全独立的,即使一个变量改变了也不会影响到第二个变量

引用类型值是引用类型的实例,它是保存在堆内存中的一个对象,引用类型是一种数据结构,最常用的是Object,Array,Function类型,另外还有Date,RegExp,Error等,ES6同样也提供了Set,Map2种新的数据结构

2.数据传递的方法:

    值传递:基本数据类型的数据不会发改变,因为基本数据类型一般存放在栈里面,值传递只是将数据拷贝了一份给另一个变量
    var a = 10;
    var b = a;
    b+=10;
    console.log(a);//10
    console.log(b);//20;
   引用传递:会改变内存中的数据,因为引用类型的数据都存放在堆里面,栈里面存放的是索引,拷贝的时候是拷贝的地址也就是索引
    var arr = [10,20,30,40];
    var newArr = arr;
    newArr[0] = 80;
    console.log(arr);//[80,20,30,40]
    console.log(newArr);//[80,20,30,40];

3.深拷贝和浅拷贝

浅拷贝:所谓的浅拷贝就是复制一份引用数据类型的地址,当改变了内存中数据的某一个值得话,也会影响到另一个对象
深拷贝:所谓的深拷贝就是复制一份引用数据类型的数据,当改变了数据的某一个值得话,不会影响到另一个对象(注意深拷贝是拷贝的数据,而不是索引,浅拷贝拷贝的是索引而不是数据)

区别:假设B复制了A,修改A的时候,看B是否发生变化:如果B变了便是浅拷贝,如果B没有变便是深拷贝

以下是一些JavaScript提供的浅拷贝方法:

3.1 Object.assign()
  Object.assign(target, ...sources)
var target = {};
var source = {a:1};
Object.assign(target ,source);
console.log(target); //{a:1}
source.a = 2;
console.log(source); //{a:2}
console.log(target); //{a:1}

  Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是仍是对象的话依然是浅拷贝

  Object.assign还有一些注意的点是:

  1. 不会拷贝对象继承的属性
  2. 不可枚举的属性
  3. 属性的数据属性/访问器属性
  4. 可以拷贝Symbol类型

  可以理解为Object.assign就是使用简单的=来赋值,遍历从右往左遍历源对象(sources)的所有属性用 = 赋值到目标对象(target)上

    3.2 扩展运算符

      var cloneObj = { ...obj };

var obj = {a:1,b:{c:1}}
var obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

  3.3 Array.prototype.slice()

  slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。

  在ES6以前,没有剩余运算符,Array.from的时候可以用 Array.prototype.slice将arguments类数组转为真正的数组,它返回一个浅拷贝后的的新数组

Array.prototype.slice.call({0: "aaa", length: 1}) //["aaa"]

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

    深拷贝

  将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

  JSON.stringify()

  JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象
通过JSON.stringify实现深拷贝有几点要注意
  • 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
  • 无法拷贝不可枚举的属性,无法拷贝对象的原型链
  • 拷贝Date引用类型会变成字符串
  • 拷贝RegExp引用类型会变成空对象
  • 对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
  • 无法拷贝对象的循环应用(即obj[key] = obj)
function Obj() {
    this.func = function () {
        alert(1) 
    };
    this.obj = {a:1};
    this.arr = [1,2,3];
    this.und = undefined;
    this.reg = /123/;
    this.date = new Date(0);
    this.NaN = NaN
    this.infinity = Infinity
    this.sym = Symbol(1)
}
var obj1 = new Obj();
Object.defineProperty(obj1,‘innumerable‘,{
    enumerable:false,
    value:‘innumerable‘
})
console.log(‘obj1‘,obj1);
var str = JSON.stringify(obj1);
var obj2 = JSON.parse(str);
console.log(‘obj2‘,obj2);

  技术图片

 

 虽说通过JSON.stringify()方法深拷贝对象也有很多无法实现的功能,但是对于日常的开发需求(对象和数组),使用这种方法是最简单和快捷的

使用第三方库实现对象的深拷贝

1.lodash

2.jQuery

自己来实现一个深拷贝函数

递归

这里简单封装了一个deepClone的函数,for in遍历传入参数的值,如果值是引用类型则再次调用deepClone函数,并且传入第一次调用deepClone参数的值作为第二次调用deepClone的参数,如果不是引用类型就直接复制
var obj1 = {
    a:{
        b:1
    }
};
function deepClone(obj) {
    var cloneObj = {}; //在堆内存中新建一个对象
    for(var key in obj){ //遍历参数的键
       if(typeof obj[key] ===‘object‘){ 
          cloneObj[key] = deepClone(obj[key]) //值是对象就再次调用函数
       }else{
           cloneObj[key] = obj[key] //基本类型直接复制值
       }
    }
    return cloneObj 
}
var obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2); //{a:{b:1}}

  

但是还有很多问题

  • 首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型
  • 这里只是针对Object引用类型的值做的循环迭代,而对于Array,Date,RegExp,Error,Function引用类型无法正确拷贝
  • 对象循环引用成环了的情况

可参考下面的 对象深拷贝和浅拷贝 作者自己实现的深拷贝

参考:深拷贝与浅拷贝的区别  深拷贝与浅拷贝的区别 对象深拷贝和浅拷贝

以上是关于深拷贝和浅拷贝的主要内容,如果未能解决你的问题,请参考以下文章

学习:类和对象——深拷贝和浅拷贝

深拷贝和浅拷贝

Python高级详解 深拷贝和浅拷贝

Python高级详解 深拷贝和浅拷贝

如何实现数组深拷贝和浅拷贝?

深拷贝和浅拷贝的区别?