浅拷贝与深拷贝

223 阅读3分钟

定义

  • 后进者先出,先进者后出,简称 后进先出(LIFO),这就是典型的栈结构。
  • 新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。
  • 在栈里,新元素都靠近栈顶,旧元素都接近栈底。
  • 从栈的操作特性来看,是一种 操作受限的线性表,只允许在一端插入和删除数据。
  • 不包含任何元素的栈称为空栈。

定义

  • 堆数据结构是一种树状结构。它的存取数据的方式,与书架与书非常相似。我们不关心书的放置顺序是怎样的,只需知道书的名字就可以取出我们想要的书了。 好比在 JSON 格式的数据中,我们存储的 key-value 是可以无序的,只要知道 key,就能取出这个 key 对应的 value。

栈和堆的区别

  • 堆是动态分配内存,大小不一,不会自动释放
  • 栈是自动分配相对固定大小的内存空间,由系统自动释放
  • 栈是线性结构,后进先出,易管理

Javascript中的栈与堆

  • Javascript中的Boolean、Null、Undefined、Number、String、Symbol都是基本类型数据,保存在栈内存中,有固定的大小,通过按值访问,并由系统自动分配和自动释放。
  • Javascript中的Object、Array、Function、RegExp、Date是引用类型数据,保存在堆内存中,大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,Javascript不允许直接访问对内存中的位置,因此操作对象时,实际操作的是对象的引用

例如

let a = 12;//基本类型 
let string = 'this is string';//基本类型
let boolean = false;//基本类型
let obj = {a: 'aa', b: 'bb'};//基本类型
let array = [1, 2, 3];//基本类型 

基本类型的复制

let a = 20;
let b = a;
b = 30;
console.log(1); //30
  • 基本类型的数据发生复制时,系统会自动为新的变量分配一个新值,相互独立,互不影响。

引用类型的复制

let obj = {a: 5, b: 10};
let copyObj = obj;
copyObj.a = 20;
console.log(obj.a);//20
  • 引用类型的复制,为新的变量分配一个新的值,保存在栈内存中,这个值仅作为引用类型的一个地址指针
  • 2个变量指针指向相同

浅拷贝与深拷贝

浅拷贝:引用类型数据的复制就是浅拷贝的过程,2个数据都指向同一个内存空间。修改了其中一个值,另一个也跟着改变。
深拷贝:复制得到的访问地址指向不同的内存空间。修改其中一个值,另一个不会改变。

数组的深拷贝

  • 循环
    function copy(arr) {
        let temp = [];
        for (let i = 0; i < arr.length; i++) {
            temp.push(arr[i])
        }
        return temp;
    }

    let arr = [1, 2, 3, 4];
    let copyArr = copy(arr);
    copyArr[0] = 10;
    console.log(arr, copyArr) //[1,2,3,4] [10,2,3,4] 
  • slice()方法
    let arr2 = [1, 2, 3, 4];
    let sliceArr = arr2.slice(0);
    sliceArr[0] = 11;
    console.log(arr, sliceArr) //[1,2,3,4] [11,2,3,4]
  • concat()方法
    let arr3 = [1, 2, 3, 4];
    let concatArr = arr3.concat();
    concatArr[0] = 0;
    console.log(arr, concatArr) //[1,2,3,4] [0,2,3,4]
  • ES6扩展运算
    let arr4 = [1, 2, 3, 4];
    let [...spreadArr] = [...arr4];
    spreadArr[0] = 10;
    console.log(arr, spreadArr) //[1,2,3,4] [0,2,3,4]
  • JSON.parse与JSON.stringify
    let arr5 = [1, 2, 3, 4];
    let stringifyArr = JSON.parse(JSON.stringify(arr5));
    stringifyArr[0] = 10;
    console.log(arr, stringifyArr); //[1,2,3,4][10,2,3,4]

注意:该方法在数据量比较大时,会有性能问题。

对象的深拷贝

  • 循环
    function copy(obj) {
        let copyObj = {};
        for (let key in obj) {
            copyObj[key] = obj[key];
        }
        return copyObj;
    }

    let obj = {x: 10, y: 20};
    let copyObj = copy(obj);
    copyObj.x = 20;
    console.log(obj, copyObj)   //{x:10,y:20} {x:20,y:20}
  • JSON.parse 与 JSON.stringify (原理同数组一样)

     注意:进行JSON.stringify() 序列化的过程中,undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
    
  • es6 扩展运算(原理同数组一样)

  • Object.assign()

    let obj = {x: 10, y: 20};
    let copyObj = Object.assign({}, obj);
    copyObj.x = 20;
    console.log(obj, copyObj)   //{x:10,y:20} {x:20,y:20}
 注意:Object.assign() 只能实现一维对象的深拷贝。