变量对象(ES标准中称为词法环境)用于存储JS中的变量,变量对象所在的位置我暂且称其为作用域。于是我们遇到了一个新的问题,变量对象存变量,作用域存储变量对象。谁又来存储作用域呢?
在JS中还存在一个东西叫做执行上下文,每一个函数(包括全局作用域)都会有其对应的一个执行上下文。执行上下文在函数定义时就已创建,函数执行时所需的一切包括作用域链都存储在执行上下文中。执行上下文中存储的东西很多也很杂,不需要一一列举,你需要知道的就是执行上下文中包括函数执行所需的一切东西。
每一个函数都会有一个执行上下文,执行上下文只有在函数执行时才会起作用。在JS中还存在一个叫做调用栈的地方,调用栈专门用来存储正在执行的函数的执行上下文。
栈(stack),所谓的栈是一种数据结构。这种结构类似于枪的弹匣。
在弹匣中,先压入的子弹会在弹匣的最底部,最后压入的子弹在弹匣的最上边。开枪时最后压入的子弹最先发射,最先押入的子弹最后发射。
计算机中栈也是如此,最先存入的数据会在栈的最下边,栈最下边的对象称为栈底对象。最后存入的数据会在栈的最上边,栈最上边的对象被称为栈顶对象。获取数据时,会先获取后存入的对象,也就是栈顶对象,最先存入的对象最后才获取。
调用栈就是一个存储执行上下文的栈。调用栈会在JS加载时就会创建,创建后会将全局作用域的执行上下文放入添加近调用栈,也就是说调用栈中的栈底对象是全局作用域的执行上下文。
这一点可以通过开发这工具查看:
通过开发者工具右侧的Call Stack就可以看到当前代码的调用栈。现在代码停在了14行,位于全局作用域,所以调用栈中只存在一个执行上下文(anonymous),anonymous意为匿名的,在这里实际上指的就是全局执行上下文。现在调用栈中只有一个执行上下文,所以它即使栈底对象又是栈顶对象对象。
简单说来,栈顶对象表示的是当前正在执行的函数。所以现在栈顶对象是全局作用域,意味着当前正在执行全局作用域中的代码。它下边的Scope显示的就是当前上下文中存储的作用域链。
现在调用一个函数,再来观察调用栈有何变化:
现在将代码停在第9行,也就是outer函数开始执行。此时你会发现调用栈中多出来一个执行上下文outer,此时执行栈的栈顶存放的是outer的执行上下文,这意味这现在JS解析器(浏览器)正在执行outer函数,同时Scope也切换为outer函数的作用域链。
当断点打在内部函数时,调用栈又会发生新的变化:
现在断点打到了内部函数中,代码停在了11行。在调用栈中又多出了一个匿名的调用栈,之所以显示匿名是因为内部函数没有起名字。
在调用栈中,outer的上下文已经消失,这是因为outer执行完毕,执行完毕的函数上下文会自动从调用栈中弹出。现在的栈顶的上下文是内部匿名函数的上下文,同时你会发现它的Scope也变成了内部函数的作用域链。
之所以内部函数可以访问到外部函数中的变量和参数,其实原因很简单,因为执行变量和参数早就存储到了内部函数的执行上下文中,调用内部函数时,其上下文被自动加载,与此同时其中存储的作用域链也被找到,由此便有了闭包。
关于执行上下文,我们可以暂且先了解到这样一个程度,下边我做一个简单的总结:
- 每一个函数(包括全局作用域)都有一个执行上下文
- 执行上下文中存储了当前函数执行所需的一切内容(包括作用域链)
- 调用栈用来存储当前正在执行或等待执行的函数的执行上下文
- 调用栈的栈顶对象表示当前正在执行的函数的执行上下文
- 执行完毕执行上下文会从栈顶弹出,然后继续执行下一个函数
太牛了
超哥,我想问问js执行机制是把所有的同步事件先全部放入栈再执行,还是放入一个执行一个
一起