执行上下文与执行上下文栈
函数环境
总结
参考
1.执行上下文与执行上下文栈
(1)什么是执行上下文?
在 JavaScript 代码运行时,解释执行全局代码、调用函数或使用 eval 函数执行一个字符串表达式都会创建并进入一个新的执行环境,而这个执行环境被称之为执行上下文。因此执行上下文有三类:全局执行上下文、函数执行上下文、eval 函数执行上下文。
执行上下文可以理解为一个抽象的对象,如下图:
Variable object:变量对象,用于存储被定义在执行上下文中的变量 (variables) 和函数声明 (function declarations) 。
Scope chain:作用域链,是一个对象列表 (list of objects) ,用以检索上下文代码中出现的标识符 (identifiers) 。
thisValue:this 指针,是一个与执行上下文相关的特殊对象,也被称之为上下文对象。
(2)什么是执行上下文栈?
在全局代码中调用函数,或函数中调用函数(如递归)等,都会涉及到在一个执行上下文中创建另一个新的执行上下文,并且等待这个新的上下文执行完毕,才会返回之前的执行上下文接着继续执行,而这样的调用方式就形成了执行上下文栈。
示例代码:
function A() { console.log('function A') B() } function B() { console.log('function B') C() } function C() { console.log('function C') } A()上述示例代码,当执行到函数 C时,此时的执行上下文栈如下图:
2.this
首先需要清楚,this 是执行上下文的一个属性,而不是某个变量对象的属性,是一个与执行上下文相关的特殊对象。由于在开发中不推荐或应尽量避免使用 eval 函数,所以在这里我们主要讨论全局执行上下文(全局环境)和函数执行上下文(函数环境)中的 this。
(1)全局环境
无论是否在严格模式下,在全局环境中(在任何函数体外部的代码),this 始终指向全局对象(在浏览器中即 window)。
示例代码(浏览器中):
console.log(this === window) // true a = 1; console.log(window.a) // 1 console.log(this.a === window.a) // true this.b = "test" console.log(window.b) // test console.log(b) //test(2)函数环境
在大多数情况下,函数的调用方式决定了 this 的值。 this 是不能够在执行期间被赋值修改的,并且在每次函数被调用时其 this 可能不同(通过 apply 或 call 方法显示设置 this 等)。
另外,ES5 引入了 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES6 引入了支持 this 词法解析的(它在闭合的执行环境内设置 this 的值)。
接下来我们主要分析:函数的调用方式是如何决定 this 的值?(对于 bind 方法以及箭头函数将留于下一篇文章进行详细分析)
要弄明白这个问题,我们来看看 EcmaScript 5.1标准的规定,了解一下 的规范:
11.2.3 函数调用
产生式 CallExpression : MemberExpression Arguments 按照下面的过程执行 :
令 ref 为解释执行 MemberExpression 的结果 .
令 func 为 GetValue(ref).
令 argList 为解释执行 Arguments 的结果 , 产生参数值们的内部列表 (see 11.2.4).
如果 Type(func) is not Object ,抛出一个 TypeError 异常 .
如果 IsCallable(func) is false ,抛出一个 TypeError 异常 .
如果 Type(ref) 为 Reference,那么 如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 否则 , ref 的基值是一个环境记录项 , 令 thisValue 为 GetBase(ref).ImplicitThisValue().
否则 , 假如 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
返回调用 func 的 [[Call]] 内置方法的结果 , 传入 thisValue 作为 this 值和列表 argList 作为参数列表
产生式 CallExpression : CallExpression Arguments以完全相同的方式执行,除了第1步执行的是其中的CallExpression。
简单解析:
第1步,令 ref 为 MemberExpression 解释执行的结果。
在 11.2 左值表达式 中有提到,MemberExpression 可以是以下五种表达式中的任意一种:
PrimaryExpression // 原始表达式
FunctionExpression // 函数定义表达式
MemberExpression [ Expression ] // 属性访问表达式
MemberExpression . IdentifierName // 属性访问表达式
new MemberExpression Arguments // 对象创建表达式
简单理解 MemberExpression 就是调用一个函数的()左侧的部分。
第2~5步,获取调用函数的参数列表以及检测所调用的函数是否合法,否则抛出相应异常。