原文链接:https://aspirinmrmi.github.io/2018/11/28/%E8%A7%A3%E8%AF%BBJavaScript%E5%BC%80%E5%8F%91%E8%80%85%E5%BA%94%E6%87%82%E7%9A%8433%E4%B8%AA%E6%A6%82%E5%BF%B5%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94-%E8%B0%83%E7%94%A8%E5%A0%86%E6%A0%88/
专业术语什么的都是纸老虎
概念
调用栈是解析器的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(当前在执行什么函数,什么函数被榨干函数调用,接下来调用哪一个函数)
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
- 如果栈占用的空间比分配给它的空间还大,那么则会导致Stack Overflow(栈溢出)错误。
示例代码
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()函数中的所有代码,直到结束。
- 把执行返回到调用sayHi()的行,并继续执行greeting()方法的其余部分。
- 把sayHi()方法从调用栈列表删除。
- 当greeting()方法中的所有代码执行完毕以后,返回到调用该方法的地方继续执行其余的代码。
- 把greeting()方法从调用栈列表中删除。
我们的调用栈列表生不带来,死不带走,从一个空的调用栈开始,当所有代码执行完毕,我们得到的还是一个空的调用栈。
基本的原理就是,每当我的代码中有函数被调用,该函数就会自动添加到栈中,在执行完该函数的所有代码任务后,它就会自动从栈中删除。
堆栈追踪
由此我们可以清楚的知道当异常发生的时候堆栈追踪是怎么被构造的,堆栈的状态是如何的。
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
如果这段代码保存在本地的一个foo.js文件中并且运行在Chrome中我们通过控制台可以看到:
堆栈溢出
调用堆栈的空间是有限的,当达到其峰值时候就会发生堆栈溢出的错误。比方说:
function foo() {
foo();
}
foo();
这个递归函数 反复调用foo()函数,结果会导致栈变成这样:
然后,在某一个时刻,调用栈中的函数的调用数量超过了调用栈的实际大小,浏览器就决定要干掉它,抛出一个错误:
掘金上有一篇非常不错的文章,我就不搬了。文章介绍的特别全面,图文结合把JavaScript这门单线程语言的执行机制介绍的淋漓尽致。有兴趣请戳:这里这里这里~
下面是我看小哥的文章下来的收获:
执行机制
图片介绍:
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
- Q:setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了???
- -------我的理解是fn的执行时间超过了延迟时间ms,那样的话,在延迟的时间里已经将下一次要执行的fn置入了Event Queue,在相对较长的fn执行时间过后,主线程会立马从Event Queue中去取下一个要执行的fn所以我们完全看不出来有时间间隔。
- 那么什么时候会出现这种完全没有时间间隔的情况呢?
- Q:setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?
- ------- setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行了。
Promise与process.nextTick(callback)
除了广义的同步任务和异步任务,我们还有对任务更为精细的定义:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
事件循环,宏任务,微任务的关系如图所示:
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
执行结果为: - promise - console - then - setTimeout