js 堆和栈的个人理解, 以及实现几种深拷贝(有问题及时指出啊)

930 阅读6分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

前言

其实我对这个东西(堆/栈)也挺模糊,翻阅了好多文章,想着总结一篇,如果理解有误请指出

大致说一下什么是, 看文章都说他先进后厨,能够直接修改的就是是自动分配相对固定大小的内存空间,并由系统自动释放,栈内存变量基本上用完就回收了。

比如:数值(Number)字符串(String)null(Null)undefinedBoolean

个人理解就是,每个都是独立的相互不影响,每个都会开辟新的空间。

关于栈的代码示例:

数值类型

//示例1

let a = 123;
let b = a;

a = 6;
console.log(a,b) //输出 6 123

//示例2
let a = 123;
let b = a;

b = 1;
console.log(a,b) //输出 123 1

总结: 数值类型的更改相互不影响

字符串类型

//示例1
let a = 'string1';
let b = a;

a = 'string2';
console.log(a,b) //输出 string2 string1

//示例2
let a = 'string1';
let b = a;

b = 'string2';
console.log(a,b) //输出 string1 string2

总结: 字符串类型的更改相互不影响

null类型

//示例1
let a = null;
let b = a;

a = 1;
console.log(a,b) //输出 1 null


//示例2
let a = null;
let b = a;

b = 1;
console.log(a,b) //输出 null 1

总结: null类型的更改相互不影响

Boolean类型

let a = false;
let b = a;

a = true;
console.log(a,b) //输出 true false


//示例2
let a = false;
let b = a;

b = true;
console.log(a,b) //输出 false true

总结: Boolean类型的更改相互不影响

undefined类型

let a = undefined;
let b = a;

a = 1;
console.log(a,b) //输出 1 undefined


//示例2
let a = undefined;
let b = a;

b = 1;
console.log(a,b) //输出 undefined 1

总结: undefined类型的更改相互不影响

栈个人理解总结

通过上面代码示例发现数值(Number)字符串(String)null(Null)undefinedBoolean 就是会开辟一个栈空间,每个都是一个独立的空间,相互更改是不受影响的

是堆内存的简称,堆是动态分配内存,内存大小不固定,也不会自动释放,堆数据结构是一种无序的树状结构,指针存放在内存中

那些类型会进入这个空间: ObjectArrayFunction

个人理解就是在一个空间栈中存放,然后是引用类型的关系,就是中的a被更改了, 然后引用a的都会被更改

214d4b860605749dde82e083b488ddd.png

关于堆的代码示例:
Object类型

//示例1
let a = {name: 1};
let b = a;
b.name = 2;
console.log(a,b) //输出:{name: 2} {name: 2}


//示例2
let a = {name: 1};
let b = a;

a.name = 2;
console.log(a,b) //输出:{name: 2} {name: 2}

//示例3

let a = {name: 1};
let b = a;

a = {name: 2};
a.name = 3;
console.log(a,b) //输出:{name: 3} {name: 1}

总结:(示例1、2)是用了一个堆内存的空间,当堆内存发生变化,引用该堆内存的变量都会发生变化, 当重新定义一个新的堆内存的话就跟之前引用的没关系了(示例3)

Array类型

//示例1
let a = [1,2,3,4];
let b = a;

a[0] = 'h';
console.log(a, b) //输出:["h", 2, 3, 4] (4) ["h", 2, 3, 4]


//示例2
let a = [1,2,3,4];
let b = a;

b[0] = 'h';
console.log(a, b) //输出:["h", 2, 3, 4] (4) ["h", 2, 3, 4]

总结:array跟object一样a = b的话就是公用了一个堆内存,当该堆内存发生变化,a、b都回发生变化

function 类型


let a = function() {
    return 123
}

let b = a;

//个人理解就是 function 本来就是一个独立的函数 如果把函数当作一个堆内存的话  就函数更改了的话 对应得变量也会更改

//function的我不知道怎么举例..... 抱歉  如果您了解这块可以放在评论区 感谢

避免这种堆得引用类型(深拷贝)

深拷贝(一)

// 利用JSON.stringify()
//示例1
let a = {name: 1};
let b = JSON.parse(JSON.stringify(a));

