通过javascript 执行环境理解她
古往今来最难的学的武功(javascript)算其一。
欲练此功必先自宫,愿少侠习的此功,笑傲江湖。
你将了解
- 执行栈(Execution stack)
- 执行上下文(Execution Context)
- 作用域链(scope chains)
- 变量提升(hoisting)
- 闭包(closures)
- this 绑定
执行栈
又叫调用栈,具有 LIFO(last in first out 后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
当 JavaScript 引擎首次读取你的脚本时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当发生一个函数调用,引擎都会为该函数创建一个新的执行上下文并将其推到当前执行栈的顶端。
引擎会运行执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文。
我们通过下面的示例来说明一下
function one() { console.log('one') two() } function two() { console.log('two') } one()
当程序(代码)开始执行时 javscript 引擎创建 GobalExecutionContext
(全局执行上下文)推入当前的执行栈,此时 GobalExecutionContext
处于栈顶会立刻执行全局执行上下文 然后遇到 one()
引擎都会为该函数创建一个新的执行上下文 oneFunctionExecutionContext
并将其推到当前执行栈的顶端并执行,然后遇到two()
twoFunctionExecutionContext
入栈并执行至出栈,回到 oneFunctionExecutionContext
继续执行至出栈 ,最后剩余一个 GobalExecutionContext
它会在程序关闭的时候出栈。
然后调用栈如下图:
如果是这样的代码
function foo() { foo() } foo()
如下
当一个递归没有结束点的时候就会出现栈溢出
什么是执行上下文
了解 JavaScript 的执行上下文,有助于你理解更高级的内容比如变量提升、作用域链和闭包。既然如此,那到底什么是“执行上下文”呢?
执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。
Javascript 中代码的执行上下文分为以下三种:
- 全局执行上下文(Global Execution Context)- 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
- 函数执行上下文(Function Execution Context) - 当执行一个函数时,运行函数体中的代码。
- Eval - 在 Eval 函数内运行的代码。
javascript 是一个单线程语言,这意味着在浏览器中同时只能做一件事情。当 javascript 解释器初始执行代码,它首先默认进入全局上下文。每次调用一个函数将会创建一个新的执行上下文。
javascript执行栈中不同执行上下文之间的词法环境有一种关联关系,从栈顶到栈底(从局部直到全局),这种关系被叫做
作用域链
。
简单的说,每次你试图访问函数执行上下文中的变量时,进程总是从自己上下文环境中开始查找。如果在自己的上下文中没发现要查找的变量,继续搜索下一层上下文。它将检查执行栈
中每一个执行上下文环境,寻找和变量名称匹配的值,直到找到为止,如果到全局都没有则抛出错误。
执行上下文的创建过程
我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在 javascript 引擎内部,这个上下文的创建过程具体分为两个阶段:
创建阶段 > 执行阶段
创建阶段
执行上下文在创建阶段创建。在创建阶段发生以下事情:
- LexicalEnvironment 组件已创建。
- VariableEnvironment 组件已创建。
因此,执行上下文可以在概念上表示如下: