首页 > 其他分享 >JS堆、栈以及事件循环的概念

JS堆、栈以及事件循环的概念

时间:2023-10-12 21:37:42浏览次数:37  
标签:队列 数据类型 JS 概念 任务 循环 内存 栈中 执行

前言

其实一开始对栈、堆的概念特别模糊,只知道好像跟内存有关,又好像事件循环也沾一点边。面试薄荷的时候,面试官正好也问到了这个问题,当时只能大方的承认不会。痛定思痛,回去好好的研究一番。我们将从JS的内存机制以及事件机制和大量的(例子)来了解栈、堆究竟是个什么玩意。概念比较多,不用死读,所有的心里想一遍,浏览器console看一遍就很清楚了。let's go

JS内存机制

因为JavaScript具有自动垃圾回收机制,所以对于前端开发来说,内存空间并不是一个经常被提及的概念,很容易被大家忽视。特别是很多不专业的朋友在进入到前端之后,会对内存空间的认知比较模糊。

在JS中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈内存(stack)与堆内存(heap)。

栈内存一般储存基础数据类型

Number String Null Undefined Boolean

(es6新引入了一种数据类型,Symbol)

最简单的

var a = 1

我们定义一个变量a,系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问。

数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则。

堆内存一般储存引用数据类型

堆内存的

var b = { xi : 20 }

与其他语言不同,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。看一下下面的图,加深理解。

比较

 


 
 

因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从栈中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

测试

 


 

同学们自己在console里打一遍,再结合下面的图例,就很好理解了

 


 

 


 

内存机制我们了解了,又引出一个新的问题,栈里只能存基础数据类型吗,我们经常用的function存在哪里呢?

浏览器的事件机制

一个经常被搬上面试题的 |

 


 

 


 

对象放在heap(堆)里,常见的基础类型和函数放在stack(栈)里,函数执行的时候在栈里执行。栈里函数执行的时候可能会调一些Dom操作,ajax操作和setTimeout定时器,这时候要等stack(栈)里面的所有程序先走(注意:栈里的代码是先进后出),走完后再走WebAPIs,WebAPIs执行后的结果放在callback queue(回调的队列里,注意:队列里的代码先放进去的先执行),也就是当栈里面的程序走完之后,再从任务队列中读取事件,将队列中的事件放到执行栈中依次执行,这个过程是循环不断的。

1.所有同步任务都在主线程上执行,形成一个执行栈

2.主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。

3.一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行

4.主线程从任务队列中读取事件,这个过程是循环不断的

概念又臭又长,没关系,我们先粗略的扫一眼,接着往下看。

举一个说明栈的执行方式

 


 
 
 

执行栈里面最先放的是全局作用域(代码执行有一个全局文本的环境),然后再放one, one执行再把two放进来,two执行再把three放进来,一层叠一层。

最先走的肯定是three,因为two要是先销毁了,那three的代码b就拿不到了,所以是先进后出(先进的后出),所以,three最先出,然后是two出,再是one出。

那队列又是怎么一回事呢?

再举一个

 


 

再举一个

 


 

再再再看一个

 


 

所以,得出结论,永远都是栈里的代码先行执行,再从队列中依次读事件,加入栈中执行

stack(栈)里面都走完之后,就会依次读取任务队列,将队列中的事件放到执行栈中依次执行,这个时候栈中又出现了事件,这个事件又去调用了WebAPIs里的异步方法,那这些异步方法会在再被调用的时候放在队列里,然后这个主线程(也就是stack)执行完后又将从任务队列中依次读取事件,这个过程是循环不断的。

再回到我们的第一个

 


 

为什么setTimeout要在Promise.then之后执行呢?

为什么new Promise又在console.log(2)之前执行呢?

setTimeout是宏任务,而Promise.then是微任务

这里的new Promise()是同步的,所以是立即执行的。

这就要引入一个新的话题宏任务和微任务(面试也会经常提及到)

宏任务和微任务

参考 Tasks, microtasks, queues and schedules(https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly)

概念:微任务和宏任务都是属于队列,而不是放在栈中

一个新的

 


 

宏任务(task)

浏览器为了能够使得JS内部宏任务与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->…)鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。但是,setTimeout不一样,setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘promise1,promise2’之后。因为打印‘promise1,promise2’是第一个宏任务里面的事情,而‘setTimeout’是另一个新的独立的任务里面打印的。

微任务 (Microtasks)

微任务通常来说就是需要在当前 task 执行结束后立即执行的任务比如对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。

一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then()会立即产生一个微任务。这就是为什么‘promise1’,'promise2’会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码必须已经执行完毕。‘promise1’,'promise2’会打印在‘setTimeout’之前是因为所有微任务总会在下一个宏任务之前全部执行完毕。

还是

 


 

很好的解释了,setTimeout会在微任务(Promise.then、MutationObserver.observe)执行完成之后,加入一个新的宏任务中

多看一些

 


 

 


 

总结回顾

栈:

存储基础数据类型

按值访问

存储的值大小固定

由系统自动分配内存空间

空间小,运行效率高

先进后出,后进先出

栈中的DOM,ajax,setTimeout会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行。

微任务和宏任务

堆:

存储引用数据类型

按引用访问

存储的值大小不定,可动态调整

主要用来存放对象

空间大,但是运行效率相对较低

无序存储,可根据引用直接获取

标签:队列,数据类型,JS,概念,任务,循环,内存,栈中,执行
From: https://www.cnblogs.com/qfy0411/p/13973130.html

相关文章

  • 4 PyExecJS模块
    PyExecJS模块pyexecjs是一个可以帮助我们运行js代码的一个第三方模块.其使用是非常容易上手的.但是它的运行是要依赖能运行js的第三方环境的.这里我们选择用node作为我们运行js的位置.1.1安装Nodejs切记.重启pycharm或者重启电脑.1.2安装pyexecjspipinstall......
  • 笨办法学Python3 习题33 while 循环
    while循环只要循环语句中的条件布尔值为True,就会不停的执行下面的代码块命令。while循环是无边界循环,forin循环是有边界循环和if语句的相似点都是检查一个布尔表达式的真假,if语句是执行一次,while循环是执行完跳回到while顶部,如此重复,直到布尔值为假False尽量少用w......
  • 21 JSONP
    JSONP为了解决浏览器跨域问题.jquery提供了jsonp请求.在网页端如果见到了服务器返回的数据是:​ xxxxxxxxxxdjsfkldasjfkldasjklfjadsklfjasdlkj({json数据})​ 在Preview里面可以像看到json一样去调试這就是jsonp。这东西依然是ajax.jsonp的逻辑是.在发送请求的时候.带......
  • js封装获取当前周数据
     /**@Author:张大碗[email protected]*@Date:2023-09-2017:36:15*@LastEditors:张大碗[email protected]*@LastEditTime:2023-10-0811:04:08*@FilePath:\vue-vant2-template-master\vue-vant2-template-master\src\utils\week.j......
  • golang map json 结构体
    要将JSON转换为Go结构体,您可以使用json.Unmarshal()函数。首先,您需要定义一个与JSON数据结构匹配的Go结构体,然后使用json.Unmarshal()将JSON数据解码为该结构体。以下是一个示例:假设有如下JSON数据:{"name":"JohnDoe","age":30,"email":"[email protected]"}......
  • R 脚本Trycatch在for循环中的使用记录
    点击查看代码x=list()p=list()outdir=paste0(getwd(),'/8.metabolites.connect.enrichment')if(dir.exists(outdir)){print("direxists")}else{dir.create(outdir)}for(iinc(2:length(each))){x[[i]]=e......
  • [node]安装node后,可以在控制台进行简单的js文件测试
    1.在Windows操作系统安装好node之后,可以在任意文件地址,按下shift+鼠标右键,打开WindowsPowerShell。  或者在VSCode中,右键选择某文件夹,选择“在集成终端中打开”。2.输入node,可以查看安装好的node版本。3.按下ctrl+d,退出node。 在js文件所在目录打开终......
  • JS 堆栈跟踪
    堆栈跟踪APIV8中抛出的所有内部错误在创建时都会捕获堆栈跟踪。可以通过非标准error.stack属性从JavaScript访问此堆栈跟踪。V8还具有各种钩子,用于控制堆栈跟踪的收集和格式化方式,以及允许自定义错误也收集堆栈跟踪。本文档概述了V8的JavaScript堆栈跟踪API。functio......
  • 双for循环+grep实现批量查找文件内容
     [root@localhostweihu1]#cattest.txt/etc/nginx/conf/wwwblackip.conf/etc/nginx/bss_acl/bss_acl.conf/etc/nginx/conf/whiteip.conf/etc/nginx/conf/apiwhitelist.conf/etc/nginx/api_acl/api_acl.conf[root@localhostweihu1]#catip.txt121.40.157.124124.70.195.3......
  • 原生js面试题(二)
    一、携带token->token的无感刷新  (token如何携带?->token的无感刷新?)目的:是为了解决管理系统接口数据的安全性考虑时间就是1-7天token时间一过就要重新登录.需要在后台定时刷新token并且替换之前老的失效的token-1 后端返回过期时间,前端每次请求就判断tok......