js有五种基本数据类型,string,number,boolean,null,undefind。这五种类型的赋值,就是值传递。特殊类型对象的赋值是将对象地址的引用赋值。这时候修改对象中的属性或者值,会导致所有引用这个对象的值改变。如果想要真的复制一个新的对象,而不是复制对象的引用,就要用到对象的深拷贝。

当一个变量存放基本数据类型时与复杂的数据类型时分别存在以下的特点:
  • 基本数据类型的特点:变量直接将基本数据类型的值存储在栈(stack)内存中
  • 引用数据类型的特点:变量将引用数据类型的引用(可以看作是地址)存储在栈(stack)内存,而对象本身存放在堆内存里,在栈中引用指向堆中的对象。

其实当解释器寻找引用值时,会首先检索其在栈中的地址引用,取得地址后从堆中获得对象。

20210323000358-README_2021-03-23

首先我们来看一个例子:
var num = 666;
var numCopy = num;
var obj = {name:"张三"};
var objCopy = obj;

对于以上的代码,变量的内存分配是在栈中,所以变量不可以存放堆中的对象,所以赋值就会出现两种情况:

  • 基本数据类型:将变量num的值拷贝一份存储在numCopy中
  • 赋值数据类型:将变量obj中直接存储的引用拷贝一份存储在objCopy中

20210323000255-README_2021-03-23

所以我们的Number类型的变量num通过简单的赋值得到了两份值(在栈内存不同的内存区域),而复杂数据类型obj通过赋值仅仅是得到了两份引用(在栈内存不同的内存区域),而实际的对象(在堆内存中是同一个值)只有一份。

所以我们可以知道,要拷贝一份复杂数据类型远没有我们想象的那样简单。

我们应该要注意一点,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。而简单数据类型由于情况简单,就是拷贝一份,所以不存在深拷贝和浅拷贝的说法。

浅拷贝和赋值对比
  • 赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的对象本身,因此,两个对象是联动的。

  • 浅拷贝:它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。但是属性值的拷贝是通过赋值来完成的。也就存在我们之前讨论的问题,如果属性是基本数据类型,拷贝的就是基本数据类型的值;如果属性是复杂数据类型,拷贝的就是引用 ,因此如果新旧对象其中一个改变了这个复杂数据类型的属性的对象本身,就会影响到另一个对象。所以仅仅是表面上得到了一个新的对象,其实内部的复杂数据类型还是原来的,会存在复杂数据类型的属性的引用相等的情况(共享同一个对象),所以叫做"浅"拷贝。

深浅拷贝的代码实现
  • 浅拷贝:concat、slice、JSON.stringify 都算是技巧类,可以根据实际项目情况选择使用,接下来我们思考下如何实现一个对象或者数组的浅拷贝。
var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
  • 深拷贝:一般的对象可以用Object的方法:assign,但也可以自己写一个递归,这里只写一下递归方式
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}