1、变量对象(variable object)
原文:Every execution context has associated with it a variable object. Variables and functions declared in the source text are added as properties of the variable object. For function code, parameters are added as properties of the variable object.
简言之就是:每一个执行上下文都会分配一个变量对象(variable object),变量对象的属性由 变量(variable) 和 函数声明(function declaration) 构成。在函数上下文情况下,参数列表(parameter list)也会被加入到变量对象(variable object)中作为属性。变量对象与当前作用域息息相关。不同作用域的变量对象互不相同,它保存了当前作用域的所有函数和变量。
这里有一点特殊就是只有 函数声明(function declaration) 会被加入到变量对象中,而 **函数表达式(function expression)**则不会。看代码:
// 函数声明
function a(){}
console.log(typeof a); // "function"
// 函数表达式
var a = function _a(){};
console.log(typeof a); // "function"
console.log(typeof _a); // "undefined"
函数声明的方式下,a会被加入到变量对象中,故当前作用域能打印出 a。
函数表达式情况下,a作为变量会加入到变量对象中,_a作为函数表达式则不会加入,故 a 在当前作用域能被正确找到,_a则不会。
2、活动对象(activation object)
原文:When control enters an execution context for function code, an object called the activation object is created and associated with the execution context. The activation object is initialised with a property with name arguments and attributes { DontDelete }. The initial value of this property is the arguments object described below.
The activation object is then used as the variable object for the purposes of variable instantiation.
简言之:当函数被激活,那么一个活动对象(activation object)就会被创建并且分配给执行上下文。活动对象由特殊对象 arguments 初始化而成。随后,他被当做变量对象(variable object)用于变量初始化。
用代码来说明就是:
function a(name, age){
var gender = "male";
function b(){}
}
a(“k”,10);
a被调用时,在a的执行上下文会创建一个活动对象AO,并且被初始化为 AO = [arguments]。随后AO又被当做变量对象(variable object)VO进行变量初始化,此时 VO = [arguments].concat([name,age,gender,b])。
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
(1)静态作用域与动态作用域
因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
让我们认真看个例子就能明白之间的区别:
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar(); // 结果是 ???
假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
而引用《JavaScript权威指南》的回答就是:
JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
4、执行环境和作用域链(execution context and scope chain)
-
execution context
顾名思义 执行环境/执行上下文。在javascript中,执行环境可以抽象的理解为一个object,它由以下几个属性构成:executionContext:{ variable object:vars,functions,arguments, scope chain: variable object + all parents scopes thisValue: context object }
此外在js解释器运行阶段还会维护一个环境栈,当执行流进入一个函数时,函数的环境就会被压入环境栈,当函数执行完后会将其环境弹出,并将控制权返回前一个执行环境。环境栈的顶端始终是当前正在执行的环境。 !通俗来讲,就是: 当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
-
执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:
- 进入执行上下文(当进入执行上下文时,这时候还没有执行代码)
- 代码执行 (在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值)
-
-
scope chain
作用域链,它在解释器进入到一个执行环境时初始化完成并将其分配给当前执行环境。每个执行环境的作用域链由当前环境的变量对象及父级环境的作用域链构成。
作用域链具体是如何构建起来的呢,先上代码:function test(num){ var a = "2"; return a+num; } test(1);
- 执行流开始 初始化function test,test函数会维护一个私有属性 [[scope]],并使用当前环境的作用域链初始化,在这里就是 test.[[Scope]]=global scope.
- test函数执行,这时候会为test函数创建一个执行环境,然后通过复制函数的[[Scope]]属性构建起test函数的作用域链。此时 test.scopeChain = [test.[[Scope]]]
- test函数的活动对象被初始化,随后活动对象被当做变量对象用于初始化。即 test.variableObject = test.activationObject.contact[num,a] = [arguments].contact[num,a]
- test函数的变量对象被压入其作用域链,此时 test.scopeChain = [ test.variableObject, test.[[scope]]];
至此test的作用域链构建完成。