likes
comments
collection
share

由浅入深了解深浅拷贝

作者站长头像
站长
· 阅读数 15

前言

深浅拷贝一直是前端里面的一个热门考点,不难,但是平常很实用,且面试方面也是经常被拿出来考查。

所以该说不说,深浅拷贝是一个合格的前端开发者必须掌握的一点。

正文

深浅拷贝介绍

什么是深拷贝?什么是浅拷贝?

深拷贝:深拷贝是对于对象的一个深度的复制,但是说复制其实不太准确,因为深拷贝从里到外都已经是一个新的对象了,与其说是复制,不如说是“照葫芦画瓢”,照着原本的对象创建出的一个新的对象,而新的对象对于原本的对象只不过是进行属性上的赋值,而不是复制,遇到基本类型就赋值,遇到引用数据类型就递归遍历到基本数据类型,再赋值,白话来说就是深拷贝出来的对象不管做任何修改都不会影响原对象

浅拷贝:浅拷贝是对于对象的一个“低层面”的一个复制,所谓低层面说白了就是该对象的第一层属性进行一次复制,不会进行更深层次的复制,如果遇到基本数据类型,就会进行赋值,而遇到引用类型则会进行复制。

那么出现深浅拷贝的原因是什么呢?

在 Javascript 里面,数据类型被分为两类,一类是基本数据类型(Number,String,Boolean,null,undefined,Symbol,Bigint),另一类则是引用类型(Object,Array,Function),而区别就是发生在这两种数据类型的区别上的。

基本数据类型因为占用内存较小,被直接存放在栈中,每次去使用基本数据类型变量的时候都等同于直接去栈空间里拿到这个值进行操作,而基本类型复制的时候,计算机会在栈内存里重新开辟出一个新的空间,用来存放新的基本数据类型,而基本数据类型的复制不应该叫复制,更应该叫“赋值”。

引用数据类型,因为大小没有一个固定的大小1,通常会很大,所以它被存放于堆空间内,而一般计算机不会直接去堆空间去取数据,所以引用数据类型在被存放在堆空间的同时,还会在栈内存空间去创建一个“引用地址”,即指针,它指向堆空间所存放的引用数据类型。

每次我们去拿取引用数据类型的变量的时候,都是先去拿存放在栈空间的引用地址,在通过引用地址的指向去到堆空间拿取引用数据变量;所以我们通常对引用类型进行各种行为其实都是在操作栈空间里的引用地址,复制也是同理,我们进行引用类型复制的时候其实就是对栈空间内的引用地址进行复制,最后还是指向同一个引用数据变量,所以就会出现下面这个情况。

let arr1 = [1,2,3];
let arr2 = arr1;
arr2[0] = 2;

console.log(arr1);    //[2,2,3]
console.log(arr2);    //[2,2,3]

修改复制后的数组,原数组也会随之改变,引用两个引用类型变量所指向的都是同一个堆内存地址。

那么问题来了,如果我们想要去对引用数据类型进行一个类似于简单数据类型的复制怎么办?(即我操作复制后的变量,不会影响到原变量)

深浅拷贝原理

刚才说到怎么让一个引用数据类型的变量也能做到简单数据类型变量的那种复制对吧?其实很简单,让它遵循简单数据类型的原则不就可以了吗?我们可以把引用数据类型里面的每一个属性或者子元素拆分出来,一个个单独进行“赋值”,那么创造出来的新的变量就会是一个全新的变量,而不是与原变量共用一个堆空间。

比如数组的“复制”:

let arr1 = [1,2,3];
let arr2 = [];
for(let i=0;i<arr1.length;i++){
    arr2.push(arr1[i])
}
arr2[0] = 2;

console.log(arr1);    //[1,2,3]
console.log(arr2);    //[2,2,3]

这段代码里,arr2 是我们新创建的一个数组,而里面每一个子元素都是通过对 arr1 数组里的子元素进行赋值添加的,而每个子元素都相当于是一个基本数据类型。

那么如果遇到对象呢?其实也和数组是是一样的方式

let obj1 = {name:'李华',sex:'男',age:'18'};
let obj2 = {};
for(let i in obj1){
    obj2[i] = obj1[i];
}
obj2.name = '橙心';

console.log(obj1);    //{name: '李华', sex: '男', age: '18'}
console.log(obj2);    //{name: '橙心', sex: '男', age: '18'}

而这就是深浅拷贝的基本原理

浅拷贝

浅拷贝上面也是说了,它只对引用数据类型进行一层次的拷贝,即不管数组是几维数组,对象有多少层属性,浅拷贝都只会对它的第一层属性进行复制。

而下面这就是浅拷贝的实现:

// 浅拷贝函数
function shallowCopy(obj) {
  // 判断参数是否为对象类型,如果不是则直接返回
  if (typeof obj !== "object" || obj === null) return obj;
  if(Array.isArray(obj)){   //判断是否是数组
      let newObj = [];
      for(let item in obj){
          newObj.push(item);
      }
      return newObj;
  }else{         //若是对象
      // 创建一个新对象,用于存储拷贝后的属性值
      let newObj = {};
      // 遍历原对象的属性,并将其复制到新对象上
      for (let key in obj) {
        // 判断是否为自身属性(非原型链上继承来的)
        if (obj.hasOwnProperty(key)) {
          // 如果是,则直接赋值即可(注意这里可能会拷贝引用地址)
          newObj[key] = obj[key];
        }
      }
      // 返回新对象
      return newObj;
  }
}

咱们再来用一点例子测试测试:

let obj1 = {name:'李华',fimaly:{father:'李青'}}
let obj2 = shallowCopy(obj1);
obj2.name = '刘然'
obj2.fimaly.father = '刘琦';
console.log(obj1);
console.log(obj2);

由浅入深了解深浅拷贝

然后你会发现,我们改变复制后的对象的第一层的 name 属性,原来的 obj1 并没有因此而改动,但是我们改fimaly 属性下的 father 属性却同时让原来的 obj1 身上的 father 属性同时也被修改了,这就是浅拷贝,仅仅拷贝第一层次。

除此之外,js能做到浅拷贝的内置方法还有:Object.assign(),扩展运算符“...”,Array.prototype.concat(),Array.prototype.slice()。

深拷贝

深拷贝,顾名思义,深层次的复制;文章上面有说拷贝的原理,但是上面写的那些例子不过是一层次的例子,真正要做到深拷贝,需要对这个引用类型由里到外的进行一次“复制”。

那么,如何做到深层次的拷贝呢?

答案是:递归!

递归是我们常用来处理深层次对象,二叉树,或者排列拼接常用的手段,那么深层次的拷贝,自然少不了递归调用,对每一层对象进行遍历;

以下为深拷贝递归算法实现:

function deepCopy(obj){
    let newObj = null;     // 创建一个用于存储拷贝赋值的新对象
    if(typeof obj != 'object' || obj === null){   //判断如果是基本数据类型直接赋值
        newObj = obj;
    }else{
        if(Array.isArray(obj)){   //判断是数组还是对象
            newObj = [];        //当为数组时给新对象进行赋值为数组
            for(let item in obj){      //遍历原数组属性,给新数组进行增加
                newObj.push(deepCopy(obj[item]))
            }
        }else{           //当为对象时
            newObj = {};        //当为对象时给新对象进行赋值为对象
            for(let item in obj){       // 遍历对象属性,给新对象的属性与原对象属性进行赋值
                newObj[item] = deepCopy(obj[item])
            }
        }
    }
    return newObj
}

这里咱们再用刚才那个例子吧

let obj1 = {name:'李华',fimaly:{father:'李青'}}
let obj2 = deepCopy(obj1);
obj2.name = '刘然'
obj2.fimaly.father = '刘琦';
console.log(obj1);
console.log(obj2);

由浅入深了解深浅拷贝

这下是不是对了?不论是深层次还是第一层次都是做到了改变新数组而原数组不会进行任何改变,这就是深拷贝。而 JS 能做到深拷贝的内置 API 也是有 JSON.parse(JSON.stringify(obj)) 的。

转载自:https://juejin.cn/post/7241461049781567548
评论
请登录