您现在的位置是:网站首页 > 执行上下文与变量对象文章详情

执行上下文与变量对象

执行上下文的概念

执行上下文是JavaScript代码执行时的环境,它决定了变量、函数以及this的访问权限。每当函数被调用时,一个新的执行上下文就会被创建并推入调用栈顶部。全局代码也有对应的执行上下文,称为全局执行上下文。

function outer() {
  console.log('outer function');
  function inner() {
    console.log('inner function');
  }
  inner();
}
outer();

在这个例子中,当调用outer()时,会创建outer的执行上下文;当outer内部调用inner()时,又会创建inner的执行上下文。这些上下文按照调用顺序被压入调用栈,执行完毕后从栈顶弹出。

执行上下文的生命周期

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

  1. 创建阶段
  2. 执行阶段
  3. 回收阶段

在创建阶段,JavaScript引擎会做以下几件事:

  • 创建变量对象(VO)
  • 建立作用域链
  • 确定this的指向
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
}
foo(1);

在foo函数的执行上下文创建阶段,变量对象会被初始化为:

VO = {
  arguments: {
    0: 1,
    length: 1
  },
  a: 1,
  b: undefined,
  c: pointer to function c(),
  d: undefined
}

变量对象的演变

变量对象(Variable Object, VO)是执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。在全局上下文中,变量对象就是全局对象本身(浏览器中是window)。

对于函数上下文,变量对象最初只包含Arguments对象。随着代码执行,变量对象会逐步填充:

  1. 函数的所有形参(如果是函数上下文)
  2. 函数声明
  3. 变量声明
function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
test(1); 

对应的变量对象在创建阶段:

VO = {
  arguments: {
    0: 1,
    1: undefined,
    length: 2
  },
  a: 1,
  b: undefined,
  d: <reference to function>,
  c: undefined,
  e: undefined
}

注意函数表达式_x和立即执行函数x不会出现在VO中。

活动对象的概念

当进入函数执行阶段,变量对象会变为活动对象(Activation Object, AO)。此时原先在变量对象中的属性可以被访问了。

function foo(x) {
  var y = 20;
  function bar() {}
  (function baz() {});
}
foo(10);

在foo的执行上下文中:

  • 创建阶段:VO包含x, y(undefined), bar
  • 执行阶段:AO包含x=10, y=20, bar

作用域链的构建

作用域链是当前执行上下文的变量对象加上所有父执行上下文的变量对象。它用于变量查找:

var a = 10;
function outer() {
  var b = 20;
  function inner() {
    var c = 30;
    console.log(a + b + c);
  }
  inner();
}
outer();

inner的作用域链为:

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

查找变量时按照这个顺序向上查找。

变量提升的机制

变量提升实际上是执行上下文创建阶段对变量对象的填充过程:

console.log(a); // undefined
var a = 1;
console.log(a); // 1

foo(); // "foo"
function foo() {
  console.log("foo");
}

bar(); // TypeError
var bar = function() {
  console.log("bar");
};

这是因为:

  • 变量声明在创建阶段被加入VO,初始化为undefined
  • 函数声明在创建阶段被完整加入VO
  • 函数表达式按变量声明处理

this的绑定规则

this的绑定发生在执行上下文创建阶段:

  1. 默认绑定:独立函数调用,this指向全局对象(严格模式为undefined)
  2. 隐式绑定:作为方法调用,this指向调用对象
  3. 显式绑定:通过call/apply/bind指定this
  4. new绑定:构造函数调用,this指向新创建的对象
// 默认绑定
function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2

// 隐式绑定
var obj = {
  a: 3,
  foo: foo
};
obj.foo(); // 3

// 显式绑定
foo.call({a: 4}); // 4

// new绑定
function Bar(a) {
  this.a = a;
}
var bar = new Bar(5);
console.log(bar.a); // 5

闭包与执行上下文

闭包是函数和其周围状态(词法环境)的组合。从执行上下文角度看,闭包保留了它被创建时的作用域链:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

createCounter执行完毕后,其执行上下文从栈中弹出,但由于返回的函数引用了count变量,createCounter的变量对象不会被销毁,形成了闭包。

块级作用域的实现

ES6引入的let/const通过词法环境和变量环境的概念实现了块级作用域:

function blockScope() {
  var a = 1;
  let b = 2;
  {
    var a = 3; // 覆盖外层的a
    let b = 4; // 新的块级绑定
    console.log(a, b); // 3, 4
  }
  console.log(a, b); // 3, 2
}
blockScope();

在创建阶段:

  • 函数作用域的变量(var)被放入变量环境
  • 块级作用域的变量(let/const)被放入词法环境

执行时,引擎会先查找词法环境,再查找变量环境。

执行上下文与事件循环

异步回调的执行会创建新的执行上下文:

console.log('Start');

setTimeout(function timeout() {
  console.log('Timeout');
}, 0);

Promise.resolve().then(function promise() {
  console.log('Promise');
});

console.log('End');

执行顺序:

  1. 全局上下文创建并执行
  2. 微任务(Promise)回调创建新上下文执行
  3. 宏任务(setTimeout)回调创建新上下文执行

每个异步回调都有自己独立的执行上下文,按照事件循环的规则依次执行。

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步