浅析JS堆、栈、执行栈和EventLoop

如今前端面试,大多都会问到关于事件循环、执行栈等问题,本文经过案列、图片等形式给你们讲解这些概念,若是认真看完,我相信90%的同窗能够完全理解。前端

JS内存机制

JavaScript具备自动垃圾回收机制,周期性会检查没有使用的变量,进行回收释放。因此在闭包中,若是引用了外部的变量,则没法进行释放和回收,通常会传参进去。面试

垃圾回收:找出那些再也不继续使用的变量,而后释放其占用的内存,垃圾收集器会按照固定的时间间隔周期性地执行这一操做。ajax

JS中,每个数据都须要一个内存空间,内存空间又分为栈内存(stack)与堆内存(heap)。数组

栈内存通常储存基础数据类型

Number String Null Undefined Boolean Symbol
复制代码

看一个例子:浏览器

var num = 1 
复制代码

咱们定义一个变量num,系统自动分配存储空间。咱们能够直接操做保存在栈内存空间的值,所以基础数据类型都是按值访问。微信

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

堆内存通常储存引用数据类型

var user = { name:'jack' }
var arr = [1,3,5]
复制代码

JS的引用数据类型,好比数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不容许直接访问堆内存中的位置,所以咱们不能直接操做对象的堆内存空间。闭包

经过下面这张图,咱们就能直观理解。 异步

var num = 1;   // 栈 
var name = '前端将来'; // 栈
 // 变量user存在于栈中,{name:'河畔'}存在于堆内存中 var user = { name: '河畔' }; // 变量arr存在于栈中,[1, 2, 3] 做为对象存在于堆内存中 var arr = [1, 3, 5]; 复制代码

所以当咱们要访问堆内存中的引用数据类型时,实际上咱们首先是从栈中获取了该对象的指针,而后再从堆内存中取得咱们须要的数据。函数

因此,咱们常常说:基本类型赋值相互不影响,引用类型赋值,会影响原对象。

一个例子就能看明白:

var a = 20;
var b = a;
b = 30;
// a为20,b为30,值类型不影响
console.log(a) 
 var user = { name: '河畔' }; var info = user; info.name = 'Jack' // 打印为jack,指向同一个内存地址 console.log(user.name) 复制代码

总结:

  • JavaScript具有自动垃圾回收机制
  • JS内存分为堆内存和栈内存
  • 引用类型在栈中保存指针,在堆中保存对象值
  • 栈内存数据遵循 先进后出

EventLoop

如今前端面试,你们都喜欢问EventLoop,但说实话,不少人看了无数篇文章,仍是稀里糊涂,今天依然经过代码+图片的方式给你们演示效果。

为了更好的理解事件机制,咱们须要先介绍执行栈。全部JS代码运行都是被放入执行中执行的,遵循进栈和出栈,直到栈被清空。

执行栈

JS 代码在运行前都会建立执行上下文,也能够理解为执行环境,JS 中有三种执行上下文:

  • 全局执行上下文,默认的,在浏览器中是 window 对象
  • 函数执行上下文,JS 的函数每当被调用时会建立一个上下文。
  • Eval 执行上下文,eval 函数会产生本身的上下文。

一般,咱们的代码中都不止一个上下文,那这些上下文的执行顺序应该是怎样的?从上往下依次执行?

栈,是一种数据结构,遵循先进后出的原则。JS 中的执行栈就具备这样的结构,当引擎第一次遇到 JS 代码时,会产生一个全局执行上下文并压入执行栈,每遇到一个函数调用,就会往栈中压入一个新的上下文。引擎执行栈顶的函数,执行完毕,弹出当前执行上下文。

接下来,咱们看一个例子:

function foo() {
  console.log('1');
  bar();
  console.log('3');
}
function bar() {
  console.log('2');
}
foo();
复制代码

这个毫无疑问,你们都知道答案,执行栈是怎么调用的?

首先执行这个JS文件,建立一个全局上下文,并压入执行栈中,当 foo() 函数被调用时,将 foo 函数的执行上下文压入执行栈,接着执行输出 ‘1’;当 bar() 函数被调用,将 bar 函数的执行上下文压入执行栈,接着执行输出 ‘2’;bar() 执行完毕,被弹出执行栈,foo() 函数接着执行,输出 ‘3’;foo() 函数执行完毕,被弹出执行栈,最后清空整个执行栈。这就是先进后出,Foo先被压入执行栈,最后才被弹出执行栈

