堆栈内存的作用
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
上面的代码执行步骤如下:
- 忽略前面所有函数声明(函数声明会保存在堆内存中),直到 greeting() 函数被调用
- 把 greeting() 添加进调用栈列表
- 执行 greeting() 函数体中的所有代码
此时调用栈列表:
- greeting
- 代码执行到 sayHi() 时,该函数被调用
- 把 sayHi() 添加进调用栈列表
此时调用栈列表:
- sayHi
- greeting
- 执行 sayHi() 函数体中的代码,直到全部执行完毕
- 继续执行 greeting() 函数体中 sayHi() 后面的代码
- 弹出调用栈列表中的 sayHi() 函数
调用栈列表:
- greeting
- 当 greeting() 函数体中的代码全部执行完毕,返回到调用 greeting() 的代码行,继续执行剩余JS代码。
- 弹出调用栈列表中的 greeting() 函数
一开始,我们得到一个空空如也的调用栈。随后,每当有函数被调用都会自动地添加进调用栈,执行完函数体中的代码后,调用栈又会自动地移除这个函数。最后,我们又得到了一个空空如也的调用栈。
堆栈溢出
"堆栈溢出",当你达到调用栈最大的大小的时候就会发生这种情况。
function foo() {
foo();
}
foo();
当我们的引擎开始执行这段代码的时候,它从 foo 函数开始。然后这是个递归的函数,并且在没有任何的终止条件的情况下开始调用自己。因此,每执行一步,就会把这个相同的函数一次又一次地添加到调用堆栈中。
然后,在某一时刻,调用栈中的函数调用的数量超过了调用栈的实际大小,浏览器决定干掉它,抛出一个错误:
栈内存的释放
全局作用域会在页面关闭或者刷新的时候释放。(栈内存释放后,存储在栈内存中的值也都会销毁。 )
私有作用域:一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉,但是也有特殊的情况。
函数执行完成,当前形成的栈内存中,某些内容被栈内存意外的变量一直占用,此时栈内存不能释放,栈内存中存储的基本值也不会被释放,一直保存下来。最典型的就是闭包。
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
参考
← JS 作用域与作用域链 JS变量提升 →