前端知识汇总——01数据存储

本文详细探讨了JavaScript中的栈和堆数据结构,包括它们的定义、特点、内存分配方式和访问方式。此外,文章还介绍了JavaScript的数据类型,以及传值与传址的区别,涉及内存分配和垃圾回收机制,以及可能导致内存泄漏的情况。
摘要由CSDN通过智能技术生成

一、栈和堆

栈(stack):是栈内存的简称,栈是自动分配相对固定大小的内存空间,并由系统自动释放,栈数据结构遵循FILO(first in last out)先进后出的原则,较为经典的就是乒乓球盒结构,先放进去的乒乓球只能最后取出来。

堆(heap):是堆内存的简称,堆是动态分配内存,内存大小不固定,也不会自动释放,堆数据结构是一种无序的树状结构,同时它还满足key-value键值对的存储方式;我们只用知道key名,就能通过key查找到对应的value。比较经典的就是书架存书的例子,我们知道书名,就可以找到对应的书籍。

 

堆和栈都是计算机中常用的内存数据结构,它们在数据结构、特点、优劣方面有一些差异,下面是它们的具体异同点:

1.数据结构:栈和堆都是内存数据结构,栈是一种线性结构,堆是一种树形结构

2.内存分配方式:栈采用的是静态内存分配,系统在编译阶段就确定了分配给栈的内存空间,而且栈内存的释放是由系统自动完成的。而堆采用的是动态内存分配,程序在运行时可以向操作系统请求动态分配一段空间,然后使用完之后再手动释放。

3.存储内容:栈中存储的是函数的调用和局部变量,而堆中存储的是对象的实例。

4.访问方式:栈是一种后进先出(LIFO)的数据结构,只有栈顶的元素可以被访问和操作,而堆是一棵树形结构,其存储的元素可以被通过指针或引用访问。

5.内存分配效率:栈的内存分配效率比较高,因为栈是一种内存结构,其内存块相互紧凑,可以直接通过指针操作,而堆的内存分配效率相对低一些,因为需要手动分配和释放内存空间。

优势:

栈:栈内存分配效率高,读写速度快,由于栈是一种线性结构,因此其内存使用效率高。

堆:堆可以动态分配内存,因此在使用时比较灵活,同时堆中存储的对象可以被持久化,不会随着函数调用结束而被销毁。

缺点:

栈的缺点在于栈内存空间有限,存储空间比堆小,且存在变量过多或递归调用可能引起栈溢出的问题,比较容易导致系统崩溃。

堆的缺点在于内存分配和释放需要手动进行操作,如果分配的内存空间过多或者没有及时释放会导致内存泄漏或内存溢出的问题。

特点:

栈:可以快速地入栈、出栈,栈顶元素可以被随时访问和操作,适合存储函数调用、局部变量等栈式数据。

堆:具有动态分配和释放内存空间的灵活性,存储的对象实例可以被持久化,适合存储较大的数据结构,如数组、对象等。


二、数据类型

JavaScript有9种数据类型,分别为:字符串(String)、数字(Number)、布尔(Boolean)、Null、Undefined、Symbol、对象(Object)、数组(Array)、函数(Function)。可以使用typeof来检测数据类型。其中基本数据类型有String、Number、Boolean、Null、Undefined、Symbol。引用数据类型有对象(Object)、数组(Array)、函数(Function),其中正则表达式是属于引用数据类型。在JavaScript中,正则表达式是通过RegExp对象来表示的,而对象都是引用数据类型。因此,正则表达式也是引用数据类型的一种。

以下是一个例子,展示如何使用typeof检测数据类型:

let str = "Hello World";
let num = 123;
let bool = true;
let nul = null;
let und = undefined;
let sym = Symbol();let obj = {name: "John", age: 30};
let arr = [1, 2, 3];
function func() {
  return "This is a function";
}

console.log(typeof str); // 输出:string
console.log(typeof num); // 输出:number
console.log(typeof bool); // 输出:boolean
console.log(typeof nul); // 输出:object
console.log(typeof und); // 输出:undefined
console.log(typeof sym); // 输出:symbol
console.log(typeof obj); // 输出:object
console.log(typeof arr); // 输出:object
console.log(typeof func); // 输出:function
console.log(typeof NaN); // 输出:number

基本数据类型:Undefined,String,Boolean,Null,Number,都是直接按值存放在栈内存中,占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。引用数据类型:指那些可能由多个值构成的对象,如对象(Object)、数组(Array)、函数(Function) ,它们是通过拷贝和new出来的,这样的数据存储于堆中。

三、 传值和传址的区别

基本类型:采用的是值传递。

引用类型:则是地址传递。

引用类型的数据的地址指针是存储于栈中的,将存放在栈内存中的地址赋值给接收的变量。当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据(保存在堆内存中,包含引用类型的变量实际上保存的不是变量本身,而是指向该对象的指针)。

四、 内存分配垃圾回收

1.内存分配

