1. 定义
调用堆栈是解析器在脚本调用多个函数时,跟踪当前正在执行的函数及下一个将要被调用及执行的函数的机制。
- 当脚本调用一个函数,解释器将它加入堆栈并开始执行函数
- 任何被该函数调用的函数都会被放入堆栈,并且运行到它们被上个程序调用的位置
- 当前函数执行完成后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码
- 如果栈占用的空间比分配的空间还大,会导致栈溢出错误。
2. JS引擎
- 最流行的引擎是谷歌的V8
- 内存堆进行内存分配,调用栈执行代码
- 引擎之外的很多API,都是浏览器提供的Web API,例如DOM,AJAX,setTimeout,同时还有事件循环和回调队列。
- JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事。
- 一旦你的浏览器开始处理调用栈中的众多任务,它可能会停止响应相当长一段时间,如何在不阻塞 UI 的情况下执行复杂的代码,让浏览器不会不响应?解决方案就是异步回调。
3. 执行上下文
- 全局上下文,就是默认或者基础上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
- 函数上下文:函数在被调用时,为该函数单独创建自己的执行上下文。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤。
- Eval会被单独创建上下文,但是用的比较少,不考虑。
- 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。 - 创建执行上下文有创建和执行两个阶段,创建阶段,就是this值的绑定,词法环境组件和变量环境组件。词法环境就是一种持有标识符-变量映射的结构,在词法环境的内部有两个组件:(1) 环境记录器和 (2) 一个外部环境的引用。
环境记录器是存储变量和函数声明的实际位置。
外部环境的引用意味着它可以访问其父级词法环境(作用域)。
变量环境同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。 - 在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定,而后者只用来存储 var 变量绑定。let和const定义的变量,没有赋值就是未初始化,而var变量未赋值是undefined。因此var定义的变量可以出现变量提升,而let和const定义的变量不能有。
- 在全局执行上下文中,this 的值指向全局对象。(在浏览器中,this引用 Window 对象)。在函数执行上下文中,this 的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下)。
- 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined。
4. JS事件循环
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。
对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。
5. 宏任务和微任务
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
6. 总结
- JS是一门单线程语言,永远都是,不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的。
- 事件循环是js实现异步的一种方法,也是js的执行机制。