阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 执行上下文与变量对象

执行上下文与变量对象

作者:陈川 阅读数:29796人阅读 分类: JavaScript

在JavaScript中,执行上下文和变量对象是理解代码运行机制的核心概念。它们决定了变量和函数的可访问性、作用域链的形成以及代码的执行顺序。

执行上下文的概念

执行上下文是JavaScript代码执行时的环境,包含了变量、函数声明、作用域链等信息。每当函数被调用时,一个新的执行上下文就会被创建。执行上下文分为三种类型:

  1. 全局执行上下文:代码首次运行时创建,只有一个全局上下文。
  2. 函数执行上下文:每次函数调用时创建。
  3. eval执行上下文:在eval函数内部代码执行时创建(较少使用)。
function foo() {
  console.log('函数执行上下文');
}
foo(); // 调用函数时创建新的执行上下文

执行上下文的生命周期

执行上下文的生命周期分为两个阶段:

  1. 创建阶段

    • 创建变量对象(VO)
    • 建立作用域链
    • 确定this指向
  2. 执行阶段

    • 变量赋值
    • 函数引用
    • 执行其他代码
function bar(a) {
  var b = 2;
  function c() {}
  var d = function() {};
}
bar(1);

bar函数的创建阶段:

  • 参数a被添加到变量对象
  • 函数声明c被提升
  • 变量bd被声明(但未赋值)

变量对象(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: 10
  • y: 20
  • z: 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();

作用域链:

  1. inner的AO
  2. outer的AO
  3. 全局VO

let/const与变量对象

使用letconst声明的变量具有块级作用域,不会像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();

执行栈的变化:

  1. 全局上下文入栈
  2. first()入栈
  3. second()入栈
  4. third()入栈
  5. third()出栈
  6. second()出栈
  7. 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"
})();

这是因为:

  1. 函数声明优先于变量声明
  2. 变量赋值发生在执行阶段

严格模式下的变化

严格模式对变量对象有特殊影响:

'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('结束');

输出顺序:

  1. "开始"
  2. "结束"
  3. "Promise"
  4. "定时器"

这是因为不同的任务会创建不同的执行上下文并进入不同的队列。

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:this绑定规则

下一篇:垃圾回收机制

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