a.name = 2;
console.log(a, b) // 输出:{name: 2} {name: 1}

//示例2
let a = {name: 1, fn(){}}
let b = JSON.parse(JSON.stringify(a))
a.name = 1;
console.log(a,b) //{name: 1, fn: ƒ} {name: 1}

总结:利用JSON.stringify()可以实现深拷贝,但是函数会丢失

深拷贝(二)

使用数组得slice 针对数组深拷贝

slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!) 用法:array.slice(start,end) start表示是起始元素的下标, end表示的是终止元素的下标
当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组

代码示例:

//示例1
let a = [1,2,3,4,5];

let b = a.slice();
a[0] = 'h'

console.log(a, b) //输出 ["h", 2, 3, 4, 5] (5) [1, 2, 3, 4, 5]

总结:slice适用于数组实现深拷贝

深拷贝(三)

使用数组得concat 针对数组深拷贝

concat() 不改变原数组,返回一个新得数组

代码示例:

//示例1
let a = [1,2,3,4,5];

let b = [].concat(a);
a[0] = 'h'

console.log(a, b) //输出 ["h", 2, 3, 4, 5] (5) [1, 2, 3, 4, 5]

总结:concat适用于数组实现深拷贝

深拷贝(四)

使用对象的Object.assign()实现深拷贝

代码示例:

//示例1:
let a = [1,2,3,4,5];

let b = Object.assign([], a);
a[0] = 'h'

console.log(a, b) //输出: ["h", 2, 3, 4, 5] (5) [1, 2, 3, 4, 5]


//示例2:

let a = {name: 1, fn(){}};
let b = Object.assign({}, a);

a.name = 2;

console.log(a, b) //输出:{name: 2, fn: ƒ} {name: 1, fn: ƒ}

总结:Object.assign()可以对数组和对象是实现深克隆。缺点:当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝

深拷贝(五)

es6扩展运算符...实现深克隆

代码示例:

//示例1:
let a = [1,2,3,4,5];

let b = [...a];
a[0] = 'h'

console.log(a, b) //输出: ["h", 2, 3, 4, 5] (5) [1, 2, 3, 4, 5]


//示例2:

let a = {name: 1, fn(){}};
let b = {...a};

a.name = 2;

console.log(a, b) //输出:{name: 2, fn: ƒ} {name: 1, fn: ƒ}


//示例3
let a = {name: 1, fn(){}, obj: {a: 1}};
let b = {...a};

a.obj.a = 2;

console.log(a, b) 
/**输出:{
    "name": 1,
    "obj": {
        "a": 2
    },
    fn(){}
}
{
    "name": 1,
    "obj": {
        "a": 2
    },
    fn(){}
}
*/

总结:...扩展运算符可以对数组和对象是实现深克隆。缺点:跟Object.assign一样当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝

深拷贝(五)

实现一个递归函数

function cloneDeep(source) {
    //判断它是数组 还是对象  定义一个新的数组或者对象
    let target = Array.isArray(source) ? [] : {}

    for (let key in source) {
        if (source.hasOwnProperty(key)) {
            if (source[key] && typeof source[key] === "object") {
                target[key] = cloneDeep(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target
}



//示例1、
let a = [1,2,3,4,5];
let b = cloneDeep(a);
a[0] = 'h';

console.log(a, b); //["h", 2, 3, 4, 5] (5) [1, 2, 3, 4, 5]


//示例2、
let a = {name:1, fn(){}, obj: {a: 1}};
let b = cloneDeep(a);

a.obj.a = 2;
console.log(a, b)
/**输出
 * 
 * {
 *   fn: ƒ fn(),
    name: 1,
    obj:{a: 2}
    }

    {
     fn: ƒ fn(),
     name: 1,
     obj:{a: 1}
    }
 * /

查阅相关资料

总结

我对的理解就这些,有发现我又不对的地方及时指出啊😀

结束语

  • 大家好 我是三原,多谢您的观看,我会更加努力(๑•̀ㅂ•́)و✧多多总结。
  • 每个方法都是敲出来验证过通过的,有需要可以放心复制。
  • 如果您给帮点个赞👍就更好了 谢谢您~~~~~
  • 期待您的关注