(1)栈内存:线性有序存储,容量小,系统分配效率高。

(2)堆内存:首先要在堆内存新分配存储区域,之后又要把指针存储到栈内存中,效率相对就要低一些了。

2.垃圾回收

(1)栈内存:变量基本上用完就回收了,相比于堆来说存取速度会快,并且栈内存中的数据是可以共享的。

(2)堆内存:堆内存中的对象不会随方法的结束而销毁,就算方法结束了,这个对象也可能会被其他引用变量所引用(参数传递)。创建对象是为了反复利用(因为对象的创建成本通常较大),这个对象将被保存到运行时数据区(也就是堆内存)。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。

五、浏览器垃圾回收机制

1.垃圾回收的概念

垃圾回收:JavaScript代码运⾏时,需要分配内存空间来储存变量和值。当变量不在参与运⾏时,就需要系统收回被占⽤的内存空间,这就是垃圾回收。

2.回收机制

● Javascript 具有⾃动垃圾回收机制,会定期对那些不再使⽤的变量、对象所占⽤的内存进⾏释放,原理就是找到不再使⽤的变量,然后释放掉其占⽤的内存。

● JavaScript中存在两种变量:局部变量和全局变量。全局变量的⽣命周期会持续要⻚⾯卸载;⽽局部变量声明在函数中,它的⽣命周期从函数执⾏开始,直到函数执⾏结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执⾏结束后,这些局部变量不再被使⽤,它们所占有的空间就会被释放。

● 不过,当局部变量被外部函数使⽤时,其中⼀种情况就是闭包,在函数执⾏结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使⽤,所以不会回收。

3.垃圾回收的⽅式

浏览器通常使⽤的垃圾回收⽅法有两种:标记清除,引⽤计数。

● 标记清除是浏览器常⻅的垃圾回收⽅式,当变量进⼊执⾏环境时,就标记这个变量“进⼊环境”,被标记为“进⼊环境”的变量是不能被回收的,因为他们正在被使⽤。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。垃圾收集器在运⾏的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引⽤的标记。⽽在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经⽆法访问到这些变量了。最后。垃圾收集器完成内存清除⼯作,销毁那些带标记的值,并回收他们所占⽤的内存空间。

● 另外⼀种垃圾回收机制就是引⽤计数,这个⽤的相对较少。引⽤计数就是跟踪记录每个值被引⽤的次数。当声明了⼀个变量并将⼀个引⽤类型赋值给该变量时,则这个值的引⽤次数就是1。相反,如果包含对这个值引⽤的变量⼜取得了另外⼀个值,则这个值的引⽤次数就减1。当这个引⽤次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运⾏时,这个变量所占有的内存空间就会被释放出来。这种⽅法会引起循环引⽤的问题:例如: obj1 和 obj2 通过属性进⾏相互引⽤,两个对象的引⽤次数都是2。当使⽤循环计数时,由于函数执⾏完后,两个对象都离开作⽤域,函数执⾏结束,obj1 和 obj2 还将会继续存在,因此它们的引⽤次数永远不会是0,就会引起循环引⽤。

function fun() {
  let obj1 = {}
  let obj2 = {}
  obj1.a = obj2 // obj1 引用了 obj2
  obj2.a = obj1 // obj2 引用了 obj1
}

这种情况下,就要⼿动释放变量占⽤的内存:

obj1.a = null
obj2.a = null

4.减少垃圾回收

虽然浏览器可以进⾏垃圾⾃动回收,但是当代码⽐较复杂时,垃圾回收所带来的代价⽐较⼤,所以应该尽量减少垃圾回收。

● 对数组进⾏优化:在清空⼀个数组时,最简单的⽅法就是给其赋值为[ ],但是与此同时会创建⼀个新的空对象,可以将数组的⻓度设置为0,以此来达到清空数组的⽬的。

● 对 object 进⾏优化:对象尽量复⽤,对于不再使⽤的对象,就将其设置为null,尽快被回收。

● 对函数进⾏优化:在循环中的函数表达式,如果可以复⽤,尽量放在函数的外⾯。

2. 哪些情况会导致内存泄漏

5.以下四种情况会造成内存的泄漏

● 意外的全局变量:由于使⽤未声明的变量,⽽意外的创建了⼀个全局变量,⽽使这个变量⼀直留在内存中⽆法被回收。

● 被遗忘的计时器或回调函数:设置了 setInterval 定时器,⽽忘记取消它,如果循环函数有对外部变量的引⽤的话,那么这个变量会被⼀直留在内存中,⽽⽆法被回收。

● 脱离 DOM 的引⽤:获取⼀个 DOM 元素的引⽤,⽽后⾯这个元素被删除,由于⼀直保留了对这个元素的引⽤,所以它也⽆法被回收。

● 闭包:不合理的使⽤闭包,从⽽导致某些变量⼀直被留在内存当中。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱在 旅途

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

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

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

打赏作者

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

抵扣说明:

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

余额充值