堆栈内存的作用

JS 引擎主要由两部分组成:

  • 内存堆:这是内存分配发生的地方
  • 调用栈:这是你的代码执行时的地方

JS 中的内存分为堆内存和栈内存,所有堆栈内存的分配处理,浏览器(引擎)会自行在内部执行

栈内存:

  • 提供一个供 JS 代码自上而下执行的环境(作用域,代码都是在栈内存中执行的)
  • 由于基本类型比较简单,它们都是直接在栈内存中开辟一个位置,直接把值存储进去的

堆内存:引用值对应的空间 存储引用类型的(对象:键值对,函数:代码字符串)

堆内存的释放

让所有引用堆内存空间地址的变量赋值为 null 即可,当堆内存没有被任何的变量或者其他东西引用时,就会在浏览器执行垃圾回收的时候,被销毁掉。

堆内存释放后,里面存储的值也就会被释放掉。

调用栈

JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事。

调用栈是一种数据结构,是解释器(引擎)追踪函数执行流的一种机制,记录现在执行到程序的哪个位置。每当我们运行到一个函数,它就会将其放置到栈顶。当从这个函数返回的时候,就会将这个函数从栈顶弹出,这就是调用栈做的事情。

  • 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
  • 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
  • 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
  • 当分配的调用栈空间被占满时,会引发“堆栈溢出”。

举个例子:

function greeting() {
   // [1] Some codes here
   sayHi();
   // [2] Some codes here
}
function sayHi() {
   return "Hi!";
}

// 调用 `greeting` 函数
greeting();

// [3] Some codes here

上面的代码执行步骤如下:

  1. 忽略前面所有函数声明(函数声明会保存在堆内存中),直到 greeting() 函数被调用
  2. 把 greeting() 添加进调用栈列表
  3. 执行 greeting() 函数体中的所有代码

此时调用栈列表:

  • greeting
  1. 代码执行到 sayHi() 时,该函数被调用
  2. 把 sayHi() 添加进调用栈列表

此时调用栈列表:

  • sayHi
  • greeting
  1. 执行 sayHi() 函数体中的代码,直到全部执行完毕
  2. 继续执行 greeting() 函数体中 sayHi() 后面的代码
  3. 弹出调用栈列表中的 sayHi() 函数

调用栈列表:

  • greeting
  1. 当 greeting() 函数体中的代码全部执行完毕,返回到调用 greeting() 的代码行,继续执行剩余JS代码。
  2. 弹出调用栈列表中的 greeting() 函数

一开始,我们得到一个空空如也的调用栈。随后,每当有函数被调用都会自动地添加进调用栈,执行完函数体中的代码后,调用栈又会自动地移除这个函数。最后,我们又得到了一个空空如也的调用栈。

堆栈溢出

"堆栈溢出",当你达到调用栈最大的大小的时候就会发生这种情况。

function foo() {
  foo();
}
foo();

当我们的引擎开始执行这段代码的时候,它从 foo 函数开始。然后这是个递归的函数,并且在没有任何的终止条件的情况下开始调用自己。因此,每执行一步,就会把这个相同的函数一次又一次地添加到调用堆栈中。

enter description here

然后,在某一时刻,调用栈中的函数调用的数量超过了调用栈的实际大小,浏览器决定干掉它,抛出一个错误:

enter description here

栈内存的释放

全局作用域会在页面关闭或者刷新的时候释放。(栈内存释放后,存储在栈内存中的值也都会销毁。 )

私有作用域:一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉,但是也有特殊的情况。

函数执行完成,当前形成的栈内存中,某些内容被栈内存意外的变量一直占用,此时栈内存不能释放,栈内存中存储的基本值也不会被释放,一直保存下来。最典型的就是闭包。

var i = 1;
function fn(i) {
  return function(n) {
    console.log(n + (++i));
  }
}
var f = fn(2); //=> i = 2
f(3); //=>7, i = 2 n =3,执行 n + (++i) => 3 + 3 = 6
fn(5)(6); //=>12, i = 5 n = 6,执行 n + (++i) => 6 + 6 = 12
fn(7)(8); //=>16, i = 7 n = 8,执行 n + (++i) => 8 + 8 = 16
f(4); //=>8, i = 3 n = 4,执行 n + (++i) => 4 + 4 = 8

参考

生命的意义不仅是活着,而是我们给别人的生命带来了何种不同。

© 2016-2019 destiny