boxboxs
技术

执行上下文作用域和作用域链

浏览 51最近编辑于
执行上下文作用域和作用域链

执行上下文

执行上下文是javascript代码执行时的运行环境。js在执行全局代码、函数代码、eval代码时,都会创建对应的执行上下文。

执行上下文的类型:全局上下文、函数上下文、eval上下文

作用域

作用域是变量、函数、参数的可访问范围。他决定了“某个标识符在什么地方可以被访问到”,作用域是词法作用域,在一个变量或者函数定义时他的可访问范围已经确定了,而不是在调用时决定的。

var a = 1;

function foo() {
  console.log(a);
}

function bar() {
  var a = 2;
  foo();
}

bar();

foo定义在全局,他的outer(下文中将会提到)是global,所以只能找到global中的var a = 1,所以输出1。很多人下意识忽略了这一点,用“动态”的思想认为:定义a、foo、bar,执行bar,定义a,执行foo,自身没有a,所以往外层的找到var a = 2,输出2。这是错误的想法,谨记JavaScript中的作用域是词法作用域

作用域链

查找变量的一条链式路径

当代码访问某个变量时,会先在当前作用域找:

  • 当前有,就直接用
  • 当前没有,就去上一层作用域
  • 还没有,就继续往外层找
  • 一直到全局作用域
  • 如果全局也没有,就报错

执行上下文作用域作用域链的关系

执行上下文是在代码执行时创建的,但是作用域是词法作用域,在代码定义时就已经确定,执行上下文会用到作用域链来查找变量

变量对象和活动对象(VO/AO)

在执行上下文中,会创建一个变量对象,用于保存定义的变量和函数。函数执行上下文中,函数的活动对象就是变量对象

活动对象通常包含:argument、形参、函数声明、变量声明

function test(x,y){
  var a = 10;
  function fn(){}
}
test(1,2)

// 活动对象
{
  argument:{0:1, 1:2, length:2}
  x:1
  y:2
  a: undefined
  fn: function fn() {}
}

 Lexical Environment 和 Variable Environment

VO/AO是一个比较笼统的说法,或者是“过时”的说法,在执行上下文中只有用一个对象管理变量和函数,太过笼统,所以新的版本使用 Lexical Environment + Variable Environment 的说法

在ES6之后,引入了块级作用域(var 、let)它们有一个特性: “暂时性死区”。这样就会出现一个变量对象中,表现出两种特性

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

console.log(b) // ReferenceError
let b = 2

在同一个活动对象里a可以访问,b却报错,让人难以理解,应该分的更清楚才对。

Lexical Environment 和 Variable Environment特点

规范原文中:The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value. The value of the VariableEnvironment component never changes while the value of the LexicalEnvironment component may change during execution of code within an execution context.

大意是:Lexical Environment和Variable Environmental的初始值是相同的;在代码执行期间,Variable Environment永远不会改变,Lecival Environment可能会发生变化

如何理解?看代码

function do_something() {
    var a = 1;
    let b = 2;
    while (true) {
        var c = 3;
        let d = 4;
        console.log(b);
        break;
    }
}
do_something();

这个函数执行时的上下文中,Lexical Environment 和 Variable Environment分别是什么呢?

// 函数执行
VariableEnvironment = {
  EnvironmentRecrod: {
    a: 1
    c: 3
  }
  outer: global
}

LecicalEnvironment = {
  EnvironmentRecrod: {
    b: 2
  }
  outer: global
}

// 进入while
LecicalEnvironment = {
  EnvironmentRecrod: {
    d: 4
  }
  outer: LecicalEnvironment = {
    EnvironmentRecrod: {
      b: 2
    }
    outer: global
  } // 上一个词法环境
}

Variable Environment是管理var变量,var是函数作用域,在这个函数执行时就已经确认,所以他不会改变。

Lexical Environment是管理let、const变量,因为他们是块级的,在进入类似while、if、for等代码块时, Lexical Environment会动态的更新。

Variable Environment和Lexical Environment实现执行上下文对变量、函数、参数更加精细的管理。

this

在函数执行时,除了有函数上下文,还有一个this也是经常我们经常会遇到到概念。在上面中我们提到了Lecical Environment,其中有一个EnvironmentRecrod(环境记录)对象,是真正管理变量、函数的对象。在函数执行时他的this,就保存在EnvironmentRecrod中[[ThisValue]]这个内部属性中。

注意,在ES5以及更早的规范中,this和指向上下文有直接关系,作为Lecical Environment和Variable Environment并列的ThisBinding存在,但是最新规范中他们已经没有直接联系了,this的获取也只和函数的调用方式有关

评论 (0)

为了减少垃圾评论,请先使用 GitHub 登录后再发表评论。

使用 GitHub 登录评论

暂无评论,成为第一个评论者!