We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
执行栈(execution context stack,简称ECStack),它遵循一种LIFO(后进先出)的数据结构,该结构拥有存储在代码执行期间创建的所有执行上下文。一般来说,执行栈中总是保留一个全局上下文,当函数调用时,会创建一个函数的上下文并推入执行栈中,函数执行完毕后又从栈中弹出。
function foo() { console.log('I\'m foo') } function bar() { console.log('I\'m bar') foo() } bar()
以上代码整个执行过程执行栈的变化:
// 代码执行前 ECStack = [ globalContext ] // 执行bar ECStack = [ barContext, globalContext ] // bar中调用foo ECStack = [ fooContext, barContext, globalContext ] // foo执行完毕 ECStack = [ barContext, globalContext ] // bar执行完毕 ECStack = [ globalContext ]
函数的执行依赖于上下文,上下文中有函数会用到的一些变量,它们保存在内存中。推入上下文意味着cpu会分配一片内存区域来保存上下文中的变量,方便函数调用。函数执行完毕,函数上下文中的变量也就没有存在的必要了,同时为了节省内存,上下文从执行栈中弹出便是理所当然。JavaScript中的一种特殊函数结构体叫闭包,情况又有所不同,后面会说到。
注:以下讨论的执行上下文针对ES6环境,ES3中的执行上下文可以移步JavaScript深入之执行上下文
执行上下文也叫执行上下文环境,它是为JavaScript代码的执行所创建的一个环境,这个环境又是什么呢? 想一想,我们在函数中常用的arguments、this等,我们并没有显示定义它们却可以使用,这大概就是这个环境带给我们的。
JavaScript中存在三种执行上下文:
注:全局执行上下文和函数执行上下文拥有同样的属性,区别在于属性值不同,后面一一讨论。
执行上下文包括两个组件:
所以一个执行上下文看起来长这样:
ExecutionContext = { LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
注:变量环境也是一个词法环境,所以它与词法环境拥有相同的属性。之所以存在两个环境,主要是为了区分定义变量的不同方式块级作用域(let和const)和var,前者存储在词法环境中,而后者存储在变量环境中。我们重点讨论词法环境,变量环境自行理解。
现在我们重点说说词法环境。
每个词法环境都有三个属性组成:
一个词法环境看起来长这样:
LexicalEnvironment = { EnvironmentRecord: { Type: "Object" // 全局执行上下文中 // Type: "Declarative" 如果是函数执行上下文 }, outer: <null>, this: <global object> }
我们分别来解释这三个属性。
环境记录是用于存储变量和函数声明时的位置,它也有两种类型:
另外,我们在上面说到过,JavaScript中四种声明变量的方式let、const、var和函数声明,只有var声明的变量存储在变量环境的环境记录中,其他三种都存储在词法环境的环境记录中。
我们看看如下代码:
function foo(x) { var a = 2 let b = 3 const c = 5 console.log(a + b + c + x) } foo(10)
函数foo的执行上下文看起来像这样:
fooExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", Arguments: { 0: 10, length: 1 }, b: <uninitialized>, // uninitialized表示未初始化,此时访问该变量会导致错误,临时死区(TDZ)原理与此 c: <uninitialized> // 同上 }, ... }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", a: undefined }, ... } }
在上例中,当全局执行上下文被创建时,一个内部属性 [[Environment]]被保存在函数foo上,它用于引用当前执行上下文中的词法环境,作用和之前[[Scope]](ES6中被删除)类似,因此,在函数foo执行上下文被创建时,会得到该属性。这意味着在函数foo的词法环境中没有找到的变量会顺着该属性继续寻找,函数在定义时就保存了该属性,这决定了JavaScript只能是词法作用域。
[[Environment]]
foo
[[Scope]]
全局执行上下文中的this引用的是全局对象,在浏览器中指向window对象。 函数执行上下文的this值,取决于函数的调用方式。例如常见的调用方式:以对象方法的形式调用,会使this的值被设置为该对象;以函数的形式调用会this值被设置为window对象(浏览器中)或者undefined。 但关于this的细节远不于此,感兴趣的可以阅读ECMAScript规范中文中关于this值是如何确定的。
我们需要记住的在各种环境中this的取值:
上面的foo函数完整的执行上下文大概长这样:
fooExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", Arguments: { 0: 10, length: 1 }, b: <uninitialized>, // uninitialized表示未初始化,此时访问该变量会导致错误,临时死区(TDZ)原理与此 c: <uninitialized> // 同上 }, outer: <globalLexicalEnvironment>, this: <global object or undefined> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", a: undefined }, outer: <globalLexicalEnvironment>, this: <global object or undefined> } }
综上,我们解释了词法环境中的三个属性,现在通过一个例子来巩固一下函数整个执行过程吧。
为了减少创建上下文的复杂度,以及未来声明变量的最佳实践,例子中只用let、const声明变量,这意味着执行上下文中只存在一个词法环境(LexicalEnvironment)组件(前面说过,只有var声明会储存在变量环境组件中),代码默认在浏览器环境中执行。
let a = 2 function foo() { let b = 3 function bar(c) { return a + b + c } return bar(5) } foo() // 10
分析以上代码:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", global: <window object>, a: <uninitialized>, foo: <fn> }, outer: <null>, this: <window> } }
执行栈情况:
ECStack = [ GlobalExectionContext ]
bar
fooExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", arguments: { length: 0 }, b: <uninitialized>, bar: <fn> }, outer: <GlobalLexicalEnvironment>, this: <window> } }
ECStack = [ fooExectionContext, GlobalExectionContext ]
barExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", arguments: { 0: 5, length: 1 } }, outer: <fooLexicalEnvironment>, this: <window> } }
a + b + c
ECStack = [ barExectionContext, fooExectionContext, GlobalExectionContext ]
至此,代码全部执行完毕
ES6的出现,重写了某些概念,比如:变量对象、作用域链等,在文中都没有提到。如果细心观察,和以前的概念对比你会发现,其实,变量对象大概就是词法环境中的变量记录器,也就是环境记录,作用域链大概就是外部环境记录outer。而和以前的区别主要是关于变量的声明方式,let、const和var进行了区别对待。 所以,这位大佬的JavaScript深入之执行上下文可以继续食用,并且更易于理解。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
先理解函数的执行过程
什么是执行栈?
执行栈(execution context stack,简称ECStack),它遵循一种LIFO(后进先出)的数据结构,该结构拥有存储在代码执行期间创建的所有执行上下文。一般来说,执行栈中总是保留一个全局上下文,当函数调用时,会创建一个函数的上下文并推入执行栈中,函数执行完毕后又从栈中弹出。
以上代码整个执行过程执行栈的变化:
上下文的推入和弹出的目的是什么?
函数的执行依赖于上下文,上下文中有函数会用到的一些变量,它们保存在内存中。推入上下文意味着cpu会分配一片内存区域来保存上下文中的变量,方便函数调用。函数执行完毕,函数上下文中的变量也就没有存在的必要了,同时为了节省内存,上下文从执行栈中弹出便是理所当然。JavaScript中的一种特殊函数结构体叫闭包,情况又有所不同,后面会说到。
什么是执行上下文?
注:以下讨论的执行上下文针对ES6环境,ES3中的执行上下文可以移步JavaScript深入之执行上下文
执行上下文也叫执行上下文环境,它是为JavaScript代码的执行所创建的一个环境,这个环境又是什么呢?
想一想,我们在函数中常用的arguments、this等,我们并没有显示定义它们却可以使用,这大概就是这个环境带给我们的。
JavaScript中存在三种执行上下文:
注:全局执行上下文和函数执行上下文拥有同样的属性,区别在于属性值不同,后面一一讨论。
执行上下文包括两个组件:
所以一个执行上下文看起来长这样:
注:变量环境也是一个词法环境,所以它与词法环境拥有相同的属性。之所以存在两个环境,主要是为了区分定义变量的不同方式块级作用域(let和const)和var,前者存储在词法环境中,而后者存储在变量环境中。我们重点讨论词法环境,变量环境自行理解。
现在我们重点说说词法环境。
词法环境
每个词法环境都有三个属性组成:
一个词法环境看起来长这样:
我们分别来解释这三个属性。
1. 环境记录
环境记录是用于存储变量和函数声明时的位置,它也有两种类型:
另外,我们在上面说到过,JavaScript中四种声明变量的方式let、const、var和函数声明,只有var声明的变量存储在变量环境的环境记录中,其他三种都存储在词法环境的环境记录中。
我们看看如下代码:
函数foo的执行上下文看起来像这样:
2. 外部环境的引用(outer)
在上例中,当全局执行上下文被创建时,一个内部属性
[[Environment]]
被保存在函数foo
上,它用于引用当前执行上下文中的词法环境,作用和之前[[Scope]]
(ES6中被删除)类似,因此,在函数foo
执行上下文被创建时,会得到该属性。这意味着在函数foo
的词法环境中没有找到的变量会顺着该属性继续寻找,函数在定义时就保存了该属性,这决定了JavaScript只能是词法作用域。3. this
全局执行上下文中的this引用的是全局对象,在浏览器中指向window对象。
函数执行上下文的this值,取决于函数的调用方式。例如常见的调用方式:以对象方法的形式调用,会使this的值被设置为该对象;以函数的形式调用会this值被设置为window对象(浏览器中)或者undefined。
但关于this的细节远不于此,感兴趣的可以阅读ECMAScript规范中文中关于this值是如何确定的。
我们需要记住的在各种环境中this的取值:
上面的foo函数完整的执行上下文大概长这样:
综上,我们解释了词法环境中的三个属性,现在通过一个例子来巩固一下函数整个执行过程吧。
例
为了减少创建上下文的复杂度,以及未来声明变量的最佳实践,例子中只用let、const声明变量,这意味着执行上下文中只存在一个词法环境(LexicalEnvironment)组件(前面说过,只有var声明会储存在变量环境组件中),代码默认在浏览器环境中执行。
分析以上代码:
[[Environment]]
被保存在函数foo
上执行栈情况:
[[Environment]]
被保存在函数bar
上执行栈情况:
a + b + c
,自己词法环境中没有变量a,于是顺着外部环境引用outer找到foo的词法环境,发现也没有,最终在全局环境中找到了该变量,值为2;同理,在foo的词法环境中找到了b,值为3;在自己的词法环境中找到了c,值为5,最终返回结果:10。执行栈情况:
执行栈情况:
执行栈情况:
至此,代码全部执行完毕
总结
ES6的出现,重写了某些概念,比如:变量对象、作用域链等,在文中都没有提到。如果细心观察,和以前的概念对比你会发现,其实,变量对象大概就是词法环境中的变量记录器,也就是环境记录,作用域链大概就是外部环境记录outer。而和以前的区别主要是关于变量的声明方式,let、const和var进行了区别对待。
所以,这位大佬的JavaScript深入之执行上下文可以继续食用,并且更易于理解。
The text was updated successfully, but these errors were encountered: