js stack栈与heap堆的区别与含义

作为前端了解栈与堆是非常必要的,如果不能充分理解那么js的深拷贝、浅拷贝就没办法正确使用。

当然如果你是大学计算机专业相信你因该了解很透彻了,如果文章有不足之处请多多指教

一、栈与堆概念

(stack):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;(通俗讲:自动分配的内存空间,它由系统自动释放
(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。(通俗讲:动态分配的内存,大小也不一定会自动释放

说到这就不得不提js数据类型

二、js数据类型

基本数据类型: Number、String、Boolean、Null、 Undefined、Symbol(ES6)这些类型可以直接操作保存在变量中的实际值。

引用类型: Function,Array,Object,当我们需要访问这三种引用类型的值时,首先得从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

 三、栈与堆区别

1、缓存方式区别
  1).栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
  2).堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

 2、堆栈数据结构区别

  1).栈(数据结构):一种先进后出的数据结构。

  2).堆(数据结构):堆可以被看成是一棵树,如:堆排序;

了解完栈与堆,我们来说说浅拷贝与深拷贝;上面我们介绍的数据的基本类型

四、基本数据类型(存放在栈中)

基本数据类型是指存放在中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问。基本数据类型的值是没有办法添加属性和方法的

案例一
let a = 10;
let b = a;
b = 20;
console.log(a); // 10值
console.log(b); // 20值

解释:let a = 10;let b =a;是 a 先取出 10,copy 一份放到 b 里面,改变 a 的值,b 的值是不变的,再把 a=20;时 b 的值还是 10,不发生改变

 

案例二
let arr = [1,2];
let arr1 =arr;
arr.push(3);
console.log(arr) // [1,2,3]
console.log(arr1) // [1,2,3]

解释:引用值是在栈内存里面放堆的地址,拷贝的也是地址,所以改变 arr,arr1 也变了,在栈中arr,arr1同时指向一个堆,

let arr = [1,2]; let arr1 =arr; arr = [1,3]; document.write(arr1)

arr = [1,3];是新建了一个新的房间。arr1 是 1,2,现在是插入新引入值”房间”,

会在堆里面重新申请一间房,并指向新房间

五、深拷贝

 

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响到原对象。

 1、数组的深拷贝:

 slice(slice()操作数组时,不会对原数组有影响,会产出一个新的数组。)

 let arr1 = [1, 42, 5, 6]
 let arr2 = arr1.slice()
 arr2[0] = 100
 console.log(arr1) // [1, 42, 5, 6]
 console.log(arr2) // [100, 42, 5, 6]

concat  (concat()方法,能够连接两个数组,同样不会改变原来的数组。用一个空数组连接另一个数组,即可实现深拷贝)

let arr3 = ['cat', 'dog', 'pig']
let arr4 = [].concat(arr3)
arr3[2] = 'big pig'
console.log(arr3) // ['cat', 'dog', 'big pig']
console.log(arr4) // ['cat', 'dog', 'pig']

ES6 ...拓展运算符

let arr5 = [0, 0, 1]
let arr6 = [...arr5]
arr5[0] = 10000
console.log(arr5) // [10000, 0, 1]
console.log(arr6) // [0, 0, 1]
我经常在数组的深拷贝中用到。

Array.from

Array.from()方法能从一个类似数组(伪数组)或可迭代对象中返回一个新的数组实例。通过Array.from()方法能获取到一个数组的深拷贝

let arr7 = [1, 2, 3]
let arr8 = Array.from(arr7)
arr7[1] = 1000
console.log(arr7) // [1, 1000, 3]
console.log(arr8) // [1, 2, 3]

2、Object的深拷贝 

Object.assign()

ES6的Object.assign() Object.assign(target, …sources)用于对象的合并,将源对象中的所有可枚举属性,复制到目标对象中,并返回合并后的目标对象。后来的源对象的属性值,将会覆盖它之前的对象的属性。

 let person = {
     name: 'xia',
     age: 25,
     height: 160
 }
 let otherPerson = Object.assign({}, person)
 person.age = 30

 console.log(person)
 console.log(otherPerson)

 Object.create

Object.create 使用现有的对象来创建一个新的对象。同样的,也只能解决第一层的复制。

let person = {
    name: "宁次",
    age: 20,
    sister: {
        name: "雏田",
        age: 18,
    },
}
 
let person2 = Object.create(person)
 
person2.name = "鸣人"
 
alert(person.name) // 宁次, 第一层 ok
 
person2.sister.name = "鸣妹"
alert(person.sister.name) // 鸣妹,第二层仍是浅拷贝

