执行上下文与变量对象
在JavaScript中,执行上下文和变量对象是理解代码运行机制的核心概念。它们决定了变量和函数的可访问性、作用域链的形成以及代码的执行顺序。
执行上下文的概念
执行上下文是JavaScript代码执行时的环境,包含了变量、函数声明、作用域链等信息。每当函数被调用时,一个新的执行上下文就会被创建。执行上下文分为三种类型:
- 全局执行上下文:代码首次运行时创建,只有一个全局上下文。
- 函数执行上下文:每次函数调用时创建。
- eval执行上下文:在
eval
函数内部代码执行时创建(较少使用)。
function foo() {
console.log('函数执行上下文');
}
foo(); // 调用函数时创建新的执行上下文
执行上下文的生命周期
执行上下文的生命周期分为两个阶段:
-
创建阶段:
- 创建变量对象(VO)
- 建立作用域链
- 确定
this
指向
-
执行阶段:
- 变量赋值
- 函数引用
- 执行其他代码
function bar(a) {
var b = 2;
function c() {}
var d = function() {};
}
bar(1);
在bar
函数的创建阶段:
- 参数
a
被添加到变量对象 - 函数声明
c
被提升 - 变量
b
和d
被声明(但未赋值)
变量对象(VO)与活动对象(AO)
变量对象是与执行上下文相关的数据作用域,存储了上下文中定义的变量和函数声明。
- 全局上下文中的变量对象:就是全局对象(浏览器中是
window
) - 函数上下文中的变量对象:称为活动对象(AO),除了变量和函数还包含
arguments
function example(x, y) {
var z = 30;
function inner() {}
console.log(arguments); // Arguments对象
}
example(10, 20);
在这个例子中,活动对象包含:
arguments
: {0:10, 1:20, length:2}x
: 10y
: 20z
: undefined (创建阶段)inner
: 指向函数
变量提升的实质
变量提升实际上是执行上下文创建阶段处理变量和函数声明的结果:
console.log(a); // undefined
var a = 1;
console.log(b()); // "hello"
function b() {
return 'hello';
}
实际执行顺序相当于:
function b() {
return 'hello';
}
var a;
console.log(a);
a = 1;
console.log(b());
作用域链的形成
当查找变量时,会从当前上下文的变量对象开始,沿着作用域链向上查找:
var globalVar = 'global';
function outer() {
var outerVar = 'outer';
function inner() {
var innerVar = 'inner';
console.log(innerVar); // 'inner'
console.log(outerVar); // 'outer'
console.log(globalVar); // 'global'
}
inner();
}
outer();
作用域链:
inner
的AOouter
的AO- 全局VO
let/const与变量对象
使用let
和const
声明的变量具有块级作用域,不会像var
那样被添加到变量对象:
function blockExample() {
console.log(a); // ReferenceError
let a = 1;
{
let b = 2;
console.log(b); // 2
}
console.log(b); // ReferenceError
}
闭包与变量对象
闭包能够访问外部函数变量对象的特性:
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
get: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.get()); // 1
即使createCounter
执行完毕,其变量对象仍然被闭包引用而不会被销毁。
执行上下文栈
JavaScript引擎使用执行上下文栈(调用栈)来管理执行上下文:
function first() {
console.log('进入first');
second();
console.log('离开first');
}
function second() {
console.log('进入second');
third();
console.log('离开second');
}
function third() {
console.log('进入third');
console.log('离开third');
}
first();
执行栈的变化:
- 全局上下文入栈
first()
入栈second()
入栈third()
入栈third()
出栈second()
出栈first()
出栈
变量对象的特殊行为
某些情况下变量对象的行为值得注意:
(function() {
console.log(typeof foo); // "function"
console.log(typeof bar); // "undefined"
var foo = 'hello';
function foo() {}
var bar = function() {};
console.log(typeof foo); // "string"
console.log(typeof bar); // "function"
})();
这是因为:
- 函数声明优先于变量声明
- 变量赋值发生在执行阶段
严格模式下的变化
严格模式对变量对象有特殊影响:
'use strict';
function strictExample() {
undeclaredVar = 1; // ReferenceError
console.log(arguments.callee); // TypeError
}
strictExample();
在严格模式下:
- 未声明的变量赋值会报错
arguments
对象变得不可修改
变量对象与内存管理
理解变量对象对内存管理很重要:
function createHugeArray() {
var arr = new Array(1000000).fill('data');
return function() {
console.log(arr[0]);
};
}
var leak = createHugeArray();
// 即使函数执行完毕,arr仍然被闭包引用无法回收
动态作用域与变量对象
虽然JavaScript是词法作用域,但this
提供了类似动态作用域的特性:
var obj = {
prop: 'value',
method: function() {
console.log(this.prop);
}
};
obj.method(); // "value"
var fn = obj.method;
fn(); // undefined (非严格模式)
变量对象的扩展
ES6引入了一些扩展变量对象概念的特性:
function spreadExample(...args) {
let [first, ...rest] = args;
const PI = 3.14;
class Inner {}
console.log(args.length);
}
spreadExample(1, 2, 3);
这些新语法结构都会影响变量对象的组成。
调试中的执行上下文
开发者工具可以观察执行上下文:
function debugExample() {
debugger;
var a = 1;
let b = 2;
}
debugExample();
在调试器中可以看到:
- 当前作用域的变量
- 闭包中的变量
- 全局变量
执行上下文与事件循环
执行上下文与事件循环机制密切相关:
console.log('开始');
setTimeout(function() {
console.log('定时器');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('结束');
输出顺序:
- "开始"
- "结束"
- "Promise"
- "定时器"
这是因为不同的任务会创建不同的执行上下文并进入不同的队列。
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn