1. 栈(heap)和堆(stack)
栈是栈内存的简称,堆是堆内存的简称。顾名思义,内存是干啥的?内存就是用来存放数据的。
栈
栈只有一个入口,同时也是出口,数据遵循先进后出、后进先出的原则。
栈用于存放基本类型数据和引用类型数据的指针,基本数据类型包含number,string,boolean,null,undefined,symbol,bigint等结构简单的数据,指针就是对象的地址而不是对象本身。因为栈中储存的数据结构简单,所以存储速度就会很快。
堆
堆用于存放引用类型的数据,引用类型数据包含对象、数组、函数、Date等等,引用数据类型比较复杂,涉及原型、嵌套、方法等等。我们将引用类型数据赋值给变量a的时候,实际上复制的是引用类型的指针(地址),并不是复制的值,他们在内存中还是指向同一个数据源,所以当我们修改这个变量a的时候,我们的数据源也会随之发生变化。
2. 主线程和任务队列
背景
JavaScript是一门单线程语言,单线程意味着所有的任务都需要排队,只有前一个任务执行完成之后,才能执行下一个任务。
这种情况显然是不合理的,比如说我触发一个30秒的延时器,延时器被挂起就阻塞了主线程,导致浏览器CPU空闲且无法执行其他任务。所以这些阻塞进程的任务就被存放在任务队列(分线程)中。
同步任务和异步任务
于是js中的任务被分为了两种,一种是同步任务,另外一种是异步任务。
- 同步任务(synchronous)指的是,在主线程上,排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。比如:普通函数、new Promise()、console.log()等等
- 异步任务(asynchronous)指的是,不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。比如:DOM 事件回调、定时器回调、网络请求的回调、promise.then()、promise.catch()的回调。
宏任务和微任务
异步任务又分为宏任务和微任务
- 宏列队 macrotask queue:用来保存待执行的宏任务(回调),比如:DOM 事件回调、定时器回调、网络请求的回调
- 微列队 microtask queue:用来保存待执行的微任务(回调),优先级高于宏任务,比如:promise.then()、promise.catch()的回调、MutationObserver 、queueMiscrotask()的回调
参考:JavaScript的堆、栈、队列、事件循环机制_js 哪些是栈和队列-CSDN博客
3. 事件循环机制
因为js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数
整体会把所有代码分为两个部分:‘同步任务’,‘异步任务’
所有同步任务都在主线程上执行,形成一个执行栈
主线程之外还存在一个任务队列,专门存放异步任务(宏任务和微任务)
- 宏任务进入到事件表(Event Table)中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到事件队列(Event Queue)中
- 微任务也会进入到另一个事件表(Event Table)中,并在里面注册回调函数,每当指定的事件完成时,事件表(Event Table)会将这个函数移到事件队列(Event Queue)中
- 整体script作为第一个宏任务进入主线程,当主线程的任务执行完毕,主线程为空时,会检查微任务的事件队列(Event Queue),如果有任务,就会全部执行,如果没有就执行下一个宏任务
- 主线程不断重复上面的步骤,这就是Event Loop事件循环,只要主线程空了,就会去读取"任务队列"。这个过程会不断重复。