注意:Object.create 对于数组的深拷贝不理想。

 JSON.parse 和 JSON.stringify

先用 JSON.stringify 将对象序列化成字符串,再用 JSON.parse 解析回来,可以达到深拷贝的效果,并且对于多层对象同样有效

let person = {
    name: "宁次",
    age: 20,
    sister: {
        name: "雏田",
        age: 18,
    },
}
 
let person2 = JSON.parse(JSON.stringify(person))
 
person2.name = "鸣人"
alert(person.name) // 宁次, 第一层 ok
 
person2.sister.name = "鸣妹"
alert(person.sister.name) // 雏田,多层对象依旧 OK

 注意:JSON.parse 和 JSON.stringify 依旧不是完美的,像一些方法或用户自定义的类型则不能有效的拷贝。

let person = {
    name: "宁次",
    age: 20,
    sayHello() {
        alert("你好~")
    },
}
 
let person2 = JSON.parse(JSON.stringify(person))
 
person2.sayHello() // error:person2.sayHello is not a function

structuredClone

structuredClone 是内置的一个方法,对于多层对象也能很好地执行拷贝,但同样对方法或自定义对象无力。

let person = {
    name: "宁次",
    age: 20,
    sister: {
        name: "雏田",
        age: 18,
    },
}
 
let person2 = structuredClone(person)
 
person2.name = "鸣人"
 
alert(person.name) // 宁次, 第一层 ok
 
person2.sister.name = "鸣妹"
alert(person.sister.name) // 雏田,多层对象依旧 OK

 对于方法或自定义对象无力无能为力。

let person = {
    name: "宁次",
    age: 20,
    sayHello() {
        alert("你好~")
    },
}
 
// Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': sayHello() {alert("你好~")} could not be cloned
let person2 = structuredClone(person)

自己实现

自己实现的灵活度就很高,根据代码实现不同达到的效果与运行效率也会有所不同。

以下是三眼鸭通过递归实现的一个深拷贝函数,经测试能正确拷贝常见的多层对象、数组、方法。

// 深拷贝
function deepclone(obj) {
    function copyList(arr) {
        let result = []
        for (let item of arr) {
            result.push(this.deepclone(item))
        }
        return result
    }
 
    if (typeof obj === "object") {
        if (Array.isArray(obj)) {
            return copyList(obj)
        } else {
            let result = {}
            for (let key in obj) {
                result[key] = deepclone(obj[key])
            }
            return result
        }
    } else {
        return obj
    }
}
 
let person = {
    name: "宁次",
    age: 20,
    sayHello() {
        alert("你好")
    },
    sister: {
        name: "雏田",
        age: 18,
    },
    arr: [1, 2, 3],
}
 
let person2 = deepclone(person)
 
person2.name = "鸣人"
 
alert(person.name) // 宁次, 第一层 ok
 
person2.sister.name = "鸣妹"
alert(person.sister.name) // 雏田,多层对象依旧 OK
 
person2.arr[0] = "x"
alert(person.arr[0]) // 1, 数组也 ok
 
person2.sayHello() // 你好,方法也 ok

 lodash

let person = {
    name: "宁次",
    age: 20,
    sayHello() {
        alert("你好")
    },
    sister: {
        name: "雏田",
        age: 18,
    },
    arr: [1, 2, 3],
}
 
let person2 = _.cloneDeep(person)
 
person2.name = "鸣人"
 
alert(person.name) // 宁次, 第一层 ok
 
person2.sister.name = "鸣妹"
alert(person.sister.name) // 雏田,多层对象依旧 OK
 
person2.arr[0] = "x"
alert(person.arr[0]) // 1, 数组也 ok
 
person2.sayHello() // 你好,方法也 ok

六、栈和堆的溢出六: 堆和栈的溢出

如果想要堆溢出,比较简单,可以循环创建对象或大的对象; 如果想要栈溢出,可以递归调用方法,这样随着栈深度的增加,JVM
(虚拟机)维持着一条长长的方法调用轨迹,直到内存不够分配,产生栈溢出。

总结:使用浅拷贝和深拷贝要取决于当时的场景

使用浅拷贝的情况下要留意对数据的修改,要想清楚修改后影响到所有引用的对象这个结果是否是你想要的,不是的话请使用深拷贝。
对于函数内部的代码则要尽量避免修改传入进来的对象,如果必须要修改,则要在函数说明中醒目提示。
也不要盲目频繁地使用深拷贝,以免造成大量的内存浪费。
对于深拷贝也不必选择大而全地完全拷贝的方法,如果只是单层对象或数组,简单地使用解构赋值就是最好的方法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jim-zf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值