EC就是Execute Context执行上下文

总结:

  • 全部JS代码运行,都须要放入执行栈中.
  • 执行上下文包含了三种(全局、函数、eval)
  • 栈是一种数据结构,遵循 先进后出

接下来,看一道经典面试题

console.log(1)
new Promise(function(resolve){
    console.log(3)
    resolve(100)
}).then(function(data){
    console.log(data)
})
setTimeout(function(){
    console.log(4);
})
console.log(2)
复制代码

上面的面试题打印结果: 1 3 2 100 4

你能说出具体执行步骤吗?

咱们都知道JS自己是单线程的,一次只能干一件事儿,那么像定时器、Promise这些它是怎么处理的呢?实际上就要介绍quene队列了。

主线程执行同步代码块,遇到定时器、Promise等异步任务时,会建立事件队列,把他们丢到队列里面去,等主线程执行完成后,再回去执行队列中的task.

因此,咱们的JS执行主要包括同步任务和异步任务,整个同步任务会进入到主线程中,最后放入执行栈中执行,就是咱们上面给你们讲解的执行栈,接下来关注异步任务。

浏览器的JS中,异步任务又分为宏任务和微任务,宏任务和微任务都是属于队列,而不是放在栈中。微任务会建立一个队列,宏任务会建立一个队列,而主线程执行完之后,会优先执行微任务,把微任务所有放到执行栈中执行,最后再从宏任务中取出一个放入执行栈进行执行,执行完后,再取一个,直到执行完全部的宏任务。

接下来看张图:

左侧JS图包含了堆和栈,全部的代码都会被放入栈中执行,咱们叫执行栈,执行栈是一条主线程,先执行同步任务,中间遇到ajax、setTimeout等异步任务后,会push到queue中,最后再把队列中事件取出来放入执行中执行,依次循环这个过程。

那咱们再来看上面的例子:

console.log(1)
new Promise(function(resolve){
    console.log(3)
    resolve(100)
}).then(function(data){
    console.log(data)
})
setTimeout(function(){
    console.log(4);
})
console.log(2)
复制代码
  1. 建立全局上下文,并压入执行栈中
  2. 把同步代码console.log压入执行栈中执行,打印 1,并出栈
  3. 把同步代码new Promise压入执行栈中执行,打印 3,并出栈

注意:new Promise 这个过程其实是同步的,只有resolve和reject后才是异步

  1. then属于异步任务,push到微任务队列中,并建立事件
  2. setTimeout属于异步任务,push到宏任务队列中,并建立事件

注意:宏任务和微任务是两个队列

  1. 把同步代码console.log压入执行栈中执行,打印 2,并出栈

到此整个执行栈只剩下全局上下文,没有能够执行的代码了

  1. 微任务先执行,因此把微任务队列中的事件所有拿出来,放入执行栈进行执行。打印 100,并出栈
  2. 从宏任务队列中,只取出一个事件放入执行栈中执行,打印 4

那咱们把上面例子改造一下:

console.log(1)
new Promise(function(resolve){
    console.log(3)
    resolve(100)
}).then(function(data){
    console.log(data)
}).then(function(){
    console.log(200)
})
setTimeout(function(){
    console.log(4);
})
setTimeout(function(){
    console.log(5);
})
console.log(2)
复制代码

有2个then,2个setTimeout,此时学完后,您以为应该打印多少? 答案是:1 3 2 100 200 4 5

整个文章到此结束,但愿你们可以看懂!

总结:

  • JavaScript具有自动垃圾回收机制
  • JS内存分为堆内存和栈内存
  • 引用类型在栈中保存指针,在堆中保存对象值
  • 全部JS代码运行,都须要放入执行栈中.
  • 执行代码前,会先建立执行上下文
  • 执行上下文包含了三种(全局、函数、eval)
  • 同步任务先执行,异步任务放队列
  • 微任务先执行,宏任务后执行
  • 微任务所有拉入执行栈,宏任务一次拉一个
  • 栈是先进后出,队列是先进先出

有多少人能理解加粗的文字,上面实际上经过图片和代码给你们演示过了。

以上是为你们整理的堆、栈、事件机制等概念,但愿你们面试的时候可以说出个张3、李四来,不要再被对方diss了。

微信公众号:前端将来

我的微信:

本文使用 mdnice 排版

相关文章
相关标签/搜索