Call Stack 与 Stack 的概念

Call Stack(调用栈) 一般指计算机程序执行时子程序之间消息处理的相互调用产生的一些列函数序列,而且几乎所有的计算机程序都依赖于调用栈。
在探讨 Call Stack 前,先来搞清楚 Stack()的概念。
Stack 就是一种特殊的串列形式的数据结构,特殊之处在于只能允许在链接串列或阵列的一端(称为堆叠顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。因此栈的数据结构只允许在一端进行操作,按照后进先出(LIFO, Last In First Out)的原理运作。
image.png
**

Call Stack 是如何运行的

image.png

结果是:c、b、a

调用栈的大小

由于操作系统对每组线程的栈内存有一定的限制,为适应线程各种操作系统,所以 Node.js 默认的栈大小为 984k

Slightly less than 1MB, since Windows’ default stack size for the main execution thread is 1MB for both 32 and 64-bit. @src/globals.h:108:1

如何获取当前环境的调用栈大小?
image.png

(call stack)调用栈是解释
器(就像浏览器中的JavaScript解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。

  • 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
  • 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
  • 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
  • 当分配的调用栈空间被占满时,会引发“堆栈溢出”。
  1. function doSomething(){
  2. sayHi();
  3. }
  4. // 被调用对象
  5. function sayHi(){
  6. //console.log('somebody sayhi')
  7. }
  8. // 调用 `doSomething` 函数
  9. doSomething()

上面的代码将这样执行:

  1. 忽略前面所有函数,直到doSomething()函数被调用。
  2. 把doSomething()添加进调用栈列表。
  3. 执行doSomething()函数体中的所有代码
    调用栈列表: - doSomething
  4. 代码执行到sayHi()时,该函数被调用。
  5. 把sayHi()添加进调用栈列表。
  6. 执行sayHi()函数体中的代码,直到全部执行完毕。
    调用栈列表: - doSomething - sayHi
  7. 返回来继续执行doSomething()函数体中sayHi()后面的代码。
  8. 删除调用栈列表中的sayHi()函数。
  9. 当doSomething()函数体中的代码全部执行完毕,返回到调用doSomething()的代码行,继续执行剩余JS代码。
    调用栈列表: - doSomething
  10. 删除调用栈列表中的doSomething()函数。

总结
Call Stack(调用栈)实际上就是用于存储函数的一种内存数据,而且遵循 LIFO 原理实现的进栈和出栈等一系列操作。栈的大小受到操作系统的限制,一般会少于 1MB 的空间,能使用的回调栈层数受制于栈中每个栈函数的内部变量数量等不同,调用栈的深浅也不一样。从我们的开发层面看,代码的执行和栈深一般都是有限的,所以默认的情况下代码都不会出现调用栈溢出异常的问题发生。
在了解调用栈的工作原理,及调用栈在各个版本上的运行表现后,其实我们应该思考一下,假设我需要设计一个类似 process.nextTick() 或者 co.next() 这样的函数时,应该如何设计函数方法体,让该函数的代码既有效率地执行同时又能被系统做优化处理,而什么样的代码不行的问题。

image.png
关注公众号 code本缘