-
Notifications
You must be signed in to change notification settings - Fork 0
Description
执行上下文
Javascript 中代码的运行环境有三种:
- 全局执行的代码
- 函数级别的代码
eval中执行的代码
Javascript 的解释器在执行代码时,首先进入全局上下文,每次调用一个函数时将会创建一个新的执行上下文,该执行上下文会被添加到执行栈的顶部,浏览器总是运行位于执行栈顶部的当前上下文。一旦该函数的代码执行完成,当前的执行上下文将从栈顶被移除,并将控制权归还到之前的执行上下文。
在不同的执行上下文中,如果存在相同的变量名,将从顶部至底部优先级逐渐降低;当调用的变量不在当前上下文中时,也会向下查找。这个叫做作用域链。
执行上下文的创建过程
在调用一个函数时,一个新的执行上下文被创建,该创建过程分为两个阶段:
- 建立阶段(即调用一个函数时,但未真正执行内部具体代码)
- 执行阶段
在建立阶段,会在上下文中建立变量,函数,arguments 对象,参数;然后建立作用域链;最后确定 this 的值。我们可以将执行上下文看作一个对象:
executionContextObj = {
variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
this: {}
}在建立 variableObject 对象时,具体顺序为:
- 建立
arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值。 - 检查当前上下文中的函数声明。每找到一个函数声明,就在
variableObject下面用函数名建立一个属性,属性值就是该函数的引用。若该函数名已存在于variableObject中,那更新该引用。 - 检查当前上下文中的变量声明。每找到一个变量声明,就在
variableObject下面用变量名建立一个属性,属性值为undefined。若该变量名已存在于variableObjecet中,则直接跳过。 - 初始化作用域链
- 确定上下文中的
this的值。
这里需要注意的是,在建立阶段,函数声明会优先确定,且晚声明的函数会覆盖早声明的函数引用。之后才进行变量声明(值为 undefined),且若有同名函数已被声明,该变量声明会被跳过。
在执行阶段,会一行行的运行代码,给 variableObject 中的变量属性赋值。
举个栗子
function foo(i) {
var a = 'hello';
var b = function B() {};
function c() {};
}
foo(22);建立阶段:
fooExecutionContextObject = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: "pointer to function c()",
a: undefined,
b: undefined
},
scopeChain: {},
this: {}
}执行阶段:
fooExecutionContextObject = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: "pointer to function c()",
a: 'hello',
b: "pointer to function B()"
},
scopeChain: {},
this: {}
}局部变量作用域的提升
来看这样一个例子:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = 'hello';
var bar = function() {
return 'world';
};
function foo() {
return 'hello'
}
}());该匿名函数被直接执行,此时便有一个执行上下文被创建。可以看到 typeof 输出 foo 为一个函数的引用,而 bar 为 undefined。
了解了上下文创建阶段,我们就能理解为什么 foo 可以在定义前被调用了,因为在创建阶段 foo 便被加入到 variableObject 中且值为 foo 函数的引用。而 bar 在创建阶段是作为变量的,其值默认就是 undefined。
再一个例子:
var foo = 1;
(function foo() {
foo = 10;
console.log(foo);
}())
// ƒ foo() { foo = 10 ; console.log(foo) }这个例子中,foo 为非匿名函数,此时会创建一个辅助对象,并将该函数名作为属性,但这个名为 foo 的属性是只读的,所以后来的赋值并不会改变其属性值。最后也就打印了调用时的函数内容。